playermanager.cpp 17.4 KB
Newer Older
1 2 3 4
/***************************************************************************
    begin                : Sat Feb 14 2004
    copyright            : (C) 2004 by Scott Wheeler
    email                : wheeler@kde.org
5 6 7 8 9 10

    copyright            : (C) 2007 by Matthias Kretz
    email                : kretz@kde.org

    copyright            : (C) 2008 by Michael Pyne
    email                : michael.pyne@kdemail.net
11 12 13 14 15 16 17 18 19 20 21
***************************************************************************/

/***************************************************************************
 *                                                                         *
 *   This program is free software; you can redistribute it and/or modify  *
 *   it under the terms of the GNU General Public License as published by  *
 *   the Free Software Foundation; either version 2 of the License, or     *
 *   (at your option) any later version.                                   *
 *                                                                         *
 ***************************************************************************/

22 23 24
#include "playermanager.h"
#include <config.h>

25
#include <kdebug.h>
Matthias Kretz's avatar
Matthias Kretz committed
26
#include <kmessagebox.h>
27
#include <klocale.h>
28 29 30
#include <kactioncollection.h>
#include <kselectaction.h>
#include <ktoggleaction.h>
Andreas Pakulat's avatar
Andreas Pakulat committed
31
#include <kurl.h>
32

33 34 35
#include <Phonon/AudioOutput>
#include <Phonon/MediaObject>
#include <Phonon/VolumeFaderEffect>
36

Laurent Montel's avatar
Laurent Montel committed
37
#include <QPixmap>
38 39
#include <QTimer>

40 41
#include <math.h>

42
#include "playlistinterface.h"
43
#include "playeradaptor.h"
44
#include "slideraction.h"
45
#include "statuslabel.h"
46
#include "actioncollection.h"
47
#include "collectionlist.h"
Michael Pyne's avatar
Michael Pyne committed
48
#include "coverinfo.h"
49
#include "tag.h"
50 51

using namespace ActionCollection;
52

Stephan Kulow's avatar
Stephan Kulow committed
53
enum PlayerManagerStatus { StatusStopped = -1, StatusPaused = 1, StatusPlaying = 2 };
54

55 56 57 58 59
////////////////////////////////////////////////////////////////////////////////
// protected members
////////////////////////////////////////////////////////////////////////////////

PlayerManager::PlayerManager() :
60
    QObject(),
61
    m_sliderAction(0),
62
    m_playlistInterface(0),
63
    m_statusLabel(0),
64
    m_noSeek(false),
65
    m_muted(false),
66 67
    m_setup(false),
    m_output(0),
68 69
    m_media(0),
    m_fader(0)
70
{
Michael Pyne's avatar
Michael Pyne committed
71 72 73 74 75
// This class is the first thing constructed during program startup, and
// therefore has no access to the widgets needed by the setup() method.
// Since the setup() method will be called indirectly by the player() method
// later, just disable it here. -- mpyne
//    setup();
Laurent Montel's avatar
Laurent Montel committed
76
    new PlayerAdaptor( this );
77
    QDBusConnection::sessionBus().registerObject("/Player", this);
Laurent Montel's avatar
Laurent Montel committed
78

79 80 81 82 83 84 85 86 87 88 89 90
}

PlayerManager::~PlayerManager()
{
}

////////////////////////////////////////////////////////////////////////////////
// public members
////////////////////////////////////////////////////////////////////////////////

PlayerManager *PlayerManager::instance() // static
{
91 92
    static PlayerManager manager;
    return &manager;
93 94
}

95 96
bool PlayerManager::playing() const
{
97
    if(!m_media)
98 99
        return false;

100
    return (m_media->state() == Phonon::PlayingState || m_media->state() == Phonon::BufferingState);
101 102 103 104
}

bool PlayerManager::paused() const
{
105
    if(!m_media)
106 107
        return false;

108
    return m_media->state() == Phonon::PausedState;
109 110 111 112
}

float PlayerManager::volume() const
{
113
    if(!m_output)
114
        return 1.0;
115

116
    return m_output->volume();
117 118
}

119 120
int PlayerManager::status() const
{
121
    if(!m_media)
122
        return StatusStopped;
123

124
    if(paused())
125
        return StatusPaused;
126

127
    if(playing())
128
        return StatusPlaying;
129

130 131 132
    return 0;
}

Scott Wheeler's avatar
Scott Wheeler committed
133
int PlayerManager::totalTime() const
134
{
135
    if(!m_media)
136 137
        return 0;

138
    return m_media->totalTime() / 1000;
139 140
}

Scott Wheeler's avatar
Scott Wheeler committed
141
int PlayerManager::currentTime() const
142
{
143
    if(!m_media)
144 145
        return 0;

146
    return m_media->currentTime() / 1000;
147 148
}

149
/*
150 151
int PlayerManager::position() const
{
152
    if(!m_media)
153 154
        return 0;

155
    long curr = m_media->currentTime();
156 157
    if(curr > 0)
        return static_cast<int>(static_cast<float>(curr * SliderAction::maxPosition) / m_media->totalTime() + 0.5f);
158
    return -1;
159
}
160
*/
161

162 163 164 165 166 167 168
QStringList PlayerManager::trackProperties()
{
    return FileHandle::properties();
}

QString PlayerManager::trackProperty(const QString &property) const
{
Michael Pyne's avatar
Michael Pyne committed
169
    if(!playing() && !paused())
170
        return QString();
171 172 173 174

    return m_file.property(property);
}

Michael Pyne's avatar
Michael Pyne committed
175 176 177 178 179
QPixmap PlayerManager::trackCover(const QString &size) const
{
    if(!playing() && !paused())
        return QPixmap();

180
    if(size.toLower() == "small")
Michael Pyne's avatar
Michael Pyne committed
181
        return m_file.coverInfo()->pixmap(CoverInfo::Thumbnail);
182
    if(size.toLower() == "large")
Michael Pyne's avatar
Michael Pyne committed
183 184 185 186 187
        return m_file.coverInfo()->pixmap(CoverInfo::FullSize);

    return QPixmap();
}

188 189 190 191 192
FileHandle PlayerManager::playingFile() const
{
    return m_file;
}

193 194 195
QString PlayerManager::playingString() const
{
    if(!playing())
196
        return QString();
197

198 199 200 201 202
    QString str = m_file.tag()->artist() + " - " + m_file.tag()->title();
    if(m_file.tag()->artist().isEmpty())
        str = m_file.tag()->title();

    return str;
203 204
}

205
void PlayerManager::setPlaylistInterface(PlaylistInterface *interface)
206 207 208 209
{
    m_playlistInterface = interface;
}

210 211 212 213 214
void PlayerManager::setStatusLabel(StatusLabel *label)
{
    m_statusLabel = label;
}

215 216 217 218
////////////////////////////////////////////////////////////////////////////////
// public slots
////////////////////////////////////////////////////////////////////////////////

219
void PlayerManager::play(const FileHandle &file)
220
{
221 222 223 224
    if(!m_media)
        setup();

    if(!m_media || !m_playlistInterface)
225 226
        return;

227 228 229 230
    // Ensure we're not trying to fade if we were previously stopped or end
    // up not crossfading.
    m_fader->setVolume(volume());

231
    if(file.isNull()) {
232 233 234 235
        if(paused())
            m_media->play();
        else if(playing()) {
            m_media->seek(0);
236
        }
237
        else {
238
            m_playlistInterface->playNext();
239
            m_file = m_playlistInterface->currentFile();
240

241
            if(!m_file.isNull())
242
            {
Matthias Kretz's avatar
Matthias Kretz committed
243
                m_media->setCurrentSource(KUrl::fromPath(m_file.absFilePath()));
244 245
                m_media->play();
            }
246
        }
247
    }
248
    else {
249
        m_file = file;
Matthias Kretz's avatar
Matthias Kretz committed
250

251 252 253
        if(m_media->state() == Phonon::PlayingState)
            crossfadeToFile(m_file);
        else {
Matthias Kretz's avatar
Matthias Kretz committed
254 255 256
            m_media->setCurrentSource(KUrl::fromPath(m_file.absFilePath()));
            m_media->play();
        }
257
    }
258

259 260
    // Our state changed handler will perform the follow up actions necessary
    // once we actually start playing.
261 262
}

