diff --git a/README.md b/README.md
index a182ce02..5925a155 100644
--- a/README.md
+++ b/README.md
@@ -28,7 +28,7 @@ This code has been run and tested on Windows XP/Vista/7, Ubuntu Linux, Mac OS X.
qtdeclarative5-dev libqtwebkit-dev libxtst-dev liblzo2-dev libbz2-dev \
libao-dev libavutil-dev libavformat-dev libtiff5-dev libeb16-dev \
libqt5webkit5-dev libqt5svg5-dev libqt5x11extras5-dev qttools5-dev \
- qttools5-dev-tools
+ qttools5-dev-tools qtmultimedia5-dev libqt5multimedia5-plugins
## How to build
@@ -82,16 +82,21 @@ If you have problem building with libeb-dev package, you can pass
qmake "CONFIG+=no_epwing_support"
-### Building without internal audio player
+### Building without internal audio players
If you have problem building with FFmpeg/libao (for example, Ubuntu older than 12.04), you can pass
-`"DISABLE_INTERNAL_PLAYER=1"` to `qmake` in order to disable internal audio player completely:
+`"CONFIG+=no_ffmpeg_player"` to `qmake` in order to disable FFmpeg+libao internal audio player back end:
- qmake "DISABLE_INTERNAL_PLAYER=1"
+ qmake "CONFIG+=no_ffmpeg_player"
+
+If you have problem building with Qt5 Multimedia or experience GStreamer run-time errors (for example, Ubuntu 14.04), you can pass
+`"CONFIG+=no_qtmultimedia_player"` to `qmake` in order to disable Qt Multimedia internal audio player back end:
+
+ qmake "CONFIG+=no_qtmultimedia_player"
NB: All additional settings for `qmake` that you need must be combined in one `qmake` launch, for example:
- qmake "CONFIG+=zim_support" "CONFIG+=no_extra_tiff_handler" "DISABLE_INTERNAL_PLAYER=1"
+ qmake "CONFIG+=zim_support" "CONFIG+=no_extra_tiff_handler" "CONFIG+=no_ffmpeg_player"
Then, invoke `make clean` before `make` because the setting change:
diff --git a/audioplayerfactory.cc b/audioplayerfactory.cc
index 68184b29..6f0fd0b1 100644
--- a/audioplayerfactory.cc
+++ b/audioplayerfactory.cc
@@ -5,11 +5,13 @@
#include
#include "audioplayerfactory.hh"
#include "ffmpegaudioplayer.hh"
+#include "multimediaaudioplayer.hh"
#include "externalaudioplayer.hh"
#include "gddebug.hh"
AudioPlayerFactory::AudioPlayerFactory( Config::Preferences const & p ) :
useInternalPlayer( p.useInternalPlayer ),
+ internalPlayerBackend( p.internalPlayerBackend ),
audioPlaybackProgram( p.audioPlaybackProgram )
{
reset();
@@ -20,10 +22,17 @@ void AudioPlayerFactory::setPreferences( Config::Preferences const & p )
if( p.useInternalPlayer != useInternalPlayer )
{
useInternalPlayer = p.useInternalPlayer;
+ internalPlayerBackend = p.internalPlayerBackend;
audioPlaybackProgram = p.audioPlaybackProgram;
reset();
}
else
+ if( useInternalPlayer && p.internalPlayerBackend != internalPlayerBackend )
+ {
+ internalPlayerBackend = p.internalPlayerBackend;
+ reset();
+ }
+ else
if( !useInternalPlayer && p.audioPlaybackProgram != audioPlaybackProgram )
{
audioPlaybackProgram = p.audioPlaybackProgram;
@@ -38,16 +47,35 @@ void AudioPlayerFactory::setPreferences( Config::Preferences const & p )
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() );
+ // qobject_cast checks below account for the case when an unsupported backend
+ // is stored in config. After this backend is replaced with the default one
+ // upon preferences saving, the code below does not reset playerPtr with
+ // another object of the same type.
+
+#ifdef MAKE_FFMPEG_PLAYER
+ Q_ASSERT( Config::InternalPlayerBackend::defaultBackend().isFfmpeg()
+ && "Adjust the code below after changing the default backend." );
+
+ if( !internalPlayerBackend.isQtmultimedia() )
+ {
+ if( qobject_cast< Ffmpeg::AudioPlayer * >( playerPtr.data() ) == 0 )
+ playerPtr.reset( new Ffmpeg::AudioPlayer );
+ return;
+ }
+#endif
+
+#ifdef MAKE_QTMULTIMEDIA_PLAYER
+ if( qobject_cast< MultimediaAudioPlayer * >( playerPtr.data() ) == 0 )
+ playerPtr.reset( new MultimediaAudioPlayer );
+ return;
+#endif
}
+
+ QScopedPointer< ExternalAudioPlayer > externalPlayer( new ExternalAudioPlayer );
+ setAudioPlaybackProgram( *externalPlayer );
+ playerPtr.reset( externalPlayer.take() );
}
void AudioPlayerFactory::setAudioPlaybackProgram( ExternalAudioPlayer & externalPlayer )
diff --git a/audioplayerfactory.hh b/audioplayerfactory.hh
index 09fdf43a..3775a81e 100644
--- a/audioplayerfactory.hh
+++ b/audioplayerfactory.hh
@@ -25,6 +25,7 @@ private:
void setAudioPlaybackProgram( ExternalAudioPlayer & externalPlayer );
bool useInternalPlayer;
+ Config::InternalPlayerBackend internalPlayerBackend;
QString audioPlaybackProgram;
AudioPlayerPtr playerPtr;
};
diff --git a/config.cc b/config.cc
index 04f8dc19..9053478c 100644
--- a/config.cc
+++ b/config.cc
@@ -78,6 +78,56 @@ QKeySequence HotKey::toKeySequence() const
return QKeySequence( key1 | modifiers, v2 );
}
+bool InternalPlayerBackend::anyAvailable()
+{
+#if defined( MAKE_FFMPEG_PLAYER ) || defined( MAKE_QTMULTIMEDIA_PLAYER )
+ return true;
+#else
+ return false;
+#endif
+}
+
+InternalPlayerBackend InternalPlayerBackend::defaultBackend()
+{
+#if defined( MAKE_FFMPEG_PLAYER )
+ return ffmpeg();
+#elif defined( MAKE_QTMULTIMEDIA_PLAYER )
+ return qtmultimedia();
+#else
+ return InternalPlayerBackend( QString() );
+#endif
+}
+
+QStringList InternalPlayerBackend::nameList()
+{
+ QStringList result;
+#ifdef MAKE_FFMPEG_PLAYER
+ result.push_back( ffmpeg().uiName() );
+#endif
+#ifdef MAKE_QTMULTIMEDIA_PLAYER
+ result.push_back( qtmultimedia().uiName() );
+#endif
+ return result;
+}
+
+bool InternalPlayerBackend::isFfmpeg() const
+{
+#ifdef MAKE_FFMPEG_PLAYER
+ return *this == ffmpeg();
+#else
+ return false;
+#endif
+}
+
+bool InternalPlayerBackend::isQtmultimedia() const
+{
+#ifdef MAKE_QTMULTIMEDIA_PLAYER
+ return *this == qtmultimedia();
+#else
+ return false;
+#endif
+}
+
Preferences::Preferences():
newTabsOpenAfterCurrentOne( false ),
newTabsOpenInBackground( true ),
@@ -114,11 +164,8 @@ Preferences::Preferences():
#endif
pronounceOnLoadMain( false ),
pronounceOnLoadPopup( false ),
-#ifndef DISABLE_INTERNAL_PLAYER
- useInternalPlayer( true ),
-#else
- useInternalPlayer( false ),
-#endif
+ useInternalPlayer( InternalPlayerBackend::anyAvailable() ),
+ internalPlayerBackend( InternalPlayerBackend::defaultBackend() ),
checkForNewReleases( true ),
disallowContentFromOtherSites( false ),
enableWebPlugins( false ),
@@ -769,12 +816,16 @@ Class load() throw( exError )
c.preferences.pronounceOnLoadMain = ( preferences.namedItem( "pronounceOnLoadMain" ).toElement().text() == "1" );
c.preferences.pronounceOnLoadPopup = ( preferences.namedItem( "pronounceOnLoadPopup" ).toElement().text() == "1" );
-#ifndef DISABLE_INTERNAL_PLAYER
- if ( !preferences.namedItem( "useInternalPlayer" ).isNull() )
- c.preferences.useInternalPlayer = ( preferences.namedItem( "useInternalPlayer" ).toElement().text() == "1" );
-#else
- c.preferences.useInternalPlayer = false;
-#endif
+ if ( InternalPlayerBackend::anyAvailable() )
+ {
+ if ( !preferences.namedItem( "useInternalPlayer" ).isNull() )
+ c.preferences.useInternalPlayer = ( preferences.namedItem( "useInternalPlayer" ).toElement().text() == "1" );
+ }
+ else
+ c.preferences.useInternalPlayer = false;
+
+ if ( !preferences.namedItem( "internalPlayerBackend" ).isNull() )
+ c.preferences.internalPlayerBackend.setUiName( preferences.namedItem( "internalPlayerBackend" ).toElement().text() );
if ( !preferences.namedItem( "audioPlaybackProgram" ).isNull() )
c.preferences.audioPlaybackProgram = preferences.namedItem( "audioPlaybackProgram" ).toElement().text();
@@ -1660,6 +1711,10 @@ void save( Class const & c ) throw( exError )
opt.appendChild( dd.createTextNode( c.preferences.useInternalPlayer ? "1" : "0" ) );
preferences.appendChild( opt );
+ opt = dd.createElement( "internalPlayerBackend" );
+ opt.appendChild( dd.createTextNode( c.preferences.internalPlayerBackend.uiName() ) );
+ preferences.appendChild( opt );
+
opt = dd.createElement( "audioPlaybackProgram" );
opt.appendChild( dd.createTextNode( c.preferences.audioPlaybackProgram ) );
preferences.appendChild( opt );
diff --git a/config.hh b/config.hh
index 435bc9e9..eff158ce 100644
--- a/config.hh
+++ b/config.hh
@@ -177,6 +177,52 @@ struct FullTextSearch
{}
};
+/// This class encapsulates supported backend preprocessor logic,
+/// discourages duplicating backend names in code, which is error-prone.
+class InternalPlayerBackend
+{
+public:
+ /// Returns true if at least one backend is available.
+ static bool anyAvailable();
+ /// Returns the default backend or null backend if none is available.
+ static InternalPlayerBackend defaultBackend();
+ /// Returns the name list of supported backends.
+ static QStringList nameList();
+
+ /// Returns true if built with FFmpeg player support and the name matches.
+ bool isFfmpeg() const;
+ /// Returns true if built with Qt Multimedia player support and the name matches.
+ bool isQtmultimedia() const;
+
+ QString const & uiName() const
+ { return name; }
+
+ void setUiName( QString const & name_ )
+ { name = name_; }
+
+ bool operator == ( InternalPlayerBackend const & other ) const
+ { return name == other.name; }
+
+ bool operator != ( InternalPlayerBackend const & other ) const
+ { return ! operator == ( other ); }
+
+private:
+#ifdef MAKE_FFMPEG_PLAYER
+ static InternalPlayerBackend ffmpeg()
+ { return InternalPlayerBackend( "FFmpeg+libao" ); }
+#endif
+
+#ifdef MAKE_QTMULTIMEDIA_PLAYER
+ static InternalPlayerBackend qtmultimedia()
+ { return InternalPlayerBackend( "Qt Multimedia" ); }
+#endif
+
+ explicit InternalPlayerBackend( QString const & name_ ) : name( name_ )
+ {}
+
+ QString name;
+};
+
/// Various user preferences
struct Preferences
{
@@ -222,8 +268,9 @@ struct Preferences
// Whether the word should be pronounced on page load, in main window/popup
bool pronounceOnLoadMain, pronounceOnLoadPopup;
- QString audioPlaybackProgram;
bool useInternalPlayer;
+ InternalPlayerBackend internalPlayerBackend;
+ QString audioPlaybackProgram;
ProxyServer proxyServer;
diff --git a/ffmpegaudio.cc b/ffmpegaudio.cc
index bc37822f..ed1172bd 100644
--- a/ffmpegaudio.cc
+++ b/ffmpegaudio.cc
@@ -1,4 +1,4 @@
-#ifndef DISABLE_INTERNAL_PLAYER
+#ifdef MAKE_FFMPEG_PLAYER
#include "ffmpegaudio.hh"
@@ -589,4 +589,4 @@ void DecoderThread::cancel( bool waitUntilFinished )
}
-#endif //DISABLE_INTERNAL_PLAYER
+#endif // MAKE_FFMPEG_PLAYER
diff --git a/ffmpegaudio.hh b/ffmpegaudio.hh
index 2dbda7bb..c1425345 100644
--- a/ffmpegaudio.hh
+++ b/ffmpegaudio.hh
@@ -1,7 +1,7 @@
#ifndef __FFMPEGAUDIO_HH_INCLUDED__
#define __FFMPEGAUDIO_HH_INCLUDED__
-#ifndef DISABLE_INTERNAL_PLAYER
+#ifdef MAKE_FFMPEG_PLAYER
#include
#include
@@ -52,6 +52,6 @@ signals:
}
-#endif // DISABLE_INTERNAL_PLAYER
+#endif // MAKE_FFMPEG_PLAYER
#endif // __FFMPEGAUDIO_HH_INCLUDED__
diff --git a/ffmpegaudioplayer.hh b/ffmpegaudioplayer.hh
index bfce56e6..49ddbf8b 100644
--- a/ffmpegaudioplayer.hh
+++ b/ffmpegaudioplayer.hh
@@ -7,7 +7,7 @@
#include "audioplayerinterface.hh"
#include "ffmpegaudio.hh"
-#ifndef DISABLE_INTERNAL_PLAYER
+#ifdef MAKE_FFMPEG_PLAYER
namespace Ffmpeg
{
@@ -36,6 +36,6 @@ public:
}
-#endif // DISABLE_INTERNAL_PLAYER
+#endif // MAKE_FFMPEG_PLAYER
#endif // FFMPEGAUDIOPLAYER_HH_INCLUDED
diff --git a/goldendict.pro b/goldendict.pro
index fa1505b2..788a5e97 100644
--- a/goldendict.pro
+++ b/goldendict.pro
@@ -28,11 +28,21 @@ greaterThan(QT_MAJOR_VERSION, 4) {
webkitwidgets \
printsupport \
help
+
+ # QMediaPlayer is not available in Qt4.
+ !CONFIG( no_qtmultimedia_player ) {
+ QT += multimedia
+ DEFINES += MAKE_QTMULTIMEDIA_PLAYER
+ }
} else {
QT += webkit
CONFIG += help
}
+!CONFIG( no_ffmpeg_player ) {
+ DEFINES += MAKE_FFMPEG_PLAYER
+}
+
QT += sql
CONFIG += exceptions \
rtti \
@@ -46,8 +56,6 @@ LIBS += \
-lbz2 \
-llzo2
-!isEmpty(DISABLE_INTERNAL_PLAYER): DEFINES += DISABLE_INTERNAL_PLAYER
-
win32 {
TARGET = GoldenDict
@@ -97,7 +105,7 @@ win32 {
LIBS += -lvorbisfile \
-lvorbis \
-logg
- isEmpty(DISABLE_INTERNAL_PLAYER) {
+ !CONFIG( no_ffmpeg_player ) {
LIBS += -lao \
-lavutil-gd \
-lavformat-gd \
@@ -143,7 +151,7 @@ unix:!mac {
vorbis \
ogg \
hunspell
- isEmpty(DISABLE_INTERNAL_PLAYER) {
+ !CONFIG( no_ffmpeg_player ) {
PKGCONFIG += ao \
libavutil \
libavformat \
@@ -193,7 +201,7 @@ mac {
-logg \
-lhunspell-1.6.1 \
-llzo2
- isEmpty(DISABLE_INTERNAL_PLAYER) {
+ !CONFIG( no_ffmpeg_player ) {
LIBS += -lao \
-lavutil-gd \
-lavformat-gd \
@@ -267,6 +275,7 @@ HEADERS += folding.hh \
audioplayerinterface.hh \
audioplayerfactory.hh \
ffmpegaudioplayer.hh \
+ multimediaaudioplayer.hh \
externalaudioplayer.hh \
externalviewer.hh \
wordfinder.hh \
@@ -399,6 +408,7 @@ SOURCES += folding.cc \
scanpopup.cc \
articleview.cc \
audioplayerfactory.cc \
+ multimediaaudioplayer.cc \
externalaudioplayer.cc \
externalviewer.cc \
wordfinder.cc \
diff --git a/multimediaaudioplayer.cc b/multimediaaudioplayer.cc
new file mode 100644
index 00000000..f4f10990
--- /dev/null
+++ b/multimediaaudioplayer.cc
@@ -0,0 +1,43 @@
+/* This file is (c) 2018 Igor Kushnir
+ * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
+
+#ifdef MAKE_QTMULTIMEDIA_PLAYER
+
+#include
+#include
+#include "multimediaaudioplayer.hh"
+
+MultimediaAudioPlayer::MultimediaAudioPlayer() :
+ player( 0, QMediaPlayer::StreamPlayback )
+{
+ typedef void( QMediaPlayer::* ErrorSignal )( QMediaPlayer::Error );
+ connect( &player, static_cast< ErrorSignal >( &QMediaPlayer::error ),
+ this, &MultimediaAudioPlayer::onMediaPlayerError );
+}
+
+QString MultimediaAudioPlayer::play( const char * data, int size )
+{
+ stop();
+
+ audioBuffer.setData( data, size );
+ if( !audioBuffer.open( QIODevice::ReadOnly ) )
+ return tr( "Couldn't open audio buffer for reading." );
+
+ player.setMedia( QMediaContent(), &audioBuffer );
+ player.play();
+ return QString();
+}
+
+void MultimediaAudioPlayer::stop()
+{
+ player.setMedia( QMediaContent() ); // Forget about audioBuffer.
+ audioBuffer.close();
+ audioBuffer.setData( QByteArray() ); // Free memory.
+}
+
+void MultimediaAudioPlayer::onMediaPlayerError()
+{
+ emit error( player.errorString() );
+}
+
+#endif // MAKE_QTMULTIMEDIA_PLAYER
diff --git a/multimediaaudioplayer.hh b/multimediaaudioplayer.hh
new file mode 100644
index 00000000..873af412
--- /dev/null
+++ b/multimediaaudioplayer.hh
@@ -0,0 +1,32 @@
+/* This file is (c) 2018 Igor Kushnir
+ * Part of GoldenDict. Licensed under GPLv3 or later, see the LICENSE file */
+
+#ifndef MULTIMEDIAAUDIOPLAYER_HH_INCLUDED
+#define MULTIMEDIAAUDIOPLAYER_HH_INCLUDED
+
+#ifdef MAKE_QTMULTIMEDIA_PLAYER
+
+#include
+#include
+#include "audioplayerinterface.hh"
+
+class MultimediaAudioPlayer : public AudioPlayerInterface
+{
+ Q_OBJECT
+public:
+ MultimediaAudioPlayer();
+
+ virtual QString play( const char * data, int size );
+ virtual void stop();
+
+private slots:
+ void onMediaPlayerError();
+
+private:
+ QBuffer audioBuffer;
+ QMediaPlayer player; ///< Depends on audioBuffer.
+};
+
+#endif // MAKE_QTMULTIMEDIA_PLAYER
+
+#endif // MULTIMEDIAAUDIOPLAYER_HH_INCLUDED
diff --git a/preferences.cc b/preferences.cc
index e4f53ea0..ee82614c 100644
--- a/preferences.cc
+++ b/preferences.cc
@@ -243,14 +243,28 @@ Preferences::Preferences( QWidget * parent, Config::Class & cfg_ ):
ui.pronounceOnLoadMain->setChecked( p.pronounceOnLoadMain );
ui.pronounceOnLoadPopup->setChecked( p.pronounceOnLoadPopup );
-#ifdef DISABLE_INTERNAL_PLAYER
- ui.useInternalPlayer->hide();
-#else
- if ( p.useInternalPlayer )
- ui.useInternalPlayer->setChecked( true );
+ ui.internalPlayerBackend->addItems( Config::InternalPlayerBackend::nameList() );
+
+ // Make sure that exactly one radio button in the group is checked and that
+ // on_useExternalPlayer_toggled() is called.
+ ui.useExternalPlayer->setChecked( true );
+
+ if( ui.internalPlayerBackend->count() > 0 )
+ {
+ // Checking ui.useInternalPlayer automatically unchecks ui.useExternalPlayer.
+ ui.useInternalPlayer->setChecked( p.useInternalPlayer );
+
+ int index = ui.internalPlayerBackend->findText( p.internalPlayerBackend.uiName() );
+ if( index < 0 ) // The specified backend is unavailable.
+ index = ui.internalPlayerBackend->findText( Config::InternalPlayerBackend::defaultBackend().uiName() );
+ Q_ASSERT( index >= 0 && "Logic error: the default backend must be present in the backend name list." );
+ ui.internalPlayerBackend->setCurrentIndex( index );
+ }
else
-#endif
- ui.useExternalPlayer->setChecked( true );
+ {
+ ui.useInternalPlayer->hide();
+ ui.internalPlayerBackend->hide();
+ }
ui.audioPlaybackProgram->setText( p.audioPlaybackProgram );
@@ -400,6 +414,7 @@ Config::Preferences Preferences::getPreferences()
p.pronounceOnLoadMain = ui.pronounceOnLoadMain->isChecked();
p.pronounceOnLoadPopup = ui.pronounceOnLoadPopup->isChecked();
p.useInternalPlayer = ui.useInternalPlayer->isChecked();
+ p.internalPlayerBackend.setUiName( ui.internalPlayerBackend->currentText() );
p.audioPlaybackProgram = ui.audioPlaybackProgram->text();
p.proxyServer.enabled = ui.useProxyServer->isChecked();
@@ -592,6 +607,7 @@ void Preferences::on_buttonBox_accepted()
void Preferences::on_useExternalPlayer_toggled( bool enabled )
{
+ ui.internalPlayerBackend->setEnabled( !enabled );
ui.audioPlaybackProgram->setEnabled( enabled );
}
diff --git a/preferences.ui b/preferences.ui
index 2e1ecee6..3e47d6fe 100644
--- a/preferences.ui
+++ b/preferences.ui
@@ -902,37 +902,43 @@ p, li { white-space: pre-wrap; }
Playback
-
- -
+
+
-
- Play audio files via FFmpeg(libav) and libao
+ Play audio files via built-in audio support
- Use internal player
+ Use internal player:
- -
-
-
-
-
-
- Use any external program to play audio files
-
-
- Use external program:
-
-
-
- -
-
-
- false
-
-
-
-
+ -
+
+
+ Choose audio back end
+
+
+
+ -
+
+
+ Use any external program to play audio files
+
+
+ Use external program:
+
+
+
+ -
+
+
+ false
+
+
+ Enter audio player command line
+
+