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 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
#include <phonon/mediaobject.h>
29
#include <phonon/mediaqueue.h>
30 31 32
#include <phonon/audiopath.h>
#include <phonon/audiooutput.h>

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 54
#include "playeradaptor.h"

55
using namespace ActionCollection;
56

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

59 60 61 62 63
////////////////////////////////////////////////////////////////////////////////
// protected members
////////////////////////////////////////////////////////////////////////////////

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

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

PlayerManager::~PlayerManager()
{
}

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

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

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

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

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

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

float PlayerManager::volume() const
{
119
    if(!m_output)
120 121
        return 0;

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

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

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

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

136 137 138
    return 0;
}

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

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

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

152
    return m_media->currentTime() / 1000;
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
QStringList PlayerManager::trackProperties()
{
    return FileHandle::properties();
}

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

    return m_file.property(property);
}

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

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

    return QPixmap();
}

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

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

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

    return str;
207 208
}

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

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

219 220 221 222
////////////////////////////////////////////////////////////////////////////////
// public slots
////////////////////////////////////////////////////////////////////////////////

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

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

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

243
            if(!m_file.isNull())
244 245 246 247
            {
                m_media->setUrl(KUrl::fromPath(m_file.absFilePath()));
                m_media->play();
            }
248
        }
249
    }
250
    else {
251
        m_file = file;
252 253
        m_media->setUrl(KUrl::fromPath(m_file.absFilePath()));
        m_media->play();
254
    }
255

256
    // Make sure that the player() actually starts before doing anything.
257

258
    if(!playing()) {
Scott Wheeler's avatar
Scott Wheeler committed
259
        kWarning(65432) << "Unable to play " << file.absFilePath() << endl;
260 261 262
        stop();
        return;
    }
263

264 265 266
    action("pause")->setEnabled(true);
    action("stop")->setEnabled(true);
    action("forward")->setEnabled(true);
267 268
    if(action<KToggleAction>("albumRandomPlay")->isChecked())
        action("forwardAlbum")->setEnabled(true);
269
    action("back")->setEnabled(true);
270

271 272
    if(m_sliderAction->trackPositionSlider())
        m_sliderAction->trackPositionSlider()->setEnabled(true);
273

274
    emit signalPlay();
275 276
}

277 278 279 280 281 282 283 284 285
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
286 287 288 289 290
void PlayerManager::play()
{
    play(FileHandle::null());
}

291 292
void PlayerManager::pause()
{
293
    if(!m_media)
294 295
        return;

296
    if(paused()) {
297 298 299 300
        play();
        return;
    }

301
    action("pause")->setEnabled(false);
302

303
    m_media->pause();
304 305

    emit signalPause();
306 307 308 309
}

void PlayerManager::stop()
{
310
    if(!m_media || !m_playlistInterface)
311 312
        return;

313 314 315 316
    action("pause")->setEnabled(false);
    action("stop")->setEnabled(false);
    action("back")->setEnabled(false);
    action("forward")->setEnabled(false);
317
    action("forwardAlbum")->setEnabled(false);
318

319 320 321 322
    if(m_sliderAction->trackPositionSlider()) {
        m_sliderAction->trackPositionSlider()->setValue(0);
        m_sliderAction->trackPositionSlider()->setEnabled(false);
    }
323

324
    m_media->stop();
325
    m_playlistInterface->stop();
326

327 328
    m_file = FileHandle::null();

329
    emit signalStop();
330 331
}

Laurent Montel's avatar
Laurent Montel committed
332
void PlayerManager::setVolume(float volume)
333
{
334 335 336
    if(!m_output)
        setup();
    m_output->setVolume(volume);
337 338
}

Scott Wheeler's avatar
Scott Wheeler committed
339
void PlayerManager::seek(int seekTime)
340
{
341
    if(!m_media)
342
        return;
343

344
    m_media->seek(seekTime);
345 346
}

