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 if (!g_application.PlayFile(*item, bAutoPlay))
263 CLog::Log(LOGERROR,"Playlist Player: skipping unplayable item: %i, path [%s]", m_iCurrentSong, item->GetPath().c_str());
264 playlist.SetUnPlayable(m_iCurrentSong);
266 // abort on 100 failed CONSECTUTIVE songs
268 m_failedSongsStart = playAttempt;
270 if ((m_iFailedSongs >= g_advancedSettings.m_playlistRetries && g_advancedSettings.m_playlistRetries >= 0)
271 || ((XbmcThreads::SystemClockMillis() - m_failedSongsStart >= (unsigned int)g_advancedSettings.m_playlistTimeout * 1000) && g_advancedSettings.m_playlistTimeout))
273 CLog::Log(LOGDEBUG,"Playlist Player: one or more items failed to play... aborting playback");
276 CGUIDialogOK::ShowAndGetInput(16026, 16027, 16029, 0);
278 CGUIMessage msg(GUI_MSG_PLAYLISTPLAYER_STOPPED, 0, 0, m_iCurrentPlayList, m_iCurrentSong);
279 g_windowManager.SendThreadMessage(msg);
281 GetPlaylist(m_iCurrentPlayList).Clear();
282 m_iCurrentPlayList = PLAYLIST_NONE;
284 m_failedSongsStart = 0;
288 // how many playable items are in the playlist?
289 if (playlist.GetPlayable() > 0)
291 return bPlayPrevious ? PlayPrevious() : PlayNext();
293 // none? then abort playback
296 CLog::Log(LOGDEBUG,"Playlist Player: no more playable items... aborting playback");
297 CGUIMessage msg(GUI_MSG_PLAYLISTPLAYER_STOPPED, 0, 0, m_iCurrentPlayList, m_iCurrentSong);
298 g_windowManager.SendThreadMessage(msg);
300 m_iCurrentPlayList = PLAYLIST_NONE;
305 // reset the start offset of this item
306 if (item->m_lStartOffset == STARTOFFSET_RESUME)
307 item->m_lStartOffset = 0;
309 // TODO - move the above failure logic and the below success logic
310 // to callbacks instead so we don't rely on the return value
313 // consecutive error counter so reset if the current item is playing
315 m_failedSongsStart = 0;
316 m_bPlaybackStarted = true;
317 m_bPlayedFirstFile = true;
321 void CPlayListPlayer::SetCurrentSong(int iSong)
323 if (iSong >= -1 && iSong < GetPlaylist(m_iCurrentPlayList).size())
324 m_iCurrentSong = iSong;
327 int CPlayListPlayer::GetCurrentSong() const
329 return m_iCurrentSong;
332 int CPlayListPlayer::GetCurrentPlaylist() const
334 return m_iCurrentPlayList;
337 void CPlayListPlayer::SetCurrentPlaylist(int iPlaylist)
339 if (iPlaylist == m_iCurrentPlayList)
342 // changing the current playlist while party mode is on
343 // disables party mode
344 if (g_partyModeManager.IsEnabled())
345 g_partyModeManager.Disable();
347 m_iCurrentPlayList = iPlaylist;
348 m_bPlayedFirstFile = false;
351 void CPlayListPlayer::ClearPlaylist(int iPlaylist)
353 // clear our applications playlist file
354 g_application.m_strPlayListFile.Empty();
356 CPlayList& playlist = GetPlaylist(iPlaylist);
359 // its likely that the playlist changed
360 CGUIMessage msg(GUI_MSG_PLAYLIST_CHANGED, 0, 0);
361 g_windowManager.SendMessage(msg);
364 CPlayList& CPlayListPlayer::GetPlaylist(int iPlaylist)
369 return *m_PlaylistMusic;
372 return *m_PlaylistVideo;
375 m_PlaylistEmpty->Clear();
376 return *m_PlaylistEmpty;
381 const CPlayList& CPlayListPlayer::GetPlaylist(int iPlaylist) const
386 return *m_PlaylistMusic;
389 return *m_PlaylistVideo;
392 // NOTE: This playlist may not be empty if the caller of the non-const version alters it!
393 return *m_PlaylistEmpty;
398 int CPlayListPlayer::RemoveDVDItems()
400 int nRemovedM = m_PlaylistMusic->RemoveDVDItems();
401 int nRemovedV = m_PlaylistVideo->RemoveDVDItems();
403 return nRemovedM + nRemovedV;
406 void CPlayListPlayer::Reset()
409 m_bPlayedFirstFile = false;
410 m_bPlaybackStarted = false;
412 // its likely that the playlist changed
413 CGUIMessage msg(GUI_MSG_PLAYLIST_CHANGED, 0, 0);
414 g_windowManager.SendMessage(msg);
417 bool CPlayListPlayer::HasPlayedFirstFile() const
419 return m_bPlayedFirstFile;
422 bool CPlayListPlayer::Repeated(int iPlaylist) const
424 if (iPlaylist >= PLAYLIST_MUSIC && iPlaylist <= PLAYLIST_VIDEO)
425 return (m_repeatState[iPlaylist] == REPEAT_ALL);
429 bool CPlayListPlayer::RepeatedOne(int iPlaylist) const
431 if (iPlaylist == PLAYLIST_MUSIC || iPlaylist == PLAYLIST_VIDEO)
432 return (m_repeatState[iPlaylist] == REPEAT_ONE);
436 void CPlayListPlayer::SetShuffle(int iPlaylist, bool bYesNo, bool bNotify /* = false */)
438 if (iPlaylist != PLAYLIST_MUSIC && iPlaylist != PLAYLIST_VIDEO)
441 // disable shuffle in party mode
442 if (g_partyModeManager.IsEnabled() && iPlaylist == PLAYLIST_MUSIC)
445 // do we even need to do anything?
446 if (bYesNo != IsShuffled(iPlaylist))
448 // save the order value of the current song so we can use it find its new location later
450 CPlayList &playlist = GetPlaylist(iPlaylist);
451 if (m_iCurrentSong >= 0 && m_iCurrentSong < playlist.size())
452 iOrder = playlist[m_iCurrentSong]->m_iprogramCount;
454 // shuffle or unshuffle as necessary
458 playlist.UnShuffle();
462 CStdString shuffleStr;
463 shuffleStr.Format("%s: %s", g_localizeStrings.Get(191), g_localizeStrings.Get(bYesNo ? 593 : 591)); // Shuffle: All/Off
464 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(559), shuffleStr);
467 // find the previous order value and fix the current song marker
470 int iIndex = playlist.FindOrder(iOrder);
472 m_iCurrentSong = iIndex;
473 // if iIndex < 0, something unexpected happened
474 // so dont do anything
478 AnnouncePropertyChanged(iPlaylist, "shuffled", IsShuffled(iPlaylist));
481 bool CPlayListPlayer::IsShuffled(int iPlaylist) const
483 // even if shuffled, party mode says its not
484 if (g_partyModeManager.IsEnabled() && iPlaylist == PLAYLIST_MUSIC)
487 if (iPlaylist == PLAYLIST_MUSIC || iPlaylist == PLAYLIST_VIDEO)
488 return GetPlaylist(iPlaylist).IsShuffled();
493 void CPlayListPlayer::SetRepeat(int iPlaylist, REPEAT_STATE state, bool bNotify /* = false */)
495 if (iPlaylist != PLAYLIST_MUSIC && iPlaylist != PLAYLIST_VIDEO)
498 // disable repeat in party mode
499 if (g_partyModeManager.IsEnabled() && iPlaylist == PLAYLIST_MUSIC)
502 // notify the user if there was a change in the repeat state
503 if (m_repeatState[iPlaylist] != state && bNotify)
505 int iLocalizedString;
506 if (state == REPEAT_NONE)
507 iLocalizedString = 595; // Repeat: Off
508 else if (state == REPEAT_ONE)
509 iLocalizedString = 596; // Repeat: One
511 iLocalizedString = 597; // Repeat: All
512 CGUIDialogKaiToast::QueueNotification(CGUIDialogKaiToast::Info, g_localizeStrings.Get(559), g_localizeStrings.Get(iLocalizedString));
515 m_repeatState[iPlaylist] = state;
530 AnnouncePropertyChanged(iPlaylist, "repeat", data);
533 REPEAT_STATE CPlayListPlayer::GetRepeat(int iPlaylist) const
535 if (iPlaylist == PLAYLIST_MUSIC || iPlaylist == PLAYLIST_VIDEO)
536 return m_repeatState[iPlaylist];
540 void CPlayListPlayer::ReShuffle(int iPlaylist, int iPosition)
542 // playlist has not played yet so shuffle the entire list
543 // (this only really works for new video playlists)
544 if (!GetPlaylist(iPlaylist).WasPlayed())
546 GetPlaylist(iPlaylist).Shuffle();
548 // we're trying to shuffle new items into the curently playing playlist
549 // so we shuffle starting at two positions below the current item
550 else if (iPlaylist == m_iCurrentPlayList)
553 (g_application.IsPlayingAudio() && iPlaylist == PLAYLIST_MUSIC) ||
554 (g_application.IsPlayingVideo() && iPlaylist == PLAYLIST_VIDEO)
557 g_playlistPlayer.GetPlaylist(iPlaylist).Shuffle(m_iCurrentSong + 2);
560 // otherwise, shuffle from the passed position
561 // which is the position of the first new item added
564 g_playlistPlayer.GetPlaylist(iPlaylist).Shuffle(iPosition);
568 void CPlayListPlayer::Add(int iPlaylist, CPlayList& playlist)
570 if (iPlaylist != PLAYLIST_MUSIC && iPlaylist != PLAYLIST_VIDEO)
572 CPlayList& list = GetPlaylist(iPlaylist);
573 int iSize = list.size();
575 if (list.IsShuffled())
576 ReShuffle(iPlaylist, iSize);
579 void CPlayListPlayer::Add(int iPlaylist, const CFileItemPtr &pItem)
581 if (iPlaylist != PLAYLIST_MUSIC && iPlaylist != PLAYLIST_VIDEO)
583 CPlayList& list = GetPlaylist(iPlaylist);
584 int iSize = list.size();
586 if (list.IsShuffled())
587 ReShuffle(iPlaylist, iSize);
590 void CPlayListPlayer::Add(int iPlaylist, CFileItemList& items)
592 if (iPlaylist != PLAYLIST_MUSIC && iPlaylist != PLAYLIST_VIDEO)
594 CPlayList& list = GetPlaylist(iPlaylist);
595 int iSize = list.size();
597 if (list.IsShuffled())
598 ReShuffle(iPlaylist, iSize);
601 void CPlayListPlayer::Insert(int iPlaylist, CPlayList& playlist, int iIndex)
603 if (iPlaylist != PLAYLIST_MUSIC && iPlaylist != PLAYLIST_VIDEO)
605 CPlayList& list = GetPlaylist(iPlaylist);
606 int iSize = list.size();
607 list.Insert(playlist, iIndex);
608 if (list.IsShuffled())
609 ReShuffle(iPlaylist, iSize);
610 else if (m_iCurrentPlayList == iPlaylist && m_iCurrentSong >= iIndex)
614 void CPlayListPlayer::Insert(int iPlaylist, const CFileItemPtr &pItem, int iIndex)
616 if (iPlaylist != PLAYLIST_MUSIC && iPlaylist != PLAYLIST_VIDEO)
618 CPlayList& list = GetPlaylist(iPlaylist);
619 int iSize = list.size();
620 list.Insert(pItem, iIndex);
621 if (list.IsShuffled())
622 ReShuffle(iPlaylist, iSize);
623 else if (m_iCurrentPlayList == iPlaylist && m_iCurrentSong >= iIndex)
627 void CPlayListPlayer::Insert(int iPlaylist, CFileItemList& items, int iIndex)
629 if (iPlaylist != PLAYLIST_MUSIC && iPlaylist != PLAYLIST_VIDEO)
631 CPlayList& list = GetPlaylist(iPlaylist);
632 int iSize = list.size();
633 list.Insert(items, iIndex);
634 if (list.IsShuffled())
635 ReShuffle(iPlaylist, iSize);
636 else if (m_iCurrentPlayList == iPlaylist && m_iCurrentSong >= iIndex)
640 void CPlayListPlayer::Remove(int iPlaylist, int iPosition)
642 if (iPlaylist != PLAYLIST_MUSIC && iPlaylist != PLAYLIST_VIDEO)
644 CPlayList& list = GetPlaylist(iPlaylist);
645 list.Remove(iPosition);
646 if (m_iCurrentPlayList == iPlaylist && m_iCurrentSong >= iPosition)
649 // its likely that the playlist changed
650 CGUIMessage msg(GUI_MSG_PLAYLIST_CHANGED, 0, 0);
651 g_windowManager.SendMessage(msg);
654 void CPlayListPlayer::Clear()
657 m_PlaylistMusic->Clear();
659 m_PlaylistVideo->Clear();
661 m_PlaylistEmpty->Clear();
664 void CPlayListPlayer::Swap(int iPlaylist, int indexItem1, int indexItem2)
666 if (iPlaylist != PLAYLIST_MUSIC && iPlaylist != PLAYLIST_VIDEO)
669 CPlayList& list = GetPlaylist(iPlaylist);
670 if (list.Swap(indexItem1, indexItem2) && iPlaylist == m_iCurrentPlayList)
672 if (m_iCurrentSong == indexItem1)
673 m_iCurrentSong = indexItem2;
674 else if (m_iCurrentSong == indexItem2)
675 m_iCurrentSong = indexItem1;
678 // its likely that the playlist changed
679 CGUIMessage msg(GUI_MSG_PLAYLIST_CHANGED, 0, 0);
680 g_windowManager.SendMessage(msg);
683 void CPlayListPlayer::AnnouncePropertyChanged(int iPlaylist, const std::string &strProperty, const CVariant &value)
685 if (strProperty.empty() || value.isNull() ||
686 (iPlaylist == PLAYLIST_VIDEO && !g_application.IsPlayingVideo()) ||
687 (iPlaylist == PLAYLIST_MUSIC && !g_application.IsPlayingAudio()))
691 data["player"]["playerid"] = iPlaylist;
692 data["property"][strProperty] = value;
693 ANNOUNCEMENT::CAnnouncementManager::Announce(ANNOUNCEMENT::Player, "xbmc", "OnPropertyChanged", data);