/* This file is (c) 2018 Igor Kushnir * 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.release() ); 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.release(); } 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.get() == destroyedViewer ) { viewer.reset( nullptr ); // viewer finished and died -> reset } } QString ExternalAudioPlayer::startViewer() { Q_ASSERT( !exitingViewer && viewer ); connect( viewer.get(), &QObject::destroyed, this, &ExternalAudioPlayer::onViewerDestroyed ); try { viewer->start(); } catch ( const ExternalViewer::Ex & e ) { viewer.reset(); return e.what(); } return QString(); }