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: