mirror of
https://github.com/tomahawk-player/tomahawk.git
synced 2025-08-11 00:24:12 +02:00
Merge pull request #266 from tomahawk-player/libvlc
Replace Phonon with libvlc directly
This commit is contained in:
@@ -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.)
|
||||
|
30
CMakeModules/FindLIBVLC.cmake
Normal file
30
CMakeModules/FindLIBVLC.cmake
Normal file
@@ -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
|
||||
)
|
||||
|
||||
|
@@ -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 <kretz@kde.org>
|
||||
#
|
||||
# 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 )
|
@@ -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}
|
||||
|
@@ -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<Phonon::AudioDataOutput::Channel, QVector<qint16> > 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<QNetworkReply> qnr = io.objectCast<QNetworkReply>();
|
||||
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 );
|
||||
}
|
||||
|
@@ -24,6 +24,7 @@
|
||||
#include "../Typedefs.h"
|
||||
|
||||
#include <QStringList>
|
||||
#include <functional>
|
||||
|
||||
#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<qint16> > data );
|
||||
// void audioDataReady( QMap< AudioEngine::AudioChannel, QVector<qint16> > 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 )
|
||||
|
@@ -1,11 +1,4 @@
|
||||
|
||||
#include <phonon/MediaObject>
|
||||
#include <phonon/AudioOutput>
|
||||
#include <phonon/AudioDataOutput>
|
||||
#include <phonon/BackendCapabilities>
|
||||
#include <phonon/Path>
|
||||
#include <phonon/Effect>
|
||||
#include <phonon/EffectParameter>
|
||||
#include "AudioOutput.h"
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
@@ -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<QIODevice> 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;
|
||||
|
525
src/libtomahawk/audio/AudioOutput.cpp
Normal file
525
src/libtomahawk/audio/AudioOutput.cpp
Normal file
@@ -0,0 +1,525 @@
|
||||
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
|
||||
*
|
||||
* Copyright 2010-2014, Christian Muehlhaeuser <muesli@tomahawk-player.org>
|
||||
* Copyright 2010-2012, Jeff Mitchell <jeff@tomahawk-player.org>
|
||||
* Copyright 2013, Teo Mrnjavac <teo@kde.org>
|
||||
* Copyright 2014, Adrien Aubry <dridri85@gmail.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#include "AudioEngine.h"
|
||||
#include "AudioOutput.h"
|
||||
|
||||
#include "utils/Logger.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QVarLengthArray>
|
||||
#include <QFile>
|
||||
#include <QDir>
|
||||
|
||||
#include <vlc/libvlc.h>
|
||||
#include <vlc/libvlc_media.h>
|
||||
#include <vlc/libvlc_media_player.h>
|
||||
#include <vlc/libvlc_events.h>
|
||||
#include <vlc/libvlc_version.h>
|
||||
|
||||
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>("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;
|
||||
}
|
104
src/libtomahawk/audio/AudioOutput.h
Normal file
104
src/libtomahawk/audio/AudioOutput.h
Normal file
@@ -0,0 +1,104 @@
|
||||
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
|
||||
*
|
||||
* Copyright 2010-2014, Christian Muehlhaeuser <muesli@tomahawk-player.org>
|
||||
* Copyright 2010-2012, Jeff Mitchell <jeff@tomahawk-player.org>
|
||||
* Copyright 2013, Teo Mrnjavac <teo@kde.org>
|
||||
* Copyright 2014, Adrien Aubry <dridri85@gmail.com>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef AUDIOOUTPUT_H
|
||||
#define AUDIOOUTPUT_H
|
||||
|
||||
#include "DllMacro.h"
|
||||
#include "Typedefs.h"
|
||||
#include "utils/MediaStream.h"
|
||||
|
||||
#include <functional>
|
||||
|
||||
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
|
205
src/libtomahawk/utils/MediaStream.cpp
Normal file
205
src/libtomahawk/utils/MediaStream.cpp
Normal file
@@ -0,0 +1,205 @@
|
||||
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
|
||||
*
|
||||
* Copyright 2010-2014, Christian Muehlhaeuser <muesli@tomahawk-player.org>
|
||||
* Copyright 2010-2012, Jeff Mitchell <jeff@tomahawk-player.org>
|
||||
* Copyright 2013, Teo Mrnjavac <teo@kde.org>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#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;
|
||||
}
|
77
src/libtomahawk/utils/MediaStream.h
Normal file
77
src/libtomahawk/utils/MediaStream.h
Normal file
@@ -0,0 +1,77 @@
|
||||
/* === This file is part of Tomahawk Player - <http://tomahawk-player.org> ===
|
||||
*
|
||||
* Copyright 2010-2014, Christian Muehlhaeuser <muesli@tomahawk-player.org>
|
||||
* Copyright 2010-2012, Jeff Mitchell <jeff@tomahawk-player.org>
|
||||
* Copyright 2013, Teo Mrnjavac <teo@kde.org>
|
||||
*
|
||||
* 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 <http://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
#ifndef MEDIASTREAM_H
|
||||
#define MEDIASTREAM_H
|
||||
|
||||
#include "DllMacro.h"
|
||||
#include "Typedefs.h"
|
||||
#include "utils/Logger.h"
|
||||
|
||||
#include <QUrl>
|
||||
#include <QIODevice>
|
||||
|
||||
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
|
@@ -33,11 +33,14 @@ using namespace Tomahawk;
|
||||
#define BLOCK_SIZE 1048576
|
||||
|
||||
QNR_IODeviceStream::QNR_IODeviceStream( const QSharedPointer<QNetworkReply>& 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<QNetworkReply>& 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<QNetworkReply>& 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<QNetworkReply>& 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()
|
||||
{
|
||||
|
@@ -25,37 +25,36 @@
|
||||
|
||||
#include "DllMacro.h"
|
||||
|
||||
#include <phonon/abstractmediastream.h>
|
||||
//#include <phonon/abstractmediastream.h>
|
||||
#include <QByteArray>
|
||||
#include <QNetworkReply>
|
||||
#include <QSharedPointer>
|
||||
|
||||
#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<QNetworkReply>& 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<QNetworkReply> m_networkReply;
|
||||
qint64 m_pos;
|
||||
QTimer* m_timer;
|
||||
};
|
||||
|
||||
|
Reference in New Issue
Block a user