diff options
author | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 18:07:22 +0000 |
---|---|---|
committer | Daniel Baumann <daniel.baumann@progress-linux.org> | 2024-04-10 18:07:22 +0000 |
commit | c04dcc2e7d834218ef2d4194331e383402495ae1 (patch) | |
tree | 7333e38d10d75386e60f336b80c2443c1166031d /xbmc/pvr/addons/PVRClient.cpp | |
parent | Initial commit. (diff) | |
download | kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.tar.xz kodi-c04dcc2e7d834218ef2d4194331e383402495ae1.zip |
Adding upstream version 2:20.4+dfsg.upstream/2%20.4+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to 'xbmc/pvr/addons/PVRClient.cpp')
-rw-r--r-- | xbmc/pvr/addons/PVRClient.cpp | 2023 |
1 files changed, 2023 insertions, 0 deletions
diff --git a/xbmc/pvr/addons/PVRClient.cpp b/xbmc/pvr/addons/PVRClient.cpp new file mode 100644 index 0000000..2a09277 --- /dev/null +++ b/xbmc/pvr/addons/PVRClient.cpp @@ -0,0 +1,2023 @@ +/* + * Copyright (C) 2012-2018 Team Kodi + * This file is part of Kodi - https://kodi.tv + * + * SPDX-License-Identifier: GPL-2.0-or-later + * See LICENSES/README.md for more information. + */ + +#include "PVRClient.h" + +#include "ServiceBroker.h" +#include "addons/AddonManager.h" +#include "addons/binary-addons/AddonDll.h" +#include "cores/VideoPlayer/DVDDemuxers/DVDDemuxUtils.h" +#include "dialogs/GUIDialogKaiToast.h" //! @todo get rid of GUI in core +#include "events/EventLog.h" +#include "events/NotificationEvent.h" +#include "filesystem/SpecialProtocol.h" +#include "guilib/LocalizeStrings.h" +#include "pvr/PVRDatabase.h" +#include "pvr/PVRManager.h" +#include "pvr/PVRStreamProperties.h" +#include "pvr/addons/PVRClientMenuHooks.h" +#include "pvr/addons/PVRClients.h" +#include "pvr/channels/PVRChannel.h" +#include "pvr/channels/PVRChannelGroup.h" +#include "pvr/channels/PVRChannelGroupInternal.h" +#include "pvr/channels/PVRChannelGroupMember.h" +#include "pvr/channels/PVRChannelGroups.h" +#include "pvr/channels/PVRChannelGroupsContainer.h" +#include "pvr/epg/Epg.h" +#include "pvr/epg/EpgContainer.h" +#include "pvr/epg/EpgInfoTag.h" +#include "pvr/providers/PVRProvider.h" +#include "pvr/providers/PVRProviders.h" +#include "pvr/recordings/PVRRecording.h" +#include "pvr/recordings/PVRRecordings.h" +#include "pvr/timers/PVRTimerInfoTag.h" +#include "pvr/timers/PVRTimerType.h" +#include "pvr/timers/PVRTimers.h" +#include "settings/AdvancedSettings.h" +#include "settings/SettingsComponent.h" +#include "utils/StringUtils.h" +#include "utils/log.h" + +#include <map> +#include <memory> +#include <mutex> +#include <string> +#include <utility> + +extern "C" +{ +#include <libavcodec/avcodec.h> +} + +using namespace ADDON; + +namespace PVR +{ + +#define DEFAULT_INFO_STRING_VALUE "unknown" + +CPVRClient::CPVRClient(const ADDON::AddonInfoPtr& addonInfo, + ADDON::AddonInstanceId instanceId, + int clientId) + : IAddonInstanceHandler(ADDON_INSTANCE_PVR, addonInfo, instanceId), m_iClientId(clientId) +{ + // Create all interface parts independent to make API changes easier if + // something is added + m_ifc.pvr = new AddonInstance_PVR; + m_ifc.pvr->props = new AddonProperties_PVR(); + m_ifc.pvr->toKodi = new AddonToKodiFuncTable_PVR(); + m_ifc.pvr->toAddon = new KodiToAddonFuncTable_PVR(); + + ResetProperties(); +} + +CPVRClient::~CPVRClient() +{ + Destroy(); + + if (m_ifc.pvr) + { + delete m_ifc.pvr->props; + delete m_ifc.pvr->toKodi; + delete m_ifc.pvr->toAddon; + } + delete m_ifc.pvr; +} + +void CPVRClient::StopRunningInstance() +{ + // stop the pvr manager and stop and unload the running pvr addon. pvr manager will be restarted on demand. + CServiceBroker::GetPVRManager().Stop(); + CServiceBroker::GetPVRManager().Clients()->StopClient(m_iClientId, false); +} + +void CPVRClient::OnPreInstall() +{ + // note: this method is also called on update; thus stop and unload possibly running instance + StopRunningInstance(); +} + +void CPVRClient::OnPreUnInstall() +{ + StopRunningInstance(); +} + +void CPVRClient::ResetProperties() +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + + /* initialise members */ + m_strUserPath = CSpecialProtocol::TranslatePath(Profile()); + m_strClientPath = CSpecialProtocol::TranslatePath(Path()); + m_bReadyToUse = false; + m_bBlockAddonCalls = false; + m_iAddonCalls = 0; + m_allAddonCallsFinished.Set(); + m_connectionState = PVR_CONNECTION_STATE_UNKNOWN; + m_prevConnectionState = PVR_CONNECTION_STATE_UNKNOWN; + m_ignoreClient = false; + m_iPriority = 0; + m_bPriorityFetched = false; + m_strBackendVersion = DEFAULT_INFO_STRING_VALUE; + m_strConnectionString = DEFAULT_INFO_STRING_VALUE; + m_strBackendName = DEFAULT_INFO_STRING_VALUE; + m_strBackendHostname.clear(); + m_menuhooks.reset(); + m_timertypes.clear(); + m_clientCapabilities.clear(); + + m_ifc.pvr->props->strUserPath = m_strUserPath.c_str(); + m_ifc.pvr->props->strClientPath = m_strClientPath.c_str(); + m_ifc.pvr->props->iEpgMaxPastDays = + CServiceBroker::GetPVRManager().EpgContainer().GetPastDaysToDisplay(); + m_ifc.pvr->props->iEpgMaxFutureDays = + CServiceBroker::GetPVRManager().EpgContainer().GetFutureDaysToDisplay(); + + m_ifc.pvr->toKodi->kodiInstance = this; + m_ifc.pvr->toKodi->TransferEpgEntry = cb_transfer_epg_entry; + m_ifc.pvr->toKodi->TransferChannelEntry = cb_transfer_channel_entry; + m_ifc.pvr->toKodi->TransferProviderEntry = cb_transfer_provider_entry; + m_ifc.pvr->toKodi->TransferTimerEntry = cb_transfer_timer_entry; + m_ifc.pvr->toKodi->TransferRecordingEntry = cb_transfer_recording_entry; + m_ifc.pvr->toKodi->AddMenuHook = cb_add_menu_hook; + m_ifc.pvr->toKodi->RecordingNotification = cb_recording_notification; + m_ifc.pvr->toKodi->TriggerChannelUpdate = cb_trigger_channel_update; + m_ifc.pvr->toKodi->TriggerProvidersUpdate = cb_trigger_provider_update; + m_ifc.pvr->toKodi->TriggerChannelGroupsUpdate = cb_trigger_channel_groups_update; + m_ifc.pvr->toKodi->TriggerTimerUpdate = cb_trigger_timer_update; + m_ifc.pvr->toKodi->TriggerRecordingUpdate = cb_trigger_recording_update; + m_ifc.pvr->toKodi->TriggerEpgUpdate = cb_trigger_epg_update; + m_ifc.pvr->toKodi->FreeDemuxPacket = cb_free_demux_packet; + m_ifc.pvr->toKodi->AllocateDemuxPacket = cb_allocate_demux_packet; + m_ifc.pvr->toKodi->TransferChannelGroup = cb_transfer_channel_group; + m_ifc.pvr->toKodi->TransferChannelGroupMember = cb_transfer_channel_group_member; + m_ifc.pvr->toKodi->ConnectionStateChange = cb_connection_state_change; + m_ifc.pvr->toKodi->EpgEventStateChange = cb_epg_event_state_change; + m_ifc.pvr->toKodi->GetCodecByName = cb_get_codec_by_name; + + // Clear function addresses to have NULL if not set by addon + memset(m_ifc.pvr->toAddon, 0, sizeof(KodiToAddonFuncTable_PVR)); +} + +ADDON_STATUS CPVRClient::Create() +{ + ADDON_STATUS status(ADDON_STATUS_UNKNOWN); + + ResetProperties(); + + /* initialise the add-on */ + CLog::LogFC(LOGDEBUG, LOGPVR, "Creating PVR add-on instance '{}'", ID()); + + bool bReadyToUse = false; + if ((status = CreateInstance()) == ADDON_STATUS_OK) + bReadyToUse = GetAddonProperties(); + + CLog::LogFC(LOGDEBUG, LOGPVR, "Created PVR add-on instance '{}'. readytouse={} ", ID(), + bReadyToUse); + + m_bReadyToUse = bReadyToUse; + return status; +} + +void CPVRClient::Destroy() +{ + if (!m_bReadyToUse) + return; + + m_bReadyToUse = false; + + CLog::LogFC(LOGDEBUG, LOGPVR, "Destroying PVR add-on instance '{}'", ID()); + + m_bBlockAddonCalls = true; + m_allAddonCallsFinished.Wait(); + + DestroyInstance(); + + CLog::LogFC(LOGDEBUG, LOGPVR, "Destroyed PVR add-on instance '{}'", ID()); + + if (m_menuhooks) + m_menuhooks->Clear(); + + ResetProperties(); +} + +void CPVRClient::Stop() +{ + m_bBlockAddonCalls = true; + m_bPriorityFetched = false; +} + +void CPVRClient::Continue() +{ + m_bBlockAddonCalls = false; +} + +void CPVRClient::ReCreate() +{ + Destroy(); + Create(); +} + +bool CPVRClient::ReadyToUse() const +{ + return m_bReadyToUse; +} + +PVR_CONNECTION_STATE CPVRClient::GetConnectionState() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_connectionState; +} + +void CPVRClient::SetConnectionState(PVR_CONNECTION_STATE state) +{ + if (state == PVR_CONNECTION_STATE_CONNECTED) + { + // update properties - some will only be available after add-on is connected to backend + if (!GetAddonProperties()) + CLog::LogF(LOGERROR, "Error reading PVR client properties"); + } + else + { + if (!GetAddonNameStringProperties()) + CLog::LogF(LOGERROR, "Cannot read PVR client name string properties"); + } + + std::unique_lock<CCriticalSection> lock(m_critSection); + + m_prevConnectionState = m_connectionState; + m_connectionState = state; + + if (m_connectionState == PVR_CONNECTION_STATE_CONNECTED) + m_ignoreClient = false; + else if (m_connectionState == PVR_CONNECTION_STATE_CONNECTING && + m_prevConnectionState == PVR_CONNECTION_STATE_UNKNOWN) + m_ignoreClient = true; // ignore until connected +} + +PVR_CONNECTION_STATE CPVRClient::GetPreviousConnectionState() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_prevConnectionState; +} + +bool CPVRClient::IgnoreClient() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + return m_ignoreClient; +} + +bool CPVRClient::IsEnabled() const +{ + if (InstanceId() == ADDON_SINGLETON_INSTANCE_ID) + { + return !CServiceBroker::GetAddonMgr().IsAddonDisabled(ID()); + } + else + { + bool instanceEnabled{false}; + Addon()->ReloadSettings(InstanceId()); + Addon()->GetSettingBool(ADDON_SETTING_INSTANCE_ENABLED_VALUE, instanceEnabled, InstanceId()); + return instanceEnabled; + } +} + +int CPVRClient::GetID() const +{ + return m_iClientId; +} + +bool CPVRClient::GetAddonProperties() +{ + if (!GetAddonNameStringProperties()) + return false; + + PVR_ADDON_CAPABILITIES addonCapabilities = {}; + std::vector<std::shared_ptr<CPVRTimerType>> timerTypes; + + /* get the capabilities */ + PVR_ERROR retVal = DoAddonCall( + __func__, + [&addonCapabilities](const AddonInstance* addon) { + return addon->toAddon->GetCapabilities(addon, &addonCapabilities); + }, + true, false); + + if (retVal != PVR_ERROR_NO_ERROR) + return false; + + /* timer types */ + retVal = DoAddonCall( + __func__, + [this, &addonCapabilities, &timerTypes](const AddonInstance* addon) { + std::unique_ptr<PVR_TIMER_TYPE[]> types_array( + new PVR_TIMER_TYPE[PVR_ADDON_TIMERTYPE_ARRAY_SIZE]); + int size = PVR_ADDON_TIMERTYPE_ARRAY_SIZE; + + PVR_ERROR retval = addon->toAddon->GetTimerTypes(addon, types_array.get(), &size); + + if (retval == PVR_ERROR_NOT_IMPLEMENTED) + { + // begin compat section + CLog::LogF(LOGWARNING, + "Add-on {} does not support timer types. It will work, but not benefit from " + "the timer features introduced with PVR Addon API 2.0.0", + GetFriendlyName()); + + // Create standard timer types (mostly) matching the timer functionality available in Isengard. + // This is for migration only and does not make changes to the addons obsolete. Addons should + // work and benefit from some UI changes (e.g. some of the timer settings dialog enhancements), + // but all old problems/bugs due to static attributes and values will remain the same as in + // Isengard. Also, new features (like epg search) are not available to addons automatically. + // This code can be removed once all addons actually support the respective PVR Addon API version. + + size = 0; + // manual one time + memset(&types_array[size], 0, sizeof(types_array[size])); + types_array[size].iId = size + 1; + types_array[size].iAttributes = + PVR_TIMER_TYPE_IS_MANUAL | PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE | + PVR_TIMER_TYPE_SUPPORTS_CHANNELS | PVR_TIMER_TYPE_SUPPORTS_START_TIME | + PVR_TIMER_TYPE_SUPPORTS_END_TIME | PVR_TIMER_TYPE_SUPPORTS_PRIORITY | + PVR_TIMER_TYPE_SUPPORTS_LIFETIME | PVR_TIMER_TYPE_SUPPORTS_RECORDING_FOLDERS; + ++size; + + // manual timer rule + memset(&types_array[size], 0, sizeof(types_array[size])); + types_array[size].iId = size + 1; + types_array[size].iAttributes = + PVR_TIMER_TYPE_IS_MANUAL | PVR_TIMER_TYPE_IS_REPEATING | + PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE | PVR_TIMER_TYPE_SUPPORTS_CHANNELS | + PVR_TIMER_TYPE_SUPPORTS_START_TIME | PVR_TIMER_TYPE_SUPPORTS_END_TIME | + PVR_TIMER_TYPE_SUPPORTS_PRIORITY | PVR_TIMER_TYPE_SUPPORTS_LIFETIME | + PVR_TIMER_TYPE_SUPPORTS_FIRST_DAY | PVR_TIMER_TYPE_SUPPORTS_WEEKDAYS | + PVR_TIMER_TYPE_SUPPORTS_RECORDING_FOLDERS; + ++size; + + if (addonCapabilities.bSupportsEPG) + { + // One-shot epg-based + memset(&types_array[size], 0, sizeof(types_array[size])); + types_array[size].iId = size + 1; + types_array[size].iAttributes = + PVR_TIMER_TYPE_SUPPORTS_ENABLE_DISABLE | PVR_TIMER_TYPE_REQUIRES_EPG_TAG_ON_CREATE | + PVR_TIMER_TYPE_SUPPORTS_CHANNELS | PVR_TIMER_TYPE_SUPPORTS_START_TIME | + PVR_TIMER_TYPE_SUPPORTS_END_TIME | PVR_TIMER_TYPE_SUPPORTS_PRIORITY | + PVR_TIMER_TYPE_SUPPORTS_LIFETIME | PVR_TIMER_TYPE_SUPPORTS_RECORDING_FOLDERS; + ++size; + } + + retval = PVR_ERROR_NO_ERROR; + // end compat section + } + + if (retval == PVR_ERROR_NO_ERROR) + { + timerTypes.reserve(size); + for (int i = 0; i < size; ++i) + { + if (types_array[i].iId == PVR_TIMER_TYPE_NONE) + { + CLog::LogF(LOGERROR, "Invalid timer type supplied by add-on '{}'.", ID()); + continue; + } + timerTypes.emplace_back( + std::shared_ptr<CPVRTimerType>(new CPVRTimerType(types_array[i], m_iClientId))); + } + } + return retval; + }, + addonCapabilities.bSupportsTimers, false); + + if (retVal == PVR_ERROR_NOT_IMPLEMENTED) + retVal = PVR_ERROR_NO_ERROR; // timer support is optional. + + /* update the members */ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_clientCapabilities = addonCapabilities; + m_timertypes = timerTypes; + + return retVal == PVR_ERROR_NO_ERROR; +} + +bool CPVRClient::GetAddonNameStringProperties() +{ + char strBackendName[PVR_ADDON_NAME_STRING_LENGTH] = {}; + char strConnectionString[PVR_ADDON_NAME_STRING_LENGTH] = {}; + char strBackendVersion[PVR_ADDON_NAME_STRING_LENGTH] = {}; + char strBackendHostname[PVR_ADDON_NAME_STRING_LENGTH] = {}; + + /* get the name of the backend */ + PVR_ERROR retVal = DoAddonCall( + __func__, + [&strBackendName](const AddonInstance* addon) { + return addon->toAddon->GetBackendName(addon, strBackendName, sizeof(strBackendName)); + }, + true, false); + + if (retVal != PVR_ERROR_NO_ERROR) + return false; + + /* get the connection string */ + retVal = DoAddonCall( + __func__, + [&strConnectionString](const AddonInstance* addon) { + return addon->toAddon->GetConnectionString(addon, strConnectionString, + sizeof(strConnectionString)); + }, + true, false); + + if (retVal != PVR_ERROR_NO_ERROR && retVal != PVR_ERROR_NOT_IMPLEMENTED) + return false; + + /* backend version number */ + retVal = DoAddonCall( + __func__, + [&strBackendVersion](const AddonInstance* addon) { + return addon->toAddon->GetBackendVersion(addon, strBackendVersion, + sizeof(strBackendVersion)); + }, + true, false); + + if (retVal != PVR_ERROR_NO_ERROR) + return false; + + /* backend hostname */ + retVal = DoAddonCall( + __func__, + [&strBackendHostname](const AddonInstance* addon) { + return addon->toAddon->GetBackendHostname(addon, strBackendHostname, + sizeof(strBackendHostname)); + }, + true, false); + + if (retVal != PVR_ERROR_NO_ERROR && retVal != PVR_ERROR_NOT_IMPLEMENTED) + return false; + + /* update the members */ + std::unique_lock<CCriticalSection> lock(m_critSection); + m_strBackendName = strBackendName; + m_strConnectionString = strConnectionString; + m_strBackendVersion = strBackendVersion; + m_strBackendHostname = strBackendHostname; + + return true; +} + +const std::string& CPVRClient::GetBackendName() const +{ + return m_strBackendName; +} + +const std::string& CPVRClient::GetBackendVersion() const +{ + return m_strBackendVersion; +} + +const std::string& CPVRClient::GetBackendHostname() const +{ + return m_strBackendHostname; +} + +const std::string& CPVRClient::GetConnectionString() const +{ + return m_strConnectionString; +} + +const std::string CPVRClient::GetFriendlyName() const +{ + + if (Addon()->SupportsInstanceSettings()) + { + std::string instanceName; + Addon()->GetSettingString(ADDON_SETTING_INSTANCE_NAME_VALUE, instanceName, InstanceId()); + if (!instanceName.empty()) + return StringUtils::Format("{} ({})", Name(), instanceName); + } + return Name(); +} + +PVR_ERROR CPVRClient::GetDriveSpace(uint64_t& iTotal, uint64_t& iUsed) +{ + /* default to 0 in case of error */ + iTotal = 0; + iUsed = 0; + + return DoAddonCall(__func__, [&iTotal, &iUsed](const AddonInstance* addon) { + uint64_t iTotalSpace = 0; + uint64_t iUsedSpace = 0; + PVR_ERROR error = addon->toAddon->GetDriveSpace(addon, &iTotalSpace, &iUsedSpace); + if (error == PVR_ERROR_NO_ERROR) + { + iTotal = iTotalSpace; + iUsed = iUsedSpace; + } + return error; + }); +} + +PVR_ERROR CPVRClient::StartChannelScan() +{ + return DoAddonCall( + __func__, + [](const AddonInstance* addon) { return addon->toAddon->OpenDialogChannelScan(addon); }, + m_clientCapabilities.SupportsChannelScan()); +} + +PVR_ERROR CPVRClient::OpenDialogChannelAdd(const std::shared_ptr<CPVRChannel>& channel) +{ + return DoAddonCall( + __func__, + [channel](const AddonInstance* addon) { + PVR_CHANNEL addonChannel; + channel->FillAddonData(addonChannel); + return addon->toAddon->OpenDialogChannelAdd(addon, &addonChannel); + }, + m_clientCapabilities.SupportsChannelSettings()); +} + +PVR_ERROR CPVRClient::OpenDialogChannelSettings(const std::shared_ptr<CPVRChannel>& channel) +{ + return DoAddonCall( + __func__, + [channel](const AddonInstance* addon) { + PVR_CHANNEL addonChannel; + channel->FillAddonData(addonChannel); + return addon->toAddon->OpenDialogChannelSettings(addon, &addonChannel); + }, + m_clientCapabilities.SupportsChannelSettings()); +} + +PVR_ERROR CPVRClient::DeleteChannel(const std::shared_ptr<CPVRChannel>& channel) +{ + return DoAddonCall( + __func__, + [channel](const AddonInstance* addon) { + PVR_CHANNEL addonChannel; + channel->FillAddonData(addonChannel); + return addon->toAddon->DeleteChannel(addon, &addonChannel); + }, + m_clientCapabilities.SupportsChannelSettings()); +} + +PVR_ERROR CPVRClient::RenameChannel(const std::shared_ptr<CPVRChannel>& channel) +{ + return DoAddonCall( + __func__, + [channel](const AddonInstance* addon) { + PVR_CHANNEL addonChannel; + channel->FillAddonData(addonChannel); + strncpy(addonChannel.strChannelName, channel->ChannelName().c_str(), + sizeof(addonChannel.strChannelName) - 1); + return addon->toAddon->RenameChannel(addon, &addonChannel); + }, + m_clientCapabilities.SupportsChannelSettings()); +} + +PVR_ERROR CPVRClient::GetEPGForChannel(int iChannelUid, CPVREpg* epg, time_t start, time_t end) +{ + return DoAddonCall( + __func__, + [this, iChannelUid, epg, start, end](const AddonInstance* addon) { + PVR_HANDLE_STRUCT handle = {}; + handle.callerAddress = this; + handle.dataAddress = epg; + + int iPVRTimeCorrection = + CServiceBroker::GetSettingsComponent()->GetAdvancedSettings()->m_iPVRTimeCorrection; + + return addon->toAddon->GetEPGForChannel(addon, &handle, iChannelUid, + start ? start - iPVRTimeCorrection : 0, + end ? end - iPVRTimeCorrection : 0); + }, + m_clientCapabilities.SupportsEPG()); +} + +PVR_ERROR CPVRClient::SetEPGMaxPastDays(int iPastDays) +{ + return DoAddonCall( + __func__, + [iPastDays](const AddonInstance* addon) { + return addon->toAddon->SetEPGMaxPastDays(addon, iPastDays); + }, + m_clientCapabilities.SupportsEPG()); +} + +PVR_ERROR CPVRClient::SetEPGMaxFutureDays(int iFutureDays) +{ + return DoAddonCall( + __func__, + [iFutureDays](const AddonInstance* addon) { + return addon->toAddon->SetEPGMaxFutureDays(addon, iFutureDays); + }, + m_clientCapabilities.SupportsEPG()); +} + +// This class wraps an EPG_TAG (PVR Addon API struct) to ensure that the string members of +// that struct, which are const char pointers, stay valid until the EPG_TAG gets destructed. +// Please note that this struct is also used to transfer huge amount of EPG_TAGs from +// addon to Kodi. Thus, changing the struct to contain char arrays is not recommended, +// because this would lead to huge amount of string copies when transferring epg data +// from addon to Kodi. +class CAddonEpgTag : public EPG_TAG +{ +public: + CAddonEpgTag() = delete; + explicit CAddonEpgTag(const std::shared_ptr<const CPVREpgInfoTag>& kodiTag) + : m_strTitle(kodiTag->Title()), + m_strPlotOutline(kodiTag->PlotOutline()), + m_strPlot(kodiTag->Plot()), + m_strOriginalTitle(kodiTag->OriginalTitle()), + m_strCast(kodiTag->DeTokenize(kodiTag->Cast())), + m_strDirector(kodiTag->DeTokenize(kodiTag->Directors())), + m_strWriter(kodiTag->DeTokenize(kodiTag->Writers())), + m_strIMDBNumber(kodiTag->IMDBNumber()), + m_strEpisodeName(kodiTag->EpisodeName()), + m_strIconPath(kodiTag->ClientIconPath()), + m_strSeriesLink(kodiTag->SeriesLink()), + m_strGenreDescription(kodiTag->GenreDescription()), + m_strParentalRatingCode(kodiTag->ParentalRatingCode()) + { + time_t t; + kodiTag->StartAsUTC().GetAsTime(t); + startTime = t; + kodiTag->EndAsUTC().GetAsTime(t); + endTime = t; + + const CDateTime firstAired = kodiTag->FirstAired(); + if (firstAired.IsValid()) + m_strFirstAired = firstAired.GetAsW3CDate(); + + iUniqueBroadcastId = kodiTag->UniqueBroadcastID(); + iUniqueChannelId = kodiTag->UniqueChannelID(); + iParentalRating = kodiTag->ParentalRating(); + iSeriesNumber = kodiTag->SeriesNumber(); + iEpisodeNumber = kodiTag->EpisodeNumber(); + iEpisodePartNumber = kodiTag->EpisodePart(); + iStarRating = kodiTag->StarRating(); + iYear = kodiTag->Year(); + iFlags = kodiTag->Flags(); + iGenreType = kodiTag->GenreType(); + iGenreSubType = kodiTag->GenreSubType(); + strTitle = m_strTitle.c_str(); + strPlotOutline = m_strPlotOutline.c_str(); + strPlot = m_strPlot.c_str(); + strOriginalTitle = m_strOriginalTitle.c_str(); + strCast = m_strCast.c_str(); + strDirector = m_strDirector.c_str(); + strWriter = m_strWriter.c_str(); + strIMDBNumber = m_strIMDBNumber.c_str(); + strEpisodeName = m_strEpisodeName.c_str(); + strIconPath = m_strIconPath.c_str(); + strSeriesLink = m_strSeriesLink.c_str(); + strGenreDescription = m_strGenreDescription.c_str(); + strFirstAired = m_strFirstAired.c_str(); + strParentalRatingCode = m_strParentalRatingCode.c_str(); + } + + virtual ~CAddonEpgTag() = default; + +private: + std::string m_strTitle; + std::string m_strPlotOutline; + std::string m_strPlot; + std::string m_strOriginalTitle; + std::string m_strCast; + std::string m_strDirector; + std::string m_strWriter; + std::string m_strIMDBNumber; + std::string m_strEpisodeName; + std::string m_strIconPath; + std::string m_strSeriesLink; + std::string m_strGenreDescription; + std::string m_strFirstAired; + std::string m_strParentalRatingCode; +}; + +PVR_ERROR CPVRClient::IsRecordable(const std::shared_ptr<const CPVREpgInfoTag>& tag, + bool& bIsRecordable) const +{ + return DoAddonCall( + __func__, + [tag, &bIsRecordable](const AddonInstance* addon) { + CAddonEpgTag addonTag(tag); + return addon->toAddon->IsEPGTagRecordable(addon, &addonTag, &bIsRecordable); + }, + m_clientCapabilities.SupportsRecordings() && m_clientCapabilities.SupportsEPG()); +} + +PVR_ERROR CPVRClient::IsPlayable(const std::shared_ptr<const CPVREpgInfoTag>& tag, + bool& bIsPlayable) const +{ + return DoAddonCall( + __func__, + [tag, &bIsPlayable](const AddonInstance* addon) { + CAddonEpgTag addonTag(tag); + return addon->toAddon->IsEPGTagPlayable(addon, &addonTag, &bIsPlayable); + }, + m_clientCapabilities.SupportsEPG()); +} + +void CPVRClient::WriteStreamProperties(const PVR_NAMED_VALUE* properties, + unsigned int iPropertyCount, + CPVRStreamProperties& props) +{ + for (unsigned int i = 0; i < iPropertyCount; ++i) + { + props.emplace_back(std::make_pair(properties[i].strName, properties[i].strValue)); + } +} + +PVR_ERROR CPVRClient::GetEpgTagStreamProperties(const std::shared_ptr<CPVREpgInfoTag>& tag, + CPVRStreamProperties& props) +{ + return DoAddonCall(__func__, [&tag, &props](const AddonInstance* addon) { + CAddonEpgTag addonTag(tag); + + unsigned int iPropertyCount = STREAM_MAX_PROPERTY_COUNT; + std::unique_ptr<PVR_NAMED_VALUE[]> properties(new PVR_NAMED_VALUE[iPropertyCount]); + memset(properties.get(), 0, iPropertyCount * sizeof(PVR_NAMED_VALUE)); + + PVR_ERROR error = addon->toAddon->GetEPGTagStreamProperties(addon, &addonTag, properties.get(), + &iPropertyCount); + if (error == PVR_ERROR_NO_ERROR) + WriteStreamProperties(properties.get(), iPropertyCount, props); + + return error; + }); +} + +PVR_ERROR CPVRClient::GetEpgTagEdl(const std::shared_ptr<const CPVREpgInfoTag>& epgTag, + std::vector<PVR_EDL_ENTRY>& edls) +{ + edls.clear(); + return DoAddonCall( + __func__, + [&epgTag, &edls](const AddonInstance* addon) { + CAddonEpgTag addonTag(epgTag); + + PVR_EDL_ENTRY edl_array[PVR_ADDON_EDL_LENGTH]; + int size = PVR_ADDON_EDL_LENGTH; + PVR_ERROR error = addon->toAddon->GetEPGTagEdl(addon, &addonTag, edl_array, &size); + if (error == PVR_ERROR_NO_ERROR) + { + edls.reserve(size); + for (int i = 0; i < size; ++i) + edls.emplace_back(edl_array[i]); + } + return error; + }, + m_clientCapabilities.SupportsEpgTagEdl()); +} + +PVR_ERROR CPVRClient::GetChannelGroupsAmount(int& iGroups) +{ + iGroups = -1; + return DoAddonCall( + __func__, + [&iGroups](const AddonInstance* addon) { + return addon->toAddon->GetChannelGroupsAmount(addon, &iGroups); + }, + m_clientCapabilities.SupportsChannelGroups()); +} + +PVR_ERROR CPVRClient::GetChannelGroups(CPVRChannelGroups* groups) +{ + return DoAddonCall(__func__, + [this, groups](const AddonInstance* addon) { + PVR_HANDLE_STRUCT handle = {}; + handle.callerAddress = this; + handle.dataAddress = groups; + return addon->toAddon->GetChannelGroups(addon, &handle, groups->IsRadio()); + }, + m_clientCapabilities.SupportsChannelGroups()); +} + +PVR_ERROR CPVRClient::GetChannelGroupMembers( + CPVRChannelGroup* group, std::vector<std::shared_ptr<CPVRChannelGroupMember>>& groupMembers) +{ + return DoAddonCall(__func__, + [this, group, &groupMembers](const AddonInstance* addon) { + PVR_HANDLE_STRUCT handle = {}; + handle.callerAddress = this; + handle.dataAddress = &groupMembers; + + PVR_CHANNEL_GROUP tag; + group->FillAddonData(tag); + return addon->toAddon->GetChannelGroupMembers(addon, &handle, &tag); + }, + m_clientCapabilities.SupportsChannelGroups()); +} + +PVR_ERROR CPVRClient::GetProvidersAmount(int& iProviders) +{ + iProviders = -1; + return DoAddonCall(__func__, [&iProviders](const AddonInstance* addon) { + return addon->toAddon->GetProvidersAmount(addon, &iProviders); + }); +} + +PVR_ERROR CPVRClient::GetProviders(CPVRProvidersContainer& providers) +{ + return DoAddonCall(__func__, + [this, &providers](const AddonInstance* addon) { + PVR_HANDLE_STRUCT handle = {}; + handle.callerAddress = this; + handle.dataAddress = &providers; + return addon->toAddon->GetProviders(addon, &handle); + }, + m_clientCapabilities.SupportsProviders()); +} + +PVR_ERROR CPVRClient::GetChannelsAmount(int& iChannels) +{ + iChannels = -1; + return DoAddonCall(__func__, [&iChannels](const AddonInstance* addon) { + return addon->toAddon->GetChannelsAmount(addon, &iChannels); + }); +} + +PVR_ERROR CPVRClient::GetChannels(bool radio, std::vector<std::shared_ptr<CPVRChannel>>& channels) +{ + return DoAddonCall(__func__, + [this, radio, &channels](const AddonInstance* addon) { + PVR_HANDLE_STRUCT handle = {}; + handle.callerAddress = this; + handle.dataAddress = &channels; + return addon->toAddon->GetChannels(addon, &handle, radio); + }, + (radio && m_clientCapabilities.SupportsRadio()) || + (!radio && m_clientCapabilities.SupportsTV())); +} + +PVR_ERROR CPVRClient::GetRecordingsAmount(bool deleted, int& iRecordings) +{ + iRecordings = -1; + return DoAddonCall( + __func__, + [deleted, &iRecordings](const AddonInstance* addon) { + return addon->toAddon->GetRecordingsAmount(addon, deleted, &iRecordings); + }, + m_clientCapabilities.SupportsRecordings() && + (!deleted || m_clientCapabilities.SupportsRecordingsUndelete())); +} + +PVR_ERROR CPVRClient::GetRecordings(CPVRRecordings* results, bool deleted) +{ + return DoAddonCall(__func__, + [this, results, deleted](const AddonInstance* addon) { + PVR_HANDLE_STRUCT handle = {}; + handle.callerAddress = this; + handle.dataAddress = results; + return addon->toAddon->GetRecordings(addon, &handle, deleted); + }, + m_clientCapabilities.SupportsRecordings() && + (!deleted || m_clientCapabilities.SupportsRecordingsUndelete())); +} + +PVR_ERROR CPVRClient::DeleteRecording(const CPVRRecording& recording) +{ + return DoAddonCall( + __func__, + [&recording](const AddonInstance* addon) { + PVR_RECORDING tag; + recording.FillAddonData(tag); + return addon->toAddon->DeleteRecording(addon, &tag); + }, + m_clientCapabilities.SupportsRecordings() && m_clientCapabilities.SupportsRecordingsDelete()); +} + +PVR_ERROR CPVRClient::UndeleteRecording(const CPVRRecording& recording) +{ + return DoAddonCall( + __func__, + [&recording](const AddonInstance* addon) { + PVR_RECORDING tag; + recording.FillAddonData(tag); + return addon->toAddon->UndeleteRecording(addon, &tag); + }, + m_clientCapabilities.SupportsRecordingsUndelete()); +} + +PVR_ERROR CPVRClient::DeleteAllRecordingsFromTrash() +{ + return DoAddonCall( + __func__, + [](const AddonInstance* addon) { + return addon->toAddon->DeleteAllRecordingsFromTrash(addon); + }, + m_clientCapabilities.SupportsRecordingsUndelete()); +} + +PVR_ERROR CPVRClient::RenameRecording(const CPVRRecording& recording) +{ + return DoAddonCall( + __func__, + [&recording](const AddonInstance* addon) { + PVR_RECORDING tag; + recording.FillAddonData(tag); + return addon->toAddon->RenameRecording(addon, &tag); + }, + m_clientCapabilities.SupportsRecordings()); +} + +PVR_ERROR CPVRClient::SetRecordingLifetime(const CPVRRecording& recording) +{ + return DoAddonCall( + __func__, + [&recording](const AddonInstance* addon) { + PVR_RECORDING tag; + recording.FillAddonData(tag); + return addon->toAddon->SetRecordingLifetime(addon, &tag); + }, + m_clientCapabilities.SupportsRecordingsLifetimeChange()); +} + +PVR_ERROR CPVRClient::SetRecordingPlayCount(const CPVRRecording& recording, int count) +{ + return DoAddonCall( + __func__, + [&recording, count](const AddonInstance* addon) { + PVR_RECORDING tag; + recording.FillAddonData(tag); + return addon->toAddon->SetRecordingPlayCount(addon, &tag, count); + }, + m_clientCapabilities.SupportsRecordingsPlayCount()); +} + +PVR_ERROR CPVRClient::SetRecordingLastPlayedPosition(const CPVRRecording& recording, + int lastplayedposition) +{ + return DoAddonCall( + __func__, + [&recording, lastplayedposition](const AddonInstance* addon) { + PVR_RECORDING tag; + recording.FillAddonData(tag); + return addon->toAddon->SetRecordingLastPlayedPosition(addon, &tag, lastplayedposition); + }, + m_clientCapabilities.SupportsRecordingsLastPlayedPosition()); +} + +PVR_ERROR CPVRClient::GetRecordingLastPlayedPosition(const CPVRRecording& recording, int& iPosition) +{ + iPosition = -1; + return DoAddonCall( + __func__, + [&recording, &iPosition](const AddonInstance* addon) { + PVR_RECORDING tag; + recording.FillAddonData(tag); + return addon->toAddon->GetRecordingLastPlayedPosition(addon, &tag, &iPosition); + }, + m_clientCapabilities.SupportsRecordingsLastPlayedPosition()); +} + +PVR_ERROR CPVRClient::GetRecordingEdl(const CPVRRecording& recording, + std::vector<PVR_EDL_ENTRY>& edls) +{ + edls.clear(); + return DoAddonCall( + __func__, + [&recording, &edls](const AddonInstance* addon) { + PVR_RECORDING tag; + recording.FillAddonData(tag); + + PVR_EDL_ENTRY edl_array[PVR_ADDON_EDL_LENGTH]; + int size = PVR_ADDON_EDL_LENGTH; + PVR_ERROR error = addon->toAddon->GetRecordingEdl(addon, &tag, edl_array, &size); + if (error == PVR_ERROR_NO_ERROR) + { + edls.reserve(size); + for (int i = 0; i < size; ++i) + edls.emplace_back(edl_array[i]); + } + return error; + }, + m_clientCapabilities.SupportsRecordingsEdl()); +} + +PVR_ERROR CPVRClient::GetRecordingSize(const CPVRRecording& recording, int64_t& sizeInBytes) +{ + return DoAddonCall( + __func__, + [&recording, &sizeInBytes](const AddonInstance* addon) { + PVR_RECORDING tag; + recording.FillAddonData(tag); + return addon->toAddon->GetRecordingSize(addon, &tag, &sizeInBytes); + }, + m_clientCapabilities.SupportsRecordingsSize()); +} + +PVR_ERROR CPVRClient::GetTimersAmount(int& iTimers) +{ + iTimers = -1; + return DoAddonCall( + __func__, + [&iTimers](const AddonInstance* addon) { + return addon->toAddon->GetTimersAmount(addon, &iTimers); + }, + m_clientCapabilities.SupportsTimers()); +} + +PVR_ERROR CPVRClient::GetTimers(CPVRTimersContainer* results) +{ + return DoAddonCall(__func__, + [this, results](const AddonInstance* addon) { + PVR_HANDLE_STRUCT handle = {}; + handle.callerAddress = this; + handle.dataAddress = results; + return addon->toAddon->GetTimers(addon, &handle); + }, + m_clientCapabilities.SupportsTimers()); +} + +PVR_ERROR CPVRClient::AddTimer(const CPVRTimerInfoTag& timer) +{ + return DoAddonCall( + __func__, + [&timer](const AddonInstance* addon) { + PVR_TIMER tag; + timer.FillAddonData(tag); + return addon->toAddon->AddTimer(addon, &tag); + }, + m_clientCapabilities.SupportsTimers()); +} + +PVR_ERROR CPVRClient::DeleteTimer(const CPVRTimerInfoTag& timer, bool bForce /* = false */) +{ + return DoAddonCall( + __func__, + [&timer, bForce](const AddonInstance* addon) { + PVR_TIMER tag; + timer.FillAddonData(tag); + return addon->toAddon->DeleteTimer(addon, &tag, bForce); + }, + m_clientCapabilities.SupportsTimers()); +} + +PVR_ERROR CPVRClient::UpdateTimer(const CPVRTimerInfoTag& timer) +{ + return DoAddonCall( + __func__, + [&timer](const AddonInstance* addon) { + PVR_TIMER tag; + timer.FillAddonData(tag); + return addon->toAddon->UpdateTimer(addon, &tag); + }, + m_clientCapabilities.SupportsTimers()); +} + +PVR_ERROR CPVRClient::GetTimerTypes(std::vector<std::shared_ptr<CPVRTimerType>>& results) const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + results = m_timertypes; + return PVR_ERROR_NO_ERROR; +} + +PVR_ERROR CPVRClient::GetStreamReadChunkSize(int& iChunkSize) +{ + return DoAddonCall( + __func__, + [&iChunkSize](const AddonInstance* addon) { + return addon->toAddon->GetStreamReadChunkSize(addon, &iChunkSize); + }, + m_clientCapabilities.SupportsRecordings() || m_clientCapabilities.HandlesInputStream()); +} + +PVR_ERROR CPVRClient::ReadLiveStream(void* lpBuf, int64_t uiBufSize, int& iRead) +{ + iRead = -1; + return DoAddonCall(__func__, [&lpBuf, uiBufSize, &iRead](const AddonInstance* addon) { + iRead = addon->toAddon->ReadLiveStream(addon, static_cast<unsigned char*>(lpBuf), + static_cast<int>(uiBufSize)); + return (iRead == -1) ? PVR_ERROR_NOT_IMPLEMENTED : PVR_ERROR_NO_ERROR; + }); +} + +PVR_ERROR CPVRClient::ReadRecordedStream(void* lpBuf, int64_t uiBufSize, int& iRead) +{ + iRead = -1; + return DoAddonCall(__func__, [&lpBuf, uiBufSize, &iRead](const AddonInstance* addon) { + iRead = addon->toAddon->ReadRecordedStream(addon, static_cast<unsigned char*>(lpBuf), + static_cast<int>(uiBufSize)); + return (iRead == -1) ? PVR_ERROR_NOT_IMPLEMENTED : PVR_ERROR_NO_ERROR; + }); +} + +PVR_ERROR CPVRClient::SeekLiveStream(int64_t iFilePosition, int iWhence, int64_t& iPosition) +{ + iPosition = -1; + return DoAddonCall(__func__, [iFilePosition, iWhence, &iPosition](const AddonInstance* addon) { + iPosition = addon->toAddon->SeekLiveStream(addon, iFilePosition, iWhence); + return (iPosition == -1) ? PVR_ERROR_NOT_IMPLEMENTED : PVR_ERROR_NO_ERROR; + }); +} + +PVR_ERROR CPVRClient::SeekRecordedStream(int64_t iFilePosition, int iWhence, int64_t& iPosition) +{ + iPosition = -1; + return DoAddonCall(__func__, [iFilePosition, iWhence, &iPosition](const AddonInstance* addon) { + iPosition = addon->toAddon->SeekRecordedStream(addon, iFilePosition, iWhence); + return (iPosition == -1) ? PVR_ERROR_NOT_IMPLEMENTED : PVR_ERROR_NO_ERROR; + }); +} + +PVR_ERROR CPVRClient::SeekTime(double time, bool backwards, double* startpts) +{ + return DoAddonCall(__func__, [time, backwards, &startpts](const AddonInstance* addon) { + return addon->toAddon->SeekTime(addon, time, backwards, startpts) ? PVR_ERROR_NO_ERROR + : PVR_ERROR_NOT_IMPLEMENTED; + }); +} + +PVR_ERROR CPVRClient::GetLiveStreamLength(int64_t& iLength) +{ + iLength = -1; + return DoAddonCall(__func__, [&iLength](const AddonInstance* addon) { + iLength = addon->toAddon->LengthLiveStream(addon); + return (iLength == -1) ? PVR_ERROR_NOT_IMPLEMENTED : PVR_ERROR_NO_ERROR; + }); +} + +PVR_ERROR CPVRClient::GetRecordedStreamLength(int64_t& iLength) +{ + iLength = -1; + return DoAddonCall(__func__, [&iLength](const AddonInstance* addon) { + iLength = addon->toAddon->LengthRecordedStream(addon); + return (iLength == -1) ? PVR_ERROR_NOT_IMPLEMENTED : PVR_ERROR_NO_ERROR; + }); +} + +PVR_ERROR CPVRClient::SignalQuality(int channelUid, PVR_SIGNAL_STATUS& qualityinfo) +{ + return DoAddonCall(__func__, [channelUid, &qualityinfo](const AddonInstance* addon) { + return addon->toAddon->GetSignalStatus(addon, channelUid, &qualityinfo); + }); +} + +PVR_ERROR CPVRClient::GetDescrambleInfo(int channelUid, PVR_DESCRAMBLE_INFO& descrambleinfo) const +{ + return DoAddonCall( + __func__, + [channelUid, &descrambleinfo](const AddonInstance* addon) { + return addon->toAddon->GetDescrambleInfo(addon, channelUid, &descrambleinfo); + }, + m_clientCapabilities.SupportsDescrambleInfo()); +} + +PVR_ERROR CPVRClient::GetChannelStreamProperties(const std::shared_ptr<CPVRChannel>& channel, + CPVRStreamProperties& props) +{ + return DoAddonCall(__func__, [this, &channel, &props](const AddonInstance* addon) { + if (!CanPlayChannel(channel)) + return PVR_ERROR_NO_ERROR; // no error, but no need to obtain the values from the addon + + PVR_CHANNEL tag = {}; + channel->FillAddonData(tag); + + unsigned int iPropertyCount = STREAM_MAX_PROPERTY_COUNT; + std::unique_ptr<PVR_NAMED_VALUE[]> properties(new PVR_NAMED_VALUE[iPropertyCount]); + memset(properties.get(), 0, iPropertyCount * sizeof(PVR_NAMED_VALUE)); + + PVR_ERROR error = + addon->toAddon->GetChannelStreamProperties(addon, &tag, properties.get(), &iPropertyCount); + if (error == PVR_ERROR_NO_ERROR) + WriteStreamProperties(properties.get(), iPropertyCount, props); + + return error; + }); +} + +PVR_ERROR CPVRClient::GetRecordingStreamProperties(const std::shared_ptr<CPVRRecording>& recording, + CPVRStreamProperties& props) +{ + return DoAddonCall(__func__, [this, &recording, &props](const AddonInstance* addon) { + if (!m_clientCapabilities.SupportsRecordings()) + return PVR_ERROR_NO_ERROR; // no error, but no need to obtain the values from the addon + + PVR_RECORDING tag = {}; + recording->FillAddonData(tag); + + unsigned int iPropertyCount = STREAM_MAX_PROPERTY_COUNT; + std::unique_ptr<PVR_NAMED_VALUE[]> properties(new PVR_NAMED_VALUE[iPropertyCount]); + memset(properties.get(), 0, iPropertyCount * sizeof(PVR_NAMED_VALUE)); + + PVR_ERROR error = addon->toAddon->GetRecordingStreamProperties(addon, &tag, properties.get(), + &iPropertyCount); + if (error == PVR_ERROR_NO_ERROR) + WriteStreamProperties(properties.get(), iPropertyCount, props); + + return error; + }); +} + +PVR_ERROR CPVRClient::GetStreamProperties(PVR_STREAM_PROPERTIES* props) +{ + return DoAddonCall(__func__, [&props](const AddonInstance* addon) { + return addon->toAddon->GetStreamProperties(addon, props); + }); +} + +PVR_ERROR CPVRClient::DemuxReset() +{ + return DoAddonCall( + __func__, + [](const AddonInstance* addon) { + addon->toAddon->DemuxReset(addon); + return PVR_ERROR_NO_ERROR; + }, + m_clientCapabilities.HandlesDemuxing()); +} + +PVR_ERROR CPVRClient::DemuxAbort() +{ + return DoAddonCall( + __func__, + [](const AddonInstance* addon) { + addon->toAddon->DemuxAbort(addon); + return PVR_ERROR_NO_ERROR; + }, + m_clientCapabilities.HandlesDemuxing()); +} + +PVR_ERROR CPVRClient::DemuxFlush() +{ + return DoAddonCall( + __func__, + [](const AddonInstance* addon) { + addon->toAddon->DemuxFlush(addon); + return PVR_ERROR_NO_ERROR; + }, + m_clientCapabilities.HandlesDemuxing()); +} + +PVR_ERROR CPVRClient::DemuxRead(DemuxPacket*& packet) +{ + return DoAddonCall( + __func__, + [&packet](const AddonInstance* addon) { + packet = static_cast<DemuxPacket*>(addon->toAddon->DemuxRead(addon)); + return packet ? PVR_ERROR_NO_ERROR : PVR_ERROR_NOT_IMPLEMENTED; + }, + m_clientCapabilities.HandlesDemuxing()); +} + +const char* CPVRClient::ToString(const PVR_ERROR error) +{ + switch (error) + { + case PVR_ERROR_NO_ERROR: + return "no error"; + case PVR_ERROR_NOT_IMPLEMENTED: + return "not implemented"; + case PVR_ERROR_SERVER_ERROR: + return "server error"; + case PVR_ERROR_SERVER_TIMEOUT: + return "server timeout"; + case PVR_ERROR_RECORDING_RUNNING: + return "recording already running"; + case PVR_ERROR_ALREADY_PRESENT: + return "already present"; + case PVR_ERROR_REJECTED: + return "rejected by the backend"; + case PVR_ERROR_INVALID_PARAMETERS: + return "invalid parameters for this method"; + case PVR_ERROR_FAILED: + return "the command failed"; + case PVR_ERROR_UNKNOWN: + default: + return "unknown error"; + } +} + +PVR_ERROR CPVRClient::DoAddonCall(const char* strFunctionName, + const std::function<PVR_ERROR(const AddonInstance*)>& function, + bool bIsImplemented /* = true */, + bool bCheckReadyToUse /* = true */) const +{ + // Check preconditions. + if (!bIsImplemented) + return PVR_ERROR_NOT_IMPLEMENTED; + + if (m_bBlockAddonCalls) + { + CLog::Log(LOGWARNING, "{}: Blocking call to add-on '{}'.", strFunctionName, ID()); + return PVR_ERROR_SERVER_ERROR; + } + + if (bCheckReadyToUse && IgnoreClient()) + { + CLog::Log(LOGWARNING, "{}: Blocking call to add-on '{}'. Add-on not (yet) connected.", + strFunctionName, ID()); + return PVR_ERROR_SERVER_ERROR; + } + + if (bCheckReadyToUse && !ReadyToUse()) + { + CLog::Log(LOGWARNING, "{}: Blocking call to add-on '{}'. Add-on not ready to use.", + strFunctionName, ID()); + return PVR_ERROR_SERVER_ERROR; + } + + // Call. + m_allAddonCallsFinished.Reset(); + m_iAddonCalls++; + + const PVR_ERROR error = function(m_ifc.pvr); + + m_iAddonCalls--; + if (m_iAddonCalls == 0) + m_allAddonCallsFinished.Set(); + + // Log error, if any. + if (error != PVR_ERROR_NO_ERROR && error != PVR_ERROR_NOT_IMPLEMENTED) + CLog::Log(LOGERROR, "{}: Add-on '{}' returned an error: {}", strFunctionName, ID(), + ToString(error)); + + return error; +} + +bool CPVRClient::CanPlayChannel(const std::shared_ptr<CPVRChannel>& channel) const +{ + return (m_bReadyToUse && ((m_clientCapabilities.SupportsTV() && !channel->IsRadio()) || + (m_clientCapabilities.SupportsRadio() && channel->IsRadio()))); +} + +PVR_ERROR CPVRClient::OpenLiveStream(const std::shared_ptr<CPVRChannel>& channel) +{ + if (!channel) + return PVR_ERROR_INVALID_PARAMETERS; + + return DoAddonCall(__func__, [this, channel](const AddonInstance* addon) { + CloseLiveStream(); + + if (!CanPlayChannel(channel)) + { + CLog::LogFC(LOGDEBUG, LOGPVR, "Add-on '{}' can not play channel '{}'", ID(), + channel->ChannelName()); + return PVR_ERROR_SERVER_ERROR; + } + else + { + CLog::LogFC(LOGDEBUG, LOGPVR, "Opening live stream for channel '{}'", channel->ChannelName()); + PVR_CHANNEL tag; + channel->FillAddonData(tag); + return addon->toAddon->OpenLiveStream(addon, &tag) ? PVR_ERROR_NO_ERROR + : PVR_ERROR_NOT_IMPLEMENTED; + } + }); +} + +PVR_ERROR CPVRClient::OpenRecordedStream(const std::shared_ptr<CPVRRecording>& recording) +{ + if (!recording) + return PVR_ERROR_INVALID_PARAMETERS; + + return DoAddonCall( + __func__, + [this, recording](const AddonInstance* addon) { + CloseRecordedStream(); + + PVR_RECORDING tag; + recording->FillAddonData(tag); + CLog::LogFC(LOGDEBUG, LOGPVR, "Opening stream for recording '{}'", recording->m_strTitle); + return addon->toAddon->OpenRecordedStream(addon, &tag) ? PVR_ERROR_NO_ERROR + : PVR_ERROR_NOT_IMPLEMENTED; + }, + m_clientCapabilities.SupportsRecordings()); +} + +PVR_ERROR CPVRClient::CloseLiveStream() +{ + return DoAddonCall(__func__, [](const AddonInstance* addon) { + addon->toAddon->CloseLiveStream(addon); + return PVR_ERROR_NO_ERROR; + }); +} + +PVR_ERROR CPVRClient::CloseRecordedStream() +{ + return DoAddonCall(__func__, [](const AddonInstance* addon) { + addon->toAddon->CloseRecordedStream(addon); + return PVR_ERROR_NO_ERROR; + }); +} + +PVR_ERROR CPVRClient::PauseStream(bool bPaused) +{ + return DoAddonCall(__func__, [bPaused](const AddonInstance* addon) { + addon->toAddon->PauseStream(addon, bPaused); + return PVR_ERROR_NO_ERROR; + }); +} + +PVR_ERROR CPVRClient::SetSpeed(int speed) +{ + return DoAddonCall(__func__, [speed](const AddonInstance* addon) { + addon->toAddon->SetSpeed(addon, speed); + return PVR_ERROR_NO_ERROR; + }); +} + +PVR_ERROR CPVRClient::FillBuffer(bool mode) +{ + return DoAddonCall(__func__, [mode](const AddonInstance* addon) { + addon->toAddon->FillBuffer(addon, mode); + return PVR_ERROR_NO_ERROR; + }); +} + +PVR_ERROR CPVRClient::CanPauseStream(bool& bCanPause) const +{ + bCanPause = false; + return DoAddonCall(__func__, [&bCanPause](const AddonInstance* addon) { + bCanPause = addon->toAddon->CanPauseStream(addon); + return PVR_ERROR_NO_ERROR; + }); +} + +PVR_ERROR CPVRClient::CanSeekStream(bool& bCanSeek) const +{ + bCanSeek = false; + return DoAddonCall(__func__, [&bCanSeek](const AddonInstance* addon) { + bCanSeek = addon->toAddon->CanSeekStream(addon); + return PVR_ERROR_NO_ERROR; + }); +} + +PVR_ERROR CPVRClient::GetStreamTimes(PVR_STREAM_TIMES* times) +{ + return DoAddonCall(__func__, [×](const AddonInstance* addon) { + return addon->toAddon->GetStreamTimes(addon, times); + }); +} + +PVR_ERROR CPVRClient::IsRealTimeStream(bool& bRealTime) const +{ + bRealTime = false; + return DoAddonCall(__func__, [&bRealTime](const AddonInstance* addon) { + bRealTime = addon->toAddon->IsRealTimeStream(addon); + return PVR_ERROR_NO_ERROR; + }); +} + +PVR_ERROR CPVRClient::OnSystemSleep() +{ + return DoAddonCall( + __func__, [](const AddonInstance* addon) { return addon->toAddon->OnSystemSleep(addon); }); +} + +PVR_ERROR CPVRClient::OnSystemWake() +{ + return DoAddonCall( + __func__, [](const AddonInstance* addon) { return addon->toAddon->OnSystemWake(addon); }); +} + +PVR_ERROR CPVRClient::OnPowerSavingActivated() +{ + return DoAddonCall(__func__, [](const AddonInstance* addon) { + return addon->toAddon->OnPowerSavingActivated(addon); + }); +} + +PVR_ERROR CPVRClient::OnPowerSavingDeactivated() +{ + return DoAddonCall(__func__, [](const AddonInstance* addon) { + return addon->toAddon->OnPowerSavingDeactivated(addon); + }); +} + +std::shared_ptr<CPVRClientMenuHooks> CPVRClient::GetMenuHooks() +{ + if (!m_menuhooks) + m_menuhooks.reset(new CPVRClientMenuHooks(ID())); + + return m_menuhooks; +} + +PVR_ERROR CPVRClient::CallEpgTagMenuHook(const CPVRClientMenuHook& hook, + const std::shared_ptr<CPVREpgInfoTag>& tag) +{ + return DoAddonCall(__func__, [&hook, &tag](const AddonInstance* addon) { + CAddonEpgTag addonTag(tag); + + PVR_MENUHOOK menuHook; + menuHook.category = PVR_MENUHOOK_EPG; + menuHook.iHookId = hook.GetId(); + menuHook.iLocalizedStringId = hook.GetLabelId(); + + return addon->toAddon->CallEPGMenuHook(addon, &menuHook, &addonTag); + }); +} + +PVR_ERROR CPVRClient::CallChannelMenuHook(const CPVRClientMenuHook& hook, + const std::shared_ptr<CPVRChannel>& channel) +{ + return DoAddonCall(__func__, [&hook, &channel](const AddonInstance* addon) { + PVR_CHANNEL tag; + channel->FillAddonData(tag); + + PVR_MENUHOOK menuHook; + menuHook.category = PVR_MENUHOOK_CHANNEL; + menuHook.iHookId = hook.GetId(); + menuHook.iLocalizedStringId = hook.GetLabelId(); + + return addon->toAddon->CallChannelMenuHook(addon, &menuHook, &tag); + }); +} + +PVR_ERROR CPVRClient::CallRecordingMenuHook(const CPVRClientMenuHook& hook, + const std::shared_ptr<CPVRRecording>& recording, + bool bDeleted) +{ + return DoAddonCall(__func__, [&hook, &recording, &bDeleted](const AddonInstance* addon) { + PVR_RECORDING tag; + recording->FillAddonData(tag); + + PVR_MENUHOOK menuHook; + menuHook.category = bDeleted ? PVR_MENUHOOK_DELETED_RECORDING : PVR_MENUHOOK_RECORDING; + menuHook.iHookId = hook.GetId(); + menuHook.iLocalizedStringId = hook.GetLabelId(); + + return addon->toAddon->CallRecordingMenuHook(addon, &menuHook, &tag); + }); +} + +PVR_ERROR CPVRClient::CallTimerMenuHook(const CPVRClientMenuHook& hook, + const std::shared_ptr<CPVRTimerInfoTag>& timer) +{ + return DoAddonCall(__func__, [&hook, &timer](const AddonInstance* addon) { + PVR_TIMER tag; + timer->FillAddonData(tag); + + PVR_MENUHOOK menuHook; + menuHook.category = PVR_MENUHOOK_TIMER; + menuHook.iHookId = hook.GetId(); + menuHook.iLocalizedStringId = hook.GetLabelId(); + + return addon->toAddon->CallTimerMenuHook(addon, &menuHook, &tag); + }); +} + +PVR_ERROR CPVRClient::CallSettingsMenuHook(const CPVRClientMenuHook& hook) +{ + return DoAddonCall(__func__, [&hook](const AddonInstance* addon) { + PVR_MENUHOOK menuHook; + menuHook.category = PVR_MENUHOOK_SETTING; + menuHook.iHookId = hook.GetId(); + menuHook.iLocalizedStringId = hook.GetLabelId(); + + return addon->toAddon->CallSettingsMenuHook(addon, &menuHook); + }); +} + +void CPVRClient::SetPriority(int iPriority) +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + if (m_iPriority != iPriority) + { + m_iPriority = iPriority; + if (m_iClientId > PVR_INVALID_CLIENT_ID) + { + CServiceBroker::GetPVRManager().GetTVDatabase()->Persist(*this); + m_bPriorityFetched = true; + } + } +} + +int CPVRClient::GetPriority() const +{ + std::unique_lock<CCriticalSection> lock(m_critSection); + if (!m_bPriorityFetched && m_iClientId > PVR_INVALID_CLIENT_ID) + { + m_iPriority = CServiceBroker::GetPVRManager().GetTVDatabase()->GetPriority(*this); + m_bPriorityFetched = true; + } + return m_iPriority; +} + +void CPVRClient::HandleAddonCallback(const char* strFunctionName, + void* kodiInstance, + const std::function<void(CPVRClient* client)>& function, + bool bForceCall /* = false */) +{ + // Check preconditions. + CPVRClient* client = static_cast<CPVRClient*>(kodiInstance); + if (!client) + { + CLog::Log(LOGERROR, "{}: No instance pointer given!", strFunctionName); + return; + } + + if (!bForceCall && client->m_bBlockAddonCalls && client->m_iAddonCalls == 0) + { + CLog::Log(LOGWARNING, LOGPVR, "{}: Ignoring callback from PVR client '{}'", strFunctionName, + client->ID()); + return; + } + + // Call. + function(client); +} + +void CPVRClient::cb_transfer_channel_group(void* kodiInstance, + const PVR_HANDLE handle, + const PVR_CHANNEL_GROUP* group) +{ + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + if (!handle || !group) + { + CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); + return; + } + + if (strlen(group->strGroupName) == 0) + { + CLog::LogF(LOGERROR, "Empty group name"); + return; + } + + // transfer this entry to the groups container + CPVRChannelGroups* kodiGroups = static_cast<CPVRChannelGroups*>(handle->dataAddress); + const auto transferGroup = + std::make_shared<CPVRChannelGroup>(*group, kodiGroups->GetGroupAll()); + kodiGroups->UpdateFromClient(transferGroup); + }); +} + +void CPVRClient::cb_transfer_channel_group_member(void* kodiInstance, + const PVR_HANDLE handle, + const PVR_CHANNEL_GROUP_MEMBER* member) +{ + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + if (!handle || !member) + { + CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); + return; + } + + const std::shared_ptr<CPVRChannel> channel = + CServiceBroker::GetPVRManager().ChannelGroups()->GetByUniqueID(member->iChannelUniqueId, + client->GetID()); + if (!channel) + { + CLog::LogF(LOGERROR, "Cannot find group '{}' or channel '{}'", member->strGroupName, + member->iChannelUniqueId); + } + else + { + auto* groupMembers = + static_cast<std::vector<std::shared_ptr<CPVRChannelGroupMember>>*>(handle->dataAddress); + groupMembers->emplace_back( + std::make_shared<CPVRChannelGroupMember>(member->strGroupName, member->iOrder, channel)); + } + }); +} + +void CPVRClient::cb_transfer_epg_entry(void* kodiInstance, + const PVR_HANDLE handle, + const EPG_TAG* epgentry) +{ + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + if (!handle || !epgentry) + { + CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); + return; + } + + // transfer this entry to the epg + CPVREpg* epg = static_cast<CPVREpg*>(handle->dataAddress); + epg->UpdateEntry(epgentry, client->GetID()); + }); +} + +void CPVRClient::cb_transfer_provider_entry(void* kodiInstance, + const PVR_HANDLE handle, + const PVR_PROVIDER* provider) +{ + if (!handle) + { + CLog::LogF(LOGERROR, "Invalid handler data"); + return; + } + + CPVRClient* client = static_cast<CPVRClient*>(kodiInstance); + CPVRProvidersContainer* kodiProviders = static_cast<CPVRProvidersContainer*>(handle->dataAddress); + if (!provider || !client || !kodiProviders) + { + CLog::LogF(LOGERROR, "Invalid handler data"); + return; + } + + /* transfer this entry to the internal channels group */ + std::shared_ptr<CPVRProvider> transferProvider( + std::make_shared<CPVRProvider>(*provider, client->GetID())); + kodiProviders->UpdateFromClient(transferProvider); +} + +void CPVRClient::cb_transfer_channel_entry(void* kodiInstance, + const PVR_HANDLE handle, + const PVR_CHANNEL* channel) +{ + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + if (!handle || !channel) + { + CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); + return; + } + + auto* channels = static_cast<std::vector<std::shared_ptr<CPVRChannel>>*>(handle->dataAddress); + channels->emplace_back(std::make_shared<CPVRChannel>(*channel, client->GetID())); + }); +} + +void CPVRClient::cb_transfer_recording_entry(void* kodiInstance, + const PVR_HANDLE handle, + const PVR_RECORDING* recording) +{ + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + if (!handle || !recording) + { + CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); + return; + } + + // transfer this entry to the recordings container + const std::shared_ptr<CPVRRecording> transferRecording = + std::make_shared<CPVRRecording>(*recording, client->GetID()); + CPVRRecordings* recordings = static_cast<CPVRRecordings*>(handle->dataAddress); + recordings->UpdateFromClient(transferRecording, *client); + }); +} + +void CPVRClient::cb_transfer_timer_entry(void* kodiInstance, + const PVR_HANDLE handle, + const PVR_TIMER* timer) +{ + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + if (!handle || !timer) + { + CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); + return; + } + + // Note: channel can be nullptr here, for instance for epg-based timer rules + // ("record on any channel" condition) + const std::shared_ptr<CPVRChannel> channel = + CServiceBroker::GetPVRManager().ChannelGroups()->GetByUniqueID(timer->iClientChannelUid, + client->GetID()); + + // transfer this entry to the timers container + const std::shared_ptr<CPVRTimerInfoTag> transferTimer = + std::make_shared<CPVRTimerInfoTag>(*timer, channel, client->GetID()); + CPVRTimersContainer* timers = static_cast<CPVRTimersContainer*>(handle->dataAddress); + timers->UpdateFromClient(transferTimer); + }); +} + +void CPVRClient::cb_add_menu_hook(void* kodiInstance, const PVR_MENUHOOK* hook) +{ + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + if (!hook) + { + CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); + return; + } + + client->GetMenuHooks()->AddHook(*hook); + }); +} + +void CPVRClient::cb_recording_notification(void* kodiInstance, + const char* strName, + const char* strFileName, + bool bOnOff) +{ + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + if (!strFileName) + { + CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); + return; + } + + const std::string strLine1 = StringUtils::Format(g_localizeStrings.Get(bOnOff ? 19197 : 19198), + client->GetFriendlyName()); + std::string strLine2; + if (strName) + strLine2 = strName; + else + strLine2 = strFileName; + + // display a notification for 5 seconds + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, strLine1, strLine2, 5000, + false); + auto eventLog = CServiceBroker::GetEventLog(); + if (eventLog) + eventLog->Add(EventPtr( + new CNotificationEvent(client->GetFriendlyName(), strLine1, client->Icon(), strLine2))); + + CLog::LogFC(LOGDEBUG, LOGPVR, "Recording {} on client '{}'. name='{}' filename='{}'", + bOnOff ? "started" : "finished", client->ID(), strName, strFileName); + }); +} + +void CPVRClient::cb_trigger_channel_update(void* kodiInstance) +{ + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + // update channels in the next iteration of the pvrmanager's main loop + CServiceBroker::GetPVRManager().TriggerChannelsUpdate(client->GetID()); + }); +} + +void CPVRClient::cb_trigger_provider_update(void* kodiInstance) +{ + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + /* update the providers table in the next iteration of the pvrmanager's main loop */ + CServiceBroker::GetPVRManager().TriggerProvidersUpdate(client->GetID()); + }); +} + +void CPVRClient::cb_trigger_timer_update(void* kodiInstance) +{ + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + // update timers in the next iteration of the pvrmanager's main loop + CServiceBroker::GetPVRManager().TriggerTimersUpdate(client->GetID()); + }); +} + +void CPVRClient::cb_trigger_recording_update(void* kodiInstance) +{ + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + // update recordings in the next iteration of the pvrmanager's main loop + CServiceBroker::GetPVRManager().TriggerRecordingsUpdate(client->GetID()); + }); +} + +void CPVRClient::cb_trigger_channel_groups_update(void* kodiInstance) +{ + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + // update all channel groups in the next iteration of the pvrmanager's main loop + CServiceBroker::GetPVRManager().TriggerChannelGroupsUpdate(client->GetID()); + }); +} + +void CPVRClient::cb_trigger_epg_update(void* kodiInstance, unsigned int iChannelUid) +{ + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + CServiceBroker::GetPVRManager().EpgContainer().UpdateRequest(client->GetID(), iChannelUid); + }); +} + +void CPVRClient::cb_free_demux_packet(void* kodiInstance, DEMUX_PACKET* pPacket) +{ + HandleAddonCallback(__func__, kodiInstance, + [&](CPVRClient* client) { + CDVDDemuxUtils::FreeDemuxPacket(static_cast<DemuxPacket*>(pPacket)); + }, + true); +} + +DEMUX_PACKET* CPVRClient::cb_allocate_demux_packet(void* kodiInstance, int iDataSize) +{ + DEMUX_PACKET* result = nullptr; + + HandleAddonCallback( + __func__, kodiInstance, + [&](CPVRClient* client) { result = CDVDDemuxUtils::AllocateDemuxPacket(iDataSize); }, true); + + return result; +} + +void CPVRClient::cb_connection_state_change(void* kodiInstance, + const char* strConnectionString, + PVR_CONNECTION_STATE newState, + const char* strMessage) +{ + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + if (!strConnectionString) + { + CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); + return; + } + + const PVR_CONNECTION_STATE prevState(client->GetConnectionState()); + if (prevState == newState) + return; + + CLog::LogFC(LOGDEBUG, LOGPVR, + "State for connection '{}' on client '{}' changed from '{}' to '{}'", + strConnectionString, client->ID(), prevState, newState); + + client->SetConnectionState(newState); + + std::string msg; + if (strMessage) + msg = strMessage; + + CServiceBroker::GetPVRManager().ConnectionStateChange(client, std::string(strConnectionString), + newState, msg); + }); +} + +void CPVRClient::cb_epg_event_state_change(void* kodiInstance, + EPG_TAG* tag, + EPG_EVENT_STATE newState) +{ + HandleAddonCallback(__func__, kodiInstance, [&](CPVRClient* client) { + if (!tag) + { + CLog::LogF(LOGERROR, "Invalid callback parameter(s)"); + return; + } + + // Note: channel data and epg id may not yet be available. Tag will be fully initialized later. + const std::shared_ptr<CPVREpgInfoTag> epgTag = + std::make_shared<CPVREpgInfoTag>(*tag, client->GetID(), nullptr, -1); + CServiceBroker::GetPVRManager().EpgContainer().UpdateFromClient(epgTag, newState); + }); +} + +class CCodecIds +{ +public: + virtual ~CCodecIds() = default; + + static CCodecIds& GetInstance() + { + static CCodecIds _instance; + return _instance; + } + + PVR_CODEC GetCodecByName(const char* strCodecName) + { + PVR_CODEC retVal = PVR_INVALID_CODEC; + if (strlen(strCodecName) == 0) + return retVal; + + std::string strUpperCodecName = strCodecName; + StringUtils::ToUpper(strUpperCodecName); + + std::map<std::string, PVR_CODEC>::const_iterator it = m_lookup.find(strUpperCodecName); + if (it != m_lookup.end()) + retVal = it->second; + + return retVal; + } + +private: + CCodecIds() + { + // get ids and names + const AVCodec* codec = nullptr; + void* i = nullptr; + PVR_CODEC tmp; + while ((codec = av_codec_iterate(&i))) + { + if (av_codec_is_decoder(codec)) + { + tmp.codec_type = static_cast<PVR_CODEC_TYPE>(codec->type); + tmp.codec_id = codec->id; + + std::string strUpperCodecName = codec->name; + StringUtils::ToUpper(strUpperCodecName); + + m_lookup.insert(std::make_pair(strUpperCodecName, tmp)); + } + } + + // teletext is not returned by av_codec_next. we got our own decoder + tmp.codec_type = PVR_CODEC_TYPE_SUBTITLE; + tmp.codec_id = AV_CODEC_ID_DVB_TELETEXT; + m_lookup.insert(std::make_pair("TELETEXT", tmp)); + + // rds is not returned by av_codec_next. we got our own decoder + tmp.codec_type = PVR_CODEC_TYPE_RDS; + tmp.codec_id = AV_CODEC_ID_NONE; + m_lookup.insert(std::make_pair("RDS", tmp)); + + // ID3 is not returned by av_codec_next. we got our own decoder + tmp.codec_type = PVR_CODEC_TYPE_ID3; + tmp.codec_id = AV_CODEC_ID_NONE; + m_lookup.insert({"ID3", tmp}); + } + + std::map<std::string, PVR_CODEC> m_lookup; +}; + +PVR_CODEC CPVRClient::cb_get_codec_by_name(const void* kodiInstance, const char* strCodecName) +{ + PVR_CODEC result = PVR_INVALID_CODEC; + + HandleAddonCallback( + __func__, const_cast<void*>(kodiInstance), + [&](CPVRClient* client) { result = CCodecIds::GetInstance().GetCodecByName(strCodecName); }, + true); + + return result; +} + +} // namespace PVR |