263 264 265 266 267 268 269 270 271
void PlayerManager::play(const QString &file)
{
    CollectionListItem *item = CollectionList::instance()->lookup(file);
    if(item) {
        Playlist::setPlaying(item);
        play(item->file());
    }
}

Scott Wheeler's avatar
Scott Wheeler committed
272 273 274 275 276
void PlayerManager::play()
{
    play(FileHandle::null());
}

277 278
void PlayerManager::pause()
{
279
    if(!m_media)
280 281
        return;

282
    if(paused()) {
283 284 285 286
        play();
        return;
    }

287
    action("pause")->setEnabled(false);
288

289
    m_media->pause();
290 291

    emit signalPause();
292 293 294 295
}

void PlayerManager::stop()
{
296
    if(!m_media || !m_playlistInterface)
297 298
        return;

299 300 301 302
    action("pause")->setEnabled(false);
    action("stop")->setEnabled(false);
    action("back")->setEnabled(false);
    action("forward")->setEnabled(false);
303
    action("forwardAlbum")->setEnabled(false);
304

305 306 307
    switch(m_media->state())
    {
    case Phonon::PlayingState:
308 309
        // Fall through

310
    case Phonon::BufferingState:
311 312 313 314
    {
        // Effect will be deleted once we actually stop playbackk.
        kDebug() << "Fading out playback.\n";

315 316
        m_fader->setFadeCurve(Phonon::VolumeFaderEffect::Fade12Decibel);
        m_fader->fadeOut(2000);
317
        QTimer::singleShot(2000, m_media, SLOT(stop()));
318
    }
319

320
    break;
321

322 323 324
    default:
        m_media->stop();
    }
325 326
}

Laurent Montel's avatar
Laurent Montel committed
327
void PlayerManager::setVolume(float volume)
328
{
329 330 331
    if(!m_output)
        setup();
    m_output->setVolume(volume);
332 333
}

Scott Wheeler's avatar
Scott Wheeler committed
334
void PlayerManager::seek(int seekTime)
335
{
336
    if(!m_media)
337
        return;
338

339
    m_media->seek(seekTime);
340 341
}

342
/*
343
void PlayerManager::seekPosition(int position)
344
{
345
    if(!m_media)
346 347
        return;

348
    if(!playing() || m_noSeek)
349
        return;
350

351
    slotUpdateTime(position);
352
    m_media->seek(static_cast<qint64>(static_cast<float>(m_media->totalTime() * position) / SliderAction::maxPosition + 0.5f));
353
}
354
*/
355 356 357

void PlayerManager::seekForward()
{
358 359 360
    const qint64 total = m_media->totalTime();
    const qint64 newtime = m_media->currentTime() + total / 100;
    m_media->seek(qMin(total, newtime));
361 362 363 364
}

void PlayerManager::seekBack()
{
365 366 367
    const qint64 total = m_media->totalTime();
    const qint64 newtime = m_media->currentTime() - total / 100;
    m_media->seek(qMax(qint64(0), newtime));
368 369
}

370 371
void PlayerManager::playPause()
{
David Faure's avatar
David Faure committed
372
    playing() ? action("pause")->trigger() : action("play")->trigger();
373 374
}

375 376
void PlayerManager::forward()
{
377 378
    m_playlistInterface->playNext();
    FileHandle file = m_playlistInterface->currentFile();
379

380 381 382 383 384 385 386 387
    if(!file.isNull())
        play(file);
    else
        stop();
}

