File indexing completed on 2024-05-05 04:51:42

0001 /*
0002     SPDX-FileCopyrightText: 2010 Michal Malek <michalm@jabster.pl>
0003     SPDX-FileCopyrightText: 1998-2007 Sebastian Trueg <trueg@k3b.org>
0004 
0005     SPDX-License-Identifier: GPL-2.0-or-later
0006 */
0007 
0008 #include "k3baudiotrackplayer.h"
0009 #include "k3baction.h"
0010 #include "k3baudiodoc.h"
0011 #include "k3baudiodocreader.h"
0012 #include "k3baudiotrack.h"
0013 #include "k3baudiotrackreader.h"
0014 #include "k3bmsf.h"
0015 
0016 #include <KLocalizedString>
0017 #include <KActionCollection>
0018 
0019 #include <QAudioDeviceInfo>
0020 #include <QAudioFormat>
0021 #include <QAudioOutput>
0022 #include <QAction>
0023 #include <QSlider>
0024 #include <QToolTip>
0025 #include <QWidgetAction>
0026 
0027 
0028 namespace K3b {
0029 
0030 namespace {
0031 
0032 class AudioTrackPlayerSeekAction : public QWidgetAction
0033 {
0034 public:
0035     AudioTrackPlayerSeekAction( AudioTrackPlayer* player, QObject* parent );
0036 
0037     void setValue( int value );
0038     void setCurrentTrack( const K3b::AudioTrack& track );
0039     void reset();
0040 
0041 protected:
0042     virtual QWidget* createWidget( QWidget* parent );
0043 
0044 private:
0045     AudioTrackPlayer* m_player;
0046 };
0047 
0048 
0049 AudioTrackPlayerSeekAction::AudioTrackPlayerSeekAction( AudioTrackPlayer* player, QObject* parent )
0050     : QWidgetAction( parent ),
0051       m_player( player )
0052 {
0053 }
0054 
0055 
0056 void AudioTrackPlayerSeekAction::setValue( int value )
0057 {
0058     Q_FOREACH( QWidget* widget, createdWidgets() ) {
0059         if( QSlider* slider = qobject_cast<QSlider*>( widget ) ) {
0060             slider->setValue( value );
0061         }
0062     }
0063 }
0064 
0065 
0066 
0067 void AudioTrackPlayerSeekAction::reset()
0068 {
0069     setValue( 0 );
0070 }
0071 
0072 
0073 void AudioTrackPlayerSeekAction::setCurrentTrack( const K3b::AudioTrack& track )
0074 {
0075     Q_FOREACH( QWidget* widget, createdWidgets() ) {
0076         if( QSlider* slider = qobject_cast<QSlider*>( widget ) ) {
0077             // we show the currently playing track as a tooltip on the slider
0078             slider->setToolTip( i18n("Playing track %1: %2 - %3",
0079                                 track.trackNumber(),
0080                                 track.artist(),
0081                                 track.title()) );
0082             slider->setMaximum( track.length().audioBytes() );
0083         }
0084     }
0085 }
0086 
0087 
0088 QWidget* AudioTrackPlayerSeekAction::createWidget( QWidget* container)
0089 {
0090     QSlider* slider = new QSlider( container );
0091     slider->setRange( 0, 100 );
0092     slider->setSingleStep( Msf::fromSeconds( 10 ).audioBytes() );
0093     slider->setPageStep( Msf::fromSeconds( 30 ).audioBytes() );
0094     slider->setOrientation( Qt::Horizontal );
0095     slider->setTracking( false );
0096     connect( slider, SIGNAL(valueChanged(int)), m_player, SLOT(slotSeek(int)) );
0097     return slider;
0098 }
0099 
0100 } // namespace
0101 
0102 
0103 class AudioTrackPlayer::Private
0104 {
0105 public:
0106     AudioDoc* doc;
0107     AudioDocReader* audioDocReader;
0108     QAudioOutput* audioOutput;
0109 
0110     QAction* actionPlay;
0111     QAction* actionPause;
0112     QAction* actionStop;
0113     QAction* actionNext;
0114     QAction* actionPrevious;
0115     AudioTrackPlayerSeekAction* actionSeek;
0116     State state;
0117 };
0118 
0119 
0120 AudioTrackPlayer::AudioTrackPlayer( AudioDoc* doc, KActionCollection* actionCollection, QObject* parent )
0121   : QObject( parent ),
0122     d( new Private )
0123 {
0124     d->doc = doc;
0125     d->audioDocReader = new AudioDocReader( *doc, this );
0126     d->state = Stopped;
0127 
0128     QAudioFormat audioFormat;
0129     audioFormat.setFrequency( 44100 );
0130     audioFormat.setChannels( 2 );
0131     audioFormat.setSampleSize( 16 );
0132     audioFormat.setSampleType( QAudioFormat::SignedInt );
0133     audioFormat.setCodec( "audio/pcm" );
0134     audioFormat.setByteOrder( QAudioFormat::BigEndian );
0135     d->audioOutput = new QAudioOutput( QAudioDeviceInfo::defaultOutputDevice(), audioFormat, this );
0136 
0137     // create the actions
0138     // TODO: create shortcuts (is there a way to let the user change them?)
0139     d->actionPlay = new QAction( QIcon::fromTheme( "media-playback-start" ), i18n("Play"), this );
0140     d->actionPlay->setToolTip( i18n("Play") );
0141     d->actionPause = new QAction( QIcon::fromTheme( "media-playback-pause" ), i18n("Pause"), this );
0142     d->actionPause->setVisible( false );
0143     d->actionPause->setToolTip( i18n("Pause") );
0144     d->actionStop = new QAction( QIcon::fromTheme( "media-playback-stop" ), i18n("Stop"), this );
0145     d->actionStop->setEnabled( false );
0146     d->actionStop->setToolTip( i18n("Stop") );
0147     d->actionNext = new QAction( QIcon::fromTheme( "media-skip-forward" ), i18n("Next"), this );
0148     d->actionNext->setEnabled( false );
0149     d->actionNext->setToolTip( i18n("Next") );
0150     d->actionPrevious = new QAction( QIcon::fromTheme( "media-skip-backward" ), i18n("Previous"), this );
0151     d->actionPrevious->setEnabled( false );
0152     d->actionPrevious->setToolTip( i18n("Previous") );
0153     d->actionSeek = new AudioTrackPlayerSeekAction( this, actionCollection );
0154     d->actionSeek->setEnabled( false );
0155 
0156     if( actionCollection ) {
0157         actionCollection->addAction( "player_play", d->actionPlay );
0158         actionCollection->addAction( "player_pause", d->actionPause );
0159         actionCollection->addAction( "player_stop", d->actionStop );
0160         actionCollection->addAction( "player_next", d->actionNext );
0161         actionCollection->addAction( "player_previous", d->actionPrevious );
0162         actionCollection->addAction( "player_seek", d->actionSeek );
0163     }
0164 
0165     connect( d->audioOutput, SIGNAL(notify()),
0166              this, SLOT(slotUpdateSlider()) );
0167     connect( d->audioOutput, SIGNAL(stateChanged(QAudio::State)),
0168              this, SLOT(slotStateChanged(QAudio::State)) );
0169     connect( d->audioDocReader, SIGNAL(currentTrackChanged(K3b::AudioTrack)),
0170              this, SLOT(slotCurrentTrackChanged(K3b::AudioTrack)) );
0171     connect( d->actionPlay, SIGNAL(triggered(bool)),
0172              this, SLOT(play()) );
0173     connect( d->actionPause, SIGNAL(triggered(bool)),
0174              this, SLOT(pause()) );
0175     connect( d->actionStop, SIGNAL(triggered(bool)),
0176              this, SLOT(stop()) );
0177     connect( d->actionNext, SIGNAL(triggered(bool)),
0178              this, SLOT(next()) );
0179     connect( d->actionPrevious, SIGNAL(triggered(bool)),
0180              this, SLOT(previous()) );
0181 }
0182 
0183 
0184 AudioTrackPlayer::~AudioTrackPlayer()
0185 {
0186 }
0187 
0188 
0189 AudioTrackPlayer::State AudioTrackPlayer::state() const
0190 {
0191     return d->state;
0192 }
0193 
0194 
0195 AudioTrack* AudioTrackPlayer::currentTrack() const
0196 {
0197     if( AudioTrackReader* reader = d->audioDocReader->currentTrackReader() )
0198         return &reader->track();
0199     else
0200         return 0;
0201 }
0202 
0203 
0204 void AudioTrackPlayer::playTrack( const K3b::AudioTrack& track )
0205 {
0206     play();
0207     d->audioDocReader->setCurrentTrack( track );
0208 }
0209 
0210 
0211 void AudioTrackPlayer::play()
0212 {
0213     if( d->audioOutput->state() == QAudio::StoppedState ||
0214         d->audioOutput->state() == QAudio::IdleState ) {
0215         if( d->audioDocReader->open() ) {
0216             d->audioOutput->start( d->audioDocReader );
0217         }
0218     }
0219     else if( d->audioOutput->state() == QAudio::SuspendedState ) {
0220         d->audioOutput->resume();
0221     }
0222 }
0223 
0224 
0225 void AudioTrackPlayer::pause()
0226 {
0227     d->audioOutput->suspend();
0228 }
0229 
0230 
0231 void AudioTrackPlayer::stop()
0232 {
0233     d->audioOutput->stop();
0234 }
0235 
0236 
0237 void AudioTrackPlayer::next()
0238 {
0239     d->audioDocReader->nextTrack();
0240 }
0241 
0242 
0243 void AudioTrackPlayer::previous()
0244 {
0245     d->audioDocReader->previousTrack();
0246 }
0247 
0248 
0249 void AudioTrackPlayer::slotSeek( int bytes )
0250 {
0251     if( AudioTrackReader* reader = d->audioDocReader->currentTrackReader() ) {
0252         reader->seek( bytes );
0253     }
0254 }
0255 
0256 
0257 void AudioTrackPlayer::slotUpdateSlider()
0258 {
0259     if( AudioTrackReader* reader = d->audioDocReader->currentTrackReader() )
0260         d->actionSeek->setValue( reader->pos() );
0261 }
0262 
0263 
0264 void AudioTrackPlayer::slotCurrentTrackChanged( const K3b::AudioTrack& track )
0265 {
0266     d->actionSeek->setCurrentTrack( track );
0267     d->actionNext->setEnabled( &track != d->doc->lastTrack() );
0268     d->actionPrevious->setEnabled( &track != d->doc->firstTrack() );
0269 
0270     emit playingTrack( track );
0271 }
0272 
0273 
0274 void AudioTrackPlayer::slotStateChanged( QAudio::State state )
0275 {
0276     switch( d->audioOutput->error() ) {
0277         case QAudio::OpenError:
0278             qDebug() << "QAudioOutput error: OpenError";
0279             break;
0280         case QAudio::IOError:
0281             qDebug() << "QAudioOutput error: IOError";
0282             break;
0283         case QAudio::UnderrunError:
0284             qDebug() << "QAudioOutput error: UnderrunError";
0285             break;
0286         case QAudio::FatalError:
0287             qDebug() << "QAudioOutput error: FatalError";
0288             break;
0289         default:
0290             break;
0291     }
0292 
0293     if( QAudio::ActiveState == state ) {
0294         d->actionPause->setEnabled( true );
0295         d->actionPlay->setEnabled( false );
0296         d->actionStop->setEnabled( true );
0297         d->actionSeek->setEnabled( true );
0298         d->state = Playing;
0299     }
0300     else if( QAudio::SuspendedState == state ) {
0301         d->actionPause->setEnabled( false );
0302         d->actionPlay->setEnabled( true );
0303         d->actionStop->setEnabled( true );
0304         d->state = Paused;
0305     }
0306     else /*if( QAudio::IdleState == state || QAudio::StoppedState == state )*/ {
0307         d->actionPause->setEnabled( false );
0308         d->actionPlay->setEnabled( true );
0309         d->actionStop->setEnabled( false );
0310         d->actionSeek->setEnabled( false );
0311         d->actionNext->setEnabled( false );
0312         d->actionPrevious->setEnabled( false );
0313         d->audioDocReader->close();
0314         d->actionSeek->reset();
0315         d->state = Stopped;
0316     }
0317     emit stateChanged();
0318 }
0319 
0320 } // namespace K3b
0321 
0322 #include "moc_k3baudiotrackplayer.cpp"