playermanager.cpp 16.2 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

31
#include <QSlider>
Laurent Montel's avatar
Laurent Montel committed
32
#include <QPixmap>
33 34 35
#include <QtDBus>
#include <QTimer>

36 37
#include <math.h>

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

using namespace ActionCollection;
48

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

51 52 53 54 55
////////////////////////////////////////////////////////////////////////////////
// protected members
////////////////////////////////////////////////////////////////////////////////

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

74 75 76 77 78 79 80 81 82 83 84 85
}

PlayerManager::~PlayerManager()
{
}

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

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

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

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

bool PlayerManager::paused() const
{
100
    if(!m_media)
101 102
        return false;

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

float PlayerManager::volume() const
{
108
    if(!m_output)
109
        return 1.0;
110

111
    return m_output->volume();
112 113
}

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

119
    if(paused())
120
        return StatusPaused;
121

122
    if(playing())
123
        return StatusPlaying;
124

125 126 127
    return 0;
}

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

133
    return m_media->totalTime() / 1000;
134 135
}

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

141
    return m_media->currentTime() / 1000;
142 143
}

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

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

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

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

    return m_file.property(property);
}

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

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

    return QPixmap();
}

183 184 185 186 187
FileHandle PlayerManager::playingFile() const
{
    return m_file;
}

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

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

    return str;
198 199
}

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

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

210 211 212 213
////////////////////////////////////////////////////////////////////////////////
// public slots
////////////////////////////////////////////////////////////////////////////////

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

    if(!m_media || !m_playlistInterface)
220 221
        return;

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

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

            m_media = new Phonon::MediaObject(this);
250
            m_output = new Phonon::AudioOutput(Phonon::MusicCategory, this);
251
            m_output->setVolume(out->volume());
252 253 254
            Phonon::VolumeFaderEffect *fader2 = new Phonon::VolumeFaderEffect(m_media);
            m_audioPath.insertEffect(fader2);

Matthias Kretz's avatar
Matthias Kretz committed
255
            connect(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), SLOT(slotStateChanged(Phonon::State)));
Matthias Kretz's avatar
Matthias Kretz committed
256
            connect(m_media, SIGNAL(aboutToFinsh()), SLOT(slotNeedNextUrl()));
257
            m_audioPath = Phonon::createPath(m_media, m_output);
Matthias Kretz's avatar
Matthias Kretz committed
258 259 260
            m_media->setTickInterval(200);
            if(m_sliderAction->trackPositionSlider())
                m_sliderAction->trackPositionSlider()->setMediaObject(m_media);
Matthias Kretz's avatar
Matthias Kretz committed
261
            connect(m_media, SIGNAL(totalTimeChanged(qint64)), SLOT(slotLength(qint64)));
Matthias Kretz's avatar
Matthias Kretz committed
262 263 264 265 266 267 268 269 270
            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()));
271
            QTimer::singleShot(3000, out, SLOT(deleteLater()));
272 273 274 275 276 277 278

            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
279 280 281 282 283 284
        }
        else
        {
            m_media->setCurrentSource(KUrl::fromPath(m_file.absFilePath()));
            m_media->play();
        }
285
    }
286

287
    // Make sure that the player() actually starts before doing anything.
288

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

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

302
    emit signalPlay();
303 304
}

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

319 320
void PlayerManager::pause()
{
321
    if(!m_media)
322 323
        return;

324
    if(paused()) {
325 326 327 328
        play();
        return;
    }

329
    action("pause")->setEnabled(false);
330

331
    m_media->pause();
332 333

    emit signalPause();
334 335 336 337
}

void PlayerManager::stop()
{
338
    if(!m_media || !m_playlistInterface)
339 340
        return;

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

Matthias Kretz's avatar
Matthias Kretz committed
347
    Phonon::VolumeFaderEffect *fader = new Phonon::VolumeFaderEffect(m_media);
348
    m_audioPath.insertEffect(fader);
Matthias Kretz's avatar
Matthias Kretz committed
349
    fader->setFadeCurve(Phonon::VolumeFaderEffect::Fade9Decibel);
350 351 352
    fader->fadeOut(200);
    QTimer::singleShot(1000, m_media, SLOT(stop()));
    QTimer::singleShot(1200, fader, SLOT(deleteLater()));
353
    m_playlistInterface->stop();
354

355 356
    m_file = FileHandle::null();

357
    emit signalStop();
358 359
}

Laurent Montel's avatar
Laurent Montel committed
360
void PlayerManager::setVolume(float volume)
361
{
362 363 364
    if(!m_output)
        setup();
    m_output->setVolume(volume);
365 366
}

Scott Wheeler's avatar
Scott Wheeler committed
367
void PlayerManager::seek(int seekTime)
368
{
369
    if(!m_media)
370
        return;
371

372
    m_media->seek(seekTime);
373 374
}

375
/*
376
void PlayerManager::seekPosition(int position)
377
{
378
    if(!m_media)
379 380
        return;

381
    if(!playing() || m_noSeek)
382
        return;
383

384
    slotUpdateTime(position);
385
    m_media->seek(static_cast<qint64>(static_cast<float>(m_media->totalTime() * position) / SliderAction::maxPosition + 0.5f));
386
}
387
*/
388 389 390

void PlayerManager::seekForward()
{
391 392 393
    const qint64 total = m_media->totalTime();
    const qint64 newtime = m_media->currentTime() + total / 100;
    m_media->seek(qMin(total, newtime));
394 395 396 397
}

void PlayerManager::seekBack()
{
398 399 400
    const qint64 total = m_media->totalTime();
    const qint64 newtime = m_media->currentTime() - total / 100;
    m_media->seek(qMax(qint64(0), newtime));
401 402
}

403 404
void PlayerManager::playPause()
{
David Faure's avatar
David Faure committed
405
    playing() ? action("pause")->trigger() : action("play")->trigger();
406 407
}

408 409
void PlayerManager::forward()
{
410 411
    m_playlistInterface->playNext();
    FileHandle file = m_playlistInterface->currentFile();
412

413 414 415 416 417 418 419 420
    if(!file.isNull())
        play(file);
    else
        stop();
}

void PlayerManager::back()
{
421 422
    m_playlistInterface->playPrevious();
    FileHandle file = m_playlistInterface->currentFile();
423

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

430 431
void PlayerManager::volumeUp()
{
432
    if(!m_output)
433
        return;
434

435 436
    float volume = m_output->volume() + 0.04; // 4% up
    m_output->setVolume(volume);
437 438 439 440
}

void PlayerManager::volumeDown()
{
441
    if(!m_output)
442
        return;
443

444 445
    float volume = m_output->volume() - 0.04; // 4% up
    m_output->setVolume(volume);
446 447 448 449
}

void PlayerManager::mute()
{
450
    if(!m_output)
451 452
        return;

453
    m_output->setMuted(!m_output->isMuted());
454 455
}

456 457 458
////////////////////////////////////////////////////////////////////////////////
// private slots
////////////////////////////////////////////////////////////////////////////////
459

460 461
void PlayerManager::slotNeedNextUrl()
{
Matthias Kretz's avatar
Matthias Kretz committed
462 463 464 465
    if(m_file.isNull())
    {
        return;
    }
466 467 468 469
    m_playlistInterface->playNext();
    FileHandle nextFile = m_playlistInterface->currentFile();
    if(!nextFile.isNull())
    {
Thiago Macieira's avatar
Thiago Macieira committed
470
        //kDebug() << m_file.absFilePath();
471
        m_file = nextFile;
Matthias Kretz's avatar
Matthias Kretz committed
472
        m_media->enqueue(KUrl::fromPath(m_file.absFilePath()));
473
    }
Matthias Kretz's avatar
Matthias Kretz committed
474
    // at this point the new totalTime is not known, but the totalTimeChanged signal will tell us
475 476
}

477 478
void PlayerManager::slotFinished()
{
Matthias Kretz's avatar
Matthias Kretz committed
479
    if(m_file.isNull())
480
    {
Thiago Macieira's avatar
Thiago Macieira committed
481
        //kDebug() << "ignoring finished signal";
482 483
        return;
    }
484 485 486 487 488 489 490 491 492
    m_playlistInterface->playNext();
    FileHandle nextFile = m_playlistInterface->currentFile();
    if(!nextFile.isNull())
        play(nextFile);
    else
        stop();
    m_statusLabel->setItemTotalTime(totalTime());
}

493 494 495 496 497
void PlayerManager::slotLength(qint64 msec)
{
    m_statusLabel->setItemTotalTime(msec / 1000);
}

498
void PlayerManager::slotTick(qint64 msec)
499
{
500
    if(!m_media || !m_playlistInterface)
501
        return;
502

503
    m_noSeek = true;
504

505 506
    if(m_statusLabel) {
        m_statusLabel->setItemCurrentTime(msec / 1000);
507
    }
508

509
    m_noSeek = false;
510 511
}

Matthias Kretz's avatar
Matthias Kretz committed
512 513 514 515 516 517
void PlayerManager::slotStateChanged(Phonon::State newstate)
{
    if(newstate == Phonon::ErrorState)
    {
        switch(m_media->errorType())
        {
518 519 520
            case Phonon::NoError:
                kDebug() << "received a state change to ErrorState but errorType is NoError!?";
                break;
Matthias Kretz's avatar
Matthias Kretz committed
521 522 523 524 525 526 527 528 529 530 531 532 533
            case Phonon::NormalError:
                forward();
                KMessageBox::information(0, m_media->errorString());
                break;
            case Phonon::FatalError:
                // stop playback
                stop();
                KMessageBox::sorry(0, m_media->errorString());
                break;
        }
    }
}

534
/*
535 536 537 538 539
void PlayerManager::slotUpdateTime(int position)
{
    if(!m_statusLabel)
        return;

540
    float positionFraction = float(position) / SliderAction::maxPosition;
541
    float totalTime = float(m_media->totalTime()) / 1000.0f;
Scott Wheeler's avatar
Scott Wheeler committed
542
    int seekTime = int(positionFraction * totalTime + 0.5); // "+0.5" for rounding
543 544 545

    m_statusLabel->setItemCurrentTime(seekTime);
}
546
*/
547

548 549 550 551 552 553 554 555
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////

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

556 557 558
    if(!action("pause") ||
       !action("stop") ||
       !action("back") ||
559
       !action("forwardAlbum") ||
560 561
       !action("forward") ||
       !action("trackPositionAction"))
562
    {
Thiago Macieira's avatar
Thiago Macieira committed
563
        kWarning(65432) << "Could not find all of the required actions.";
564 565 566
        return;
    }

567 568 569 570
    if(m_setup)
        return;
    m_setup = true;

571
    m_output = new Phonon::AudioOutput(Phonon::MusicCategory, this);
572

Matthias Kretz's avatar
Matthias Kretz committed
573
    m_media = new Phonon::MediaObject(this);
Matthias Kretz's avatar
Matthias Kretz committed
574
    connect(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), SLOT(slotStateChanged(Phonon::State)));
Matthias Kretz's avatar
Matthias Kretz committed
575
    connect(m_media, SIGNAL(aboutToFinish()), SLOT(slotNeedNextUrl()));
576
    m_audioPath = Phonon::createPath(m_media, m_output);
577
    m_media->setTickInterval(200);
578

579 580
    // initialize action states

581 582 583 584
    action("pause")->setEnabled(false);
    action("stop")->setEnabled(false);
    action("back")->setEnabled(false);
    action("forward")->setEnabled(false);
585
    action("forwardAlbum")->setEnabled(false);
586

587 588
    // setup sliders

589
    m_sliderAction = action<SliderAction>("trackPositionAction");
590

591
    /*
592 593 594 595
    connect(m_sliderAction, SIGNAL(signalPositionChanged(int)),
            this, SLOT(seekPosition(int)));
    connect(m_sliderAction->trackPositionSlider(), SIGNAL(valueChanged(int)),
            this, SLOT(slotUpdateTime(int)));
596
            */
597

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

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

Michael Pyne's avatar
Michael Pyne committed
612 613 614 615 616 617 618 619 620 621 622
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)
{
623
    if(randomMode.toLower() == "random")
Michael Pyne's avatar
Michael Pyne committed
624
        action<KToggleAction>("randomPlay")->setChecked(true);
625
    if(randomMode.toLower() == "albumrandom")
Michael Pyne's avatar
Michael Pyne committed
626
        action<KToggleAction>("albumRandomPlay")->setChecked(true);
627
    if(randomMode.toLower() == "norandom")
Michael Pyne's avatar
Michael Pyne committed
628 629 630
        action<KToggleAction>("disableRandomPlay")->setChecked(true);
}

631
#include "playermanager.moc"
632

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