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: