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"
38 using namespace PLAYLIST;
40 CPlayListPlayer::CPlayListPlayer(void)
42 m_PlaylistMusic = new CPlayList(PLAYLIST_MUSIC);
43 m_PlaylistVideo = new CPlayList(PLAYLIST_VIDEO);
44 m_PlaylistEmpty = new CPlayList;
46 m_bPlayedFirstFile = false;
47 m_bPlaybackStarted = false;
48 m_iCurrentPlayList = PLAYLIST_NONE;
49 for (int i = 0; i < 2; i++)
50 m_repeatState[i] = REPEAT_NONE;
52 m_failedSongsStart = 0;
55 CPlayListPlayer::~CPlayListPlayer(void)
58 delete m_PlaylistMusic;
59 delete m_PlaylistVideo;
60 delete m_PlaylistEmpty;
63 bool CPlayListPlayer::OnMessage(CGUIMessage &message)
65 switch (message.GetMessage())
67 case GUI_MSG_NOTIFY_ALL:
68 if (message.GetParam1() == GUI_MSG_UPDATE_ITEM && message.GetItem())
70 // update the items in our playlist(s) if necessary
71 for (int i = PLAYLIST_MUSIC; i <= PLAYLIST_VIDEO; i++)
73 CPlayList &playlist = GetPlaylist(i);
74 CFileItemPtr item = boost::static_pointer_cast<CFileItem>(message.GetItem());
75 playlist.UpdateItem(item.get());
79 case GUI_MSG_PLAYBACK_STOPPED:
81 if (m_iCurrentPlayList != PLAYLIST_NONE && m_bPlaybackStarted)
83 CGUIMessage msg(GUI_MSG_PLAYLISTPLAYER_STOPPED, 0, 0, m_iCurrentPlayList, m_iCurrentSong);
84 g_windowManager.SendThreadMessage(msg);
86 m_iCurrentPlayList = PLAYLIST_NONE;
96 int CPlayListPlayer::GetNextSong(int offset) const
98 if (m_iCurrentPlayList == PLAYLIST_NONE)
101 const CPlayList& playlist = GetPlaylist(m_iCurrentPlayList);
102 if (playlist.size() <= 0)
105 int song = m_iCurrentSong;
108 if (g_partyModeManager.IsEnabled() && GetCurrentPlaylist() == PLAYLIST_MUSIC)
109 return song + offset;
111 // wrap around in the case of repeating
112 if (RepeatedOne(m_iCurrentPlayList))
116 if (song >= playlist.size() && Repeated(m_iCurrentPlayList))
117 song %= playlist.size();
122 int CPlayListPlayer::GetNextSong()
124 if (m_iCurrentPlayList == PLAYLIST_NONE)
126 CPlayList& playlist = GetPlaylist(m_iCurrentPlayList);
127 if (playlist.size() <= 0)
129 int iSong = m_iCurrentSong;
132 if (g_partyModeManager.IsEnabled() && GetCurrentPlaylist() == PLAYLIST_MUSIC)
135 // if repeat one, keep playing the current song if its valid
136 if (RepeatedOne(m_iCurrentPlayList))
138 // otherwise immediately abort playback
139 if (m_iCurrentSong >= 0 && m_iCurrentSong < playlist.size() && playlist[m_iCurrentSong]->GetProperty("unplayable").asBoolean())
141 CLog::Log(LOGERROR,"Playlist Player: RepeatOne stuck on unplayable item: %i, path [%s]", m_iCurrentSong, playlist[m_iCurrentSong]->GetPath().c_str());
142 CGUIMessage msg(GUI_MSG_PLAYLISTPLAYER_STOPPED, 0, 0, m_iCurrentPlayList, m_iCurrentSong);
143 g_windowManager.SendThreadMessage(msg);
145 m_iCurrentPlayList = PLAYLIST_NONE;
151 // if we've gone beyond the playlist and repeat all is enabled,
152 // then we clear played status and wrap around
154 if (iSong >= playlist.size() && Repeated(m_iCurrentPlayList))
160 bool CPlayListPlayer::PlayNext(int offset, bool bAutoPlay)
162 int iSong = GetNextSong(offset);
163 CPlayList& playlist = GetPlaylist(m_iCurrentPlayList);
165 if ((iSong < 0) || (iSong >= playlist.size()) || (playlist.GetPlayable() <= 0))
168 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(559), g_localizeStrings.Get(34201));
170 CGUIMessage msg(GUI_MSG_PLAYLISTPLAYER_STOPPED, 0, 0, m_iCurrentPlayList, m_iCurrentSong);
171 g_windowManager.SendThreadMessage(msg);
173 m_iCurrentPlayList = PLAYLIST_NONE;
177 return Play(iSong, false);
180 bool CPlayListPlayer::PlayPrevious()
182 if (m_iCurrentPlayList == PLAYLIST_NONE)
185 CPlayList& playlist = GetPlaylist(m_iCurrentPlayList);
186 int iSong = m_iCurrentSong;
188 if (!RepeatedOne(m_iCurrentPlayList))
191 if (iSong < 0 && Repeated(m_iCurrentPlayList))
192 iSong = playlist.size() - 1;
194 if (iSong < 0 || playlist.size() <= 0)
196 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(559), g_localizeStrings.Get(34202));
200 return Play(iSong, false, true);
203 bool CPlayListPlayer::Play()
205 if (m_iCurrentPlayList == PLAYLIST_NONE)
208 CPlayList& playlist = GetPlaylist(m_iCurrentPlayList);
209 if (playlist.size() <= 0)
215 bool CPlayListPlayer::PlaySongId(int songId)
217 if (m_iCurrentPlayList == PLAYLIST_NONE)
220 CPlayList& playlist = GetPlaylist(m_iCurrentPlayList);
221 if (playlist.size() <= 0)
224 for (int i = 0; i < playlist.size(); i++)
226 if (playlist[i]->HasMusicInfoTag() && playlist[i]->GetMusicInfoTag()->GetDatabaseId() == songId)
232 bool CPlayListPlayer::Play(int iSong, bool bAutoPlay /* = false */, bool bPlayPrevious /* = false */)
234 if (m_iCurrentPlayList == PLAYLIST_NONE)
237 CPlayList& playlist = GetPlaylist(m_iCurrentPlayList);
238 if (playlist.size() <= 0)
242 if (iSong >= playlist.size())
243 iSong = playlist.size() - 1;
245 // check if the item itself is a playlist, and can be expanded
246 // only allow a few levels, this could end up in a loop
247 // if they refer to each other in a loop
250 if(!playlist.Expand(iSong))
254 m_iCurrentSong = iSong;
255 CFileItemPtr item = playlist[m_iCurrentSong];
256 playlist.SetPlayed(true);
258 m_bPlaybackStarted = false;
260 unsigned int playAttempt = XbmcThreads::SystemClockMillis();
261 PlayBackRet ret = g_application.PlayFile(*item, bAutoPlay);
262 if (ret == PLAYBACK_CANCELED)
264 if (ret == PLAYBACK_FAIL)
266 CLog::Log(LOGERROR,"Playlist Player: skipping unplayable item: %i, path [%s]", m_iCurrentSong, item->GetPath().c_str());
267 playlist.SetUnPlayable(m_iCurrentSong);
269 // abort on 100 failed CONSECTUTIVE songs
271 m_failedSongsStart = playAttempt;
273 if ((m_iFailedSongs >= g_advancedSettings.m_playlistRetries && g_advancedSettings.m_playlistRetries >= 0)
274 || ((XbmcThreads::SystemClockMillis() - m_failedSongsStart >= (unsigned int)g_advancedSettings.m_playlistTimeout * 1000) && g_advancedSettings.m_playlistTimeout))
276 CLog::Log(LOGDEBUG,"Playlist Player: one or more items failed to play... aborting playback");
279 CGUIDialogOK::ShowAndGetInput(16026, 16027, 16029, 0);
281 CGUIMessage msg(GUI_MSG_PLAYLISTPLAYER_STOPPED, 0, 0, m_iCurrentPlayList, m_iCurrentSong);
282 g_windowManager.SendThreadMessage(msg);
284 GetPlaylist(m_iCurrentPlayList).Clear();
285 m_iCurrentPlayList = PLAYLIST_NONE;
287 m_failedSongsStart = 0;
291 // how many playable items are in the playlist?
292 if (playlist.GetPlayable() > 0)
294 return bPlayPrevious ? PlayPrevious() : PlayNext();
296 // none? then abort playback
299 CLog::Log(LOGDEBUG,"Playlist Player: no more playable items... aborting playback");
300 CGUIMessage msg(GUI_MSG_PLAYLISTPLAYER_STOPPED, 0, 0, m_iCurrentPlayList, m_iCurrentSong);
301 g_windowManager.SendThreadMessage(msg);
303 m_iCurrentPlayList = PLAYLIST_NONE;
308 // reset the start offset of this item
309 if (item->m_lStartOffset == STARTOFFSET_RESUME)
310 item->m_lStartOffset = 0;
312 // TODO - move the above failure logic and the below success logic
313 // to callbacks instead so we don't rely on the return value
316 // consecutive error counter so reset if the current item is playing
318 m_failedSongsStart = 0;
319 m_bPlaybackStarted = true;
320 m_bPlayedFirstFile = true;
324 void CPlayListPlayer::SetCurrentSong(int iSong)
326 if (iSong >= -1 && iSong < GetPlaylist(m_iCurrentPlayList).size())
327 m_iCurrentSong = iSong;
330 int CPlayListPlayer::GetCurrentSong() const
332 return m_iCurrentSong;
335 int CPlayListPlayer::GetCurrentPlaylist() const
337 return m_iCurrentPlayList;
340 void CPlayListPlayer::SetCurrentPlaylist(int iPlaylist)
342 if (iPlaylist == m_iCurrentPlayList)
345 // changing the current playlist while party mode is on
346 // disables party mode
347 if (g_partyModeManager.IsEnabled())
348 g_partyModeManager.Disable();
350 m_iCurrentPlayList = iPlaylist;
351 m_bPlayedFirstFile = false;
354 void CPlayListPlayer::ClearPlaylist(int iPlaylist)
356 // clear our applications playlist file
357 g_application.m_strPlayListFile.Empty();
359 CPlayList& playlist = GetPlaylist(iPlaylist);
362 // its likely that the playlist changed
363 CGUIMessage msg(GUI_MSG_PLAYLIST_CHANGED, 0, 0);
364 g_windowManager.SendMessage(msg);
367 CPlayList& CPlayListPlayer::GetPlaylist(int iPlaylist)
372 return *m_PlaylistMusic;
375 return *m_PlaylistVideo;
378 m_PlaylistEmpty->Clear();
379 return *m_PlaylistEmpty;
384 const CPlayList& CPlayListPlayer::GetPlaylist(int iPlaylist) const
389 return *m_PlaylistMusic;
392 return *m_PlaylistVideo;
395 // NOTE: This playlist may not be empty if the caller of the non-const version alters it!
396 return *m_PlaylistEmpty;
401 int CPlayListPlayer::RemoveDVDItems()
403 int nRemovedM = m_PlaylistMusic->RemoveDVDItems();
404 int nRemovedV = m_PlaylistVideo->RemoveDVDItems();
406 return nRemovedM + nRemovedV;
409 void CPlayListPlayer::Reset()
412 m_bPlayedFirstFile = false;
413 m_bPlaybackStarted = false;
415 // its likely that the playlist changed
416 CGUIMessage msg(GUI_MSG_PLAYLIST_CHANGED, 0, 0);
417 g_windowManager.SendMessage(msg);
420 bool CPlayListPlayer::HasPlayedFirstFile() const
422 return m_bPlayedFirstFile;
425 bool CPlayListPlayer::Repeated(int iPlaylist) const
427 if (iPlaylist >= PLAYLIST_MUSIC && iPlaylist <= PLAYLIST_VIDEO)
428 return (m_repeatState[iPlaylist] == REPEAT_ALL);
432 bool CPlayListPlayer::RepeatedOne(int iPlaylist) const
434 if (iPlaylist == PLAYLIST_MUSIC || iPlaylist == PLAYLIST_VIDEO)
435 return (m_repeatState[iPlaylist] == REPEAT_ONE);
439 void CPlayListPlayer::SetShuffle(int iPlaylist, bool bYesNo, bool bNotify /* = false */)
441 if (iPlaylist != PLAYLIST_MUSIC && iPlaylist != PLAYLIST_VIDEO)
444 // disable shuffle in party mode
445 if (g_partyModeManager.IsEnabled() && iPlaylist == PLAYLIST_MUSIC)
448 // do we even need to do anything?
449 if (bYesNo != IsShuffled(iPlaylist))
451 // save the order value of the current song so we can use it find its new location later
453 CPlayList &playlist = GetPlaylist(iPlaylist);
454 if (m_iCurrentSong >= 0 && m_iCurrentSong < playlist.size())
455 iOrder = playlist[m_iCurrentSong]->m_iprogramCount;
457 // shuffle or unshuffle as necessary
461 playlist.UnShuffle();
465 CStdString shuffleStr;
466 shuffleStr.Format("%s: %s", g_localizeStrings.Get(191), g_localizeStrings.Get(bYesNo ? 593 : 591)); // Shuffle: All/Off
467 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(559), shuffleStr);
470 // find the previous order value and fix the current song marker
473 int iIndex = playlist.FindOrder(iOrder);
475 m_iCurrentSong = iIndex;
476 // if iIndex < 0, something unexpected happened
477 // so dont do anything
481 AnnouncePropertyChanged(iPlaylist, "shuffled", IsShuffled(iPlaylist));
484 bool CPlayListPlayer::IsShuffled(int iPlaylist) const
486 // even if shuffled, party mode says its not
487 if (g_partyModeManager.IsEnabled() && iPlaylist == PLAYLIST_MUSIC)
490 if (iPlaylist == PLAYLIST_MUSIC || iPlaylist == PLAYLIST_VIDEO)
491 return GetPlaylist(iPlaylist).IsShuffled();
496 void CPlayListPlayer::SetRepeat(int iPlaylist, REPEAT_STATE state, bool bNotify /* = false */)
498 if (iPlaylist != PLAYLIST_MUSIC && iPlaylist != PLAYLIST_VIDEO)
501 // disable repeat in party mode
502 if (g_partyModeManager.IsEnabled() && iPlaylist == PLAYLIST_MUSIC)
505 // notify the user if there was a change in the repeat state
506 if (m_repeatState[iPlaylist] != state && bNotify)
508 int iLocalizedString;
509 if (state == REPEAT_NONE)
510 iLocalizedString = 595; // Repeat: Off
511 else if (state == REPEAT_ONE)
512 iLocalizedString = 596; // Repeat: One
514 iLocalizedString = 597; // Repeat: All
515 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(559), g_localizeStrings.Get(iLocalizedString));
518 m_repeatState[iPlaylist] = state;
533 AnnouncePropertyChanged(iPlaylist, "repeat", data);
536 REPEAT_STATE CPlayListPlayer::GetRepeat(int iPlaylist) const
538 if (iPlaylist == PLAYLIST_MUSIC || iPlaylist == PLAYLIST_VIDEO)
539 return m_repeatState[iPlaylist];
543 void CPlayListPlayer::ReShuffle(int iPlaylist, int iPosition)
545 // playlist has not played yet so shuffle the entire list
546 // (this only really works for new video playlists)
547 if (!GetPlaylist(iPlaylist).WasPlayed())
549 GetPlaylist(iPlaylist).Shuffle();
551 // we're trying to shuffle new items into the curently playing playlist
552 // so we shuffle starting at two positions below the current item
553 else if (iPlaylist == m_iCurrentPlayList)
556 (g_application.IsPlayingAudio() && iPlaylist == PLAYLIST_MUSIC) ||
557 (g_application.IsPlayingVideo() && iPlaylist == PLAYLIST_VIDEO)
560 g_playlistPlayer.GetPlaylist(iPlaylist).Shuffle(m_iCurrentSong + 2);
563 // otherwise, shuffle from the passed position
564 // which is the position of the first new item added
567 g_playlistPlayer.GetPlaylist(iPlaylist).Shuffle(iPosition);
571 void CPlayListPlayer::Add(int iPlaylist, CPlayList& playlist)
573 if (iPlaylist != PLAYLIST_MUSIC && iPlaylist != PLAYLIST_VIDEO)
575 CPlayList& list = GetPlaylist(iPlaylist);
576 int iSize = list.size();
578 if (list.IsShuffled())
579 ReShuffle(iPlaylist, iSize);
582 void CPlayListPlayer::Add(int iPlaylist, const CFileItemPtr &pItem)
584 if (iPlaylist != PLAYLIST_MUSIC && iPlaylist != PLAYLIST_VIDEO)
586 CPlayList& list = GetPlaylist(iPlaylist);
587 int iSize = list.size();
589 if (list.IsShuffled())
590 ReShuffle(iPlaylist, iSize);
593 void CPlayListPlayer::Add(int iPlaylist, CFileItemList& items)
595 if (iPlaylist != PLAYLIST_MUSIC && iPlaylist != PLAYLIST_VIDEO)
597 CPlayList& list = GetPlaylist(iPlaylist);
598 int iSize = list.size();
600 if (list.IsShuffled())
601 ReShuffle(iPlaylist, iSize);
604 void CPlayListPlayer::Insert(int iPlaylist, CPlayList& playlist, int iIndex)
606 if (iPlaylist != PLAYLIST_MUSIC && iPlaylist != PLAYLIST_VIDEO)
608 CPlayList& list = GetPlaylist(iPlaylist);
609 int iSize = list.size();
610 list.Insert(playlist, iIndex);
611 if (list.IsShuffled())
612 ReShuffle(iPlaylist, iSize);
613 else if (m_iCurrentPlayList == iPlaylist && m_iCurrentSong >= iIndex)
617 void CPlayListPlayer::Insert(int iPlaylist, const CFileItemPtr &pItem, int iIndex)
619 if (iPlaylist != PLAYLIST_MUSIC && iPlaylist != PLAYLIST_VIDEO)
621 CPlayList& list = GetPlaylist(iPlaylist);
622 int iSize = list.size();
623 list.Insert(pItem, iIndex);
624 if (list.IsShuffled())
625 ReShuffle(iPlaylist, iSize);
626 else if (m_iCurrentPlayList == iPlaylist && m_iCurrentSong >= iIndex)
630 void CPlayListPlayer::Insert(int iPlaylist, CFileItemList& items, int iIndex)
632 if (iPlaylist != PLAYLIST_MUSIC && iPlaylist != PLAYLIST_VIDEO)
634 CPlayList& list = GetPlaylist(iPlaylist);
635 int iSize = list.size();
636 list.Insert(items, iIndex);
637 if (list.IsShuffled())
638 ReShuffle(iPlaylist, iSize);
639 else if (m_iCurrentPlayList == iPlaylist && m_iCurrentSong >= iIndex)
643 void CPlayListPlayer::Remove(int iPlaylist, int iPosition)
645 if (iPlaylist != PLAYLIST_MUSIC && iPlaylist != PLAYLIST_VIDEO)
647 CPlayList& list = GetPlaylist(iPlaylist);
648 list.Remove(iPosition);
649 if (m_iCurrentPlayList == iPlaylist && m_iCurrentSong >= iPosition)
652 // its likely that the playlist changed
653 CGUIMessage msg(GUI_MSG_PLAYLIST_CHANGED, 0, 0);
654 g_windowManager.SendMessage(msg);
657 void CPlayListPlayer::Clear()
660 m_PlaylistMusic->Clear();
662 m_PlaylistVideo->Clear();
664 m_PlaylistEmpty->Clear();
667 void CPlayListPlayer::Swap(int iPlaylist, int indexItem1, int indexItem2)
669 if (iPlaylist != PLAYLIST_MUSIC && iPlaylist != PLAYLIST_VIDEO)
672 CPlayList& list = GetPlaylist(iPlaylist);
673 if (list.Swap(indexItem1, indexItem2) && iPlaylist == m_iCurrentPlayList)
675 if (m_iCurrentSong == indexItem1)
676 m_iCurrentSong = indexItem2;
677 else if (m_iCurrentSong == indexItem2)
678 m_iCurrentSong = indexItem1;
681 // its likely that the playlist changed
682 CGUIMessage msg(GUI_MSG_PLAYLIST_CHANGED, 0, 0);
683 g_windowManager.SendMessage(msg);
686 void CPlayListPlayer::AnnouncePropertyChanged(int iPlaylist, const std::string &strProperty, const CVariant &value)
688 if (strProperty.empty() || value.isNull() ||
689 (iPlaylist == PLAYLIST_VIDEO && !g_application.IsPlayingVideo()) ||
690 (iPlaylist == PLAYLIST_MUSIC && !g_application.IsPlayingAudio()))
694 data["player"]["playerid"] = iPlaylist;
695 data["property"][strProperty] = value;
696 ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::Player, "xbmc", "OnPropertyChanged", data);