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>
25

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

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

35 36
#include <math.h>

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

using namespace ActionCollection;
47

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

50 51 52 53 54
////////////////////////////////////////////////////////////////////////////////
// protected members
////////////////////////////////////////////////////////////////////////////////

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

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

PlayerManager::~PlayerManager()
{
}

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

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

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

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

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

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

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

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

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

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

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

124 125 126
    return 0;
}

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

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

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

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

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

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

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

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

    return m_file.property(property);
}

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

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

    return QPixmap();
}

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

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

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

    return str;
197 198
}

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

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

209 210 211 212
////////////////////////////////////////////////////////////////////////////////
// public slots
////////////////////////////////////////////////////////////////////////////////

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

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

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

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

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

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

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

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

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

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

301
    emit signalPlay();
302 303
}

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

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

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

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

330
    m_media->pause();
331 332

    emit signalPause();
333 334 335 336
}

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

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

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

354 355
    m_file = FileHandle::null();

356
    emit signalStop();
357 358
}

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

455 456 457
////////////////////////////////////////////////////////////////////////////////
// private slots
////////////////////////////////////////////////////////////////////////////////
458

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

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

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

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

502
    m_noSeek = true;
503

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

508
    m_noSeek = false;
509 510
}

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

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

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

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

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

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

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

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

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

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

578 579
    // initialize action states

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

586 587
    // setup sliders

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

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

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

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

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

630
#include "playermanager.moc"
631

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