2 * Copyright (C) 2005-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 "PlayListPlayer.h"
12 #include "FileItemList.h"
13 #include "GUIPassword.h"
14 #include "GUIUserMessages.h"
15 #include "PartyModeManager.h"
16 #include "ServiceBroker.h"
18 #include "application/Application.h"
19 #include "application/ApplicationComponents.h"
20 #include "application/ApplicationPlayer.h"
21 #include "application/ApplicationPowerHandling.h"
22 #include "dialogs/GUIDialogKaiToast.h"
23 #include "filesystem/PluginDirectory.h"
24 #include "filesystem/VideoDatabaseFile.h"
25 #include "guilib/GUIComponent.h"
26 #include "guilib/GUIWindowManager.h"
27 #include "guilib/LocalizeStrings.h"
28 #include "input/actions/Action.h"
29 #include "input/actions/ActionIDs.h"
30 #include "interfaces/AnnouncementManager.h"
31 #include "messaging/ApplicationMessenger.h"
32 #include "messaging/helpers/DialogOKHelper.h"
33 #include "music/MusicFileItemClassify.h"
34 #include "music/tags/MusicInfoTag.h"
35 #include "playlists/PlayList.h"
36 #include "playlists/PlayListFileItemClassify.h"
37 #include "settings/AdvancedSettings.h"
38 #include "settings/SettingsComponent.h"
39 #include "utils/StringUtils.h"
40 #include "utils/URIUtils.h"
41 #include "utils/Variant.h"
42 #include "utils/log.h"
43 #include "video/VideoDatabase.h"
44 #include "video/VideoFileItemClassify.h"
46 using namespace KODI::MESSAGING
;
47 using namespace KODI::VIDEO
;
49 namespace KODI::PLAYLIST
52 CPlayListPlayer::CPlayListPlayer(void)
54 m_PlaylistMusic
= new CPlayList(Id::TYPE_MUSIC
);
55 m_PlaylistVideo
= new CPlayList(Id::TYPE_VIDEO
);
56 m_PlaylistEmpty
= new CPlayList
;
58 m_bPlayedFirstFile
= false;
59 m_bPlaybackStarted
= false;
61 m_failedSongsStart
= std::chrono::steady_clock::now();
64 CPlayListPlayer::~CPlayListPlayer(void)
67 delete m_PlaylistMusic
;
68 delete m_PlaylistVideo
;
69 delete m_PlaylistEmpty
;
72 bool CPlayListPlayer::OnAction(const CAction
&action
)
74 if (action
.GetID() == ACTION_PREV_ITEM
&& !IsSingleItemNonRepeatPlaylist())
79 else if (action
.GetID() == ACTION_NEXT_ITEM
&& !IsSingleItemNonRepeatPlaylist())
88 bool CPlayListPlayer::OnMessage(CGUIMessage
&message
)
90 switch (message
.GetMessage())
92 case GUI_MSG_NOTIFY_ALL
:
93 if (message
.GetParam1() == GUI_MSG_UPDATE_ITEM
&& message
.GetItem())
95 // update the items in our playlist(s) if necessary
96 for (Id playlistId
: {Id::TYPE_MUSIC
, Id::TYPE_VIDEO
})
98 CPlayList
& playlist
= GetPlaylist(playlistId
);
99 CFileItemPtr item
= std::static_pointer_cast
<CFileItem
>(message
.GetItem());
100 playlist
.UpdateItem(item
.get());
104 case GUI_MSG_PLAYBACK_STOPPED
:
106 if (m_iCurrentPlayList
!= Id::TYPE_NONE
&& m_bPlaybackStarted
)
108 CGUIMessage
msg(GUI_MSG_PLAYLISTPLAYER_STOPPED
, 0, 0, static_cast<int>(m_iCurrentPlayList
),
110 CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg
);
112 m_iCurrentPlayList
= Id::TYPE_NONE
;
117 case GUI_MSG_PLAYBACK_STARTED
:
119 m_bPlaybackStarted
= true;
127 int CPlayListPlayer::GetNextItemIdx(int offset
) const
129 if (m_iCurrentPlayList
== Id::TYPE_NONE
)
132 const CPlayList
& playlist
= GetPlaylist(m_iCurrentPlayList
);
133 if (playlist
.size() <= 0)
136 int song
= m_iCurrentSong
;
139 if (g_partyModeManager
.IsEnabled() && GetCurrentPlaylist() == Id::TYPE_MUSIC
)
140 return song
+ offset
;
142 // wrap around in the case of repeating
143 if (RepeatedOne(m_iCurrentPlayList
))
147 if (song
>= playlist
.size() && Repeated(m_iCurrentPlayList
))
148 song
%= playlist
.size();
153 int CPlayListPlayer::GetNextItemIdx()
155 if (m_iCurrentPlayList
== Id::TYPE_NONE
)
157 CPlayList
& playlist
= GetPlaylist(m_iCurrentPlayList
);
158 if (playlist
.size() <= 0)
160 int iSong
= m_iCurrentSong
;
163 if (g_partyModeManager
.IsEnabled() && GetCurrentPlaylist() == Id::TYPE_MUSIC
)
166 // if repeat one, keep playing the current song if its valid
167 if (RepeatedOne(m_iCurrentPlayList
))
169 // otherwise immediately abort playback
170 if (m_iCurrentSong
>= 0 && m_iCurrentSong
< playlist
.size() && playlist
[m_iCurrentSong
]->GetProperty("unplayable").asBoolean())
172 CLog::Log(LOGERROR
, "Playlist Player: RepeatOne stuck on unplayable item: {}, path [{}]",
173 m_iCurrentSong
, playlist
[m_iCurrentSong
]->GetPath());
174 CGUIMessage
msg(GUI_MSG_PLAYLISTPLAYER_STOPPED
, 0, 0, static_cast<int>(m_iCurrentPlayList
),
176 CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg
);
178 m_iCurrentPlayList
= Id::TYPE_NONE
;
184 // if we've gone beyond the playlist and repeat all is enabled,
185 // then we clear played status and wrap around
187 if (iSong
>= playlist
.size() && Repeated(m_iCurrentPlayList
))
193 bool CPlayListPlayer::PlayNext(int offset
, bool bAutoPlay
)
195 int iSong
= GetNextItemIdx(offset
);
196 const CPlayList
& playlist
= GetPlaylist(m_iCurrentPlayList
);
198 if ((iSong
< 0) || (iSong
>= playlist
.size()) || (playlist
.GetPlayable() <= 0))
201 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info
, g_localizeStrings
.Get(559), g_localizeStrings
.Get(34201));
203 CGUIMessage
msg(GUI_MSG_PLAYLISTPLAYER_STOPPED
, 0, 0, static_cast<int>(m_iCurrentPlayList
),
205 CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg
);
207 m_iCurrentPlayList
= Id::TYPE_NONE
;
211 const auto& components
= CServiceBroker::GetAppComponents();
212 const auto appPlayer
= components
.GetComponent
<CApplicationPlayer
>();
213 const std::string player
= appPlayer
->GetName();
215 return Play(iSong
, player
, false);
218 bool CPlayListPlayer::PlayPrevious()
220 if (m_iCurrentPlayList
== Id::TYPE_NONE
)
223 const CPlayList
& playlist
= GetPlaylist(m_iCurrentPlayList
);
224 int iSong
= m_iCurrentSong
;
226 if (!RepeatedOne(m_iCurrentPlayList
))
229 if (iSong
< 0 && Repeated(m_iCurrentPlayList
))
230 iSong
= playlist
.size() - 1;
232 if (iSong
< 0 || playlist
.size() <= 0)
234 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info
, g_localizeStrings
.Get(559), g_localizeStrings
.Get(34202));
238 return Play(iSong
, "", false, true);
241 bool CPlayListPlayer::IsSingleItemNonRepeatPlaylist() const
243 const CPlayList
& playlist
= GetPlaylist(m_iCurrentPlayList
);
244 return (playlist
.size() <= 1 && !RepeatedOne(m_iCurrentPlayList
) && !Repeated(m_iCurrentPlayList
));
247 bool CPlayListPlayer::Play()
249 if (m_iCurrentPlayList
== Id::TYPE_NONE
)
252 const CPlayList
& playlist
= GetPlaylist(m_iCurrentPlayList
);
253 if (playlist
.size() <= 0)
259 bool CPlayListPlayer::PlayItemIdx(int itemIdx
)
261 if (m_iCurrentPlayList
== Id::TYPE_NONE
)
264 CPlayList
& playlist
= GetPlaylist(m_iCurrentPlayList
);
265 if (playlist
.size() <= 0)
268 for (int i
= 0; i
< playlist
.size(); i
++)
270 if (playlist
[i
]->HasMusicInfoTag() &&
271 playlist
[i
]->GetMusicInfoTag()->GetDatabaseId() == itemIdx
)
277 bool CPlayListPlayer::Play(const CFileItemPtr
& pItem
,
278 const std::string
& player
,
279 bool forceSelection
/* = false */)
282 bool isVideo
{IsVideo(*pItem
)};
283 bool isAudio
{MUSIC::IsAudio(*pItem
)};
285 if (isAudio
&& !isVideo
)
286 playlistId
= Id::TYPE_MUSIC
;
287 else if (isVideo
&& !isAudio
)
288 playlistId
= Id::TYPE_VIDEO
;
289 else if (pItem
->HasProperty("playlist_type_hint"))
291 // There are two main cases that can fall here:
292 // - If an extension is set on both audio / video extension lists example .strm
293 // see GetFileExtensionProvider() -> GetVideoExtensions() / GetAudioExtensions()
294 // When you play the .strm containing single path, cause that
295 // IsVideo() and IsAudio() methods both return true
297 // - When you play a playlist (e.g. .m3u / .strm) containing multiple paths,
298 // and the path played is generic (e.g.without extension) and have no properties
299 // to detect the media type, IsVideo() / IsAudio() both return false
301 // for these cases the type is unknown so we rely on the hint
303 Id
{pItem
->GetProperty("playlist_type_hint").asInteger32(static_cast<int>(Id::TYPE_NONE
))};
307 CLog::LogF(LOGWARNING
, "ListItem type must be audio or video type. The type can be specified "
308 "by using ListItem::getVideoInfoTag or ListItem::getMusicInfoTag, in "
309 "the case of playlist entries by adding #KODIPROP mimetype value.");
313 ClearPlaylist(playlistId
);
315 SetCurrentPlaylist(playlistId
);
316 Add(playlistId
, pItem
);
318 return Play(0, player
, false, false, forceSelection
);
321 bool CPlayListPlayer::Play(int iSong
,
322 const std::string
& player
,
323 bool bAutoPlay
/* = false */,
324 bool bPlayPrevious
/* = false */,
325 bool forceSelection
/* = false */)
327 if (m_iCurrentPlayList
== Id::TYPE_NONE
)
330 CPlayList
& playlist
= GetPlaylist(m_iCurrentPlayList
);
331 if (playlist
.size() <= 0)
335 if (iSong
>= playlist
.size())
336 iSong
= playlist
.size() - 1;
338 // check if the item itself is a playlist, and can be expanded
339 // only allow a few levels, this could end up in a loop
340 // if they refer to each other in a loop
341 for (int i
=0; i
<5; i
++)
343 if(!playlist
.Expand(iSong
))
347 m_iCurrentSong
= iSong
;
348 CFileItemPtr item
= playlist
[m_iCurrentSong
];
349 if (IsVideoDb(*item
) && !item
->HasVideoInfoTag())
350 *(item
->GetVideoInfoTag()) = XFILE::CVideoDatabaseFile::GetVideoTag(CURL(item
->GetDynPath()));
352 playlist
.SetPlayed(true);
354 m_bPlaybackStarted
= false;
356 const auto playAttempt
= std::chrono::steady_clock::now();
357 bool ret
= g_application
.PlayFile(*item
, player
, bAutoPlay
, forceSelection
);
360 CLog::Log(LOGERROR
, "Playlist Player: skipping unplayable item: {}, path [{}]", m_iCurrentSong
,
361 CURL::GetRedacted(item
->GetDynPath()));
362 playlist
.SetUnPlayable(m_iCurrentSong
);
364 // abort on 100 failed CONSECUTIVE songs
366 m_failedSongsStart
= playAttempt
;
368 const std::shared_ptr
<CAdvancedSettings
> advancedSettings
= CServiceBroker::GetSettingsComponent()->GetAdvancedSettings();
370 auto now
= std::chrono::steady_clock::now();
371 auto duration
= std::chrono::duration_cast
<std::chrono::milliseconds
>(now
- m_failedSongsStart
);
373 if ((m_iFailedSongs
>= advancedSettings
->m_playlistRetries
&&
374 advancedSettings
->m_playlistRetries
>= 0) ||
375 ((duration
.count() >=
376 static_cast<unsigned int>(advancedSettings
->m_playlistTimeout
) * 1000) &&
377 advancedSettings
->m_playlistTimeout
))
379 CLog::Log(LOGDEBUG
,"Playlist Player: one or more items failed to play... aborting playback");
382 HELPERS::ShowOKDialogText(CVariant
{16026}, CVariant
{16027});
384 CGUIMessage
msg(GUI_MSG_PLAYLISTPLAYER_STOPPED
, 0, 0, static_cast<int>(m_iCurrentPlayList
),
386 CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg
);
388 GetPlaylist(m_iCurrentPlayList
).Clear();
389 m_iCurrentPlayList
= Id::TYPE_NONE
;
391 m_failedSongsStart
= std::chrono::steady_clock::now();
395 // how many playable items are in the playlist?
396 if (playlist
.GetPlayable() > 0)
398 return bPlayPrevious
? PlayPrevious() : PlayNext();
400 // none? then abort playback
403 CLog::Log(LOGDEBUG
,"Playlist Player: no more playable items... aborting playback");
404 CGUIMessage
msg(GUI_MSG_PLAYLISTPLAYER_STOPPED
, 0, 0, static_cast<int>(m_iCurrentPlayList
),
406 CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg
);
408 m_iCurrentPlayList
= Id::TYPE_NONE
;
413 // reset the start offset of this item
414 if (item
->GetStartOffset() == STARTOFFSET_RESUME
)
415 item
->SetStartOffset(0);
417 //! @todo - move the above failure logic and the below success logic
418 //! to callbacks instead so we don't rely on the return value
421 // consecutive error counter so reset if the current item is playing
423 m_failedSongsStart
= std::chrono::steady_clock::now();
424 m_bPlayedFirstFile
= true;
428 void CPlayListPlayer::SetCurrentItemIdx(int iSong
)
430 if (iSong
>= -1 && iSong
< GetPlaylist(m_iCurrentPlayList
).size())
431 m_iCurrentSong
= iSong
;
434 int CPlayListPlayer::GetCurrentItemIdx() const
436 return m_iCurrentSong
;
439 Id
CPlayListPlayer::GetCurrentPlaylist() const
441 return m_iCurrentPlayList
;
444 void CPlayListPlayer::SetCurrentPlaylist(Id playlistId
)
446 if (playlistId
== m_iCurrentPlayList
)
449 // changing the current playlist while party mode is on
450 // disables party mode
451 if (g_partyModeManager
.IsEnabled())
452 g_partyModeManager
.Disable();
454 m_iCurrentPlayList
= playlistId
;
455 m_bPlayedFirstFile
= false;
458 void CPlayListPlayer::ClearPlaylist(Id playlistId
)
460 // clear our applications playlist file
461 g_application
.m_strPlayListFile
.clear();
463 CPlayList
& playlist
= GetPlaylist(playlistId
);
466 // its likely that the playlist changed
467 CGUIMessage
msg(GUI_MSG_PLAYLIST_CHANGED
, 0, 0);
468 CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg
);
471 CPlayList
& CPlayListPlayer::GetPlaylist(Id playlistId
)
476 return *m_PlaylistMusic
;
479 return *m_PlaylistVideo
;
482 m_PlaylistEmpty
->Clear();
483 return *m_PlaylistEmpty
;
488 const CPlayList
& CPlayListPlayer::GetPlaylist(Id playlistId
) const
493 return *m_PlaylistMusic
;
496 return *m_PlaylistVideo
;
499 // NOTE: This playlist may not be empty if the caller of the non-const version alters it!
500 return *m_PlaylistEmpty
;
505 int CPlayListPlayer::RemoveDVDItems()
507 int nRemovedM
= m_PlaylistMusic
->RemoveDVDItems();
508 int nRemovedV
= m_PlaylistVideo
->RemoveDVDItems();
510 return nRemovedM
+ nRemovedV
;
513 void CPlayListPlayer::Reset()
516 m_bPlayedFirstFile
= false;
517 m_bPlaybackStarted
= false;
519 // its likely that the playlist changed
520 CGUIMessage
msg(GUI_MSG_PLAYLIST_CHANGED
, 0, 0);
521 CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg
);
524 bool CPlayListPlayer::HasPlayedFirstFile() const
526 return m_bPlayedFirstFile
;
529 bool CPlayListPlayer::Repeated(Id playlistId
) const
531 const auto repStatePos
= m_repeatState
.find(playlistId
);
532 if (repStatePos
!= m_repeatState
.end())
533 return repStatePos
->second
== RepeatState::ALL
;
537 bool CPlayListPlayer::RepeatedOne(Id playlistId
) const
539 const auto repStatePos
= m_repeatState
.find(playlistId
);
540 if (repStatePos
!= m_repeatState
.end())
541 return (repStatePos
->second
== RepeatState::ONE
);
545 void CPlayListPlayer::SetShuffle(Id playlistId
, bool bYesNo
, bool bNotify
/* = false */)
547 if (playlistId
!= Id::TYPE_MUSIC
&& playlistId
!= Id::TYPE_VIDEO
)
550 // disable shuffle in party mode
551 if (g_partyModeManager
.IsEnabled() && playlistId
== Id::TYPE_MUSIC
)
554 // do we even need to do anything?
555 if (bYesNo
!= IsShuffled(playlistId
))
557 // save the order value of the current song so we can use it find its new location later
559 CPlayList
& playlist
= GetPlaylist(playlistId
);
560 if (m_iCurrentSong
>= 0 && m_iCurrentSong
< playlist
.size())
561 iOrder
= playlist
[m_iCurrentSong
]->m_iprogramCount
;
563 // shuffle or unshuffle as necessary
567 playlist
.UnShuffle();
571 std::string shuffleStr
=
572 StringUtils::Format("{}: {}", g_localizeStrings
.Get(191),
573 g_localizeStrings
.Get(bYesNo
? 593 : 591)); // Shuffle: All/Off
574 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info
, g_localizeStrings
.Get(559), shuffleStr
);
577 // find the previous order value and fix the current song marker
580 int iIndex
= playlist
.FindOrder(iOrder
);
582 m_iCurrentSong
= iIndex
;
583 // if iIndex < 0, something unexpected happened
584 // so dont do anything
588 // its likely that the playlist changed
589 if (CServiceBroker::GetGUI() != nullptr)
591 CGUIMessage
msg(GUI_MSG_PLAYLIST_CHANGED
, 0, 0);
592 CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg
);
595 AnnouncePropertyChanged(playlistId
, "shuffled", IsShuffled(playlistId
));
598 bool CPlayListPlayer::IsShuffled(Id playlistId
) const
600 // even if shuffled, party mode says its not
601 if (g_partyModeManager
.IsEnabled() && playlistId
== Id::TYPE_MUSIC
)
604 if (playlistId
== Id::TYPE_MUSIC
|| playlistId
== Id::TYPE_VIDEO
)
605 return GetPlaylist(playlistId
).IsShuffled();
610 void CPlayListPlayer::SetRepeat(Id playlistId
, RepeatState state
, bool bNotify
/* = false */)
612 if (playlistId
!= Id::TYPE_MUSIC
&& playlistId
!= Id::TYPE_VIDEO
)
615 // disable repeat in party mode
616 if (g_partyModeManager
.IsEnabled() && playlistId
== Id::TYPE_MUSIC
)
617 state
= RepeatState::NONE
;
619 // notify the user if there was a change in the repeat state
620 if (m_repeatState
[playlistId
] != state
&& bNotify
)
622 int iLocalizedString
;
623 if (state
== RepeatState::NONE
)
624 iLocalizedString
= 595; // Repeat: Off
625 else if (state
== RepeatState::ONE
)
626 iLocalizedString
= 596; // Repeat: One
628 iLocalizedString
= 597; // Repeat: All
629 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info
, g_localizeStrings
.Get(559), g_localizeStrings
.Get(iLocalizedString
));
632 m_repeatState
[playlistId
] = state
;
637 case RepeatState::ONE
:
640 case RepeatState::ALL
:
648 // its likely that the playlist changed
649 if (CServiceBroker::GetGUI() != nullptr)
651 CGUIMessage
msg(GUI_MSG_PLAYLIST_CHANGED
, 0, 0);
652 CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg
);
655 AnnouncePropertyChanged(playlistId
, "repeat", data
);
658 RepeatState
CPlayListPlayer::GetRepeat(Id playlistId
) const
660 const auto repStatePos
= m_repeatState
.find(playlistId
);
661 if (repStatePos
!= m_repeatState
.end())
662 return repStatePos
->second
;
663 return RepeatState::NONE
;
666 void CPlayListPlayer::ReShuffle(Id playlistId
, int iPosition
)
668 // playlist has not played yet so shuffle the entire list
669 // (this only really works for new video playlists)
670 if (!GetPlaylist(playlistId
).WasPlayed())
672 GetPlaylist(playlistId
).Shuffle();
674 // we're trying to shuffle new items into the currently playing playlist
675 // so we shuffle starting at two positions below the current item
676 else if (playlistId
== m_iCurrentPlayList
)
678 const auto& components
= CServiceBroker::GetAppComponents();
679 const auto appPlayer
= components
.GetComponent
<CApplicationPlayer
>();
680 if ((appPlayer
->IsPlayingAudio() && playlistId
== Id::TYPE_MUSIC
) ||
681 (appPlayer
->IsPlayingVideo() && playlistId
== Id::TYPE_VIDEO
))
683 GetPlaylist(playlistId
).Shuffle(m_iCurrentSong
+ 2);
686 // otherwise, shuffle from the passed position
687 // which is the position of the first new item added
690 GetPlaylist(playlistId
).Shuffle(iPosition
);
694 void CPlayListPlayer::Add(Id playlistId
, const CPlayList
& playlist
)
696 if (playlistId
!= Id::TYPE_MUSIC
&& playlistId
!= Id::TYPE_VIDEO
)
698 CPlayList
& list
= GetPlaylist(playlistId
);
699 int iSize
= list
.size();
701 if (list
.IsShuffled())
702 ReShuffle(playlistId
, iSize
);
705 void CPlayListPlayer::Add(Id playlistId
, const CFileItemPtr
& pItem
)
707 if (playlistId
!= Id::TYPE_MUSIC
&& playlistId
!= Id::TYPE_VIDEO
)
709 CPlayList
& list
= GetPlaylist(playlistId
);
710 int iSize
= list
.size();
712 if (list
.IsShuffled())
713 ReShuffle(playlistId
, iSize
);
715 // its likely that the playlist changed
716 CGUIMessage
msg(GUI_MSG_PLAYLIST_CHANGED
, 0, 0);
717 CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg
);
720 void CPlayListPlayer::Add(Id playlistId
, const CFileItemList
& items
)
722 if (playlistId
!= Id::TYPE_MUSIC
&& playlistId
!= Id::TYPE_VIDEO
)
724 CPlayList
& list
= GetPlaylist(playlistId
);
725 int iSize
= list
.size();
727 if (list
.IsShuffled())
728 ReShuffle(playlistId
, iSize
);
730 // its likely that the playlist changed
731 CGUIMessage
msg(GUI_MSG_PLAYLIST_CHANGED
, 0, 0);
732 CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg
);
735 void CPlayListPlayer::Insert(Id playlistId
, const CPlayList
& playlist
, int iIndex
)
737 if (playlistId
!= Id::TYPE_MUSIC
&& playlistId
!= Id::TYPE_VIDEO
)
739 CPlayList
& list
= GetPlaylist(playlistId
);
740 int iSize
= list
.size();
741 list
.Insert(playlist
, iIndex
);
742 if (list
.IsShuffled())
743 ReShuffle(playlistId
, iSize
);
744 else if (m_iCurrentPlayList
== playlistId
&& m_iCurrentSong
>= iIndex
)
748 void CPlayListPlayer::Insert(Id playlistId
, const CFileItemPtr
& pItem
, int iIndex
)
750 if (playlistId
!= Id::TYPE_MUSIC
&& playlistId
!= Id::TYPE_VIDEO
)
752 CPlayList
& list
= GetPlaylist(playlistId
);
753 int iSize
= list
.size();
754 list
.Insert(pItem
, iIndex
);
755 if (list
.IsShuffled())
756 ReShuffle(playlistId
, iSize
);
757 else if (m_iCurrentPlayList
== playlistId
&& m_iCurrentSong
>= iIndex
)
761 void CPlayListPlayer::Insert(Id playlistId
, const CFileItemList
& items
, int iIndex
)
763 if (playlistId
!= Id::TYPE_MUSIC
&& playlistId
!= Id::TYPE_VIDEO
)
765 CPlayList
& list
= GetPlaylist(playlistId
);
766 int iSize
= list
.size();
767 list
.Insert(items
, iIndex
);
768 if (list
.IsShuffled())
769 ReShuffle(playlistId
, iSize
);
770 else if (m_iCurrentPlayList
== playlistId
&& m_iCurrentSong
>= iIndex
)
773 // its likely that the playlist changed
774 CGUIMessage
msg(GUI_MSG_PLAYLIST_CHANGED
, 0, 0);
775 CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg
);
778 void CPlayListPlayer::Remove(Id playlistId
, int iPosition
)
780 if (playlistId
!= Id::TYPE_MUSIC
&& playlistId
!= Id::TYPE_VIDEO
)
782 CPlayList
& list
= GetPlaylist(playlistId
);
783 list
.Remove(iPosition
);
784 if (m_iCurrentPlayList
== playlistId
&& m_iCurrentSong
>= iPosition
)
787 // its likely that the playlist changed
788 CGUIMessage
msg(GUI_MSG_PLAYLIST_CHANGED
, 0, 0);
789 CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg
);
792 void CPlayListPlayer::Clear()
795 m_PlaylistMusic
->Clear();
797 m_PlaylistVideo
->Clear();
799 m_PlaylistEmpty
->Clear();
802 void CPlayListPlayer::Swap(Id playlistId
, int indexItem1
, int indexItem2
)
804 if (playlistId
!= Id::TYPE_MUSIC
&& playlistId
!= Id::TYPE_VIDEO
)
807 CPlayList
& list
= GetPlaylist(playlistId
);
808 if (list
.Swap(indexItem1
, indexItem2
) && playlistId
== m_iCurrentPlayList
)
810 if (m_iCurrentSong
== indexItem1
)
811 m_iCurrentSong
= indexItem2
;
812 else if (m_iCurrentSong
== indexItem2
)
813 m_iCurrentSong
= indexItem1
;
816 // its likely that the playlist changed
817 CGUIMessage
msg(GUI_MSG_PLAYLIST_CHANGED
, 0, 0);
818 CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg
);
821 void CPlayListPlayer::AnnouncePropertyChanged(Id playlistId
,
822 const std::string
& strProperty
,
823 const CVariant
& value
)
825 const auto& components
= CServiceBroker::GetAppComponents();
826 const auto appPlayer
= components
.GetComponent
<CApplicationPlayer
>();
828 if (strProperty
.empty() || value
.isNull() ||
829 (playlistId
== Id::TYPE_VIDEO
&& !appPlayer
->IsPlayingVideo()) ||
830 (playlistId
== Id::TYPE_MUSIC
&& !appPlayer
->IsPlayingAudio()))
834 data
["player"]["playerid"] = static_cast<int>(playlistId
);
835 data
["property"][strProperty
] = value
;
836 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player
, "OnPropertyChanged",
840 int PLAYLIST::CPlayListPlayer::GetMessageMask()
842 return TMSG_MASK_PLAYLISTPLAYER
;
845 void PLAYLIST::CPlayListPlayer::OnApplicationMessage(KODI::MESSAGING::ThreadMessage
* pMsg
)
847 auto& components
= CServiceBroker::GetAppComponents();
848 const auto appPlayer
= components
.GetComponent
<CApplicationPlayer
>();
850 auto wakeScreensaver
= []() {
851 auto& components
= CServiceBroker::GetAppComponents();
852 const auto appPower
= components
.GetComponent
<CApplicationPowerHandling
>();
853 appPower
->ResetScreenSaver();
854 appPower
->WakeUpScreenSaverAndDPMS();
857 switch (pMsg
->dwMessage
)
859 case TMSG_PLAYLISTPLAYER_PLAY
:
860 if (pMsg
->param1
!= -1)
861 Play(pMsg
->param1
, "");
866 case TMSG_PLAYLISTPLAYER_PLAY_ITEM_ID
:
867 if (pMsg
->param1
!= -1)
869 bool *result
= (bool*)pMsg
->lpVoid
;
870 *result
= PlayItemIdx(pMsg
->param1
);
876 case TMSG_PLAYLISTPLAYER_NEXT
:
880 case TMSG_PLAYLISTPLAYER_PREV
:
884 case TMSG_PLAYLISTPLAYER_ADD
:
887 CFileItemList
*list
= static_cast<CFileItemList
*>(pMsg
->lpVoid
);
889 Add(Id
{pMsg
->param1
}, (*list
));
894 case TMSG_PLAYLISTPLAYER_INSERT
:
897 CFileItemList
*list
= static_cast<CFileItemList
*>(pMsg
->lpVoid
);
898 Insert(Id
{pMsg
->param1
}, (*list
), pMsg
->param2
);
903 case TMSG_PLAYLISTPLAYER_REMOVE
:
904 if (pMsg
->param1
!= -1)
905 Remove(Id
{pMsg
->param1
}, pMsg
->param2
);
908 case TMSG_PLAYLISTPLAYER_CLEAR
:
909 ClearPlaylist(Id
{pMsg
->param1
});
912 case TMSG_PLAYLISTPLAYER_SHUFFLE
:
913 SetShuffle(Id
{pMsg
->param1
}, pMsg
->param2
> 0);
916 case TMSG_PLAYLISTPLAYER_REPEAT
:
917 SetRepeat(Id
{pMsg
->param1
}, static_cast<RepeatState
>(pMsg
->param2
));
920 case TMSG_PLAYLISTPLAYER_GET_ITEMS
:
923 PLAYLIST::CPlayList playlist
= GetPlaylist(Id
{pMsg
->param1
});
924 CFileItemList
*list
= static_cast<CFileItemList
*>(pMsg
->lpVoid
);
926 for (int i
= 0; i
< playlist
.size(); i
++)
927 list
->Add(std::make_shared
<CFileItem
>(*playlist
[i
]));
931 case TMSG_PLAYLISTPLAYER_SWAP
:
934 auto indexes
= static_cast<std::vector
<int>*>(pMsg
->lpVoid
);
935 if (indexes
->size() == 2)
936 Swap(Id
{pMsg
->param1
}, indexes
->at(0), indexes
->at(1));
941 case TMSG_MEDIA_PLAY
:
945 // first check if we were called from the PlayFile() function
946 if (pMsg
->lpVoid
&& pMsg
->param2
== 0)
948 // Discard the current playlist, if TMSG_MEDIA_PLAY gets posted with just a single item.
949 // Otherwise items may fail to play, when started while a playlist is playing.
952 CFileItem
*item
= static_cast<CFileItem
*>(pMsg
->lpVoid
);
953 g_application
.PlayFile(*item
, "", pMsg
->param1
!= 0);
958 //g_application.StopPlaying();
962 CFileItemList
*list
= static_cast<CFileItemList
*>(pMsg
->lpVoid
);
964 if (list
->Size() > 0)
966 Id playlistId
= Id::TYPE_MUSIC
;
967 for (int i
= 0; i
< list
->Size(); i
++)
969 if (IsVideo(*list
->Get(i
)))
971 playlistId
= Id::TYPE_VIDEO
;
976 ClearPlaylist(playlistId
);
977 SetCurrentPlaylist(playlistId
);
978 if (list
->Size() == 1 && !IsPlayList(*list
->Get(0)))
980 CFileItemPtr item
= (*list
)[0];
981 // if the item is a plugin we need to resolve the URL to ensure the infotags are filled.
982 if (URIUtils::HasPluginPath(*item
) &&
983 !XFILE::CPluginDirectory::GetResolvedPluginResult(*item
))
987 const bool isVideo
{VIDEO::IsVideo(*item
)};
988 const bool isAudio
{MUSIC::IsAudio(*item
)};
989 if (isAudio
|| isVideo
)
991 if ((isVideo
&& !g_passwordManager
.IsVideoUnlocked()) ||
992 (isAudio
&& !g_passwordManager
.IsMusicUnlocked()))
995 "MasterCode or MediaSource-code is wrong: {} will not be played.",
999 Play(item
, pMsg
->strParam
);
1002 g_application
.PlayMedia(*item
, pMsg
->strParam
, playlistId
);
1006 // Handle "shuffled" option if present
1007 if (list
->HasProperty("shuffled") && list
->GetProperty("shuffled").isBoolean())
1008 SetShuffle(playlistId
, list
->GetProperty("shuffled").asBoolean(), false);
1009 // Handle "repeat" option if present
1010 if (list
->HasProperty("repeat") && list
->GetProperty("repeat").isInteger())
1011 SetRepeat(playlistId
, static_cast<RepeatState
>(list
->GetProperty("repeat").asInteger()),
1014 Add(playlistId
, (*list
));
1015 Play(pMsg
->param1
, pMsg
->strParam
);
1021 else if (Id
{pMsg
->param1
} == Id::TYPE_MUSIC
|| Id
{pMsg
->param1
} == Id::TYPE_VIDEO
)
1023 if (GetCurrentPlaylist() != Id
{pMsg
->param1
})
1024 SetCurrentPlaylist(Id
{pMsg
->param1
});
1026 CServiceBroker::GetAppMessenger()->SendMsg(TMSG_PLAYLISTPLAYER_PLAY
, pMsg
->param2
);
1031 case TMSG_MEDIA_RESTART
:
1032 g_application
.Restart(true);
1035 case TMSG_MEDIA_STOP
:
1037 // restore to previous window if needed
1038 bool stopSlideshow
= true;
1039 bool stopVideo
= true;
1040 bool stopMusic
= true;
1042 Id playlistId
= Id
{pMsg
->param1
};
1043 if (playlistId
!= Id::TYPE_NONE
)
1045 stopSlideshow
= (playlistId
== Id::TYPE_PICTURE
);
1046 stopVideo
= (playlistId
== Id::TYPE_VIDEO
);
1047 stopMusic
= (playlistId
== Id::TYPE_MUSIC
);
1050 if ((stopSlideshow
&& CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_SLIDESHOW
) ||
1051 (stopVideo
&& CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_FULLSCREEN_VIDEO
) ||
1052 (stopVideo
&& CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_FULLSCREEN_GAME
) ||
1053 (stopMusic
&& CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_VISUALISATION
))
1054 CServiceBroker::GetGUI()->GetWindowManager().PreviousWindow();
1058 // stop playing file
1059 if (appPlayer
->IsPlaying())
1060 g_application
.StopPlaying();
1064 case TMSG_MEDIA_PAUSE
:
1065 if (appPlayer
->HasPlayer())
1072 case TMSG_MEDIA_UNPAUSE
:
1073 if (appPlayer
->IsPausedPlayback())
1080 case TMSG_MEDIA_PAUSE_IF_PLAYING
:
1081 if (appPlayer
->IsPlaying() && !appPlayer
->IsPaused())
1088 case TMSG_MEDIA_SEEK_TIME
:
1090 if (appPlayer
->IsPlaying() || appPlayer
->IsPaused())
1091 appPlayer
->SeekTime(pMsg
->param3
);
1100 } // namespace KODI::PLAYLIST