playermanager.cpp 13.8 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
/**
 * Copyright (C) 2004 Scott Wheeler <wheeler@kde.org>
 * Copyright (C) 2007 Matthias Kretz <kretz@kde.org>
 * Copyright (C) 2008, 2009 Michael Pyne <mpyne@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.
 *
 * This program is distributed in the hope that it will be useful, but WITHOUT ANY
 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
 * PARTICULAR PURPOSE. See the GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License along with
 * this program.  If not, see <http://www.gnu.org/licenses/>.
 */
18

19 20
#include "playermanager.h"

Matthias Kretz's avatar
Matthias Kretz committed
21
#include <kmessagebox.h>
22 23 24
#include <kactioncollection.h>
#include <kselectaction.h>
#include <ktoggleaction.h>
Michael Pyne's avatar
Michael Pyne committed
25
#include <KLocalizedString>
26

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

Laurent Montel's avatar
Laurent Montel committed
30
#include <QPixmap>
31
#include <QTimer>
Michael Pyne's avatar
Michael Pyne committed
32
#include <QUrl>
33

34 35
#include <math.h>

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

using namespace ActionCollection;
49

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

52 53 54 55 56
////////////////////////////////////////////////////////////////////////////////
// protected members
////////////////////////////////////////////////////////////////////////////////

PlayerManager::PlayerManager() :
57
    QObject(),
58 59 60
    m_playlistInterface(nullptr),
    m_statusLabel(nullptr),
    m_setup(false)
61
{
Michael Pyne's avatar
Michael Pyne committed
62 63 64 65 66
// 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();
67
    new PlayerAdaptor(this);
68 69 70 71 72 73
}

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

74 75
bool PlayerManager::playing() const
{
76
    if(!m_setup)
77 78
        return false;

79
    Phonon::State state = m_media->state();
80
    return (state == Phonon::PlayingState || state == Phonon::BufferingState);
81 82 83 84
}

bool PlayerManager::paused() const
{
85
    if(!m_setup)
86 87
        return false;

88
    return m_media->state() == Phonon::PausedState;
89 90
}

91 92 93 94 95
bool PlayerManager::muted() const
{
    if(!m_setup)
        return false;

96
    return m_output->isMuted();
97 98
}

99 100
float PlayerManager::volume() const
{
101
    if(!m_setup)
102
        return 1.0;
103

104
    return m_output->volume();
105 106
}

107 108
int PlayerManager::status() const
{
109
    if(!m_setup)
110
        return StatusStopped;
111

112
    if(paused())
113
        return StatusPaused;
114

115
    if(playing())
116
        return StatusPlaying;
117

118
    return StatusStopped;
119 120
}

Scott Wheeler's avatar
Scott Wheeler committed
121
int PlayerManager::totalTime() const
122 123 124 125 126 127 128 129 130 131
{
    return totalTimeMSecs() / 1000;
}

int PlayerManager::currentTime() const
{
    return currentTimeMSecs() / 1000;
}

int PlayerManager::totalTimeMSecs() const
132
{
133
    if(!m_setup)
134 135
        return 0;

136
    return m_media->totalTime();
137 138
}

139
int PlayerManager::currentTimeMSecs() const
140
{
141
    if(!m_setup)
142 143
        return 0;

144
    return m_media->currentTime();
145 146
}

147 148 149 150 151
bool PlayerManager::seekable() const
{
    if(!m_setup)
        return false;

152
    return m_media->isSeekable();
153 154
}

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

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

    return m_file.property(property);
}

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

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

    return QPixmap();
}

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

186 187
QString PlayerManager::playingString() const
{
188
    if(!playing() || m_file.isNull())
189
        return QString();
190

191
    return m_file.tag()->playingString();
192 193
}

194
void PlayerManager::setPlaylistInterface(PlaylistInterface *interface)
195 196 197 198
{
    m_playlistInterface = interface;
}

199 200 201 202 203
void PlayerManager::setStatusLabel(StatusLabel *label)
{
    m_statusLabel = label;
}

204 205 206 207
////////////////////////////////////////////////////////////////////////////////
// public slots
////////////////////////////////////////////////////////////////////////////////

208
void PlayerManager::play(const FileHandle &file)
209
{
210
    if(!m_setup)
211 212
        setup();

213
    if(!m_media || !m_playlistInterface)
214 215
        return;

216
    if(file.isNull()) {
217
        if(paused())
218
            m_media->play();
219
        else if(playing()) {
220
            m_media->seek(0);
221
            emit seeked(0);
222
        }
223
        else {
224
            m_playlistInterface->playNext();
225
            m_file = m_playlistInterface->currentFile();
226

227
            if(!m_file.isNull())
228
            {
229 230
                m_media->setCurrentSource(QUrl::fromLocalFile(m_file.absFilePath()));
                m_media->play();
231 232

                emit signalItemChanged(m_file);
233
            }
234
        }
235
    }
236
    else {
237 238
        m_media->setCurrentSource(QUrl::fromLocalFile(file.absFilePath()));
        m_media->play();
239 240 241 242 243

        if(m_file != file)
            emit signalItemChanged(file);

        m_file = file;
244
    }
245

246 247
    // Our state changed handler will perform the follow up actions necessary
    // once we actually start playing.
248 249
}