void PlayerManager::back()
{
388 389
    m_playlistInterface->playPrevious();
    FileHandle file = m_playlistInterface->currentFile();
390

391 392 393 394 395 396
    if(!file.isNull())
        play(file);
    else
        stop();
}

397 398
void PlayerManager::volumeUp()
{
399
    if(!m_output)
400
        return;
401

402 403
    float volume = m_output->volume() + 0.04; // 4% up
    m_output->setVolume(volume);
404 405 406 407
}

void PlayerManager::volumeDown()
{
408
    if(!m_output)
409
        return;
410

411 412
    float volume = m_output->volume() - 0.04; // 4% up
    m_output->setVolume(volume);
413 414 415 416
}

void PlayerManager::mute()
{
417
    if(!m_output)
418 419
        return;

420
    m_output->setMuted(!m_output->isMuted());
421 422
}

423 424 425
////////////////////////////////////////////////////////////////////////////////
// private slots
////////////////////////////////////////////////////////////////////////////////
426

427 428
void PlayerManager::slotNeedNextUrl()
{
Matthias Kretz's avatar
Matthias Kretz committed
429 430 431 432
    if(m_file.isNull())
    {
        return;
    }
433 434 435 436
    m_playlistInterface->playNext();
    FileHandle nextFile = m_playlistInterface->currentFile();
    if(!nextFile.isNull())
    {
Thiago Macieira's avatar
Thiago Macieira committed
437
        //kDebug() << m_file.absFilePath();
438
        m_file = nextFile;
439
        crossfadeToFile(m_file);
440 441 442
    }
}

443 444
void PlayerManager::slotFinished()
{
445 446 447 448 449 450 451 452 453 454 455
    action("pause")->setEnabled(false);
    action("stop")->setEnabled(false);
    action("back")->setEnabled(false);
    action("forward")->setEnabled(false);
    action("forwardAlbum")->setEnabled(false);

    m_playlistInterface->stop();

    m_file = FileHandle::null();

    emit signalStop();
456 457
}

458 459 460 461 462
void PlayerManager::slotLength(qint64 msec)
{
    m_statusLabel->setItemTotalTime(msec / 1000);
}

463
void PlayerManager::slotTick(qint64 msec)
464
{
465
    if(!m_media || !m_playlistInterface)
466
        return;
467

468
    m_noSeek = true;
469

470 471
    if(m_statusLabel) {
        m_statusLabel->setItemCurrentTime(msec / 1000);
472
    }
473

474
    m_noSeek = false;
475 476
}

Matthias Kretz's avatar
Matthias Kretz committed
477 478
void PlayerManager::slotStateChanged(Phonon::State newstate)
{
479 480
    if(newstate == Phonon::ErrorState) {
        switch(m_media->errorType()) {
481 482 483
            case Phonon::NoError:
                kDebug() << "received a state change to ErrorState but errorType is NoError!?";
                break;
484

Matthias Kretz's avatar
Matthias Kretz committed
485 486 487 488
            case Phonon::NormalError:
                forward();
                KMessageBox::information(0, m_media->errorString());
                break;
489

Matthias Kretz's avatar
Matthias Kretz committed
490 491 492 493 494 495 496
            case Phonon::FatalError:
                // stop playback
                stop();
                KMessageBox::sorry(0, m_media->errorString());
                break;
        }
    }
497 498 499 500 501 502 503
    else if(newstate == Phonon::StoppedState) {
        m_playlistInterface->stop();

        m_file = FileHandle::null();

        emit signalStop();
    }
504 505 506 507 508 509 510 511 512 513
    else if(newstate == Phonon::PlayingState) {
        action("pause")->setEnabled(true);
        action("stop")->setEnabled(true);
        action("forward")->setEnabled(true);
        if(action<KToggleAction>("albumRandomPlay")->isChecked())
            action("forwardAlbum")->setEnabled(true);
        action("back")->setEnabled(true);

        emit signalPlay();
    }
514 515 516 517 518 519 520 521 522 523
}

void PlayerManager::slotKillSender()
{
    // When called as a slot the sender() property has a QObject that called us.
    // If it's a MediaObject we should delete it to clean up from crossfading.
    // Note: This is not a good general practice (deleting our senders) and is dependant on what
    // MediaObject does.  So we use deleteLater() to delete at some later time.
    if(qobject_cast<Phonon::MediaObject *>(sender()))
        sender()->deleteLater();
Matthias Kretz's avatar
Matthias Kretz committed
524 525
}

526
/*
527 528 529 530 531
void PlayerManager::slotUpdateTime(int position)
{
    if(!m_statusLabel)
        return;

532
    float positionFraction = float(position) / SliderAction::maxPosition;
533
    float totalTime = float(m_media->totalTime()) / 1000.0f;
Scott Wheeler's avatar
Scott Wheeler committed
534
    int seekTime = int(positionFraction * totalTime + 0.5); // "+0.5" for rounding
535 536 537

    m_statusLabel->setItemCurrentTime(seekTime);
}
538
*/
539

540 541 542 543 544 545 546 547
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////

void PlayerManager::setup()
{
    // All of the actions required by this class should be listed here.

548 549 550
    if(!action("pause") ||
       !action("stop") ||
       !action("back") ||
551
       !action("forwardAlbum") ||
552 553
       !action("forward") ||
       !action("trackPositionAction"))
554
    {
Thiago Macieira's avatar
Thiago Macieira committed
555
        kWarning(65432) << "Could not find all of the required actions.";
556 557 558
        return;
    }

559 560 561 562
    if(m_setup)
        return;
    m_setup = true;

563
    m_output = new Phonon::AudioOutput(Phonon::MusicCategory, this);
564

Matthias Kretz's avatar
Matthias Kretz committed
565
    m_media = new Phonon::MediaObject(this);
Matthias Kretz's avatar
Matthias Kretz committed
566
    connect(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), SLOT(slotStateChanged(Phonon::State)));
Matthias Kretz's avatar
Matthias Kretz committed
567
    connect(m_media, SIGNAL(aboutToFinish()), SLOT(slotNeedNextUrl()));
568
    m_audioPath = Phonon::createPath(m_media, m_output);
569
    m_media->setTickInterval(200);
570

571 572 573 574 575
    // Pre-cache a volume fader object
    m_fader = new Phonon::VolumeFaderEffect(m_media);
    m_audioPath.insertEffect(m_fader);
    m_fader->setVolume(volume());

576 577
    // initialize action states

578 579 580 581
    action("pause")->setEnabled(false);
    action("stop")->setEnabled(false);
    action("back")->setEnabled(false);
    action("forward")->setEnabled(false);
582
    action("forwardAlbum")->setEnabled(false);
583

584 585
    // setup sliders

586
    m_sliderAction = action<SliderAction>("trackPositionAction");
587

588
    /*
589 590 591 592
    connect(m_sliderAction, SIGNAL(signalPositionChanged(int)),
            this, SLOT(seekPosition(int)));
    connect(m_sliderAction->trackPositionSlider(), SIGNAL(valueChanged(int)),
            this, SLOT(slotUpdateTime(int)));
593
            */
594

595 596
    if(m_sliderAction->trackPositionSlider())
    {
Matthias Kretz's avatar
Matthias Kretz committed
597
        m_sliderAction->trackPositionSlider()->setMediaObject(m_media);
598 599 600 601
    }
    if(m_sliderAction->volumeSlider())
    {
        m_sliderAction->volumeSlider()->setAudioOutput(m_output);
602
    }
603

Matthias Kretz's avatar
Matthias Kretz committed
604
    connect(m_media, SIGNAL(totalTimeChanged(qint64)), SLOT(slotLength(qint64)));
