mirror of
https://github.com/xiaoyifang/goldendict-ng.git
synced 2024-11-28 03:44:07 +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
|
### 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)
|
||||||
|
|
|
@ -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" ) );
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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
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 */
|
* 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;
|
||||||
|
}
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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 );
|
||||||
}
|
}
|
||||||
|
|
|
@ -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
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 \
|
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 \
|
||||||
|
|
|
@ -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 );
|
||||||
|
|
|
@ -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;
|
||||||
|
|
|
@ -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()
|
||||||
);
|
);
|
||||||
|
|
|
@ -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 & );
|
||||||
|
|
Loading…
Reference in a new issue