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 "music/tags/MusicInfoTag.h"
34 #include "dialogs/GUIDialogKaiToast.h"
35 #include "guilib/LocalizeStrings.h"
36 #include "interfaces/AnnouncementManager.h"
37 #include "guilib/Key.h"
39 using namespace PLAYLIST;
41 CPlayListPlayer::CPlayListPlayer(void)
43 m_PlaylistMusic = new CPlayList(PLAYLIST_MUSIC);
44 m_PlaylistVideo = new CPlayList(PLAYLIST_VIDEO);
45 m_PlaylistEmpty = new CPlayList;
47 m_bPlayedFirstFile = false;
48 m_bPlaybackStarted = false;
49 m_iCurrentPlayList = PLAYLIST_NONE;
50 for (int i = 0; i < 2; i++)
51 m_repeatState[i] = REPEAT_NONE;
53 m_failedSongsStart = 0;
56 CPlayListPlayer::~CPlayListPlayer(void)
59 delete m_PlaylistMusic;
60 delete m_PlaylistVideo;
61 delete m_PlaylistEmpty;
64 bool CPlayListPlayer::OnAction(const CAction &action)
66 if (action.GetID() == ACTION_PREV_ITEM && !IsSingleItemNonRepeatPlaylist())
71 else if (action.GetID() == ACTION_NEXT_ITEM && !IsSingleItemNonRepeatPlaylist())
80 bool CPlayListPlayer::OnMessage(CGUIMessage &message)
82 switch (message.GetMessage())
84 case GUI_MSG_NOTIFY_ALL:
85 if (message.GetParam1() == GUI_MSG_UPDATE_ITEM && message.GetItem())
87 // update the items in our playlist(s) if necessary
88 for (int i = PLAYLIST_MUSIC; i <= PLAYLIST_VIDEO; i++)
90 CPlayList &playlist = GetPlaylist(i);
91 CFileItemPtr item = boost::static_pointer_cast<CFileItem>(message.GetItem());
92 playlist.UpdateItem(item.get());
96 case GUI_MSG_PLAYBACK_STOPPED:
98 if (m_iCurrentPlayList != PLAYLIST_NONE && m_bPlaybackStarted)
100 CGUIMessage msg(GUI_MSG_PLAYLISTPLAYER_STOPPED, 0, 0, m_iCurrentPlayList, m_iCurrentSong);
101 g_windowManager.SendThreadMessage(msg);
103 m_iCurrentPlayList = PLAYLIST_NONE;
113 int CPlayListPlayer::GetNextSong(int offset) const
115 if (m_iCurrentPlayList == PLAYLIST_NONE)
118 const CPlayList& playlist = GetPlaylist(m_iCurrentPlayList);
119 if (playlist.size() <= 0)
122 int song = m_iCurrentSong;
125 if (g_partyModeManager.IsEnabled() && GetCurrentPlaylist() == PLAYLIST_MUSIC)
126 return song + offset;
128 // wrap around in the case of repeating
129 if (RepeatedOne(m_iCurrentPlayList))
133 if (song >= playlist.size() && Repeated(m_iCurrentPlayList))
134 song %= playlist.size();
139 int CPlayListPlayer::GetNextSong()
141 if (m_iCurrentPlayList == PLAYLIST_NONE)
143 CPlayList& playlist = GetPlaylist(m_iCurrentPlayList);
144 if (playlist.size() <= 0)
146 int iSong = m_iCurrentSong;
149 if (g_partyModeManager.IsEnabled() && GetCurrentPlaylist() == PLAYLIST_MUSIC)
152 // if repeat one, keep playing the current song if its valid
153 if (RepeatedOne(m_iCurrentPlayList))
155 // otherwise immediately abort playback
156 if (m_iCurrentSong >= 0 && m_iCurrentSong < playlist.size() && playlist[m_iCurrentSong]->GetProperty("unplayable").asBoolean())
158 CLog::Log(LOGERROR,"Playlist Player: RepeatOne stuck on unplayable item: %i, path [%s]", m_iCurrentSong, playlist[m_iCurrentSong]->GetPath().c_str());
159 CGUIMessage msg(GUI_MSG_PLAYLISTPLAYER_STOPPED, 0, 0, m_iCurrentPlayList, m_iCurrentSong);
160 g_windowManager.SendThreadMessage(msg);
162 m_iCurrentPlayList = PLAYLIST_NONE;
168 // if we've gone beyond the playlist and repeat all is enabled,
169 // then we clear played status and wrap around
171 if (iSong >= playlist.size() && Repeated(m_iCurrentPlayList))
177 bool CPlayListPlayer::PlayNext(int offset, bool bAutoPlay)
179 int iSong = GetNextSong(offset);
180 CPlayList& playlist = GetPlaylist(m_iCurrentPlayList);
182 if ((iSong < 0) || (iSong >= playlist.size()) || (playlist.GetPlayable() <= 0))
185 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(559), g_localizeStrings.Get(34201));
187 CGUIMessage msg(GUI_MSG_PLAYLISTPLAYER_STOPPED, 0, 0, m_iCurrentPlayList, m_iCurrentSong);
188 g_windowManager.SendThreadMessage(msg);
190 m_iCurrentPlayList = PLAYLIST_NONE;
194 return Play(iSong, false);
197 bool CPlayListPlayer::PlayPrevious()
199 if (m_iCurrentPlayList == PLAYLIST_NONE)
202 CPlayList& playlist = GetPlaylist(m_iCurrentPlayList);
203 int iSong = m_iCurrentSong;
205 if (!RepeatedOne(m_iCurrentPlayList))
208 if (iSong < 0 && Repeated(m_iCurrentPlayList))
209 iSong = playlist.size() - 1;
211 if (iSong < 0 || playlist.size() <= 0)
213 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(559), g_localizeStrings.Get(34202));
217 return Play(iSong, false, true);
220 bool CPlayListPlayer::IsSingleItemNonRepeatPlaylist() const
222 const CPlayList& playlist = GetPlaylist(m_iCurrentPlayList);
223 return (playlist.size() <= 1 && !RepeatedOne(m_iCurrentPlayList) && !Repeated(m_iCurrentPlayList));
226 bool CPlayListPlayer::Play()
228 if (m_iCurrentPlayList == PLAYLIST_NONE)
231 CPlayList& playlist = GetPlaylist(m_iCurrentPlayList);
232 if (playlist.size() <= 0)
238 bool CPlayListPlayer::PlaySongId(int songId)
240 if (m_iCurrentPlayList == PLAYLIST_NONE)
243 CPlayList& playlist = GetPlaylist(m_iCurrentPlayList);
244 if (playlist.size() <= 0)
247 for (int i = 0; i < playlist.size(); i++)
249 if (playlist[i]->HasMusicInfoTag() && playlist[i]->GetMusicInfoTag()->GetDatabaseId() == songId)
255 bool CPlayListPlayer::Play(int iSong, bool bAutoPlay /* = false */, bool bPlayPrevious /* = false */)
257 if (m_iCurrentPlayList == PLAYLIST_NONE)
260 CPlayList& playlist = GetPlaylist(m_iCurrentPlayList);
261 if (playlist.size() <= 0)
265 if (iSong >= playlist.size())
266 iSong = playlist.size() - 1;
268 // check if the item itself is a playlist, and can be expanded
269 // only allow a few levels, this could end up in a loop
270 // if they refer to each other in a loop
273 if(!playlist.Expand(iSong))
277 m_iCurrentSong = iSong;
278 CFileItemPtr item = playlist[m_iCurrentSong];
279 playlist.SetPlayed(true);
281 m_bPlaybackStarted = false;
283 unsigned int playAttempt = XbmcThreads::SystemClockMillis();
284 PlayBackRet ret = g_application.PlayFile(*item, bAutoPlay);
285 if (ret == PLAYBACK_CANCELED)
287 if (ret == PLAYBACK_FAIL)
289 CLog::Log(LOGERROR,"Playlist Player: skipping unplayable item: %i, path [%s]", m_iCurrentSong, item->GetPath().c_str());
290 playlist.SetUnPlayable(m_iCurrentSong);
292 // abort on 100 failed CONSECTUTIVE songs
294 m_failedSongsStart = playAttempt;
296 if ((m_iFailedSongs >= g_advancedSettings.m_playlistRetries && g_advancedSettings.m_playlistRetries >= 0)
297 || ((XbmcThreads::SystemClockMillis() - m_failedSongsStart >= (unsigned int)g_advancedSettings.m_playlistTimeout * 1000) && g_advancedSettings.m_playlistTimeout))
299 CLog::Log(LOGDEBUG,"Playlist Player: one or more items failed to play... aborting playback");
302 CGUIDialogOK::ShowAndGetInput(16026, 16027, 16029, 0);
304 CGUIMessage msg(GUI_MSG_PLAYLISTPLAYER_STOPPED, 0, 0, m_iCurrentPlayList, m_iCurrentSong);
305 g_windowManager.SendThreadMessage(msg);
307 GetPlaylist(m_iCurrentPlayList).Clear();
308 m_iCurrentPlayList = PLAYLIST_NONE;
310 m_failedSongsStart = 0;
314 // how many playable items are in the playlist?
315 if (playlist.GetPlayable() > 0)
317 return bPlayPrevious ? PlayPrevious() : PlayNext();
319 // none? then abort playback
322 CLog::Log(LOGDEBUG,"Playlist Player: no more playable items... aborting playback");
323 CGUIMessage msg(GUI_MSG_PLAYLISTPLAYER_STOPPED, 0, 0, m_iCurrentPlayList, m_iCurrentSong);
324 g_windowManager.SendThreadMessage(msg);
326 m_iCurrentPlayList = PLAYLIST_NONE;
331 // reset the start offset of this item
332 if (item->m_lStartOffset == STARTOFFSET_RESUME)
333 item->m_lStartOffset = 0;
335 // TODO - move the above failure logic and the below success logic
336 // to callbacks instead so we don't rely on the return value
339 // consecutive error counter so reset if the current item is playing
341 m_failedSongsStart = 0;
342 m_bPlaybackStarted = true;
343 m_bPlayedFirstFile = true;
347 void CPlayListPlayer::SetCurrentSong(int iSong)
349 if (iSong >= -1 && iSong < GetPlaylist(m_iCurrentPlayList).size())
350 m_iCurrentSong = iSong;
353 int CPlayListPlayer::GetCurrentSong() const
355 return m_iCurrentSong;
358 int CPlayListPlayer::GetCurrentPlaylist() const
360 return m_iCurrentPlayList;
363 void CPlayListPlayer::SetCurrentPlaylist(int iPlaylist)
365 if (iPlaylist == m_iCurrentPlayList)
368 // changing the current playlist while party mode is on
369 // disables party mode
370 if (g_partyModeManager.IsEnabled())
371 g_partyModeManager.Disable();
373 m_iCurrentPlayList = iPlaylist;
374 m_bPlayedFirstFile = false;
377 void CPlayListPlayer::ClearPlaylist(int iPlaylist)
379 // clear our applications playlist file
380 g_application.m_strPlayListFile.Empty();
382 CPlayList& playlist = GetPlaylist(iPlaylist);
385 // its likely that the playlist changed
386 CGUIMessage msg(GUI_MSG_PLAYLIST_CHANGED, 0, 0);
387 g_windowManager.SendMessage(msg);
390 CPlayList& CPlayListPlayer::GetPlaylist(int iPlaylist)
395 return *m_PlaylistMusic;
398 return *m_PlaylistVideo;
401 m_PlaylistEmpty->Clear();
402 return *m_PlaylistEmpty;
407 const CPlayList& CPlayListPlayer::GetPlaylist(int iPlaylist) const
412 return *m_PlaylistMusic;
415 return *m_PlaylistVideo;
418 // NOTE: This playlist may not be empty if the caller of the non-const version alters it!
419 return *m_PlaylistEmpty;
424 int CPlayListPlayer::RemoveDVDItems()
426 int nRemovedM = m_PlaylistMusic->RemoveDVDItems();
427 int nRemovedV = m_PlaylistVideo->RemoveDVDItems();
429 return nRemovedM + nRemovedV;
432 void CPlayListPlayer::Reset()
435 m_bPlayedFirstFile = false;
436 m_bPlaybackStarted = false;
438 // its likely that the playlist changed
439 CGUIMessage msg(GUI_MSG_PLAYLIST_CHANGED, 0, 0);
440 g_windowManager.SendMessage(msg);
443 bool CPlayListPlayer::HasPlayedFirstFile() const
445 return m_bPlayedFirstFile;
448 bool CPlayListPlayer::Repeated(int iPlaylist) const
450 if (iPlaylist >= PLAYLIST_MUSIC && iPlaylist <= PLAYLIST_VIDEO)
451 return (m_repeatState[iPlaylist] == REPEAT_ALL);
455 bool CPlayListPlayer::RepeatedOne(int iPlaylist) const
457 if (iPlaylist == PLAYLIST_MUSIC || iPlaylist == PLAYLIST_VIDEO)
458 return (m_repeatState[iPlaylist] == REPEAT_ONE);
462 void CPlayListPlayer::SetShuffle(int iPlaylist, bool bYesNo, bool bNotify /* = false */)
464 if (iPlaylist != PLAYLIST_MUSIC && iPlaylist != PLAYLIST_VIDEO)
467 // disable shuffle in party mode
468 if (g_partyModeManager.IsEnabled() && iPlaylist == PLAYLIST_MUSIC)
471 // do we even need to do anything?
472 if (bYesNo != IsShuffled(iPlaylist))
474 // save the order value of the current song so we can use it find its new location later
476 CPlayList &playlist = GetPlaylist(iPlaylist);
477 if (m_iCurrentSong >= 0 && m_iCurrentSong < playlist.size())
478 iOrder = playlist[m_iCurrentSong]->m_iprogramCount;
480 // shuffle or unshuffle as necessary
484 playlist.UnShuffle();
488 CStdString shuffleStr;
489 shuffleStr.Format("%s: %s", g_localizeStrings.Get(191), g_localizeStrings.Get(bYesNo ? 593 : 591)); // 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);