mirror of
https://github.com/xiaoyifang/goldendict-ng.git
synced 2024-11-24 08:34:08 +00:00
Merge pull request #983 from vedgy/overhaul-audio-player
Overhaul audio player
This commit is contained in:
commit
c5b8102cf5
|
@ -9,7 +9,7 @@ This code has been run and tested on Windows XP/Vista/7, Ubuntu Linux, Mac OS X.
|
|||
### External Deps
|
||||
|
||||
* Make, GCC, Git
|
||||
* Qt framework. Minimum required version is 4.6 for Windows, 4.5 for all other platforms. But Qt 4.7 or 4.8 is recommended.
|
||||
* Qt framework. Minimum required version is 4.6. But Qt 4.7 or 4.8 is recommended.
|
||||
* If you want to use Qt 5.x then use branch qt4x5
|
||||
* Qt Creator IDE is recommended for development
|
||||
* Various libraries on Linux (png, zlib, etc)
|
||||
|
|
|
@ -2,7 +2,6 @@
|
|||
* Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
|
||||
|
||||
#include "articleview.hh"
|
||||
#include "externalviewer.hh"
|
||||
#include <map>
|
||||
#include <QMessageBox>
|
||||
#include <QWebHitTestResult>
|
||||
|
@ -17,7 +16,6 @@
|
|||
#include "webmultimediadownload.hh"
|
||||
#include "programs.hh"
|
||||
#include "gddebug.hh"
|
||||
#include "ffmpegaudio.hh"
|
||||
#include <QDebug>
|
||||
#include <QCryptographicHash>
|
||||
#include "gestures.hh"
|
||||
|
@ -110,6 +108,7 @@ static QVariant evaluateJavaScriptVariableSafe( QWebFrame * frame, const QString
|
|||
}
|
||||
|
||||
ArticleView::ArticleView( QWidget * parent, ArticleNetworkAccessManager & nm,
|
||||
AudioPlayerPtr const & audioPlayer_,
|
||||
std::vector< sptr< Dictionary::Class > > const & allDictionaries_,
|
||||
Instances::Groups const & groups_, bool popupView_,
|
||||
Config::Class const & cfg_,
|
||||
|
@ -118,6 +117,7 @@ ArticleView::ArticleView( QWidget * parent, ArticleNetworkAccessManager & nm,
|
|||
GroupComboBox const * groupComboBox_ ):
|
||||
QFrame( parent ),
|
||||
articleNetMgr( nm ),
|
||||
audioPlayer( audioPlayer_ ),
|
||||
allDictionaries( allDictionaries_ ),
|
||||
groups( groups_ ),
|
||||
popupView( popupView_ ),
|
||||
|
@ -268,11 +268,7 @@ void ArticleView::setGroupComboBox( GroupComboBox const * g )
|
|||
ArticleView::~ArticleView()
|
||||
{
|
||||
cleanupTemp();
|
||||
|
||||
#ifndef DISABLE_INTERNAL_PLAYER
|
||||
if ( cfg.preferences.useInternalPlayer )
|
||||
Ffmpeg::AudioPlayer::instance().stop();
|
||||
#endif
|
||||
audioPlayer->stop();
|
||||
|
||||
#if QT_VERSION >= QT_VERSION_CHECK(4, 6, 0)
|
||||
ui.definition->ungrabGesture( Gestures::GDPinchGestureType );
|
||||
|
@ -284,12 +280,8 @@ void ArticleView::showDefinition( QString const & word, unsigned group,
|
|||
QString const & scrollTo,
|
||||
Contexts const & contexts_ )
|
||||
{
|
||||
|
||||
#ifndef DISABLE_INTERNAL_PLAYER
|
||||
// first, let's stop the player
|
||||
if ( cfg.preferences.useInternalPlayer )
|
||||
Ffmpeg::AudioPlayer::instance().stop();
|
||||
#endif
|
||||
audioPlayer->stop();
|
||||
|
||||
QUrl req;
|
||||
Contexts contexts( contexts_ );
|
||||
|
@ -353,11 +345,8 @@ void ArticleView::showDefinition( QString const & word, QStringList const & dict
|
|||
if( dictIDs.isEmpty() )
|
||||
return;
|
||||
|
||||
#ifndef DISABLE_INTERNAL_PLAYER
|
||||
// first, let's stop the player
|
||||
if ( cfg.preferences.useInternalPlayer )
|
||||
Ffmpeg::AudioPlayer::instance().stop();
|
||||
#endif
|
||||
audioPlayer->stop();
|
||||
|
||||
QUrl req;
|
||||
|
||||
|
@ -1909,29 +1898,10 @@ void ArticleView::resourceDownloadFinished()
|
|||
Dictionary::WebMultimediaDownload::isAudioUrl( resourceDownloadUrl ) )
|
||||
{
|
||||
// Audio data
|
||||
#ifndef DISABLE_INTERNAL_PLAYER
|
||||
if ( cfg.preferences.useInternalPlayer )
|
||||
{
|
||||
Ffmpeg::AudioPlayer & player = Ffmpeg::AudioPlayer::instance();
|
||||
connect( &player, SIGNAL( error( QString ) ), this, SLOT( audioPlayerError( QString ) ), Qt::UniqueConnection );
|
||||
player.playMemory( data.data(), data.size() );
|
||||
}
|
||||
else
|
||||
#endif
|
||||
{
|
||||
// Use external viewer to play the file
|
||||
try
|
||||
{
|
||||
ExternalViewer * viewer = new ExternalViewer( this, data, "wav", cfg.preferences.audioPlaybackProgram.trimmed() );
|
||||
|
||||
// Once started, it will erase itself
|
||||
viewer->start();
|
||||
}
|
||||
catch( ExternalViewer::Ex & e )
|
||||
{
|
||||
QMessageBox::critical( this, "GoldenDict", tr( "Failed to run a player to play sound file: %1" ).arg( e.what() ) );
|
||||
}
|
||||
}
|
||||
connect( audioPlayer.data(), SIGNAL( error( QString ) ), this, SLOT( audioPlayerError( QString ) ), Qt::UniqueConnection );
|
||||
QString errorMessage = audioPlayer->play( data.data(), data.size() );
|
||||
if( !errorMessage.isEmpty() )
|
||||
QMessageBox::critical( this, "GoldenDict", tr( "Failed to play sound file: %1" ).arg( errorMessage ) );
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -1987,7 +1957,7 @@ void ArticleView::resourceDownloadFinished()
|
|||
|
||||
void ArticleView::audioPlayerError( QString const & message )
|
||||
{
|
||||
emit statusBarMessage( tr( "WARNING: FFmpeg Audio Player: %1" ).arg( message ),
|
||||
emit statusBarMessage( tr( "WARNING: Audio Player: %1" ).arg( message ),
|
||||
10000, QPixmap( ":/icons/error.png" ) );
|
||||
}
|
||||
|
||||
|
|
|
@ -10,6 +10,7 @@
|
|||
#include <QSet>
|
||||
#include <list>
|
||||
#include "article_netmgr.hh"
|
||||
#include "audioplayerinterface.hh"
|
||||
#include "instances.hh"
|
||||
#include "groupcombobox.hh"
|
||||
#include "ui_articleview.h"
|
||||
|
@ -23,6 +24,7 @@ class ArticleView: public QFrame
|
|||
Q_OBJECT
|
||||
|
||||
ArticleNetworkAccessManager & articleNetMgr;
|
||||
AudioPlayerPtr const & audioPlayer;
|
||||
std::vector< sptr< Dictionary::Class > > const & allDictionaries;
|
||||
Instances::Groups const & groups;
|
||||
bool popupView;
|
||||
|
@ -68,6 +70,7 @@ public:
|
|||
/// The groups aren't copied -- rather than that, the reference is kept
|
||||
ArticleView( QWidget * parent,
|
||||
ArticleNetworkAccessManager &,
|
||||
AudioPlayerPtr const &,
|
||||
std::vector< sptr< Dictionary::Class > > const & allDictionaries,
|
||||
Instances::Groups const &,
|
||||
bool popupView,
|
||||
|
|
56
audioplayerfactory.cc
Normal file
56
audioplayerfactory.cc
Normal file
|
@ -0,0 +1,56 @@
|
|||
/* This file is (c) 2018 Igor Kushnir <igorkuo@gmail.com>
|
||||
* Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
|
||||
|
||||
#include <QScopedPointer>
|
||||
#include <QObject>
|
||||
#include "audioplayerfactory.hh"
|
||||
#include "ffmpegaudioplayer.hh"
|
||||
#include "externalaudioplayer.hh"
|
||||
#include "gddebug.hh"
|
||||
|
||||
AudioPlayerFactory::AudioPlayerFactory( Config::Preferences const & p ) :
|
||||
useInternalPlayer( p.useInternalPlayer ),
|
||||
audioPlaybackProgram( p.audioPlaybackProgram )
|
||||
{
|
||||
reset();
|
||||
}
|
||||
|
||||
void AudioPlayerFactory::setPreferences( Config::Preferences const & p )
|
||||
{
|
||||
if( p.useInternalPlayer != useInternalPlayer )
|
||||
{
|
||||
useInternalPlayer = p.useInternalPlayer;
|
||||
audioPlaybackProgram = p.audioPlaybackProgram;
|
||||
reset();
|
||||
}
|
||||
else
|
||||
if( !useInternalPlayer && p.audioPlaybackProgram != audioPlaybackProgram )
|
||||
{
|
||||
audioPlaybackProgram = p.audioPlaybackProgram;
|
||||
ExternalAudioPlayer * const externalPlayer =
|
||||
qobject_cast< ExternalAudioPlayer * >( playerPtr.data() );
|
||||
if( externalPlayer )
|
||||
setAudioPlaybackProgram( *externalPlayer );
|
||||
else
|
||||
gdWarning( "External player was expected, but it does not exist.\n" );
|
||||
}
|
||||
}
|
||||
|
||||
void AudioPlayerFactory::reset()
|
||||
{
|
||||
#ifndef DISABLE_INTERNAL_PLAYER
|
||||
if( useInternalPlayer )
|
||||
playerPtr.reset( new Ffmpeg::AudioPlayer );
|
||||
else
|
||||
#endif
|
||||
{
|
||||
QScopedPointer< ExternalAudioPlayer > externalPlayer( new ExternalAudioPlayer );
|
||||
setAudioPlaybackProgram( *externalPlayer );
|
||||
playerPtr.reset( externalPlayer.take() );
|
||||
}
|
||||
}
|
||||
|
||||
void AudioPlayerFactory::setAudioPlaybackProgram( ExternalAudioPlayer & externalPlayer )
|
||||
{
|
||||
externalPlayer.setPlayerCommandLine( audioPlaybackProgram.trimmed() );
|
||||
}
|
32
audioplayerfactory.hh
Normal file
32
audioplayerfactory.hh
Normal file
|
@ -0,0 +1,32 @@
|
|||
/* This file is (c) 2018 Igor Kushnir <igorkuo@gmail.com>
|
||||
* Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
|
||||
|
||||
#ifndef AUDIOPLAYERFACTORY_HH_INCLUDED
|
||||
#define AUDIOPLAYERFACTORY_HH_INCLUDED
|
||||
|
||||
#include "audioplayerinterface.hh"
|
||||
#include "config.hh"
|
||||
|
||||
class ExternalAudioPlayer;
|
||||
|
||||
class AudioPlayerFactory
|
||||
{
|
||||
Q_DISABLE_COPY( AudioPlayerFactory )
|
||||
public:
|
||||
explicit AudioPlayerFactory( Config::Preferences const & );
|
||||
void setPreferences( Config::Preferences const & );
|
||||
/// The returned reference to a smart pointer is valid as long as this object
|
||||
/// exists. The pointer to the owned AudioPlayerInterface may change after the
|
||||
/// call to setPreferences(), but it is guaranteed to never be null.
|
||||
AudioPlayerPtr const & player() const { return playerPtr; }
|
||||
|
||||
private:
|
||||
void reset();
|
||||
void setAudioPlaybackProgram( ExternalAudioPlayer & externalPlayer );
|
||||
|
||||
bool useInternalPlayer;
|
||||
QString audioPlaybackProgram;
|
||||
AudioPlayerPtr playerPtr;
|
||||
};
|
||||
|
||||
#endif // AUDIOPLAYERFACTORY_HH_INCLUDED
|
30
audioplayerinterface.hh
Normal file
30
audioplayerinterface.hh
Normal file
|
@ -0,0 +1,30 @@
|
|||
/* This file is (c) 2018 Igor Kushnir <igorkuo@gmail.com>
|
||||
* Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
|
||||
|
||||
#ifndef AUDIOPLAYERINTERFACE_HH_INCLUDED
|
||||
#define AUDIOPLAYERINTERFACE_HH_INCLUDED
|
||||
|
||||
#include <QScopedPointer>
|
||||
#include <QString>
|
||||
#include <QObject>
|
||||
|
||||
class AudioPlayerInterface : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
/// Stops current playback if any, copies the audio buffer at [data, data + size),
|
||||
/// then plays this buffer. It is safe to invalidate \p data after this function call.
|
||||
/// Returns an error message in case of immediate failure; an empty string
|
||||
/// in case of success.
|
||||
virtual QString play( const char * data, int size ) = 0;
|
||||
/// Stops current playback if any.
|
||||
virtual void stop() = 0;
|
||||
|
||||
signals:
|
||||
/// Notifies of asynchronous errors.
|
||||
void error( QString message );
|
||||
};
|
||||
|
||||
typedef QScopedPointer< AudioPlayerInterface > AudioPlayerPtr;
|
||||
|
||||
#endif // AUDIOPLAYERINTERFACE_HH_INCLUDED
|
99
externalaudioplayer.cc
Normal file
99
externalaudioplayer.cc
Normal file
|
@ -0,0 +1,99 @@
|
|||
/* This file is (c) 2018 Igor Kushnir <igorkuo@gmail.com>
|
||||
* Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
|
||||
|
||||
#include "externalaudioplayer.hh"
|
||||
#include "externalviewer.hh"
|
||||
|
||||
ExternalAudioPlayer::ExternalAudioPlayer() : exitingViewer( 0 )
|
||||
{
|
||||
}
|
||||
|
||||
ExternalAudioPlayer::~ExternalAudioPlayer()
|
||||
{
|
||||
// Destroy viewers immediately to prevent memory and temporary file leaks
|
||||
// at application exit.
|
||||
|
||||
// Set viewer to null first and foremost to make sure that onViewerDestroyed()
|
||||
// doesn't attempt to start viewer or mess the smart pointer up.
|
||||
stopAndDestroySynchronously( viewer.take() );
|
||||
|
||||
stopAndDestroySynchronously( exitingViewer );
|
||||
}
|
||||
|
||||
void ExternalAudioPlayer::setPlayerCommandLine( QString const & playerCommandLine_ )
|
||||
{
|
||||
playerCommandLine = playerCommandLine_;
|
||||
}
|
||||
|
||||
QString ExternalAudioPlayer::play( const char * data, int size )
|
||||
{
|
||||
stop();
|
||||
|
||||
Q_ASSERT( !viewer && "viewer must be null at this point for exception safety." );
|
||||
try
|
||||
{
|
||||
// Our destructor properly destroys viewers we remember about.
|
||||
// In the unlikely case that we call viewer.reset() during the application
|
||||
// exit, ~QObject() prevents leaks as this class is a parent of all viewers.
|
||||
viewer.reset( new ExternalViewer( data, size, "wav", playerCommandLine, this ) );
|
||||
}
|
||||
catch( const ExternalViewer::Ex & e )
|
||||
{
|
||||
return e.what();
|
||||
}
|
||||
|
||||
if( exitingViewer )
|
||||
return QString(); // Will start later.
|
||||
return startViewer();
|
||||
}
|
||||
|
||||
void ExternalAudioPlayer::stop()
|
||||
{
|
||||
if( !exitingViewer && viewer && !viewer->stop() )
|
||||
{
|
||||
// Give the previous viewer a chance to stop before starting a new one.
|
||||
// This prevents overlapping audio and possible conflicts between
|
||||
// external program instances.
|
||||
// Graceful stopping is better than calling viewer.reset() because:
|
||||
// 1) the process gets a chance to clean up and save its state;
|
||||
// 2) there is no event loop blocking and consequently no (short) UI freeze
|
||||
// while synchronously waiting for the external process to exit.
|
||||
exitingViewer = viewer.take();
|
||||
}
|
||||
else // viewer is either not started or already stopped -> simply destroy it.
|
||||
viewer.reset();
|
||||
}
|
||||
|
||||
void ExternalAudioPlayer::onViewerDestroyed( QObject * destroyedViewer )
|
||||
{
|
||||
if( exitingViewer == destroyedViewer )
|
||||
{
|
||||
exitingViewer = 0;
|
||||
if( viewer )
|
||||
{
|
||||
QString errorMessage = startViewer();
|
||||
if( !errorMessage.isEmpty() )
|
||||
emit error( errorMessage );
|
||||
}
|
||||
}
|
||||
else
|
||||
if( viewer.data() == destroyedViewer )
|
||||
viewer.take(); // viewer finished and died -> release ownership.
|
||||
}
|
||||
|
||||
QString ExternalAudioPlayer::startViewer()
|
||||
{
|
||||
Q_ASSERT( !exitingViewer && viewer );
|
||||
connect( viewer.data(), SIGNAL( destroyed( QObject * ) ),
|
||||
this, SLOT( onViewerDestroyed( QObject * ) ) );
|
||||
try
|
||||
{
|
||||
viewer->start();
|
||||
}
|
||||
catch( const ExternalViewer::Ex & e )
|
||||
{
|
||||
viewer.reset();
|
||||
return e.what();
|
||||
}
|
||||
return QString();
|
||||
}
|
44
externalaudioplayer.hh
Normal file
44
externalaudioplayer.hh
Normal file
|
@ -0,0 +1,44 @@
|
|||
/* This file is (c) 2018 Igor Kushnir <igorkuo@gmail.com>
|
||||
* Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
|
||||
|
||||
#ifndef EXTERNALAUDIOPLAYER_HH_INCLUDED
|
||||
#define EXTERNALAUDIOPLAYER_HH_INCLUDED
|
||||
|
||||
#include <QScopedPointer>
|
||||
#include <QString>
|
||||
#include "audioplayerinterface.hh"
|
||||
|
||||
class ExternalViewer;
|
||||
|
||||
class ExternalAudioPlayer : public AudioPlayerInterface
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
ExternalAudioPlayer();
|
||||
~ExternalAudioPlayer();
|
||||
/// \param playerCommandLine_ Will be used in future play() calls.
|
||||
void setPlayerCommandLine( QString const & playerCommandLine_ );
|
||||
|
||||
virtual QString play( const char * data, int size );
|
||||
virtual void stop();
|
||||
|
||||
private slots:
|
||||
void onViewerDestroyed( QObject * destroyedViewer );
|
||||
|
||||
private:
|
||||
QString startViewer();
|
||||
|
||||
QString playerCommandLine;
|
||||
ExternalViewer * exitingViewer; ///< If not null: points to the previous viewer,
|
||||
///< the current viewer (if any) is not started yet
|
||||
///< and waits for exitingViewer to be destroyed first.
|
||||
|
||||
struct ScopedPointerDeleteLater
|
||||
{
|
||||
static void cleanup( QObject * p ) { if( p ) p->deleteLater(); }
|
||||
};
|
||||
// deleteLater() is safer because viewer actively participates in the QEventLoop.
|
||||
QScopedPointer< ExternalViewer, ScopedPointerDeleteLater > viewer;
|
||||
};
|
||||
|
||||
#endif // EXTERNALAUDIOPLAYER_HH_INCLUDED
|
|
@ -2,22 +2,21 @@
|
|||
* Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
|
||||
|
||||
#include <QDir>
|
||||
#include <QTimer>
|
||||
#include "externalviewer.hh"
|
||||
#include "parsecmdline.hh"
|
||||
#include "gddebug.hh"
|
||||
|
||||
using std::vector;
|
||||
|
||||
ExternalViewer::ExternalViewer( QObject * parent, vector< char > const & data,
|
||||
QString const & extension,
|
||||
QString const & viewerCmdLine_ )
|
||||
ExternalViewer::ExternalViewer( const char * data, int size,
|
||||
QString const & extension, QString const & viewerCmdLine_,
|
||||
QObject * parent)
|
||||
throw( exCantCreateTempFile ):
|
||||
QObject( parent ),
|
||||
tempFile( QDir::temp().filePath( QString( "gd-XXXXXXXX." ) + extension ) ),
|
||||
viewer( this ),
|
||||
viewerCmdLine( viewerCmdLine_ )
|
||||
{
|
||||
if ( !tempFile.open() || (size_t) tempFile.write( &data.front(), data.size() ) != data.size() )
|
||||
if ( !tempFile.open() || tempFile.write( data, size ) != size )
|
||||
throw exCantCreateTempFile();
|
||||
|
||||
tempFileName = tempFile.fileName(); // For some reason it loses it after it was closed()
|
||||
|
@ -53,3 +52,29 @@ void ExternalViewer::start() throw( exCantRunViewer )
|
|||
else
|
||||
throw exCantRunViewer( tr( "the viewer program name is empty" ).toUtf8().data() );
|
||||
}
|
||||
|
||||
bool ExternalViewer::stop()
|
||||
{
|
||||
if( viewer.state() == QProcess::NotRunning )
|
||||
return true;
|
||||
viewer.terminate();
|
||||
QTimer::singleShot( 1000, &viewer, SLOT( kill() ) ); // In case terminate() fails.
|
||||
return false;
|
||||
}
|
||||
|
||||
void ExternalViewer::stopSynchronously()
|
||||
{
|
||||
// This implementation comes straight from QProcess::~QProcess().
|
||||
if( viewer.state() == QProcess::NotRunning )
|
||||
return;
|
||||
viewer.kill();
|
||||
viewer.waitForFinished();
|
||||
}
|
||||
|
||||
void stopAndDestroySynchronously( ExternalViewer * viewer )
|
||||
{
|
||||
if( !viewer )
|
||||
return;
|
||||
viewer->stopSynchronously();
|
||||
delete viewer;
|
||||
}
|
||||
|
|
|
@ -7,7 +7,6 @@
|
|||
#include <QObject>
|
||||
#include <QTemporaryFile>
|
||||
#include <QProcess>
|
||||
#include <vector>
|
||||
#include "ex.hh"
|
||||
|
||||
/// An external viewer, opens resources in other programs
|
||||
|
@ -26,14 +25,26 @@ public:
|
|||
DEF_EX( exCantCreateTempFile, "Couldn't create temporary file.", Ex )
|
||||
DEF_EX_STR( exCantRunViewer, "Couldn't run external viewer:", Ex )
|
||||
|
||||
ExternalViewer( QObject * parent, std::vector< char > const & data,
|
||||
QString const & extension, QString const & viewerCmdLine )
|
||||
ExternalViewer( const char * data, int size,
|
||||
QString const & extension, QString const & viewerCmdLine,
|
||||
QObject * parent = 0 )
|
||||
throw( exCantCreateTempFile );
|
||||
|
||||
// Once this is called, the object will be deleted when it's done, even if
|
||||
// the function throws.
|
||||
void start() throw( exCantRunViewer );
|
||||
|
||||
/// If the external process is running, requests its termination and returns
|
||||
/// false - expect the QObject::destroyed() signal to be emitted soon.
|
||||
/// If the external process is not running, returns true, the object
|
||||
/// destruction is not necessarily scheduled in this case.
|
||||
bool stop();
|
||||
/// Kills the process if it is running and waits for it to finish.
|
||||
void stopSynchronously();
|
||||
};
|
||||
|
||||
#endif
|
||||
/// Call this function instead of simply deleting viewer to prevent the
|
||||
/// "QProcess: Destroyed while process X is still running." warning in log.
|
||||
void stopAndDestroySynchronously( ExternalViewer * viewer );
|
||||
|
||||
#endif
|
||||
|
|
|
@ -43,28 +43,28 @@ static inline QString avErrorString( int errnum )
|
|||
return QString::fromLatin1( buf );
|
||||
}
|
||||
|
||||
AudioPlayer & AudioPlayer::instance()
|
||||
AudioService & AudioService::instance()
|
||||
{
|
||||
static AudioPlayer a;
|
||||
static AudioService a;
|
||||
return a;
|
||||
}
|
||||
|
||||
AudioPlayer::AudioPlayer()
|
||||
AudioService::AudioService()
|
||||
{
|
||||
av_register_all();
|
||||
ao_initialize();
|
||||
}
|
||||
|
||||
AudioPlayer::~AudioPlayer()
|
||||
AudioService::~AudioService()
|
||||
{
|
||||
emit cancelPlaying( true );
|
||||
ao_shutdown();
|
||||
}
|
||||
|
||||
void AudioPlayer::playMemory( const void * ptr, int size )
|
||||
void AudioService::playMemory( const char * ptr, int size )
|
||||
{
|
||||
emit cancelPlaying( false );
|
||||
QByteArray audioData( ( char * )ptr, size );
|
||||
QByteArray audioData( ptr, size );
|
||||
DecoderThread * thread = new DecoderThread( audioData, this );
|
||||
|
||||
connect( thread, SIGNAL( error( QString ) ), this, SIGNAL( error( QString ) ) );
|
||||
|
@ -74,7 +74,7 @@ void AudioPlayer::playMemory( const void * ptr, int size )
|
|||
thread->start();
|
||||
}
|
||||
|
||||
void AudioPlayer::stop()
|
||||
void AudioService::stop()
|
||||
{
|
||||
emit cancelPlaying( false );
|
||||
}
|
||||
|
|
|
@ -12,13 +12,13 @@
|
|||
namespace Ffmpeg
|
||||
{
|
||||
|
||||
class AudioPlayer : public QObject
|
||||
class AudioService : public QObject
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
static AudioPlayer & instance();
|
||||
void playMemory( const void * ptr, int size );
|
||||
static AudioService & instance();
|
||||
void playMemory( const char * ptr, int size );
|
||||
void stop();
|
||||
|
||||
signals:
|
||||
|
@ -26,10 +26,8 @@ signals:
|
|||
void error( QString const & message );
|
||||
|
||||
private:
|
||||
AudioPlayer();
|
||||
~AudioPlayer();
|
||||
AudioPlayer( AudioPlayer const & );
|
||||
AudioPlayer & operator=( AudioPlayer const & );
|
||||
AudioService();
|
||||
~AudioService();
|
||||
};
|
||||
|
||||
class DecoderThread: public QThread
|
||||
|
|
41
ffmpegaudioplayer.hh
Normal file
41
ffmpegaudioplayer.hh
Normal file
|
@ -0,0 +1,41 @@
|
|||
/* This file is (c) 2018 Igor Kushnir <igorkuo@gmail.com>
|
||||
* Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
|
||||
|
||||
#ifndef FFMPEGAUDIOPLAYER_HH_INCLUDED
|
||||
#define FFMPEGAUDIOPLAYER_HH_INCLUDED
|
||||
|
||||
#include "audioplayerinterface.hh"
|
||||
#include "ffmpegaudio.hh"
|
||||
|
||||
#ifndef DISABLE_INTERNAL_PLAYER
|
||||
|
||||
namespace Ffmpeg
|
||||
{
|
||||
|
||||
class AudioPlayer : public AudioPlayerInterface
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
AudioPlayer()
|
||||
{
|
||||
connect( &AudioService::instance(), SIGNAL( error( QString ) ),
|
||||
this, SIGNAL( error( QString ) ) );
|
||||
}
|
||||
|
||||
virtual QString play( const char * data, int size )
|
||||
{
|
||||
AudioService::instance().playMemory( data, size );
|
||||
return QString();
|
||||
}
|
||||
|
||||
virtual void stop()
|
||||
{
|
||||
AudioService::instance().stop();
|
||||
}
|
||||
};
|
||||
|
||||
}
|
||||
|
||||
#endif // DISABLE_INTERNAL_PLAYER
|
||||
|
||||
#endif // FFMPEGAUDIOPLAYER_HH_INCLUDED
|
|
@ -264,6 +264,10 @@ HEADERS += folding.hh \
|
|||
article_maker.hh \
|
||||
scanpopup.hh \
|
||||
articleview.hh \
|
||||
audioplayerinterface.hh \
|
||||
audioplayerfactory.hh \
|
||||
ffmpegaudioplayer.hh \
|
||||
externalaudioplayer.hh \
|
||||
externalviewer.hh \
|
||||
wordfinder.hh \
|
||||
groupcombobox.hh \
|
||||
|
@ -394,6 +398,8 @@ SOURCES += folding.cc \
|
|||
article_maker.cc \
|
||||
scanpopup.cc \
|
||||
articleview.cc \
|
||||
audioplayerfactory.cc \
|
||||
externalaudioplayer.cc \
|
||||
externalviewer.cc \
|
||||
wordfinder.cc \
|
||||
groupcombobox.cc \
|
||||
|
|
|
@ -118,6 +118,7 @@ MainWindow::MainWindow( Config::Class & cfg_ ):
|
|||
articleNetMgr( this, dictionaries, articleMaker,
|
||||
cfg.preferences.disallowContentFromOtherSites, cfg.preferences.hideGoldenDictHeader ),
|
||||
dictNetMgr( this ),
|
||||
audioPlayerFactory( cfg.preferences ),
|
||||
wordFinder( this ),
|
||||
newReleaseCheckTimer( this ),
|
||||
latestReleaseReply( 0 ),
|
||||
|
@ -1373,8 +1374,8 @@ void MainWindow::makeScanPopup()
|
|||
!cfg.preferences.enableClipboardHotkey )
|
||||
return;
|
||||
|
||||
scanPopup = new ScanPopup( 0, cfg, articleNetMgr, dictionaries, groupInstances,
|
||||
history );
|
||||
scanPopup = new ScanPopup( 0, cfg, articleNetMgr, audioPlayerFactory.player(),
|
||||
dictionaries, groupInstances, history );
|
||||
|
||||
scanPopup->setStyleSheet( styleSheet() );
|
||||
|
||||
|
@ -1530,8 +1531,8 @@ void MainWindow::addNewTab()
|
|||
ArticleView * MainWindow::createNewTab( bool switchToIt,
|
||||
QString const & name )
|
||||
{
|
||||
ArticleView * view = new ArticleView( this, articleNetMgr, dictionaries,
|
||||
groupInstances, false, cfg,
|
||||
ArticleView * view = new ArticleView( this, articleNetMgr, audioPlayerFactory.player(),
|
||||
dictionaries, groupInstances, false, cfg,
|
||||
*ui.searchInPageAction,
|
||||
dictionaryBar.toggleViewAction(),
|
||||
groupList );
|
||||
|
@ -2087,6 +2088,8 @@ void MainWindow::editPreferences()
|
|||
|
||||
cfg.preferences = p;
|
||||
|
||||
audioPlayerFactory.setPreferences( cfg.preferences );
|
||||
|
||||
beforeScanPopupSeparator->setVisible( cfg.preferences.enableScanPopup );
|
||||
enableScanPopup->setVisible( cfg.preferences.enableScanPopup );
|
||||
afterScanPopupSeparator->setVisible( cfg.preferences.enableScanPopup );
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
#include "config.hh"
|
||||
#include "dictionary.hh"
|
||||
#include "article_netmgr.hh"
|
||||
#include "audioplayerfactory.hh"
|
||||
#include "instances.hh"
|
||||
#include "article_maker.hh"
|
||||
#include "scanpopup.hh"
|
||||
|
@ -151,6 +152,7 @@ private:
|
|||
QNetworkAccessManager dictNetMgr; // We give dictionaries a separate manager,
|
||||
// since their requests can be destroyed
|
||||
// in a separate thread
|
||||
AudioPlayerFactory audioPlayerFactory;
|
||||
|
||||
WordList * wordList;
|
||||
QLineEdit * translateLine;
|
||||
|
|
|
@ -36,6 +36,7 @@ Qt::Popup
|
|||
ScanPopup::ScanPopup( QWidget * parent,
|
||||
Config::Class & cfg_,
|
||||
ArticleNetworkAccessManager & articleNetMgr,
|
||||
AudioPlayerPtr const & audioPlayer_,
|
||||
std::vector< sptr< Dictionary::Class > > const & allDictionaries_,
|
||||
Instances::Groups const & groups_,
|
||||
History & history_ ):
|
||||
|
@ -72,8 +73,8 @@ ScanPopup::ScanPopup( QWidget * parent,
|
|||
|
||||
ui.queryError->hide();
|
||||
|
||||
definition = new ArticleView( ui.outerFrame, articleNetMgr, allDictionaries,
|
||||
groups, true, cfg,
|
||||
definition = new ArticleView( ui.outerFrame, articleNetMgr, audioPlayer_,
|
||||
allDictionaries, groups, true, cfg,
|
||||
openSearchAction,
|
||||
dictionaryBar.toggleViewAction()
|
||||
);
|
||||
|
|
|
@ -30,6 +30,7 @@ public:
|
|||
ScanPopup( QWidget * parent,
|
||||
Config::Class & cfg,
|
||||
ArticleNetworkAccessManager &,
|
||||
AudioPlayerPtr const &,
|
||||
std::vector< sptr< Dictionary::Class > > const & allDictionaries,
|
||||
Instances::Groups const &,
|
||||
History & );
|
||||
|
|
Loading…
Reference in a new issue