diff --git a/CMakeLists.txt b/CMakeLists.txt index b7e005927..137dc78c3 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -346,6 +346,9 @@ endif() macro_log_feature(LIBSNORE_FOUND "Libsnore" "Library for notifications" "https://github.com/TheOneRing/Snorenotify" FALSE "" "") endif() +find_package(LIBVLC REQUIRED 2.1.0) +macro_log_feature(LIBVLC_FOUND "LibVLC" "Provides audio output" TRUE "" "") + set(QXTWEB_FOUND TRUE) set(QXTWEB_LIBRARIES qxtweb-standalone) set(QXTWEB_INCLUDE_DIRS ${THIRDPARTY_DIR}/qxt/qxtweb-standalone/web ${THIRDPARTY_DIR}/qxt/qxtweb-standalone/network ${THIRDPARTY_DIR}/qxt/qxtweb-standalone/core ${CMAKE_CURRENT_BINARY_DIR}) @@ -377,16 +380,7 @@ if (WITH_KDE4) endif(WITH_KDE4) macro_log_feature(KDE4_FOUND "KDE4" "Provides support for configuring Telepathy Accounts from inside Tomahawk" "https://www.kde.org" FALSE "" "") -if(NOT Phonon_FOUND) - macro_optional_find_package(Phonon 4.5.0) -endif() -macro_log_feature(Phonon_FOUND "Phonon" "The Phonon multimedia library" "http://phonon.kde.org" TRUE "" "") - -if(Phonon_FOUND) - message(STATUS "Phonon found; ensure that phonon-vlc is at least 0.4") -endif() - -IF( KDE4_FOUND OR Phonon_FOUND ) +IF( KDE4_FOUND ) IF( CMAKE_C_FLAGS ) # KDE4 adds and removes some compiler flags that we don't like # (only for gcc not for clang e.g.) diff --git a/CMakeModules/FindLIBVLC.cmake b/CMakeModules/FindLIBVLC.cmake new file mode 100644 index 000000000..3e15769dc --- /dev/null +++ b/CMakeModules/FindLIBVLC.cmake @@ -0,0 +1,30 @@ +find_package(PkgConfig QUIET) +pkg_check_modules(PC_LIBVLC QUIET libvlc) +set(LIBVLC_DEFINITIONS ${PC_LIBVLC_CFLAGS_OTHER}) + +find_path(LIBVLC_INCLUDE_DIR vlc/vlc.h + HINTS + ${PC_LIBVLC_INCLUDEDIR} + ${PC_LIBVLC_INCLUDE_DIRS} +) + +find_library(LIBVLC_LIBRARY NAMES vlc libvlc + HINTS + ${PC_LIBVLC_LIBDIR} + ${PC_LIBVLC_LIBRARY_DIRS} +) + +find_library(LIBVLCCORE_LIBRARY NAMES vlccore libvlccore + HINTS + ${PC_LIBVLC_LIBDIR} + ${PC_LIBVLC_LIBRARY_DIRS} +) + +set(LIBVLC_VERSION ${PC_LIBVLC_VERSION}) + +find_package_handle_standard_args(LibVLC + REQUIRED_VARS LIBVLC_LIBRARY LIBVLCCORE_LIBRARY LIBVLC_INCLUDE_DIR + VERSION_VAR LIBVLC_VERSION +) + + diff --git a/CMakeModules/FindPhonon.cmake b/CMakeModules/FindPhonon.cmake deleted file mode 100644 index ff7fd8e49..000000000 --- a/CMakeModules/FindPhonon.cmake +++ /dev/null @@ -1,24 +0,0 @@ -# Find libphonon -# Once done this will define -# -# PHONON_FOUND - system has Phonon Library -# PHONON_INCLUDES - the Phonon include directory -# PHONON_LIBS - link these to use Phonon -# PHONON_VERSION - the version of the Phonon Library - -# Copyright (c) 2008, Matthias Kretz -# -# Redistribution and use is allowed according to the terms of the BSD license. -# For details see the accompanying COPYING-CMAKE-SCRIPTS file. - -include(FindPackageHandleStandardArgs) - -if( TOMAHAWK_QT5 ) - find_package(Phonon4Qt5 NO_MODULE) - set(Phonon_FOUND ${Phonon4Qt5_FOUND}) - set(Phonon_DIR ${Phonon4Qt5_DIR}) -else() - find_package(Phonon NO_MODULE) -endif() - -find_package_handle_standard_args(Phonon DEFAULT_MSG Phonon_DIR ) diff --git a/src/libtomahawk/CMakeLists.txt b/src/libtomahawk/CMakeLists.txt index a6ea0d840..1afa607c3 100644 --- a/src/libtomahawk/CMakeLists.txt +++ b/src/libtomahawk/CMakeLists.txt @@ -214,6 +214,7 @@ list(APPEND libSources accounts/spotify/SpotifyInfoPlugin.cpp audio/AudioEngine.cpp + audio/AudioOutput.cpp collection/Collection.cpp collection/ArtistsRequest.cpp @@ -353,6 +354,7 @@ list(APPEND libSources utils/WeakObjectHash.cpp utils/WeakObjectList.cpp utils/PluginLoader.cpp + utils/MediaStream.cpp ) add_subdirectory( accounts/configstorage ) @@ -389,7 +391,7 @@ include_directories( ${QJSON_INCLUDE_DIR} ${ECHONEST_INCLUDE_DIR} ${LUCENEPP_INCLUDE_DIRS} - ${PHONON_INCLUDES} + ${LIBVLC_INCLUDE_DIR} ${Boost_INCLUDE_DIR} ${LIBPORTFWD_INCLUDE_DIR} @@ -501,7 +503,8 @@ ENDIF( UNIX AND NOT APPLE ) TARGET_LINK_LIBRARIES( tomahawklib LINK_PRIVATE - ${PHONON_LIBRARY} + ${LIBVLC_LIBRARY} + ${LIBVLCCORE_LIBRARY} # Thirdparty shipped with tomahawk ${LIBPORTFWD_LIBRARIES} diff --git a/src/libtomahawk/audio/AudioEngine.cpp b/src/libtomahawk/audio/AudioEngine.cpp index 4efec2257..94ba4a4a4 100644 --- a/src/libtomahawk/audio/AudioEngine.cpp +++ b/src/libtomahawk/audio/AudioEngine.cpp @@ -53,16 +53,16 @@ static QString s_aeInfoIdentifier = QString( "AUDIOENGINE" ); void -AudioEnginePrivate::onStateChanged( Phonon::State newState, Phonon::State oldState ) +AudioEnginePrivate::onStateChanged( AudioOutput::AudioState newState, AudioOutput::AudioState oldState ) { - tDebug( LOGVERBOSE ) << Q_FUNC_INFO << oldState << newState << expectStop << q_ptr->state(); + tDebug() << Q_FUNC_INFO << oldState << newState << expectStop << q_ptr->state(); - if ( newState == Phonon::LoadingState ) + if ( newState == AudioOutput::Loading ) { // We don't emit this state to listeners - yet. state = AudioEngine::Loading; } - if ( newState == Phonon::BufferingState ) + if ( newState == AudioOutput::Buffering ) { if ( underrunCount > UNDERRUNTHRESHOLD && !underrunNotified ) { @@ -72,16 +72,14 @@ AudioEnginePrivate::onStateChanged( Phonon::State newState, Phonon::State oldSta else underrunCount++; } - if ( newState == Phonon::ErrorState ) + if ( newState == AudioOutput::Error ) { q_ptr->stop( AudioEngine::UnknownError ); - - tDebug() << "Phonon Error:" << mediaObject->errorString() << mediaObject->errorType(); - + tDebug() << "AudioOutput Error"; emit q_ptr->error( AudioEngine::UnknownError ); q_ptr->setState( AudioEngine::Error ); } - if ( newState == Phonon::PlayingState ) + if ( newState == AudioOutput::Playing ) { bool emitSignal = false; if ( q_ptr->state() != AudioEngine::Paused && q_ptr->state() != AudioEngine::Playing ) @@ -95,23 +93,22 @@ AudioEnginePrivate::onStateChanged( Phonon::State newState, Phonon::State oldSta if ( emitSignal ) emit q_ptr->started( currentTrack ); } - if ( newState == Phonon::StoppedState && oldState == Phonon::PausedState ) + if ( newState == AudioOutput::Stopped && oldState == AudioOutput::Paused ) { - // GStreamer backend hack: instead of going from PlayingState to StoppedState, it traverses PausedState q_ptr->setState( AudioEngine::Stopped ); } - if ( oldState == Phonon::PlayingState ) + if ( oldState == AudioOutput::Playing ) { bool stopped = false; switch ( newState ) { - case Phonon::PausedState: + case AudioOutput::Paused: { - if ( mediaObject && currentTrack ) + if ( audioOutput && currentTrack ) { - qint64 duration = mediaObject->totalTime() > 0 ? mediaObject->totalTime() : currentTrack->track()->duration() * 1000; - stopped = ( duration - 1000 < mediaObject->currentTime() ); + qint64 duration = audioOutput->totalTime() > 0 ? audioOutput->totalTime() : currentTrack->track()->duration() * 1000; + stopped = ( duration - 1000 < audioOutput->currentTime() ); } else stopped = true; @@ -121,7 +118,7 @@ AudioEnginePrivate::onStateChanged( Phonon::State newState, Phonon::State oldSta break; } - case Phonon::StoppedState: + case AudioOutput::Stopped: { stopped = true; break; @@ -133,7 +130,7 @@ AudioEnginePrivate::onStateChanged( Phonon::State newState, Phonon::State oldSta if ( stopped && expectStop ) { expectStop = false; - tDebug( LOGVERBOSE ) << "Finding next track."; + tDebug() << "Finding next track."; if ( q_ptr->canGoNext() ) { q_ptr->loadNextTrack(); @@ -159,40 +156,6 @@ AudioEnginePrivate::onStateChanged( Phonon::State newState, Phonon::State oldSta } -void -AudioEnginePrivate::onAudioDataArrived( QMap > data ) -{ - QMap< AudioEngine::AudioChannel, QVector< qint16 > > result; - - if( data.contains( Phonon::AudioDataOutput::LeftChannel ) ) - { - result[ AudioEngine::LeftChannel ] = QVector< qint16 >( data[ Phonon::AudioDataOutput::LeftChannel ] ); - } - if( data.contains( Phonon::AudioDataOutput::LeftSurroundChannel ) ) - { - result[ AudioEngine::LeftChannel ] = QVector< qint16 >( data[ Phonon::AudioDataOutput::LeftSurroundChannel ] ); - } - if( data.contains( Phonon::AudioDataOutput::RightChannel ) ) - { - result[ AudioEngine::RightChannel ] = QVector< qint16 >( data[ Phonon::AudioDataOutput::RightChannel ] ); - } - if( data.contains( Phonon::AudioDataOutput::RightSurroundChannel ) ) - { - result[ AudioEngine::LeftChannel ] = QVector< qint16 >( data[ Phonon::AudioDataOutput::RightSurroundChannel ] ); - } - if( data.contains( Phonon::AudioDataOutput::CenterChannel ) ) - { - result[ AudioEngine::LeftChannel ] = QVector< qint16 >( data[ Phonon::AudioDataOutput::CenterChannel ] ); - } - if( data.contains( Phonon::AudioDataOutput::SubwooferChannel ) ) - { - result[ AudioEngine::LeftChannel ] = QVector< qint16 >( data[ Phonon::AudioDataOutput::SubwooferChannel ] ); - } - - s_instance->audioDataArrived( result ); -} - - AudioEngine* AudioEnginePrivate::s_instance = 0; @@ -214,31 +177,20 @@ AudioEngine::AudioEngine() d->waitingOnNewTrack = false; d->state = Stopped; d->coverTempFile = 0; - d->audioEffect = 0; d->s_instance = this; tDebug() << "Init AudioEngine"; - qRegisterMetaType< AudioErrorCode >("AudioErrorCode"); - qRegisterMetaType< AudioState >("AudioState"); + d->audioOutput = new AudioOutput(this); - d->mediaObject = new Phonon::MediaObject( this ); - d->audioOutput = new Phonon::AudioOutput( Phonon::MusicCategory, this ); - d->audioDataOutput = new Phonon::AudioDataOutput( this ); + connect( d->audioOutput, SIGNAL( stateChanged( AudioOutput::AudioState, AudioOutput::AudioState ) ), d_func(), SLOT( onStateChanged( AudioOutput::AudioState, AudioOutput::AudioState ) ) ); + connect( d->audioOutput, SIGNAL( tick( qint64 ) ), SLOT( timerTriggered( qint64 ) ) ); + connect( d->audioOutput, SIGNAL( aboutToFinish() ), SLOT( onAboutToFinish() ) ); - d->audioPath = Phonon::createPath( d->mediaObject, d->audioOutput ); - - d->mediaObject->setTickInterval( 150 ); - connect( d->mediaObject, SIGNAL( stateChanged( Phonon::State, Phonon::State ) ), d_func(), SLOT( onStateChanged( Phonon::State, Phonon::State ) ) ); - connect( d->mediaObject, SIGNAL( tick( qint64 ) ), SLOT( timerTriggered( qint64 ) ) ); - connect( d->mediaObject, SIGNAL( aboutToFinish() ), SLOT( onAboutToFinish() ) ); - connect( d->audioOutput, SIGNAL( volumeChanged( qreal ) ), SLOT( onVolumeChanged( qreal ) ) ); - connect( d->audioOutput, SIGNAL( mutedChanged( bool ) ), SIGNAL( mutedChanged( bool ) ) ); - - onVolumeChanged( d->audioOutput->volume() ); setVolume( TomahawkSettings::instance()->volume() ); - // initEqualizer(); + qRegisterMetaType< AudioErrorCode >("AudioErrorCode"); + qRegisterMetaType< AudioState >("AudioState"); } @@ -246,29 +198,12 @@ AudioEngine::~AudioEngine() { tDebug() << Q_FUNC_INFO; - d_func()->mediaObject->stop(); TomahawkSettings::instance()->setVolume( volume() ); - delete d_ptr; } -QStringList -AudioEngine::supportedMimeTypes() const -{ - if ( d_func()->supportedMimeTypes.isEmpty() ) - { - d_func()->supportedMimeTypes = Phonon::BackendCapabilities::availableMimeTypes(); - d_func()->supportedMimeTypes << "audio/basic"; - - return d_func()->supportedMimeTypes; - } - else - return d_func()->supportedMimeTypes; -} - - void AudioEngine::playPause() { @@ -301,7 +236,7 @@ AudioEngine::play() if ( isPaused() ) { - d->mediaObject->play(); + d->audioOutput->play(); emit resumed(); sendNowPlayingNotification( Tomahawk::InfoSystem::InfoNowResumed ); @@ -331,7 +266,7 @@ AudioEngine::pause() tDebug( LOGEXTRA ) << Q_FUNC_INFO; - d->mediaObject->pause(); + d->audioOutput->pause(); emit paused(); Tomahawk::InfoSystem::InfoSystem::instance()->pushInfo( Tomahawk::InfoSystem::InfoPushData( s_aeInfoIdentifier, Tomahawk::InfoSystem::InfoNowPaused, QVariant(), Tomahawk::InfoSystem::PushNoFlag ) ); @@ -359,8 +294,8 @@ AudioEngine::stop( AudioErrorCode errorCode ) else setState( Error ); - if ( d->mediaObject->state() != Phonon::StoppedState ) - d->mediaObject->stop(); + if ( d->audioOutput->state() != AudioOutput::Stopped ) + d->audioOutput->stop(); emit stopped(); @@ -379,32 +314,6 @@ AudioEngine::stop( AudioErrorCode errorCode ) } -bool AudioEngine::activateDataOutput() -{ - Q_D( AudioEngine ); - - d->audioDataPath = Phonon::createPath( d->mediaObject, d->audioDataOutput ); - connect( d->audioDataOutput, SIGNAL( dataReady( QMap< Phonon::AudioDataOutput::Channel, QVector< qint16 > > ) ), - d_func(), SLOT( onAudioDataArrived( QMap< Phonon::AudioDataOutput::Channel, QVector< qint16 > > ) ) ); - - return d->audioDataPath.isValid(); - -} - - -bool AudioEngine::deactivateDataOutput() -{ - Q_D( AudioEngine ); - - return d->audioDataPath.disconnect(); -} - -void AudioEngine::audioDataArrived( QMap< AudioEngine::AudioChannel, QVector< qint16 > >& data ) -{ - emit audioDataReady( data ); -} - - void AudioEngine::previous() { @@ -492,15 +401,11 @@ AudioEngine::canSeek() { Q_D( AudioEngine ); - bool phononCanSeek = true; - /* TODO: When phonon properly reports this, re-enable it - if ( d->mediaObject && d->mediaObject->isValid() ) - phononCanSeek = d->mediaObject->isSeekable(); - */ - if ( d->playlist.isNull() ) - return phononCanSeek; + if ( !d->audioOutput->isSeekable() ) { + return false; + } - return !d->playlist.isNull() && ( d->playlist.data()->seekRestrictions() != PlaylistModes::NoSeek ) && phononCanSeek; + return !d->playlist.isNull() && ( d->playlist.data()->seekRestrictions() != PlaylistModes::NoSeek ); } @@ -509,16 +414,16 @@ AudioEngine::seek( qint64 ms ) { Q_D( AudioEngine ); - if ( !canSeek() ) + /*if ( !canSeek() ) { tDebug( LOGEXTRA ) << "Could not seek!"; return; - } + }*/ if ( isPlaying() || isPaused() ) { tDebug( LOGVERBOSE ) << Q_FUNC_INFO << ms; - d->mediaObject->seek( ms ); + d->audioOutput->seek( ms ); emit seeked( ms ); } } @@ -543,6 +448,7 @@ AudioEngine::setVolume( int percentage ) if ( percentage > 0 && d->audioOutput->isMuted() ) d->audioOutput->setMuted( false ); + emit volumeChanged( percentage ); } @@ -767,18 +673,18 @@ AudioEngine::performLoadTrack( const Tomahawk::result_ptr result, const QString QSharedPointer qnr = io.objectCast(); if ( !qnr.isNull() ) { - d->mediaObject->setCurrentSource( new QNR_IODeviceStream( qnr, this ) ); + d->audioOutput->setCurrentSource( new QNR_IODeviceStream( qnr, this ) ); // We keep track of the QNetworkReply in QNR_IODeviceStream // and Phonon handles the deletion of the // QNR_IODeviceStream object ioToKeep.clear(); - d->mediaObject->currentSource().setAutoDelete( true ); + d->audioOutput->setAutoDelete( true ); } else { - d->mediaObject->setCurrentSource( io.data() ); + d->audioOutput->setCurrentSource( io.data() ); // We handle the deletion via tracking in d->input - d->mediaObject->currentSource().setAutoDelete( false ); + d->audioOutput->setAutoDelete( false ); } } else @@ -797,7 +703,7 @@ AudioEngine::performLoadTrack( const Tomahawk::result_ptr result, const QString } tLog( LOGVERBOSE ) << "Passing to Phonon:" << furl; - d->mediaObject->setCurrentSource( furl ); + d->audioOutput->setCurrentSource( furl ); } else { @@ -806,10 +712,10 @@ AudioEngine::performLoadTrack( const Tomahawk::result_ptr result, const QString furl = furl.right( furl.length() - 7 ); tLog( LOGVERBOSE ) << "Passing to Phonon:" << QUrl::fromLocalFile( furl ); - d->mediaObject->setCurrentSource( QUrl::fromLocalFile( furl ) ); + d->audioOutput->setCurrentSource( QUrl::fromLocalFile( furl ) ); } - d->mediaObject->currentSource().setAutoDelete( true ); + d->audioOutput->setAutoDelete( true ); } if ( !d->input.isNull() ) @@ -818,7 +724,7 @@ AudioEngine::performLoadTrack( const Tomahawk::result_ptr result, const QString d->input.clear(); } d->input = ioToKeep; - d->mediaObject->play(); + d->audioOutput->play(); if ( TomahawkSettings::instance()->privateListeningMode() != TomahawkSettings::FullyPrivate ) { @@ -1291,14 +1197,23 @@ AudioEngine::setState( AudioState state ) qint64 AudioEngine::currentTime() const { - return d_func()->mediaObject->currentTime(); + return d_func()->audioOutput->currentTime(); } qint64 AudioEngine::currentTrackTotalTime() const { - return d_func()->mediaObject->totalTime(); + Q_D( const AudioEngine ); + + // FIXME : This is too hacky. The problem is that I don't know why + // libVLC doesn't report total duration for stream data (imem://) + // But it's not a real problem for playback, since EndOfStream is emitted by libVLC itself + // This value is only used by AudioOutput to evaluate if it's close to end of stream + if ( d->audioOutput->totalTime() <= 0 && d->currentTrack && d->currentTrack->track() ) { + return d->currentTrack->track()->duration() * 1000 + 1000; + } + return d->audioOutput->totalTime(); } @@ -1386,52 +1301,9 @@ AudioEngine::setCurrentTrackPlaylist( const playlistinterface_ptr& playlist ) void -AudioEngine::initEqualizer() +AudioEngine::setDspCallback( std::function< void( int state, int frameNumber, float* samples, int nb_channels, int nb_samples ) > cb ) { Q_D( AudioEngine ); - QList< Phonon::EffectDescription > effectDescriptions = Phonon::BackendCapabilities::availableAudioEffects(); - foreach ( Phonon::EffectDescription effectDesc, effectDescriptions ) - { - if ( effectDesc.name().toLower().contains( "eq" ) ) - { - d->audioEffect = new Phonon::Effect( effectDesc ); - d->audioPath.insertEffect( d->audioEffect ); - break; - } - } -} - - -int -AudioEngine::equalizerBandCount() -{ - Q_D( AudioEngine ); - - if ( d->audioEffect ) - { - QList< Phonon::EffectParameter > params = d->audioEffect->parameters(); - return params.size(); - } - - return 0; -} - - -bool -AudioEngine::setEqualizerBand( int band, int value ) -{ - Q_D( AudioEngine ); - - if ( d->audioEffect ) - { - QList< Phonon::EffectParameter > params = d->audioEffect->parameters(); - if ( band < params.size() ) - { - d->audioEffect->setParameterValue( params.at( band ), value ); - return true; - } - } - - return false; + d->audioOutput->setDspCallback( cb ); } diff --git a/src/libtomahawk/audio/AudioEngine.h b/src/libtomahawk/audio/AudioEngine.h index d8363334e..84058efed 100644 --- a/src/libtomahawk/audio/AudioEngine.h +++ b/src/libtomahawk/audio/AudioEngine.h @@ -24,6 +24,7 @@ #include "../Typedefs.h" #include +#include #include "DllMacro.h" @@ -109,8 +110,7 @@ public: */ qint64 currentTrackTotalTime() const; - int equalizerBandCount(); - bool setEqualizerBand( int band, int value ); + void setDspCallback( std::function< void( int state, int frameNumber, float* samples, int nb_channels, int nb_samples ) > cb ); public slots: void playPause(); @@ -118,9 +118,6 @@ public slots: void pause(); void stop( AudioErrorCode errorCode = NoError ); - bool activateDataOutput(); - bool deactivateDataOutput(); - void previous(); void next(); @@ -157,7 +154,7 @@ signals: void paused(); void resumed(); - void audioDataReady( QMap< AudioEngine::AudioChannel, QVector > data ); +// void audioDataReady( QMap< AudioEngine::AudioChannel, QVector > data ); void stopAfterTrackChanged(); @@ -201,8 +198,7 @@ private: void setState( AudioState state ); void setCurrentTrackPlaylist( const Tomahawk::playlistinterface_ptr& playlist ); - void initEqualizer(); - void audioDataArrived( QMap< AudioEngine::AudioChannel, QVector< qint16 > >& data ); +// void audioDataArrived( QMap< AudioEngine::AudioChannel, QVector< qint16 > >& data ); Q_DECLARE_PRIVATE( AudioEngine ) diff --git a/src/libtomahawk/audio/AudioEngine_p.h b/src/libtomahawk/audio/AudioEngine_p.h index cfe1f3695..824b85a53 100644 --- a/src/libtomahawk/audio/AudioEngine_p.h +++ b/src/libtomahawk/audio/AudioEngine_p.h @@ -1,11 +1,4 @@ - -#include -#include -#include -#include -#include -#include -#include +#include "AudioOutput.h" #include @@ -30,8 +23,7 @@ public: public slots: - void onStateChanged( Phonon::State newState, Phonon::State oldState ); - void onAudioDataArrived( QMap< Phonon::AudioDataOutput::Channel, QVector< qint16 > > data ); + void onStateChanged( AudioOutput::AudioState newState, AudioOutput::AudioState oldState ); private: QSharedPointer input; @@ -42,22 +34,12 @@ private: Tomahawk::playlistinterface_ptr currentTrackPlaylist; Tomahawk::playlistinterface_ptr queue; - Phonon::MediaObject* mediaObject; - Phonon::AudioOutput* audioOutput; - - Phonon::Path audioPath; - Phonon::Effect* audioEffect; - - Phonon::AudioDataOutput* audioDataOutput; - Phonon::Path audioDataPath; - + AudioOutput* audioOutput; unsigned int timeElapsed; bool expectStop; bool waitingOnNewTrack; - mutable QStringList supportedMimeTypes; - AudioState state; QQueue< AudioState > stateQueue; QTimer stateQueueTimer; diff --git a/src/libtomahawk/audio/AudioOutput.cpp b/src/libtomahawk/audio/AudioOutput.cpp new file mode 100644 index 000000000..f6c63fe48 --- /dev/null +++ b/src/libtomahawk/audio/AudioOutput.cpp @@ -0,0 +1,525 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2014, Christian Muehlhaeuser + * Copyright 2010-2012, Jeff Mitchell + * Copyright 2013, Teo Mrnjavac + * Copyright 2014, Adrien Aubry + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + +#include "AudioEngine.h" +#include "AudioOutput.h" + +#include "utils/Logger.h" + +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +static QString s_aeInfoIdentifier = QString( "AUDIOOUTPUT" ); + +static const int ABOUT_TO_FINISH_TIME = 2000; + +AudioOutput* AudioOutput::s_instance = 0; + + +AudioOutput* +AudioOutput::instance() +{ + return AudioOutput::s_instance; +} + + +AudioOutput::AudioOutput( QObject* parent ) + : QObject( parent ) + , currentState( Stopped ) + , currentStream( nullptr ) + , seekable( true ) + , muted( false ) + , m_autoDelete ( true ) + , m_volume( 1.0 ) + , m_currentTime( 0 ) + , m_totalTime( 0 ) + , m_aboutToFinish( false ) + , m_justSeeked( false ) + , dspPluginCallback( nullptr ) + , vlcInstance( nullptr ) + , vlcPlayer( nullptr ) + , vlcMedia( nullptr ) +{ + tDebug() << Q_FUNC_INFO; + + AudioOutput::s_instance = this; + + qRegisterMetaType("AudioOutput::AudioState"); + + const char* vlcArgs[] = { + "--ignore-config", + "--extraintf=logger", + qApp->arguments().contains( "--verbose" ) ? "--verbose=3" : "", + // "--no-plugins-cache", + // "--no-media-library", + // "--no-osd", + // "--no-stats", + // "--no-video-title-show", + // "--no-snapshot-preview", + // "--services-discovery=''", + "--no-video", + "--no-xlib" + }; + + // Create and initialize a libvlc instance (it should be done only once) + vlcInstance = libvlc_new( sizeof(vlcArgs) / sizeof(*vlcArgs), vlcArgs ); + if ( !vlcInstance ) { + tDebug() << "libVLC: could not initialize"; + } + + + vlcPlayer = libvlc_media_player_new( vlcInstance ); + + libvlc_event_manager_t* manager = libvlc_media_player_event_manager( 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, + libvlc_MediaPlayerVout + }; + const int eventCount = sizeof(events) / sizeof( *events ); + for ( int i = 0 ; i < eventCount ; i++ ) { + libvlc_event_attach( manager, events[ i ], &AudioOutput::vlcEventCallback, this ); + } + + tDebug() << "AudioOutput::AudioOutput OK !\n"; +} + + +AudioOutput::~AudioOutput() +{ + tDebug() << Q_FUNC_INFO; + + if ( vlcPlayer != nullptr ) { + libvlc_media_player_stop( vlcPlayer ); + libvlc_media_player_release( vlcPlayer ); + vlcPlayer = nullptr; + } + if ( vlcMedia != nullptr ) { + libvlc_media_release( vlcMedia ); + vlcMedia = nullptr; + } + if ( vlcInstance != nullptr ) { + libvlc_release( vlcInstance ); + } +} + + +void +AudioOutput::setAutoDelete ( bool ad ) +{ + m_autoDelete = ad; +} + + +void +AudioOutput::setCurrentSource( const QUrl& stream ) +{ + setCurrentSource( new MediaStream( stream ) ); +} + + +void +AudioOutput::setCurrentSource( QIODevice* stream ) +{ + setCurrentSource( new MediaStream( stream ) ); +} + + +void +AudioOutput::setCurrentSource( MediaStream* stream ) +{ + tDebug() << Q_FUNC_INFO; + + setState(Loading); + + if ( vlcMedia != nullptr ) { + // Ensure playback is stopped, then release media + libvlc_media_player_stop( vlcPlayer ); + libvlc_media_release( vlcMedia ); + vlcMedia = nullptr; + } + if ( m_autoDelete && currentStream != nullptr ) { + delete currentStream; + } + + currentStream = stream; + m_totalTime = 0; + m_currentTime = 0; + m_justSeeked = false; + seekable = true; + + QByteArray url; + switch (stream->type()) { + case MediaStream::Unknown: + tDebug() << "MediaStream Type is Invalid:" << stream->type(); + break; + + case MediaStream::Empty: + tDebug() << "MediaStream is empty."; + break; + + case MediaStream::Url: + tDebug() << "MediaStream::Url:" << stream->url(); + if ( stream->url().scheme().isEmpty() ) { + url = "file:///"; + if ( stream->url().isRelative() ) { + url.append( QFile::encodeName( QDir::currentPath() ) + '/' ); + } + } + url += stream->url().toEncoded(); + break; + + case MediaStream::Stream: + case MediaStream::IODevice: + url = QByteArray( "imem://" ); + break; + } + + tDebug() << "MediaStream::Final Url:" << url; + + + vlcMedia = libvlc_media_new_location( vlcInstance, url.constData() ); + + libvlc_event_manager_t* manager = libvlc_media_event_manager( vlcMedia ); + libvlc_event_type_t events[] = { + libvlc_MediaDurationChanged, + }; + const int eventCount = sizeof(events) / sizeof( *events ); + for ( int i = 0 ; i < eventCount ; i++ ) { + libvlc_event_attach( manager, events[ i ], &AudioOutput::vlcEventCallback, this ); + } + + libvlc_media_player_set_media( vlcPlayer, vlcMedia ); + + if ( stream->type() == MediaStream::Url ) + { + m_totalTime = libvlc_media_get_duration( vlcMedia ); + } + else if ( stream->type() == MediaStream::Stream || stream->type() == MediaStream::IODevice ) + { + libvlc_media_add_option_flag(vlcMedia, "imem-cat=4", libvlc_media_option_trusted); + const char* imemData = QString( "imem-data=%1" ).arg( (uintptr_t)stream ).toLatin1().constData(); + libvlc_media_add_option_flag(vlcMedia, imemData, libvlc_media_option_trusted); + const char* imemGet = QString( "imem-get=%1" ).arg( (uintptr_t)&MediaStream::readCallback ).toLatin1().constData(); + libvlc_media_add_option_flag(vlcMedia, imemGet, libvlc_media_option_trusted); + const char* imemRelease = QString( "imem-release=%1" ).arg( (uintptr_t)&MediaStream::readDoneCallback ).toLatin1().constData(); + libvlc_media_add_option_flag(vlcMedia, imemRelease, libvlc_media_option_trusted); + const char* imemSeek = QString( "imem-seek=%1" ).arg( (uintptr_t)&MediaStream::seekCallback ).toLatin1().constData(); + libvlc_media_add_option_flag(vlcMedia, imemSeek, libvlc_media_option_trusted); + } + + m_aboutToFinish = false; + setState(Stopped); +} + + +AudioOutput::AudioState +AudioOutput::state() +{ + tDebug() << Q_FUNC_INFO; + return currentState; +} + + +void +AudioOutput::setState( AudioState state ) +{ + tDebug() << Q_FUNC_INFO; + AudioState last = currentState; + currentState = state; + emit stateChanged ( state, last ); +} + + +qint64 +AudioOutput::currentTime() +{ + return m_currentTime; +} + + +void +AudioOutput::setCurrentTime( qint64 time ) +{ + // FIXME : This is a bit hacky, but m_totalTime is only used to determine + // if we are about to finish + if ( m_totalTime == 0 ) { + m_totalTime = AudioEngine::instance()->currentTrackTotalTime(); + seekable = true; + } + + m_currentTime = time; + emit tick( time ); + +// tDebug() << "Current time : " << m_currentTime << " / " << m_totalTime; + + // FIXME pt 2 : we use temporary variable to avoid overriding m_totalTime + // in the case it is < 0 (which means that the media is not seekable) + qint64 total = m_totalTime; + if ( total <= 0 ) { + total = AudioEngine::instance()->currentTrackTotalTime(); + } + + if ( time < total - ABOUT_TO_FINISH_TIME ) { + m_aboutToFinish = false; + } + if ( !m_aboutToFinish && total > 0 && time >= total - ABOUT_TO_FINISH_TIME ) { + m_aboutToFinish = true; + emit aboutToFinish(); + } +} + + +qint64 +AudioOutput::totalTime() +{ + return m_totalTime; +} + + +void +AudioOutput::setTotalTime( qint64 time ) +{ + tDebug() << Q_FUNC_INFO << " " << time; + + if ( time <= 0 ) { + seekable = false; + } else { + m_totalTime = time; + seekable = true; + // emit current time to refresh total time + emit tick( time ); + } +} + + +void +AudioOutput::play() +{ + tDebug() << Q_FUNC_INFO; + + if ( libvlc_media_player_is_playing ( vlcPlayer ) ) { + libvlc_media_player_set_pause ( vlcPlayer, 0 ); + } else { + libvlc_media_player_play ( vlcPlayer ); + } + + setState( Playing ); +} + + +void +AudioOutput::pause() +{ + tDebug() << Q_FUNC_INFO; + + libvlc_media_player_set_pause ( vlcPlayer, 1 ); + + setState( Paused ); +} + + +void +AudioOutput::stop() +{ + tDebug() << Q_FUNC_INFO; + libvlc_media_player_stop ( vlcPlayer ); + + setState( Stopped ); +} + + +void +AudioOutput::seek( qint64 milliseconds ) +{ + tDebug() << Q_FUNC_INFO; + + // Even seek if reported as not seekable. VLC can seek in some cases where + // it tells us it can't. + // if ( !seekable ) { + // return; + // } + + switch ( currentState ) { + case Playing: + case Paused: + case Loading: + case Buffering: + break; + default: + return; + } + +// tDebug() << "AudioOutput:: seeking" << milliseconds << "msec"; + + m_justSeeked = true; + libvlc_media_player_set_time ( vlcPlayer, milliseconds ); + setCurrentTime( milliseconds ); +} + + +bool +AudioOutput::isSeekable() +{ + tDebug() << Q_FUNC_INFO; + + return seekable; +} + + +bool +AudioOutput::isMuted() +{ + tDebug() << Q_FUNC_INFO; + + return muted; +} + + +void +AudioOutput::setMuted(bool m) +{ + tDebug() << Q_FUNC_INFO; + + muted = m; + if ( muted == true ) { + libvlc_audio_set_volume( vlcPlayer, 0 ); + } else { + libvlc_audio_set_volume( vlcPlayer, m_volume * 100.0 ); + } +} + + +qreal +AudioOutput::volume() +{ + tDebug() << Q_FUNC_INFO; + + return muted ? 0 : m_volume; +} + + +void +AudioOutput::setVolume(qreal vol) +{ + tDebug() << Q_FUNC_INFO; + + m_volume = vol; + if ( !muted ) { + libvlc_audio_set_volume( vlcPlayer, m_volume * 100.0 ); + } +} + + +void +AudioOutput::vlcEventCallback( const libvlc_event_t* event, void* opaque ) +{ +// tDebug() << Q_FUNC_INFO; + + AudioOutput* that = reinterpret_cast < AudioOutput * > ( opaque ); + Q_ASSERT( that ); + + switch (event->type) { + case libvlc_MediaPlayerTimeChanged: + that->setCurrentTime( event->u.media_player_time_changed.new_time ); + break; + case libvlc_MediaPlayerSeekableChanged: + // tDebug() << Q_FUNC_INFO << " : seekable changed : " << event->u.media_player_seekable_changed.new_seekable; + break; + case libvlc_MediaDurationChanged: + that->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_MediaPlayerNothingSpecial: + case libvlc_MediaPlayerOpening: + case libvlc_MediaPlayerBuffering: + case libvlc_MediaPlayerPlaying: + case libvlc_MediaPlayerPaused: + case libvlc_MediaPlayerStopped: + break; + case libvlc_MediaPlayerEndReached: + that->setState(Stopped); + break; + case libvlc_MediaPlayerEncounteredError: + tDebug() << "LibVLC error : MediaPlayerEncounteredError. Stopping"; + if ( that->vlcPlayer != 0 ) { + that->stop(); + } + that->setState( Error ); + break; + case libvlc_MediaPlayerVout: + case libvlc_MediaPlayerMediaChanged: + case libvlc_MediaPlayerForward: + case libvlc_MediaPlayerBackward: + case libvlc_MediaPlayerPositionChanged: + case libvlc_MediaPlayerPausableChanged: + case libvlc_MediaPlayerTitleChanged: + case libvlc_MediaPlayerSnapshotTaken: + default: + break; + } +} + + +void +AudioOutput::s_dspCallback( int frameNumber, float* samples, int nb_channels, int nb_samples ) +{ +// tDebug() << Q_FUNC_INFO; + + int state = AudioOutput::instance()->m_justSeeked ? 1 : 0; + AudioOutput::instance()->m_justSeeked = false; + if ( AudioOutput::instance()->dspPluginCallback ) { + AudioOutput::instance()->dspPluginCallback( state, frameNumber, samples, nb_channels, nb_samples ); + } +} + + +void +AudioOutput::setDspCallback( std::function< void( int, int, float*, int, int ) > cb ) +{ + dspPluginCallback = cb; +} diff --git a/src/libtomahawk/audio/AudioOutput.h b/src/libtomahawk/audio/AudioOutput.h new file mode 100644 index 000000000..3fbb70742 --- /dev/null +++ b/src/libtomahawk/audio/AudioOutput.h @@ -0,0 +1,104 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2014, Christian Muehlhaeuser + * Copyright 2010-2012, Jeff Mitchell + * Copyright 2013, Teo Mrnjavac + * Copyright 2014, Adrien Aubry + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + +#ifndef AUDIOOUTPUT_H +#define AUDIOOUTPUT_H + +#include "DllMacro.h" +#include "Typedefs.h" +#include "utils/MediaStream.h" + +#include + +struct libvlc_instance_t; +struct libvlc_media_player_t; +struct libvlc_media_t; +struct libvlc_event_t; + +class DLLEXPORT AudioOutput : public QObject +{ +Q_OBJECT + +public: + enum AudioState { Stopped = 0, Playing = 1, Paused = 2, Error = 3, Loading = 4, Buffering = 5 }; + + explicit AudioOutput( QObject* parent = nullptr ); + ~AudioOutput(); + + AudioState state(); + + void setCurrentSource( const QUrl& stream ); + void setCurrentSource( QIODevice* stream ); + void setCurrentSource( MediaStream* stream ); + + void play(); + void pause(); + void stop(); + void seek( qint64 milliseconds ); + + bool isSeekable(); + bool isMuted(); + void setMuted( bool m ); + void setVolume( qreal vol ); + qreal volume(); + qint64 currentTime(); + qint64 totalTime(); + void setAutoDelete ( bool ad ); + + void setDspCallback( std::function< void( int, int, float*, int, int ) > cb ); + + static AudioOutput* instance(); + +public slots: + +signals: + void stateChanged( AudioOutput::AudioState, AudioOutput::AudioState ); + void tick( qint64 ); + void aboutToFinish(); + +private: + void setState( AudioState state ); + void setCurrentTime( qint64 time ); + void setTotalTime( qint64 time ); + + static void vlcEventCallback( const libvlc_event_t *event, void *opaque ); + static void s_dspCallback( int frameNumber, float* samples, int nb_channels, int nb_samples ); + + static AudioOutput* s_instance; + AudioState currentState; + MediaStream* currentStream; + bool seekable; + bool muted; + bool m_autoDelete; + qreal m_volume; + qint64 m_currentTime; + qint64 m_totalTime; + bool m_aboutToFinish; + bool m_justSeeked; + + std::function< void( int state, int frameNumber, float* samples, int nb_channels, int nb_samples ) > dspPluginCallback; + + libvlc_instance_t* vlcInstance; + libvlc_media_player_t* vlcPlayer; + libvlc_media_t* vlcMedia; +}; + +#endif // AUDIOOUTPUT_H diff --git a/src/libtomahawk/utils/MediaStream.cpp b/src/libtomahawk/utils/MediaStream.cpp new file mode 100644 index 000000000..3827656b9 --- /dev/null +++ b/src/libtomahawk/utils/MediaStream.cpp @@ -0,0 +1,205 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2014, Christian Muehlhaeuser + * Copyright 2010-2012, Jeff Mitchell + * Copyright 2013, Teo Mrnjavac + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + +#include "MediaStream.h" + +#include "utils/Logger.h" + +#define BLOCK_SIZE 1048576 + +static QString s_aeInfoIdentifier = QString( "MEDIASTREAM" ); + + +MediaStream::MediaStream( QObject* parent ) + : QObject( parent ) + , m_type( Unknown ) + , m_ioDevice ( nullptr ) + , m_started( false ) + , m_bufferingFinished( false ) + , m_eos( false ) + , m_pos( 0 ) + , m_streamSize( 0 ) +{ + tDebug() << Q_FUNC_INFO; +} + + +MediaStream::MediaStream( const QUrl &url ) + : QObject( 0 ) + , m_type( Url ) + , m_url( url ) + , m_ioDevice ( nullptr ) + , m_started( false ) + , m_bufferingFinished( false ) + , m_eos( false ) + , m_pos( 0 ) + , m_streamSize( 0 ) +{ + tDebug() << Q_FUNC_INFO; +} + + +MediaStream::MediaStream( QIODevice* device ) + : QObject( 0 ) + , m_type( IODevice ) + , m_ioDevice ( device ) + , m_started( false ) + , m_bufferingFinished( false ) + , m_eos( false ) + , m_pos( 0 ) + , m_streamSize( 0 ) +{ + tDebug() << Q_FUNC_INFO; + + QObject::connect( m_ioDevice, SIGNAL( readChannelFinished() ), this, SLOT( bufferingFinished() ) ); +} + + +MediaStream::~MediaStream() +{ + tDebug() << Q_FUNC_INFO; +} + + +MediaStream::MediaType +MediaStream::type() +{ + tDebug() << Q_FUNC_INFO; + return m_type; +} + + +QUrl +MediaStream::url() +{ + tDebug() << Q_FUNC_INFO; + return m_url; +} + + +qint64 +MediaStream::streamSize() +{ + tDebug() << Q_FUNC_INFO; + + return m_streamSize; +} + + +void +MediaStream::setStreamSize( qint64 size ) +{ + tDebug() << Q_FUNC_INFO; + + m_streamSize = size; +} + + +void +MediaStream::endOfData() +{ + tDebug() << Q_FUNC_INFO; + + m_eos = true; +} + + +void MediaStream::bufferingFinished() +{ + tDebug() << Q_FUNC_INFO; + + m_bufferingFinished = true; +} + + +int +MediaStream::readCallback ( void* data, const char* cookie, int64_t* dts, int64_t* pts, unsigned* flags, size_t* bufferSize, void** buffer ) +{ + Q_UNUSED(cookie); + Q_UNUSED(dts); + Q_UNUSED(pts); + Q_UNUSED(flags); + + MediaStream* that = static_cast < MediaStream * > ( data ); + qint64 bufsize = 0; + *bufferSize = 0; + + if ( that->m_eos == true ) { + return -1; + } + + if ( that->m_type == Stream ) { + bufsize = that->needData(buffer); + } + else if ( that->m_type == IODevice ) { + bufsize = that->m_ioDevice->read( that->m_buffer, BLOCK_SIZE ); + *buffer = that->m_buffer; + } + + if ( bufsize > 0 ) { + that->m_started = true; + } + if ( that->m_type == IODevice && bufsize == 0 && that->m_started && that->m_bufferingFinished == true ) { + that->m_eos = true; + return -1; + } + if ( bufsize < 0 ) { + that->m_eos = true; + return -1; + } + + *bufferSize = bufsize; + return 0; +} + + +int +MediaStream::readDoneCallback ( void *data, const char *cookie, size_t bufferSize, void *buffer ) +{ + Q_UNUSED(cookie); + Q_UNUSED(bufferSize); + + MediaStream* that = static_cast < MediaStream * > ( data ); + + if ( ( that->m_type == Stream ) && buffer != 0 && bufferSize > 0 ) { + delete static_cast< char* >( buffer ); + } + + return 0; +} + + +int +MediaStream::seekCallback ( void *data, const uint64_t pos ) +{ + MediaStream* that = static_cast < MediaStream * > ( data ); + + if ( that->m_type == Stream && static_cast < int64_t > ( pos ) > that->streamSize() ) { + return -1; + } + + that->m_started = false; + that->m_pos = pos; + if ( that->m_type == IODevice ) { + that->m_ioDevice->seek(pos); + } + + return 0; +} diff --git a/src/libtomahawk/utils/MediaStream.h b/src/libtomahawk/utils/MediaStream.h new file mode 100644 index 000000000..a893e6cb9 --- /dev/null +++ b/src/libtomahawk/utils/MediaStream.h @@ -0,0 +1,77 @@ +/* === This file is part of Tomahawk Player - === + * + * Copyright 2010-2014, Christian Muehlhaeuser + * Copyright 2010-2012, Jeff Mitchell + * Copyright 2013, Teo Mrnjavac + * + * Tomahawk is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * Tomahawk is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with Tomahawk. If not, see . + */ + +#ifndef MEDIASTREAM_H +#define MEDIASTREAM_H + +#include "DllMacro.h" +#include "Typedefs.h" +#include "utils/Logger.h" + +#include +#include + +class DLLEXPORT MediaStream : public QObject +{ + Q_OBJECT + +public: + enum MediaType { Unknown = -1, Empty = 0, Url = 1, Stream = 2, IODevice = 3 }; + + MediaStream( QObject* parent = nullptr ); + explicit MediaStream( const QUrl &url ); + explicit MediaStream( QIODevice* device ); + virtual ~MediaStream(); + + MediaType type(); + QUrl url(); + + void setStreamSize( qint64 size ); + qint64 streamSize(); + + virtual void seekStream( qint64 offset ) { (void)offset; } + virtual qint64 needData ( void** buffer ) { (void)buffer; tDebug() << Q_FUNC_INFO; return 0; } + + static int readCallback ( void* data, const char* cookie, int64_t* dts, int64_t* pts, unsigned* flags, size_t* bufferSize, void** buffer ); + static int readDoneCallback ( void *data, const char *cookie, size_t bufferSize, void *buffer ); + static int seekCallback ( void *data, const uint64_t pos ); + +public slots: + void bufferingFinished(); + +protected: + void endOfData(); + + MediaType m_type; + QUrl m_url; + QIODevice* m_ioDevice; + + bool m_started; + bool m_bufferingFinished; + bool m_eos; + qint64 m_pos; + qint64 m_streamSize; + + char m_buffer[1048576]; +private: + Q_DISABLE_COPY( MediaStream ) +}; + +#endif // MEDIASTREAM_H diff --git a/src/libtomahawk/utils/Qnr_IoDeviceStream.cpp b/src/libtomahawk/utils/Qnr_IoDeviceStream.cpp index 0176f452d..fca6de94b 100644 --- a/src/libtomahawk/utils/Qnr_IoDeviceStream.cpp +++ b/src/libtomahawk/utils/Qnr_IoDeviceStream.cpp @@ -33,11 +33,14 @@ using namespace Tomahawk; #define BLOCK_SIZE 1048576 QNR_IODeviceStream::QNR_IODeviceStream( const QSharedPointer& reply, QObject* parent ) - : Phonon::AbstractMediaStream( parent ) + : MediaStream( parent ) , m_networkReply( reply ) - , m_pos( 0 ) , m_timer( new QTimer( this ) ) { + tDebug() << Q_FUNC_INFO; + + m_type = MediaStream::Stream; + if ( !m_networkReply->isOpen() ) { m_networkReply->open(QIODevice::ReadOnly); } @@ -50,7 +53,7 @@ QNR_IODeviceStream::QNR_IODeviceStream( const QSharedPointer& rep // All data is ready, read it! m_data = m_networkReply->readAll(); Q_ASSERT( m_networkReply->atEnd() ); - setStreamSeekable( true ); +//TODO setStreamSeekable( true ); setStreamSize( m_data.size() ); } else @@ -59,7 +62,7 @@ QNR_IODeviceStream::QNR_IODeviceStream( const QSharedPointer& rep QVariant contentLength = m_networkReply->header( QNetworkRequest::ContentLengthHeader ); if ( contentLength.isValid() && contentLength.toLongLong() > 0 ) { - setStreamSize( contentLength.toLongLong() ); +//TODO setStreamSize( contentLength.toLongLong() ); } // Just consume all data that is already available. m_data = m_networkReply->readAll(); @@ -73,54 +76,39 @@ QNR_IODeviceStream::QNR_IODeviceStream( const QSharedPointer& rep QNR_IODeviceStream::~QNR_IODeviceStream() { -} - - -void -QNR_IODeviceStream::enoughData() -{ - m_timer->stop(); -} - - -void -QNR_IODeviceStream::needData() -{ - m_timer->start(); - moreData(); -} - -void -QNR_IODeviceStream::reset() -{ - m_pos = 0; + tDebug() << Q_FUNC_INFO; } void QNR_IODeviceStream::seekStream( qint64 offset ) { + tDebug() << Q_FUNC_INFO; + m_pos = offset; } -void -QNR_IODeviceStream::moreData() + +qint64 +QNR_IODeviceStream::needData ( void** buffer ) { +// tDebug() << Q_FUNC_INFO; + QByteArray data = m_data.mid( m_pos, BLOCK_SIZE ); m_pos += data.size(); if ( ( data.size() == 0 ) && m_networkReply->atEnd() && m_networkReply->isFinished() ) { // We're done. endOfData(); - m_timer->stop(); - } - else - { - writeData( data ); + return 0; } + + *buffer = new char[data.size()]; + memcpy(*buffer, data.data(), data.size()); +// tDebug() << Q_FUNC_INFO << " Returning buffer with size " << data.size(); + return data.size(); } - void QNR_IODeviceStream::readyRead() { diff --git a/src/libtomahawk/utils/Qnr_IoDeviceStream.h b/src/libtomahawk/utils/Qnr_IoDeviceStream.h index 27f5d9ad4..ef3b89263 100644 --- a/src/libtomahawk/utils/Qnr_IoDeviceStream.h +++ b/src/libtomahawk/utils/Qnr_IoDeviceStream.h @@ -25,37 +25,36 @@ #include "DllMacro.h" -#include +//#include #include #include #include +#include "MediaStream.h" + class QIODevice; class QTimer; namespace Tomahawk { -class DLLEXPORT QNR_IODeviceStream : public Phonon::AbstractMediaStream +class DLLEXPORT QNR_IODeviceStream : public MediaStream { Q_OBJECT + public: explicit QNR_IODeviceStream( const QSharedPointer& reply, QObject *parent = 0 ); ~QNR_IODeviceStream(); - virtual void enoughData(); - virtual void needData(); - virtual void reset(); virtual void seekStream( qint64 offset ); + virtual qint64 needData ( void** buffer ); private slots: - void moreData(); void readyRead(); private: QByteArray m_data; QSharedPointer m_networkReply; - qint64 m_pos; QTimer* m_timer; };