/* * 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 #include #include #include #include extern "C" { #include } 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 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 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 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 lock(m_critSection); return m_prevConnectionState; } bool CPVRClient::IgnoreClient() const { std::unique_lock 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> 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 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(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 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 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& 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& 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& 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& 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& 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& 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& 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& tag, CPVRStreamProperties& props) { return DoAddonCall(__func__, [&tag, &props](const AddonInstance* addon) { CAddonEpgTag addonTag(tag); unsigned int iPropertyCount = STREAM_MAX_PROPERTY_COUNT; std::unique_ptr 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& epgTag, std::vector& 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>& 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>& 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& 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>& results) const { std::unique_lock 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(lpBuf), static_cast(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(lpBuf), static_cast(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& 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 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& 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 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(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& 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& channel) const { return (m_bReadyToUse && ((m_clientCapabilities.SupportsTV() && !channel->IsRadio()) || (m_clientCapabilities.SupportsRadio() && channel->IsRadio()))); } PVR_ERROR CPVRClient::OpenLiveStream(const std::shared_ptr& 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& 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 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& 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& 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& 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& 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 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 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& function, bool bForceCall /* = false */) { // Check preconditions. CPVRClient* client = static_cast(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(handle->dataAddress); const auto transferGroup = std::make_shared(*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 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>*>(handle->dataAddress); groupMembers->emplace_back( std::make_shared(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(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(kodiInstance); CPVRProvidersContainer* kodiProviders = static_cast(handle->dataAddress); if (!provider || !client || !kodiProviders) { CLog::LogF(LOGERROR, "Invalid handler data"); return; } /* transfer this entry to the internal channels group */ std::shared_ptr transferProvider( std::make_shared(*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>*>(handle->dataAddress); channels->emplace_back(std::make_shared(*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 transferRecording = std::make_shared(*recording, client->GetID()); CPVRRecordings* recordings = static_cast(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 channel = CServiceBroker::GetPVRManager().ChannelGroups()->GetByUniqueID(timer->iClientChannelUid, client->GetID()); // transfer this entry to the timers container const std::shared_ptr transferTimer = std::make_shared(*timer, channel, client->GetID()); CPVRTimersContainer* timers = static_cast(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(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 epgTag = std::make_shared(*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::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(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 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(kodiInstance), [&](CPVRClient* client) { result = CCodecIds::GetInstance().GetCodecByName(strCodecName); }, true); return result; } } // namespace PVR