playermanager.cpp 15.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 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
#include <kdebug.h>
26
#include <klocale.h>
27

28 29 30
#include <phonon/mediaobject.h>
#include <phonon/audiopath.h>
#include <phonon/audiooutput.h>
Matthias Kretz's avatar
Matthias Kretz committed
31
#include <phonon/volumefadereffect.h>
32

33
#include <qslider.h>
Laurent Montel's avatar
Laurent Montel committed
34 35
//Added by qt3to4:
#include <QPixmap>
Laurent Montel's avatar
Poirt  
Laurent Montel committed
36 37 38
#include <ktoggleaction.h>
#include <kactioncollection.h>
#include <kselectaction.h>
39 40
#include <math.h>

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

50 51
#include "config.h"

Laurent Montel's avatar
Laurent Montel committed
52
#include <QtDBus>
Laurent Montel's avatar
Laurent Montel committed
53
#include "playeradaptor.h"
Matthias Kretz's avatar
Matthias Kretz committed
54
#include <QTimer>
Laurent Montel's avatar
Laurent Montel committed
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
Laurent Montel committed
81
    new PlayerAdaptor( this );
82
    QDBusConnection::sessionBus().registerObject("/Player", this);
Laurent Montel's avatar
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 265 266 267 268 269
        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);
            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
270
            connect(m_media, SIGNAL(totalTimeChanged(qint64)), SLOT(slotLength(qint64)));
Matthias Kretz's avatar
Matthias Kretz committed
271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286
            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();
        }
287
    }
288

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

291
    if(!playing()) {
Scott Wheeler's avatar
Scott Wheeler committed
292
        kWarning(65432) << "Unable to play " << file.absFilePath() << endl;
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

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

357 358
    m_file = FileHandle::null();

359
    emit signalStop();
360 361
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

void PlayerManager::volumeDown()
{
443
    if(!m_output)
444
        return;
445

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

void PlayerManager::mute()
{
452
    if(!m_output)
453 454
        return;

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

458 459 460
////////////////////////////////////////////////////////////////////////////////
// private slots
////////////////////////////////////////////////////////////////////////////////
461

462 463
void PlayerManager::slotNeedNextUrl()
{
Matthias Kretz's avatar
Matthias Kretz committed
464 465 466 467
    if(m_file.isNull())
    {
        return;
    }
468 469 470 471 472 473
    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
474
        m_media->enqueue(KUrl::fromPath(m_file.absFilePath()));
475
    }
Matthias Kretz's avatar
Matthias Kretz committed
476
    // at this point the new totalTime is not known, but the totalTimeChanged signal will tell us
477 478
}

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

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

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

505
    m_noSeek = true;
506

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

511
    m_noSeek = false;
512 513
}

514
/*
515 516 517 518 519
void PlayerManager::slotUpdateTime(int position)
{
    if(!m_statusLabel)
        return;

520
    float positionFraction = float(position) / SliderAction::maxPosition;
521
    float totalTime = float(m_media->totalTime()) / 1000.0f;
Scott Wheeler's avatar
Scott Wheeler committed
522
    int seekTime = int(positionFraction * totalTime + 0.5); // "+0.5" for rounding
523 524 525

    m_statusLabel->setItemCurrentTime(seekTime);
}
526
*/
527

528 529 530 531 532 533 534 535
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////

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

536 537 538
    if(!action("pause") ||
       !action("stop") ||
       !action("back") ||
539
       !action("forwardAlbum") ||
540 541
       !action("forward") ||
       !action("trackPositionAction"))
542
    {
Laurent Montel's avatar
Laurent Montel committed
543
        kWarning(65432) << k_funcinfo << "Could not find all of the required actions." << endl;
544 545 546
        return;
    }

547 548 549 550
    if(m_setup)
        return;
    m_setup = true;

551 552 553
    m_output = new Phonon::AudioOutput(Phonon::MusicCategory, this);
    m_audioPath = new Phonon::AudioPath(this);
    m_audioPath->addOutput(m_output);
554

Matthias Kretz's avatar
Matthias Kretz committed
555 556
    m_media = new Phonon::MediaObject(this);
    connect(m_media, SIGNAL(aboutToFinish()), SLOT(slotNeedNextUrl()));
557
    m_media->addAudioPath(m_audioPath);
558
    m_media->setTickInterval(200);
559

560 561
    // initialize action states

562 563 564 565
    action("pause")->setEnabled(false);
    action("stop")->setEnabled(false);
    action("back")->setEnabled(false);
    action("forward")->setEnabled(false);
566
    action("forwardAlbum")->setEnabled(false);
567

568 569
    // setup sliders

570
    m_sliderAction = action<SliderAction>("trackPositionAction");
571

572
    /*
573 574 575 576
    connect(m_sliderAction, SIGNAL(signalPositionChanged(int)),
            this, SLOT(seekPosition(int)));
    connect(m_sliderAction->trackPositionSlider(), SIGNAL(valueChanged(int)),
            this, SLOT(slotUpdateTime(int)));
577
            */
578

579 580
    if(m_sliderAction->trackPositionSlider())
    {
Matthias Kretz's avatar
Matthias Kretz committed
581
        m_sliderAction->trackPositionSlider()->setMediaObject(m_media);
582 583 584 585
    }
    if(m_sliderAction->volumeSlider())
    {
        m_sliderAction->volumeSlider()->setAudioOutput(m_output);
586
    }
587

Matthias Kretz's avatar
Matthias Kretz committed
588
    connect(m_media, SIGNAL(totalTimeChanged(qint64)), SLOT(slotLength(qint64)));
589
    connect(m_media, SIGNAL(tick(qint64)), SLOT(slotTick(qint64)));
590
    connect(m_media, SIGNAL(finished()), SLOT(slotFinished()));
591 592
}

Michael Pyne's avatar
Michael Pyne committed
593 594 595 596 597 598 599 600 601 602 603
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)
{
604
    if(randomMode.toLower() == "random")
Michael Pyne's avatar
Michael Pyne committed
605
        action<KToggleAction>("randomPlay")->setChecked(true);
606
    if(randomMode.toLower() == "albumrandom")
Michael Pyne's avatar
Michael Pyne committed
607
        action<KToggleAction>("albumRandomPlay")->setChecked(true);
608
    if(randomMode.toLower() == "norandom")
Michael Pyne's avatar
Michael Pyne committed
609 610 611
        action<KToggleAction>("disableRandomPlay")->setChecked(true);
}

612
#include "playermanager.moc"
613

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