merged: Linuxport revisions through to r15292.
[xbmc:xbmc-antiquated.git] / xbmc / PlayListPlayer.cpp
1 /*
2  *      Copyright (C) 2005-2008 Team XBMC
3  *      http://www.xbmc.org
4  *
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)
8  *  any later version.
9  *
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.
14  *
15  *  You should have received a copy of the GNU General Public License
16  *  along with XBMC; see the file COPYING.  If not, write to
17  *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA.
18  *  http://www.gnu.org/copyleft/gpl.html
19  *
20  */
21
22 #include "stdafx.h"
23 #include "PlayListPlayer.h"
24 #include "PlayListFactory.h"
25 #include "Application.h"
26 #include "PartyModeManager.h"
27 #include "Settings.h"
28 #include "GUIWindowManager.h"
29 #include "GUIDialogOK.h"
30 #include "PlayList.h"
31
32 using namespace PLAYLIST;
33
34 CPlayListPlayer g_playlistPlayer;
35
36 CPlayListPlayer::CPlayListPlayer(void)
37 {
38   m_PlaylistMusic = new CPlayList;
39   m_PlaylistVideo = new CPlayList;
40   m_PlaylistEmpty = new CPlayList;
41   m_iCurrentSong = -1;
42   m_bPlayedFirstFile = false;
43   m_bPlaybackStarted = false;
44   m_iCurrentPlayList = PLAYLIST_NONE;
45   for (int i = 0; i < 2; i++)
46     m_repeatState[i] = REPEAT_NONE;
47   m_iFailedSongs = 0;
48   m_failedSongsStart = 0;
49 }
50
51 CPlayListPlayer::~CPlayListPlayer(void)
52 {
53   Clear();
54   delete m_PlaylistMusic;
55   delete m_PlaylistVideo;
56   delete m_PlaylistEmpty;
57 }
58
59 bool CPlayListPlayer::OnMessage(CGUIMessage &message)
60 {
61   switch (message.GetMessage())
62   {
63   case GUI_MSG_NOTIFY_ALL:
64     if (message.GetParam1() == GUI_MSG_UPDATE_ITEM && message.GetLPVOID())
65     {
66       // update our item if necessary
67       CPlayList &playlist = GetPlaylist(m_iCurrentPlayList);
68       CFileItemPtr item = boost::static_pointer_cast<CFileItem>(message.GetItem());
69       playlist.UpdateItem(item.get());
70     }
71     break;
72   case GUI_MSG_PLAYBACK_STOPPED:
73     {
74       if (m_iCurrentPlayList != PLAYLIST_NONE && m_bPlaybackStarted)
75       {
76         CGUIMessage msg(GUI_MSG_PLAYLISTPLAYER_STOPPED, 0, 0, m_iCurrentPlayList, m_iCurrentSong);
77         m_gWindowManager.SendThreadMessage(msg);
78         Reset();
79         m_iCurrentPlayList = PLAYLIST_NONE;
80         return true;
81       }
82     }
83     break;
84   }
85
86   return false;
87 }
88
89 int CPlayListPlayer::GetNextSong(int offset) const
90 {
91   if (m_iCurrentPlayList == PLAYLIST_NONE)
92     return -1;
93
94   const CPlayList& playlist = GetPlaylist(m_iCurrentPlayList);
95   if (playlist.size() <= 0)
96     return -1;
97
98   int song = m_iCurrentSong;
99
100   // party mode
101   if (g_partyModeManager.IsEnabled() && GetCurrentPlaylist() == PLAYLIST_MUSIC)
102     return song + offset;
103
104   // wrap around in the case of repeating
105   if (RepeatedOne(m_iCurrentPlayList))
106     return song;
107
108   song += offset;
109   if (song >= playlist.size() && Repeated(m_iCurrentPlayList))
110     song %= playlist.size();
111
112   return song;
113 }
114
115 int CPlayListPlayer::GetNextSong()
116 {
117   if (m_iCurrentPlayList == PLAYLIST_NONE)
118     return -1;
119   CPlayList& playlist = GetPlaylist(m_iCurrentPlayList);
120   if (playlist.size() <= 0)
121     return -1;
122   int iSong = m_iCurrentSong;
123
124   // party mode
125   if (g_partyModeManager.IsEnabled() && GetCurrentPlaylist() == PLAYLIST_MUSIC)
126     return iSong + 1;
127
128   // if repeat one, keep playing the current song if its valid
129   if (RepeatedOne(m_iCurrentPlayList))
130   {
131     // otherwise immediately abort playback
132     if (m_iCurrentSong >= 0 && m_iCurrentSong < playlist.size() && playlist[m_iCurrentSong]->GetPropertyBOOL("unplayable"))
133     {
134       CLog::Log(LOGERROR,"Playlist Player: RepeatOne stuck on unplayable item: %i, path [%s]", m_iCurrentSong, playlist[m_iCurrentSong]->m_strPath.c_str());
135       CGUIMessage msg(GUI_MSG_PLAYLISTPLAYER_STOPPED, 0, 0, m_iCurrentPlayList, m_iCurrentSong);
136       m_gWindowManager.SendThreadMessage(msg);
137       Reset();
138       m_iCurrentPlayList = PLAYLIST_NONE;
139       return -1;
140     }
141     return iSong;
142   }
143
144   // if we've gone beyond the playlist and repeat all is enabled,
145   // then we clear played status and wrap around
146   iSong++;
147   if (iSong >= playlist.size() && Repeated(m_iCurrentPlayList))
148     iSong = 0;
149
150   return iSong;
151 }
152
153 /// \brief Play next entry in current playlist
154 void CPlayListPlayer::PlayNext(bool bAutoPlay)
155 {
156   int iSong = GetNextSong();
157   CPlayList& playlist = GetPlaylist(m_iCurrentPlayList);
158
159   // stop playing
160   if ((iSong < 0) || (iSong >= playlist.size()) || (playlist.GetPlayable() <= 0))
161   {
162     CGUIMessage msg(GUI_MSG_PLAYLISTPLAYER_STOPPED, 0, 0, m_iCurrentPlayList, m_iCurrentSong);
163     m_gWindowManager.SendThreadMessage(msg);
164     Reset();
165     m_iCurrentPlayList = PLAYLIST_NONE;
166     return;
167   }
168
169   if (bAutoPlay)
170   {
171     CFileItemPtr item = playlist[iSong];
172     if ( item->IsShoutCast() )
173     {
174       return ;
175     }
176   }
177   Play(iSong, bAutoPlay);
178   if (iSong < playlist.size() && playlist[iSong]->IsAudio() && m_gWindowManager.GetActiveWindow() == WINDOW_FULLSCREEN_VIDEO)
179     m_gWindowManager.ActivateWindow(WINDOW_VISUALISATION);
180   //g_partyModeManager.OnSongChange();
181 }
182
183 /// \brief Play previous entry in current playlist
184 void CPlayListPlayer::PlayPrevious()
185 {
186   if (m_iCurrentPlayList == PLAYLIST_NONE)
187     return ;
188
189   CPlayList& playlist = GetPlaylist(m_iCurrentPlayList);
190   if (playlist.size() <= 0) return ;
191   int iSong = m_iCurrentSong;
192
193   if (!RepeatedOne(m_iCurrentPlayList))
194     iSong--;
195
196   if (iSong < 0)
197     iSong = playlist.size() - 1;
198
199   Play(iSong, false, true);
200 }
201
202 void CPlayListPlayer::Play()
203 {
204   if (m_iCurrentPlayList == PLAYLIST_NONE)
205     return;
206
207   CPlayList& playlist = GetPlaylist(m_iCurrentPlayList);
208   if (playlist.size() <= 0) return;
209
210   Play(0);
211 }
212
213 /// \brief Start playing entry \e iSong in current playlist
214 /// \param iSong Song in playlist
215 void CPlayListPlayer::Play(int iSong, bool bAutoPlay /* = false */, bool bPlayPrevious /* = false */)
216 {
217   if (m_iCurrentPlayList == PLAYLIST_NONE)
218     return ;
219
220   CPlayList& playlist = GetPlaylist(m_iCurrentPlayList);
221   if (playlist.size() <= 0) return ;
222   if (iSong < 0) iSong = 0;
223   if (iSong >= playlist.size()) iSong = playlist.size() - 1;
224
225
226   // check if the item itself is a playlist, and can be expanded
227   // only allow a few levels, this could end up in a loop
228   // if they refer to each other in a loop
229   for(int i=0;i<5;i++)
230   {
231     if(!playlist.Expand(iSong))
232       break;
233   }
234
235   int iPreviousSong = m_iCurrentSong;
236   m_iCurrentSong = iSong;
237   CFileItemPtr item = playlist[m_iCurrentSong];
238   playlist.SetPlayed(true);
239
240   m_bPlaybackStarted = false;
241
242   DWORD playAttempt = timeGetTime();
243   if (!g_application.PlayFile(*item, bAutoPlay))
244   {
245     CLog::Log(LOGERROR,"Playlist Player: skipping unplayable item: %i, path [%s]", m_iCurrentSong, item->m_strPath.c_str());
246     playlist.SetUnPlayable(m_iCurrentSong);
247
248     // abort on 100 failed CONSECTUTIVE songs
249     if (!m_iFailedSongs)
250       m_failedSongsStart = playAttempt;
251     m_iFailedSongs++;
252     if ((m_iFailedSongs >= g_advancedSettings.m_playlistRetries && g_advancedSettings.m_playlistRetries >= 0)
253         || (timeGetTime() - m_failedSongsStart  >= (unsigned int)g_advancedSettings.m_playlistTimeout * 1000) && g_advancedSettings.m_playlistTimeout)
254     {
255       CLog::Log(LOGDEBUG,"Playlist Player: too many consecutive failures... aborting playback");
256
257       // open error dialog
258       CGUIDialogOK::ShowAndGetInput(257, 16026, 16027, 0);
259
260       CGUIMessage msg(GUI_MSG_PLAYLISTPLAYER_STOPPED, 0, 0, m_iCurrentPlayList, m_iCurrentSong);
261       m_gWindowManager.SendThreadMessage(msg);
262       Reset();
263       GetPlaylist(m_iCurrentPlayList).Clear();
264       m_iCurrentPlayList = PLAYLIST_NONE;
265       m_iFailedSongs = 0;
266       m_failedSongsStart = 0;
267       return;
268     }
269
270     // how many playable items are in the playlist?
271     if (playlist.GetPlayable() > 0)
272     {
273       if (bPlayPrevious)
274         PlayPrevious();
275       else
276         PlayNext();
277       return;
278     }
279     // none? then abort playback
280     else
281     {
282       CLog::Log(LOGDEBUG,"Playlist Player: no more playable items... aborting playback");
283       CGUIMessage msg(GUI_MSG_PLAYLISTPLAYER_STOPPED, 0, 0, m_iCurrentPlayList, m_iCurrentSong);
284       m_gWindowManager.SendThreadMessage(msg);
285       Reset();
286       m_iCurrentPlayList = PLAYLIST_NONE;
287       return;
288     }
289   }
290
291   // TODO - move the above failure logic and the below success logic
292   //        to callbacks instead so we don't rely on the return value
293   //        of PlayFile()
294
295   // consecutive error counter so reset if the current item is playing
296   m_iFailedSongs = 0;
297   m_failedSongsStart = 0;
298   m_bPlaybackStarted = true;
299   m_bPlayedFirstFile = true;
300
301   if (!item->IsShoutCast())
302   {
303     if (iPreviousSong < 0)
304     {
305       CGUIMessage msg(GUI_MSG_PLAYLISTPLAYER_STARTED, 0, 0, m_iCurrentPlayList, m_iCurrentSong, item);
306       m_gWindowManager.SendThreadMessage( msg );
307     }
308     else
309     {
310       CGUIMessage msg(GUI_MSG_PLAYLISTPLAYER_CHANGED, 0, 0, m_iCurrentPlayList, MAKELONG(m_iCurrentSong, iPreviousSong), item);
311       m_gWindowManager.SendThreadMessage(msg);
312     }
313   }
314 }
315
316 /// \brief Change the current song in playlistplayer.
317 /// \param iSong Song in playlist
318 void CPlayListPlayer::SetCurrentSong(int iSong)
319 {
320   if (iSong >= -1 && iSong < GetPlaylist(m_iCurrentPlayList).size())
321     m_iCurrentSong = iSong;
322 }
323
324 /// \brief Returns to current song in active playlist.
325 /// \return Current song
326 int CPlayListPlayer::GetCurrentSong() const
327 {
328   return m_iCurrentSong;
329 }
330
331 /// \brief Returns the active playlist.
332 /// \return Active playlist \n
333 /// Return values can be: \n
334 /// - PLAYLIST_NONE \n No playlist active
335 /// - PLAYLIST_MUSIC \n Playlist from music playlist window
336 /// - PLAYLIST_VIDEO \n Playlist from music playlist window
337 int CPlayListPlayer::GetCurrentPlaylist() const
338 {
339   return m_iCurrentPlayList;
340 }
341
342 /// \brief Set active playlist.
343 /// \param iPlayList Playlist to set active \n
344 /// Values can be: \n
345 /// - PLAYLIST_NONE \n No playlist active
346 /// - PLAYLIST_MUSIC \n Playlist from music playlist window
347 /// - PLAYLIST_VIDEO \n Playlist from music playlist window
348 void CPlayListPlayer::SetCurrentPlaylist(int iPlaylist)
349 {
350   if (iPlaylist == m_iCurrentPlayList)
351     return;
352
353   // changing the current playlist while party mode is on
354   // disables party mode
355   if (g_partyModeManager.IsEnabled())
356     g_partyModeManager.Disable();
357
358   m_iCurrentPlayList = iPlaylist;
359   m_bPlayedFirstFile = false;
360 }
361
362 void CPlayListPlayer::ClearPlaylist(int iPlaylist)
363 {
364   // clear our applications playlist file
365   g_application.m_strPlayListFile.Empty();
366
367   CPlayList& playlist = GetPlaylist(iPlaylist);
368   playlist.Clear();
369
370   // its likely that the playlist changed
371   CGUIMessage msg(GUI_MSG_PLAYLIST_CHANGED, 0, 0);
372   m_gWindowManager.SendMessage(msg);
373 }
374
375 /// \brief Get the playlist object specified in \e nPlayList
376 /// \param nPlayList Values can be: \n
377 /// - PLAYLIST_MUSIC \n Playlist from music playlist window
378 /// - PLAYLIST_VIDEO \n Playlist from music playlist window
379 /// \return A reference to the CPlayList object.
380 CPlayList& CPlayListPlayer::GetPlaylist(int iPlaylist)
381 {
382   switch ( iPlaylist )
383   {
384   case PLAYLIST_MUSIC:
385     return *m_PlaylistMusic;
386     break;
387   case PLAYLIST_VIDEO:
388     return *m_PlaylistVideo;
389     break;
390   default:
391     m_PlaylistEmpty->Clear();
392     return *m_PlaylistEmpty;
393     break;
394   }
395 }
396
397 const CPlayList& CPlayListPlayer::GetPlaylist(int iPlaylist) const
398 {
399   switch ( iPlaylist )
400   {
401   case PLAYLIST_MUSIC:
402     return *m_PlaylistMusic;
403     break;
404   case PLAYLIST_VIDEO:
405     return *m_PlaylistVideo;
406     break;
407   default:
408     // NOTE: This playlist may not be empty if the caller of the non-const version alters it!
409     return *m_PlaylistEmpty;
410     break;
411   }
412 }
413
414 /// \brief Removes any item from all playlists located on a removable share
415 /// \return Number of items removed from PLAYLIST_MUSIC and PLAYLIST_VIDEO
416 int CPlayListPlayer::RemoveDVDItems()
417 {
418   int nRemovedM = m_PlaylistMusic->RemoveDVDItems();
419   int nRemovedV = m_PlaylistVideo->RemoveDVDItems();
420
421   return nRemovedM + nRemovedV;
422 }
423
424 /// \brief Resets the playlistplayer, but the active playlist stays the same.
425 void CPlayListPlayer::Reset()
426 {
427   m_iCurrentSong = -1;
428   m_bPlayedFirstFile = false;
429   m_bPlaybackStarted = false;
430
431   // its likely that the playlist changed
432   CGUIMessage msg(GUI_MSG_PLAYLIST_CHANGED, 0, 0);
433   m_gWindowManager.SendMessage(msg);
434 }
435
436 /// \brief Whether or not something has been played yet or not from the current playlist.
437 bool CPlayListPlayer::HasPlayedFirstFile() const
438 {
439   return m_bPlayedFirstFile;
440 }
441
442 /// \brief Returns \e true if iPlaylist is repeated
443 /// \param iPlaylist Playlist to be asked
444 bool CPlayListPlayer::Repeated(int iPlaylist) const
445 {
446   if (iPlaylist >= PLAYLIST_MUSIC && iPlaylist <= PLAYLIST_VIDEO)
447     return (m_repeatState[iPlaylist] == REPEAT_ALL);
448   return false;
449 }
450
451 /// \brief Returns \e true if iPlaylist repeats one song
452 /// \param iPlaylist Playlist to be asked
453 bool CPlayListPlayer::RepeatedOne(int iPlaylist) const
454 {
455   if (iPlaylist >= PLAYLIST_MUSIC && iPlaylist <= PLAYLIST_VIDEO)
456     return (m_repeatState[iPlaylist] == REPEAT_ONE);
457   return false;
458 }
459
460 /// \brief Shuffle play the current playlist
461 /// \param bYesNo To Enable shuffle play, set to \e true
462 void CPlayListPlayer::SetShuffle(int iPlaylist, bool bYesNo)
463 {
464   if (iPlaylist < PLAYLIST_MUSIC || iPlaylist > PLAYLIST_VIDEO)
465     return;
466
467   // disable shuffle in party mode
468   if (g_partyModeManager.IsEnabled() && iPlaylist == PLAYLIST_MUSIC)
469     return;
470
471   // do we even need to do anything?
472   if (bYesNo != IsShuffled(iPlaylist))
473   {
474     // save the order value of the current song so we can use it find its new location later
475     int iOrder = -1;
476     CPlayList &playlist = GetPlaylist(iPlaylist);
477     if (m_iCurrentSong >= 0 && m_iCurrentSong < playlist.size())
478       iOrder = playlist[m_iCurrentSong]->m_iprogramCount;
479
480     // shuffle or unshuffle as necessary
481     if (bYesNo)
482       playlist.Shuffle();
483     else
484       playlist.UnShuffle();
485
486     // find the previous order value and fix the current song marker
487     if (iOrder >= 0)
488     {
489       int iIndex = playlist.FindOrder(iOrder);
490       if (iIndex >= 0)
491         m_iCurrentSong = iIndex;
492       // if iIndex < 0, something unexpected happened
493       // so dont do anything
494     }
495   }
496 }
497
498 bool CPlayListPlayer::IsShuffled(int iPlaylist) const
499 {
500   // even if shuffled, party mode says its not
501   if (g_partyModeManager.IsEnabled() && iPlaylist == PLAYLIST_MUSIC)
502     return false;
503
504   if (iPlaylist >= PLAYLIST_MUSIC && iPlaylist <= PLAYLIST_VIDEO)
505   {
506     return GetPlaylist(iPlaylist).IsShuffled();
507   }
508   return false;
509 }
510
511 void CPlayListPlayer::SetRepeat(int iPlaylist, REPEAT_STATE state)
512 {
513   if (iPlaylist < PLAYLIST_MUSIC || iPlaylist > PLAYLIST_VIDEO)
514     return;
515
516   // disable repeat in party mode
517   if (g_partyModeManager.IsEnabled() && iPlaylist == PLAYLIST_MUSIC)
518     state = REPEAT_NONE;
519
520   m_repeatState[iPlaylist] = state;
521 }
522
523 REPEAT_STATE CPlayListPlayer::GetRepeat(int iPlaylist) const
524 {
525   if (iPlaylist >= PLAYLIST_MUSIC && iPlaylist <= PLAYLIST_VIDEO)
526     return m_repeatState[iPlaylist];
527   return REPEAT_NONE;
528 }
529
530 void CPlayListPlayer::ReShuffle(int iPlaylist, int iPosition)
531 {
532   // playlist has not played yet so shuffle the entire list
533   // (this only really works for new video playlists)
534   if (!GetPlaylist(iPlaylist).WasPlayed())
535   {
536     GetPlaylist(iPlaylist).Shuffle();
537   }
538   // we're trying to shuffle new items into the curently playing playlist
539   // so we shuffle starting at two positions below the current item
540   else if (iPlaylist == m_iCurrentPlayList)
541   {
542     if (
543       (g_application.IsPlayingAudio() && iPlaylist == PLAYLIST_MUSIC) ||
544       (g_application.IsPlayingVideo() && iPlaylist == PLAYLIST_VIDEO)
545       )
546     {
547             g_playlistPlayer.GetPlaylist(iPlaylist).Shuffle(m_iCurrentSong + 2);
548     }
549   }
550   // otherwise, shuffle from the passed position
551   // which is the position of the first new item added
552   else
553   {
554     g_playlistPlayer.GetPlaylist(iPlaylist).Shuffle(iPosition);
555   }
556 }
557
558 void CPlayListPlayer::Add(int iPlaylist, CPlayList& playlist)
559 {
560   if (iPlaylist < PLAYLIST_MUSIC || iPlaylist > PLAYLIST_VIDEO)
561     return;
562   CPlayList& list = GetPlaylist(iPlaylist);
563   int iSize = list.size();
564   list.Add(playlist);
565         if (list.IsShuffled())
566                 ReShuffle(iPlaylist, iSize);
567 }
568
569 void CPlayListPlayer::Add(int iPlaylist, const CFileItemPtr &pItem)
570 {
571   if (iPlaylist < PLAYLIST_MUSIC || iPlaylist > PLAYLIST_VIDEO)
572     return;
573   CPlayList& list = GetPlaylist(iPlaylist);
574   int iSize = list.size();
575   list.Add(pItem);
576         if (list.IsShuffled())
577                 ReShuffle(iPlaylist, iSize);
578 }
579
580 void CPlayListPlayer::Add(int iPlaylist, CFileItemList& items)
581 {
582   if (iPlaylist < PLAYLIST_MUSIC || iPlaylist > PLAYLIST_VIDEO)
583     return;
584   CPlayList& list = GetPlaylist(iPlaylist);
585   int iSize = list.size();
586   list.Add(items);
587         if (list.IsShuffled())
588                 ReShuffle(iPlaylist, iSize);
589 }
590
591 void CPlayListPlayer::Clear()
592 {
593   if (m_PlaylistMusic)
594   {
595     m_PlaylistMusic->Clear();
596   }
597   if (m_PlaylistVideo)
598   {
599     m_PlaylistVideo->Clear();
600   }
601   if (m_PlaylistEmpty)
602   {
603     m_PlaylistEmpty->Clear();
604   }
605 }
606