347
void PlayerManager::seekPosition(int position)
348
{
349
    if(!m_media)
350 351
        return;

352
    if(!playing() || m_noSeek)
353
        return;
354

355
    slotUpdateTime(position);
356
    m_media->seek(static_cast<qint64>(static_cast<float>(m_media->totalTime() * position) / SliderAction::maxPosition + 0.5f));
357 358 359

    if(m_sliderAction->trackPositionSlider())
        m_sliderAction->trackPositionSlider()->setValue(position);
360 361 362 363
}

void PlayerManager::seekForward()
{
Laurent Montel's avatar
Laurent Montel committed
364
    seekPosition(qMin(SliderAction::maxPosition, position() + 10));
365 366 367 368
}

void PlayerManager::seekBack()
{
Laurent Montel's avatar
Laurent Montel committed
369
    seekPosition(qMax(SliderAction::minPosition, position() - 10));
370 371
}

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

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

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

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

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

399 400
void PlayerManager::volumeUp()
{
401
    if(!m_media || !m_sliderAction || !m_sliderAction->volumeSlider())
402
        return;
403

404
    int volume = m_sliderAction->volumeSlider()->volume() +
405
        m_sliderAction->volumeSlider()->maximum() / 25; // 4% up
406 407

    slotSetVolume(volume);
408
    m_sliderAction->volumeSlider()->setVolume(volume);
409 410 411 412
}

void PlayerManager::volumeDown()
{
413
    if(!m_media || !m_sliderAction || !m_sliderAction->volumeSlider())
414
        return;
415 416

    int volume = m_sliderAction->volumeSlider()->value() -
417
        m_sliderAction->volumeSlider()->maximum() / 25; // 4% down
418 419

    slotSetVolume(volume);
420
    m_sliderAction->volumeSlider()->setVolume(volume);
421 422 423 424
}

void PlayerManager::mute()
{
425
    if(!m_media || !m_sliderAction || !m_sliderAction->volumeSlider())
426 427 428 429 430 431
        return;

    slotSetVolume(m_muted ? m_sliderAction->volumeSlider()->value() : 0);
    m_muted = !m_muted;
}

432 433 434
////////////////////////////////////////////////////////////////////////////////
// private slots
////////////////////////////////////////////////////////////////////////////////
435

436 437 438 439 440 441 442 443 444 445 446 447 448 449
void PlayerManager::slotNeedNextUrl()
{
    m_playlistInterface->playNext();
    FileHandle nextFile = m_playlistInterface->currentFile();
    if(!nextFile.isNull())
    {
        //kDebug() << k_funcinfo << m_file.absFilePath() << endl;
        m_file = nextFile;
        m_mqueue->setNextUrl(KUrl::fromPath(m_file.absFilePath()));
        m_ignoreFinished = true;
    }
    // at this point the new totalTime is not known, but the length signal will tell us
}

450 451
void PlayerManager::slotFinished()
{
452 453 454 455 456 457
    if(m_ignoreFinished)
    {
        //kDebug() << k_funcinfo << "ignoring finished signal" << endl;
        m_ignoreFinished = false;
        return;
    }
458 459 460 461 462 463 464 465 466
    m_playlistInterface->playNext();
    FileHandle nextFile = m_playlistInterface->currentFile();
    if(!nextFile.isNull())
        play(nextFile);
    else
        stop();
    m_statusLabel->setItemTotalTime(totalTime());
}

467 468 469 470 471
void PlayerManager::slotLength(qint64 msec)
{
    m_statusLabel->setItemTotalTime(msec / 1000);
}

