71
67
// later, just disable it here. -- mpyne
73
69
new PlayerAdaptor( this );
70
QDBusConnection::sessionBus().registerObject("/Player", this);
76
74
PlayerManager::~PlayerManager()
78
delete m_sliderAction;
82
78
////////////////////////////////////////////////////////////////////////////////
84
80
////////////////////////////////////////////////////////////////////////////////
82
PlayerManager *PlayerManager::instance() // static
84
static PlayerManager manager;
86
88
bool PlayerManager::playing() const
91
Phonon::State state = m_media[m_curOutputPath]->state();
92
return (state == Phonon::PlayingState || state == Phonon::BufferingState);
93
return (m_media->state() == Phonon::PlayingState || m_media->state() == Phonon::BufferingState);
95
96
bool PlayerManager::paused() const
100
return m_media[m_curOutputPath]->state() == Phonon::PausedState;
101
return m_media->state() == Phonon::PausedState;
103
104
float PlayerManager::volume() const
108
return m_output[m_curOutputPath]->volume();
109
return m_output->volume();
111
112
int PlayerManager::status() const
114
115
return StatusStopped;
217
230
if(!m_file.isNull())
219
mediaObject->setCurrentSource(KUrl::fromPath(m_file.absFilePath()));
222
emit signalItemChanged(m_file);
232
m_media->setCurrentSource(KUrl::fromPath(m_file.absFilePath()));
227
mediaObject->setCurrentSource(KUrl::fromPath(file.absFilePath()));
231
emit signalItemChanged(file);
236
// Our state changed handler will perform the follow up actions necessary
237
// once we actually start playing.
239
if(m_media->state() == Phonon::PlayingState)
242
Phonon::VolumeFaderEffect *fader1 = new Phonon::VolumeFaderEffect(m_media);
243
m_audioPath.insertEffect(fader1);
244
Phonon::MediaObject *mo = m_media;
245
Phonon::AudioOutput *out = m_output;
247
mo->disconnect(this);
249
m_media = new Phonon::MediaObject(this);
250
m_output = new Phonon::AudioOutput(Phonon::MusicCategory, this);
251
Phonon::VolumeFaderEffect *fader2 = new Phonon::VolumeFaderEffect(m_media);
252
m_audioPath = Phonon::createPath(m_media, m_output);
253
m_audioPath.insertEffect(fader2);
254
m_output->setVolume(out->volume());
255
m_output->setMuted(out->isMuted());
256
m_media->setTickInterval(200);
258
connect(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), SLOT(slotStateChanged(Phonon::State)));
259
connect(m_media, SIGNAL(aboutToFinish()), SLOT(slotNeedNextUrl()));
260
if(m_sliderAction->trackPositionSlider())
261
m_sliderAction->trackPositionSlider()->setMediaObject(m_media);
262
connect(m_media, SIGNAL(totalTimeChanged(qint64)), SLOT(slotLength(qint64)));
263
connect(m_media, SIGNAL(tick(qint64)), SLOT(slotTick(qint64)));
264
connect(m_media, SIGNAL(finished()), SLOT(slotFinished()));
265
m_media->setCurrentSource(KUrl::fromPath(m_file.absFilePath()));
267
fader2->setVolume(0.0f);
268
fader1->fadeOut(2000);
269
fader2->fadeIn(2000);
271
QTimer::singleShot(3000, mo, SLOT(deleteLater()));
272
QTimer::singleShot(3000, out, SLOT(deleteLater()));
273
QTimer::singleShot(3000, fader2, SLOT(deleteLater()));
275
if(m_sliderAction->trackPositionSlider()) {
276
m_sliderAction->trackPositionSlider()->setMediaObject(m_media);
278
if(m_sliderAction->volumeSlider()) {
279
m_sliderAction->volumeSlider()->setAudioOutput(m_output);
284
m_media->setCurrentSource(KUrl::fromPath(m_file.absFilePath()));
289
// Make sure that the player() actually starts before doing anything.
292
kWarning(65432) << "Unable to play " << file.absFilePath();
297
action("pause")->setEnabled(true);
298
action("stop")->setEnabled(true);
299
action("forward")->setEnabled(true);
300
if(action<KToggleAction>("albumRandomPlay")->isChecked())
301
action("forwardAlbum")->setEnabled(true);
302
action("back")->setEnabled(true);
240
307
void PlayerManager::play(const QString &file)
279
346
action("forward")->setEnabled(false);
280
347
action("forwardAlbum")->setEnabled(false);
282
// Fading out playback is for chumps.
287
if(!m_file.isNull()) {
288
m_file = FileHandle::null();
289
emit signalItemChanged(m_file);
349
switch(m_media->state())
351
case Phonon::PlayingState:
352
case Phonon::BufferingState:
354
Phonon::VolumeFaderEffect *fader = new Phonon::VolumeFaderEffect(m_media);
355
m_audioPath.insertEffect(fader);
356
fader->setFadeCurve(Phonon::VolumeFaderEffect::Fade9Decibel);
358
QTimer::singleShot(1000, m_media, SLOT(stop()));
359
QTimer::singleShot(1200, fader, SLOT(deleteLater()));
365
m_playlistInterface->stop();
367
m_file = FileHandle::null();
293
372
void PlayerManager::setVolume(float volume)
298
m_output[0]->setVolume(volume);
299
m_output[1]->setVolume(volume);
376
m_output->setVolume(volume);
302
379
void PlayerManager::seek(int seekTime)
304
if(!m_setup || m_media[m_curOutputPath]->currentTime() == seekTime)
307
kDebug() << "Stopping crossfade to seek from" << m_media[m_curOutputPath]->currentTime()
310
m_media[m_curOutputPath]->seek(seekTime);
384
m_media->seek(seekTime);
388
void PlayerManager::seekPosition(int position)
393
if(!playing() || m_noSeek)
396
slotUpdateTime(position);
397
m_media->seek(static_cast<qint64>(static_cast<float>(m_media->totalTime() * position) / SliderAction::maxPosition + 0.5f));
313
401
void PlayerManager::seekForward()
315
Phonon::MediaObject *mediaObject = m_media[m_curOutputPath];
316
const qint64 total = mediaObject->totalTime();
317
const qint64 newtime = mediaObject->currentTime() + total / 100;
320
mediaObject->seek(qMin(total, newtime));
403
const qint64 total = m_media->totalTime();
404
const qint64 newtime = m_media->currentTime() + total / 100;
405
m_media->seek(qMin(total, newtime));
323
408
void PlayerManager::seekBack()
325
Phonon::MediaObject *mediaObject = m_media[m_curOutputPath];
326
const qint64 total = mediaObject->totalTime();
327
const qint64 newtime = mediaObject->currentTime() - total / 100;
330
mediaObject->seek(qMax(qint64(0), newtime));
410
const qint64 total = m_media->totalTime();
411
const qint64 newtime = m_media->currentTime() - total / 100;
412
m_media->seek(qMax(qint64(0), newtime));
333
415
void PlayerManager::playPause()
388
472
void PlayerManager::slotNeedNextUrl()
390
if(m_file.isNull() || !m_crossfadeTracks)
393
478
m_playlistInterface->playNext();
394
479
FileHandle nextFile = m_playlistInterface->currentFile();
396
if(!nextFile.isNull()) {
480
if(!nextFile.isNull())
482
//kDebug() << m_file.absFilePath();
397
483
m_file = nextFile;
398
crossfadeToFile(m_file);
484
m_media->enqueue(KUrl::fromPath(m_file.absFilePath()));
402
490
void PlayerManager::slotFinished()
404
// It is possible to end up in this function if a file simply fails to play or if the
405
// user moves the slider all the way to the end, therefore see if we can keep playing
406
// and if we can, do so. Otherwise, stop. Note that this slot should
407
// only be called by the currently "main" output path (i.e. not from the
408
// crossfading one). However life isn't always so nice apparently, so do some
411
Phonon::MediaObject *mediaObject = qobject_cast<Phonon::MediaObject *>(sender());
412
if(mediaObject != m_media[m_curOutputPath])
415
m_playlistInterface->playNext();
416
m_file = m_playlistInterface->currentFile();
418
if(m_file.isNull()) {
422
emit signalItemChanged(m_file);
423
m_media[m_curOutputPath]->setCurrentSource(m_file.absFilePath());
424
m_media[m_curOutputPath]->play();
492
action("pause")->setEnabled(false);
493
action("stop")->setEnabled(false);
494
action("back")->setEnabled(false);
495
action("forward")->setEnabled(false);
496
action("forwardAlbum")->setEnabled(false);
498
m_playlistInterface->stop();
500
m_file = FileHandle::null();
428
505
void PlayerManager::slotLength(qint64 msec)
433
510
void PlayerManager::slotTick(qint64 msec)
435
if(!m_setup || !m_playlistInterface)
512
if(!m_media || !m_playlistInterface)
439
518
m_statusLabel->setItemCurrentTime(msec / 1000);
442
void PlayerManager::slotStateChanged(Phonon::State newstate, Phonon::State oldstate)
524
void PlayerManager::slotStateChanged(Phonon::State newstate)
444
// Use sender() since either media object may have sent the signal.
445
Phonon::MediaObject *mediaObject = qobject_cast<Phonon::MediaObject *>(sender());
449
// Handle errors for either media object
450
if(newstate == Phonon::ErrorState) {
451
QString errorMessage =
453
"%1 will be the /path/to/file, %2 will be some string from Phonon describing the error",
454
"JuK is unable to play the audio file<nl/><filename>%1</filename><nl/>"
455
"for the following reason:<nl/><message>%2</message>",
456
m_file.absFilePath(),
457
mediaObject->errorString()
460
switch(mediaObject->errorType()) {
526
if(newstate == Phonon::ErrorState)
528
switch(m_media->errorType())
461
530
case Phonon::NoError:
462
531
kDebug() << "received a state change to ErrorState but errorType is NoError!?";
465
533
case Phonon::NormalError:
467
KMessageBox::information(0, errorMessage);
535
KMessageBox::information(0, m_media->errorString());
470
537
case Phonon::FatalError:
472
KMessageBox::sorry(0, errorMessage);
540
KMessageBox::sorry(0, m_media->errorString());
477
// Now bail out if we're not dealing with the currently playing media
480
if(mediaObject != m_media[m_curOutputPath])
547
void PlayerManager::slotUpdateTime(int position)
483
// Handle state changes for the playing media object.
484
if(newstate == Phonon::StoppedState && oldstate != Phonon::LoadingState) {
485
// If this occurs it should be due to a transitory shift (i.e. playing a different
486
// song when one is playing now), since it didn't occur in the error handler. Just
487
// in case we really did abruptly stop, handle that case in a couple of seconds.
488
QTimer::singleShot(2000, this, SLOT(slotUpdateGuiIfStopped()));
491
else if(newstate == Phonon::PlayingState) {
492
action("pause")->setEnabled(true);
493
action("stop")->setEnabled(true);
494
action("forward")->setEnabled(true);
495
if(action<KToggleAction>("albumRandomPlay")->isChecked())
496
action("forwardAlbum")->setEnabled(true);
497
action("back")->setEnabled(true);
552
float positionFraction = float(position) / SliderAction::maxPosition;
553
float totalTime = float(m_media->totalTime()) / 1000.0f;
554
int seekTime = int(positionFraction * totalTime + 0.5); // "+0.5" for rounding
556
m_statusLabel->setItemCurrentTime(seekTime);
503
560
////////////////////////////////////////////////////////////////////////////////
504
561
// private members
526
// We use two audio paths at all times to make cross fading easier (and to also easily
527
// support not using cross fading with the same code). The currently playing audio
528
// path is controlled using m_curOutputPath.
530
for(int i = 0; i < 2; ++i) {
531
m_output[i] = new Phonon::AudioOutput(Phonon::MusicCategory, this);
533
m_media[i] = new Phonon::MediaObject(this);
534
m_audioPath[i] = Phonon::createPath(m_media[i], m_output[i]);
535
m_media[i]->setTickInterval(200);
536
m_media[i]->setPrefinishMark(2000);
538
// Pre-cache a volume fader object
539
m_fader[i] = new Phonon::VolumeFaderEffect(m_media[i]);
540
m_audioPath[i].insertEffect(m_fader[i]);
541
m_fader[i]->setVolume(1.0f);
543
connect(m_media[i], SIGNAL(stateChanged(Phonon::State, Phonon::State)), SLOT(slotStateChanged(Phonon::State, Phonon::State)));
544
connect(m_media[i], SIGNAL(prefinishMarkReached(qint32)), SLOT(slotNeedNextUrl()));
545
connect(m_media[i], SIGNAL(totalTimeChanged(qint64)), SLOT(slotLength(qint64)));
546
connect(m_media[i], SIGNAL(tick(qint64)), SLOT(slotTick(qint64)));
547
connect(m_media[i], SIGNAL(finished()), SLOT(slotFinished()));
583
m_output = new Phonon::AudioOutput(Phonon::MusicCategory, this);
585
m_media = new Phonon::MediaObject(this);
586
connect(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), SLOT(slotStateChanged(Phonon::State)));
587
connect(m_media, SIGNAL(aboutToFinish()), SLOT(slotNeedNextUrl()));
588
m_audioPath = Phonon::createPath(m_media, m_output);
589
m_media->setTickInterval(200);
550
591
// initialize action states
555
596
action("forward")->setEnabled(false);
556
597
action("forwardAlbum")->setEnabled(false);
558
// setup sliders (a separate slot is used to switch as needed)
560
601
m_sliderAction = action<SliderAction>("trackPositionAction");
604
connect(m_sliderAction, SIGNAL(signalPositionChanged(int)),
605
this, SLOT(seekPosition(int)));
606
connect(m_sliderAction->trackPositionSlider(), SIGNAL(valueChanged(int)),
607
this, SLOT(slotUpdateTime(int)));
562
610
if(m_sliderAction->trackPositionSlider())
563
m_sliderAction->trackPositionSlider()->setMediaObject(m_media[0]);
612
m_sliderAction->trackPositionSlider()->setMediaObject(m_media);
565
614
if(m_sliderAction->volumeSlider())
566
m_sliderAction->volumeSlider()->setAudioOutput(m_output[0]);
568
QDBusConnection::sessionBus().registerObject("/Player", this);
571
void PlayerManager::slotUpdateSliders()
573
m_sliderAction->trackPositionSlider()->setMediaObject(m_media[m_curOutputPath]);
574
m_sliderAction->volumeSlider()->setAudioOutput(m_output[m_curOutputPath]);
576
disconnect(m_media[1 - m_curOutputPath], 0, this, SLOT(slotUpdateSliders()));
577
connect(m_media[1 - m_curOutputPath], SIGNAL(finished()), SLOT(slotFinished()));
580
void PlayerManager::slotUpdateGuiIfStopped()
582
if(m_media[0]->state() == Phonon::StoppedState && m_media[1]->state() == Phonon::StoppedState)
586
void PlayerManager::crossfadeToFile(const FileHandle &newFile)
588
int nextOutputPath = 1 - m_curOutputPath;
590
// Don't need this anymore
591
disconnect(m_media[m_curOutputPath], SIGNAL(finished()), this, 0);
592
connect(m_media[nextOutputPath], SIGNAL(finished()), SLOT(slotFinished()));
594
// Wait a couple of seconds and switch slider objects. (We would simply
595
// handle this when finished() is emitted but phonon-gst is buggy).
596
QTimer::singleShot(2000, this, SLOT(slotUpdateSliders()));
598
m_fader[nextOutputPath]->setVolume(0.0f);
600
emit signalItemChanged(newFile);
601
m_media[nextOutputPath]->setCurrentSource(newFile.absFilePath());
602
m_media[nextOutputPath]->play();
604
m_fader[m_curOutputPath]->setVolume(1.0f);
605
m_fader[m_curOutputPath]->fadeTo(0.0f, 2000);
607
m_fader[nextOutputPath]->fadeTo(1.0f, 2000);
609
m_curOutputPath = nextOutputPath;
612
void PlayerManager::stopCrossfade()
614
// According to the Phonon docs, setVolume immediately takes effect,
615
// which is "good enough for government work" ;)
617
// 1 - curOutputPath is the other output path...
618
m_fader[m_curOutputPath]->setVolume(1.0f);
619
m_fader[1 - m_curOutputPath]->setVolume(0.0f);
621
// We don't actually need to physically stop crossfading as the playback
622
// code will call ->play() when necessary anyways. If we hit stop()
623
// here instead of pause() then we will trick our stateChanged handler
624
// into thinking Phonon had a spurious stop and we'll switch tracks
625
// unnecessarily. (This isn't a problem after crossfade completes due to
626
// the signals being disconnected).
628
m_media[1 - m_curOutputPath]->pause();
616
m_sliderAction->volumeSlider()->setAudioOutput(m_output);
619
connect(m_media, SIGNAL(totalTimeChanged(qint64)), SLOT(slotLength(qint64)));
620
connect(m_media, SIGNAL(tick(qint64)), SLOT(slotTick(qint64)));
621
connect(m_media, SIGNAL(finished()), SLOT(slotFinished()));
631
624
QString PlayerManager::randomPlayMode() const