Playlist: fix small breakage introduced by commit 1fafbc4bd3a27a40028f6a68614822d673a...
[amarok:amarok.git] / src / playlist / PlaylistActions.cpp
1 /****************************************************************************************
2  * Copyright (c) 2007-2008 Ian Monroe <ian@monroe.nu>                                   *
3  * Copyright (c) 2007-2009 Nikolaj Hald Nielsen <nhn@kde.org>                           *
4  * Copyright (c) 2008 Seb Ruiz <ruiz@kde.org>                                           *
5  * Copyright (c) 2008 Soren Harward <stharward@gmail.com>                               *
6  * Copyright (c) 2009 Téo Mrnjavac <teo@kde.org>                                        *
7  *                                                                                      *
8  * This program is free software; you can redistribute it and/or modify it under        *
9  * the terms of the GNU General Public License as published by the Free Software        *
10  * Foundation; either version 2 of the License, or (at your option) version 3 or        *
11  * any later version accepted by the membership of KDE e.V. (or its successor approved  *
12  * by the membership of KDE e.V.), which shall act as a proxy defined in Section 14 of  *
13  * version 3 of the license.                                                            *
14  *                                                                                      *
15  * This program is distributed in the hope that it will be useful, but WITHOUT ANY      *
16  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A      *
17  * PARTICULAR PURPOSE. See the GNU General Public License for more details.             *
18  *                                                                                      *
19  * You should have received a copy of the GNU General Public License along with         *
20  * this program.  If not, see <http://www.gnu.org/licenses/>.                           *
21  ****************************************************************************************/
22
23 #define DEBUG_PREFIX "Playlist::Actions"
24
25 #include "PlaylistActions.h"
26
27 #include "core/support/Amarok.h"
28 #include "core/support/Components.h"
29 #include "core-impl/playlists/types/file/PlaylistFileSupport.h"
30 #include "amarokconfig.h"
31 #include "dbus/PlayerDBusHandler.h"
32 #include "core/support/Debug.h"
33 #include "DynamicModel.h"
34 #include "EngineController.h"
35 #include "core/engine/EngineObserver.h"
36 #include "core/interfaces/Logger.h"
37 #include "MainWindow.h"
38 #include "navigators/DynamicTrackNavigator.h"
39 #include "navigators/RandomAlbumNavigator.h"
40 #include "navigators/RandomTrackNavigator.h"
41 #include "navigators/RepeatAlbumNavigator.h"
42 #include "navigators/RepeatTrackNavigator.h"
43 #include "navigators/StandardTrackNavigator.h"
44 #include "navigators/FavoredRandomTrackNavigator.h"
45 #include "PlaylistModelStack.h"
46 #include "playlist/PlaylistWidget.h"
47 #include "playlistmanager/PlaylistManager.h"
48
49 #include <typeinfo>
50
51 Playlist::Actions* Playlist::Actions::s_instance = 0;
52
53 Playlist::Actions* Playlist::Actions::instance()
54 {
55     if( !s_instance )
56         s_instance = new Actions();
57     return s_instance;
58 }
59
60 void
61 Playlist::Actions::destroy()
62 {
63     delete s_instance;
64     s_instance = 0;
65 }
66
67 Playlist::Actions::Actions()
68         : QObject()
69         , Engine::EngineObserver( The::engineController() )
70         , m_nextTrackCandidate( 0 )
71         , m_trackToBeLast( 0 )
72         , m_navigator( 0 )
73         , m_stopAfterMode( StopNever )
74         , m_trackError( false )
75         , m_waitingForNextTrack( false )
76 {
77     DEBUG_BLOCK
78
79     m_topmostModel = Playlist::ModelStack::instance()->top();
80     playlistModeChanged(); // sets m_navigator.
81
82     restoreDefaultPlaylist();
83 }
84
85 Playlist::Actions::~Actions()
86 {
87     DEBUG_BLOCK
88
89     delete m_navigator;
90 }
91
92 Meta::TrackPtr
93 Playlist::Actions::likelyNextTrack()
94 {
95     return m_topmostModel->trackForId( m_navigator->likelyNextTrack() );
96 }
97
98 Meta::TrackPtr
99 Playlist::Actions::likelyPrevTrack()
100 {
101     return m_topmostModel->trackForId( m_navigator->likelyLastTrack() );
102 }
103
104 void
105 Playlist::Actions::requestNextTrack()
106 {
107     DEBUG_BLOCK
108     if ( m_nextTrackCandidate != 0 )
109         return;
110     if( m_trackError )
111         return;
112
113     debug() << "so far so good!";
114     m_trackError = false;
115     if ( stopAfterMode() == StopAfterQueue && m_topmostModel->activeId() == m_trackToBeLast )
116     {
117         setStopAfterMode( StopAfterCurrent );
118         m_trackToBeLast = 0;
119     }
120
121     m_nextTrackCandidate = m_navigator->requestNextTrack();
122
123     if( m_nextTrackCandidate == 0 )
124     {
125
126         debug() << "nothing more to play...";
127         //No more stuff to play. make sure to reset the active track so that
128         //pressing play will start at the top of the playlist (or whereever the navigator wants to start)
129         //instead of just replaying the last track.
130         m_topmostModel->setActiveRow( -1 );
131
132         //We also need to mark all tracks as unplayed or some navigators might be unhappy.
133         m_topmostModel->setAllUnplayed();
134
135         //if what is currently playing is a cd track, we need to stop playback as the cd will otherwise continue playing
136         if( The::engineController()->isPlayingAudioCd() )
137             The::engineController()->stop();
138
139         return;
140     }
141
142     if ( stopAfterMode() == StopAfterCurrent )  //stop after current / stop after track starts here
143         setStopAfterMode( StopNever );
144     else
145         play( m_nextTrackCandidate, false );
146 }
147
148 void
149 Playlist::Actions::requestUserNextTrack()
150 {
151     m_trackError = false;
152     m_nextTrackCandidate = m_navigator->requestUserNextTrack();
153     play( m_nextTrackCandidate );
154 }
155
156 void
157 Playlist::Actions::requestPrevTrack()
158 {
159     m_trackError = false;
160     m_nextTrackCandidate = m_navigator->requestLastTrack();
161     play( m_nextTrackCandidate );
162 }
163
164 void
165 Playlist::Actions::requestTrack( quint64 id )
166 {
167     m_trackError = false;
168     m_nextTrackCandidate = id;
169 }
170
171
172 void
173 Playlist::Actions::play()
174 {
175     DEBUG_BLOCK
176
177     if( 0 == m_nextTrackCandidate )
178     {
179         m_nextTrackCandidate = m_topmostModel->activeId();
180         if( 0 == m_nextTrackCandidate )
181             m_nextTrackCandidate = m_navigator->requestNextTrack();
182     }
183
184     play( m_nextTrackCandidate );
185 }
186
187 void
188 Playlist::Actions::play( const QModelIndex& index )
189 {
190     DEBUG_BLOCK
191
192     if( index.isValid() )
193     {
194         m_nextTrackCandidate = index.data( UniqueIdRole ).value<quint64>();
195         play( m_nextTrackCandidate );
196     }
197 }
198
199 void
200 Playlist::Actions::play( const int row )
201 {
202     DEBUG_BLOCK
203
204     m_nextTrackCandidate = m_topmostModel->idAt( row );
205     play( m_nextTrackCandidate );
206 }
207
208 void
209 Playlist::Actions::play( const quint64 trackid, bool now )
210 {
211     DEBUG_BLOCK
212
213     Meta::TrackPtr track = m_topmostModel->trackForId( trackid );
214     if ( track )
215     {
216         if ( now )
217         {
218             Meta::TrackPtr currentTrack = The::engineController()->currentTrack();
219             Phonon::State engineState = The::engineController()->state();
220             if( currentTrack && ( engineState == Phonon::PlayingState
221                                || engineState == Phonon::PausedState
222                                || engineState == Phonon::BufferingState ) )
223             {
224                 //Theres a track playing now, calculate statistics for that track before playing a new one.
225                 const double finishedPercent = (double)The::engineController()->trackPositionMs() / (double)currentTrack->length();
226                 debug() << "Manually advancing to the next track, calculating previous statistics for track here.  Finished % is: "  << finishedPercent;
227                 currentTrack->finishedPlaying( finishedPercent );
228             }
229             The::engineController()->play( track );
230         }
231         else
232             The::engineController()->setNextTrack( track );
233     }
234     else
235     {
236         m_trackError = true;
237         warning() << "Invalid trackid" << trackid;
238     }
239 }
240
241 void
242 Playlist::Actions::next()
243 {
244     DEBUG_BLOCK
245     requestUserNextTrack();
246 }
247
248 void
249 Playlist::Actions::back()
250 {
251     DEBUG_BLOCK
252     requestPrevTrack();
253 }
254
255 void
256 Playlist::Actions::playlistModeChanged()
257 {
258     DEBUG_BLOCK
259
260     QQueue<quint64> currentQueue;
261
262     if ( m_navigator )
263     {
264         //HACK: Migrate the queue to the new navigator
265         //TODO: The queue really should not be maintained by the navigators in this way
266         // but should be handled by a seperate and persistant object.
267
268         currentQueue = m_navigator->queue();
269         m_navigator->deleteLater();
270     }
271
272     debug() << "Dynamic mode:   " << AmarokConfig::dynamicMode();
273
274     if ( AmarokConfig::dynamicMode() )
275     {
276         PlaylistBrowserNS::DynamicModel* dm = PlaylistBrowserNS::DynamicModel::instance();
277
278         Dynamic::DynamicPlaylistPtr playlist = dm->activePlaylist();
279
280         if ( !playlist )
281         {
282             debug() << "No dynamic playlist current loaded! Creating dynamic track navigator with null playlist!";
283         }
284
285         m_navigator = new DynamicTrackNavigator( playlist );
286
287         return;
288
289     }
290
291     m_navigator = 0;
292
293
294     switch( AmarokConfig::trackProgression() )
295     {
296
297         case AmarokConfig::EnumTrackProgression::RepeatTrack:
298             m_navigator = new RepeatTrackNavigator();
299             break;
300
301         case AmarokConfig::EnumTrackProgression::RepeatAlbum:
302             m_navigator = new RepeatAlbumNavigator();
303             break;
304
305         case AmarokConfig::EnumTrackProgression::RandomTrack:
306             switch( AmarokConfig::favorTracks() )
307             {
308                 case AmarokConfig::EnumFavorTracks::HigherScores:
309                 case AmarokConfig::EnumFavorTracks::HigherRatings:
310                 case AmarokConfig::EnumFavorTracks::LessRecentlyPlayed:
311                     m_navigator = new FavoredRandomTrackNavigator();
312                     break;
313
314                 case AmarokConfig::EnumFavorTracks::Off:
315                 default:
316                     m_navigator = new RandomTrackNavigator();
317                     break;
318             }
319             break;
320
321         case AmarokConfig::EnumTrackProgression::RandomAlbum:
322             m_navigator = new RandomAlbumNavigator();
323             break;
324
325         //repeat playlist, standard and fallback are all the normal navigator.
326         case AmarokConfig::EnumTrackProgression::RepeatPlaylist:
327         case AmarokConfig::EnumTrackProgression::Normal:
328         default:
329             m_navigator = new StandardTrackNavigator();
330             break;
331     }
332
333     m_navigator->queueIds( currentQueue );
334
335     The::playerDBusHandler()->updateStatus();
336
337     emit navigatorChanged();
338 }
339
340 void
341 Playlist::Actions::repopulateDynamicPlaylist()
342 {
343     DEBUG_BLOCK
344
345     if ( typeid( *m_navigator ) == typeid( DynamicTrackNavigator ) )
346     {
347         static_cast<DynamicTrackNavigator*>(m_navigator)->repopulate();
348     }
349 }
350
351 int
352 Playlist::Actions::queuePosition( quint64 id )
353 {
354     return m_navigator->queuePosition( id );
355 }
356
357 QQueue<quint64>
358 Playlist::Actions::queue()
359 {
360     return m_navigator->queue();
361 }
362
363 void
364 Playlist::Actions::queue( QList<int> rows )
365 {
366     DEBUG_BLOCK
367
368     foreach( int row, rows )
369     {
370         quint64 id = m_topmostModel->idAt( row );
371         debug() << "About to queue proxy row"<< row;
372         m_navigator->queueId( id );
373         m_topmostModel->setRowQueued( row );
374     }
375 }
376
377 void
378 Playlist::Actions::dequeue( QList<int> rows )
379 {
380     DEBUG_BLOCK
381
382     foreach( int row, rows )
383     {
384         quint64 id = m_topmostModel->idAt( row );
385         m_navigator->dequeueId( id );
386         m_topmostModel->setRowDequeued( row );
387     }
388 }
389
390 void
391 Playlist::Actions::engineStateChanged( Phonon::State currentState, Phonon::State )
392 {
393     static int failures = 0;
394     const int maxFailures = 10;
395
396     m_trackError = false;
397
398     if ( currentState == Phonon::ErrorState )
399     {
400         failures++;
401         warning() << "Error, can not play this track.";
402         warning() << "Failure count: " << failures;
403         if ( failures >= maxFailures )
404         {
405             Amarok::Components::logger()->longMessage( i18n( "Too many errors encountered in playlist. Playback stopped." ), Amarok::Logger::Warning );
406             error() << "Stopping playlist.";
407             failures = 0;
408             m_trackError = true;
409         }
410     }
411     else if ( currentState == Phonon::PlayingState )
412     {
413         if ( failures > 0 )
414         {
415             debug() << "Successfully played track. Resetting failure count.";
416             failures = 0;
417         }
418     }
419 }
420
421 void
422 Playlist::Actions::engineNewTrackPlaying()
423 {
424     DEBUG_BLOCK
425
426     Meta::TrackPtr engineTrack = The::engineController()->currentTrack();
427     if ( engineTrack )
428     {
429         Meta::TrackPtr candidateTrack = m_topmostModel->trackForId( m_nextTrackCandidate );    // May be 0.
430         if ( engineTrack == candidateTrack )
431         {   // The engine is playing what we planned: everything is OK.
432             m_topmostModel->setActiveId( m_nextTrackCandidate );
433         }
434         else
435         {
436             warning() << "engineNewTrackPlaying:" << engineTrack->prettyName() << "does not match what the playlist controller thought it should be";
437             if ( m_topmostModel->activeTrack() != engineTrack )
438             {
439                  // this will set active row to -1 if the track isn't in the playlist at all
440                 int row = m_topmostModel->firstRowForTrack( engineTrack );
441                 if( row != -1 )
442                     m_topmostModel->setActiveRow( row );
443                 else
444                     m_topmostModel->setActiveRow( AmarokConfig::lastPlaying() );
445             }
446             //else
447             //  Engine and playlist are in sync even though we didn't plan it; do nothing
448         }
449     }
450     else
451         warning() << "engineNewTrackPlaying: not really a track";
452
453     m_nextTrackCandidate = 0;
454 }
455
456
457 void
458 Playlist::Actions::normalizeDynamicPlaylist()
459 {
460     if ( typeid( *m_navigator ) == typeid( DynamicTrackNavigator ) )
461     {
462         static_cast<DynamicTrackNavigator*>(m_navigator)->appendUpcoming();
463     }
464 }
465
466 void
467 Playlist::Actions::repaintPlaylist()
468 {
469     The::mainWindow()->playlistWidget()->currentView()->repaint();
470 }
471
472 void
473 Playlist::Actions::restoreDefaultPlaylist()
474 {
475     DEBUG_BLOCK
476
477     // The PlaylistManager needs to be loaded or podcast episodes and other
478     // non-collection Tracks will not be loaded correctly.
479     The::playlistManager();
480
481
482     Playlists::PlaylistFilePtr playlist = Playlists::loadPlaylistFile( Playlist::ModelStack::instance()->bottom()->defaultPlaylistPath() );
483     if ( playlist )
484     {
485         Meta::TrackList tracks = playlist->tracks();
486
487         QMutableListIterator<Meta::TrackPtr> i( tracks );
488         while ( i.hasNext() )
489         {
490             i.next();
491             Meta::TrackPtr track = i.value();
492             if ( ! track )
493                 i.remove();
494             else if( Playlists::canExpand( track ) )
495             {
496                 Playlists::PlaylistPtr playlist = Playlists::expand( track );
497                 //expand() can return 0 if the KIO job errors out
498                 if( playlist )
499                 {
500                     i.remove();
501                     Meta::TrackList newtracks = playlist->tracks();
502                     foreach( Meta::TrackPtr t, newtracks )
503                         if( t )
504                             i.insert( t );
505                 }
506             }
507         }
508
509         The::playlistController()->insertTracks( 0, tracks );
510
511         QList<int> queuedRows = playlist->queue();
512         queue( playlist->queue() );
513
514         //Select previously playing track
515         const int lastPlayingRow = AmarokConfig::lastPlaying();
516         if( lastPlayingRow >= 0 )
517             Playlist::ModelStack::instance()->bottom()->setActiveRow( lastPlayingRow );
518     }
519 }
520
521 namespace The
522 {
523     AMAROK_EXPORT Playlist::Actions* playlistActions() { return Playlist::Actions::instance(); }
524 }