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"