/* * 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 #include #include 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 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(&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(&item)->SetStartOffset(STARTOFFSET_RESUME); } else { if (bFallbackToPlay) const_cast(&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 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(item)); CheckAndSwitchToFullscreen(bFullscreen); } bool CPVRGUIActionsPlayback::PlayRecording(const CFileItem& item, bool bCheckResume) const { const std::shared_ptr 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 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 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 groupMember = CServiceBroker::GetPVRManager().Get().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 recording; const std::shared_ptr 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().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 groupMember = CServiceBroker::GetPVRManager().Get().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 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 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 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 channelGroup = CServiceBroker::GetPVRManager().PlaybackState()->GetActiveChannelGroup(bIsRadio); if (channelGroup) { // try to start playback of first channel in this group const std::vector> 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 groupMember = CServiceBroker::GetPVRManager().PlaybackState()->GetLastPlayedChannelGroupMember(playRadio); if (!groupMember) { const std::shared_ptr 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 pvrItem = std::make_unique(item); if (URIUtils::IsPVRChannel(item.GetPath()) && !item.HasPVRChannelInfoTag()) { const std::shared_ptr groupMember = CServiceBroker::GetPVRManager().ChannelGroups()->GetChannelGroupMemberByPath( item.GetPath()); if (groupMember) pvrItem = std::make_unique(groupMember); } else if (URIUtils::IsPVRRecording(item.GetPath()) && !item.HasPVRRecordingInfoTag()) { const std::shared_ptr recording = CServiceBroker::GetPVRManager().Recordings()->GetByPath(item.GetPath()); if (recording) { pvrItem = std::make_unique(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 playingChannel = CServiceBroker::GetPVRManager().PlaybackState()->GetPlayingChannel(); if (playingChannel) { time_t nextTime = 0; std::shared_ptr 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 playingChannel = CServiceBroker::GetPVRManager().PlaybackState()->GetPlayingChannel(); if (playingChannel) { time_t prevTime = 0; std::shared_ptr 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); } } }