2 * Copyright (C) 2016-2018 Team Kodi
3 * This file is part of Kodi - https://kodi.tv
5 * SPDX-License-Identifier: GPL-2.0-or-later
6 * See LICENSES/README.md for more information.
9 #include "PVRGUIActionsPlayback.h"
12 #include "FileItemList.h"
13 #include "ServiceBroker.h"
14 #include "application/ApplicationEnums.h"
15 #include "cores/DataCacheCore.h"
16 #include "dialogs/GUIDialogKaiToast.h"
17 #include "dialogs/GUIDialogYesNo.h"
18 #include "guilib/GUIComponent.h"
19 #include "guilib/GUIWindowManager.h"
20 #include "guilib/LocalizeStrings.h"
21 #include "guilib/WindowIDs.h"
22 #include "messaging/ApplicationMessenger.h"
23 #include "pvr/PVRItem.h"
24 #include "pvr/PVRManager.h"
25 #include "pvr/PVRPlaybackState.h"
26 #include "pvr/PVRStreamProperties.h"
27 #include "pvr/addons/PVRClient.h"
28 #include "pvr/channels/PVRChannel.h"
29 #include "pvr/channels/PVRChannelGroup.h"
30 #include "pvr/channels/PVRChannelGroupMember.h"
31 #include "pvr/channels/PVRChannelGroups.h"
32 #include "pvr/channels/PVRChannelGroupsContainer.h"
33 #include "pvr/epg/EpgInfoTag.h"
34 #include "pvr/guilib/PVRGUIActionsChannels.h"
35 #include "pvr/guilib/PVRGUIActionsParentalControl.h"
36 #include "pvr/recordings/PVRRecording.h"
37 #include "pvr/recordings/PVRRecordings.h"
38 #include "settings/MediaSettings.h"
39 #include "settings/Settings.h"
40 #include "utils/StringUtils.h"
41 #include "utils/URIUtils.h"
42 #include "utils/Variant.h"
43 #include "utils/log.h"
44 #include "video/VideoUtils.h"
45 #include "video/guilib/VideoGUIUtils.h"
46 #include "video/guilib/VideoSelectActionProcessor.h"
54 using namespace KODI::MESSAGING
;
56 CPVRGUIActionsPlayback::CPVRGUIActionsPlayback()
57 : m_settings({CSettings::SETTING_LOOKANDFEEL_STARTUPACTION
,
58 CSettings::SETTING_PVRPLAYBACK_SWITCHTOFULLSCREENCHANNELTYPES
})
62 bool CPVRGUIActionsPlayback::CheckResumeRecording(const CFileItem
& item
) const
66 const VIDEO::GUILIB::Action action
=
67 VIDEO::GUILIB::CVideoSelectActionProcessorBase::ChoosePlayOrResume(item
);
68 if (action
== VIDEO::GUILIB::ACTION_RESUME
)
70 const_cast<CFileItem
*>(&item
)->SetStartOffset(STARTOFFSET_RESUME
);
72 else if (action
== VIDEO::GUILIB::ACTION_PLAY_FROM_BEGINNING
)
74 const_cast<CFileItem
*>(&item
)->SetStartOffset(0);
78 // The Resume dialog was closed without any choice
85 bool CPVRGUIActionsPlayback::ResumePlayRecording(const CFileItem
& item
, bool bFallbackToPlay
) const
87 if (VIDEO::UTILS::GetItemResumeInformation(item
).isResumable
)
89 const_cast<CFileItem
*>(&item
)->SetStartOffset(STARTOFFSET_RESUME
);
94 const_cast<CFileItem
*>(&item
)->SetStartOffset(0);
99 return PlayRecording(item
, false /* skip resume check */);
102 void CPVRGUIActionsPlayback::CheckAndSwitchToFullscreen(bool bFullscreen
) const
104 CMediaSettings::GetInstance().SetMediaStartWindowed(!bFullscreen
);
108 CGUIMessage
msg(GUI_MSG_FULLSCREEN
, 0,
109 CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow());
110 CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg
);
114 bool CPVRGUIActionsPlayback::PlayRecording(const CFileItem
& item
, bool bCheckResume
) const
116 const std::shared_ptr
<CPVRRecording
> recording(CPVRItem(item
).GetRecording());
120 if (CServiceBroker::GetPVRManager().PlaybackState()->IsPlayingRecording(recording
))
122 CGUIMessage
msg(GUI_MSG_FULLSCREEN
, 0,
123 CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow());
124 CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg
);
128 if (!bCheckResume
|| CheckResumeRecording(item
))
130 if (!item
.m_bIsFolder
&& VIDEO::UTILS::IsAutoPlayNextItem(item
))
132 // recursively add items located in the same folder as item to play list, starting with item
133 std::string parentPath
= item
.GetProperty("ParentPath").asString();
134 if (parentPath
.empty())
135 URIUtils::GetParentPath(item
.GetPath(), parentPath
);
137 if (parentPath
.empty())
139 CLog::LogF(LOGERROR
, "Unable to obtain parent path for '{}'", item
.GetPath());
143 const auto parentItem
= std::make_shared
<CFileItem
>(parentPath
, true);
144 if (item
.GetStartOffset() == STARTOFFSET_RESUME
)
145 parentItem
->SetStartOffset(STARTOFFSET_RESUME
);
147 auto queuedItems
= std::make_unique
<CFileItemList
>();
148 VIDEO::UTILS::GetItemsForPlayList(parentItem
, *queuedItems
);
150 // figure out where to start playback
152 for (const std::shared_ptr
<CFileItem
>& queuedItem
: *queuedItems
)
154 if (queuedItem
->IsSamePath(&item
))
160 CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MEDIA_PLAY
, pos
, -1,
161 static_cast<void*>(queuedItems
.release()));
165 CFileItem
* itemToPlay
= new CFileItem(recording
);
166 itemToPlay
->SetStartOffset(item
.GetStartOffset());
167 CServiceBroker::GetPVRManager().PlaybackState()->StartPlayback(itemToPlay
);
169 CheckAndSwitchToFullscreen(true);
174 bool CPVRGUIActionsPlayback::PlayRecordingFolder(const CFileItem
& item
, bool bCheckResume
) const
176 if (!item
.m_bIsFolder
)
179 if (!bCheckResume
|| CheckResumeRecording(item
))
181 // recursively add items to list
182 const auto itemToQueue
= std::make_shared
<CFileItem
>(item
);
183 auto queuedItems
= std::make_unique
<CFileItemList
>();
184 VIDEO::UTILS::GetItemsForPlayList(itemToQueue
, *queuedItems
);
186 CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MEDIA_PLAY
, 0, -1,
187 static_cast<void*>(queuedItems
.release()));
188 CheckAndSwitchToFullscreen(true);
193 bool CPVRGUIActionsPlayback::PlayEpgTag(
194 const CFileItem
& item
,
195 ContentUtils::PlayMode mode
/* = ContentUtils::PlayMode::CHECK_AUTO_PLAY_NEXT_ITEM */) const
197 const std::shared_ptr
<CPVREpgInfoTag
> epgTag(CPVRItem(item
).GetEpgInfoTag());
201 if (CServiceBroker::GetPVRManager().PlaybackState()->IsPlayingEpgTag(epgTag
))
203 CGUIMessage
msg(GUI_MSG_FULLSCREEN
, 0,
204 CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow());
205 CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg
);
209 // Obtain dynamic playback url and properties from the respective pvr client
210 const std::shared_ptr
<const CPVRClient
> client
=
211 CServiceBroker::GetPVRManager().GetClient(epgTag
->ClientID());
215 CPVRStreamProperties props
;
216 client
->GetEpgTagStreamProperties(epgTag
, props
);
218 CFileItem
* itemToPlay
= nullptr;
219 if (props
.EPGPlaybackAsLive())
221 const std::shared_ptr
<CPVRChannelGroupMember
> groupMember
=
222 CServiceBroker::GetPVRManager().Get
<PVR::GUI::Channels
>().GetChannelGroupMember(item
);
226 itemToPlay
= new CFileItem(groupMember
);
230 itemToPlay
= new CFileItem(epgTag
);
233 CServiceBroker::GetPVRManager().PlaybackState()->StartPlayback(itemToPlay
, mode
);
234 CheckAndSwitchToFullscreen(true);
238 bool CPVRGUIActionsPlayback::SwitchToChannel(const CFileItem
& item
, bool bCheckResume
) const
240 if (item
.m_bIsFolder
)
243 std::shared_ptr
<CPVRRecording
> recording
;
244 const std::shared_ptr
<const CPVRChannel
> channel(CPVRItem(item
).GetChannel());
247 bool bSwitchToFullscreen
=
248 CServiceBroker::GetPVRManager().PlaybackState()->IsPlayingChannel(channel
);
250 if (!bSwitchToFullscreen
)
253 CServiceBroker::GetPVRManager().Recordings()->GetRecordingForEpgTag(channel
->GetEPGNow());
254 bSwitchToFullscreen
=
256 CServiceBroker::GetPVRManager().PlaybackState()->IsPlayingRecording(recording
);
259 if (bSwitchToFullscreen
)
261 CGUIMessage
msg(GUI_MSG_FULLSCREEN
, 0,
262 CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow());
263 CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg
);
268 ParentalCheckResult result
=
269 channel
? CServiceBroker::GetPVRManager().Get
<PVR::GUI::Parental
>().CheckParentalLock(channel
)
270 : ParentalCheckResult::FAILED
;
271 if (result
== ParentalCheckResult::SUCCESS
)
273 // switch to channel or if recording present, ask whether to switch or play recording...
276 CServiceBroker::GetPVRManager().Recordings()->GetRecordingForEpgTag(channel
->GetEPGNow());
281 bool bPlayRecording
= CGUIDialogYesNo::ShowAndGetInput(
282 CVariant
{19687}, // "Play recording"
283 CVariant
{""}, CVariant
{12021}, // "Play from beginning"
284 CVariant
{recording
->m_strTitle
}, bCancel
, CVariant
{19000}, // "Switch to channel"
285 CVariant
{19687}, // "Play recording"
291 return PlayRecording(CFileItem(recording
), bCheckResume
);
295 switch (m_settings
.GetIntValue(CSettings::SETTING_PVRPLAYBACK_SWITCHTOFULLSCREENCHANNELTYPES
))
300 case 1: // TV channels
301 bFullscreen
= !channel
->IsRadio();
303 case 2: // Radio channels
304 bFullscreen
= channel
->IsRadio();
306 case 3: // TV and radio channels
311 const std::shared_ptr
<CPVRChannelGroupMember
> groupMember
=
312 CServiceBroker::GetPVRManager().Get
<PVR::GUI::Channels
>().GetChannelGroupMember(item
);
316 CServiceBroker::GetPVRManager().PlaybackState()->StartPlayback(new CFileItem(groupMember
));
317 CheckAndSwitchToFullscreen(bFullscreen
);
320 else if (result
== ParentalCheckResult::FAILED
)
322 const std::string channelName
=
323 channel
? channel
->ChannelName() : g_localizeStrings
.Get(19029); // Channel
324 const std::string msg
= StringUtils::Format(
325 g_localizeStrings
.Get(19035),
326 channelName
); // CHANNELNAME could not be played. Check the log for details.
328 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Error
, g_localizeStrings
.Get(19166),
329 msg
); // PVR information
335 bool CPVRGUIActionsPlayback::SwitchToChannel(PlaybackType type
) const
337 std::shared_ptr
<CPVRChannelGroupMember
> groupMember
;
338 bool bIsRadio(false);
340 // check if the desired PlaybackType is already playing,
341 // and if not, try to grab the last played channel of this type
344 case PlaybackTypeRadio
:
346 if (CServiceBroker::GetPVRManager().PlaybackState()->IsPlayingRadio())
349 const std::shared_ptr
<CPVRChannelGroup
> allGroup
=
350 CServiceBroker::GetPVRManager().ChannelGroups()->GetGroupAllRadio();
352 groupMember
= allGroup
->GetLastPlayedChannelGroupMember();
359 if (CServiceBroker::GetPVRManager().PlaybackState()->IsPlayingTV())
362 const std::shared_ptr
<const CPVRChannelGroup
> allGroup
=
363 CServiceBroker::GetPVRManager().ChannelGroups()->GetGroupAllTV();
365 groupMember
= allGroup
->GetLastPlayedChannelGroupMember();
370 if (CServiceBroker::GetPVRManager().PlaybackState()->IsPlaying())
374 CServiceBroker::GetPVRManager().ChannelGroups()->GetLastPlayedChannelGroupMember();
378 // if we have a last played channel, start playback
381 return SwitchToChannel(CFileItem(groupMember
), true);
385 // if we don't, find the active channel group of the demanded type and play it's first channel
386 const std::shared_ptr
<const CPVRChannelGroup
> channelGroup
=
387 CServiceBroker::GetPVRManager().PlaybackState()->GetActiveChannelGroup(bIsRadio
);
390 // try to start playback of first channel in this group
391 const std::vector
<std::shared_ptr
<CPVRChannelGroupMember
>> groupMembers
=
392 channelGroup
->GetMembers();
393 if (!groupMembers
.empty())
395 return SwitchToChannel(CFileItem(*groupMembers
.begin()), true);
401 "Could not determine {} channel to playback. No last played channel found, and "
402 "first channel of active group could also not be determined.",
403 bIsRadio
? "Radio" : "TV");
405 CGUIDialogKaiToast::QueueNotification(
406 CGUIDialogKaiToast::Error
,
407 g_localizeStrings
.Get(19166), // PVR information
409 g_localizeStrings
.Get(19035),
410 g_localizeStrings
.Get(
412 : 19020))); // Radio/TV could not be played. Check the log for details.
416 bool CPVRGUIActionsPlayback::PlayChannelOnStartup() const
418 int iAction
= m_settings
.GetIntValue(CSettings::SETTING_LOOKANDFEEL_STARTUPACTION
);
419 if (iAction
!= STARTUP_ACTION_PLAY_TV
&& iAction
!= STARTUP_ACTION_PLAY_RADIO
)
422 bool playRadio
= (iAction
== STARTUP_ACTION_PLAY_RADIO
);
424 // get the last played channel or fallback to first channel of all channels group
425 std::shared_ptr
<CPVRChannelGroupMember
> groupMember
=
426 CServiceBroker::GetPVRManager().PlaybackState()->GetLastPlayedChannelGroupMember(playRadio
);
430 const std::shared_ptr
<const CPVRChannelGroup
> group
=
431 CServiceBroker::GetPVRManager().ChannelGroups()->Get(playRadio
)->GetGroupAll();
432 auto channels
= group
->GetMembers();
433 if (channels
.empty())
436 groupMember
= channels
.front();
441 CLog::Log(LOGINFO
, "PVR is starting playback of channel '{}'",
442 groupMember
->Channel()->ChannelName());
443 return SwitchToChannel(CFileItem(groupMember
), true);
446 bool CPVRGUIActionsPlayback::PlayMedia(const CFileItem
& item
) const
448 std::unique_ptr
<CFileItem
> pvrItem
= std::make_unique
<CFileItem
>(item
);
449 if (URIUtils::IsPVRChannel(item
.GetPath()) && !item
.HasPVRChannelInfoTag())
451 const std::shared_ptr
<CPVRChannelGroupMember
> groupMember
=
452 CServiceBroker::GetPVRManager().ChannelGroups()->GetChannelGroupMemberByPath(
455 pvrItem
= std::make_unique
<CFileItem
>(groupMember
);
457 else if (URIUtils::IsPVRRecording(item
.GetPath()) && !item
.HasPVRRecordingInfoTag())
459 const std::shared_ptr
<CPVRRecording
> recording
=
460 CServiceBroker::GetPVRManager().Recordings()->GetByPath(item
.GetPath());
463 pvrItem
= std::make_unique
<CFileItem
>(recording
);
464 pvrItem
->SetStartOffset(item
.GetStartOffset());
467 bool bCheckResume
= true;
468 if (item
.HasProperty("check_resume"))
469 bCheckResume
= item
.GetProperty("check_resume").asBoolean();
471 if (pvrItem
&& pvrItem
->HasPVRChannelInfoTag())
473 return SwitchToChannel(*pvrItem
, bCheckResume
);
475 else if (pvrItem
&& pvrItem
->HasPVRRecordingInfoTag())
477 return PlayRecording(*pvrItem
, bCheckResume
);
483 void CPVRGUIActionsPlayback::SeekForward()
485 time_t playbackStartTime
= CServiceBroker::GetDataCacheCore().GetStartTime();
486 if (playbackStartTime
> 0)
488 const std::shared_ptr
<const CPVRChannel
> playingChannel
=
489 CServiceBroker::GetPVRManager().PlaybackState()->GetPlayingChannel();
493 std::shared_ptr
<CPVREpgInfoTag
> next
= playingChannel
->GetEPGNext();
496 next
->StartAsUTC().GetAsTime(nextTime
);
500 // if there is no next event, jump to end of currently playing event
501 next
= playingChannel
->GetEPGNow();
503 next
->EndAsUTC().GetAsTime(nextTime
);
506 int64_t seekTime
= 0;
509 seekTime
= (nextTime
- playbackStartTime
) * 1000;
513 // no epg; jump to end of buffer
514 seekTime
= CServiceBroker::GetDataCacheCore().GetMaxTime();
516 CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MEDIA_SEEK_TIME
, seekTime
);
521 void CPVRGUIActionsPlayback::SeekBackward(unsigned int iThreshold
)
523 time_t playbackStartTime
= CServiceBroker::GetDataCacheCore().GetStartTime();
524 if (playbackStartTime
> 0)
526 const std::shared_ptr
<const CPVRChannel
> playingChannel
=
527 CServiceBroker::GetPVRManager().PlaybackState()->GetPlayingChannel();
531 std::shared_ptr
<CPVREpgInfoTag
> prev
= playingChannel
->GetEPGNow();
534 prev
->StartAsUTC().GetAsTime(prevTime
);
536 // if playback time of current event is above threshold jump to start of current event
537 int64_t playTime
= CServiceBroker::GetDataCacheCore().GetPlayTime() / 1000;
538 if ((playbackStartTime
+ playTime
- prevTime
) <= iThreshold
)
540 // jump to start of previous event
542 prev
= playingChannel
->GetEPGPrevious();
544 prev
->StartAsUTC().GetAsTime(prevTime
);
548 int64_t seekTime
= 0;
551 seekTime
= (prevTime
- playbackStartTime
) * 1000;
555 // no epg; jump to begin of buffer
556 seekTime
= CServiceBroker::GetDataCacheCore().GetMinTime();
558 CServiceBroker::GetAppMessenger()->PostMsg(TMSG_MEDIA_SEEK_TIME
, seekTime
);