From 9242a7c942a52e2ba87fb9d77860d5feac2cc3d5 Mon Sep 17 00:00:00 2001 From: Jeff Mitchell Date: Thu, 12 Jan 2012 17:55:33 -0500 Subject: [PATCH] Add the ability to listen along in real-time. Won't automatically switch if you (currently, might need to tweak) have <= 6 seconds left of the current track...after a while if you get further behind it'll automatically correct. Can toggle via source context menu right now, need to work in an icon. If you pause the music, it turns off real-time if it's on. --- src/libtomahawk/LatchManager.cpp | 28 +++++++++++++++++---- src/libtomahawk/LatchManager.h | 1 + src/libtomahawk/actioncollection.cpp | 4 +++ src/libtomahawk/actioncollection.h | 2 +- src/libtomahawk/audio/audioengine.cpp | 14 ++++++++--- src/libtomahawk/playlistinterface.cpp | 1 + src/libtomahawk/playlistinterface.h | 10 ++++++++ src/libtomahawk/sourceplaylistinterface.cpp | 10 ++++++-- src/libtomahawk/sourceplaylistinterface.h | 3 ++- src/sourcetree/sourcetreeview.cpp | 27 ++++++++++++++++++-- src/sourcetree/sourcetreeview.h | 2 ++ 11 files changed, 88 insertions(+), 14 deletions(-) diff --git a/src/libtomahawk/LatchManager.cpp b/src/libtomahawk/LatchManager.cpp index 5e5f3649c..572577564 100644 --- a/src/libtomahawk/LatchManager.cpp +++ b/src/libtomahawk/LatchManager.cpp @@ -22,8 +22,7 @@ #include "audio/audioengine.h" #include "database/database.h" -#include -#include +#include #include "sourcelist.h" #include "database/databasecommand_socialaction.h" #include "sourceplaylistinterface.h" @@ -82,7 +81,9 @@ LatchManager::playlistChanged( Tomahawk::playlistinterface_ptr ) cmd->setTimestamp( QDateTime::currentDateTime().toTime_t() ); Database::instance()->enqueue( QSharedPointer< DatabaseCommand >( cmd ) ); - ActionCollection::instance()->getAction( "latchOn" )->setText( tr( "&Catch Up" ) ); + QAction *latchOnAction = ActionCollection::instance()->getAction( "latchOn" ); + latchOnAction->setText( tr( "&Catch Up" ) ); + latchOnAction->setIcon( QIcon() ); // If not, then keep waiting return; @@ -117,7 +118,9 @@ LatchManager::playlistChanged( Tomahawk::playlistinterface_ptr ) m_state = NotLatched; - ActionCollection::instance()->getAction( "latchOn" )->setText( tr( "&Listen Along" ) ); + QAction *latchOnAction = ActionCollection::instance()->getAction( "latchOn" ); + latchOnAction->setText( tr( "&Listen Along" ) ); + latchOnAction->setIcon( QIcon( RESPATH "images/headphones-sidebar.png" ) ); } @@ -128,6 +131,7 @@ LatchManager::catchUpRequest() AudioEngine::instance()->next(); } + void LatchManager::unlatchRequest( const source_ptr& source ) { @@ -135,5 +139,19 @@ LatchManager::unlatchRequest( const source_ptr& source ) AudioEngine::instance()->stop(); AudioEngine::instance()->setPlaylist( Tomahawk::playlistinterface_ptr() ); - ActionCollection::instance()->getAction( "latchOn" )->setText( tr( "&Listen Along" ) ); + QAction *latchOnAction = ActionCollection::instance()->getAction( "latchOn" ); + latchOnAction->setText( tr( "&Listen Along" ) ); + latchOnAction->setIcon( QIcon( RESPATH "images/headphones-sidebar.png" ) ); +} + + +void +LatchManager::latchModeChangeRequest( const Tomahawk::source_ptr& source, bool realtime ) +{ + if ( !isLatched( source ) ) + return; + + source->getPlaylistInterface()->setLatchMode( realtime ? Tomahawk::PlaylistInterface::RealTime : Tomahawk::PlaylistInterface::StayOnSong ); + if ( realtime ) + catchUpRequest(); } diff --git a/src/libtomahawk/LatchManager.h b/src/libtomahawk/LatchManager.h index 26d8b8f8f..07c27ba30 100644 --- a/src/libtomahawk/LatchManager.h +++ b/src/libtomahawk/LatchManager.h @@ -43,6 +43,7 @@ public slots: void latchRequest( const Tomahawk::source_ptr& source ); void unlatchRequest( const Tomahawk::source_ptr& source ); void catchUpRequest(); + void latchModeChangeRequest( const Tomahawk::source_ptr& source, bool realtime ); private slots: void playlistChanged( Tomahawk::playlistinterface_ptr ); diff --git a/src/libtomahawk/actioncollection.cpp b/src/libtomahawk/actioncollection.cpp index 802794df1..3088e25b8 100644 --- a/src/libtomahawk/actioncollection.cpp +++ b/src/libtomahawk/actioncollection.cpp @@ -46,6 +46,10 @@ ActionCollection::initActions() latchOff->setIcon( QIcon( RESPATH "images/headphones-off.png" ) ); m_actionCollection[ "latchOff" ] = latchOff; + QAction *realtimeFollowingAlong = new QAction( tr( "&Follow in real-time" ), this ); + realtimeFollowingAlong->setCheckable( true ); + m_actionCollection[ "realtimeFollowingAlong" ] = realtimeFollowingAlong; + bool isPublic = TomahawkSettings::instance()->privateListeningMode() == TomahawkSettings::PublicListening; QAction *privacyToggle = new QAction( ( isPublic ? tr( "&Listen Privately" ) : tr( "&Listen Publicly" ) ), this ); privacyToggle->setIcon( QIcon( RESPATH "images/private-listening.png" ) ); diff --git a/src/libtomahawk/actioncollection.h b/src/libtomahawk/actioncollection.h index 09ede7839..1183935dc 100644 --- a/src/libtomahawk/actioncollection.h +++ b/src/libtomahawk/actioncollection.h @@ -21,7 +21,7 @@ #include "dllmacro.h" -#include +#include class DLLEXPORT ActionCollection : public QObject { diff --git a/src/libtomahawk/audio/audioengine.cpp b/src/libtomahawk/audio/audioengine.cpp index 9be1cf518..2e5a7e509 100644 --- a/src/libtomahawk/audio/audioengine.cpp +++ b/src/libtomahawk/audio/audioengine.cpp @@ -228,12 +228,12 @@ AudioEngine::canGoNext() m_playlist.data()->skipRestrictions() == PlaylistInterface::NoSkipForwards ) return false; - if ( !m_currentTrack.isNull() && !m_playlist.data()->hasNextItem() && - ( m_playlist.data()->currentItem().isNull() || ( m_currentTrack->id() == m_playlist.data()->currentItem()->id() ) ) ) + if ( !m_currentTrack.isNull() && !m_playlist->hasNextItem() && + ( m_playlist->currentItem().isNull() || ( m_currentTrack->id() == m_playlist->currentItem()->id() ) ) ) { //For instance, when doing a catch-up while listening along, but the person //you're following hasn't started a new track yet...don't do anything - tDebug( LOGEXTRA ) << Q_FUNC_INFO << "catch up"; + tDebug( LOGEXTRA ) << Q_FUNC_INFO << "catch up, but same track or can't move on because don't have next track or it wasn't resolved"; return false; } @@ -580,6 +580,14 @@ AudioEngine::playItem( Tomahawk::playlistinterface_ptr playlist, const Tomahawk: void AudioEngine::playlistNextTrackReady() { + // If in real-time and you have a few seconds left, you're probably lagging -- finish it up + if ( m_playlist && m_playlist->latchMode() == PlaylistInterface::RealTime && ( m_waitingOnNewTrack || m_currentTrack.isNull() || m_currentTrack->id() == 0 || ( currentTrackTotalTime() - currentTime() > 6000 ) ) ) + { + m_waitingOnNewTrack = false; + loadNextTrack(); + return; + } + if ( !m_waitingOnNewTrack ) return; diff --git a/src/libtomahawk/playlistinterface.cpp b/src/libtomahawk/playlistinterface.cpp index 8c03d9df1..11d60ea3e 100644 --- a/src/libtomahawk/playlistinterface.cpp +++ b/src/libtomahawk/playlistinterface.cpp @@ -24,6 +24,7 @@ using namespace Tomahawk; PlaylistInterface::PlaylistInterface () : QObject() + , m_latchMode( StayOnSong ) { qRegisterMetaType( "Tomahawk::PlaylistInterface::RepeatMode" ); } diff --git a/src/libtomahawk/playlistinterface.h b/src/libtomahawk/playlistinterface.h index b125d8ae9..45350b9e1 100644 --- a/src/libtomahawk/playlistinterface.h +++ b/src/libtomahawk/playlistinterface.h @@ -39,6 +39,7 @@ public: enum SeekRestrictions { NoSeekRestrictions, NoSeek }; enum SkipRestrictions { NoSkipRestrictions, NoSkipForwards, NoSkipBackwards, NoSkip }; enum RetryMode { NoRetry, Retry }; + enum LatchMode { StayOnSong, RealTime }; explicit PlaylistInterface(); virtual ~PlaylistInterface(); @@ -55,14 +56,20 @@ public: virtual Tomahawk::result_ptr siblingItem( int itemsAway ) = 0; virtual PlaylistInterface::RepeatMode repeatMode() const = 0; + virtual bool shuffled() const = 0; + virtual PlaylistInterface::ViewMode viewMode() const { return Unknown; } + virtual PlaylistInterface::SeekRestrictions seekRestrictions() const { return NoSeekRestrictions; } virtual PlaylistInterface::SkipRestrictions skipRestrictions() const { return NoSkipRestrictions; } virtual PlaylistInterface::RetryMode retryMode() const { return NoRetry; } virtual quint32 retryInterval() const { return 30000; } + virtual PlaylistInterface::LatchMode latchMode() const { return m_latchMode; } + virtual void setLatchMode( PlaylistInterface::LatchMode latchMode ) { m_latchMode = latchMode; } + virtual QString filter() const { return m_filter; } virtual void setFilter( const QString& pattern ) { m_filter = pattern; } @@ -84,6 +91,9 @@ signals: void sourceTrackCountChanged( unsigned int tracks ); void nextTrackReady(); +protected: + LatchMode m_latchMode; + private: Q_DISABLE_COPY( PlaylistInterface ) diff --git a/src/libtomahawk/sourceplaylistinterface.cpp b/src/libtomahawk/sourceplaylistinterface.cpp index b1ee26ef6..d6108563d 100644 --- a/src/libtomahawk/sourceplaylistinterface.cpp +++ b/src/libtomahawk/sourceplaylistinterface.cpp @@ -20,20 +20,26 @@ #include "source.h" #include "pipeline.h" +#include "audio/audioengine.h" #include "utils/logger.h" using namespace Tomahawk; -SourcePlaylistInterface::SourcePlaylistInterface( Tomahawk::Source *source ) +SourcePlaylistInterface::SourcePlaylistInterface( Tomahawk::Source *source, Tomahawk::PlaylistInterface::LatchMode latchMode ) : PlaylistInterface() , m_source( source ) , m_currentItem( 0 ) , m_gotNextItem( false ) { + setLatchMode( latchMode ); + if ( !m_source.isNull() ) connect( m_source.data(), SIGNAL( playbackStarted( const Tomahawk::query_ptr& ) ), SLOT( onSourcePlaybackStarted( const Tomahawk::query_ptr& ) ) ); + + if ( AudioEngine::instance() ) + connect( AudioEngine::instance(), SIGNAL( paused() ), SLOT( audioPaused() ) ); } @@ -95,7 +101,7 @@ QList SourcePlaylistInterface::tracks() { QList tracks; - return tracks; // FIXME + return tracks; // FIXME (with what?) } diff --git a/src/libtomahawk/sourceplaylistinterface.h b/src/libtomahawk/sourceplaylistinterface.h index 82de8ad44..0c1c5dff4 100644 --- a/src/libtomahawk/sourceplaylistinterface.h +++ b/src/libtomahawk/sourceplaylistinterface.h @@ -35,7 +35,7 @@ class DLLEXPORT SourcePlaylistInterface : public Tomahawk::PlaylistInterface Q_OBJECT public: - SourcePlaylistInterface( Tomahawk::Source *source ); + SourcePlaylistInterface( Tomahawk::Source *source, Tomahawk::PlaylistInterface::LatchMode latchMode = PlaylistInterface::RealTime ); virtual ~SourcePlaylistInterface(); QList tracks(); @@ -64,6 +64,7 @@ public: public slots: virtual void setRepeatMode( PlaylistInterface::RepeatMode ) {} virtual void setShuffled( bool ) {} + virtual void audioPaused() { setLatchMode( PlaylistInterface::StayOnSong ); } private slots: void onSourcePlaybackStarted( const Tomahawk::query_ptr& query ); diff --git a/src/sourcetree/sourcetreeview.cpp b/src/sourcetree/sourcetreeview.cpp index 242315b4b..4db5c483c 100644 --- a/src/sourcetree/sourcetreeview.cpp +++ b/src/sourcetree/sourcetreeview.cpp @@ -118,6 +118,7 @@ SourceTreeView::SourceTreeView( QWidget* parent ) connect( this, SIGNAL( latchRequest( Tomahawk::source_ptr ) ), m_latchManager, SLOT( latchRequest( Tomahawk::source_ptr ) ) ); connect( this, SIGNAL( unlatchRequest( Tomahawk::source_ptr ) ), m_latchManager, SLOT( unlatchRequest( Tomahawk::source_ptr ) ) ); connect( this, SIGNAL( catchUpRequest() ), m_latchManager, SLOT( catchUpRequest() ) ); + connect( this, SIGNAL( latchModeChangeRequest( Tomahawk::source_ptr, bool ) ), m_latchManager, SLOT( latchModeChangeRequest( Tomahawk::source_ptr, bool ) ) ); connect( ActionCollection::instance(), SIGNAL( privacyModeChanged() ), SLOT( repaint() ) ); } @@ -162,10 +163,14 @@ SourceTreeView::setupMenus() { if ( m_latchManager->isLatched( source ) ) { - m_latchMenu.addSeparator(); QAction *latchOffAction = ActionCollection::instance()->getAction( "latchOff" ); m_latchMenu.addAction( latchOffAction ); - connect( latchOffAction, SIGNAL( triggered() ), SLOT( latchOff() ), Qt::QueuedConnection ); + connect( latchOffAction, SIGNAL( triggered() ), SLOT( latchOff() ) ); + m_latchMenu.addSeparator(); + QAction *latchRealtimeAction = ActionCollection::instance()->getAction( "realtimeFollowingAlong" ); + latchRealtimeAction->setChecked( source->getPlaylistInterface()->latchMode() == Tomahawk::PlaylistInterface::RealTime ); + m_latchMenu.addAction( latchRealtimeAction ); + connect( latchRealtimeAction, SIGNAL( toggled( bool ) ), SLOT( latchModeToggled( bool ) ) ); } } } @@ -416,6 +421,24 @@ SourceTreeView::latchOff( const Tomahawk::source_ptr& source ) } +void +SourceTreeView::latchModeToggled( bool checked ) +{ + + disconnect( this, SLOT( latchOff() ) ); + qDebug() << Q_FUNC_INFO; + if ( !m_contextMenuIndex.isValid() ) + return; + + SourcesModel::RowType type = ( SourcesModel::RowType )model()->data( m_contextMenuIndex, SourcesModel::SourceTreeItemTypeRole ).toInt(); + if( type != SourcesModel::Collection ) + return; + + const SourceItem* item = itemFromIndex< SourceItem >( m_contextMenuIndex ); + const source_ptr source = item->source(); + emit latchModeChangeRequest( source, checked ); +} + void SourceTreeView::renamePlaylist() diff --git a/src/sourcetree/sourcetreeview.h b/src/sourcetree/sourcetreeview.h index 4a518cac7..0d6a9b7b1 100644 --- a/src/sourcetree/sourcetreeview.h +++ b/src/sourcetree/sourcetreeview.h @@ -58,6 +58,7 @@ signals: void latchRequest( const Tomahawk::source_ptr& source ); void unlatchRequest( const Tomahawk::source_ptr& source ); void catchUpRequest(); + void latchModeChangeRequest( const Tomahawk::source_ptr& source, bool realtime ); private slots: void onItemExpanded( const QModelIndex& idx ); @@ -75,6 +76,7 @@ private slots: void latchOff(); void latchOnOrCatchUp( const Tomahawk::source_ptr& source ); void latchOff( const Tomahawk::source_ptr& source ); + void latchModeToggled( bool checked ); void onCustomContextMenu( const QPoint& pos );