playermanager.cpp 13.7 KB
Newer Older
1 2 3
/**
 * Copyright (C) 2004 Scott Wheeler <wheeler@kde.org>
 * Copyright (C) 2007 Matthias Kretz <kretz@kde.org>
4
 * Copyright (C) 2008, 2009, 2018 Michael Pyne <mpyne@kde.org>
5 6 7 8 9 10 11 12 13 14 15 16 17
 *
 * 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
#include <math.h>
35
#include <algorithm>
36

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
#include "scrobbler.h"
46
#include "juk.h"
Michael Pyne's avatar
Michael Pyne committed
47
#include "juk_debug.h"
48 49

using namespace ActionCollection;
50

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

53 54 55 56 57
////////////////////////////////////////////////////////////////////////////////
// protected members
////////////////////////////////////////////////////////////////////////////////

PlayerManager::PlayerManager() :
58
    QObject(),
59 60
    m_playlistInterface(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 199 200 201 202
{
    m_playlistInterface = interface;
}

////////////////////////////////////////////////////////////////////////////////
// public slots
////////////////////////////////////////////////////////////////////////////////

203
void PlayerManager::play(const FileHandle &file)
204
{
205
    if(!m_setup)
206 207
        setup();

208
    if(!m_media || !m_playlistInterface)
209 210
        return;

211
    if(file.isNull()) {
212
        if(paused())
213
            m_media->play();
214
        else if(playing()) {
215
            m_media->seek(0);
216
            emit seeked(0);
217
        }
218
        else {
219
            m_playlistInterface->playNext();
220
            m_file = m_playlistInterface->currentFile();
221

222
            if(!m_file.isNull())
223
            {
224 225
                m_media->setCurrentSource(QUrl::fromLocalFile(m_file.absFilePath()));
                m_media->play();
226 227

                emit signalItemChanged(m_file);
228
            }
229
        }
230
    }
231
    else {
232 233
        m_media->setCurrentSource(QUrl::fromLocalFile(file.absFilePath()));
        m_media->play();
234 235 236 237 238

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

        m_file = file;
239
    }
240

241 242
    // Our state changed handler will perform the follow up actions necessary
    // once we actually start playing.
243 244
}

245 246 247 248 249 250 251 252 253
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
254 255
void PlayerManager::play()
{
256
    play(FileHandle());
Scott Wheeler's avatar
Scott Wheeler committed
257 258
}

259 260
void PlayerManager::pause()
{
261
    if(!m_setup || paused())
262 263
        return;

264
    action("pause")->setEnabled(false);
265

266
    m_media->pause();
267 268 269 270
}

void PlayerManager::stop()
{
271
    if(!m_setup || !m_playlistInterface)
272 273
        return;

274 275 276 277
    action("pause")->setEnabled(false);
    action("stop")->setEnabled(false);
    action("back")->setEnabled(false);
    action("forward")->setEnabled(false);
278
    action("forwardAlbum")->setEnabled(false);
279

280
    if(!m_file.isNull()) {
281
        m_file = FileHandle();
282 283
        emit signalItemChanged(m_file);
    }
284 285

    m_media->stop();
286 287
}

Laurent Montel's avatar
Laurent Montel committed
288
void PlayerManager::setVolume(float volume)
289
{
290
    if(!m_setup)
291
        setup();
292

293
    m_output->setVolume(volume);
294 295
}

296
void PlayerManager::seek(int seekTime)
297
{
298
    if(!m_setup || m_media->currentTime() == seekTime)
299
        return;
300

301
    m_media->seek(seekTime);
302
    emit seeked(seekTime);
303 304 305 306
}

void PlayerManager::seekForward()
{
307 308
    const qint64 total = m_media->totalTime();
    const qint64 newtime = m_media->currentTime() + total / 100;
309
    const qint64 seekTo = qMin(total, newtime);
310

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

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

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

325 326
void PlayerManager::playPause()
{
David Faure's avatar
David Faure committed
327
    playing() ? action("pause")->trigger() : action("play")->trigger();
328 329
}

330 331
void PlayerManager::forward()
{
332 333
    m_playlistInterface->playNext();
    FileHandle file = m_playlistInterface->currentFile();
334

335 336 337 338 339 340 341 342
    if(!file.isNull())
        play(file);
    else
        stop();
}

void PlayerManager::back()
{
343 344
    m_playlistInterface->playPrevious();
    FileHandle file = m_playlistInterface->currentFile();
345

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

352 353
void PlayerManager::volumeUp()
{
354
    if(!m_setup)
355
        return;
356

357 358
    const auto newVolume = std::min(m_output->volume() + 0.04, 1.0);
    m_output->setVolume(newVolume); // 4% up
359 360 361 362
}

void PlayerManager::volumeDown()
{
363
    if(!m_setup)
364
        return;
365

366 367
    const auto newVolume = std::max(m_output->volume() - 0.04, 0.0);
    m_output->setVolume(newVolume); // 4% down
368 369
}

370
void PlayerManager::setMuted(bool m)
371
{
372
    if(!m_setup)
373 374
        return;

375
    m_output->setMuted(m);
376 377 378 379 380 381 382 383 384 385
}

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

    bool newState = !muted();
    setMuted(newState);
    return newState;
386 387
}

388 389 390
////////////////////////////////////////////////////////////////////////////////
// private slots
////////////////////////////////////////////////////////////////////////////////
391

392 393
void PlayerManager::slotFinished()
{
394 395
    // 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
396
    // and if we can, do so.  Otherwise, stop.
397

398
    m_playlistInterface->playNext();
399
    play(m_playlistInterface->currentFile());
400 401
}

402 403
void PlayerManager::slotLength(qint64 msec)
{
404
    emit totalTimeChanged(msec);
405 406
}

407
void PlayerManager::slotTick(qint64 msec)
408
{
409
    emit tick(msec);
410 411
}

412
void PlayerManager::slotStateChanged(Phonon::State newstate, Phonon::State)
Matthias Kretz's avatar
Matthias Kretz committed
413
{
414
    if(newstate == Phonon::ErrorState) {
415 416 417
        QString errorMessage =
            i18nc(
              "%1 will be the /path/to/file, %2 will be some string from Phonon describing the error",
418 419
              "JuK is unable to play the audio file<nl/><filename>%1</filename><nl/>"
                "for the following reason:<nl/><message>%2</message>",
420
              m_file.absFilePath(),
421
              m_media->errorString()
422 423
            );

424 425 426 427 428
        qCWarning(JUK_LOG)
                << "Phonon is in error state" << m_media->errorString()
                << "while playing" << m_file.absFilePath();

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

Matthias Kretz's avatar
Matthias Kretz committed
433
            case Phonon::NormalError:
434
                KMessageBox::information(0, errorMessage);
Matthias Kretz's avatar
Matthias Kretz committed
435
                break;
436

Matthias Kretz's avatar
Matthias Kretz committed
437
            case Phonon::FatalError:
438
                KMessageBox::sorry(0, errorMessage);
Matthias Kretz's avatar
Matthias Kretz committed
439 440
                break;
        }
441

442
        stop();
443
        return;
444
    }
445 446

    // "normal" path
447
    if(newstate == Phonon::StoppedState && m_file.isNull()) {
448 449 450
        JuK::JuKInstance()->setWindowTitle(i18n("JuK"));
        emit signalStop();
    }
451 452 453 454
    else if(newstate == Phonon::PausedState) {
        emit signalPause();
    }
    else { // PlayingState or BufferingState
455 456 457 458 459 460 461
        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);

462 463
        JuK::JuKInstance()->setWindowTitle(i18nc(
            "%1 is the artist and %2 is the title of the currently playing track.", 
464 465
            "%1 - %2 :: JuK",
            m_file.tag()->artist(),
466
            m_file.tag()->title()));
467

468 469
        emit signalPlay();
    }
470 471
}

472 473 474 475 476 477 478 479 480 481 482 483
void PlayerManager::slotSeekableChanged(bool isSeekable)
{
    emit seekableChanged(isSeekable);
}

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

void PlayerManager::slotVolumeChanged(qreal volume)
{
484
    if(qFuzzyCompare(m_output->volume(), volume))
485
    {
486
        return;
487
    }
488 489 490 491

    emit volumeChanged(volume);
}

492 493 494 495 496 497
////////////////////////////////////////////////////////////////////////////////
// private members
////////////////////////////////////////////////////////////////////////////////

void PlayerManager::setup()
{
498 499 500 501
    if(m_setup)
        return;

    m_setup = true;
502

503
    // All of the actions required by this class should be listed here.
504 505 506
    if(!action("pause") ||
       !action("stop") ||
       !action("back") ||
507
       !action("forwardAlbum") ||
508 509
       !action("forward") ||
       !action("trackPositionAction"))
510
    {
Michael Pyne's avatar
Michael Pyne committed
511
        qCWarning(JUK_LOG) << "Could not find all of the required actions.";
512 513 514
        return;
    }

515 516 517 518
    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);
519

520 521 522 523 524 525 526 527
    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);
528

529 530
    m_media->setTickInterval(100);

531 532
    // initialize action states

533 534 535 536
    action("pause")->setEnabled(false);
    action("stop")->setEnabled(false);
    action("back")->setEnabled(false);
    action("forward")->setEnabled(false);
537
    action("forwardAlbum")->setEnabled(false);
538

539
    QDBusConnection::sessionBus().registerObject("/Player", this);
540 541
}

Michael Pyne's avatar
Michael Pyne committed
542 543 544 545 546 547 548 549 550 551 552
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)
{
553
    if(randomMode.toLower() == "random")
Michael Pyne's avatar
Michael Pyne committed
554
        action<KToggleAction>("randomPlay")->setChecked(true);
555
    if(randomMode.toLower() == "albumrandom")
Michael Pyne's avatar
Michael Pyne committed
556
        action<KToggleAction>("albumRandomPlay")->setChecked(true);
557
    if(randomMode.toLower() == "norandom")
Michael Pyne's avatar
Michael Pyne committed
558 559 560
        action<KToggleAction>("disableRandomPlay")->setChecked(true);
}

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