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

/***************************************************************************
 *                                                                         *
 *   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.                                   *
 *                                                                         *
 ***************************************************************************/

16 17 18
#include "playermanager.h"
#include <config.h>

19
#include <kdebug.h>
Matthias Kretz's avatar
Matthias Kretz committed
20
#include <kmessagebox.h>
21
#include <klocale.h>
22 23 24
#include <kactioncollection.h>
#include <kselectaction.h>
#include <ktoggleaction.h>
Andreas Pakulat's avatar
Andreas Pakulat committed
25
#include <kurl.h>
26

27 28 29
#include <Phonon/AudioOutput>
#include <Phonon/MediaObject>
#include <Phonon/VolumeFaderEffect>
30

Laurent Montel's avatar
Laurent Montel committed
31
#include <QPixmap>
32 33
#include <QTimer>

34 35
#include <math.h>

36
#include "playlistinterface.h"
37
#include "playeradaptor.h"
38
#include "slideraction.h"
39
#include "statuslabel.h"
40
#include "actioncollection.h"
41
#include "collectionlist.h"
Michael Pyne's avatar
Michael Pyne committed
42
#include "coverinfo.h"
43
#include "tag.h"
44 45

using namespace ActionCollection;
46

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

49 50 51 52 53
////////////////////////////////////////////////////////////////////////////////
// protected members
////////////////////////////////////////////////////////////////////////////////

PlayerManager::PlayerManager() :
54
    QObject(),
55
    m_sliderAction(0),
56
    m_playlistInterface(0),
57
    m_statusLabel(0),
58
    m_noSeek(false),
59
    m_muted(false),
60 61 62
    m_setup(false),
    m_output(0),
    m_media(0)
63
{
Michael Pyne's avatar
Michael Pyne committed
64 65 66 67 68
// 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
69
    new PlayerAdaptor( this );
70
    QDBusConnection::sessionBus().registerObject("/Player", this);
Laurent Montel's avatar
Laurent Montel committed
71

72 73 74 75 76 77 78 79 80 81 82 83
}

PlayerManager::~PlayerManager()
{
}

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

PlayerManager *PlayerManager::instance() // static
{
84 85
    static PlayerManager manager;
    return &manager;
86 87
}

88 89
bool PlayerManager::playing() const
{
90
    if(!m_media)
91 92
        return false;

93
    return (m_media->state() == Phonon::PlayingState || m_media->state() == Phonon::BufferingState);
94 95 96 97
}

bool PlayerManager::paused() const
{
98
    if(!m_media)
99 100
        return false;

101
    return m_media->state() == Phonon::PausedState;
102 103 104 105
}

float PlayerManager::volume() const
{
106
    if(!m_output)
107
        return 1.0;
108

109
    return m_output->volume();
110 111
}

112 113
int PlayerManager::status() const
{
114
    if(!m_media)
115
        return StatusStopped;
116

117
    if(paused())
118
        return StatusPaused;
119

120
    if(playing())
121
        return StatusPlaying;
122

123 124 125
    return 0;
}

Scott Wheeler's avatar
Scott Wheeler committed
126
int PlayerManager::totalTime() const
127
{
128
    if(!m_media)
129 130
        return 0;

131
    return m_media->totalTime() / 1000;
132 133
}

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

139
    return m_media->currentTime() / 1000;
140 141
}

142
/*
143 144
int PlayerManager::position() const
{
145
    if(!m_media)
146 147
        return 0;

148
    long curr = m_media->currentTime();
149 150
    if(curr > 0)
        return static_cast<int>(static_cast<float>(curr * SliderAction::maxPosition) / m_media->totalTime() + 0.5f);
151
    return -1;
152
}
153
*/
154

155 156 157 158 159 160 161
QStringList PlayerManager::trackProperties()
{
    return FileHandle::properties();
}

QString PlayerManager::trackProperty(const QString &property) const
{
Michael Pyne's avatar
Michael Pyne committed
162
    if(!playing() && !paused())
163
        return QString();
164 165 166 167

    return m_file.property(property);
}

Michael Pyne's avatar
Michael Pyne committed
168 169 170 171 172
QPixmap PlayerManager::trackCover(const QString &size) const
{
    if(!playing() && !paused())
        return QPixmap();

173
    if(size.toLower() == "small")
Michael Pyne's avatar
Michael Pyne committed
174
        return m_file.coverInfo()->pixmap(CoverInfo::Thumbnail);
175
    if(size.toLower() == "large")
Michael Pyne's avatar
Michael Pyne committed
176 177 178 179 180
        return m_file.coverInfo()->pixmap(CoverInfo::FullSize);

    return QPixmap();
}

181 182 183 184 185
FileHandle PlayerManager::playingFile() const
{
    return m_file;
}

186 187 188
QString PlayerManager::playingString() const
{
    if(!playing())
189
        return QString();
190

191 192 193 194 195
    QString str = m_file.tag()->artist() + " - " + m_file.tag()->title();
    if(m_file.tag()->artist().isEmpty())
        str = m_file.tag()->title();

    return str;
196 197
}

198
void PlayerManager::setPlaylistInterface(PlaylistInterface *interface)
199 200 201 202
{
    m_playlistInterface = interface;
}

203 204 205 206 207
void PlayerManager::setStatusLabel(StatusLabel *label)
{
    m_statusLabel = label;
}

208 209 210 211
////////////////////////////////////////////////////////////////////////////////
// public slots
////////////////////////////////////////////////////////////////////////////////

212
void PlayerManager::play(const FileHandle &file)
213
{
214 215 216 217
    if(!m_media)
        setup();

    if(!m_media || !m_playlistInterface)
218 219
        return;

220
    if(file.isNull()) {
221 222 223 224
        if(paused())
            m_media->play();
        else if(playing()) {
            m_media->seek(0);
225
        }
226
        else {
227
            m_playlistInterface->playNext();
228
            m_file = m_playlistInterface->currentFile();
229

230
            if(!m_file.isNull())
231
            {
Matthias Kretz's avatar
Matthias Kretz committed
232
                m_media->setCurrentSource(KUrl::fromPath(m_file.absFilePath()));
233 234
                m_media->play();
            }
235
        }
236
    }
237
    else {
238
        m_file = file;
Matthias Kretz's avatar
Matthias Kretz committed
239 240 241
        if(m_media->state() == Phonon::PlayingState)
        {
            // do a crossfade
242 243
            Phonon::VolumeFaderEffect *fader1 = new Phonon::VolumeFaderEffect(m_media);
            m_audioPath.insertEffect(fader1);
Matthias Kretz's avatar
Matthias Kretz committed
244
            Phonon::MediaObject *mo = m_media;
245
            Phonon::AudioOutput *out = m_output;
Matthias Kretz's avatar
Matthias Kretz committed
246

247 248
            mo->disconnect(this);

Matthias Kretz's avatar
Matthias Kretz committed
249
            m_media = new Phonon::MediaObject(this);
250 251
            m_output = new Phonon::AudioOutput(Phonon::MusicCategory, this);
            Phonon::VolumeFaderEffect *fader2 = new Phonon::VolumeFaderEffect(m_media);
252
            m_audioPath = Phonon::createPath(m_media, m_output);
253
            m_audioPath.insertEffect(fader2);
254 255 256
            m_output->setVolume(out->volume());
            m_output->setMuted(out->isMuted());
            m_media->setTickInterval(200);
257

Matthias Kretz's avatar
Matthias Kretz committed
258
            connect(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), SLOT(slotStateChanged(Phonon::State)));
259
            connect(m_media, SIGNAL(aboutToFinish()), SLOT(slotNeedNextUrl()));
Matthias Kretz's avatar
Matthias Kretz committed
260 261
            if(m_sliderAction->trackPositionSlider())
                m_sliderAction->trackPositionSlider()->setMediaObject(m_media);
Matthias Kretz's avatar
Matthias Kretz committed
262
            connect(m_media, SIGNAL(totalTimeChanged(qint64)), SLOT(slotLength(qint64)));
Matthias Kretz's avatar
Matthias Kretz committed
263 264 265 266 267 268 269 270 271
            connect(m_media, SIGNAL(tick(qint64)), SLOT(slotTick(qint64)));
            connect(m_media, SIGNAL(finished()), SLOT(slotFinished()));
            m_media->setCurrentSource(KUrl::fromPath(m_file.absFilePath()));

            fader2->setVolume(0.0f);
            fader1->fadeOut(2000);
            fader2->fadeIn(2000);
            m_media->play();
            QTimer::singleShot(3000, mo, SLOT(deleteLater()));
272
            QTimer::singleShot(3000, out, SLOT(deleteLater()));
273
            QTimer::singleShot(3000, fader2, SLOT(deleteLater()));
274 275 276 277 278 279 280

            if(m_sliderAction->trackPositionSlider()) {
                m_sliderAction->trackPositionSlider()->setMediaObject(m_media);
            }
            if(m_sliderAction->volumeSlider()) {
                m_sliderAction->volumeSlider()->setAudioOutput(m_output);
            }
Matthias Kretz's avatar
Matthias Kretz committed
281 282 283 284 285 286
        }
        else
        {
            m_media->setCurrentSource(KUrl::fromPath(m_file.absFilePath()));
            m_media->play();
        }
287
    }
288

289
    // Make sure that the player() actually starts before doing anything.
290

291
    if(!playing()) {
292
        kWarning(65432) << "Unable to play " << file.absFilePath();
293 294 295
        stop();
        return;
    }
296

297 298 299
    action("pause")->setEnabled(true);
    action("stop")->setEnabled(true);
    action("forward")->setEnabled(true);
300 301
    if(action<KToggleAction>("albumRandomPlay")->isChecked())
        action("forwardAlbum")->setEnabled(true);
302
    action("back")->setEnabled(true);
303

304
    emit signalPlay();
305 306
}

307 308 309 310 311 312 313 314 315
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
316 317 318 319 320
void PlayerManager::play()
{
    play(FileHandle::null());
}

321 322
void PlayerManager::pause()
{
323
    if(!m_media)
324 325
        return;

326
    if(paused()) {
327 328 329 330
        play();
        return;
    }

331
    action("pause")->setEnabled(false);
332

333
    m_media->pause();
334 335

    emit signalPause();
336 337 338 339
}

void PlayerManager::stop()
{
340
    if(!m_media || !m_playlistInterface)
341 342
        return;

343 344 345 346
    action("pause")->setEnabled(false);
    action("stop")->setEnabled(false);
    action("back")->setEnabled(false);
    action("forward")->setEnabled(false);
347
    action("forwardAlbum")->setEnabled(false);
348

349 350 351 352 353 354 355 356 357 358 359 360 361 362 363 364
    switch(m_media->state())
    {
    case Phonon::PlayingState:
    case Phonon::BufferingState:
        {
            Phonon::VolumeFaderEffect *fader = new Phonon::VolumeFaderEffect(m_media);
            m_audioPath.insertEffect(fader);
            fader->setFadeCurve(Phonon::VolumeFaderEffect::Fade9Decibel);
            fader->fadeOut(200);
            QTimer::singleShot(1000, m_media, SLOT(stop()));
            QTimer::singleShot(1200, fader, SLOT(deleteLater()));
            break;
        }
    default:
        m_media->stop();
    }
365
    m_playlistInterface->stop();
366

367 368
    m_file = FileHandle::null();

369
    emit signalStop();
370 371
}

Laurent Montel's avatar
Laurent Montel committed
372
void PlayerManager::setVolume(float volume)
373
{
374 375 376
    if(!m_output)
        setup();
    m_output->setVolume(volume);
377 378
}

Scott Wheeler's avatar
Scott Wheeler committed
379
void PlayerManager::seek(int seekTime)
380
{
381
    if(!m_media)
382
        return;
383

384
    m_media->seek(seekTime);
385 386
}

387
/*
388
void PlayerManager::seekPosition(int position)
389
{
390
    if(!m_media)
391 392
        return;

393
    if(!playing() || m_noSeek)
394
        return;
395

396
    slotUpdateTime(position);
397
    m_media->seek(static_cast<qint64>(static_cast<float>(m_media->totalTime() * position) / SliderAction::maxPosition + 0.5f));
398
}
399
*/
400 401 402

void PlayerManager::seekForward()
{
403 404 405
    const qint64 total = m_media->totalTime();
    const qint64 newtime = m_media->currentTime() + total / 100;
    m_media->seek(qMin(total, newtime));
406 407 408 409
}

void PlayerManager::seekBack()
{
410 411 412
    const qint64 total = m_media->totalTime();
    const qint64 newtime = m_media->currentTime() - total / 100;
    m_media->seek(qMax(qint64(0), newtime));
413 414
}

415 416
void PlayerManager::playPause()
{
David Faure's avatar
David Faure committed
417
    playing() ? action("pause")->trigger() : action("play")->trigger();
418 419
}

420 421
void PlayerManager::forward()
{
422 423
    m_playlistInterface->playNext();
    FileHandle file = m_playlistInterface->currentFile();
424

425 426 427 428 429 430 431 432
    if(!file.isNull())
        play(file);
    else
        stop();
}

void PlayerManager::back()
{
433 434
    m_playlistInterface->playPrevious();
    FileHandle file = m_playlistInterface->currentFile();
435

436 437 438 439 440 441
    if(!file.isNull())
        play(file);
    else
        stop();
}

442 443
void PlayerManager::volumeUp()
{
444
    if(!m_output)
445
        return;
446

447 448
    float volume = m_output->volume() + 0.04; // 4% up
    m_output->setVolume(volume);
449 450 451 452
}

void PlayerManager::volumeDown()
{
453
    if(!m_output)
454
        return;
455

456 457
    float volume = m_output->volume() - 0.04; // 4% up
    m_output->setVolume(volume);
458 459 460 461
}

void PlayerManager::mute()
{
462
    if(!m_output)
463 464
        return;

465
    m_output->setMuted(!m_output->isMuted());
466 467
}

468 469 470
////////////////////////////////////////////////////////////////////////////////
// private slots
////////////////////////////////////////////////////////////////////////////////
471

472 473
void PlayerManager::slotNeedNextUrl()
{
Matthias Kretz's avatar
Matthias Kretz committed
474 475 476 477
    if(m_file.isNull())
    {
        return;
    }
478 479 480 481
    m_playlistInterface->playNext();
    FileHandle nextFile = m_playlistInterface->currentFile();
    if(!nextFile.isNull())
    {
Thiago Macieira's avatar
Thiago Macieira committed
482
        //kDebug() << m_file.absFilePath();
483
        m_file = nextFile;
Matthias Kretz's avatar
Matthias Kretz committed
484
        m_media->enqueue(KUrl::fromPath(m_file.absFilePath()));
485 486

        emit signalPlay();
487 488 489
    }
}

490 491
void PlayerManager::slotFinished()
{
492 493 494 495 496 497 498 499 500 501 502
    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();
503 504
}

505 506 507 508 509
void PlayerManager::slotLength(qint64 msec)
{
    m_statusLabel->setItemTotalTime(msec / 1000);
}

510
void PlayerManager::slotTick(qint64 msec)
511
{
512
    if(!m_media || !m_playlistInterface)
513
        return;
514

515
    m_noSeek = true;
516

517 518
    if(m_statusLabel) {
        m_statusLabel->setItemCurrentTime(msec / 1000);
519
    }
520

521
    m_noSeek = false;
522 523
}

Matthias Kretz's avatar
Matthias Kretz committed
524 525 526 527 528 529
void PlayerManager::slotStateChanged(Phonon::State newstate)
{
    if(newstate == Phonon::ErrorState)
    {
        switch(m_media->errorType())
        {
530 531 532
            case Phonon::NoError:
                kDebug() << "received a state change to ErrorState but errorType is NoError!?";
                break;
Matthias Kretz's avatar
Matthias Kretz committed
533 534 535 536 537 538 539 540 541 542 543 544 545
            case Phonon::NormalError:
                forward();
                KMessageBox::information(0, m_media->errorString());
                break;
            case Phonon::FatalError:
                // stop playback
                stop();
                KMessageBox::sorry(0, m_media->errorString());
                break;
        }
    }
}

546
/*
547 548 549 550 551
void PlayerManager::slotUpdateTime(int position)
{
    if(!m_statusLabel)
        return;

552
    float positionFraction = float(position) / SliderAction::maxPosition;
553
    float totalTime = float(m_media->totalTime()) / 1000.0f;
Scott Wheeler's avatar
Scott Wheeler committed
554
    int seekTime = int(positionFraction * totalTime + 0.5); // "+0.5" for rounding
555 556 557

    m_statusLabel->setItemCurrentTime(seekTime);
}
558
*/
559

560 561 562 563 564 565 566 567
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////

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

568 569 570
    if(!action("pause") ||
       !action("stop") ||
       !action("back") ||
571
       !action("forwardAlbum") ||
572 573
       !action("forward") ||
       !action("trackPositionAction"))
574
    {
Thiago Macieira's avatar
Thiago Macieira committed
575
        kWarning(65432) << "Could not find all of the required actions.";
576 577 578
        return;
    }

579 580 581 582
    if(m_setup)
        return;
    m_setup = true;

583
    m_output = new Phonon::AudioOutput(Phonon::MusicCategory, this);
584

Matthias Kretz's avatar
Matthias Kretz committed
585
    m_media = new Phonon::MediaObject(this);
Matthias Kretz's avatar
Matthias Kretz committed
586
    connect(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), SLOT(slotStateChanged(Phonon::State)));
Matthias Kretz's avatar
Matthias Kretz committed
587
    connect(m_media, SIGNAL(aboutToFinish()), SLOT(slotNeedNextUrl()));
588
    m_audioPath = Phonon::createPath(m_media, m_output);
589
    m_media->setTickInterval(200);
590

591 592
    // initialize action states

593 594 595 596
    action("pause")->setEnabled(false);
    action("stop")->setEnabled(false);
    action("back")->setEnabled(false);
    action("forward")->setEnabled(false);
597
    action("forwardAlbum")->setEnabled(false);
598

599 600
    // setup sliders

601
    m_sliderAction = action<SliderAction>("trackPositionAction");
602

603
    /*
604 605 606 607
    connect(m_sliderAction, SIGNAL(signalPositionChanged(int)),
            this, SLOT(seekPosition(int)));
    connect(m_sliderAction->trackPositionSlider(), SIGNAL(valueChanged(int)),
            this, SLOT(slotUpdateTime(int)));
608
            */
609

610 611
    if(m_sliderAction->trackPositionSlider())
    {
Matthias Kretz's avatar
Matthias Kretz committed
612
        m_sliderAction->trackPositionSlider()->setMediaObject(m_media);
613 614 615 616
    }
    if(m_sliderAction->volumeSlider())
    {
        m_sliderAction->volumeSlider()->setAudioOutput(m_output);
617
    }
618

Matthias Kretz's avatar
Matthias Kretz committed
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()));
622 623
}

Michael Pyne's avatar
Michael Pyne committed
624 625 626 627 628 629 630 631 632 633 634
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)
{
635
    if(randomMode.toLower() == "random")
Michael Pyne's avatar
Michael Pyne committed
636
        action<KToggleAction>("randomPlay")->setChecked(true);
637
    if(randomMode.toLower() == "albumrandom")
Michael Pyne's avatar
Michael Pyne committed
638
        action<KToggleAction>("albumRandomPlay")->setChecked(true);
639
    if(randomMode.toLower() == "norandom")
Michael Pyne's avatar
Michael Pyne committed
640 641 642
        action<KToggleAction>("disableRandomPlay")->setChecked(true);
}

643
#include "playermanager.moc"
644

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