250 251 252 253 254 255 256 257 258
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
259 260 261 262 263
void PlayerManager::play()
{
    play(FileHandle::null());
}

264 265
void PlayerManager::pause()
{
266
    if(!m_setup)
267 268
        return;

269
    if(paused()) {
270 271 272 273
        play();
        return;
    }

274
    action("pause")->setEnabled(false);
275

276
    m_media->pause();
277 278 279 280
}

void PlayerManager::stop()
{
281
    if(!m_setup || !m_playlistInterface)
282 283
        return;

284 285 286 287
    action("pause")->setEnabled(false);
    action("stop")->setEnabled(false);
    action("back")->setEnabled(false);
    action("forward")->setEnabled(false);
288
    action("forwardAlbum")->setEnabled(false);
289

290
    m_media->stop();
291 292 293 294 295

    if(!m_file.isNull()) {
        m_file = FileHandle::null();
        emit signalItemChanged(m_file);
    }
296 297
}

Laurent Montel's avatar
Laurent Montel committed
298
void PlayerManager::setVolume(float volume)
299
{
300
    if(!m_setup)
301
        setup();
302

303
    m_output->setVolume(volume);
304 305
}

306
void PlayerManager::seek(int seekTime)
307
{
308
    if(!m_setup || m_media->currentTime() == seekTime)
309
        return;
310

311
    m_media->seek(seekTime);
312
    emit seeked(seekTime);
313 314 315 316
}

void PlayerManager::seekForward()
{
317 318
    const qint64 total = m_media->totalTime();
    const qint64 newtime = m_media->currentTime() + total / 100;
319
    const qint64 seekTo = qMin(total, newtime);
320

321
    m_media->seek(seekTo);
322
    emit seeked(seekTo);
323 324 325 326
}

void PlayerManager::seekBack()
{
327 328
    const qint64 total = m_media->totalTime();
    const qint64 newtime = m_media->currentTime() - total / 100;
329
    const qint64 seekTo = qMax(qint64(0), newtime);
330

331
    m_media->seek(seekTo);
332
    emit seeked(seekTo);
333 334
}

335 336
void PlayerManager::playPause()
{
David Faure's avatar
David Faure committed
337
    playing() ? action("pause")->trigger() : action("play")->trigger();
338 339
}

340 341
void PlayerManager::forward()
{
342 343
    m_playlistInterface->playNext();
    FileHandle file = m_playlistInterface->currentFile();
344

345 346 347 348 349 350 351 352
    if(!file.isNull())
        play(file);
    else
        stop();
}

void PlayerManager::back()
{
353 354
    m_playlistInterface->playPrevious();
    FileHandle file = m_playlistInterface->currentFile();
355

356 357 358 359 360 361
    if(!file.isNull())
        play(file);
    else
        stop();
}

362 363
void PlayerManager::volumeUp()
{
364
    if(!m_setup)
365
        return;
366

367
    setVolume(volume() + 0.04); // 4% up
368 369 370 371
}

void PlayerManager::volumeDown()
{
372
    if(!m_setup)
373
        return;
374

375
    setVolume(volume() - 0.04); // 4% down
376 377
}

378
void PlayerManager::setMuted(bool m)
379
{
380
    if(!m_setup)
381 382
        return;

383
    m_output->setMuted(m);
384 385 386 387 388 389 390 391 392 393
}

bool PlayerManager::mute()
{
    if(!m_setup)
        return false;

    bool newState = !muted();
    setMuted(newState);
    return newState;
394 395
}

396 397 398
////////////////////////////////////////////////////////////////////////////////
// private slots
////////////////////////////////////////////////////////////////////////////////
399

400 401
void PlayerManager::slotFinished()
{
402 403
    // It is possible to end up in this function if a file simply fails to play or if the
    // user moves the slider all the way to the end, therefore see if we can keep playing
404
    // and if we can, do so.  Otherwise, stop.
405

406
    m_playlistInterface->playNext();
407
    play(m_playlistInterface->currentFile());
408 409
}

410 411 412
void PlayerManager::slotLength(qint64 msec)
{
    m_statusLabel->setItemTotalTime(msec / 1000);
413
    emit totalTimeChanged(msec);
414 415
}

416
void PlayerManager::slotTick(qint64 msec)
417
{
418
    if(!m_setup || !m_playlistInterface)
419
        return;
420

421
    if(m_statusLabel)
422
        m_statusLabel->setItemCurrentTime(msec / 1000);
423 424

    emit tick(msec);
425 426
}

427
void PlayerManager::slotStateChanged(Phonon::State newstate, Phonon::State)
Matthias Kretz's avatar
Matthias Kretz committed
428
{
429
    if(newstate == Phonon::ErrorState) {
430 431 432
        QString errorMessage =
            i18nc(
              "%1 will be the /path/to/file, %2 will be some string from Phonon describing the error",
433 434
              "JuK is unable to play the audio file<nl/><filename>%1</filename><nl/>"
                "for the following reason:<nl/><message>%2</message>",
435
              m_file.absFilePath(),
436
              m_media->errorString()
437 438
            );

439 440 441 442 443
        qCWarning(JUK_LOG)
                << "Phonon is in error state" << m_media->errorString()
                << "while playing" << m_file.absFilePath();

        switch(m_media->errorType()) {
444
            case Phonon::NoError:
Michael Pyne's avatar
Michael Pyne committed
445
                qCDebug(JUK_LOG) << "received a state change to ErrorState but errorType is NoError!?";
446
                break;
447

Matthias Kretz's avatar
Matthias Kretz committed
448
            case Phonon::NormalError:
449
                KMessageBox::information(0, errorMessage);
Matthias Kretz's avatar
Matthias Kretz committed
450
                break;
451

Matthias Kretz's avatar
Matthias Kretz committed
452
            case Phonon::FatalError:
453
                KMessageBox::sorry(0, errorMessage);
Matthias Kretz's avatar
Matthias Kretz committed
454 455
                break;
        }
456

457
        stop();
458
        return;
459
    }
460 461 462

    // "normal" path
    if(newstate == Phonon::StoppedState) {
463 464 465
        JuK::JuKInstance()->setWindowTitle(i18n("JuK"));
        emit signalStop();
    }
466 467 468 469
    else if(newstate == Phonon::PausedState) {
        emit signalPause();
    }
    else { // PlayingState or BufferingState
470 471 472 473 474 475 476
        action("pause")->setEnabled(true);
        action("stop")->setEnabled(true);
        action("forward")->setEnabled(true);
        if(action<KToggleAction>("albumRandomPlay")->isChecked())
            action("forwardAlbum")->setEnabled(true);
        action("back")->setEnabled(true);

477 478
        JuK::JuKInstance()->setWindowTitle(i18nc(
            "%1 is the artist and %2 is the title of the currently playing track.", 
479 480
            "%1 - %2 :: JuK",
            m_file.tag()->artist(),
481
            m_file.tag()->title()));
482

483 484
        emit signalPlay();
    }
485 486
}

487 488 489 490 491 492 493 494 495 496 497 498
void PlayerManager::slotSeekableChanged(bool isSeekable)
{
    emit seekableChanged(isSeekable);
}

void PlayerManager::slotMutedChanged(bool muted)
{
    emit mutedChanged(muted);
}

void PlayerManager::slotVolumeChanged(qreal volume)
{
499
    if(qFuzzyCompare(m_output->volume(), volume))
500
    {
501
        return;
502
    }
503 504 505 506

    emit volumeChanged(volume);
}

507 508 509 510 511 512
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////

void PlayerManager::setup()
{
513 514 515 516
    if(m_setup)
        return;

    m_setup = true;
517

518
    // All of the actions required by this class should be listed here.
519 520 521
    if(!action("pause") ||
       !action("stop") ||
       !action("back") ||
522
       !action("forwardAlbum") ||
523 524
       !action("forward") ||
       !action("trackPositionAction"))
525
    {
Michael Pyne's avatar
Michael Pyne committed
526
        qCWarning(JUK_LOG) << "Could not find all of the required actions.";
527 528 529
        return;
    }

530 531 532 533
    using namespace Phonon;
    m_output = new AudioOutput(MusicCategory, this);
    connect(m_output, &AudioOutput::mutedChanged,  this, &PlayerManager::slotMutedChanged);
    connect(m_output, &AudioOutput::volumeChanged, this, &PlayerManager::slotVolumeChanged);
534

535 536 537 538 539 540 541 542
    m_media = new MediaObject(this);
    m_audioPath = createPath(m_media, m_output);

    connect(m_media, &MediaObject::stateChanged, this, &PlayerManager::slotStateChanged);
    connect(m_media, &MediaObject::totalTimeChanged, this, &PlayerManager::slotLength);
    connect(m_media, &MediaObject::tick, this, &PlayerManager::slotTick);
    connect(m_media, &MediaObject::finished, this, &PlayerManager::slotFinished);
    connect(m_media, &MediaObject::seekableChanged, this, &PlayerManager::slotSeekableChanged);
543

544 545
    // initialize action states

546 547 548 549
    action("pause")->setEnabled(false);
    action("stop")->setEnabled(false);
    action("back")->setEnabled(false);
    action("forward")->setEnabled(false);
550
    action("forwardAlbum")->setEnabled(false);
551

552
    QDBusConnection::sessionBus().registerObject("/Player", this);
553 554
}

Michael Pyne's avatar
Michael Pyne committed
555 556 557 558 559 560 561 562 563 564 565
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)
{
566
    if(randomMode.toLower() == "random")
Michael Pyne's avatar
Michael Pyne committed
567
        action<KToggleAction>("randomPlay")->setChecked(true);
568
    if(randomMode.toLower() == "albumrandom")
Michael Pyne's avatar
Michael Pyne committed
569
        action<KToggleAction>("albumRandomPlay")->setChecked(true);
570
    if(randomMode.toLower() == "norandom")
Michael Pyne's avatar
Michael Pyne committed
571 572 573
        action<KToggleAction>("disableRandomPlay")->setChecked(true);
}

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