Use Phonon framework for audio playback.

This is now the default way to play back sound under Windows. The other one,
"Use external program", was also made available there. The old method of audio
playback under Windows (via PlaySound) was completely removed.
Since Phonon uses DirectShow as a backend under Windows, all file types
supported by it should now play back. Therefore, the previous limitation to
just .wav files is now lifted.
Since Phonon is only available starting from Qt 4.6.0 under Windows, no older
versions of Qt are supported anymore there.
This commit is contained in:
Konstantin Isakov 2010-01-02 21:16:22 +03:00
parent 0e187e1e76
commit c25e7f268e
9 changed files with 153 additions and 73 deletions

View file

@ -14,14 +14,38 @@
#include "folding.hh" #include "folding.hh"
#include "wstring_qt.hh" #include "wstring_qt.hh"
#ifdef Q_OS_WIN32 #include <QBuffer>
#include <windows.h>
#include <mmsystem.h> // For PlaySound
#endif
using std::map; using std::map;
using std::list; using std::list;
/// A phonon-based audio player, created on demand
struct AudioPlayer
{
Phonon::AudioOutput output;
Phonon::MediaObject object;
static AudioPlayer & instance();
private:
AudioPlayer();
};
AudioPlayer::AudioPlayer():
output( Phonon::AccessibilityCategory )
{
Phonon::createPath( &object, &output );
}
AudioPlayer & AudioPlayer::instance()
{
static AudioPlayer a;
return a;
}
ArticleView::ArticleView( QWidget * parent, ArticleNetworkAccessManager & nm, ArticleView::ArticleView( QWidget * parent, ArticleNetworkAccessManager & nm,
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_,
@ -118,15 +142,6 @@ void ArticleView::setGroupComboBox( GroupComboBox const * g )
ArticleView::~ArticleView() ArticleView::~ArticleView()
{ {
cleanupTemp(); cleanupTemp();
#ifdef Q_OS_WIN32
if ( winWavData.size() )
{
// If we were playing some sound some time ago, make sure it stopped
// playing before freeing the waveform memory.
PlaySoundA( 0, 0, 0 );
}
#endif
} }
void ArticleView::showDefinition( QString const & word, unsigned group, void ArticleView::showDefinition( QString const & word, unsigned group,
@ -945,42 +960,49 @@ void ArticleView::resourceDownloadFinished()
if ( resourceDownloadUrl.scheme() == "gdau" ) if ( resourceDownloadUrl.scheme() == "gdau" )
{ {
#ifdef Q_OS_WIN32 // Audio data
// Windows-only: use system PlaySound function
if ( winWavData.size() ) if ( !cfg.preferences.useExternalPlayer )
PlaySoundA( 0, 0, 0 ); // Stop any currently playing sound to make sure
// previous data isn't used anymore
//
winWavData = data;
PlaySoundA( &winWavData.front(), 0,
SND_ASYNC | SND_MEMORY | SND_NODEFAULT | SND_NOWAIT );
#else
// Use external viewer to play the file
try
{ {
ExternalViewer * viewer = new ExternalViewer( this, data, "wav", cfg.preferences.audioPlaybackProgram.trimmed() ); // Play via Phonon
QBuffer * buf = new QBuffer;
buf->buffer().append( &data.front(), data.size() );
Phonon::MediaSource source( buf );
source.setAutoDelete( true ); // Dispose of our buf when done
AudioPlayer::instance().object.stop();
AudioPlayer::instance().object.clear();
AudioPlayer::instance().object.enqueue( source );
AudioPlayer::instance().object.play();
}
else
{
// Use external viewer to play the file
try try
{ {
viewer->start(); ExternalViewer * viewer = new ExternalViewer( this, data, "wav", cfg.preferences.audioPlaybackProgram.trimmed() );
// Once started, it will erase itself try
{
viewer->start();
// Once started, it will erase itself
}
catch( ... )
{
delete viewer;
throw;
}
} }
catch( ... ) catch( ExternalViewer::Ex & e )
{ {
delete viewer; QMessageBox::critical( this, tr( "GoldenDict" ), tr( "Failed to run a player to play sound file: %1" ).arg( e.what() ) );
throw;
} }
} }
catch( ExternalViewer::Ex & e )
{
QMessageBox::critical( this, tr( "GoldenDict" ), tr( "Failed to run a player to play sound file: %1" ).arg( e.what() ) );
}
#endif
} }
else else
{ {

View file

@ -12,6 +12,7 @@
#include "instances.hh" #include "instances.hh"
#include "groupcombobox.hh" #include "groupcombobox.hh"
#include "ui_articleview.h" #include "ui_articleview.h"
#include <phonon>
/// A widget with the web view tailored to view and handle articles -- it /// A widget with the web view tailored to view and handle articles -- it
/// uses the appropriate netmgr, handles link clicks, rmb clicks etc /// uses the appropriate netmgr, handles link clicks, rmb clicks etc
@ -31,11 +32,6 @@ class ArticleView: public QFrame
goBackAction, goForwardAction, openSearchAction; goBackAction, goForwardAction, openSearchAction;
bool searchIsOpened; bool searchIsOpened;
#ifdef Q_OS_WIN32
// Used in Windows only
vector< char > winWavData;
#endif
/// Any resource we've decided to download off the dictionary gets stored here. /// Any resource we've decided to download off the dictionary gets stored here.
/// Full vector capacity is used for search requests, where we have to make /// Full vector capacity is used for search requests, where we have to make
/// a multitude of requests. /// a multitude of requests.

View file

@ -94,6 +94,11 @@ Preferences::Preferences():
scanPopupAltModeSecs( 3 ), scanPopupAltModeSecs( 3 ),
pronounceOnLoadMain( false ), pronounceOnLoadMain( false ),
pronounceOnLoadPopup( false ), pronounceOnLoadPopup( false ),
#ifdef Q_WS_WIN
useExternalPlayer( false ),
#else
useExternalPlayer( true ), // Phonon on Linux still feels quite buggy
#endif
checkForNewReleases( true ), checkForNewReleases( true ),
disallowContentFromOtherSites( false ), disallowContentFromOtherSites( false ),
zoomFactor( 1 ) zoomFactor( 1 )
@ -487,6 +492,9 @@ Class load() throw( exError )
c.preferences.pronounceOnLoadMain = ( preferences.namedItem( "pronounceOnLoadMain" ).toElement().text() == "1" ); c.preferences.pronounceOnLoadMain = ( preferences.namedItem( "pronounceOnLoadMain" ).toElement().text() == "1" );
c.preferences.pronounceOnLoadPopup = ( preferences.namedItem( "pronounceOnLoadPopup" ).toElement().text() == "1" ); c.preferences.pronounceOnLoadPopup = ( preferences.namedItem( "pronounceOnLoadPopup" ).toElement().text() == "1" );
if ( !preferences.namedItem( "useExternalPlayer" ).isNull() )
c.preferences.useExternalPlayer = ( preferences.namedItem( "useExternalPlayer" ).toElement().text() == "1" );
if ( !preferences.namedItem( "audioPlaybackProgram" ).isNull() ) if ( !preferences.namedItem( "audioPlaybackProgram" ).isNull() )
c.preferences.audioPlaybackProgram = preferences.namedItem( "audioPlaybackProgram" ).toElement().text(); c.preferences.audioPlaybackProgram = preferences.namedItem( "audioPlaybackProgram" ).toElement().text();
@ -892,6 +900,10 @@ void save( Class const & c ) throw( exError )
opt.appendChild( dd.createTextNode( c.preferences.pronounceOnLoadPopup ? "1" : "0" ) ); opt.appendChild( dd.createTextNode( c.preferences.pronounceOnLoadPopup ? "1" : "0" ) );
preferences.appendChild( opt ); preferences.appendChild( opt );
opt = dd.createElement( "useExternalPlayer" );
opt.appendChild( dd.createTextNode( c.preferences.useExternalPlayer ? "1" : "0" ) );
preferences.appendChild( opt );
opt = dd.createElement( "audioPlaybackProgram" ); opt = dd.createElement( "audioPlaybackProgram" );
opt.appendChild( dd.createTextNode( c.preferences.audioPlaybackProgram ) ); opt.appendChild( dd.createTextNode( c.preferences.audioPlaybackProgram ) );
preferences.appendChild( opt ); preferences.appendChild( opt );

View file

@ -156,6 +156,7 @@ struct Preferences
// Whether the word should be pronounced on page load, in main window/popup // Whether the word should be pronounced on page load, in main window/popup
bool pronounceOnLoadMain, pronounceOnLoadPopup; bool pronounceOnLoadMain, pronounceOnLoadPopup;
QString audioPlaybackProgram; QString audioPlaybackProgram;
bool useExternalPlayer;
ProxyServer proxyServer; ProxyServer proxyServer;

View file

@ -52,7 +52,16 @@ bool isNameOfSound( string const & name )
endsWith( s, ".au" ) || endsWith( s, ".au" ) ||
endsWith( s, ".voc" ) || endsWith( s, ".voc" ) ||
endsWith( s, ".ogg" ) || endsWith( s, ".ogg" ) ||
endsWith( s, ".mp3" ); endsWith( s, ".mp3" ) ||
endsWith( s, ".mp4" ) ||
endsWith( s, ".aac" ) ||
endsWith( s, ".flac" ) ||
endsWith( s, ".mid" ) ||
endsWith( s, ".kar" ) ||
endsWith( s, ".mpc" ) ||
endsWith( s, ".wma" ) ||
endsWith( s, ".wv" ) ||
endsWith( s, ".ape" );
} }
bool isNameOfPicture( string const & name ) bool isNameOfPicture( string const & name )

View file

@ -10,6 +10,7 @@ INCLUDEPATH += .
QT += webkit QT += webkit
QT += xml QT += xml
QT += network QT += network
QT += phonon
CONFIG += exceptions \ CONFIG += exceptions \
rtti \ rtti \
stl stl

View file

@ -125,16 +125,18 @@ Preferences::Preferences( QWidget * parent, Config::Preferences const & p ):
// Sound // Sound
#ifdef Q_WS_WIN
// Since there's only one Phonon backend under Windows, be more precise
ui.playViaPhonon->setText( tr( "Play via DirectShow" ) );
#endif
ui.pronounceOnLoadPopup->setEnabled( p.enableScanPopup ); ui.pronounceOnLoadPopup->setEnabled( p.enableScanPopup );
ui.pronounceOnLoadMain->setChecked( p.pronounceOnLoadMain ); ui.pronounceOnLoadMain->setChecked( p.pronounceOnLoadMain );
ui.pronounceOnLoadPopup->setChecked( p.pronounceOnLoadPopup ); ui.pronounceOnLoadPopup->setChecked( p.pronounceOnLoadPopup );
ui.audioPlaybackProgram->setText( p.audioPlaybackProgram );
#ifdef Q_OS_WIN32 ui.useExternalPlayer->setChecked( p.useExternalPlayer );
ui.audioPlaybackProgram->hide(); ui.audioPlaybackProgram->setText( p.audioPlaybackProgram );
ui.audioPlaybackProgramLabel->hide();
#endif
if ( !isRECORDBroken() ) if ( !isRECORDBroken() )
ui.brokenXRECORD->hide(); ui.brokenXRECORD->hide();
@ -203,6 +205,7 @@ Config::Preferences Preferences::getPreferences()
p.pronounceOnLoadMain = ui.pronounceOnLoadMain->isChecked(); p.pronounceOnLoadMain = ui.pronounceOnLoadMain->isChecked();
p.pronounceOnLoadPopup = ui.pronounceOnLoadPopup->isChecked(); p.pronounceOnLoadPopup = ui.pronounceOnLoadPopup->isChecked();
p.useExternalPlayer = ui.useExternalPlayer->isChecked();
p.audioPlaybackProgram = ui.audioPlaybackProgram->text(); p.audioPlaybackProgram = ui.audioPlaybackProgram->text();
p.proxyServer.enabled = ui.useProxyServer->isChecked(); p.proxyServer.enabled = ui.useProxyServer->isChecked();
@ -294,3 +297,8 @@ void Preferences::on_buttonBox_accepted()
tr( "Restart the program to apply the language change." ) ); tr( "Restart the program to apply the language change." ) );
} }
void Preferences::on_useExternalPlayer_toggled( bool enabled )
{
ui.audioPlaybackProgram->setEnabled( enabled );
}

View file

@ -39,6 +39,8 @@ private slots:
void on_enableClipboardHotkey_toggled( bool checked ); void on_enableClipboardHotkey_toggled( bool checked );
void on_buttonBox_accepted(); void on_buttonBox_accepted();
void on_useExternalPlayer_toggled( bool enabled );
}; };
#endif #endif

View file

@ -703,33 +703,65 @@ p, li { white-space: pre-wrap; }
</spacer> </spacer>
</item> </item>
<item> <item>
<widget class="QCheckBox" name="pronounceOnLoadMain"> <widget class="QGroupBox" name="groupBox_3">
<property name="text"> <property name="title">
<string>Auto-pronounce words in main window</string> <string>Pronunciation</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_15">
<item>
<widget class="QCheckBox" name="pronounceOnLoadMain">
<property name="text">
<string>Auto-pronounce words in main window</string>
</property>
</widget>
</item>
<item>
<widget class="QCheckBox" name="pronounceOnLoadPopup">
<property name="text">
<string>Auto-pronounce words in scan popup</string>
</property>
</widget>
</item>
</layout>
</widget> </widget>
</item> </item>
<item> <item>
<widget class="QCheckBox" name="pronounceOnLoadPopup"> <widget class="QGroupBox" name="groupBox_4">
<property name="text"> <property name="title">
<string>Auto-pronounce words in scan popup</string> <string>Playback</string>
</property> </property>
<layout class="QVBoxLayout" name="verticalLayout_16">
<item>
<widget class="QRadioButton" name="playViaPhonon">
<property name="text">
<string>Play via Phonon</string>
</property>
<property name="checked">
<bool>true</bool>
</property>
</widget>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_12">
<item>
<widget class="QRadioButton" name="useExternalPlayer">
<property name="text">
<string>Use external program:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="audioPlaybackProgram">
<property name="enabled">
<bool>false</bool>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget> </widget>
</item> </item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_4">
<item>
<widget class="QLabel" name="audioPlaybackProgramLabel">
<property name="text">
<string>Program to play audio files:</string>
</property>
</widget>
</item>
<item>
<widget class="QLineEdit" name="audioPlaybackProgram"/>
</item>
</layout>
</item>
<item> <item>
<spacer name="verticalSpacer_2"> <spacer name="verticalSpacer_2">
<property name="orientation"> <property name="orientation">
@ -973,9 +1005,6 @@ download page.</string>
<tabstop>winKey</tabstop> <tabstop>winKey</tabstop>
<tabstop>scanPopupAltMode</tabstop> <tabstop>scanPopupAltMode</tabstop>
<tabstop>scanPopupAltModeSecs</tabstop> <tabstop>scanPopupAltModeSecs</tabstop>
<tabstop>pronounceOnLoadMain</tabstop>
<tabstop>pronounceOnLoadPopup</tabstop>
<tabstop>audioPlaybackProgram</tabstop>
<tabstop>useProxyServer</tabstop> <tabstop>useProxyServer</tabstop>
<tabstop>proxyType</tabstop> <tabstop>proxyType</tabstop>
<tabstop>proxyHost</tabstop> <tabstop>proxyHost</tabstop>