summaryrefslogtreecommitdiffstats
path: root/xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--xbmc/pvr/guilib/PVRGUIActionsPlayback.cpp564
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);
+ }
+ }
+}