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 "GUIUserMessages.h"
14 #include "PartyModeManager.h"
15 #include "ServiceBroker.h"
17 #include "application/Application.h"
18 #include "application/ApplicationComponents.h"
19 #include "application/ApplicationPlayer.h"
20 #include "application/ApplicationPowerHandling.h"
21 #include "dialogs/GUIDialogKaiToast.h"
22 #include "filesystem/PluginDirectory.h"
23 #include "filesystem/VideoDatabaseFile.h"
24 #include "guilib/GUIComponent.h"
25 #include "guilib/GUIWindowManager.h"
26 #include "guilib/LocalizeStrings.h"
27 #include "input/actions/Action.h"
28 #include "input/actions/ActionIDs.h"
29 #include "interfaces/AnnouncementManager.h"
30 #include "messaging/ApplicationMessenger.h"
31 #include "messaging/helpers/DialogOKHelper.h"
32 #include "music/MusicFileItemClassify.h"
33 #include "music/tags/MusicInfoTag.h"
34 #include "playlists/PlayList.h"
35 #include "playlists/PlayListFileItemClassify.h"
36 #include "settings/AdvancedSettings.h"
37 #include "settings/SettingsComponent.h"
38 #include "utils/StringUtils.h"
39 #include "utils/URIUtils.h"
40 #include "utils/Variant.h"
41 #include "utils/log.h"
42 #include "video/VideoDatabase.h"
43 #include "video/VideoFileItemClassify.h"
45 using namespace KODI::MESSAGING
;
46 using namespace KODI::VIDEO
;
48 namespace KODI::PLAYLIST
51 CPlayListPlayer::CPlayListPlayer(void)
53 m_PlaylistMusic
= new CPlayList(Id::TYPE_MUSIC
);
54 m_PlaylistVideo
= new CPlayList(Id::TYPE_VIDEO
);
55 m_PlaylistEmpty
= new CPlayList
;
57 m_bPlayedFirstFile
= false;
58 m_bPlaybackStarted
= false;
60 m_failedSongsStart
= std::chrono::steady_clock::now();
63 CPlayListPlayer::~CPlayListPlayer(void)
66 delete m_PlaylistMusic
;
67 delete m_PlaylistVideo
;
68 delete m_PlaylistEmpty
;
71 bool CPlayListPlayer::OnAction(const CAction
&action
)
73 if (action
.GetID() == ACTION_PREV_ITEM
&& !IsSingleItemNonRepeatPlaylist())
78 else if (action
.GetID() == ACTION_NEXT_ITEM
&& !IsSingleItemNonRepeatPlaylist())
87 bool CPlayListPlayer::OnMessage(CGUIMessage
&message
)
89 switch (message
.GetMessage())
91 case GUI_MSG_NOTIFY_ALL
:
92 if (message
.GetParam1() == GUI_MSG_UPDATE_ITEM
&& message
.GetItem())
94 // update the items in our playlist(s) if necessary
95 for (Id playlistId
: {Id::TYPE_MUSIC
, Id::TYPE_VIDEO
})
97 CPlayList
& playlist
= GetPlaylist(playlistId
);
98 CFileItemPtr item
= std::static_pointer_cast
<CFileItem
>(message
.GetItem());
99 playlist
.UpdateItem(item
.get());
103 case GUI_MSG_PLAYBACK_STOPPED
:
105 if (m_iCurrentPlayList
!= Id::TYPE_NONE
&& m_bPlaybackStarted
)
107 CGUIMessage
msg(GUI_MSG_PLAYLISTPLAYER_STOPPED
, 0, 0, static_cast<int>(m_iCurrentPlayList
),
109 CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg
);
111 m_iCurrentPlayList
= Id::TYPE_NONE
;
116 case GUI_MSG_PLAYBACK_STARTED
:
118 m_bPlaybackStarted
= true;
126 int CPlayListPlayer::GetNextItemIdx(int offset
) const
128 if (m_iCurrentPlayList
== Id::TYPE_NONE
)
131 const CPlayList
& playlist
= GetPlaylist(m_iCurrentPlayList
);
132 if (playlist
.size() <= 0)
135 int song
= m_iCurrentSong
;
138 if (g_partyModeManager
.IsEnabled() && GetCurrentPlaylist() == Id::TYPE_MUSIC
)
139 return song
+ offset
;
141 // wrap around in the case of repeating
142 if (RepeatedOne(m_iCurrentPlayList
))
146 if (song
>= playlist
.size() && Repeated(m_iCurrentPlayList
))
147 song
%= playlist
.size();
152 int CPlayListPlayer::GetNextItemIdx()
154 if (m_iCurrentPlayList
== Id::TYPE_NONE
)
156 CPlayList
& playlist
= GetPlaylist(m_iCurrentPlayList
);
157 if (playlist
.size() <= 0)
159 int iSong
= m_iCurrentSong
;
162 if (g_partyModeManager
.IsEnabled() && GetCurrentPlaylist() == Id::TYPE_MUSIC
)
165 // if repeat one, keep playing the current song if its valid
166 if (RepeatedOne(m_iCurrentPlayList
))
168 // otherwise immediately abort playback
169 if (m_iCurrentSong
>= 0 && m_iCurrentSong
< playlist
.size() && playlist
[m_iCurrentSong
]->GetProperty("unplayable").asBoolean())
171 CLog::Log(LOGERROR
, "Playlist Player: RepeatOne stuck on unplayable item: {}, path [{}]",
172 m_iCurrentSong
, playlist
[m_iCurrentSong
]->GetPath());
173 CGUIMessage
msg(GUI_MSG_PLAYLISTPLAYER_STOPPED
, 0, 0, static_cast<int>(m_iCurrentPlayList
),
175 CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg
);
177 m_iCurrentPlayList
= Id::TYPE_NONE
;
183 // if we've gone beyond the playlist and repeat all is enabled,
184 // then we clear played status and wrap around
186 if (iSong
>= playlist
.size() && Repeated(m_iCurrentPlayList
))
192 bool CPlayListPlayer::PlayNext(int offset
, bool bAutoPlay
)
194 int iSong
= GetNextItemIdx(offset
);
195 const CPlayList
& playlist
= GetPlaylist(m_iCurrentPlayList
);
197 if ((iSong
< 0) || (iSong
>= playlist
.size()) || (playlist
.GetPlayable() <= 0))
200 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info
, g_localizeStrings
.Get(559), g_localizeStrings
.Get(34201));
202 CGUIMessage
msg(GUI_MSG_PLAYLISTPLAYER_STOPPED
, 0, 0, static_cast<int>(m_iCurrentPlayList
),
204 CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg
);
206 m_iCurrentPlayList
= Id::TYPE_NONE
;
210 const auto& components
= CServiceBroker::GetAppComponents();
211 const auto appPlayer
= components
.GetComponent
<CApplicationPlayer
>();
212 const std::string player
= appPlayer
->GetName();
214 return Play(iSong
, player
, false);
217 bool CPlayListPlayer::PlayPrevious()
219 if (m_iCurrentPlayList
== Id::TYPE_NONE
)
222 const CPlayList
& playlist
= GetPlaylist(m_iCurrentPlayList
);
223 int iSong
= m_iCurrentSong
;
225 if (!RepeatedOne(m_iCurrentPlayList
))
228 if (iSong
< 0 && Repeated(m_iCurrentPlayList
))
229 iSong
= playlist
.size() - 1;
231 if (iSong
< 0 || playlist
.size() <= 0)
233 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info
, g_localizeStrings
.Get(559), g_localizeStrings
.Get(34202));
237 return Play(iSong
, "", false, true);
240 bool CPlayListPlayer::IsSingleItemNonRepeatPlaylist() const
242 const CPlayList
& playlist
= GetPlaylist(m_iCurrentPlayList
);
243 return (playlist
.size() <= 1 && !RepeatedOne(m_iCurrentPlayList
) && !Repeated(m_iCurrentPlayList
));
246 bool CPlayListPlayer::Play()
248 if (m_iCurrentPlayList
== Id::TYPE_NONE
)
251 const CPlayList
& playlist
= GetPlaylist(m_iCurrentPlayList
);
252 if (playlist
.size() <= 0)
258 bool CPlayListPlayer::PlayItemIdx(int itemIdx
)
260 if (m_iCurrentPlayList
== Id::TYPE_NONE
)
263 CPlayList
& playlist
= GetPlaylist(m_iCurrentPlayList
);
264 if (playlist
.size() <= 0)
267 for (int i
= 0; i
< playlist
.size(); i
++)
269 if (playlist
[i
]->HasMusicInfoTag() &&
270 playlist
[i
]->GetMusicInfoTag()->GetDatabaseId() == itemIdx
)
276 bool CPlayListPlayer::Play(const CFileItemPtr
& pItem
,
277 const std::string
& player
,
278 bool forceSelection
/* = false */)
281 bool isVideo
{IsVideo(*pItem
)};
282 bool isAudio
{MUSIC::IsAudio(*pItem
)};
284 if (isAudio
&& !isVideo
)
285 playlistId
= Id::TYPE_MUSIC
;
286 else if (isVideo
&& !isAudio
)
287 playlistId
= Id::TYPE_VIDEO
;
288 else if (pItem
->HasProperty("playlist_type_hint"))
290 // There are two main cases that can fall here:
291 // - If an extension is set on both audio / video extension lists example .strm
292 // see GetFileExtensionProvider() -> GetVideoExtensions() / GetAudioExtensions()
293 // When you play the .strm containing single path, cause that
294 // IsVideo() and IsAudio() methods both return true
296 // - When you play a playlist (e.g. .m3u / .strm) containing multiple paths,
297 // and the path played is generic (e.g.without extension) and have no properties
298 // to detect the media type, IsVideo() / IsAudio() both return false
300 // for these cases the type is unknown so we rely on the hint
302 Id
{pItem
->GetProperty("playlist_type_hint").asInteger32(static_cast<int>(Id::TYPE_NONE
))};
306 CLog::LogF(LOGWARNING
, "ListItem type must be audio or video type. The type can be specified "
307 "by using ListItem::getVideoInfoTag or ListItem::getMusicInfoTag, in "
308 "the case of playlist entries by adding #KODIPROP mimetype value.");
312 ClearPlaylist(playlistId
);
314 SetCurrentPlaylist(playlistId
);
315 Add(playlistId
, pItem
);
317 return Play(0, player
, false, false, forceSelection
);
320 bool CPlayListPlayer::Play(int iSong
,
321 const std::string
& player
,
322 bool bAutoPlay
/* = false */,
323 bool bPlayPrevious
/* = false */,
324 bool forceSelection
/* = false */)
326 if (m_iCurrentPlayList
== Id::TYPE_NONE
)
329 CPlayList
& playlist
= GetPlaylist(m_iCurrentPlayList
);
330 if (playlist
.size() <= 0)
334 if (iSong
>= playlist
.size())
335 iSong
= playlist
.size() - 1;
337 // check if the item itself is a playlist, and can be expanded
338 // only allow a few levels, this could end up in a loop
339 // if they refer to each other in a loop
340 for (int i
=0; i
<5; i
++)
342 if(!playlist
.Expand(iSong
))
346 m_iCurrentSong
= iSong
;
347 CFileItemPtr item
= playlist
[m_iCurrentSong
];
348 if (IsVideoDb(*item
) && !item
->HasVideoInfoTag())
349 *(item
->GetVideoInfoTag()) = XFILE::CVideoDatabaseFile::GetVideoTag(CURL(item
->GetDynPath()));
351 playlist
.SetPlayed(true);
353 m_bPlaybackStarted
= false;
355 const auto playAttempt
= std::chrono::steady_clock::now();
356 bool ret
= g_application
.PlayFile(*item
, player
, bAutoPlay
, forceSelection
);
359 CLog::Log(LOGERROR
, "Playlist Player: skipping unplayable item: {}, path [{}]", m_iCurrentSong
,
360 CURL::GetRedacted(item
->GetDynPath()));
361 playlist
.SetUnPlayable(m_iCurrentSong
);
363 // abort on 100 failed CONSECUTIVE songs
365 m_failedSongsStart
= playAttempt
;
367 const std::shared_ptr
<CAdvancedSettings
> advancedSettings
= CServiceBroker::GetSettingsComponent()->GetAdvancedSettings();
369 auto now
= std::chrono::steady_clock::now();
370 auto duration
= std::chrono::duration_cast
<std::chrono::milliseconds
>(now
- m_failedSongsStart
);
372 if ((m_iFailedSongs
>= advancedSettings
->m_playlistRetries
&&
373 advancedSettings
->m_playlistRetries
>= 0) ||
374 ((duration
.count() >=
375 static_cast<unsigned int>(advancedSettings
->m_playlistTimeout
) * 1000) &&
376 advancedSettings
->m_playlistTimeout
))
378 CLog::Log(LOGDEBUG
,"Playlist Player: one or more items failed to play... aborting playback");
381 HELPERS::ShowOKDialogText(CVariant
{16026}, CVariant
{16027});
383 CGUIMessage
msg(GUI_MSG_PLAYLISTPLAYER_STOPPED
, 0, 0, static_cast<int>(m_iCurrentPlayList
),
385 CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg
);
387 GetPlaylist(m_iCurrentPlayList
).Clear();
388 m_iCurrentPlayList
= Id::TYPE_NONE
;
390 m_failedSongsStart
= std::chrono::steady_clock::now();
394 // how many playable items are in the playlist?
395 if (playlist
.GetPlayable() > 0)
397 return bPlayPrevious
? PlayPrevious() : PlayNext();
399 // none? then abort playback
402 CLog::Log(LOGDEBUG
,"Playlist Player: no more playable items... aborting playback");
403 CGUIMessage
msg(GUI_MSG_PLAYLISTPLAYER_STOPPED
, 0, 0, static_cast<int>(m_iCurrentPlayList
),
405 CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg
);
407 m_iCurrentPlayList
= Id::TYPE_NONE
;
412 // reset the start offset of this item
413 if (item
->GetStartOffset() == STARTOFFSET_RESUME
)
414 item
->SetStartOffset(0);
416 //! @todo - move the above failure logic and the below success logic
417 //! to callbacks instead so we don't rely on the return value
420 // consecutive error counter so reset if the current item is playing
422 m_failedSongsStart
= std::chrono::steady_clock::now();
423 m_bPlayedFirstFile
= true;
427 void CPlayListPlayer::SetCurrentItemIdx(int iSong
)
429 if (iSong
>= -1 && iSong
< GetPlaylist(m_iCurrentPlayList
).size())
430 m_iCurrentSong
= iSong
;
433 int CPlayListPlayer::GetCurrentItemIdx() const
435 return m_iCurrentSong
;
438 Id
CPlayListPlayer::GetCurrentPlaylist() const
440 return m_iCurrentPlayList
;
443 void CPlayListPlayer::SetCurrentPlaylist(Id playlistId
)
445 if (playlistId
== m_iCurrentPlayList
)
448 // changing the current playlist while party mode is on
449 // disables party mode
450 if (g_partyModeManager
.IsEnabled())
451 g_partyModeManager
.Disable();
453 m_iCurrentPlayList
= playlistId
;
454 m_bPlayedFirstFile
= false;
457 void CPlayListPlayer::ClearPlaylist(Id playlistId
)
459 // clear our applications playlist file
460 g_application
.m_strPlayListFile
.clear();
462 CPlayList
& playlist
= GetPlaylist(playlistId
);
465 // its likely that the playlist changed
466 CGUIMessage
msg(GUI_MSG_PLAYLIST_CHANGED
, 0, 0);
467 CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg
);
470 CPlayList
& CPlayListPlayer::GetPlaylist(Id playlistId
)
475 return *m_PlaylistMusic
;
478 return *m_PlaylistVideo
;
481 m_PlaylistEmpty
->Clear();
482 return *m_PlaylistEmpty
;
487 const CPlayList
& CPlayListPlayer::GetPlaylist(Id playlistId
) const
492 return *m_PlaylistMusic
;
495 return *m_PlaylistVideo
;
498 // NOTE: This playlist may not be empty if the caller of the non-const version alters it!
499 return *m_PlaylistEmpty
;
504 int CPlayListPlayer::RemoveDVDItems()
506 int nRemovedM
= m_PlaylistMusic
->RemoveDVDItems();
507 int nRemovedV
= m_PlaylistVideo
->RemoveDVDItems();
509 return nRemovedM
+ nRemovedV
;
512 void CPlayListPlayer::Reset()
515 m_bPlayedFirstFile
= false;
516 m_bPlaybackStarted
= false;
518 // its likely that the playlist changed
519 CGUIMessage
msg(GUI_MSG_PLAYLIST_CHANGED
, 0, 0);
520 CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg
);
523 bool CPlayListPlayer::HasPlayedFirstFile() const
525 return m_bPlayedFirstFile
;
528 bool CPlayListPlayer::Repeated(Id playlistId
) const
530 const auto repStatePos
= m_repeatState
.find(playlistId
);
531 if (repStatePos
!= m_repeatState
.end())
532 return repStatePos
->second
== RepeatState::ALL
;
536 bool CPlayListPlayer::RepeatedOne(Id playlistId
) const
538 const auto repStatePos
= m_repeatState
.find(playlistId
);
539 if (repStatePos
!= m_repeatState
.end())
540 return (repStatePos
->second
== RepeatState::ONE
);
544 void CPlayListPlayer::SetShuffle(Id playlistId
, bool bYesNo
, bool bNotify
/* = false */)
546 if (playlistId
!= Id::TYPE_MUSIC
&& playlistId
!= Id::TYPE_VIDEO
)
549 // disable shuffle in party mode
550 if (g_partyModeManager
.IsEnabled() && playlistId
== Id::TYPE_MUSIC
)
553 // do we even need to do anything?
554 if (bYesNo
!= IsShuffled(playlistId
))
556 // save the order value of the current song so we can use it find its new location later
558 CPlayList
& playlist
= GetPlaylist(playlistId
);
559 if (m_iCurrentSong
>= 0 && m_iCurrentSong
< playlist
.size())
560 iOrder
= playlist
[m_iCurrentSong
]->m_iprogramCount
;
562 // shuffle or unshuffle as necessary
566 playlist
.UnShuffle();
570 std::string shuffleStr
=
571 StringUtils::Format("{}: {}", g_localizeStrings
.Get(191),
572 g_localizeStrings
.Get(bYesNo
? 593 : 591)); // Shuffle: All/Off
573 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info
, g_localizeStrings
.Get(559), shuffleStr
);
576 // find the previous order value and fix the current song marker
579 int iIndex
= playlist
.FindOrder(iOrder
);
581 m_iCurrentSong
= iIndex
;
582 // if iIndex < 0, something unexpected happened
583 // so dont do anything
587 // its likely that the playlist changed
588 if (CServiceBroker::GetGUI() != nullptr)
590 CGUIMessage
msg(GUI_MSG_PLAYLIST_CHANGED
, 0, 0);
591 CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg
);
594 AnnouncePropertyChanged(playlistId
, "shuffled", IsShuffled(playlistId
));
597 bool CPlayListPlayer::IsShuffled(Id playlistId
) const
599 // even if shuffled, party mode says its not
600 if (g_partyModeManager
.IsEnabled() && playlistId
== Id::TYPE_MUSIC
)
603 if (playlistId
== Id::TYPE_MUSIC
|| playlistId
== Id::TYPE_VIDEO
)
604 return GetPlaylist(playlistId
).IsShuffled();
609 void CPlayListPlayer::SetRepeat(Id playlistId
, RepeatState state
, bool bNotify
/* = false */)
611 if (playlistId
!= Id::TYPE_MUSIC
&& playlistId
!= Id::TYPE_VIDEO
)
614 // disable repeat in party mode
615 if (g_partyModeManager
.IsEnabled() && playlistId
== Id::TYPE_MUSIC
)
616 state
= RepeatState::NONE
;
618 // notify the user if there was a change in the repeat state
619 if (m_repeatState
[playlistId
] != state
&& bNotify
)
621 int iLocalizedString
;
622 if (state
== RepeatState::NONE
)
623 iLocalizedString
= 595; // Repeat: Off
624 else if (state
== RepeatState::ONE
)
625 iLocalizedString
= 596; // Repeat: One
627 iLocalizedString
= 597; // Repeat: All
628 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info
, g_localizeStrings
.Get(559), g_localizeStrings
.Get(iLocalizedString
));
631 m_repeatState
[playlistId
] = state
;
636 case RepeatState::ONE
:
639 case RepeatState::ALL
:
647 // its likely that the playlist changed
648 if (CServiceBroker::GetGUI() != nullptr)
650 CGUIMessage
msg(GUI_MSG_PLAYLIST_CHANGED
, 0, 0);
651 CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg
);
654 AnnouncePropertyChanged(playlistId
, "repeat", data
);
657 RepeatState
CPlayListPlayer::GetRepeat(Id playlistId
) const
659 const auto repStatePos
= m_repeatState
.find(playlistId
);
660 if (repStatePos
!= m_repeatState
.end())
661 return repStatePos
->second
;
662 return RepeatState::NONE
;
665 void CPlayListPlayer::ReShuffle(Id playlistId
, int iPosition
)
667 // playlist has not played yet so shuffle the entire list
668 // (this only really works for new video playlists)
669 if (!GetPlaylist(playlistId
).WasPlayed())
671 GetPlaylist(playlistId
).Shuffle();
673 // we're trying to shuffle new items into the currently playing playlist
674 // so we shuffle starting at two positions below the current item
675 else if (playlistId
== m_iCurrentPlayList
)
677 const auto& components
= CServiceBroker::GetAppComponents();
678 const auto appPlayer
= components
.GetComponent
<CApplicationPlayer
>();
679 if ((appPlayer
->IsPlayingAudio() && playlistId
== Id::TYPE_MUSIC
) ||
680 (appPlayer
->IsPlayingVideo() && playlistId
== Id::TYPE_VIDEO
))
682 GetPlaylist(playlistId
).Shuffle(m_iCurrentSong
+ 2);
685 // otherwise, shuffle from the passed position
686 // which is the position of the first new item added
689 GetPlaylist(playlistId
).Shuffle(iPosition
);
693 void CPlayListPlayer::Add(Id playlistId
, const CPlayList
& playlist
)
695 if (playlistId
!= Id::TYPE_MUSIC
&& playlistId
!= Id::TYPE_VIDEO
)
697 CPlayList
& list
= GetPlaylist(playlistId
);
698 int iSize
= list
.size();
700 if (list
.IsShuffled())
701 ReShuffle(playlistId
, iSize
);
704 void CPlayListPlayer::Add(Id playlistId
, const CFileItemPtr
& pItem
)
706 if (playlistId
!= Id::TYPE_MUSIC
&& playlistId
!= Id::TYPE_VIDEO
)
708 CPlayList
& list
= GetPlaylist(playlistId
);
709 int iSize
= list
.size();
711 if (list
.IsShuffled())
712 ReShuffle(playlistId
, iSize
);
714 // its likely that the playlist changed
715 CGUIMessage
msg(GUI_MSG_PLAYLIST_CHANGED
, 0, 0);
716 CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg
);
719 void CPlayListPlayer::Add(Id playlistId
, const CFileItemList
& items
)
721 if (playlistId
!= Id::TYPE_MUSIC
&& playlistId
!= Id::TYPE_VIDEO
)
723 CPlayList
& list
= GetPlaylist(playlistId
);
724 int iSize
= list
.size();
726 if (list
.IsShuffled())
727 ReShuffle(playlistId
, iSize
);
729 // its likely that the playlist changed
730 CGUIMessage
msg(GUI_MSG_PLAYLIST_CHANGED
, 0, 0);
731 CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg
);
734 void CPlayListPlayer::Insert(Id playlistId
, const CPlayList
& playlist
, int iIndex
)
736 if (playlistId
!= Id::TYPE_MUSIC
&& playlistId
!= Id::TYPE_VIDEO
)
738 CPlayList
& list
= GetPlaylist(playlistId
);
739 int iSize
= list
.size();
740 list
.Insert(playlist
, iIndex
);
741 if (list
.IsShuffled())
742 ReShuffle(playlistId
, iSize
);
743 else if (m_iCurrentPlayList
== playlistId
&& m_iCurrentSong
>= iIndex
)
747 void CPlayListPlayer::Insert(Id playlistId
, const CFileItemPtr
& pItem
, int iIndex
)
749 if (playlistId
!= Id::TYPE_MUSIC
&& playlistId
!= Id::TYPE_VIDEO
)
751 CPlayList
& list
= GetPlaylist(playlistId
);
752 int iSize
= list
.size();
753 list
.Insert(pItem
, iIndex
);
754 if (list
.IsShuffled())
755 ReShuffle(playlistId
, iSize
);
756 else if (m_iCurrentPlayList
== playlistId
&& m_iCurrentSong
>= iIndex
)
760 void CPlayListPlayer::Insert(Id playlistId
, const CFileItemList
& items
, int iIndex
)
762 if (playlistId
!= Id::TYPE_MUSIC
&& playlistId
!= Id::TYPE_VIDEO
)
764 CPlayList
& list
= GetPlaylist(playlistId
);
765 int iSize
= list
.size();
766 list
.Insert(items
, iIndex
);
767 if (list
.IsShuffled())
768 ReShuffle(playlistId
, iSize
);
769 else if (m_iCurrentPlayList
== playlistId
&& m_iCurrentSong
>= iIndex
)
772 // its likely that the playlist changed
773 CGUIMessage
msg(GUI_MSG_PLAYLIST_CHANGED
, 0, 0);
774 CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg
);
777 void CPlayListPlayer::Remove(Id playlistId
, int iPosition
)
779 if (playlistId
!= Id::TYPE_MUSIC
&& playlistId
!= Id::TYPE_VIDEO
)
781 CPlayList
& list
= GetPlaylist(playlistId
);
782 list
.Remove(iPosition
);
783 if (m_iCurrentPlayList
== playlistId
&& m_iCurrentSong
>= iPosition
)
786 // its likely that the playlist changed
787 CGUIMessage
msg(GUI_MSG_PLAYLIST_CHANGED
, 0, 0);
788 CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg
);
791 void CPlayListPlayer::Clear()
794 m_PlaylistMusic
->Clear();
796 m_PlaylistVideo
->Clear();
798 m_PlaylistEmpty
->Clear();
801 void CPlayListPlayer::Swap(Id playlistId
, int indexItem1
, int indexItem2
)
803 if (playlistId
!= Id::TYPE_MUSIC
&& playlistId
!= Id::TYPE_VIDEO
)
806 CPlayList
& list
= GetPlaylist(playlistId
);
807 if (list
.Swap(indexItem1
, indexItem2
) && playlistId
== m_iCurrentPlayList
)
809 if (m_iCurrentSong
== indexItem1
)
810 m_iCurrentSong
= indexItem2
;
811 else if (m_iCurrentSong
== indexItem2
)
812 m_iCurrentSong
= indexItem1
;
815 // its likely that the playlist changed
816 CGUIMessage
msg(GUI_MSG_PLAYLIST_CHANGED
, 0, 0);
817 CServiceBroker::GetGUI()->GetWindowManager().SendMessage(msg
);
820 void CPlayListPlayer::AnnouncePropertyChanged(Id playlistId
,
821 const std::string
& strProperty
,
822 const CVariant
& value
)
824 const auto& components
= CServiceBroker::GetAppComponents();
825 const auto appPlayer
= components
.GetComponent
<CApplicationPlayer
>();
827 if (strProperty
.empty() || value
.isNull() ||
828 (playlistId
== Id::TYPE_VIDEO
&& !appPlayer
->IsPlayingVideo()) ||
829 (playlistId
== Id::TYPE_MUSIC
&& !appPlayer
->IsPlayingAudio()))
833 data
["player"]["playerid"] = static_cast<int>(playlistId
);
834 data
["property"][strProperty
] = value
;
835 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player
, "OnPropertyChanged",
839 int PLAYLIST::CPlayListPlayer::GetMessageMask()
841 return TMSG_MASK_PLAYLISTPLAYER
;
844 void PLAYLIST::CPlayListPlayer::OnApplicationMessage(KODI::MESSAGING::ThreadMessage
* pMsg
)
846 auto& components
= CServiceBroker::GetAppComponents();
847 const auto appPlayer
= components
.GetComponent
<CApplicationPlayer
>();
849 auto wakeScreensaver
= []() {
850 auto& components
= CServiceBroker::GetAppComponents();
851 const auto appPower
= components
.GetComponent
<CApplicationPowerHandling
>();
852 appPower
->ResetScreenSaver();
853 appPower
->WakeUpScreenSaverAndDPMS();
856 switch (pMsg
->dwMessage
)
858 case TMSG_PLAYLISTPLAYER_PLAY
:
859 if (pMsg
->param1
!= -1)
860 Play(pMsg
->param1
, "");
865 case TMSG_PLAYLISTPLAYER_PLAY_ITEM_ID
:
866 if (pMsg
->param1
!= -1)
868 bool *result
= (bool*)pMsg
->lpVoid
;
869 *result
= PlayItemIdx(pMsg
->param1
);
875 case TMSG_PLAYLISTPLAYER_NEXT
:
879 case TMSG_PLAYLISTPLAYER_PREV
:
883 case TMSG_PLAYLISTPLAYER_ADD
:
886 CFileItemList
*list
= static_cast<CFileItemList
*>(pMsg
->lpVoid
);
888 Add(Id
{pMsg
->param1
}, (*list
));
893 case TMSG_PLAYLISTPLAYER_INSERT
:
896 CFileItemList
*list
= static_cast<CFileItemList
*>(pMsg
->lpVoid
);
897 Insert(Id
{pMsg
->param1
}, (*list
), pMsg
->param2
);
902 case TMSG_PLAYLISTPLAYER_REMOVE
:
903 if (pMsg
->param1
!= -1)
904 Remove(Id
{pMsg
->param1
}, pMsg
->param2
);
907 case TMSG_PLAYLISTPLAYER_CLEAR
:
908 ClearPlaylist(Id
{pMsg
->param1
});
911 case TMSG_PLAYLISTPLAYER_SHUFFLE
:
912 SetShuffle(Id
{pMsg
->param1
}, pMsg
->param2
> 0);
915 case TMSG_PLAYLISTPLAYER_REPEAT
:
916 SetRepeat(Id
{pMsg
->param1
}, static_cast<RepeatState
>(pMsg
->param2
));
919 case TMSG_PLAYLISTPLAYER_GET_ITEMS
:
922 PLAYLIST::CPlayList playlist
= GetPlaylist(Id
{pMsg
->param1
});
923 CFileItemList
*list
= static_cast<CFileItemList
*>(pMsg
->lpVoid
);
925 for (int i
= 0; i
< playlist
.size(); i
++)
926 list
->Add(std::make_shared
<CFileItem
>(*playlist
[i
]));
930 case TMSG_PLAYLISTPLAYER_SWAP
:
933 auto indexes
= static_cast<std::vector
<int>*>(pMsg
->lpVoid
);
934 if (indexes
->size() == 2)
935 Swap(Id
{pMsg
->param1
}, indexes
->at(0), indexes
->at(1));
940 case TMSG_MEDIA_PLAY
:
944 // first check if we were called from the PlayFile() function
945 if (pMsg
->lpVoid
&& pMsg
->param2
== 0)
947 // Discard the current playlist, if TMSG_MEDIA_PLAY gets posted with just a single item.
948 // Otherwise items may fail to play, when started while a playlist is playing.
951 CFileItem
*item
= static_cast<CFileItem
*>(pMsg
->lpVoid
);
952 g_application
.PlayFile(*item
, "", pMsg
->param1
!= 0);
957 //g_application.StopPlaying();
961 CFileItemList
*list
= static_cast<CFileItemList
*>(pMsg
->lpVoid
);
963 if (list
->Size() > 0)
965 Id playlistId
= Id::TYPE_MUSIC
;
966 for (int i
= 0; i
< list
->Size(); i
++)
968 if (IsVideo(*list
->Get(i
)))
970 playlistId
= Id::TYPE_VIDEO
;
975 ClearPlaylist(playlistId
);
976 SetCurrentPlaylist(playlistId
);
977 if (list
->Size() == 1 && !IsPlayList(*list
->Get(0)))
979 CFileItemPtr item
= (*list
)[0];
980 // if the item is a plugin we need to resolve the URL to ensure the infotags are filled.
981 if (URIUtils::HasPluginPath(*item
) &&
982 !XFILE::CPluginDirectory::GetResolvedPluginResult(*item
))
986 if (MUSIC::IsAudio(*item
) || IsVideo(*item
))
987 Play(item
, pMsg
->strParam
);
989 g_application
.PlayMedia(*item
, pMsg
->strParam
, playlistId
);
993 // Handle "shuffled" option if present
994 if (list
->HasProperty("shuffled") && list
->GetProperty("shuffled").isBoolean())
995 SetShuffle(playlistId
, list
->GetProperty("shuffled").asBoolean(), false);
996 // Handle "repeat" option if present
997 if (list
->HasProperty("repeat") && list
->GetProperty("repeat").isInteger())
998 SetRepeat(playlistId
, static_cast<RepeatState
>(list
->GetProperty("repeat").asInteger()),
1001 Add(playlistId
, (*list
));
1002 Play(pMsg
->param1
, pMsg
->strParam
);
1008 else if (Id
{pMsg
->param1
} == Id::TYPE_MUSIC
|| Id
{pMsg
->param1
} == Id::TYPE_VIDEO
)
1010 if (GetCurrentPlaylist() != Id
{pMsg
->param1
})
1011 SetCurrentPlaylist(Id
{pMsg
->param1
});
1013 CServiceBroker::GetAppMessenger()->SendMsg(TMSG_PLAYLISTPLAYER_PLAY
, pMsg
->param2
);
1018 case TMSG_MEDIA_RESTART
:
1019 g_application
.Restart(true);
1022 case TMSG_MEDIA_STOP
:
1024 // restore to previous window if needed
1025 bool stopSlideshow
= true;
1026 bool stopVideo
= true;
1027 bool stopMusic
= true;
1029 Id playlistId
= Id
{pMsg
->param1
};
1030 if (playlistId
!= Id::TYPE_NONE
)
1032 stopSlideshow
= (playlistId
== Id::TYPE_PICTURE
);
1033 stopVideo
= (playlistId
== Id::TYPE_VIDEO
);
1034 stopMusic
= (playlistId
== Id::TYPE_MUSIC
);
1037 if ((stopSlideshow
&& CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_SLIDESHOW
) ||
1038 (stopVideo
&& CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_FULLSCREEN_VIDEO
) ||
1039 (stopVideo
&& CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_FULLSCREEN_GAME
) ||
1040 (stopMusic
&& CServiceBroker::GetGUI()->GetWindowManager().GetActiveWindow() == WINDOW_VISUALISATION
))
1041 CServiceBroker::GetGUI()->GetWindowManager().PreviousWindow();
1045 // stop playing file
1046 if (appPlayer
->IsPlaying())
1047 g_application
.StopPlaying();
1051 case TMSG_MEDIA_PAUSE
:
1052 if (appPlayer
->HasPlayer())
1059 case TMSG_MEDIA_UNPAUSE
:
1060 if (appPlayer
->IsPausedPlayback())
1067 case TMSG_MEDIA_PAUSE_IF_PLAYING
:
1068 if (appPlayer
->IsPlaying() && !appPlayer
->IsPaused())
1075 case TMSG_MEDIA_SEEK_TIME
:
1077 if (appPlayer
->IsPlaying() || appPlayer
->IsPaused())
1078 appPlayer
->SeekTime(pMsg
->param3
);
1087 } // namespace KODI::PLAYLIST