diff options
Diffstat (limited to 'xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp')
-rw-r--r-- | xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp | 564 |
1 files changed, 564 insertions, 0 deletions
diff --git a/xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp b/xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp new file mode 100644 index 0000000..66fdb80 --- /dev/null +++ b/xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp @@ -0,0 +1,564 @@ +/* + * Copyright (C) 2016-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 "PVRGUIActionsPlayback.h" + +#include "FileItem.h" +#include "ServiceBroker.h" +#include "application/ApplicationEnums.h" +#include "cores/DataCacheCore.h" +#include "dialogs/GUIDialogContextMenu.h" +#include "dialogs/GUIDialogKaiToast.h" +#include "dialogs/GUIDialogYesNo.h" +#include "guilib/GUIComponent.h" +#include "guilib/GUIWindowManager.h" +#include "guilib/LocalizeStrings.h" +#include "guilib/WindowIDs.h" +#include "messaging/ApplicationMessenger.h" +#include "pvr/PVRItem.h" +#include "pvr/PVRManager.h" +#include "pvr/PVRPlaybackState.h" +#include "pvr/PVRStreamProperties.h" +#include "pvr/addons/PVRClient.h" +#include "pvr/channels/PVRChannel.h" +#include "pvr/channels/PVRChannelGroup.h" +#include "pvr/channels/PVRChannelGroupMember.h" +#include "pvr/channels/PVRChannelGroups.h" +#include "pvr/channels/PVRChannelGroupsContainer.h" +#include "pvr/epg/EpgInfoTag.h" +#include "pvr/guilib/PVRGUIActionsChannels.h" +#include "pvr/guilib/PVRGUIActionsParentalControl.h" +#include "pvr/recordings/PVRRecording.h" +#include "pvr/recordings/PVRRecordings.h" +#include "settings/MediaSettings.h" +#include "settings/Settings.h" +#include "utils/StringUtils.h" +#include "utils/URIUtils.h" +#include "utils/Variant.h" +#include "utils/log.h" + +#include <memory> +#include <string> +#include <vector> + +using namespace PVR; +using namespace KODI::MESSAGING; + +CPVRGUIActionsPlayback::CPVRGUIActionsPlayback() + : m_settings({CSettings::SETTING_LOOKANDFEEL_STARTUPACTION, + CSettings::SETTING_PVRPLAYBACK_SWITCHTOFULLSCREENCHANNELTYPES}) +{ +} + +std::string CPVRGUIActionsPlayback::GetResumeLabel(const CFileItem& item) const +{ + std::string resumeString; + + const std::shared_ptr<CPVRRecording> recording( + CPVRItem(CFileItemPtr(new CFileItem(item))).GetRecording()); + if (recording && !recording->IsDeleted()) + { + int positionInSeconds = lrint(recording->GetResumePoint().timeInSeconds); + if (positionInSeconds > 0) + resumeString = StringUtils::Format( + g_localizeStrings.Get(12022), + StringUtils::SecondsToTimeString(positionInSeconds, TIME_FORMAT_HH_MM_SS)); + } + return resumeString; +} + +bool CPVRGUIActionsPlayback::CheckResumeRecording(const CFileItem& item) const +{ + bool bPlayIt(true); + std::string resumeString(GetResumeLabel(item)); + if (!resumeString.empty()) + { + CContextButtons choices; + choices.Add(CONTEXT_BUTTON_RESUME_ITEM, resumeString); + choices.Add(CONTEXT_BUTTON_PLAY_ITEM, 12021); // Play from beginning + int choice = CGUIDialogContextMenu::ShowAndGetChoice(choices); + if (choice > 0) + const_cast<CFileItem*>(&item)->SetStartOffset( + choice == CONTEXT_BUTTON_RESUME_ITEM ? STARTOFFSET_RESUME : 0); + else + bPlayIt = false; // context menu cancelled + } + return bPlayIt; +} + +bool CPVRGUIActionsPlayback::ResumePlayRecording(const CFileItem& item, bool bFallbackToPlay) const +{ + bool bCanResume = !GetResumeLabel(item).empty(); + if (bCanResume) + { + const_cast<CFileItem*>(&item)->SetStartOffset(STARTOFFSET_RESUME); + } + else + { + if (bFallbackToPlay) + const_cast<CFileItem*>(&item)->SetStartOffset(0); + else + return false; + } + + return PlayRecording(item, false); +} + +void CPVRGUIActionsPlayback::CheckAndSwitchToFullscreen(bool bFullscreen) const +{ + CMediaSettings::GetInstance().SetMediaStartWindowed(!bFullscreen); + + if (bFullscreen) + { + CGUIMessage msg(GUI_MSG_FULLSCREEN, 0, + CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow()); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg); + } +} + +void CPVRGUIActionsPlayback::StartPlayback(CFileItem* item, + bool bFullscreen, + const CPVRStreamProperties* epgProps) const +{ + // Obtain dynamic playback url and properties from the respective pvr client + const std::shared_ptr<CPVRClient> client = CServiceBroker::GetPVRManager().GetClient(*item); + if (client) + { + CPVRStreamProperties props; + + if (item->IsPVRChannel()) + { + // If this was an EPG Tag to be played as live then PlayEpgTag() will create a channel + // fileitem instead and pass the epg tags props so we use those and skip the client call + if (epgProps) + props = *epgProps; + else + client->GetChannelStreamProperties(item->GetPVRChannelInfoTag(), props); + } + else if (item->IsPVRRecording()) + { + client->GetRecordingStreamProperties(item->GetPVRRecordingInfoTag(), props); + } + else if (item->IsEPG()) + { + if (epgProps) // we already have props from PlayEpgTag() + props = *epgProps; + else + client->GetEpgTagStreamProperties(item->GetEPGInfoTag(), props); + } + + if (props.size()) + { + const std::string url = props.GetStreamURL(); + if (!url.empty()) + item->SetDynPath(url); + + const std::string mime = props.GetStreamMimeType(); + if (!mime.empty()) + { + item->SetMimeType(mime); + item->SetContentLookup(false); + } + + for (const auto& prop : props) + item->SetProperty(prop.first, prop.second); + } + } + + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MEDIA_PLAY, 0, 0, static_cast<void*>(item)); + CheckAndSwitchToFullscreen(bFullscreen); +} + +bool CPVRGUIActionsPlayback::PlayRecording(const CFileItem& item, bool bCheckResume) const +{ + const std::shared_ptr<CPVRRecording> recording(CPVRItem(item).GetRecording()); + if (!recording) + return false; + + if (CServiceBroker::GetPVRManager().PlaybackState()->IsPlayingRecording(recording)) + { + CGUIMessage msg(GUI_MSG_FULLSCREEN, 0, + CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow()); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg); + return true; + } + + if (!bCheckResume || CheckResumeRecording(item)) + { + CFileItem* itemToPlay = new CFileItem(recording); + itemToPlay->SetStartOffset(item.GetStartOffset()); + StartPlayback(itemToPlay, true); + } + return true; +} + +bool CPVRGUIActionsPlayback::PlayEpgTag(const CFileItem& item) const +{ + const std::shared_ptr<CPVREpgInfoTag> epgTag(CPVRItem(item).GetEpgInfoTag()); + if (!epgTag) + return false; + + if (CServiceBroker::GetPVRManager().PlaybackState()->IsPlayingEpgTag(epgTag)) + { + CGUIMessage msg(GUI_MSG_FULLSCREEN, 0, + CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow()); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg); + return true; + } + + // Obtain dynamic playback url and properties from the respective pvr client + const std::shared_ptr<CPVRClient> client = + CServiceBroker::GetPVRManager().GetClient(epgTag->ClientID()); + if (!client) + return false; + + CPVRStreamProperties props; + client->GetEpgTagStreamProperties(epgTag, props); + + CFileItem* itemToPlay = nullptr; + if (props.EPGPlaybackAsLive()) + { + const std::shared_ptr<CPVRChannelGroupMember> groupMember = + CServiceBroker::GetPVRManager().Get<PVR::GUI::Channels>().GetChannelGroupMember(item); + if (!groupMember) + return false; + + itemToPlay = new CFileItem(groupMember); + } + else + { + itemToPlay = new CFileItem(epgTag); + } + + StartPlayback(itemToPlay, true, &props); + return true; +} + +bool CPVRGUIActionsPlayback::SwitchToChannel(const CFileItem& item, bool bCheckResume) const +{ + if (item.m_bIsFolder) + return false; + + std::shared_ptr<CPVRRecording> recording; + const std::shared_ptr<CPVRChannel> channel(CPVRItem(item).GetChannel()); + if (channel) + { + bool bSwitchToFullscreen = + CServiceBroker::GetPVRManager().PlaybackState()->IsPlayingChannel(channel); + + if (!bSwitchToFullscreen) + { + recording = + CServiceBroker::GetPVRManager().Recordings()->GetRecordingForEpgTag(channel->GetEPGNow()); + bSwitchToFullscreen = + recording && + CServiceBroker::GetPVRManager().PlaybackState()->IsPlayingRecording(recording); + } + + if (bSwitchToFullscreen) + { + CGUIMessage msg(GUI_MSG_FULLSCREEN, 0, + CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow()); + CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg); + return true; + } + } + + ParentalCheckResult result = + channel ? CServiceBroker::GetPVRManager().Get<PVR::GUI::Parental>().CheckParentalLock(channel) + : ParentalCheckResult::FAILED; + if (result == ParentalCheckResult::SUCCESS) + { + // switch to channel or if recording present, ask whether to switch or play recording... + if (!recording) + recording = + CServiceBroker::GetPVRManager().Recordings()->GetRecordingForEpgTag(channel->GetEPGNow()); + + if (recording) + { + bool bCancel(false); + bool bPlayRecording = CGUIDialogYesNo::ShowAndGetInput( + CVariant{19687}, // "Play recording" + CVariant{""}, CVariant{12021}, // "Play from beginning" + CVariant{recording->m_strTitle}, bCancel, CVariant{19000}, // "Switch to channel" + CVariant{19687}, // "Play recording" + 0); // no autoclose + if (bCancel) + return false; + + if (bPlayRecording) + return PlayRecording(CFileItem(recording), bCheckResume); + } + + bool bFullscreen; + switch (m_settings.GetIntValue(CSettings::SETTING_PVRPLAYBACK_SWITCHTOFULLSCREENCHANNELTYPES)) + { + case 0: // never + bFullscreen = false; + break; + case 1: // TV channels + bFullscreen = !channel->IsRadio(); + break; + case 2: // Radio channels + bFullscreen = channel->IsRadio(); + break; + case 3: // TV and radio channels + default: + bFullscreen = true; + break; + } + const std::shared_ptr<CPVRChannelGroupMember> groupMember = + CServiceBroker::GetPVRManager().Get<PVR::GUI::Channels>().GetChannelGroupMember(item); + if (!groupMember) + return false; + + StartPlayback(new CFileItem(groupMember), bFullscreen); + return true; + } + else if (result == ParentalCheckResult::FAILED) + { + const std::string channelName = + channel ? channel->ChannelName() : g_localizeStrings.Get(19029); // Channel + const std::string msg = StringUtils::Format( + g_localizeStrings.Get(19035), + channelName); // CHANNELNAME could not be played. Check the log for details. + + CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error, g_localizeStrings.Get(19166), + msg); // PVR information + } + + return false; +} + +bool CPVRGUIActionsPlayback::SwitchToChannel(PlaybackType type) const +{ + std::shared_ptr<CPVRChannelGroupMember> groupMember; + bool bIsRadio(false); + + // check if the desired PlaybackType is already playing, + // and if not, try to grab the last played channel of this type + switch (type) + { + case PlaybackTypeRadio: + { + if (CServiceBroker::GetPVRManager().PlaybackState()->IsPlayingRadio()) + return true; + + const std::shared_ptr<CPVRChannelGroup> allGroup = + CServiceBroker::GetPVRManager().ChannelGroups()->GetGroupAllRadio(); + if (allGroup) + groupMember = allGroup->GetLastPlayedChannelGroupMember(); + + bIsRadio = true; + break; + } + case PlaybackTypeTV: + { + if (CServiceBroker::GetPVRManager().PlaybackState()->IsPlayingTV()) + return true; + + const std::shared_ptr<CPVRChannelGroup> allGroup = + CServiceBroker::GetPVRManager().ChannelGroups()->GetGroupAllTV(); + if (allGroup) + groupMember = allGroup->GetLastPlayedChannelGroupMember(); + + break; + } + default: + if (CServiceBroker::GetPVRManager().PlaybackState()->IsPlaying()) + return true; + + groupMember = + CServiceBroker::GetPVRManager().ChannelGroups()->GetLastPlayedChannelGroupMember(); + break; + } + + // if we have a last played channel, start playback + if (groupMember) + { + return SwitchToChannel(CFileItem(groupMember), true); + } + else + { + // if we don't, find the active channel group of the demanded type and play it's first channel + const std::shared_ptr<CPVRChannelGroup> channelGroup = + CServiceBroker::GetPVRManager().PlaybackState()->GetActiveChannelGroup(bIsRadio); + if (channelGroup) + { + // try to start playback of first channel in this group + const std::vector<std::shared_ptr<CPVRChannelGroupMember>> groupMembers = + channelGroup->GetMembers(); + if (!groupMembers.empty()) + { + return SwitchToChannel(CFileItem(*groupMembers.begin()), true); + } + } + } + + CLog::LogF(LOGERROR, + "Could not determine {} channel to playback. No last played channel found, and " + "first channel of active group could also not be determined.", + bIsRadio ? "Radio" : "TV"); + + CGUIDialogKaiToast::QueueNotification( + CGUIDialogKaiToast::Error, + g_localizeStrings.Get(19166), // PVR information + StringUtils::Format( + g_localizeStrings.Get(19035), + g_localizeStrings.Get( + bIsRadio ? 19021 + : 19020))); // Radio/TV could not be played. Check the log for details. + return false; +} + +bool CPVRGUIActionsPlayback::PlayChannelOnStartup() const +{ + int iAction = m_settings.GetIntValue(CSettings::SETTING_LOOKANDFEEL_STARTUPACTION); + if (iAction != STARTUP_ACTION_PLAY_TV && iAction != STARTUP_ACTION_PLAY_RADIO) + return false; + + bool playRadio = (iAction == STARTUP_ACTION_PLAY_RADIO); + + // get the last played channel or fallback to first channel of all channels group + std::shared_ptr<CPVRChannelGroupMember> groupMember = + CServiceBroker::GetPVRManager().PlaybackState()->GetLastPlayedChannelGroupMember(playRadio); + + if (!groupMember) + { + const std::shared_ptr<CPVRChannelGroup> group = + CServiceBroker::GetPVRManager().ChannelGroups()->Get(playRadio)->GetGroupAll(); + auto channels = group->GetMembers(); + if (channels.empty()) + return false; + + groupMember = channels.front(); + if (!groupMember) + return false; + } + + CLog::Log(LOGINFO, "PVR is starting playback of channel '{}'", + groupMember->Channel()->ChannelName()); + return SwitchToChannel(CFileItem(groupMember), true); +} + +bool CPVRGUIActionsPlayback::PlayMedia(const CFileItem& item) const +{ + std::unique_ptr<CFileItem> pvrItem = std::make_unique<CFileItem>(item); + if (URIUtils::IsPVRChannel(item.GetPath()) && !item.HasPVRChannelInfoTag()) + { + const std::shared_ptr<CPVRChannelGroupMember> groupMember = + CServiceBroker::GetPVRManager().ChannelGroups()->GetChannelGroupMemberByPath( + item.GetPath()); + if (groupMember) + pvrItem = std::make_unique<CFileItem>(groupMember); + } + else if (URIUtils::IsPVRRecording(item.GetPath()) && !item.HasPVRRecordingInfoTag()) + { + const std::shared_ptr<CPVRRecording> recording = + CServiceBroker::GetPVRManager().Recordings()->GetByPath(item.GetPath()); + if (recording) + { + pvrItem = std::make_unique<CFileItem>(recording); + pvrItem->SetStartOffset(item.GetStartOffset()); + } + } + bool bCheckResume = true; + if (item.HasProperty("check_resume")) + bCheckResume = item.GetProperty("check_resume").asBoolean(); + + if (pvrItem && pvrItem->HasPVRChannelInfoTag()) + { + return SwitchToChannel(*pvrItem, bCheckResume); + } + else if (pvrItem && pvrItem->HasPVRRecordingInfoTag()) + { + return PlayRecording(*pvrItem, bCheckResume); + } + + return false; +} + +void CPVRGUIActionsPlayback::SeekForward() +{ + time_t playbackStartTime = CServiceBroker::GetDataCacheCore().GetStartTime(); + if (playbackStartTime > 0) + { + const std::shared_ptr<CPVRChannel> playingChannel = + CServiceBroker::GetPVRManager().PlaybackState()->GetPlayingChannel(); + if (playingChannel) + { + time_t nextTime = 0; + std::shared_ptr<CPVREpgInfoTag> next = playingChannel->GetEPGNext(); + if (next) + { + next->StartAsUTC().GetAsTime(nextTime); + } + else + { + // if there is no next event, jump to end of currently playing event + next = playingChannel->GetEPGNow(); + if (next) + next->EndAsUTC().GetAsTime(nextTime); + } + + int64_t seekTime = 0; + if (nextTime != 0) + { + seekTime = (nextTime - playbackStartTime) * 1000; + } + else + { + // no epg; jump to end of buffer + seekTime = CServiceBroker::GetDataCacheCore().GetMaxTime(); + } + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MEDIA_SEEK_TIME, seekTime); + } + } +} + +void CPVRGUIActionsPlayback::SeekBackward(unsigned int iThreshold) +{ + time_t playbackStartTime = CServiceBroker::GetDataCacheCore().GetStartTime(); + if (playbackStartTime > 0) + { + const std::shared_ptr<CPVRChannel> playingChannel = + CServiceBroker::GetPVRManager().PlaybackState()->GetPlayingChannel(); + if (playingChannel) + { + time_t prevTime = 0; + std::shared_ptr<CPVREpgInfoTag> prev = playingChannel->GetEPGNow(); + if (prev) + { + prev->StartAsUTC().GetAsTime(prevTime); + + // if playback time of current event is above threshold jump to start of current event + int64_t playTime = CServiceBroker::GetDataCacheCore().GetPlayTime() / 1000; + if ((playbackStartTime + playTime - prevTime) <= iThreshold) + { + // jump to start of previous event + prevTime = 0; + prev = playingChannel->GetEPGPrevious(); + if (prev) + prev->StartAsUTC().GetAsTime(prevTime); + } + } + + int64_t seekTime = 0; + if (prevTime != 0) + { + seekTime = (prevTime - playbackStartTime) * 1000; + } + else + { + // no epg; jump to begin of buffer + seekTime = CServiceBroker::GetDataCacheCore().GetMinTime(); + } + CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MEDIA_SEEK_TIME, seekTime); + } + } +} |