[Windows] Remove redundant DirectSound error codes
[xbmc.git] / xbmc / PlayListPlayer.cpp
blobf9aba3373345efef7d18cc50ea40dd8392e16039
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 "GUIPassword.h"
14 #include "GUIUserMessages.h"
15 #include "PartyModeManager.h"
16 #include "ServiceBroker.h"
17 #include "URL.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;
57 m_iCurrentSong = -1;
58 m_bPlayedFirstFile = false;
59 m_bPlaybackStarted = false;
60 m_iFailedSongs = 0;
61 m_failedSongsStart = std::chrono::steady_clock::now();
64 CPlayListPlayer::~CPlayListPlayer(void)
66 Clear();
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())
76 PlayPrevious();
77 return true;
79 else if (action.GetID() == ACTION_NEXT_ITEM && !IsSingleItemNonRepeatPlaylist())
81 PlayNext();
82 return true;
84 else
85 return false;
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());
103 break;
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),
109 m_iCurrentSong);
110 CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg);
111 Reset();
112 m_iCurrentPlayList = Id::TYPE_NONE;
113 return true;
116 break;
117 case GUI_MSG_PLAYBACK_STARTED:
119 m_bPlaybackStarted = true;
121 break;
124 return false;
127 int CPlayListPlayer::GetNextItemIdx(int offset) const
129 if (m_iCurrentPlayList == Id::TYPE_NONE)
130 return -1;
132 const CPlayList& playlist = GetPlaylist(m_iCurrentPlayList);
133 if (playlist.size() <= 0)
134 return -1;
136 int song = m_iCurrentSong;
138 // party mode
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))
144 return song;
146 song += offset;
147 if (song >= playlist.size() && Repeated(m_iCurrentPlayList))
148 song %= playlist.size();
150 return song;
153 int CPlayListPlayer::GetNextItemIdx()
155 if (m_iCurrentPlayList == Id::TYPE_NONE)
156 return -1;
157 CPlayList& playlist = GetPlaylist(m_iCurrentPlayList);
158 if (playlist.size() <= 0)
159 return -1;
160 int iSong = m_iCurrentSong;
162 // party mode
163 if (g_partyModeManager.IsEnabled() && GetCurrentPlaylist() == Id::TYPE_MUSIC)
164 return iSong + 1;
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),
175 m_iCurrentSong);
176 CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg);
177 Reset();
178 m_iCurrentPlayList = Id::TYPE_NONE;
179 return -1;
181 return iSong;
184 // if we've gone beyond the playlist and repeat all is enabled,
185 // then we clear played status and wrap around
186 iSong++;
187 if (iSong >= playlist.size() && Repeated(m_iCurrentPlayList))
188 iSong = 0;
190 return iSong;
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))
200 if(!bAutoPlay)
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),
204 m_iCurrentSong);
205 CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg);
206 Reset();
207 m_iCurrentPlayList = Id::TYPE_NONE;
208 return false;
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)
221 return false;
223 const CPlayList& playlist = GetPlaylist(m_iCurrentPlayList);
224 int iSong = m_iCurrentSong;
226 if (!RepeatedOne(m_iCurrentPlayList))
227 iSong--;
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));
235 return false;
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)
250 return false;
252 const CPlayList& playlist = GetPlaylist(m_iCurrentPlayList);
253 if (playlist.size() <= 0)
254 return false;
256 return Play(0, "");
259 bool CPlayListPlayer::PlayItemIdx(int itemIdx)
261 if (m_iCurrentPlayList == Id::TYPE_NONE)
262 return false;
264 CPlayList& playlist = GetPlaylist(m_iCurrentPlayList);
265 if (playlist.size() <= 0)
266 return Play();
268 for (int i = 0; i < playlist.size(); i++)
270 if (playlist[i]->HasMusicInfoTag() &&
271 playlist[i]->GetMusicInfoTag()->GetDatabaseId() == itemIdx)
272 return Play(i, "");
274 return Play();
277 bool CPlayListPlayer::Play(const CFileItemPtr& pItem,
278 const std::string& player,
279 bool forceSelection /* = false */)
281 Id playlistId;
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
302 playlistId =
303 Id{pItem->GetProperty("playlist_type_hint").asInteger32(static_cast<int>(Id::TYPE_NONE))};
305 else
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.");
310 return false;
313 ClearPlaylist(playlistId);
314 Reset();
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)
328 return false;
330 CPlayList& playlist = GetPlaylist(m_iCurrentPlayList);
331 if (playlist.size() <= 0)
332 return false;
333 if (iSong < 0)
334 iSong = 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))
344 break;
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);
358 if (!ret)
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
365 if (!m_iFailedSongs)
366 m_failedSongsStart = playAttempt;
367 m_iFailedSongs++;
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");
381 // open error dialog
382 HELPERS::ShowOKDialogText(CVariant{16026}, CVariant{16027});
384 CGUIMessage msg(GUI_MSG_PLAYLISTPLAYER_STOPPED, 0, 0, static_cast<int>(m_iCurrentPlayList),
385 m_iCurrentSong);
386 CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg);
387 Reset();
388 GetPlaylist(m_iCurrentPlayList).Clear();
389 m_iCurrentPlayList = Id::TYPE_NONE;
390 m_iFailedSongs = 0;
391 m_failedSongsStart = std::chrono::steady_clock::now();
392 return false;
395 // how many playable items are in the playlist?
396 if (playlist.GetPlayable() > 0)
398 return bPlayPrevious ? PlayPrevious() : PlayNext();
400 // none? then abort playback
401 else
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),
405 m_iCurrentSong);
406 CServiceBroker::GetGUI()->GetWindowManager().SendThreadMessage(msg);
407 Reset();
408 m_iCurrentPlayList = Id::TYPE_NONE;
409 return false;
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
419 //! of PlayFile()
421 // consecutive error counter so reset if the current item is playing
422 m_iFailedSongs = 0;
423 m_failedSongsStart = std::chrono::steady_clock::now();
424 m_bPlayedFirstFile = true;
425 return 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)
447 return;
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);
464 playlist.Clear();
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)
473 switch (playlistId)
475 case Id::TYPE_MUSIC:
476 return *m_PlaylistMusic;
477 break;
478 case Id::TYPE_VIDEO:
479 return *m_PlaylistVideo;
480 break;
481 default:
482 m_PlaylistEmpty->Clear();
483 return *m_PlaylistEmpty;
484 break;
488 const CPlayList& CPlayListPlayer::GetPlaylist(Id playlistId) const
490 switch (playlistId)
492 case Id::TYPE_MUSIC:
493 return *m_PlaylistMusic;
494 break;
495 case Id::TYPE_VIDEO:
496 return *m_PlaylistVideo;
497 break;
498 default:
499 // NOTE: This playlist may not be empty if the caller of the non-const version alters it!
500 return *m_PlaylistEmpty;
501 break;
505 int CPlayListPlayer::RemoveDVDItems()
507 int nRemovedM = m_PlaylistMusic->RemoveDVDItems();
508 int nRemovedV = m_PlaylistVideo->RemoveDVDItems();
510 return nRemovedM + nRemovedV;
513 void CPlayListPlayer::Reset()
515 m_iCurrentSong = -1;
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;
534 return false;
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);
542 return false;
545 void CPlayListPlayer::SetShuffle(Id playlistId, bool bYesNo, bool bNotify /* = false */)
547 if (playlistId != Id::TYPE_MUSIC && playlistId != Id::TYPE_VIDEO)
548 return;
550 // disable shuffle in party mode
551 if (g_partyModeManager.IsEnabled() && playlistId == Id::TYPE_MUSIC)
552 return;
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
558 int iOrder = -1;
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
564 if (bYesNo)
565 playlist.Shuffle();
566 else
567 playlist.UnShuffle();
569 if (bNotify)
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
578 if (iOrder >= 0)
580 int iIndex = playlist.FindOrder(iOrder);
581 if (iIndex >= 0)
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)
602 return false;
604 if (playlistId == Id::TYPE_MUSIC || playlistId == Id::TYPE_VIDEO)
605 return GetPlaylist(playlistId).IsShuffled();
607 return false;
610 void CPlayListPlayer::SetRepeat(Id playlistId, RepeatState state, bool bNotify /* = false */)
612 if (playlistId != Id::TYPE_MUSIC && playlistId != Id::TYPE_VIDEO)
613 return;
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
627 else
628 iLocalizedString = 597; // Repeat: All
629 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(559), g_localizeStrings.Get(iLocalizedString));
632 m_repeatState[playlistId] = state;
634 CVariant data;
635 switch (state)
637 case RepeatState::ONE:
638 data = "one";
639 break;
640 case RepeatState::ALL:
641 data = "all";
642 break;
643 default:
644 data = "off";
645 break;
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
688 else
690 GetPlaylist(playlistId).Shuffle(iPosition);
694 void CPlayListPlayer::Add(Id playlistId, const CPlayList& playlist)
696 if (playlistId != Id::TYPE_MUSIC && playlistId != Id::TYPE_VIDEO)
697 return;
698 CPlayList& list = GetPlaylist(playlistId);
699 int iSize = list.size();
700 list.Add(playlist);
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)
708 return;
709 CPlayList& list = GetPlaylist(playlistId);
710 int iSize = list.size();
711 list.Add(pItem);
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)
723 return;
724 CPlayList& list = GetPlaylist(playlistId);
725 int iSize = list.size();
726 list.Add(items);
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)
738 return;
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)
745 m_iCurrentSong++;
748 void CPlayListPlayer::Insert(Id playlistId, const CFileItemPtr& pItem, int iIndex)
750 if (playlistId != Id::TYPE_MUSIC && playlistId != Id::TYPE_VIDEO)
751 return;
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)
758 m_iCurrentSong++;
761 void CPlayListPlayer::Insert(Id playlistId, const CFileItemList& items, int iIndex)
763 if (playlistId != Id::TYPE_MUSIC && playlistId != Id::TYPE_VIDEO)
764 return;
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)
771 m_iCurrentSong++;
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)
781 return;
782 CPlayList& list = GetPlaylist(playlistId);
783 list.Remove(iPosition);
784 if (m_iCurrentPlayList == playlistId && m_iCurrentSong >= iPosition)
785 m_iCurrentSong--;
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()
794 if (m_PlaylistMusic)
795 m_PlaylistMusic->Clear();
796 if (m_PlaylistVideo)
797 m_PlaylistVideo->Clear();
798 if (m_PlaylistEmpty)
799 m_PlaylistEmpty->Clear();
802 void CPlayListPlayer::Swap(Id playlistId, int indexItem1, int indexItem2)
804 if (playlistId != Id::TYPE_MUSIC && playlistId != Id::TYPE_VIDEO)
805 return;
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()))
831 return;
833 CVariant data;
834 data["player"]["playerid"] = static_cast<int>(playlistId);
835 data["property"][strProperty] = value;
836 CServiceBroker::GetAnnouncementManager()->Announce(ANNOUNCEMENT::Player, "OnPropertyChanged",
837 data);
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, "");
862 else
863 Play();
864 break;
866 case TMSG_PLAYLISTPLAYER_PLAY_ITEM_ID:
867 if (pMsg->param1 != -1)
869 bool *result = (bool*)pMsg->lpVoid;
870 *result = PlayItemIdx(pMsg->param1);
872 else
873 Play();
874 break;
876 case TMSG_PLAYLISTPLAYER_NEXT:
877 PlayNext();
878 break;
880 case TMSG_PLAYLISTPLAYER_PREV:
881 PlayPrevious();
882 break;
884 case TMSG_PLAYLISTPLAYER_ADD:
885 if (pMsg->lpVoid)
887 CFileItemList *list = static_cast<CFileItemList*>(pMsg->lpVoid);
889 Add(Id{pMsg->param1}, (*list));
890 delete list;
892 break;
894 case TMSG_PLAYLISTPLAYER_INSERT:
895 if (pMsg->lpVoid)
897 CFileItemList *list = static_cast<CFileItemList*>(pMsg->lpVoid);
898 Insert(Id{pMsg->param1}, (*list), pMsg->param2);
899 delete list;
901 break;
903 case TMSG_PLAYLISTPLAYER_REMOVE:
904 if (pMsg->param1 != -1)
905 Remove(Id{pMsg->param1}, pMsg->param2);
906 break;
908 case TMSG_PLAYLISTPLAYER_CLEAR:
909 ClearPlaylist(Id{pMsg->param1});
910 break;
912 case TMSG_PLAYLISTPLAYER_SHUFFLE:
913 SetShuffle(Id{pMsg->param1}, pMsg->param2 > 0);
914 break;
916 case TMSG_PLAYLISTPLAYER_REPEAT:
917 SetRepeat(Id{pMsg->param1}, static_cast<RepeatState>(pMsg->param2));
918 break;
920 case TMSG_PLAYLISTPLAYER_GET_ITEMS:
921 if (pMsg->lpVoid)
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]));
929 break;
931 case TMSG_PLAYLISTPLAYER_SWAP:
932 if (pMsg->lpVoid)
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));
937 delete indexes;
939 break;
941 case TMSG_MEDIA_PLAY:
943 wakeScreensaver();
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.
950 Reset();
952 CFileItem *item = static_cast<CFileItem*>(pMsg->lpVoid);
953 g_application.PlayFile(*item, "", pMsg->param1 != 0);
954 delete item;
955 return;
958 //g_application.StopPlaying();
959 // play file
960 if (pMsg->lpVoid)
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;
972 break;
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))
985 return;
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()))
994 CLog::LogF(LOGERROR,
995 "MasterCode or MediaSource-code is wrong: {} will not be played.",
996 item->GetPath());
997 return;
999 Play(item, pMsg->strParam);
1001 else
1002 g_application.PlayMedia(*item, pMsg->strParam, playlistId);
1004 else
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()),
1012 false);
1014 Add(playlistId, (*list));
1015 Play(pMsg->param1, pMsg->strParam);
1019 delete list;
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);
1029 break;
1031 case TMSG_MEDIA_RESTART:
1032 g_application.Restart(true);
1033 break;
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();
1056 wakeScreensaver();
1058 // stop playing file
1059 if (appPlayer->IsPlaying())
1060 g_application.StopPlaying();
1062 break;
1064 case TMSG_MEDIA_PAUSE:
1065 if (appPlayer->HasPlayer())
1067 wakeScreensaver();
1068 appPlayer->Pause();
1070 break;
1072 case TMSG_MEDIA_UNPAUSE:
1073 if (appPlayer->IsPausedPlayback())
1075 wakeScreensaver();
1076 appPlayer->Pause();
1078 break;
1080 case TMSG_MEDIA_PAUSE_IF_PLAYING:
1081 if (appPlayer->IsPlaying() && !appPlayer->IsPaused())
1083 wakeScreensaver();
1084 appPlayer->Pause();
1086 break;
1088 case TMSG_MEDIA_SEEK_TIME:
1090 if (appPlayer->IsPlaying() || appPlayer->IsPaused())
1091 appPlayer->SeekTime(pMsg->param3);
1093 break;
1095 default:
1096 break;
1100 } // namespace KODI::PLAYLIST