Merge pull request #983 from vedgy/overhaul-audio-player

Overhaul audio player
This commit is contained in:
Abs62 2018-03-26 17:33:49 +03:00 committed by GitHub
commit c5b8102cf5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 393 additions and 71 deletions

View file

@ -9,7 +9,7 @@ This code has been run and tested on Windows XP/Vista/7, Ubuntu Linux, Mac OS X.
### External Deps ### External Deps
* Make, GCC, Git * 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 * If you want to use Qt 5.x then use branch qt4x5
* Qt Creator IDE is recommended for development * Qt Creator IDE is recommended for development
* Various libraries on Linux (png, zlib, etc) * Various libraries on Linux (png, zlib, etc)

View file

@ -2,7 +2,6 @@
* Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */ * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
#include "articleview.hh" #include "articleview.hh"
#include "externalviewer.hh"
#include <map> #include <map>
#include <QMessageBox> #include <QMessageBox>
#include <QWebHitTestResult> #include <QWebHitTestResult>
@ -17,7 +16,6 @@
#include "webmultimediadownload.hh" #include "webmultimediadownload.hh"
#include "programs.hh" #include "programs.hh"
#include "gddebug.hh" #include "gddebug.hh"
#include "ffmpegaudio.hh"
#include <QDebug> #include <QDebug>
#include <QCryptographicHash> #include <QCryptographicHash>
#include "gestures.hh" #include "gestures.hh"
@ -110,6 +108,7 @@ static QVariant evaluateJavaScriptVariableSafe( QWebFrame * frame, const QString
} }
ArticleView::ArticleView( QWidget * parent, ArticleNetworkAccessManager & nm, ArticleView::ArticleView( QWidget * parent, ArticleNetworkAccessManager & nm,
AudioPlayerPtr const & audioPlayer_,
std::vector< sptr< Dictionary::Class > > const & allDictionaries_, std::vector< sptr< Dictionary::Class > > const & allDictionaries_,
Instances::Groups const & groups_, bool popupView_, Instances::Groups const & groups_, bool popupView_,
Config::Class const & cfg_, Config::Class const & cfg_,
@ -118,6 +117,7 @@ ArticleView::ArticleView( QWidget * parent, ArticleNetworkAccessManager & nm,
GroupComboBox const * groupComboBox_ ): GroupComboBox const * groupComboBox_ ):
QFrame( parent ), QFrame( parent ),
articleNetMgr( nm ), articleNetMgr( nm ),
audioPlayer( audioPlayer_ ),
allDictionaries( allDictionaries_ ), allDictionaries( allDictionaries_ ),
groups( groups_ ), groups( groups_ ),
popupView( popupView_ ), popupView( popupView_ ),
@ -268,11 +268,7 @@ void ArticleView::setGroupComboBox( GroupComboBox const * g )
ArticleView::~ArticleView() ArticleView::~ArticleView()
{ {
cleanupTemp(); cleanupTemp();
audioPlayer->stop();
#ifndef DISABLE_INTERNAL_PLAYER
if ( cfg.preferences.useInternalPlayer )
Ffmpeg::AudioPlayer::instance().stop();
#endif
#if QT_VERSION >= QT_VERSION_CHECK(4, 6, 0) #if QT_VERSION >= QT_VERSION_CHECK(4, 6, 0)
ui.definition->ungrabGesture( Gestures::GDPinchGestureType ); ui.definition->ungrabGesture( Gestures::GDPinchGestureType );
@ -284,12 +280,8 @@ void ArticleView::showDefinition( QString const & word, unsigned group,
QString const & scrollTo, QString const & scrollTo,
Contexts const & contexts_ ) Contexts const & contexts_ )
{ {
#ifndef DISABLE_INTERNAL_PLAYER
// first, let's stop the player // first, let's stop the player
if ( cfg.preferences.useInternalPlayer ) audioPlayer->stop();
Ffmpeg::AudioPlayer::instance().stop();
#endif
QUrl req; QUrl req;
Contexts contexts( contexts_ ); Contexts contexts( contexts_ );
@ -353,11 +345,8 @@ void ArticleView::showDefinition( QString const & word, QStringList const & dict
if( dictIDs.isEmpty() ) if( dictIDs.isEmpty() )
return; return;
#ifndef DISABLE_INTERNAL_PLAYER
// first, let's stop the player // first, let's stop the player
if ( cfg.preferences.useInternalPlayer ) audioPlayer->stop();
Ffmpeg::AudioPlayer::instance().stop();
#endif
QUrl req; QUrl req;
@ -1909,29 +1898,10 @@ void ArticleView::resourceDownloadFinished()
Dictionary::WebMultimediaDownload::isAudioUrl( resourceDownloadUrl ) ) Dictionary::WebMultimediaDownload::isAudioUrl( resourceDownloadUrl ) )
{ {
// Audio data // Audio data
#ifndef DISABLE_INTERNAL_PLAYER connect( audioPlayer.data(), SIGNAL( error( QString ) ), this, SLOT( audioPlayerError( QString ) ), Qt::UniqueConnection );
if ( cfg.preferences.useInternalPlayer ) QString errorMessage = audioPlayer->play( data.data(), data.size() );
{ if( !errorMessage.isEmpty() )
Ffmpeg::AudioPlayer & player = Ffmpeg::AudioPlayer::instance(); QMessageBox::critical( this, "GoldenDict", tr( "Failed to play sound file: %1" ).arg( errorMessage ) );
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() ) );
}
}
} }
else else
{ {
@ -1987,7 +1957,7 @@ void ArticleView::resourceDownloadFinished()
void ArticleView::audioPlayerError( QString const & message ) 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" ) ); 10000, QPixmap( ":/icons/error.png" ) );
} }

View file

@ -10,6 +10,7 @@
#include <QSet> #include <QSet>
#include <list> #include <list>
#include "article_netmgr.hh" #include "article_netmgr.hh"
#include "audioplayerinterface.hh"
#include "instances.hh" #include "instances.hh"
#include "groupcombobox.hh" #include "groupcombobox.hh"
#include "ui_articleview.h" #include "ui_articleview.h"
@ -23,6 +24,7 @@ class ArticleView: public QFrame
Q_OBJECT Q_OBJECT
ArticleNetworkAccessManager & articleNetMgr; ArticleNetworkAccessManager & articleNetMgr;
AudioPlayerPtr const & audioPlayer;
std::vector< sptr< Dictionary::Class > > const & allDictionaries; std::vector< sptr< Dictionary::Class > > const & allDictionaries;
Instances::Groups const & groups; Instances::Groups const & groups;
bool popupView; bool popupView;
@ -68,6 +70,7 @@ public:
/// The groups aren't copied -- rather than that, the reference is kept /// The groups aren't copied -- rather than that, the reference is kept
ArticleView( QWidget * parent, ArticleView( QWidget * parent,
ArticleNetworkAccessManager &, ArticleNetworkAccessManager &,
AudioPlayerPtr const &,
std::vector< sptr< Dictionary::Class > > const & allDictionaries, std::vector< sptr< Dictionary::Class > > const & allDictionaries,
Instances::Groups const &, Instances::Groups const &,
bool popupView, bool popupView,

56
audioplayerfactory.cc Normal file
View 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
View 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
View 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
View 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
View 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

View file

@ -2,22 +2,21 @@
* Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */ * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
#include <QDir> #include <QDir>
#include <QTimer>
#include "externalviewer.hh" #include "externalviewer.hh"
#include "parsecmdline.hh" #include "parsecmdline.hh"
#include "gddebug.hh" #include "gddebug.hh"
using std::vector; ExternalViewer::ExternalViewer( const char * data, int size,
QString const & extension, QString const & viewerCmdLine_,
ExternalViewer::ExternalViewer( QObject * parent, vector< char > const & data, QObject * parent)
QString const & extension,
QString const & viewerCmdLine_ )
throw( exCantCreateTempFile ): throw( exCantCreateTempFile ):
QObject( parent ), QObject( parent ),
tempFile( QDir::temp().filePath( QString( "gd-XXXXXXXX." ) + extension ) ), tempFile( QDir::temp().filePath( QString( "gd-XXXXXXXX." ) + extension ) ),
viewer( this ), viewer( this ),
viewerCmdLine( viewerCmdLine_ ) 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(); throw exCantCreateTempFile();
tempFileName = tempFile.fileName(); // For some reason it loses it after it was closed() tempFileName = tempFile.fileName(); // For some reason it loses it after it was closed()
@ -53,3 +52,29 @@ void ExternalViewer::start() throw( exCantRunViewer )
else else
throw exCantRunViewer( tr( "the viewer program name is empty" ).toUtf8().data() ); 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;
}

View file

@ -7,7 +7,6 @@
#include <QObject> #include <QObject>
#include <QTemporaryFile> #include <QTemporaryFile>
#include <QProcess> #include <QProcess>
#include <vector>
#include "ex.hh" #include "ex.hh"
/// An external viewer, opens resources in other programs /// An external viewer, opens resources in other programs
@ -26,14 +25,26 @@ public:
DEF_EX( exCantCreateTempFile, "Couldn't create temporary file.", Ex ) DEF_EX( exCantCreateTempFile, "Couldn't create temporary file.", Ex )
DEF_EX_STR( exCantRunViewer, "Couldn't run external viewer:", Ex ) DEF_EX_STR( exCantRunViewer, "Couldn't run external viewer:", Ex )
ExternalViewer( QObject * parent, std::vector< char > const & data, ExternalViewer( const char * data, int size,
QString const & extension, QString const & viewerCmdLine ) QString const & extension, QString const & viewerCmdLine,
QObject * parent = 0 )
throw( exCantCreateTempFile ); throw( exCantCreateTempFile );
// Once this is called, the object will be deleted when it's done, even if // Once this is called, the object will be deleted when it's done, even if
// the function throws. // the function throws.
void start() throw( exCantRunViewer ); 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

View file

@ -43,28 +43,28 @@ static inline QString avErrorString( int errnum )
return QString::fromLatin1( buf ); return QString::fromLatin1( buf );
} }
AudioPlayer & AudioPlayer::instance() AudioService & AudioService::instance()
{ {
static AudioPlayer a; static AudioService a;
return a; return a;
} }
AudioPlayer::AudioPlayer() AudioService::AudioService()
{ {
av_register_all(); av_register_all();
ao_initialize(); ao_initialize();
} }
AudioPlayer::~AudioPlayer() AudioService::~AudioService()
{ {
emit cancelPlaying( true ); emit cancelPlaying( true );
ao_shutdown(); ao_shutdown();
} }
void AudioPlayer::playMemory( const void * ptr, int size ) void AudioService::playMemory( const char * ptr, int size )
{ {
emit cancelPlaying( false ); emit cancelPlaying( false );
QByteArray audioData( ( char * )ptr, size ); QByteArray audioData( ptr, size );
DecoderThread * thread = new DecoderThread( audioData, this ); DecoderThread * thread = new DecoderThread( audioData, this );
connect( thread, SIGNAL( error( QString ) ), this, SIGNAL( error( QString ) ) ); connect( thread, SIGNAL( error( QString ) ), this, SIGNAL( error( QString ) ) );
@ -74,7 +74,7 @@ void AudioPlayer::playMemory( const void * ptr, int size )
thread->start(); thread->start();
} }
void AudioPlayer::stop() void AudioService::stop()
{ {
emit cancelPlaying( false ); emit cancelPlaying( false );
} }

View file

@ -12,13 +12,13 @@
namespace Ffmpeg namespace Ffmpeg
{ {
class AudioPlayer : public QObject class AudioService : public QObject
{ {
Q_OBJECT Q_OBJECT
public: public:
static AudioPlayer & instance(); static AudioService & instance();
void playMemory( const void * ptr, int size ); void playMemory( const char * ptr, int size );
void stop(); void stop();
signals: signals:
@ -26,10 +26,8 @@ signals:
void error( QString const & message ); void error( QString const & message );
private: private:
AudioPlayer(); AudioService();
~AudioPlayer(); ~AudioService();
AudioPlayer( AudioPlayer const & );
AudioPlayer & operator=( AudioPlayer const & );
}; };
class DecoderThread: public QThread class DecoderThread: public QThread

41
ffmpegaudioplayer.hh Normal file
View 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

View file

@ -264,6 +264,10 @@ HEADERS += folding.hh \
article_maker.hh \ article_maker.hh \
scanpopup.hh \ scanpopup.hh \
articleview.hh \ articleview.hh \
audioplayerinterface.hh \
audioplayerfactory.hh \
ffmpegaudioplayer.hh \
externalaudioplayer.hh \
externalviewer.hh \ externalviewer.hh \
wordfinder.hh \ wordfinder.hh \
groupcombobox.hh \ groupcombobox.hh \
@ -394,6 +398,8 @@ SOURCES += folding.cc \
article_maker.cc \ article_maker.cc \
scanpopup.cc \ scanpopup.cc \
articleview.cc \ articleview.cc \
audioplayerfactory.cc \
externalaudioplayer.cc \
externalviewer.cc \ externalviewer.cc \
wordfinder.cc \ wordfinder.cc \
groupcombobox.cc \ groupcombobox.cc \

View file

@ -118,6 +118,7 @@ MainWindow::MainWindow( Config::Class & cfg_ ):
articleNetMgr( this, dictionaries, articleMaker, articleNetMgr( this, dictionaries, articleMaker,
cfg.preferences.disallowContentFromOtherSites, cfg.preferences.hideGoldenDictHeader ), cfg.preferences.disallowContentFromOtherSites, cfg.preferences.hideGoldenDictHeader ),
dictNetMgr( this ), dictNetMgr( this ),
audioPlayerFactory( cfg.preferences ),
wordFinder( this ), wordFinder( this ),
newReleaseCheckTimer( this ), newReleaseCheckTimer( this ),
latestReleaseReply( 0 ), latestReleaseReply( 0 ),
@ -1373,8 +1374,8 @@ void MainWindow::makeScanPopup()
!cfg.preferences.enableClipboardHotkey ) !cfg.preferences.enableClipboardHotkey )
return; return;
scanPopup = new ScanPopup( 0, cfg, articleNetMgr, dictionaries, groupInstances, scanPopup = new ScanPopup( 0, cfg, articleNetMgr, audioPlayerFactory.player(),
history ); dictionaries, groupInstances, history );
scanPopup->setStyleSheet( styleSheet() ); scanPopup->setStyleSheet( styleSheet() );
@ -1530,8 +1531,8 @@ void MainWindow::addNewTab()
ArticleView * MainWindow::createNewTab( bool switchToIt, ArticleView * MainWindow::createNewTab( bool switchToIt,
QString const & name ) QString const & name )
{ {
ArticleView * view = new ArticleView( this, articleNetMgr, dictionaries, ArticleView * view = new ArticleView( this, articleNetMgr, audioPlayerFactory.player(),
groupInstances, false, cfg, dictionaries, groupInstances, false, cfg,
*ui.searchInPageAction, *ui.searchInPageAction,
dictionaryBar.toggleViewAction(), dictionaryBar.toggleViewAction(),
groupList ); groupList );
@ -2087,6 +2088,8 @@ void MainWindow::editPreferences()
cfg.preferences = p; cfg.preferences = p;
audioPlayerFactory.setPreferences( cfg.preferences );
beforeScanPopupSeparator->setVisible( cfg.preferences.enableScanPopup ); beforeScanPopupSeparator->setVisible( cfg.preferences.enableScanPopup );
enableScanPopup->setVisible( cfg.preferences.enableScanPopup ); enableScanPopup->setVisible( cfg.preferences.enableScanPopup );
afterScanPopupSeparator->setVisible( cfg.preferences.enableScanPopup ); afterScanPopupSeparator->setVisible( cfg.preferences.enableScanPopup );

View file

@ -15,6 +15,7 @@
#include "config.hh" #include "config.hh"
#include "dictionary.hh" #include "dictionary.hh"
#include "article_netmgr.hh" #include "article_netmgr.hh"
#include "audioplayerfactory.hh"
#include "instances.hh" #include "instances.hh"
#include "article_maker.hh" #include "article_maker.hh"
#include "scanpopup.hh" #include "scanpopup.hh"
@ -151,6 +152,7 @@ private:
QNetworkAccessManager dictNetMgr; // We give dictionaries a separate manager, QNetworkAccessManager dictNetMgr; // We give dictionaries a separate manager,
// since their requests can be destroyed // since their requests can be destroyed
// in a separate thread // in a separate thread
AudioPlayerFactory audioPlayerFactory;
WordList * wordList; WordList * wordList;
QLineEdit * translateLine; QLineEdit * translateLine;

View file

@ -36,6 +36,7 @@ Qt::Popup
ScanPopup::ScanPopup( QWidget * parent, ScanPopup::ScanPopup( QWidget * parent,
Config::Class & cfg_, Config::Class & cfg_,
ArticleNetworkAccessManager & articleNetMgr, ArticleNetworkAccessManager & articleNetMgr,
AudioPlayerPtr const & audioPlayer_,
std::vector< sptr< Dictionary::Class > > const & allDictionaries_, std::vector< sptr< Dictionary::Class > > const & allDictionaries_,
Instances::Groups const & groups_, Instances::Groups const & groups_,
History & history_ ): History & history_ ):
@ -72,8 +73,8 @@ ScanPopup::ScanPopup( QWidget * parent,
ui.queryError->hide(); ui.queryError->hide();
definition = new ArticleView( ui.outerFrame, articleNetMgr, allDictionaries, definition = new ArticleView( ui.outerFrame, articleNetMgr, audioPlayer_,
groups, true, cfg, allDictionaries, groups, true, cfg,
openSearchAction, openSearchAction,
dictionaryBar.toggleViewAction() dictionaryBar.toggleViewAction()
); );

View file

@ -30,6 +30,7 @@ public:
ScanPopup( QWidget * parent, ScanPopup( QWidget * parent,
Config::Class & cfg, Config::Class & cfg,
ArticleNetworkAccessManager &, ArticleNetworkAccessManager &,
AudioPlayerPtr const &,
std::vector< sptr< Dictionary::Class > > const & allDictionaries, std::vector< sptr< Dictionary::Class > > const & allDictionaries,
Instances::Groups const &, Instances::Groups const &,
History & ); History & );