[videodb] remove unused seasons table from episode_view
[xbmc.git] / xbmc / PlayListPlayer.cpp
blob8d45e7f611bb8170535f2a3c29a730a081cf448f
1 /*
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.
7 */
9 #include "PlayListPlayer.h"
11 #include "FileItem.h"
12 #include "FileItemList.h"
13 #include "GUIUserMessages.h"
14 #include "PartyModeManager.h"
15 #include "ServiceBroker.h"
16 #include "URL.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;
56 m_iCurrentSong = -1;
57 m_bPlayedFirstFile = false;
58 m_bPlaybackStarted = false;
59 m_iFailedSongs = 0;
60 m_failedSongsStart = std::chrono::steady_clock::now();
63 CPlayListPlayer::~CPlayListPlayer(void)
65 Clear();
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())
75 PlayPrevious();
76 return true;
78 else if (action.GetID() == ACTION_NEXT_ITEM && !IsSingleItemNonRepeatPlaylist())
80 PlayNext();
81 return true;
83 else
84 return false;
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());
102 break;
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),
108 m_iCurrentSong);
109 CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg);
110 Reset();
111 m_iCurrentPlayList = Id::TYPE_NONE;
112 return true;
115 break;
116 case GUI_MSG_PLAYBACK_STARTED:
118 m_bPlaybackStarted = true;
120 break;
123 return false;
126 int CPlayListPlayer::GetNextItemIdx(int offset) const
128 if (m_iCurrentPlayList == Id::TYPE_NONE)
129 return -1;
131 const CPlayList& playlist = GetPlaylist(m_iCurrentPlayList);
132 if (playlist.size() <= 0)
133 return -1;
135 int song = m_iCurrentSong;
137 // party mode
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))
143 return song;
145 song += offset;
146 if (song >= playlist.size() && Repeated(m_iCurrentPlayList))
147 song %= playlist.size();
149 return song;
152 int CPlayListPlayer::GetNextItemIdx()
154 if (m_iCurrentPlayList == Id::TYPE_NONE)
155 return -1;
156 CPlayList& playlist = GetPlaylist(m_iCurrentPlayList);
157 if (playlist.size() <= 0)
158 return -1;
159 int iSong = m_iCurrentSong;
161 // party mode
162 if (g_partyModeManager.IsEnabled() && GetCurrentPlaylist() == Id::TYPE_MUSIC)
163 return iSong + 1;
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),
174 m_iCurrentSong);
175 CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg);
176 Reset();
177 m_iCurrentPlayList = Id::TYPE_NONE;
178 return -1;
180 return iSong;
183 // if we've gone beyond the playlist and repeat all is enabled,
184 // then we clear played status and wrap around
185 iSong++;
186 if (iSong >= playlist.size() && Repeated(m_iCurrentPlayList))
187 iSong = 0;
189 return iSong;
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))
199 if(!bAutoPlay)
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),
203 m_iCurrentSong);
204 CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg);
205 Reset();
206 m_iCurrentPlayList = Id::TYPE_NONE;
207 return false;
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)
220 return false;
222 const CPlayList& playlist = GetPlaylist(m_iCurrentPlayList);
223 int iSong = m_iCurrentSong;
225 if (!RepeatedOne(m_iCurrentPlayList))
226 iSong--;
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));
234 return false;
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)
249 return false;
251 const CPlayList& playlist = GetPlaylist(m_iCurrentPlayList);
252 if (playlist.size() <= 0)
253 return false;
255 return Play(0, "");
258 bool CPlayListPlayer::PlayItemIdx(int itemIdx)
260 if (m_iCurrentPlayList == Id::TYPE_NONE)
261 return false;
263 CPlayList& playlist = GetPlaylist(m_iCurrentPlayList);
264 if (playlist.size() <= 0)
265 return Play();
267 for (int i = 0; i < playlist.size(); i++)
269 if (playlist[i]->HasMusicInfoTag() &&
270 playlist[i]->GetMusicInfoTag()->GetDatabaseId() == itemIdx)
271 return Play(i, "");
273 return Play();
276 bool CPlayListPlayer::Play(const CFileItemPtr& pItem,
277 const std::string& player,
278 bool forceSelection /* = false */)
280 Id playlistId;
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
301 playlistId =
302 Id{pItem->GetProperty("playlist_type_hint").asInteger32(static_cast<int>(Id::TYPE_NONE))};
304 else
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.");
309 return false;
312 ClearPlaylist(playlistId);
313 Reset();
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)
327 return false;
329 CPlayList& playlist = GetPlaylist(m_iCurrentPlayList);
330 if (playlist.size() <= 0)
331 return false;
332 if (iSong < 0)
333 iSong = 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))
343 break;
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);
357 if (!ret)
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
364 if (!m_iFailedSongs)
365 m_failedSongsStart = playAttempt;
366 m_iFailedSongs++;
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");
380 // open error dialog
381 HELPERS::ShowOKDialogText(CVariant{16026}, CVariant{16027});
383 CGUIMessage msg(GUI_MSG_PLAYLISTPLAYER_STOPPED, 0, 0, static_cast<int>(m_iCurrentPlayList),
384 m_iCurrentSong);
385 CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg);
386 Reset();
387 GetPlaylist(m_iCurrentPlayList).Clear();
388 m_iCurrentPlayList = Id::TYPE_NONE;
389 m_iFailedSongs = 0;
390 m_failedSongsStart = std::chrono::steady_clock::now();
391 return false;
394 // how many playable items are in the playlist?
395 if (playlist.GetPlayable() > 0)
397 return bPlayPrevious ? PlayPrevious() : PlayNext();
399 // none? then abort playback
400 else
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),
404 m_iCurrentSong);
405 CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg);
406 Reset();
407 m_iCurrentPlayList = Id::TYPE_NONE;
408 return false;
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
418 //! of PlayFile()
420 // consecutive error counter so reset if the current item is playing
421 m_iFailedSongs = 0;
422 m_failedSongsStart = std::chrono::steady_clock::now();
423 m_bPlayedFirstFile = true;
424 return 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)
446 return;
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);
463 playlist.Clear();
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)
472 switch (playlistId)
474 case Id::TYPE_MUSIC:
475 return *m_PlaylistMusic;
476 break;
477 case Id::TYPE_VIDEO:
478 return *m_PlaylistVideo;
479 break;
480 default:
481 m_PlaylistEmpty->Clear();
482 return *m_PlaylistEmpty;
483 break;
487 const CPlayList& CPlayListPlayer::GetPlaylist(Id playlistId) const
489 switch (playlistId)
491 case Id::TYPE_MUSIC:
492 return *m_PlaylistMusic;
493 break;
494 case Id::TYPE_VIDEO:
495 return *m_PlaylistVideo;
496 break;
497 default:
498 // NOTE: This playlist may not be empty if the caller of the non-const version alters it!
499 return *m_PlaylistEmpty;
500 break;
504 int CPlayListPlayer::RemoveDVDItems()
506 int nRemovedM = m_PlaylistMusic->RemoveDVDItems();
507 int nRemovedV = m_PlaylistVideo->RemoveDVDItems();
509 return nRemovedM + nRemovedV;
512 void CPlayListPlayer::Reset()
514 m_iCurrentSong = -1;
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;
533 return false;
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);
541 return false;
544 void CPlayListPlayer::SetShuffle(Id playlistId, bool bYesNo, bool bNotify /* = false */)
546 if (playlistId != Id::TYPE_MUSIC && playlistId != Id::TYPE_VIDEO)
547 return;
549 // disable shuffle in party mode
550 if (g_partyModeManager.IsEnabled() && playlistId == Id::TYPE_MUSIC)
551 return;
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
557 int iOrder = -1;
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
563 if (bYesNo)
564 playlist.Shuffle();
565 else
566 playlist.UnShuffle();
568 if (bNotify)
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
577 if (iOrder >= 0)
579 int iIndex = playlist.FindOrder(iOrder);
580 if (iIndex >= 0)
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)
601 return false;
603 if (playlistId == Id::TYPE_MUSIC || playlistId == Id::TYPE_VIDEO)
604 return GetPlaylist(playlistId).IsShuffled();
606 return false;
609 void CPlayListPlayer::SetRepeat(Id playlistId, RepeatState state, bool bNotify /* = false */)
611 if (playlistId != Id::TYPE_MUSIC && playlistId != Id::TYPE_VIDEO)
612 return;
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
626 else
627 iLocalizedString = 597; // Repeat: All
628 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(559), g_localizeStrings.Get(iLocalizedString));
631 m_repeatState[playlistId] = state;
633 CVariant data;
634 switch (state)
636 case RepeatState::ONE:
637 data = "one";
638 break;
639 case RepeatState::ALL:
640 data = "all";
641 break;
642 default:
643 data = "off";
644 break;
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
687 else
689 GetPlaylist(playlistId).Shuffle(iPosition);
693 void CPlayListPlayer::Add(Id playlistId, const CPlayList& playlist)
695 if (playlistId != Id::TYPE_MUSIC && playlistId != Id::TYPE_VIDEO)
696 return;
697 CPlayList& list = GetPlaylist(playlistId);
698 int iSize = list.size();
699 list.Add(playlist);
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)
707 return;
708 CPlayList& list = GetPlaylist(playlistId);
709 int iSize = list.size();
710 list.Add(pItem);
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)
722 return;
723 CPlayList& list = GetPlaylist(playlistId);
724 int iSize = list.size();
725 list.Add(items);
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)
737 return;
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)
744 m_iCurrentSong++;
747 void CPlayListPlayer::Insert(Id playlistId, const CFileItemPtr& pItem, int iIndex)
749 if (playlistId != Id::TYPE_MUSIC && playlistId != Id::TYPE_VIDEO)
750 return;
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)
757 m_iCurrentSong++;
760 void CPlayListPlayer::Insert(Id playlistId, const CFileItemList& items, int iIndex)
762 if (playlistId != Id::TYPE_MUSIC && playlistId != Id::TYPE_VIDEO)
763 return;
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)
770 m_iCurrentSong++;
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)
780 return;
781 CPlayList& list = GetPlaylist(playlistId);
782 list.Remove(iPosition);
783 if (m_iCurrentPlayList == playlistId && m_iCurrentSong >= iPosition)
784 m_iCurrentSong--;
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()
793 if (m_PlaylistMusic)
794 m_PlaylistMusic->Clear();
795 if (m_PlaylistVideo)
796 m_PlaylistVideo->Clear();
797 if (m_PlaylistEmpty)
798 m_PlaylistEmpty->Clear();
801 void CPlayListPlayer::Swap(Id playlistId, int indexItem1, int indexItem2)
803 if (playlistId != Id::TYPE_MUSIC && playlistId != Id::TYPE_VIDEO)
804 return;
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()))
830 return;
832 CVariant data;
833 data["player"]["playerid"] = static_cast<int>(playlistId);
834 data["property"][strProperty] = value;
835 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player, "OnPropertyChanged",
836 data);
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, "");
861 else
862 Play();
863 break;
865 case TMSG_PLAYLISTPLAYER_PLAY_ITEM_ID:
866 if (pMsg->param1 != -1)
868 bool *result = (bool*)pMsg->lpVoid;
869 *result = PlayItemIdx(pMsg->param1);
871 else
872 Play();
873 break;
875 case TMSG_PLAYLISTPLAYER_NEXT:
876 PlayNext();
877 break;
879 case TMSG_PLAYLISTPLAYER_PREV:
880 PlayPrevious();
881 break;
883 case TMSG_PLAYLISTPLAYER_ADD:
884 if (pMsg->lpVoid)
886 CFileItemList *list = static_cast<CFileItemList*>(pMsg->lpVoid);
888 Add(Id{pMsg->param1}, (*list));
889 delete list;
891 break;
893 case TMSG_PLAYLISTPLAYER_INSERT:
894 if (pMsg->lpVoid)
896 CFileItemList *list = static_cast<CFileItemList*>(pMsg->lpVoid);
897 Insert(Id{pMsg->param1}, (*list), pMsg->param2);
898 delete list;
900 break;
902 case TMSG_PLAYLISTPLAYER_REMOVE:
903 if (pMsg->param1 != -1)
904 Remove(Id{pMsg->param1}, pMsg->param2);
905 break;
907 case TMSG_PLAYLISTPLAYER_CLEAR:
908 ClearPlaylist(Id{pMsg->param1});
909 break;
911 case TMSG_PLAYLISTPLAYER_SHUFFLE:
912 SetShuffle(Id{pMsg->param1}, pMsg->param2 > 0);
913 break;
915 case TMSG_PLAYLISTPLAYER_REPEAT:
916 SetRepeat(Id{pMsg->param1}, static_cast<RepeatState>(pMsg->param2));
917 break;
919 case TMSG_PLAYLISTPLAYER_GET_ITEMS:
920 if (pMsg->lpVoid)
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]));
928 break;
930 case TMSG_PLAYLISTPLAYER_SWAP:
931 if (pMsg->lpVoid)
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));
936 delete indexes;
938 break;
940 case TMSG_MEDIA_PLAY:
942 wakeScreensaver();
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.
949 Reset();
951 CFileItem *item = static_cast<CFileItem*>(pMsg->lpVoid);
952 g_application.PlayFile(*item, "", pMsg->param1 != 0);
953 delete item;
954 return;
957 //g_application.StopPlaying();
958 // play file
959 if (pMsg->lpVoid)
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;
971 break;
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))
984 return;
986 if (MUSIC::IsAudio(*item) || IsVideo(*item))
987 Play(item, pMsg->strParam);
988 else
989 g_application.PlayMedia(*item, pMsg->strParam, playlistId);
991 else
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()),
999 false);
1001 Add(playlistId, (*list));
1002 Play(pMsg->param1, pMsg->strParam);
1006 delete list;
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);
1016 break;
1018 case TMSG_MEDIA_RESTART:
1019 g_application.Restart(true);
1020 break;
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();
1043 wakeScreensaver();
1045 // stop playing file
1046 if (appPlayer->IsPlaying())
1047 g_application.StopPlaying();
1049 break;
1051 case TMSG_MEDIA_PAUSE:
1052 if (appPlayer->HasPlayer())
1054 wakeScreensaver();
1055 appPlayer->Pause();
1057 break;
1059 case TMSG_MEDIA_UNPAUSE:
1060 if (appPlayer->IsPausedPlayback())
1062 wakeScreensaver();
1063 appPlayer->Pause();
1065 break;
1067 case TMSG_MEDIA_PAUSE_IF_PLAYING:
1068 if (appPlayer->IsPlaying() && !appPlayer->IsPaused())
1070 wakeScreensaver();
1071 appPlayer->Pause();
1073 break;
1075 case TMSG_MEDIA_SEEK_TIME:
1077 if (appPlayer->IsPlaying() || appPlayer->IsPaused())
1078 appPlayer->SeekTime(pMsg->param3);
1080 break;
1082 default:
1083 break;
1087 } // namespace KODI::PLAYLIST