File indexing completed on 2022-08-04 15:34:42

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