472
void PlayerManager::slotTick(qint64 msec)
473
{
474
    if(!m_media || !m_playlistInterface)
475
        return;
476

477
    m_noSeek = true;
478

479 480
    if(!m_sliderAction->dragging()) {
        if(m_sliderAction->trackPositionSlider()) {
481
            int position = static_cast<int>(static_cast<float>(msec * SliderAction::maxPosition) / m_media->totalTime() + 0.5f);
482 483
            m_sliderAction->trackPositionSlider()->setValue(position);
        }
484

485
        if(m_statusLabel) {
486
            m_statusLabel->setItemCurrentTime(msec / 1000);
487
        }
488
    }
489

490
    // This call is done because when the user adds the slider to the toolbar
491
    // while playback is occurring the volume slider generally defaults to 0,
492 493 494 495 496 497
    // and doesn't get updated to the correct volume.  It might be better to
    // have the SliderAction class fill in the correct volume, but I'm trying
    // to avoid having it depend on PlayerManager since it may not be
    // constructed in time during startup. -mpyne

    if(!m_sliderAction->volumeDragging() && m_sliderAction->volumeSlider())
498
    {
499
        int maxV = m_sliderAction->volumeSlider()->maximum();
500
        float v = sqrt(sqrt(volume())); // Cancel out exponential scaling
501 502 503 504 505

        m_sliderAction->volumeSlider()->blockSignals(true);
        m_sliderAction->volumeSlider()->setVolume((int)((v) * maxV));
        m_sliderAction->volumeSlider()->blockSignals(false);
    }
506

507
    m_noSeek = false;
508 509
}

510 511
void PlayerManager::slotSetVolume(int volume)
{
512 513
    float scaledVolume;

514
    if(m_sliderAction->volumeSlider())
515
        scaledVolume = float(volume) / m_sliderAction->volumeSlider()->maximum();
516 517
    else {
        scaledVolume = float(volume) / 100.0; // Hopefully this is accurate
Laurent Montel's avatar
Laurent Montel committed
518
        scaledVolume = qMin(1.0f, scaledVolume);
519 520 521 522 523 524 525
    }

    // Perform exponential scaling to counteract the fact that humans perceive
    // volume changes logarithmically.

    scaledVolume *= scaledVolume;
    scaledVolume *= scaledVolume;
526
    m_output->setVolume(scaledVolume); // scaledVolume ^ 4
527 528 529 530 531 532 533
}

void PlayerManager::slotUpdateTime(int position)
{
    if(!m_statusLabel)
        return;

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

    m_statusLabel->setItemCurrentTime(seekTime);
}

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

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

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

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

564 565 566
    m_output = new Phonon::AudioOutput(Phonon::MusicCategory, this);
    m_audioPath = new Phonon::AudioPath(this);
    m_audioPath->addOutput(m_output);
567 568 569 570 571 572 573 574 575 576

    m_mqueue = new Phonon::MediaQueue(this);
    if(m_mqueue->isValid())
        m_media = m_mqueue;
    else
    {
        delete m_mqueue;
        m_mqueue = 0;
        m_media = new Phonon::MediaObject(this);
    }
577
    m_media->addAudioPath(m_audioPath);
578
    m_media->setTickInterval(200);
579

580 581
    // initialize action states

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

588 589
    // setup sliders

590
    m_sliderAction = action<SliderAction>("trackPositionAction");
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
    connect(m_sliderAction, SIGNAL(signalVolumeChanged(int)),
597 598
            this, SLOT(slotSetVolume(int)));

599
    float volume;
600

601
    if(m_sliderAction->volumeSlider()) {
602
        volume =
603
            float(m_sliderAction->volumeSlider()->volume()) /
604
            float(m_sliderAction->volumeSlider()->maximum());
605 606
    }
    else
607
        volume = 1.0f; // Assume user wants full volume
608

609
    m_output->setVolume(volume);
610

611 612
    connect(m_media, SIGNAL(length(qint64)), SLOT(slotLength(qint64)));
    connect(m_media, SIGNAL(tick(qint64)), SLOT(slotTick(qint64)));
613
    connect(m_media, SIGNAL(finished()), SLOT(slotFinished()));
614
    connect(m_mqueue, SIGNAL(needNextUrl()), SLOT(slotNeedNextUrl()));
615 616
}

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

636
#include "playermanager.moc"
637

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