2 * Copyright (C) 2005-2013 Team XBMC
5 * This Program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2, or (at your option)
10 * This Program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with XBMC; see the file COPYING. If not, see
17 * <http://www.gnu.org/licenses/>.
21 #include "threads/SystemClock.h"
22 #include "PlayListPlayer.h"
23 #include "playlists/PlayListFactory.h"
24 #include "Application.h"
25 #include "PartyModeManager.h"
26 #include "settings/AdvancedSettings.h"
27 #include "GUIUserMessages.h"
28 #include "guilib/GUIWindowManager.h"
29 #include "dialogs/GUIDialogOK.h"
30 #include "playlists/PlayList.h"
31 #include "utils/log.h"
32 #include "utils/TimeUtils.h"
33 #include "utils/StringUtils.h"
34 #include "music/tags/MusicInfoTag.h"
35 #include "dialogs/GUIDialogKaiToast.h"
36 #include "guilib/LocalizeStrings.h"
37 #include "interfaces/AnnouncementManager.h"
38 #include "guilib/Key.h"
40 using namespace PLAYLIST;
42 CPlayListPlayer::CPlayListPlayer(void)
44 m_PlaylistMusic = new CPlayList(PLAYLIST_MUSIC);
45 m_PlaylistVideo = new CPlayList(PLAYLIST_VIDEO);
46 m_PlaylistEmpty = new CPlayList;
48 m_bPlayedFirstFile = false;
49 m_bPlaybackStarted = false;
50 m_iCurrentPlayList = PLAYLIST_NONE;
51 for (int i = 0; i < 2; i++)
52 m_repeatState[i] = REPEAT_NONE;
54 m_failedSongsStart = 0;
57 CPlayListPlayer::~CPlayListPlayer(void)
60 delete m_PlaylistMusic;
61 delete m_PlaylistVideo;
62 delete m_PlaylistEmpty;
65 bool CPlayListPlayer::OnAction(const CAction &action)
67 if (action.GetID() == ACTION_PREV_ITEM && !IsSingleItemNonRepeatPlaylist())
72 else if (action.GetID() == ACTION_NEXT_ITEM && !IsSingleItemNonRepeatPlaylist())
81 bool CPlayListPlayer::OnMessage(CGUIMessage &message)
83 switch (message.GetMessage())
85 case GUI_MSG_NOTIFY_ALL:
86 if (message.GetParam1() == GUI_MSG_UPDATE_ITEM && message.GetItem())
88 // update the items in our playlist(s) if necessary
89 for (int i = PLAYLIST_MUSIC; i <= PLAYLIST_VIDEO; i++)
91 CPlayList &playlist = GetPlaylist(i);
92 CFileItemPtr item = boost::static_pointer_cast<CFileItem>(message.GetItem());
93 playlist.UpdateItem(item.get());
97 case GUI_MSG_PLAYBACK_STOPPED:
99 if (m_iCurrentPlayList != PLAYLIST_NONE && m_bPlaybackStarted)
101 CGUIMessage msg(GUI_MSG_PLAYLISTPLAYER_STOPPED, 0, 0, m_iCurrentPlayList, m_iCurrentSong);
102 g_windowManager.SendThreadMessage(msg);
104 m_iCurrentPlayList = PLAYLIST_NONE;
114 int CPlayListPlayer::GetNextSong(int offset) const
116 if (m_iCurrentPlayList == PLAYLIST_NONE)
119 const CPlayList& playlist = GetPlaylist(m_iCurrentPlayList);
120 if (playlist.size() <= 0)
123 int song = m_iCurrentSong;
126 if (g_partyModeManager.IsEnabled() && GetCurrentPlaylist() == PLAYLIST_MUSIC)
127 return song + offset;
129 // wrap around in the case of repeating
130 if (RepeatedOne(m_iCurrentPlayList))
134 if (song >= playlist.size() && Repeated(m_iCurrentPlayList))
135 song %= playlist.size();
140 int CPlayListPlayer::GetNextSong()
142 if (m_iCurrentPlayList == PLAYLIST_NONE)
144 CPlayList& playlist = GetPlaylist(m_iCurrentPlayList);
145 if (playlist.size() <= 0)
147 int iSong = m_iCurrentSong;
150 if (g_partyModeManager.IsEnabled() && GetCurrentPlaylist() == PLAYLIST_MUSIC)
153 // if repeat one, keep playing the current song if its valid
154 if (RepeatedOne(m_iCurrentPlayList))
156 // otherwise immediately abort playback
157 if (m_iCurrentSong >= 0 && m_iCurrentSong < playlist.size() && playlist[m_iCurrentSong]->GetProperty("unplayable").asBoolean())
159 CLog::Log(LOGERROR,"Playlist Player: RepeatOne stuck on unplayable item: %i, path [%s]", m_iCurrentSong, playlist[m_iCurrentSong]->GetPath().c_str());
160 CGUIMessage msg(GUI_MSG_PLAYLISTPLAYER_STOPPED, 0, 0, m_iCurrentPlayList, m_iCurrentSong);
161 g_windowManager.SendThreadMessage(msg);
163 m_iCurrentPlayList = PLAYLIST_NONE;
169 // if we've gone beyond the playlist and repeat all is enabled,
170 // then we clear played status and wrap around
172 if (iSong >= playlist.size() && Repeated(m_iCurrentPlayList))
178 bool CPlayListPlayer::PlayNext(int offset, bool bAutoPlay)
180 int iSong = GetNextSong(offset);
181 CPlayList& playlist = GetPlaylist(m_iCurrentPlayList);
183 if ((iSong < 0) || (iSong >= playlist.size()) || (playlist.GetPlayable() <= 0))
186 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(559), g_localizeStrings.Get(34201));
188 CGUIMessage msg(GUI_MSG_PLAYLISTPLAYER_STOPPED, 0, 0, m_iCurrentPlayList, m_iCurrentSong);
189 g_windowManager.SendThreadMessage(msg);
191 m_iCurrentPlayList = PLAYLIST_NONE;
195 return Play(iSong, false);
198 bool CPlayListPlayer::PlayPrevious()
200 if (m_iCurrentPlayList == PLAYLIST_NONE)
203 CPlayList& playlist = GetPlaylist(m_iCurrentPlayList);
204 int iSong = m_iCurrentSong;
206 if (!RepeatedOne(m_iCurrentPlayList))
209 if (iSong < 0 && Repeated(m_iCurrentPlayList))
210 iSong = playlist.size() - 1;
212 if (iSong < 0 || playlist.size() <= 0)
214 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(559), g_localizeStrings.Get(34202));
218 return Play(iSong, false, true);
221 bool CPlayListPlayer::IsSingleItemNonRepeatPlaylist() const
223 const CPlayList& playlist = GetPlaylist(m_iCurrentPlayList);
224 return (playlist.size() <= 1 && !RepeatedOne(m_iCurrentPlayList) && !Repeated(m_iCurrentPlayList));
227 bool CPlayListPlayer::Play()
229 if (m_iCurrentPlayList == PLAYLIST_NONE)
232 CPlayList& playlist = GetPlaylist(m_iCurrentPlayList);
233 if (playlist.size() <= 0)
239 bool CPlayListPlayer::PlaySongId(int songId)
241 if (m_iCurrentPlayList == PLAYLIST_NONE)
244 CPlayList& playlist = GetPlaylist(m_iCurrentPlayList);
245 if (playlist.size() <= 0)
248 for (int i = 0; i < playlist.size(); i++)
250 if (playlist[i]->HasMusicInfoTag() && playlist[i]->GetMusicInfoTag()->GetDatabaseId() == songId)
256 bool CPlayListPlayer::Play(int iSong, bool bAutoPlay /* = false */, bool bPlayPrevious /* = false */)
258 if (m_iCurrentPlayList == PLAYLIST_NONE)
261 CPlayList& playlist = GetPlaylist(m_iCurrentPlayList);
262 if (playlist.size() <= 0)
266 if (iSong >= playlist.size())
267 iSong = playlist.size() - 1;
269 // check if the item itself is a playlist, and can be expanded
270 // only allow a few levels, this could end up in a loop
271 // if they refer to each other in a loop
274 if(!playlist.Expand(iSong))
278 m_iCurrentSong = iSong;
279 CFileItemPtr item = playlist[m_iCurrentSong];
280 playlist.SetPlayed(true);
282 m_bPlaybackStarted = false;
284 unsigned int playAttempt = XbmcThreads::SystemClockMillis();
285 PlayBackRet ret = g_application.PlayFile(*item, bAutoPlay);
286 if (ret == PLAYBACK_CANCELED)
288 if (ret == PLAYBACK_FAIL)
290 CLog::Log(LOGERROR,"Playlist Player: skipping unplayable item: %i, path [%s]", m_iCurrentSong, item->GetPath().c_str());
291 playlist.SetUnPlayable(m_iCurrentSong);
293 // abort on 100 failed CONSECTUTIVE songs
295 m_failedSongsStart = playAttempt;
297 if ((m_iFailedSongs >= g_advancedSettings.m_playlistRetries && g_advancedSettings.m_playlistRetries >= 0)
298 || ((XbmcThreads::SystemClockMillis() - m_failedSongsStart >= (unsigned int)g_advancedSettings.m_playlistTimeout * 1000) && g_advancedSettings.m_playlistTimeout))
300 CLog::Log(LOGDEBUG,"Playlist Player: one or more items failed to play... aborting playback");
303 CGUIDialogOK::ShowAndGetInput(16026, 16027, 16029, 0);
305 CGUIMessage msg(GUI_MSG_PLAYLISTPLAYER_STOPPED, 0, 0, m_iCurrentPlayList, m_iCurrentSong);
306 g_windowManager.SendThreadMessage(msg);
308 GetPlaylist(m_iCurrentPlayList).Clear();
309 m_iCurrentPlayList = PLAYLIST_NONE;
311 m_failedSongsStart = 0;
315 // how many playable items are in the playlist?
316 if (playlist.GetPlayable() > 0)
318 return bPlayPrevious ? PlayPrevious() : PlayNext();
320 // none? then abort playback
323 CLog::Log(LOGDEBUG,"Playlist Player: no more playable items... aborting playback");
324 CGUIMessage msg(GUI_MSG_PLAYLISTPLAYER_STOPPED, 0, 0, m_iCurrentPlayList, m_iCurrentSong);
325 g_windowManager.SendThreadMessage(msg);
327 m_iCurrentPlayList = PLAYLIST_NONE;
332 // reset the start offset of this item
333 if (item->m_lStartOffset == STARTOFFSET_RESUME)
334 item->m_lStartOffset = 0;
336 // TODO - move the above failure logic and the below success logic
337 // to callbacks instead so we don't rely on the return value
340 // consecutive error counter so reset if the current item is playing
342 m_failedSongsStart = 0;
343 m_bPlaybackStarted = true;
344 m_bPlayedFirstFile = true;
348 void CPlayListPlayer::SetCurrentSong(int iSong)
350 if (iSong >= -1 && iSong < GetPlaylist(m_iCurrentPlayList).size())
351 m_iCurrentSong = iSong;
354 int CPlayListPlayer::GetCurrentSong() const
356 return m_iCurrentSong;
359 int CPlayListPlayer::GetCurrentPlaylist() const
361 return m_iCurrentPlayList;
364 void CPlayListPlayer::SetCurrentPlaylist(int iPlaylist)
366 if (iPlaylist == m_iCurrentPlayList)
369 // changing the current playlist while party mode is on
370 // disables party mode
371 if (g_partyModeManager.IsEnabled())
372 g_partyModeManager.Disable();
374 m_iCurrentPlayList = iPlaylist;
375 m_bPlayedFirstFile = false;
378 void CPlayListPlayer::ClearPlaylist(int iPlaylist)
380 // clear our applications playlist file
381 g_application.m_strPlayListFile.clear();
383 CPlayList& playlist = GetPlaylist(iPlaylist);
386 // its likely that the playlist changed
387 CGUIMessage msg(GUI_MSG_PLAYLIST_CHANGED, 0, 0);
388 g_windowManager.SendMessage(msg);
391 CPlayList& CPlayListPlayer::GetPlaylist(int iPlaylist)
396 return *m_PlaylistMusic;
399 return *m_PlaylistVideo;
402 m_PlaylistEmpty->Clear();
403 return *m_PlaylistEmpty;
408 const CPlayList& CPlayListPlayer::GetPlaylist(int iPlaylist) const
413 return *m_PlaylistMusic;
416 return *m_PlaylistVideo;
419 // NOTE: This playlist may not be empty if the caller of the non-const version alters it!
420 return *m_PlaylistEmpty;
425 int CPlayListPlayer::RemoveDVDItems()
427 int nRemovedM = m_PlaylistMusic->RemoveDVDItems();
428 int nRemovedV = m_PlaylistVideo->RemoveDVDItems();
430 return nRemovedM + nRemovedV;
433 void CPlayListPlayer::Reset()
436 m_bPlayedFirstFile = false;
437 m_bPlaybackStarted = false;
439 // its likely that the playlist changed
440 CGUIMessage msg(GUI_MSG_PLAYLIST_CHANGED, 0, 0);
441 g_windowManager.SendMessage(msg);
444 bool CPlayListPlayer::HasPlayedFirstFile() const
446 return m_bPlayedFirstFile;
449 bool CPlayListPlayer::Repeated(int iPlaylist) const
451 if (iPlaylist >= PLAYLIST_MUSIC && iPlaylist <= PLAYLIST_VIDEO)
452 return (m_repeatState[iPlaylist] == REPEAT_ALL);
456 bool CPlayListPlayer::RepeatedOne(int iPlaylist) const
458 if (iPlaylist == PLAYLIST_MUSIC || iPlaylist == PLAYLIST_VIDEO)
459 return (m_repeatState[iPlaylist] == REPEAT_ONE);
463 void CPlayListPlayer::SetShuffle(int iPlaylist, bool bYesNo, bool bNotify /* = false */)
465 if (iPlaylist != PLAYLIST_MUSIC && iPlaylist != PLAYLIST_VIDEO)
468 // disable shuffle in party mode
469 if (g_partyModeManager.IsEnabled() && iPlaylist == PLAYLIST_MUSIC)
472 // do we even need to do anything?
473 if (bYesNo != IsShuffled(iPlaylist))
475 // save the order value of the current song so we can use it find its new location later
477 CPlayList &playlist = GetPlaylist(iPlaylist);
478 if (m_iCurrentSong >= 0 && m_iCurrentSong < playlist.size())
479 iOrder = playlist[m_iCurrentSong]->m_iprogramCount;
481 // shuffle or unshuffle as necessary
485 playlist.UnShuffle();
489 CStdString shuffleStr = StringUtils::Format("%s: %s", g_localizeStrings.Get(191).c_str(), g_localizeStrings.Get(bYesNo ? 593 : 591).c_str()); // Shuffle: All/Off
490 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(559), shuffleStr);
493 // find the previous order value and fix the current song marker
496 int iIndex = playlist.FindOrder(iOrder);
498 m_iCurrentSong = iIndex;
499 // if iIndex < 0, something unexpected happened
500 // so dont do anything
504 AnnouncePropertyChanged(iPlaylist, "shuffled", IsShuffled(iPlaylist));
507 bool CPlayListPlayer::IsShuffled(int iPlaylist) const
509 // even if shuffled, party mode says its not
510 if (g_partyModeManager.IsEnabled() && iPlaylist == PLAYLIST_MUSIC)
513 if (iPlaylist == PLAYLIST_MUSIC || iPlaylist == PLAYLIST_VIDEO)
514 return GetPlaylist(iPlaylist).IsShuffled();
519 void CPlayListPlayer::SetRepeat(int iPlaylist, REPEAT_STATE state, bool bNotify /* = false */)
521 if (iPlaylist != PLAYLIST_MUSIC && iPlaylist != PLAYLIST_VIDEO)
524 // disable repeat in party mode
525 if (g_partyModeManager.IsEnabled() && iPlaylist == PLAYLIST_MUSIC)
528 // notify the user if there was a change in the repeat state
529 if (m_repeatState[iPlaylist] != state && bNotify)
531 int iLocalizedString;
532 if (state == REPEAT_NONE)
533 iLocalizedString = 595; // Repeat: Off
534 else if (state == REPEAT_ONE)
535 iLocalizedString = 596; // Repeat: One
537 iLocalizedString = 597; // Repeat: All
538 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(559), g_localizeStrings.Get(iLocalizedString));
541 m_repeatState[iPlaylist] = state;
556 AnnouncePropertyChanged(iPlaylist, "repeat", data);
559 REPEAT_STATE CPlayListPlayer::GetRepeat(int iPlaylist) const
561 if (iPlaylist == PLAYLIST_MUSIC || iPlaylist == PLAYLIST_VIDEO)
562 return m_repeatState[iPlaylist];
566 void CPlayListPlayer::ReShuffle(int iPlaylist, int iPosition)
568 // playlist has not played yet so shuffle the entire list
569 // (this only really works for new video playlists)
570 if (!GetPlaylist(iPlaylist).WasPlayed())
572 GetPlaylist(iPlaylist).Shuffle();
574 // we're trying to shuffle new items into the curently playing playlist
575 // so we shuffle starting at two positions below the current item
576 else if (iPlaylist == m_iCurrentPlayList)
579 (g_application.m_pPlayer->IsPlayingAudio() && iPlaylist == PLAYLIST_MUSIC) ||
580 (g_application.m_pPlayer->IsPlayingVideo() && iPlaylist == PLAYLIST_VIDEO)
583 g_playlistPlayer.GetPlaylist(iPlaylist).Shuffle(m_iCurrentSong + 2);
586 // otherwise, shuffle from the passed position
587 // which is the position of the first new item added
590 g_playlistPlayer.GetPlaylist(iPlaylist).Shuffle(iPosition);
594 void CPlayListPlayer::Add(int iPlaylist, CPlayList& playlist)
596 if (iPlaylist != PLAYLIST_MUSIC && iPlaylist != PLAYLIST_VIDEO)
598 CPlayList& list = GetPlaylist(iPlaylist);
599 int iSize = list.size();
601 if (list.IsShuffled())
602 ReShuffle(iPlaylist, iSize);
605 void CPlayListPlayer::Add(int iPlaylist, const CFileItemPtr &pItem)
607 if (iPlaylist != PLAYLIST_MUSIC && iPlaylist != PLAYLIST_VIDEO)
609 CPlayList& list = GetPlaylist(iPlaylist);
610 int iSize = list.size();
612 if (list.IsShuffled())
613 ReShuffle(iPlaylist, iSize);
616 void CPlayListPlayer::Add(int iPlaylist, CFileItemList& items)
618 if (iPlaylist != PLAYLIST_MUSIC && iPlaylist != PLAYLIST_VIDEO)
620 CPlayList& list = GetPlaylist(iPlaylist);
621 int iSize = list.size();
623 if (list.IsShuffled())
624 ReShuffle(iPlaylist, iSize);
627 void CPlayListPlayer::Insert(int iPlaylist, CPlayList& playlist, int iIndex)
629 if (iPlaylist != PLAYLIST_MUSIC && iPlaylist != PLAYLIST_VIDEO)
631 CPlayList& list = GetPlaylist(iPlaylist);
632 int iSize = list.size();
633 list.Insert(playlist, iIndex);
634 if (list.IsShuffled())
635 ReShuffle(iPlaylist, iSize);
636 else if (m_iCurrentPlayList == iPlaylist && m_iCurrentSong >= iIndex)
640 void CPlayListPlayer::Insert(int iPlaylist, const CFileItemPtr &pItem, int iIndex)
642 if (iPlaylist != PLAYLIST_MUSIC && iPlaylist != PLAYLIST_VIDEO)
644 CPlayList& list = GetPlaylist(iPlaylist);
645 int iSize = list.size();
646 list.Insert(pItem, iIndex);
647 if (list.IsShuffled())
648 ReShuffle(iPlaylist, iSize);
649 else if (m_iCurrentPlayList == iPlaylist && m_iCurrentSong >= iIndex)
653 void CPlayListPlayer::Insert(int iPlaylist, CFileItemList& items, int iIndex)
655 if (iPlaylist != PLAYLIST_MUSIC && iPlaylist != PLAYLIST_VIDEO)
657 CPlayList& list = GetPlaylist(iPlaylist);
658 int iSize = list.size();
659 list.Insert(items, iIndex);
660 if (list.IsShuffled())
661 ReShuffle(iPlaylist, iSize);
662 else if (m_iCurrentPlayList == iPlaylist && m_iCurrentSong >= iIndex)
666 void CPlayListPlayer::Remove(int iPlaylist, int iPosition)
668 if (iPlaylist != PLAYLIST_MUSIC && iPlaylist != PLAYLIST_VIDEO)
670 CPlayList& list = GetPlaylist(iPlaylist);
671 list.Remove(iPosition);
672 if (m_iCurrentPlayList == iPlaylist && m_iCurrentSong >= iPosition)
675 // its likely that the playlist changed
676 CGUIMessage msg(GUI_MSG_PLAYLIST_CHANGED, 0, 0);
677 g_windowManager.SendMessage(msg);
680 void CPlayListPlayer::Clear()
683 m_PlaylistMusic->Clear();
685 m_PlaylistVideo->Clear();
687 m_PlaylistEmpty->Clear();
690 void CPlayListPlayer::Swap(int iPlaylist, int indexItem1, int indexItem2)
692 if (iPlaylist != PLAYLIST_MUSIC && iPlaylist != PLAYLIST_VIDEO)
695 CPlayList& list = GetPlaylist(iPlaylist);
696 if (list.Swap(indexItem1, indexItem2) && iPlaylist == m_iCurrentPlayList)
698 if (m_iCurrentSong == indexItem1)
699 m_iCurrentSong = indexItem2;
700 else if (m_iCurrentSong == indexItem2)
701 m_iCurrentSong = indexItem1;
704 // its likely that the playlist changed
705 CGUIMessage msg(GUI_MSG_PLAYLIST_CHANGED, 0, 0);
706 g_windowManager.SendMessage(msg);
709 void CPlayListPlayer::AnnouncePropertyChanged(int iPlaylist, const std::string &strProperty, const CVariant &value)
711 if (strProperty.empty() || value.isNull() ||
712 (iPlaylist == PLAYLIST_VIDEO && !g_application.m_pPlayer->IsPlayingVideo()) ||
713 (iPlaylist == PLAYLIST_MUSIC && !g_application.m_pPlayer->IsPlayingAudio()))
717 data["player"]["playerid"] = iPlaylist;
718 data["property"][strProperty] = value;
719 ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::Player, "xbmc", "OnPropertyChanged", data);