diff --git a/src/libtomahawk/Query.cpp b/src/libtomahawk/Query.cpp index 1a6312a19..6f70f4a9f 100644 --- a/src/libtomahawk/Query.cpp +++ b/src/libtomahawk/Query.cpp @@ -685,7 +685,7 @@ Query::howSimilar( const Tomahawk::result_ptr& r ) qTrackname = queryTrack()->trackSortname(); } - static const QRegExp filterOutChars = QRegExp(QString::fromUtf8("[-`´~!@#$%^&*()_—+=|:;<>«»,.?/{}\'\"\\[\\]\\\\]")); + static const QRegExp filterOutChars = QRegExp(QString::fromUtf8("[-`´~!@#$%^&*\\(\\)_—+=|:;<>«»,.?/{}\'\"\\[\\]\\\\]")); //Cleanup symbols for minor naming differences qArtistname.remove(filterOutChars); diff --git a/src/libtomahawk/audio/AudioEngine.cpp b/src/libtomahawk/audio/AudioEngine.cpp index 9b7ababb6..792f69292 100644 --- a/src/libtomahawk/audio/AudioEngine.cpp +++ b/src/libtomahawk/audio/AudioEngine.cpp @@ -572,10 +572,10 @@ AudioEngine::onNowPlayingInfoReady( const Tomahawk::InfoSystem::InfoType type ) void -AudioEngine::loadTrack( const Tomahawk::result_ptr& result ) +AudioEngine::loadTrack( const Tomahawk::result_ptr& result, bool preload ) { Q_D( AudioEngine ); - tDebug( LOGEXTRA ) << Q_FUNC_INFO << ( result.isNull() ? QString() : result->url() ); + tDebug( LOGEXTRA ) << Q_FUNC_INFO << ( result.isNull() ? QString() : result->url() ) << " preload:" << preload; if ( !d->audioOutput->isInitialized() ) @@ -589,18 +589,57 @@ AudioEngine::loadTrack( const Tomahawk::result_ptr& result ) return; } - // We do this to stop the audio as soon as a user activated another track - // If we don't block the audioOutput signals, the state change will trigger - // loading yet another track - d->audioOutput->blockSignals( true ); - d->audioOutput->stop(); - d->audioOutput->blockSignals( false ); + if (preload && d->preloadedTrack == result) + return; - setCurrentTrack( result ); + if (preload) + tDebug( LOGEXTRA ) << Q_FUNC_INFO << "not preloaded yet, preloading"; + + if (preload) + { + setPreloadTrack( result ); + } + else + { + // We do this to stop the audio as soon as a user activated another track + // If we don't block the audioOutput signals, the state change will trigger + // loading yet another track + d->audioOutput->blockSignals( true ); + d->audioOutput->stop(); + d->audioOutput->blockSignals( false ); + + setCurrentTrack( result ); + if ( result == d->preloadedTrack ) + { + setPreloadTrack( Tomahawk::result_ptr(nullptr) ); + d->state = Loading; + emit loading( d->currentTrack ); + d->audioOutput->switchToPreloadedMedia(); + if ( !d->input.isNull() ) + { + d->input->close(); + d->input.clear(); + } + d->input = d->inputPreloaded; + + d->audioOutput->play(); + + if ( TomahawkSettings::instance()->privateListeningMode() != TomahawkSettings::FullyPrivate ) + { + d->currentTrack->track()->startPlaying(); + } + + sendNowPlayingNotification( Tomahawk::InfoSystem::InfoNowPlaying ); + return; + } + setPreloadTrack( Tomahawk::result_ptr(nullptr) ); + } ScriptJob* job = result->resolvedBy()->getStreamUrl( result ); connect( job, SIGNAL( done( QVariantMap ) ), SLOT( gotStreamUrl( QVariantMap ) ) ); job->setProperty( "result", QVariant::fromValue( result ) ); + job->setProperty( "isPreload", QVariant::fromValue(preload) ); + tDebug() << "preload:" << preload << ", for result:" << result; job->start(); } @@ -611,6 +650,9 @@ AudioEngine::gotStreamUrl( const QVariantMap& data ) QString streamUrl = data[ "url" ].toString(); QVariantMap headers = data[ "headers" ].toMap(); Tomahawk::result_ptr result = sender()->property( "result" ).value(); + bool isPreload = sender()->property( "isPreload" ).value(); + + tDebug() << Q_FUNC_INFO << " is preload:" << isPreload << ", for result:" << result; if ( streamUrl.isEmpty() || headers.isEmpty() || !( TomahawkUtils::isHttpResult( streamUrl ) || TomahawkUtils::isHttpsResult( streamUrl ) ) ) @@ -618,7 +660,7 @@ AudioEngine::gotStreamUrl( const QVariantMap& data ) // We can't supply custom headers to VLC - but prefer using its HTTP streaming due to improved seeking ability // Not an RTMP or HTTP-with-headers URL, get IO device QSharedPointer< QIODevice > sp; - performLoadIODevice( result, streamUrl ); + performLoadIODevice( result, streamUrl, isPreload ); } else { @@ -654,26 +696,32 @@ AudioEngine::gotStreamUrl( const QVariantMap& data ) void AudioEngine::gotRedirectedStreamUrl( const Tomahawk::result_ptr& result, NetworkReply* reply ) { + Q_D( AudioEngine ); // std::functions cannot accept temporaries as parameters QSharedPointer< QIODevice > sp ( reply->reply(), &QObject::deleteLater ); QString url = reply->reply()->url().toString(); reply->disconnectFromReply(); reply->deleteLater(); - performLoadTrack( result, url, sp ); + bool isPreload = result == d->preloadedTrack; + tDebug() << Q_FUNC_INFO << " is preload:" << isPreload; + + performLoadTrack( result, url, sp, isPreload ); } void AudioEngine::onPositionChanged( float new_position ) { + if ( new_position >= 0.90 ) + loadNextTrack(true); // tDebug() << Q_FUNC_INFO << new_position << state(); emit trackPosition( new_position ); } void -AudioEngine::performLoadIODevice( const result_ptr& result, const QString& url ) +AudioEngine::performLoadIODevice( const result_ptr& result, const QString& url, bool preload ) { tDebug( LOGEXTRA ) << Q_FUNC_INFO << ( result.isNull() ? QString() : url ); @@ -683,37 +731,39 @@ AudioEngine::performLoadIODevice( const result_ptr& result, const QString& url ) std::function< void ( const QString, QSharedPointer< QIODevice > ) > callback = std::bind( &AudioEngine::performLoadTrack, this, result, std::placeholders::_1, - std::placeholders::_2 ); + std::placeholders::_2, + preload ); Tomahawk::UrlHandler::getIODeviceForUrl( result, url, callback ); } else { QSharedPointer< QIODevice > io; - performLoadTrack( result, url, io ); + performLoadTrack( result, url, io, preload ); } } void -AudioEngine::performLoadTrack( const Tomahawk::result_ptr result, const QString& url, QSharedPointer< QIODevice > io ) +AudioEngine::performLoadTrack( const Tomahawk::result_ptr result, const QString& url, QSharedPointer< QIODevice > io, bool preload ) { if ( QThread::currentThread() != thread() ) { QMetaObject::invokeMethod( this, "performLoadTrack", Qt::QueuedConnection, Q_ARG( const Tomahawk::result_ptr, result ), Q_ARG( const QString, url ), - Q_ARG( QSharedPointer< QIODevice >, io ) + Q_ARG( QSharedPointer< QIODevice >, io ), + Q_ARG( bool, preload ) ); return; } Q_D( AudioEngine ); - if ( currentTrack() != result ) + if ( !preload && currentTrack() != result ) { tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Track loaded too late, skip."; return; } - tDebug( LOGEXTRA ) << Q_FUNC_INFO << ( result.isNull() ? QString() : result->url() ); + tDebug( LOGEXTRA ) << Q_FUNC_INFO << ( result.isNull() ? QString() : result->url() ) << preload; QSharedPointer< QIODevice > ioToKeep = io; bool err = false; @@ -727,9 +777,16 @@ AudioEngine::performLoadTrack( const Tomahawk::result_ptr result, const QString& if ( !err ) { - tLog() << Q_FUNC_INFO << "Starting new song:" << url; - d->state = Loading; - emit loading( d->currentTrack ); + if (preload) + { + tLog() << Q_FUNC_INFO << "Preloading new song:" << url; + } + else + { + tLog() << Q_FUNC_INFO << "Starting new song:" << url; + d->state = Loading; + emit loading( d->currentTrack ); + } if ( !TomahawkUtils::isLocalResult( url ) && !( TomahawkUtils::isHttpResult( url ) && io.isNull() ) @@ -738,18 +795,18 @@ AudioEngine::performLoadTrack( const Tomahawk::result_ptr result, const QString& QSharedPointer qnr = io.objectCast(); if ( !qnr.isNull() ) { - d->audioOutput->setCurrentSource( new QNR_IODeviceStream( qnr, this ) ); + d->audioOutput->setCurrentSource( new QNR_IODeviceStream( qnr, this ), preload ); // We keep track of the QNetworkReply in QNR_IODeviceStream // and AudioOutput handles the deletion of the // QNR_IODeviceStream object ioToKeep.clear(); - d->audioOutput->setAutoDelete( true ); + d->audioOutput->setAutoDelete( true, preload ); } else { - d->audioOutput->setCurrentSource( io.data() ); + d->audioOutput->setCurrentSource( io.data(), preload); // We handle the deletion via tracking in d->input - d->audioOutput->setAutoDelete( false ); + d->audioOutput->setAutoDelete( false, preload); } } else @@ -768,7 +825,7 @@ AudioEngine::performLoadTrack( const Tomahawk::result_ptr result, const QString& } tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Passing to VLC:" << furl; - d->audioOutput->setCurrentSource( furl ); + d->audioOutput->setCurrentSource( furl, preload ); } else { @@ -777,26 +834,37 @@ AudioEngine::performLoadTrack( const Tomahawk::result_ptr result, const QString& furl = furl.right( furl.length() - 7 ); tLog( LOGVERBOSE ) << Q_FUNC_INFO << "Passing to VLC:" << QUrl::fromLocalFile( furl ); - d->audioOutput->setCurrentSource( QUrl::fromLocalFile( furl ) ); + d->audioOutput->setCurrentSource( QUrl::fromLocalFile( furl ), preload ); } - d->audioOutput->setAutoDelete( true ); + d->audioOutput->setAutoDelete( true, preload ); } - if ( !d->input.isNull() ) + if ( preload ) { + if ( !d->inputPreloaded.isNull() ) + { + d->inputPreloaded->close(); + d->inputPreloaded.clear(); + } + d->inputPreloaded = ioToKeep; + } + else { - d->input->close(); - d->input.clear(); - } - d->input = ioToKeep; - d->audioOutput->play(); + if ( !d->input.isNull() ) + { + d->input->close(); + d->input.clear(); + } + d->input = ioToKeep; + d->audioOutput->play(); - if ( TomahawkSettings::instance()->privateListeningMode() != TomahawkSettings::FullyPrivate ) - { - d->currentTrack->track()->startPlaying(); - } + if ( TomahawkSettings::instance()->privateListeningMode() != TomahawkSettings::FullyPrivate ) + { + d->currentTrack->track()->startPlaying(); + } - sendNowPlayingNotification( Tomahawk::InfoSystem::InfoNowPlaying ); + sendNowPlayingNotification( Tomahawk::InfoSystem::InfoNowPlaying ); + } } } @@ -806,7 +874,10 @@ AudioEngine::performLoadTrack( const Tomahawk::result_ptr result, const QString& return; } - d->waitingOnNewTrack = false; + if ( !preload ) + { + d->waitingOnNewTrack = false; + } return; } @@ -838,18 +909,19 @@ AudioEngine::loadPreviousTrack() } if ( result ) - loadTrack( result ); + loadTrack( result, false ); else stop(); } void -AudioEngine::loadNextTrack() +AudioEngine::loadNextTrack( bool preload ) { if ( QThread::currentThread() != thread() ) { - QMetaObject::invokeMethod( this, "loadNextTrack", Qt::QueuedConnection ); + QMetaObject::invokeMethod( this, "loadNextTrack", Qt::QueuedConnection, + Q_ARG( bool, preload )); return; } @@ -863,8 +935,11 @@ AudioEngine::loadNextTrack() { if ( d->stopAfterTrack->track()->equals( d->currentTrack->track() ) ) { - d->stopAfterTrack.clear(); - stop(); + if ( !preload ) + { + d->stopAfterTrack.clear(); + stop(); + } return; } } @@ -882,17 +957,24 @@ AudioEngine::loadNextTrack() if ( d->playlist.data()->nextResult() ) { - result = d->playlist.data()->setSiblingResult( 1 ); - setCurrentTrackPlaylist( d->playlist ); + if ( preload ) + { + result = d->playlist.data()->nextResult(); + } + else + { + result = d->playlist.data()->setSiblingResult( 1 ); + setCurrentTrackPlaylist( d->playlist ); + } } } if ( result ) { - tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "Got next item, loading track"; - loadTrack( result ); + tDebug( LOGVERBOSE ) << Q_FUNC_INFO << "Got next item, loading track, preload:" << preload; + loadTrack( result, preload ); } - else + else if ( !preload ) { if ( !d->playlist.isNull() && d->playlist.data()->retryMode() == Tomahawk::PlaylistModes::Retry ) d->waitingOnNewTrack = true; @@ -961,7 +1043,7 @@ AudioEngine::playItem( Tomahawk::playlistinterface_ptr playlist, const Tomahawk: if ( result ) { - loadTrack( result ); + loadTrack( result, false ); } else if ( !d->playlist.isNull() && d->playlist.data()->retryMode() == PlaylistModes::Retry ) { @@ -1236,6 +1318,14 @@ AudioEngine::setStopAfterTrack( const query_ptr& query ) } +void +AudioEngine::setPreloadTrack( const Tomahawk::result_ptr& result ) +{ + Q_D( AudioEngine ); + + d->preloadedTrack = result; +} + void AudioEngine::setCurrentTrack( const Tomahawk::result_ptr& result ) { @@ -1262,7 +1352,6 @@ AudioEngine::setCurrentTrack( const Tomahawk::result_ptr& result ) } } - void AudioEngine::setState( AudioState state ) { diff --git a/src/libtomahawk/audio/AudioEngine.h b/src/libtomahawk/audio/AudioEngine.h index 403ccb2a4..795f65bc5 100644 --- a/src/libtomahawk/audio/AudioEngine.h +++ b/src/libtomahawk/audio/AudioEngine.h @@ -135,10 +135,10 @@ public slots: void toggleMute(); void play( const QUrl& url ); - void playItem( Tomahawk::playlistinterface_ptr playlist, const Tomahawk::result_ptr& result, const Tomahawk::query_ptr& fromQuery = Tomahawk::query_ptr() ); - void playItem( Tomahawk::playlistinterface_ptr playlist, const Tomahawk::query_ptr& query ); - void playItem( const Tomahawk::artist_ptr& artist ); - void playItem( const Tomahawk::album_ptr& album ); + void playItem( Tomahawk::playlistinterface_ptr playlist, const Tomahawk::result_ptr& result, const Tomahawk::query_ptr& fromQuery = Tomahawk::query_ptr()); + void playItem( Tomahawk::playlistinterface_ptr playlist, const Tomahawk::query_ptr& query); + void playItem( const Tomahawk::artist_ptr& artist); + void playItem( const Tomahawk::album_ptr& album); void playPlaylistInterface( const Tomahawk::playlistinterface_ptr& playlist ); void setPlaylist( Tomahawk::playlistinterface_ptr playlist ); void setQueue( const Tomahawk::playlistinterface_ptr& queue ); @@ -182,21 +182,22 @@ signals: void error( AudioEngine::AudioErrorCode errorCode ); private slots: - void loadTrack( const Tomahawk::result_ptr& result ); //async! + void loadTrack( const Tomahawk::result_ptr& result, bool preload ); //async! void gotStreamUrl( const QVariantMap& data ); void gotRedirectedStreamUrl( const Tomahawk::result_ptr& result, NetworkReply* reply ); - void performLoadIODevice( const Tomahawk::result_ptr& result, const QString& url ); //only call from loadTrack kthxbi - void performLoadTrack( const Tomahawk::result_ptr result, const QString& url, QSharedPointer< QIODevice > io ); //only call from loadTrack or performLoadIODevice kthxbi + void performLoadIODevice( const Tomahawk::result_ptr& result, const QString& url, bool preload ); //only call from loadTrack kthxbi + void performLoadTrack( const Tomahawk::result_ptr result, const QString& url, QSharedPointer< QIODevice > io, bool preload ); //only call from loadTrack or performLoadIODevice kthxbi void loadPreviousTrack(); - void loadNextTrack(); + void loadNextTrack(bool preload = false); void onVolumeChanged( qreal volume ); void timerTriggered( qint64 time ); void onPositionChanged( float new_position ); void setCurrentTrack( const Tomahawk::result_ptr& result ); + void setPreloadTrack( const Tomahawk::result_ptr& result ); void onNowPlayingInfoReady( const Tomahawk::InfoSystem::InfoType type ); void onPlaylistNextTrackAvailable(); diff --git a/src/libtomahawk/audio/AudioEngine_p.h b/src/libtomahawk/audio/AudioEngine_p.h index 5fc26286f..26afa95d4 100644 --- a/src/libtomahawk/audio/AudioEngine_p.h +++ b/src/libtomahawk/audio/AudioEngine_p.h @@ -28,9 +28,11 @@ public slots: private: QSharedPointer input; + QSharedPointer inputPreloaded; Tomahawk::query_ptr stopAfterTrack; Tomahawk::result_ptr currentTrack; + Tomahawk::result_ptr preloadedTrack; Tomahawk::playlistinterface_ptr playlist; Tomahawk::playlistinterface_ptr currentTrackPlaylist; Tomahawk::playlistinterface_ptr queue; diff --git a/src/libtomahawk/audio/AudioOutput.cpp b/src/libtomahawk/audio/AudioOutput.cpp index 6d93fd9ac..ea97ba98c 100644 --- a/src/libtomahawk/audio/AudioOutput.cpp +++ b/src/libtomahawk/audio/AudioOutput.cpp @@ -56,9 +56,11 @@ AudioOutput::AudioOutput( QObject* parent ) : QObject( parent ) , m_currentState( Stopped ) , m_currentStream( nullptr ) + , m_preloadedStream( nullptr ) , m_seekable( true ) , m_muted( false ) , m_autoDelete( true ) + , m_preloadedAutoDelete( true ) , m_volume( 1.0 ) , m_currentTime( 0 ) , m_totalTime( 0 ) @@ -68,6 +70,8 @@ AudioOutput::AudioOutput( QObject* parent ) , m_vlcInstance( nullptr ) , m_vlcPlayer( nullptr ) , m_vlcMedia( nullptr ) + , m_vlcPreloadedPlayer( nullptr ) + , m_vlcPreloadedMedia( nullptr ) { tDebug() << Q_FUNC_INFO; @@ -123,37 +127,46 @@ AudioOutput::AudioOutput( QObject* parent ) #endif m_vlcPlayer = libvlc_media_player_new( m_vlcInstance ); - libvlc_event_manager_t* manager = libvlc_media_player_event_manager( m_vlcPlayer ); - libvlc_event_type_t events[] = { - libvlc_MediaPlayerMediaChanged, - libvlc_MediaPlayerNothingSpecial, - libvlc_MediaPlayerOpening, - libvlc_MediaPlayerBuffering, - libvlc_MediaPlayerPlaying, - libvlc_MediaPlayerPaused, - libvlc_MediaPlayerStopped, - libvlc_MediaPlayerForward, - libvlc_MediaPlayerBackward, - libvlc_MediaPlayerEndReached, - libvlc_MediaPlayerEncounteredError, - libvlc_MediaPlayerTimeChanged, - libvlc_MediaPlayerPositionChanged, - libvlc_MediaPlayerSeekableChanged, - libvlc_MediaPlayerPausableChanged, - libvlc_MediaPlayerTitleChanged, - libvlc_MediaPlayerSnapshotTaken, - //libvlc_MediaPlayerLengthChanged, -#if (LIBVLC_VERSION_INT >= LIBVLC_VERSION(2, 2, 2, 0)) - libvlc_MediaPlayerAudioVolume, - libvlc_MediaPlayerMuted, - libvlc_MediaPlayerUnmuted, -#endif - libvlc_MediaPlayerVout - }; - const int eventCount = sizeof(events) / sizeof( *events ); - for ( int i = 0; i < eventCount; i++ ) + m_vlcPreloadedPlayer = libvlc_media_player_new( m_vlcInstance ); + + for( auto player : { m_vlcPlayer, m_vlcPreloadedPlayer } ) { - libvlc_event_attach( manager, events[ i ], &AudioOutput::vlcEventCallback, this ); + libvlc_audio_set_mute( player, 0 ); + { + libvlc_event_manager_t* current_manager = libvlc_media_player_event_manager( player ); + libvlc_event_manager_t* new_manager = libvlc_media_player_event_manager( player ); + static libvlc_event_type_t events[] = { + libvlc_MediaPlayerMediaChanged, + libvlc_MediaPlayerNothingSpecial, + libvlc_MediaPlayerOpening, + libvlc_MediaPlayerBuffering, + libvlc_MediaPlayerPlaying, + libvlc_MediaPlayerPaused, + libvlc_MediaPlayerStopped, + libvlc_MediaPlayerForward, + libvlc_MediaPlayerBackward, + libvlc_MediaPlayerEndReached, + libvlc_MediaPlayerEncounteredError, + libvlc_MediaPlayerTimeChanged, + libvlc_MediaPlayerPositionChanged, + libvlc_MediaPlayerSeekableChanged, + libvlc_MediaPlayerPausableChanged, + libvlc_MediaPlayerTitleChanged, + libvlc_MediaPlayerSnapshotTaken, + //libvlc_MediaPlayerLengthChanged, +#if (LIBVLC_VERSION_INT >= LIBVLC_VERSION(2, 2, 2, 0)) + libvlc_MediaPlayerAudioVolume, + libvlc_MediaPlayerMuted, + libvlc_MediaPlayerUnmuted, +#endif + libvlc_MediaPlayerVout + }; + const int eventCount = sizeof(events) / sizeof( *events ); + for ( int i = 0; i < eventCount; i++ ) + { + libvlc_event_attach( new_manager, events[ i ], &AudioOutput::vlcEventCallback, this ); + } + } } // HACK: play silent ogg file and set volume on that to workaround vlc not allowing to set volume before a file is played @@ -161,7 +174,9 @@ AudioOutput::AudioOutput( QObject* parent ) Q_ASSERT( m_silenceFile.exists() ); Q_ASSERT( m_silenceFile.open( QIODevice::ReadOnly ) ); - setCurrentSource( new MediaStream( &m_silenceFile, true ) ); + setCurrentSource( new MediaStream( &m_silenceFile, true ), false ); + setCurrentSource( new MediaStream( &m_silenceFile, true ), true ); + libvlc_media_player_play( m_vlcPlayer ); #if QT_VERSION >= QT_VERSION_CHECK(5,4,0) @@ -169,24 +184,71 @@ AudioOutput::AudioOutput( QObject* parent ) QTimer::singleShot( 15000, [&]() { if ( !m_initialized ) { - m_initialized = true; + m_initialized = 2; emit initialized(); } } ); #endif } +void AudioOutput::switchToPreloadedMedia( void ) +{ + + //Swap + auto tempPlayer = m_vlcPreloadedPlayer; + m_vlcPreloadedPlayer = m_vlcPlayer; + m_vlcPlayer = tempPlayer; + + libvlc_media_player_stop( m_vlcPreloadedPlayer ); + + if ( m_vlcMedia != nullptr ) + { + libvlc_media_release( m_vlcMedia ); + } + //Now Media + { + if ( m_autoDelete && m_currentStream != nullptr ) + { + delete m_currentStream; + } + + m_vlcMedia = m_vlcPreloadedMedia; + m_vlcPreloadedMedia = nullptr; + m_currentStream = m_preloadedStream; + m_preloadedStream = nullptr; + m_autoDelete = m_preloadedAutoDelete; + + m_totalTime = libvlc_media_get_duration( m_vlcMedia ); + + m_currentTime = 0; + m_justSeeked = false; + m_seekable = true; + } + + libvlc_media_player_set_position( m_vlcPlayer, 0.0 ); +} AudioOutput::~AudioOutput() { tDebug() << Q_FUNC_INFO; + if ( m_vlcPreloadedPlayer != nullptr ) + { + libvlc_media_player_stop( m_vlcPreloadedPlayer ); + libvlc_media_player_release( m_vlcPreloadedPlayer ); + m_vlcPreloadedPlayer = nullptr; + } if ( m_vlcPlayer != nullptr ) { libvlc_media_player_stop( m_vlcPlayer ); libvlc_media_player_release( m_vlcPlayer ); m_vlcPlayer = nullptr; } + if ( m_vlcPreloadedMedia != nullptr ) + { + libvlc_media_release( m_vlcPreloadedMedia ); + m_vlcPreloadedMedia = nullptr; + } if ( m_vlcMedia != nullptr ) { libvlc_media_release( m_vlcMedia ); @@ -208,11 +270,19 @@ AudioOutput::onInitVlcEvent( const libvlc_event_t* event ) setVolume( volume() ); setMuted( isMuted() ); - m_initialized = true; - m_silenceFile.close(); + m_initialized ++; tDebug() << Q_FUNC_INFO << "Init OK"; - emit initialized(); + if (m_initialized >=2) + { + m_silenceFile.close(); + emit initialized(); + } + else + { + switchToPreloadedMedia(); + libvlc_media_player_play( m_vlcPlayer ); + } break; default: @@ -222,23 +292,26 @@ AudioOutput::onInitVlcEvent( const libvlc_event_t* event ) void -AudioOutput::setAutoDelete( bool ad ) +AudioOutput::setAutoDelete( bool ad, bool preload ) { - m_autoDelete = ad; + if (preload) + m_preloadedAutoDelete = ad; + else + m_autoDelete = ad; } void -AudioOutput::setCurrentSource( const QUrl& stream ) +AudioOutput::setCurrentSource( const QUrl& stream, bool preload ) { - setCurrentSource( new MediaStream( stream ) ); + setCurrentSource( new MediaStream( stream ), preload ); } void -AudioOutput::setCurrentSource( QIODevice* stream ) +AudioOutput::setCurrentSource( QIODevice* stream, bool preload ) { - setCurrentSource( new MediaStream( stream ) ); + setCurrentSource( new MediaStream( stream ), preload ); } @@ -258,29 +331,24 @@ readDoneCallback( void* data, const char* cookie, size_t bufferSize, void* buffe void -AudioOutput::setCurrentSource( MediaStream* stream ) +AudioOutput::setCurrentSource( MediaStream* stream, bool preload ) { - tDebug() << Q_FUNC_INFO; + tDebug() << Q_FUNC_INFO << ", preload = " << preload; - setState( Loading ); + if ( !preload ) + setState( Loading ); - if ( m_vlcMedia != nullptr ) - { - // Ensure playback is stopped, then release media - libvlc_media_player_stop( m_vlcPlayer ); - libvlc_media_release( m_vlcMedia ); - m_vlcMedia = nullptr; + if ( m_vlcPreloadedMedia ) { + libvlc_media_player_stop( m_vlcPreloadedPlayer ); + libvlc_media_release( m_vlcPreloadedMedia ); + m_vlcPreloadedMedia = nullptr; } - if ( m_autoDelete && m_currentStream != nullptr ) + if ( m_preloadedAutoDelete && m_preloadedStream != nullptr ) { - delete m_currentStream; + delete m_preloadedStream; } - m_currentStream = stream; - m_totalTime = 0; - m_currentTime = 0; - m_justSeeked = false; - m_seekable = true; + m_preloadedStream = stream; QByteArray url; switch ( stream->type() ) @@ -314,23 +382,19 @@ AudioOutput::setCurrentSource( MediaStream* stream ) tDebug() << Q_FUNC_INFO << "MediaStream::Final Url:" << url; - m_vlcMedia = libvlc_media_new_location( m_vlcInstance, url.constData() ); - if ( stream->type() == MediaStream::Url ) - { - m_totalTime = libvlc_media_get_duration( m_vlcMedia ); - } - else if ( stream->type() == MediaStream::Stream || stream->type() == MediaStream::IODevice ) + m_vlcPreloadedMedia = libvlc_media_new_location( m_vlcInstance, url.constData() ); + if ( stream->type() == MediaStream::Stream || stream->type() == MediaStream::IODevice ) { QString tempString; - libvlc_media_add_option_flag(m_vlcMedia, "imem-cat=4", libvlc_media_option_trusted); + libvlc_media_add_option_flag(m_vlcPreloadedMedia, "imem-cat=4", libvlc_media_option_trusted); tempString = QString( "imem-data=%1" ).arg( (uintptr_t)stream ); - libvlc_media_add_option_flag(m_vlcMedia, tempString.toLatin1().constData(), libvlc_media_option_trusted); + libvlc_media_add_option_flag(m_vlcPreloadedMedia, tempString.toLatin1().constData(), libvlc_media_option_trusted); tempString = QString( "imem-get=%1" ).arg( (uintptr_t)&readCallback ); - libvlc_media_add_option_flag(m_vlcMedia, tempString.toLatin1().constData(), libvlc_media_option_trusted); + libvlc_media_add_option_flag(m_vlcPreloadedMedia, tempString.toLatin1().constData(), libvlc_media_option_trusted); tempString = QString( "imem-release=%1" ).arg( (uintptr_t)&readDoneCallback ); - libvlc_media_add_option_flag(m_vlcMedia, tempString.toLatin1().constData(), libvlc_media_option_trusted); + libvlc_media_add_option_flag(m_vlcPreloadedMedia, tempString.toLatin1().constData(), libvlc_media_option_trusted); tempString = QString( "imem-seek=%1" ).arg( (uintptr_t)&MediaStream::seekCallback ); - libvlc_media_add_option_flag(m_vlcMedia, tempString.toLatin1().constData(), libvlc_media_option_trusted); + libvlc_media_add_option_flag(m_vlcPreloadedMedia, tempString.toLatin1().constData(), libvlc_media_option_trusted); } if ( qApp->arguments().contains( "--chromecast-ip" ) ) { @@ -346,7 +410,7 @@ AudioOutput::setCurrentSource( MediaStream* stream ) { QString castIP = qApp->arguments().at( qApp->arguments().indexOf( "--chromecast-ip" ) + 1 ); QString sout( ":sout=#transcode{vcodec=none,acodec=vorb,ab=320,channels=2,samplerate=44100}:chromecast{ip=%1,mux=webm}" ); - libvlc_media_add_option( m_vlcMedia, sout.arg( castIP ).toLatin1().constData() ); + libvlc_media_add_option( m_vlcPreloadedMedia, sout.arg( castIP ).toLatin1().constData() ); } else { @@ -354,8 +418,8 @@ AudioOutput::setCurrentSource( MediaStream* stream ) } } - libvlc_event_manager_t* manager = libvlc_media_event_manager( m_vlcMedia ); - libvlc_event_type_t events[] = { + libvlc_event_manager_t* manager = libvlc_media_event_manager( m_vlcPreloadedMedia ); + static libvlc_event_type_t events[] = { libvlc_MediaDurationChanged, }; const int eventCount = sizeof(events) / sizeof( *events ); @@ -364,7 +428,13 @@ AudioOutput::setCurrentSource( MediaStream* stream ) libvlc_event_attach( manager, events[ i ], &AudioOutput::vlcEventCallback, this ); } - libvlc_media_player_set_media( m_vlcPlayer, m_vlcMedia ); + libvlc_media_player_set_media( m_vlcPreloadedPlayer, m_vlcPreloadedMedia ); + + libvlc_audio_set_volume( m_vlcPreloadedPlayer, 0.0 ); + libvlc_media_player_play( m_vlcPreloadedPlayer ); + + if ( !preload ) + switchToPreloadedMedia(); // setState( Stopped ); } @@ -373,7 +443,7 @@ AudioOutput::setCurrentSource( MediaStream* stream ) bool AudioOutput::isInitialized() const { - return m_initialized; + return m_initialized > 1; } @@ -526,7 +596,7 @@ AudioOutput::seek( qint64 milliseconds ) bool AudioOutput::isSeekable() const { - // tDebug() << Q_FUNC_INFO << m_seekable << m_havePosition << m_totalTime << libvlc_media_player_is_seekable( m_vlcPlayer ); + tDebug() << Q_FUNC_INFO << m_seekable << m_havePosition << m_totalTime << libvlc_media_player_is_seekable( m_vlcPlayer ); return m_havePosition || (libvlc_media_player_is_seekable( m_vlcPlayer ) && m_totalTime > 0 ); } @@ -576,64 +646,107 @@ AudioOutput::setVolume( qreal vol ) void AudioOutput::onVlcEvent( const libvlc_event_t* event ) { - switch ( event->type ) + if ( event->p_obj == m_vlcPlayer || event->p_obj == m_vlcMedia ) { - case libvlc_MediaPlayerTimeChanged: - setCurrentTime( event->u.media_player_time_changed.new_time ); - break; - case libvlc_MediaPlayerPositionChanged: - setCurrentPosition( event->u.media_player_position_changed.new_position ); - break; - case libvlc_MediaPlayerSeekableChanged: - // tDebug() << Q_FUNC_INFO << " : seekable changed : " << event->u.media_player_seekable_changed.new_seekable; - break; - case libvlc_MediaDurationChanged: - setTotalTime( event->u.media_duration_changed.new_duration ); - break; - case libvlc_MediaPlayerLengthChanged: - // tDebug() << Q_FUNC_INFO << " : length changed : " << event->u.media_player_length_changed.new_length; - break; - case libvlc_MediaPlayerPlaying: - setState( Playing ); - break; - case libvlc_MediaPlayerPaused: - setState( Paused ); - break; - case libvlc_MediaPlayerEndReached: - setState( Stopped ); - break; - case libvlc_MediaPlayerEncounteredError: - tDebug() << Q_FUNC_INFO << "LibVLC error: MediaPlayerEncounteredError. Stopping"; - // Don't call stop() here - it will deadlock libvlc - setState( Error ); - break; + switch ( event->type ) + { + case libvlc_MediaPlayerTimeChanged: + setCurrentTime( event->u.media_player_time_changed.new_time ); + break; + case libvlc_MediaPlayerPositionChanged: + setCurrentPosition( event->u.media_player_position_changed.new_position ); + break; + case libvlc_MediaPlayerSeekableChanged: + // tDebug() << Q_FUNC_INFO << " : seekable changed : " << event->u.media_player_seekable_changed.new_seekable; + break; + case libvlc_MediaDurationChanged: + setTotalTime( event->u.media_duration_changed.new_duration ); + break; + case libvlc_MediaPlayerLengthChanged: + // tDebug() << Q_FUNC_INFO << " : length changed : " << event->u.media_player_length_changed.new_length; + break; + case libvlc_MediaPlayerPlaying: + setState( Playing ); + break; + case libvlc_MediaPlayerPaused: + setState( Paused ); + break; + case libvlc_MediaPlayerEndReached: + setState( Stopped ); + break; + case libvlc_MediaPlayerEncounteredError: + tDebug() << Q_FUNC_INFO << "LibVLC error: MediaPlayerEncounteredError. Stopping"; + // Don't call stop() here - it will deadlock libvlc + setState( Error ); + break; #if (LIBVLC_VERSION_INT >= LIBVLC_VERSION(2, 2, 2, 0)) - case libvlc_MediaPlayerAudioVolume: - m_volume = event->u.media_player_audio_volume.volume; - emit volumeChanged( volume() ); - break; - case libvlc_MediaPlayerMuted: - m_muted = true; - emit mutedChanged( true ); - break; - case libvlc_MediaPlayerUnmuted: - m_muted = false; - emit mutedChanged( false ); - break; + case libvlc_MediaPlayerAudioVolume: + m_volume = event->u.media_player_audio_volume.volume; + tDebug() << Q_FUNC_INFO << "Got signal in current player that volume changed to:" << m_volume; + emit volumeChanged( volume() ); + break; + case libvlc_MediaPlayerMuted: + m_muted = true; + emit mutedChanged( true ); + break; + case libvlc_MediaPlayerUnmuted: + m_muted = false; + emit mutedChanged( false ); + break; #endif - case libvlc_MediaPlayerNothingSpecial: - case libvlc_MediaPlayerOpening: - case libvlc_MediaPlayerBuffering: - case libvlc_MediaPlayerStopped: - case libvlc_MediaPlayerVout: - case libvlc_MediaPlayerMediaChanged: - case libvlc_MediaPlayerForward: - case libvlc_MediaPlayerBackward: - case libvlc_MediaPlayerPausableChanged: - case libvlc_MediaPlayerTitleChanged: - case libvlc_MediaPlayerSnapshotTaken: - default: - break; + case libvlc_MediaPlayerNothingSpecial: + case libvlc_MediaPlayerOpening: + case libvlc_MediaPlayerBuffering: + case libvlc_MediaPlayerStopped: + case libvlc_MediaPlayerVout: + case libvlc_MediaPlayerMediaChanged: + case libvlc_MediaPlayerForward: + case libvlc_MediaPlayerBackward: + case libvlc_MediaPlayerPausableChanged: + case libvlc_MediaPlayerTitleChanged: + case libvlc_MediaPlayerSnapshotTaken: + default: + break; + } + } + else + { + tDebug() << "Event for preloaded: " << libvlc_event_type_name(event->type); + switch ( event->type ) + { + case libvlc_MediaPlayerPausableChanged: + case libvlc_MediaPlayerPlaying: + case libvlc_MediaPlayerMediaChanged: + case libvlc_MediaPlayerTitleChanged: + case libvlc_MediaPlayerSeekableChanged: + case libvlc_MediaDurationChanged: + case libvlc_MediaPlayerPositionChanged: + case libvlc_MediaPlayerPaused: + case libvlc_MediaPlayerBuffering: + libvlc_media_player_set_pause( m_vlcPreloadedPlayer, 1 ); + libvlc_audio_set_volume( m_vlcPreloadedPlayer, 0.0 ); + libvlc_media_player_set_position( m_vlcPreloadedPlayer, 0.0 ); + break; + case libvlc_MediaPlayerTimeChanged: + case libvlc_MediaPlayerLengthChanged: + case libvlc_MediaPlayerOpening: + case libvlc_MediaPlayerEndReached: + case libvlc_MediaPlayerEncounteredError: + //TODO +#if (LIBVLC_VERSION_INT >= LIBVLC_VERSION(2, 2, 2, 0)) + case libvlc_MediaPlayerAudioVolume: + case libvlc_MediaPlayerMuted: + case libvlc_MediaPlayerUnmuted: +#endif + case libvlc_MediaPlayerNothingSpecial: + case libvlc_MediaPlayerStopped: + case libvlc_MediaPlayerVout: + case libvlc_MediaPlayerForward: + case libvlc_MediaPlayerBackward: + case libvlc_MediaPlayerSnapshotTaken: + default: + break; + } } } diff --git a/src/libtomahawk/audio/AudioOutput.h b/src/libtomahawk/audio/AudioOutput.h index 0e979bdf9..0867972f0 100644 --- a/src/libtomahawk/audio/AudioOutput.h +++ b/src/libtomahawk/audio/AudioOutput.h @@ -49,9 +49,10 @@ public: bool isInitialized() const; AudioState state() const; - void setCurrentSource( const QUrl& stream ); - void setCurrentSource( QIODevice* stream ); - void setCurrentSource( MediaStream* stream ); + void setCurrentSource( const QUrl& stream, bool preload ); + void setCurrentSource( QIODevice* stream, bool preload ); + void setCurrentSource( MediaStream* stream, bool preload ); + void setPreloadedSourceAsCurrent( void ); void play(); void pause(); @@ -65,13 +66,15 @@ public: qreal volume() const; qint64 currentTime() const; qint64 totalTime() const; - void setAutoDelete ( bool ad ); + void setAutoDelete ( bool ad, bool preload ); void setDspCallback( std::function< void( int, int, float*, int, int ) > cb ); static AudioOutput* instance(); libvlc_instance_t* vlcInstance() const; + void switchToPreloadedMedia( void ); + public slots: signals: @@ -90,6 +93,7 @@ private: void setCurrentPosition( float position ); void setTotalTime( qint64 time ); + void onVlcEvent( const libvlc_event_t* event ); static void vlcEventCallback( const libvlc_event_t* event, void* opaque ); static void s_dspCallback( int frameNumber, float* samples, int nb_channels, int nb_samples ); @@ -97,9 +101,11 @@ private: static AudioOutput* s_instance; AudioState m_currentState; MediaStream* m_currentStream; + MediaStream* m_preloadedStream; bool m_seekable; bool m_muted; bool m_autoDelete; + bool m_preloadedAutoDelete; bool m_havePosition; bool m_haveTiming; qreal m_volume; @@ -107,7 +113,7 @@ private: qint64 m_totalTime; bool m_justSeeked; - bool m_initialized; + int m_initialized; QFile m_silenceFile; std::function< void( int state, int frameNumber, float* samples, int nb_channels, int nb_samples ) > dspPluginCallback; @@ -115,6 +121,9 @@ private: libvlc_instance_t* m_vlcInstance; libvlc_media_player_t* m_vlcPlayer; libvlc_media_t* m_vlcMedia; + libvlc_media_player_t* m_vlcBackPlayer; + libvlc_media_player_t* m_vlcPreloadedPlayer; + libvlc_media_t* m_vlcPreloadedMedia; }; #endif // AUDIOOUTPUT_H diff --git a/src/libtomahawk/resolvers/JSAccount.cpp b/src/libtomahawk/resolvers/JSAccount.cpp index 8e81d12a6..6fc51336a 100644 --- a/src/libtomahawk/resolvers/JSAccount.cpp +++ b/src/libtomahawk/resolvers/JSAccount.cpp @@ -190,7 +190,7 @@ JSAccount::reportNativeScriptJobResult( int resultId, const QVariantMap& result .arg( serializeQVariantMap( result ) ); // Remove when new scripting api turned out to work reliably - tDebug( LOGVERBOSE ) << Q_FUNC_INFO << eval; + //tDebug( LOGVERBOSE ) << Q_FUNC_INFO << eval; evaluateJavaScript( eval ); }