605
    connect(m_media, SIGNAL(tick(qint64)), SLOT(slotTick(qint64)));
606
    connect(m_media, SIGNAL(finished()), SLOT(slotFinished()));
607 608
}

609 610 611 612 613
void PlayerManager::crossfadeToFile(const FileHandle &newFile)
{
    // Crossfade by introducting a second MediaObject, inserting appropriate effects in the new
    // and old MediaObject, and letting the old MediaObject die a graceful death afterwards.

614
    Phonon::VolumeFaderEffect *oldFader = m_fader;
615 616 617 618 619
    Phonon::Path oldPath(m_audioPath);
    Phonon::MediaObject *mo = m_media;
    Phonon::AudioOutput *out = m_output;

    mo->disconnect(this);
620
    out->setParent(mo); // Allow mo's death to also kill out
621 622 623 624
    connect(mo, SIGNAL(finished()), SLOT(slotKillSender()));

    m_media = new Phonon::MediaObject(this);
    m_output = new Phonon::AudioOutput(Phonon::MusicCategory, this);
625
    m_fader = new Phonon::VolumeFaderEffect(m_media);
626 627 628 629 630

    m_audioPath = Phonon::createPath(m_media, m_output);
    m_output->setVolume(out->volume());
    m_output->setMuted(out->isMuted());
    m_media->setTickInterval(200);
631
    m_audioPath.insertEffect(m_fader);
632 633 634 635 636 637 638 639 640 641

    // Reconnect everything
    connect(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), SLOT(slotStateChanged(Phonon::State)));
    connect(m_media, SIGNAL(aboutToFinish()), SLOT(slotNeedNextUrl()));
    if(m_sliderAction->trackPositionSlider())
        m_sliderAction->trackPositionSlider()->setMediaObject(m_media);
    connect(m_media, SIGNAL(totalTimeChanged(qint64)), SLOT(slotLength(qint64)));
    connect(m_media, SIGNAL(tick(qint64)), SLOT(slotTick(qint64)));
    connect(m_media, SIGNAL(finished()), SLOT(slotFinished()));

642 643
    if(m_sliderAction->trackPositionSlider())
        m_sliderAction->trackPositionSlider()->setMediaObject(m_media);
644

645 646
    if(m_sliderAction->volumeSlider())
        m_sliderAction->volumeSlider()->setAudioOutput(m_output);
647 648 649 650

    m_media->setCurrentSource(newFile.absFilePath());
    m_media->play();

651
    oldFader->fadeTo(0.0f, 2000);
652

653 654
    m_fader->setVolume(0.0f);
    m_fader->fadeTo(m_output->volume(), 2000);
655 656 657

    // Give the media object some extra time for good measure but then kill it.
    QTimer::singleShot(4000, mo, SLOT(deleteLater()));
658 659
}

Michael Pyne's avatar
Michael Pyne committed
660 661 662 663 664 665 666 667 668 669 670
QString PlayerManager::randomPlayMode() const
{
    if(action<KToggleAction>("randomPlay")->isChecked())
        return "Random";
    if(action<KToggleAction>("albumRandomPlay")->isChecked())
        return "AlbumRandom";
    return "NoRandom";
}

void PlayerManager::setRandomPlayMode(const QString &randomMode)
{
671
    if(randomMode.toLower() == "random")
Michael Pyne's avatar
Michael Pyne committed
672
        action<KToggleAction>("randomPlay")->setChecked(true);
673
    if(randomMode.toLower() == "albumrandom")
Michael Pyne's avatar
Michael Pyne committed
674
        action<KToggleAction>("albumRandomPlay")->setChecked(true);
675
    if(randomMode.toLower() == "norandom")
Michael Pyne's avatar
Michael Pyne committed
676 677 678
        action<KToggleAction>("disableRandomPlay")->setChecked(true);
}

679
#include "playermanager.moc"
680

681
// vim: set et sw=4 tw=0 sta: