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 19 20 21 22 23 24
/**
 * Note to those who work here.  The preprocessor variables HAVE_ARTS and HAVE_GSTREAMER
 * are ::ALWAYS DEFINED::.  You can't use #ifdef to see if they're present, you should just
 * use #if.
 *
 * However, HAVE_AKODE is #define'd if present, and undefined if not present.
 * - mpyne
 */

25 26 27
#include "playermanager.h"
#include <config.h>

28
#include <kdebug.h>
Matthias Kretz's avatar
Matthias Kretz committed
29
#include <kmessagebox.h>
30
#include <klocale.h>
31 32 33
#include <kactioncollection.h>
#include <kselectaction.h>
#include <ktoggleaction.h>
34

35 36 37
#include <phonon/mediaobject.h>
#include <phonon/audiopath.h>
#include <phonon/audiooutput.h>
Matthias Kretz's avatar
Matthias Kretz committed
38
#include <phonon/volumefadereffect.h>
39

40
#include <QSlider>
Laurent Montel's avatar
Laurent Montel committed
41
#include <QPixmap>
42 43 44
#include <QtDBus>
#include <QTimer>

45 46
#include <math.h>

47
#include "playlistinterface.h"
48
#include "playeradaptor.h"
49
#include "slideraction.h"
50
#include "statuslabel.h"
51
#include "actioncollection.h"
52
#include "collectionlist.h"
Michael Pyne's avatar
Michael Pyne committed
53
#include "coverinfo.h"
54
#include "tag.h"
55 56

using namespace ActionCollection;
57

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

60 61 62 63 64
////////////////////////////////////////////////////////////////////////////////
// protected members
////////////////////////////////////////////////////////////////////////////////

PlayerManager::PlayerManager() :
65
    QObject(),
66
    m_sliderAction(0),
67
    m_playlistInterface(0),
68
    m_statusLabel(0),
69
    m_noSeek(false),
70
    m_muted(false),
71 72 73 74
    m_setup(false),
    m_output(0),
    m_audioPath(0),
    m_media(0)
75
{
Michael Pyne's avatar
Michael Pyne committed
76 77 78 79 80
// 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
81
    new PlayerAdaptor( this );
82
    QDBusConnection::sessionBus().registerObject("/Player", this);
Laurent Montel's avatar
Port it  
Laurent Montel committed
83

84 85 86 87 88 89 90 91 92 93 94 95
}

PlayerManager::~PlayerManager()
{
}

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

PlayerManager *PlayerManager::instance() // static
{
96 97
    static PlayerManager manager;
    return &manager;
98 99
}

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

105
    return (m_media->state() == Phonon::PlayingState || m_media->state() == Phonon::BufferingState);
106 107 108 109
}

bool PlayerManager::paused() const
{
110
    if(!m_media)
111 112
        return false;

113
    return m_media->state() == Phonon::PausedState;
114 115 116 117
}

float PlayerManager::volume() const
{
118
    if(!m_output)
119
        return 1.0;
120

121
    return m_output->volume();
122 123
}

124 125
int PlayerManager::status() const
{
126
    if(!m_media)
127
        return StatusStopped;
128

129
    if(paused())
130
        return StatusPaused;
131

132
    if(playing())
133
        return StatusPlaying;
134

135 136 137
    return 0;
}

Scott Wheeler's avatar
Scott Wheeler committed
138
int PlayerManager::totalTime() const
139
{
140
    if(!m_media)
141 142
        return 0;

143
    return m_media->totalTime() / 1000;
144 145
}

Scott Wheeler's avatar
Scott Wheeler committed
146
int PlayerManager::currentTime() const
147
{
148
    if(!m_media)
149 150
        return 0;

151
    return m_media->currentTime() / 1000;
152 153
}

154
/*
155 156
int PlayerManager::position() const
{
157
    if(!m_media)
158 159
        return 0;

160
    long curr = m_media->currentTime();
161 162
    if(curr > 0)
        return static_cast<int>(static_cast<float>(curr * SliderAction::maxPosition) / m_media->totalTime() + 0.5f);
163
    return -1;
164
}
165
*/
166

167 168 169 170 171 172 173
QStringList PlayerManager::trackProperties()
{
    return FileHandle::properties();
}

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

    return m_file.property(property);
}

Michael Pyne's avatar
Michael Pyne committed
180 181 182 183 184
QPixmap PlayerManager::trackCover(const QString &size) const
{
    if(!playing() && !paused())
        return QPixmap();

185
    if(size.toLower() == "small")
Michael Pyne's avatar
Michael Pyne committed
186
        return m_file.coverInfo()->pixmap(CoverInfo::Thumbnail);
187
    if(size.toLower() == "large")
Michael Pyne's avatar
Michael Pyne committed
188 189 190 191 192
        return m_file.coverInfo()->pixmap(CoverInfo::FullSize);

    return QPixmap();
}

193 194 195 196 197
FileHandle PlayerManager::playingFile() const
{
    return m_file;
}

198 199 200
QString PlayerManager::playingString() const
{
    if(!playing())
201
        return QString();
202

203 204 205 206 207
    QString str = m_file.tag()->artist() + " - " + m_file.tag()->title();
    if(m_file.tag()->artist().isEmpty())
        str = m_file.tag()->title();

    return str;
208 209
}

210
void PlayerManager::setPlaylistInterface(PlaylistInterface *interface)
211 212 213 214
{
    m_playlistInterface = interface;
}

215 216 217 218 219
void PlayerManager::setStatusLabel(StatusLabel *label)
{
    m_statusLabel = label;
}

220 221 222 223
////////////////////////////////////////////////////////////////////////////////
// public slots
////////////////////////////////////////////////////////////////////////////////

224
void PlayerManager::play(const FileHandle &file)
225
{
226 227 228 229
    if(!m_media)
        setup();

    if(!m_media || !m_playlistInterface)
230 231
        return;

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

242
            if(!m_file.isNull())
243
            {
Matthias Kretz's avatar
Matthias Kretz committed
244
                m_media->setCurrentSource(KUrl::fromPath(m_file.absFilePath()));
245 246
                m_media->play();
            }
247
        }
248
    }
249
    else {
250
        m_file = file;
Matthias Kretz's avatar
Matthias Kretz committed
251 252 253 254 255 256 257 258 259 260 261 262 263 264
        if(m_media->state() == Phonon::PlayingState)
        {
            // do a crossfade
            Phonon::VolumeFaderEffect *fader1 = new Phonon::VolumeFaderEffect(m_audioPath);
            m_audioPath->insertEffect(fader1);
            Phonon::MediaObject *mo = m_media;
            Phonon::AudioPath *ap = m_audioPath;

            m_audioPath = new Phonon::AudioPath(this);
            m_audioPath->addOutput(m_output);
            Phonon::VolumeFaderEffect *fader2 = new Phonon::VolumeFaderEffect(m_audioPath);
            m_audioPath->insertEffect(fader2);

            m_media = new Phonon::MediaObject(this);
Matthias Kretz's avatar
Matthias Kretz committed
265
            connect(m_media, SIGNAL(stateChanged(Phonon::State, Phonon::State)), SLOT(slotStateChanged(Phonon::State)));
Matthias Kretz's avatar
Matthias Kretz committed
266 267 268 269 270
            connect(m_media, SIGNAL(aboutToFinsh()), SLOT(slotNeedNextUrl()));
            m_media->addAudioPath(m_audioPath);
            m_media->setTickInterval(200);
            if(m_sliderAction->trackPositionSlider())
                m_sliderAction->trackPositionSlider()->setMediaObject(m_media);
Matthias Kretz's avatar
Matthias Kretz committed
271
            connect(m_media, SIGNAL(totalTimeChanged(qint64)), SLOT(slotLength(qint64)));
Matthias Kretz's avatar
Matthias Kretz committed
272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287
            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()));
            QTimer::singleShot(3000, ap, SLOT(deleteLater()));
        }
        else
        {
            m_media->setCurrentSource(KUrl::fromPath(m_file.absFilePath()));
            m_media->play();
        }
288
    }
289

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

292
    if(!playing()) {
Scott Wheeler's avatar
Scott Wheeler committed
293
        kWarning(65432) << "Unable to play " << file.absFilePath() << endl;
294 295 296
        stop();
        return;
    }
297

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

305
    emit signalPlay();
306 307
}

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

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

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

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

334
    m_media->pause();
335 336

    emit signalPause();
337 338 339 340
}

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

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

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

358 359
    m_file = FileHandle::null();

360
    emit signalStop();
361 362
}

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

Scott Wheeler's avatar
Scott Wheeler committed
370
void PlayerManager::seek(int seekTime)
371
{
372
    if(!m_media)
373
        return;
374

375
    m_media->seek(seekTime);
376 377
}

378
/*
379
void PlayerManager::seekPosition(int position)
380
{
381
    if(!m_media)
382 383
        return;

384
    if(!playing() || m_noSeek)
385
        return;
386

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

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

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

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

411 412
void PlayerManager::forward()
{
413 414
    m_playlistInterface->playNext();
    FileHandle file = m_playlistInterface->currentFile();
415

416 417 418 419 420 421 422 423
    if(!file.isNull())
        play(file);
    else
        stop();
}

void PlayerManager::back()
{
424 425
    m_playlistInterface->playPrevious();
    FileHandle file = m_playlistInterface->currentFile();
426

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

433 434
void PlayerManager::volumeUp()
{
435
    if(!m_output)
436
        return;
437

438 439
    float volume = m_output->volume() + 0.04; // 4% up
    m_output->setVolume(volume);
440 441 442 443
}

void PlayerManager::volumeDown()
{
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::mute()
{
453
    if(!m_output)
454 455
        return;

456
    m_output->setMuted(!m_output->isMuted());
457 458
}

459 460 461
////////////////////////////////////////////////////////////////////////////////
// private slots
////////////////////////////////////////////////////////////////////////////////
462

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

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

496 497 498 499 500
void PlayerManager::slotLength(qint64 msec)
{
    m_statusLabel->setItemTotalTime(msec / 1000);
}

501
void PlayerManager::slotTick(qint64 msec)
502
{
503
    if(!m_media || !m_playlistInterface)
504
        return;
505

506
    m_noSeek = true;
507

508 509
    if(m_statusLabel) {
        m_statusLabel->setItemCurrentTime(msec / 1000);
510
    }
511

512
    m_noSeek = false;
513 514
}

Matthias Kretz's avatar
Matthias Kretz committed
515 516 517 518 519 520 521 522 523 524 525 526 527 528 529 530 531 532 533
void PlayerManager::slotStateChanged(Phonon::State newstate)
{
    if(newstate == Phonon::ErrorState)
    {
        switch(m_media->errorType())
        {
            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
    {
Laurent Montel's avatar
Laurent Montel committed
563
        kWarning(65432) << k_funcinfo << "Could not find all of the required actions." << endl;
564 565 566
        return;
    }

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

571 572 573
    m_output = new Phonon::AudioOutput(Phonon::MusicCategory, this);
    m_audioPath = new Phonon::AudioPath(this);
    m_audioPath->addOutput(m_output);
574

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

581 582
    // initialize action states

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

589 590
    // setup sliders

591
    m_sliderAction = action<SliderAction>("trackPositionAction");
592

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

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

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

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

633
#include "playermanager.moc"
634

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