Warning, file /multimedia/juk/playermanager.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

0001 /**
0002  * Copyright (C) 2004 Scott Wheeler <wheeler@kde.org>
0003  * Copyright (C) 2007 Matthias Kretz <kretz@kde.org>
0004  * Copyright (C) 2008, 2009, 2018 Michael Pyne <mpyne@kde.org>
0005  *
0006  * This program is free software; you can redistribute it and/or modify it under
0007  * the terms of the GNU General Public License as published by the Free Software
0008  * Foundation; either version 2 of the License, or (at your option) any later
0009  * version.
0010  *
0011  * This program is distributed in the hope that it will be useful, but WITHOUT ANY
0012  * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
0013  * PARTICULAR PURPOSE. See the GNU General Public License for more details.
0014  *
0015  * You should have received a copy of the GNU General Public License along with
0016  * this program.  If not, see <http://www.gnu.org/licenses/>.
0017  */
0018 
0019 #include "playermanager.h"
0020 
0021 #include <kmessagebox.h>
0022 #include <kactioncollection.h>
0023 #include <kselectaction.h>
0024 #include <ktoggleaction.h>
0025 #include <KLocalizedString>
0026 
0027 #include <Phonon/AudioOutput>
0028 #include <Phonon/MediaObject>
0029 #include <Phonon/MediaSource>
0030 
0031 #include <QPixmap>
0032 #include <QUrl>
0033 
0034 #include <algorithm>
0035 
0036 #include "playlistinterface.h"
0037 #include "playeradaptor.h"
0038 #include "slideraction.h"
0039 #include "statuslabel.h"
0040 #include "actioncollection.h"
0041 #include "collectionlist.h"
0042 #include "coverinfo.h"
0043 #include "juktag.h"
0044 #include "scrobbler.h"
0045 #include "juk.h"
0046 #include "juk_debug.h"
0047 
0048 using namespace ActionCollection;
0049 
0050 enum PlayerManagerStatus { StatusStopped = -1, StatusPaused = 1, StatusPlaying = 2 };
0051 
0052 ////////////////////////////////////////////////////////////////////////////////
0053 // static functions
0054 ////////////////////////////////////////////////////////////////////////////////
0055 static void updateWindowTitle(const FileHandle &file)
0056 {
0057     JuK::JuKInstance()->setWindowTitle(i18nc(
0058         "%1 is the artist and %2 is the title of the currently playing track.", 
0059         "%1 - %2 :: JuK",
0060         file.tag()->artist(),
0061         file.tag()->title()));
0062 }
0063 
0064 ////////////////////////////////////////////////////////////////////////////////
0065 // protected members
0066 ////////////////////////////////////////////////////////////////////////////////
0067 
0068 PlayerManager::PlayerManager() :
0069     QObject(),
0070     m_playlistInterface(nullptr),
0071     m_output(new Phonon::AudioOutput(Phonon::MusicCategory, this)),
0072     m_media( new Phonon::MediaObject(this)),
0073     m_audioPath(Phonon::createPath(m_media, m_output))
0074 {
0075     setupAudio();
0076     new PlayerAdaptor(this);
0077     QDBusConnection::sessionBus().registerObject("/Player", this);
0078 }
0079 
0080 ////////////////////////////////////////////////////////////////////////////////
0081 // public members
0082 ////////////////////////////////////////////////////////////////////////////////
0083 
0084 bool PlayerManager::playing() const
0085 {
0086     Phonon::State state = m_media->state();
0087     return (state == Phonon::PlayingState || state == Phonon::BufferingState);
0088 }
0089 
0090 bool PlayerManager::paused() const
0091 {
0092     return m_media->state() == Phonon::PausedState;
0093 }
0094 
0095 bool PlayerManager::muted() const
0096 {
0097     return m_output->isMuted();
0098 }
0099 
0100 float PlayerManager::volume() const
0101 {
0102     return m_output->volume();
0103 }
0104 
0105 int PlayerManager::status() const
0106 {
0107     if(paused())
0108         return StatusPaused;
0109 
0110     if(playing())
0111         return StatusPlaying;
0112 
0113     return StatusStopped;
0114 }
0115 
0116 int PlayerManager::totalTime() const
0117 {
0118     return totalTimeMSecs() / 1000;
0119 }
0120 
0121 int PlayerManager::currentTime() const
0122 {
0123     return currentTimeMSecs() / 1000;
0124 }
0125 
0126 int PlayerManager::totalTimeMSecs() const
0127 {
0128     return m_media->totalTime();
0129 }
0130 
0131 int PlayerManager::currentTimeMSecs() const
0132 {
0133     return m_media->currentTime();
0134 }
0135 
0136 bool PlayerManager::seekable() const
0137 {
0138     return m_media->isSeekable();
0139 }
0140 
0141 QStringList PlayerManager::trackProperties()
0142 {
0143     return FileHandle::properties();
0144 }
0145 
0146 QString PlayerManager::trackProperty(const QString &property) const
0147 {
0148     if(!playing() && !paused())
0149         return QString();
0150 
0151     return m_file.property(property);
0152 }
0153 
0154 QPixmap PlayerManager::trackCover(const QString &size) const
0155 {
0156     if(!playing() && !paused())
0157         return QPixmap();
0158 
0159     if(size.toLower() == "small")
0160         return m_file.coverInfo()->pixmap(CoverInfo::Thumbnail);
0161     if(size.toLower() == "large")
0162         return m_file.coverInfo()->pixmap(CoverInfo::FullSize);
0163 
0164     return QPixmap();
0165 }
0166 
0167 FileHandle PlayerManager::playingFile() const
0168 {
0169     return m_file;
0170 }
0171 
0172 QString PlayerManager::playingString() const
0173 {
0174     if(!playing() || m_file.isNull())
0175         return QString();
0176 
0177     return m_file.tag()->playingString();
0178 }
0179 
0180 void PlayerManager::setPlaylistInterface(PlaylistInterface *interface)
0181 {
0182     m_playlistInterface = interface;
0183 }
0184 
0185 ////////////////////////////////////////////////////////////////////////////////
0186 // public slots
0187 ////////////////////////////////////////////////////////////////////////////////
0188 
0189 void PlayerManager::play(const FileHandle &file)
0190 {
0191     if(!m_media || !m_playlistInterface || file.isNull())
0192         return;
0193 
0194     if(m_file != file) {
0195         emit signalItemChanged(file);
0196         m_media->setCurrentSource(QUrl::fromLocalFile(file.absFilePath()));
0197         m_media->play();
0198     }
0199 
0200     m_file = file;
0201 
0202     // Our state changed handler will perform the follow up actions necessary
0203     // once we actually start playing.
0204 }
0205 
0206 void PlayerManager::play(const QString &file)
0207 {
0208     CollectionListItem *item = CollectionList::instance()->lookup(file);
0209     if(item) {
0210         item->playlist()->setPlaying(item); // Will reentrantly call play(FileHandle)
0211     }
0212 }
0213 
0214 void PlayerManager::play()
0215 {
0216     if(paused())
0217         m_media->play();
0218     else if(playing()) {
0219         m_media->seek(0);
0220         emit seeked(0);
0221     }
0222     else {
0223         // Will reentrantly call play(FileHandle)
0224         m_playlistInterface->playNext();
0225     }
0226 }
0227 
0228 void PlayerManager::pause()
0229 {
0230     if(paused())
0231         return;
0232 
0233     action("pause")->setEnabled(false);
0234 
0235     m_media->pause();
0236 }
0237 
0238 void PlayerManager::stop()
0239 {
0240     if(!m_playlistInterface)
0241         return;
0242 
0243     action("pause")->setEnabled(false);
0244     action("stop")->setEnabled(false);
0245     action("back")->setEnabled(false);
0246     action("forward")->setEnabled(false);
0247     action("forwardAlbum")->setEnabled(false);
0248 
0249     if(!m_file.isNull()) {
0250         m_file = FileHandle();
0251         emit signalItemChanged(m_file);
0252     }
0253 
0254     m_media->stop();
0255 }
0256 
0257 void PlayerManager::seek(int seekTime)
0258 {
0259     if(m_media->currentTime() == seekTime)
0260         return;
0261 
0262     m_media->seek(seekTime);
0263     emit seeked(seekTime);
0264 }
0265 
0266 void PlayerManager::seekForward()
0267 {
0268     const qint64 total = m_media->totalTime();
0269     const qint64 newtime = m_media->currentTime() + total / 100;
0270     const qint64 seekTo = qMin(total, newtime);
0271 
0272     m_media->seek(seekTo);
0273     emit seeked(seekTo);
0274 }
0275 
0276 void PlayerManager::seekBack()
0277 {
0278     const qint64 total = m_media->totalTime();
0279     const qint64 newtime = m_media->currentTime() - total / 100;
0280     const qint64 seekTo = qMax(qint64(0), newtime);
0281 
0282     m_media->seek(seekTo);
0283     emit seeked(seekTo);
0284 }
0285 
0286 void PlayerManager::playPause()
0287 {
0288     playing() ? action("pause")->trigger() : action("play")->trigger();
0289 }
0290 
0291 void PlayerManager::forward()
0292 {
0293     m_playlistInterface->playNext();
0294     FileHandle file = m_playlistInterface->currentFile();
0295 
0296     if(!file.isNull())
0297         play(file);
0298     else
0299         stop();
0300 }
0301 
0302 void PlayerManager::back()
0303 {
0304     m_playlistInterface->playPrevious();
0305     FileHandle file = m_playlistInterface->currentFile();
0306 
0307     if(!file.isNull())
0308         play(file);
0309     else
0310         stop();
0311 }
0312 
0313 void PlayerManager::volumeUp()
0314 {
0315     const auto newVolume = std::min(m_output->volume() + 0.04, 1.0);
0316     m_output->setVolume(newVolume); // 4% up
0317 }
0318 
0319 void PlayerManager::volumeDown()
0320 {
0321     const auto newVolume = std::max(m_output->volume() - 0.04, 0.0);
0322     m_output->setVolume(newVolume); // 4% down
0323 }
0324 
0325 void PlayerManager::setMuted(bool m)
0326 {
0327     m_output->setMuted(m);
0328 }
0329 
0330 bool PlayerManager::mute()
0331 {
0332     bool newState = !muted();
0333     setMuted(newState);
0334     return newState;
0335 }
0336 
0337 ////////////////////////////////////////////////////////////////////////////////
0338 // private slots
0339 ////////////////////////////////////////////////////////////////////////////////
0340 
0341 void PlayerManager::slotFinished()
0342 {
0343     // It is possible to end up in this function if a file simply fails to play or if the
0344     // user moves the slider all the way to the end, therefore see if we can keep playing
0345     // and if we can, do so.  Otherwise, stop.
0346 
0347     m_playlistInterface->playNext();
0348     play(m_playlistInterface->currentFile());
0349 }
0350 
0351 void PlayerManager::slotLength(qint64 msec)
0352 {
0353     emit totalTimeChanged(msec);
0354 }
0355 
0356 void PlayerManager::slotTick(qint64 msec)
0357 {
0358     emit tick(msec);
0359 }
0360 
0361 void PlayerManager::slotStateChanged(Phonon::State newstate, Phonon::State)
0362 {
0363     if(newstate == Phonon::ErrorState) {
0364         QString errorMessage =
0365             i18nc(
0366               "%1 will be the /path/to/file, %2 will be some string from Phonon describing the error",
0367               "JuK is unable to play the audio file<nl/><filename>%1</filename><nl/>"
0368                 "for the following reason:<nl/><message>%2</message>",
0369               m_file.absFilePath(),
0370               m_media->errorString()
0371             );
0372 
0373         qCWarning(JUK_LOG)
0374                 << "Phonon is in error state" << m_media->errorString()
0375                 << "while playing" << m_file.absFilePath();
0376 
0377         switch(m_media->errorType()) {
0378             case Phonon::NoError:
0379                 qCDebug(JUK_LOG) << "received a state change to ErrorState but errorType is NoError!?";
0380                 break;
0381 
0382             case Phonon::NormalError:
0383                 KMessageBox::information(0, errorMessage);
0384                 break;
0385 
0386             case Phonon::FatalError:
0387                 KMessageBox::error(0, errorMessage);
0388                 break;
0389         }
0390 
0391         stop();
0392         return;
0393     }
0394 
0395     // "normal" path
0396     if(newstate == Phonon::StoppedState && m_file.isNull()) {
0397         JuK::JuKInstance()->setWindowTitle(i18n("JuK"));
0398         emit signalStop();
0399     }
0400     else if(newstate == Phonon::PausedState) {
0401         emit signalPause();
0402     }
0403     else { // PlayingState or BufferingState
0404         action("pause")->setEnabled(true);
0405         action("stop")->setEnabled(true);
0406         action("forward")->setEnabled(true);
0407         if(action<KToggleAction>("albumRandomPlay")->isChecked())
0408             action("forwardAlbum")->setEnabled(true);
0409         action("back")->setEnabled(true);
0410 
0411         updateWindowTitle(m_file);
0412         emit signalPlay();
0413     }
0414 }
0415 
0416 void PlayerManager::slotSeekableChanged(bool isSeekable)
0417 {
0418     emit seekableChanged(isSeekable);
0419 }
0420 
0421 void PlayerManager::slotMutedChanged(bool muted)
0422 {
0423     emit mutedChanged(muted);
0424 }
0425 
0426 void PlayerManager::setVolume(qreal volume)
0427 {
0428     if(qFuzzyCompare(m_output->volume(), volume))
0429     {
0430         return;
0431     }
0432 
0433     m_output->setVolume(volume);
0434     emit volumeChanged(volume);
0435 }
0436 
0437 ////////////////////////////////////////////////////////////////////////////////
0438 // private members
0439 ////////////////////////////////////////////////////////////////////////////////
0440 
0441 void PlayerManager::setupAudio()
0442 {
0443     using namespace Phonon;
0444     connect(m_output, &AudioOutput::mutedChanged,
0445             this, &PlayerManager::slotMutedChanged);
0446     connect(m_output, &AudioOutput::volumeChanged,
0447             this, &PlayerManager::setVolume);
0448 
0449     connect(m_media, &MediaObject::stateChanged,
0450             this, &PlayerManager::slotStateChanged);
0451     connect(m_media, &MediaObject::currentSourceChanged,
0452             this, &PlayerManager::trackHasChanged);
0453     connect(m_media, &MediaObject::totalTimeChanged,
0454             this, &PlayerManager::slotLength);
0455     connect(m_media, &MediaObject::tick,
0456             this, &PlayerManager::slotTick);
0457     connect(m_media, &MediaObject::finished,
0458             this, &PlayerManager::slotFinished);
0459     connect(m_media, &MediaObject::seekableChanged,
0460             this, &PlayerManager::slotSeekableChanged);
0461 
0462     m_media->setTickInterval(100);
0463 }
0464 
0465 QString PlayerManager::randomPlayMode() const
0466 {
0467     if(action<KToggleAction>("randomPlay")->isChecked())
0468         return "Random";
0469     if(action<KToggleAction>("albumRandomPlay")->isChecked())
0470         return "AlbumRandom";
0471     return "NoRandom";
0472 }
0473 
0474 void PlayerManager::setRandomPlayMode(const QString &randomMode)
0475 {
0476     if(randomMode.toLower() == "random")
0477         action<KToggleAction>("randomPlay")->setChecked(true);
0478     if(randomMode.toLower() == "albumrandom")
0479         action<KToggleAction>("albumRandomPlay")->setChecked(true);
0480     if(randomMode.toLower() == "norandom")
0481         action<KToggleAction>("disableRandomPlay")->setChecked(true);
0482 }
0483 
0484 void PlayerManager::trackHasChanged(const Phonon::MediaSource &newSource)
0485 {
0486     if(newSource.type() == Phonon::MediaSource::Url) {
0487         const auto item = CollectionList::instance()->lookup(newSource.url().path());
0488         if(item) {
0489             const auto newFile = item->file();
0490             if(m_file != newFile)
0491                 emit signalItemChanged(newFile);
0492             m_file = newFile;
0493             updateWindowTitle(m_file);
0494             emit signalPlay();
0495             emit seeked(0);
0496         }
0497     } else {
0498         qCWarning(JUK_LOG) << "Track has changed so something we didn't set???";
0499         return;
0500     }
0501 }
0502 
0503 // vim: set et sw=4 tw=0 sta: