File indexing completed on 2024-05-12 16:21:27
0001 /** 0002 * SPDX-FileCopyrightText: 2017 (c) Matthieu Gallien <matthieu_gallien@yahoo.fr> 0003 * SPDX-FileCopyrightText: 2021-2023 Bart De Vries <bart@mogwai.be> 0004 * 0005 * SPDX-License-Identifier: LGPL-3.0-or-later 0006 */ 0007 0008 #include "audiomanager.h" 0009 0010 #include <QEventLoop> 0011 #include <QTimer> 0012 #include <QtMath> 0013 #include <algorithm> 0014 0015 #include <KLocalizedString> 0016 0017 #include "audiologging.h" 0018 #include "datamanager.h" 0019 #include "feed.h" 0020 #include "fetcher.h" 0021 #include "models/errorlogmodel.h" 0022 #include "networkconnectionmanager.h" 0023 #include "settingsmanager.h" 0024 0025 class AudioManagerPrivate 0026 { 0027 private: 0028 KMediaSession m_player = KMediaSession(QStringLiteral("kasts"), QStringLiteral("org.kde.kasts")); 0029 0030 Entry *m_entry = nullptr; 0031 bool m_readyToPlay = false; 0032 bool m_isSeekable = false; 0033 bool m_continuePlayback = false; 0034 0035 // sort of lock mutex to prevent updating the player position while changing 0036 // sources (which will emit lots of playerPositionChanged signals) 0037 bool m_lockPositionSaving = false; 0038 0039 // m_pendingSeek is used to indicate whether a seek action is still pending 0040 // * -1 corresponds to no seek action pending 0041 // * any positive value indicates that a seek to position=m_pendingSeek is 0042 // still pending 0043 qint64 m_pendingSeek = -1; 0044 0045 QTimer *m_sleepTimer = nullptr; 0046 qint64 m_sleepTime = -1; 0047 qint64 m_remainingSleepTime = -1; 0048 0049 bool m_isStreaming = false; 0050 0051 friend class AudioManager; 0052 }; 0053 0054 AudioManager::AudioManager(QObject *parent) 0055 : QObject(parent) 0056 , d(std::make_unique<AudioManagerPrivate>()) 0057 { 0058 d->m_player.setMpris2PauseInsteadOfStop(true); 0059 0060 connect(&d->m_player, &KMediaSession::currentBackendChanged, this, &AudioManager::currentBackendChanged); 0061 connect(&d->m_player, &KMediaSession::mutedChanged, this, &AudioManager::playerMutedChanged); 0062 connect(&d->m_player, &KMediaSession::volumeChanged, this, &AudioManager::playerVolumeChanged); 0063 connect(&d->m_player, &KMediaSession::sourceChanged, this, &AudioManager::sourceChanged); 0064 connect(&d->m_player, &KMediaSession::mediaStatusChanged, this, &AudioManager::statusChanged); 0065 connect(&d->m_player, &KMediaSession::mediaStatusChanged, this, &AudioManager::mediaStatusChanged); 0066 connect(&d->m_player, &KMediaSession::playbackStateChanged, this, &AudioManager::playbackStateChanged); 0067 connect(&d->m_player, &KMediaSession::playbackRateChanged, this, &AudioManager::playbackRateChanged); 0068 connect(&d->m_player, &KMediaSession::errorChanged, this, &AudioManager::errorChanged); 0069 connect(&d->m_player, &KMediaSession::durationChanged, this, &AudioManager::playerDurationChanged); 0070 connect(&d->m_player, &KMediaSession::positionChanged, this, &AudioManager::positionChanged); 0071 connect(this, &AudioManager::positionChanged, this, &AudioManager::savePlayPosition); 0072 0073 // connect signals for MPRIS2 0074 connect(this, &AudioManager::canSkipForwardChanged, this, [this]() { 0075 d->m_player.setCanGoNext(canSkipForward()); 0076 }); 0077 connect(this, &AudioManager::canSkipBackwardChanged, this, [this]() { 0078 d->m_player.setCanGoPrevious(canSkipBackward()); 0079 }); 0080 connect(&d->m_player, &KMediaSession::nextRequested, this, &AudioManager::skipForward); 0081 connect(&d->m_player, &KMediaSession::previousRequested, this, &AudioManager::skipBackward); 0082 connect(&d->m_player, &KMediaSession::raiseWindowRequested, this, &AudioManager::raiseWindowRequested); 0083 connect(&d->m_player, &KMediaSession::quitRequested, this, &AudioManager::quitRequested); 0084 0085 connect(this, &AudioManager::playbackRateChanged, &DataManager::instance(), &DataManager::playbackRateChanged); 0086 connect(&DataManager::instance(), &DataManager::queueEntryMoved, this, &AudioManager::canGoNextChanged); 0087 connect(&DataManager::instance(), &DataManager::queueEntryAdded, this, &AudioManager::canGoNextChanged); 0088 connect(&DataManager::instance(), &DataManager::queueEntryRemoved, this, &AudioManager::canGoNextChanged); 0089 // we'll send custom seekableChanged signal to work around possible backend glitches 0090 0091 connect(this, &AudioManager::logError, &ErrorLogModel::instance(), &ErrorLogModel::monitorErrorMessages); 0092 0093 // Check if an entry was playing when the program was shut down and restore it 0094 if (DataManager::instance().lastPlayingEntry() != QStringLiteral("none")) { 0095 setEntry(DataManager::instance().getEntry(DataManager::instance().lastPlayingEntry())); 0096 } 0097 } 0098 0099 AudioManager::~AudioManager() 0100 { 0101 } 0102 0103 QString AudioManager::backendName(KMediaSession::MediaBackends backend) const 0104 { 0105 qCDebug(kastsAudio) << "AudioManager::backendName()"; 0106 return d->m_player.backendName(backend); 0107 } 0108 0109 KMediaSession::MediaBackends AudioManager::currentBackend() const 0110 { 0111 qCDebug(kastsAudio) << "AudioManager::currentBackend()"; 0112 return d->m_player.currentBackend(); 0113 } 0114 0115 QList<KMediaSession::MediaBackends> AudioManager::availableBackends() const 0116 { 0117 qCDebug(kastsAudio) << "AudioManager::availableBackends()"; 0118 return d->m_player.availableBackends(); 0119 } 0120 0121 Entry *AudioManager::entry() const 0122 { 0123 return d->m_entry; 0124 } 0125 0126 bool AudioManager::muted() const 0127 { 0128 return d->m_player.muted(); 0129 } 0130 0131 qreal AudioManager::volume() const 0132 { 0133 return d->m_player.volume(); 0134 } 0135 0136 QUrl AudioManager::source() const 0137 { 0138 return d->m_player.source(); 0139 } 0140 0141 KMediaSession::Error AudioManager::error() const 0142 { 0143 if (d->m_player.error() != KMediaSession::NoError) { 0144 qCDebug(kastsAudio) << "AudioManager::error" << d->m_player.error(); 0145 // Some error occurred: probably best to unset the lastPlayingEntry to 0146 // avoid a deadlock when starting up again. 0147 DataManager::instance().setLastPlayingEntry(QStringLiteral("none")); 0148 } 0149 0150 return d->m_player.error(); 0151 } 0152 0153 qint64 AudioManager::duration() const 0154 { 0155 // we fake the duration in case the track has not been properly loaded yet 0156 if (!d->m_readyToPlay) { 0157 if (d->m_entry && d->m_entry->enclosure()) { 0158 return d->m_entry->enclosure()->duration() * 1000; 0159 } else { 0160 return 0; 0161 } 0162 } else if (d->m_player.duration() > 0) { 0163 return d->m_player.duration(); 0164 } else if (d->m_entry && d->m_entry->enclosure()) { 0165 return d->m_entry->enclosure()->duration() * 1000; 0166 } else { 0167 return 0; 0168 } 0169 } 0170 0171 qint64 AudioManager::position() const 0172 { 0173 // we fake the player position in case there is still a pending seek 0174 if (!d->m_readyToPlay) { 0175 if (d->m_entry && d->m_entry->enclosure()) { 0176 return d->m_entry->enclosure()->playPosition(); 0177 } else { 0178 return 0; 0179 } 0180 } else if (d->m_pendingSeek != -1) { 0181 return d->m_pendingSeek; 0182 } else { 0183 return d->m_player.position(); 0184 } 0185 } 0186 0187 bool AudioManager::seekable() const 0188 { 0189 return d->m_isSeekable; 0190 } 0191 0192 bool AudioManager::canPlay() const 0193 { 0194 return (d->m_readyToPlay); 0195 } 0196 0197 bool AudioManager::canPause() const 0198 { 0199 return (d->m_readyToPlay); 0200 } 0201 0202 bool AudioManager::canSkipForward() const 0203 { 0204 return (d->m_readyToPlay); 0205 } 0206 0207 bool AudioManager::canSkipBackward() const 0208 { 0209 return (d->m_readyToPlay); 0210 } 0211 0212 KMediaSession::PlaybackState AudioManager::playbackState() const 0213 { 0214 return d->m_player.playbackState(); 0215 } 0216 0217 qreal AudioManager::playbackRate() const 0218 { 0219 return d->m_player.playbackRate(); 0220 } 0221 0222 qreal AudioManager::minimumPlaybackRate() const 0223 { 0224 return d->m_player.minimumPlaybackRate(); 0225 } 0226 0227 qreal AudioManager::maximumPlaybackRate() const 0228 { 0229 return d->m_player.maximumPlaybackRate(); 0230 } 0231 0232 bool AudioManager::isStreaming() const 0233 { 0234 return d->m_isStreaming; 0235 } 0236 0237 KMediaSession::MediaStatus AudioManager::status() const 0238 { 0239 return d->m_player.mediaStatus(); 0240 } 0241 0242 void AudioManager::setCurrentBackend(KMediaSession::MediaBackends backend) 0243 { 0244 qCDebug(kastsAudio) << "AudioManager::setCurrentBackend(" << backend << ")"; 0245 0246 KMediaSession::PlaybackState currentState = playbackState(); 0247 qint64 currentRate = playbackRate(); 0248 0249 d->m_player.setCurrentBackend(backend); 0250 0251 setEntry(d->m_entry); 0252 if (currentState == KMediaSession::PlaybackState::PlayingState) { 0253 play(); 0254 } 0255 // TODO: Fix restoring the current playback rate 0256 setPlaybackRate(currentRate); 0257 } 0258 0259 void AudioManager::setEntry(Entry *entry) 0260 { 0261 qCDebug(kastsAudio) << "begin AudioManager::setEntry"; 0262 // First unset current track and save playing state, such that any signal 0263 // that still fires doesn't operate on the wrong track. 0264 0265 // disconnect any pending redirectUrl signals 0266 bool signalDisconnect = disconnect(&Fetcher::instance(), &Fetcher::foundRedirectedUrl, this, nullptr); 0267 qCDebug(kastsAudio) << "disconnected dangling foundRedirectedUrl signal:" << signalDisconnect; 0268 0269 // reset any pending seek action and lock position saving 0270 d->m_pendingSeek = -1; 0271 d->m_lockPositionSaving = true; 0272 0273 Entry *oldEntry = d->m_entry; 0274 d->m_entry = nullptr; 0275 0276 // Check if the previous track needs to be marked as read 0277 if (oldEntry && !signalDisconnect) { 0278 qCDebug(kastsAudio) << "Checking previous track"; 0279 qCDebug(kastsAudio) << "Left time" << (duration() - position()); 0280 qCDebug(kastsAudio) << "MediaStatus" << d->m_player.mediaStatus(); 0281 if (((duration() > 0) && (position() > 0) && ((duration() - position()) < SettingsManager::self()->markAsPlayedBeforeEnd() * 1000)) 0282 || (d->m_player.mediaStatus() == KMediaSession::EndOfMedia)) { 0283 qCDebug(kastsAudio) << "Mark as read:" << oldEntry->title(); 0284 oldEntry->enclosure()->setPlayPosition(0); 0285 oldEntry->setRead(true); 0286 stop(); 0287 d->m_continuePlayback = SettingsManager::self()->continuePlayingNextEntry(); 0288 } else { 0289 bool continuePlaying = d->m_continuePlayback; // saving to local bool because it will be overwritten by the stop action 0290 stop(); 0291 d->m_continuePlayback = continuePlaying; 0292 } 0293 } 0294 0295 // do some checks on the new entry to see whether it's valid and not corrupted 0296 if (entry != nullptr && entry->hasEnclosure() && entry->enclosure() 0297 && (entry->enclosure()->status() == Enclosure::Downloaded || NetworkConnectionManager::instance().streamingAllowed())) { 0298 qCDebug(kastsAudio) << "Going to change source"; 0299 0300 setEntryInfo(entry); 0301 0302 if (entry->enclosure()->status() == Enclosure::Downloaded) { // i.e. local file 0303 if (d->m_isStreaming) { 0304 d->m_isStreaming = false; 0305 Q_EMIT isStreamingChanged(); 0306 } 0307 0308 // call method which will try to make sure that the stream will skip 0309 // to the previously save position and make sure that the duration 0310 // and position are reported correctly 0311 prepareAudio(QUrl::fromLocalFile(entry->enclosure()->path())); 0312 } else { 0313 // i.e. streaming; we first want to resolve the real URL, following 0314 // redirects 0315 QUrl loadUrl = QUrl(entry->enclosure()->url()); 0316 Fetcher::instance().getRedirectedUrl(loadUrl); 0317 connect(&Fetcher::instance(), &Fetcher::foundRedirectedUrl, this, [this, entry, loadUrl](const QUrl &oldUrl, const QUrl &newUrl) { 0318 qCDebug(kastsAudio) << oldUrl << newUrl; 0319 if (loadUrl == oldUrl) { 0320 bool signalDisconnect = disconnect(&Fetcher::instance(), &Fetcher::foundRedirectedUrl, this, nullptr); 0321 qCDebug(kastsAudio) << "disconnected" << signalDisconnect; 0322 0323 if (!d->m_isStreaming) { 0324 d->m_isStreaming = true; 0325 Q_EMIT isStreamingChanged(); 0326 } 0327 0328 d->m_entry = entry; 0329 Q_EMIT entryChanged(entry); 0330 0331 // call method which will try to make sure that the stream will skip 0332 // to the previously save position and make sure that the duration 0333 // and position are reported correctly 0334 prepareAudio(newUrl); 0335 } 0336 }); 0337 } 0338 0339 } else { 0340 DataManager::instance().setLastPlayingEntry(QStringLiteral("none")); 0341 d->m_entry = nullptr; 0342 Q_EMIT entryChanged(nullptr); 0343 d->m_player.stop(); 0344 d->m_player.setSource(QUrl()); 0345 d->m_readyToPlay = false; 0346 Q_EMIT durationChanged(0); 0347 Q_EMIT positionChanged(0); 0348 Q_EMIT canPlayChanged(); 0349 Q_EMIT canPauseChanged(); 0350 Q_EMIT canSkipForwardChanged(); 0351 Q_EMIT canSkipBackwardChanged(); 0352 Q_EMIT canGoNextChanged(); 0353 d->m_isSeekable = false; 0354 Q_EMIT seekableChanged(false); 0355 } 0356 } 0357 0358 void AudioManager::setMuted(bool muted) 0359 { 0360 d->m_player.setMuted(muted); 0361 } 0362 0363 void AudioManager::setVolume(qreal volume) 0364 { 0365 qCDebug(kastsAudio) << "AudioManager::setVolume" << volume; 0366 0367 d->m_player.setVolume(qRound(volume)); 0368 } 0369 0370 void AudioManager::setPosition(qint64 position) 0371 { 0372 qCDebug(kastsAudio) << "AudioManager::setPosition" << position; 0373 0374 seek(position); 0375 } 0376 0377 void AudioManager::setPlaybackRate(const qreal rate) 0378 { 0379 qCDebug(kastsAudio) << "AudioManager::setPlaybackRate" << rate; 0380 0381 d->m_player.setPlaybackRate(rate); 0382 } 0383 0384 void AudioManager::play() 0385 { 0386 qCDebug(kastsAudio) << "AudioManager::play"; 0387 0388 // if we're streaming, check that we're still connected and check for metered 0389 // connection 0390 if (isStreaming()) { 0391 if (!NetworkConnectionManager::instance().streamingAllowed()) { 0392 qCDebug(kastsAudio) << "Refusing to play: no connection or streaming on metered connection not allowed"; 0393 QString feedUrl, entryId; 0394 if (d->m_entry) { 0395 feedUrl = d->m_entry->feed()->url(); 0396 entryId = d->m_entry->id(); 0397 } 0398 Q_EMIT logError(Error::Type::MeteredStreamingNotAllowed, 0399 feedUrl, 0400 entryId, 0401 0, 0402 i18n("No connection or streaming on metered connection not allowed"), 0403 QString()); 0404 return; 0405 } 0406 } 0407 0408 // setting m_continuePlayback will make sure that, if the audio stream is 0409 // still being prepared, that the playback will start once it's ready 0410 d->m_continuePlayback = true; 0411 0412 if (d->m_readyToPlay) { 0413 d->m_player.play(); 0414 d->m_isSeekable = true; 0415 Q_EMIT seekableChanged(d->m_isSeekable); 0416 0417 if (d->m_entry && d->m_entry->getNew()) { 0418 d->m_entry->setNew(false); 0419 } 0420 } 0421 } 0422 0423 void AudioManager::pause() 0424 { 0425 qCDebug(kastsAudio) << "AudioManager::pause"; 0426 0427 // setting m_continuePlayback will make sure that, if the audio stream is 0428 // still being prepared, that the playback will pause once it's ready 0429 d->m_continuePlayback = false; 0430 0431 if (d->m_readyToPlay) { 0432 d->m_isSeekable = true; 0433 d->m_player.pause(); 0434 } 0435 } 0436 0437 void AudioManager::playPause() 0438 { 0439 if (playbackState() == KMediaSession::PlaybackState::PausedState) 0440 play(); 0441 else if (playbackState() == KMediaSession::PlaybackState::PlayingState) 0442 pause(); 0443 } 0444 0445 void AudioManager::stop() 0446 { 0447 qCDebug(kastsAudio) << "AudioManager::stop"; 0448 0449 d->m_player.stop(); 0450 d->m_continuePlayback = false; 0451 d->m_isSeekable = false; 0452 Q_EMIT seekableChanged(d->m_isSeekable); 0453 } 0454 0455 void AudioManager::seek(qint64 position) 0456 { 0457 qCDebug(kastsAudio) << "AudioManager::seek" << position; 0458 0459 // if there is still a pending seek, then we simply update that pending 0460 // value, and then manually send the positionChanged signal to have the UI 0461 // updated 0462 // NOTE: this can also happen while the streaming URL is still resolving, so 0463 // we also allow seeking even when the track is not yet readyToPlay. 0464 if (d->m_pendingSeek != -1 || !d->m_readyToPlay) { 0465 d->m_pendingSeek = position; 0466 Q_EMIT positionChanged(position); 0467 } else if (d->m_pendingSeek == -1 && d->m_readyToPlay) { 0468 d->m_player.setPosition(position); 0469 } 0470 } 0471 0472 void AudioManager::skipForward() 0473 { 0474 qCDebug(kastsAudio) << "AudioManager::skipForward"; 0475 seek(std::min((position() + (1000 * SettingsManager::skipForward())), duration())); 0476 } 0477 0478 void AudioManager::skipBackward() 0479 { 0480 qCDebug(kastsAudio) << "AudioManager::skipBackward"; 0481 seek(std::max((qint64)0, (position() - (1000 * SettingsManager::skipBackward())))); 0482 } 0483 0484 bool AudioManager::canGoNext() const 0485 { 0486 if (d->m_entry) { 0487 int index = DataManager::instance().queue().indexOf(d->m_entry->id()); 0488 if (index >= 0) { 0489 // check if there is a next track 0490 if (index < DataManager::instance().queue().count() - 1) { 0491 Entry *next_entry = DataManager::instance().getEntry(DataManager::instance().queue()[index + 1]); 0492 if (next_entry && next_entry->enclosure()) { 0493 qCDebug(kastsAudio) << "Enclosure status" << next_entry->enclosure()->path() << next_entry->enclosure()->status(); 0494 if (next_entry->enclosure()->status() == Enclosure::Downloaded) { 0495 return true; 0496 } else { 0497 if (NetworkConnectionManager::instance().streamingAllowed()) { 0498 return true; 0499 } 0500 } 0501 } 0502 } 0503 } 0504 } 0505 return false; 0506 } 0507 0508 void AudioManager::next() 0509 { 0510 if (canGoNext()) { 0511 int index = DataManager::instance().queue().indexOf(d->m_entry->id()); 0512 qCDebug(kastsAudio) << "Skipping to" << DataManager::instance().queue()[index + 1]; 0513 setEntry(DataManager::instance().getEntry(DataManager::instance().queue()[index + 1])); 0514 } else { 0515 qCDebug(kastsAudio) << "Next track cannot be played, changing entry to nullptr"; 0516 setEntry(nullptr); 0517 } 0518 } 0519 0520 void AudioManager::mediaStatusChanged() 0521 { 0522 qCDebug(kastsAudio) << "AudioManager::mediaStatusChanged" << d->m_player.mediaStatus(); 0523 0524 // File has reached the end and has stopped 0525 if (d->m_player.mediaStatus() == KMediaSession::EndOfMedia) { 0526 next(); 0527 } 0528 0529 // if there is a problem with the current track, make sure that it's not 0530 // loaded again when the application is restarted, skip to next track and 0531 // delete the enclosure 0532 if (d->m_player.mediaStatus() == KMediaSession::InvalidMedia) { 0533 // save pointer to this bad entry to allow 0534 // us to delete the enclosure after the track has been unloaded 0535 Entry *badEntry = d->m_entry; 0536 DataManager::instance().setLastPlayingEntry(QStringLiteral("none")); 0537 stop(); 0538 next(); 0539 if (badEntry && badEntry->enclosure()) { 0540 badEntry->enclosure()->deleteFile(); 0541 Q_EMIT logError(Error::Type::InvalidMedia, badEntry->feed()->url(), badEntry->id(), KMediaSession::InvalidMedia, i18n("Invalid Media"), QString()); 0542 } 0543 } 0544 } 0545 0546 void AudioManager::playerDurationChanged(qint64 duration) 0547 { 0548 qCDebug(kastsAudio) << "AudioManager::playerDurationChanged" << duration; 0549 0550 // Check if duration mentioned in enclosure corresponds to real duration 0551 if (d->m_entry && d->m_entry->enclosure()) { 0552 if (duration > 0 && (duration / 1000) != d->m_entry->enclosure()->duration()) { 0553 qCDebug(kastsAudio) << "Correcting duration of" << d->m_entry->id() << "to" << duration / 1000 << "(was" << d->m_entry->enclosure()->duration() 0554 << ")"; 0555 d->m_entry->enclosure()->setDuration(duration / 1000); 0556 } 0557 } 0558 0559 qint64 correctedDuration = duration; 0560 QTimer::singleShot(0, this, [this, correctedDuration]() { 0561 Q_EMIT durationChanged(correctedDuration); 0562 }); 0563 } 0564 0565 void AudioManager::playerVolumeChanged() 0566 { 0567 qCDebug(kastsAudio) << "AudioManager::playerVolumeChanged" << d->m_player.volume(); 0568 0569 QTimer::singleShot(0, this, [this]() { 0570 Q_EMIT volumeChanged(); 0571 }); 0572 } 0573 0574 void AudioManager::playerMutedChanged() 0575 { 0576 qCDebug(kastsAudio) << "AudioManager::playerMutedChanged" << muted(); 0577 0578 QTimer::singleShot(0, this, [this]() { 0579 Q_EMIT mutedChanged(muted()); 0580 }); 0581 } 0582 0583 void AudioManager::savePlayPosition() 0584 { 0585 qCDebug(kastsAudio) << "AudioManager::savePlayPosition"; 0586 0587 // First check if there is still a pending seek 0588 checkForPendingSeek(); 0589 0590 if (!d->m_lockPositionSaving) { 0591 if (d->m_entry && d->m_entry->enclosure()) { 0592 if (d->m_entry->enclosure()) { 0593 d->m_entry->enclosure()->setPlayPosition(position()); 0594 } 0595 } 0596 } 0597 qCDebug(kastsAudio) << d->m_player.mediaStatus(); 0598 } 0599 0600 void AudioManager::setEntryInfo(Entry *entry) 0601 { 0602 // Set info for next track in preparation for the actual audio player to be 0603 // set up and configured. We set all the info based on what's in the entry 0604 // and disable all the controls until the track is ready to be played 0605 0606 d->m_player.setSource(QUrl()); 0607 d->m_entry = entry; 0608 Q_EMIT entryChanged(entry); 0609 0610 qint64 newDuration = entry->enclosure()->duration() * 1000; 0611 qint64 newPosition = entry->enclosure()->playPosition(); 0612 if (newPosition > newDuration && newPosition < 0) { 0613 newPosition = 0; 0614 } 0615 0616 // Emit positionChanged and durationChanged signals to make sure that 0617 // the GUI can see the faked values (if needed) 0618 Q_EMIT durationChanged(newDuration); 0619 Q_EMIT positionChanged(newPosition); 0620 0621 d->m_readyToPlay = false; 0622 Q_EMIT canPlayChanged(); 0623 Q_EMIT canPauseChanged(); 0624 Q_EMIT canSkipForwardChanged(); 0625 Q_EMIT canSkipBackwardChanged(); 0626 Q_EMIT canGoNextChanged(); 0627 d->m_isSeekable = false; 0628 Q_EMIT seekableChanged(false); 0629 } 0630 0631 void AudioManager::prepareAudio(const QUrl &loadUrl) 0632 { 0633 d->m_player.setSource(loadUrl); 0634 0635 // save the current playing track in the settingsfile for restoring on startup 0636 DataManager::instance().setLastPlayingEntry(d->m_entry->id()); 0637 qCDebug(kastsAudio) << "Changed source to" << d->m_entry->title(); 0638 0639 d->m_player.pause(); 0640 0641 qint64 newDuration = duration(); 0642 0643 qint64 startingPosition = d->m_entry->enclosure()->playPosition(); 0644 qCDebug(kastsAudio) << "Changing position to" << startingPosition / 1000 << "sec"; 0645 // if a seek is still pending then we don't set the position here 0646 // this can happen e.g. if a chapter marker was clicked on a non-playing entry 0647 if (d->m_pendingSeek == -1) { 0648 if (startingPosition <= newDuration) { 0649 d->m_pendingSeek = startingPosition; 0650 } else { 0651 d->m_pendingSeek = -1; 0652 } 0653 } 0654 0655 // Emit positionChanged and durationChanged signals to make sure that 0656 // the GUI can see the faked values (if needed) 0657 qint64 newPosition = position(); 0658 Q_EMIT durationChanged(newDuration); 0659 Q_EMIT positionChanged(newPosition); 0660 0661 d->m_readyToPlay = true; 0662 Q_EMIT canPlayChanged(); 0663 Q_EMIT canPauseChanged(); 0664 Q_EMIT canSkipForwardChanged(); 0665 Q_EMIT canSkipBackwardChanged(); 0666 Q_EMIT canGoNextChanged(); 0667 d->m_isSeekable = true; 0668 Q_EMIT seekableChanged(true); 0669 0670 qCDebug(kastsAudio) << "Duration reported by d->m_player" << d->m_player.duration(); 0671 qCDebug(kastsAudio) << "Duration reported by enclosure (in ms)" << d->m_entry->enclosure()->duration() * 1000; 0672 qCDebug(kastsAudio) << "Duration reported by AudioManager" << newDuration; 0673 qCDebug(kastsAudio) << "Position reported by d->m_player" << d->m_player.position(); 0674 qCDebug(kastsAudio) << "Saved position stored in enclosure (in ms)" << startingPosition; 0675 qCDebug(kastsAudio) << "Position reported by AudioManager" << newPosition; 0676 0677 if (d->m_continuePlayback) { 0678 // we call play() and not d->m_player.play() because we want to trigger 0679 // things like inhibit suspend 0680 play(); 0681 } else { 0682 pause(); 0683 } 0684 0685 d->m_lockPositionSaving = false; 0686 0687 // set metadata for MPRIS2 0688 updateMetaData(); 0689 } 0690 0691 void AudioManager::checkForPendingSeek() 0692 { 0693 qint64 position = d->m_player.position(); 0694 qCDebug(kastsAudio) << "Seek pending?" << d->m_pendingSeek; 0695 qCDebug(kastsAudio) << "Current position" << position; 0696 0697 // Check if we're supposed to skip to a new position 0698 if (d->m_pendingSeek != -1 && d->m_player.mediaStatus() == KMediaSession::BufferedMedia && d->m_player.duration() > 0) { 0699 if (abs(d->m_pendingSeek - position) > 2000) { 0700 qCDebug(kastsAudio) << "Position seek still pending to position" << d->m_pendingSeek; 0701 qCDebug(kastsAudio) << "Current reported position and duration" << d->m_player.position() << d->m_player.duration(); 0702 // be very careful because this setPosition call will trigger 0703 // a positionChanged signal, which will be nested, so we call it in 0704 // a QTimer::singleShot 0705 if (playbackState() == KMediaSession::PlaybackState::PlayingState) { 0706 qint64 seekPosition = d->m_pendingSeek; 0707 QTimer::singleShot(0, this, [this, seekPosition]() { 0708 d->m_player.setPosition(seekPosition); 0709 }); 0710 } 0711 } else { 0712 qCDebug(kastsAudio) << "Pending position seek has been executed; to position" << d->m_pendingSeek; 0713 d->m_pendingSeek = -1; 0714 // d->m_player.setNotifyInterval(1000); 0715 } 0716 } 0717 } 0718 0719 void AudioManager::updateMetaData() 0720 { 0721 // set metadata for MPRIS2 0722 if (!d->m_entry->title().isEmpty()) { 0723 d->m_player.metaData()->setTitle(d->m_entry->title()); 0724 } 0725 // TODO: set URL?? d->m_entry->enclosure()->path(); 0726 if (!d->m_entry->feed()->name().isEmpty()) { 0727 d->m_player.metaData()->setAlbum(d->m_entry->feed()->name()); 0728 } 0729 if (d->m_entry->authors().count() > 0) { 0730 QString authors; 0731 for (auto &author : d->m_entry->authors()) 0732 authors.append(author->name()); 0733 d->m_player.metaData()->setArtist(authors); 0734 } 0735 if (!d->m_entry->image().isEmpty()) { 0736 d->m_player.metaData()->setArtworkUrl(QUrl(d->m_entry->cachedImage())); 0737 } 0738 } 0739 0740 QString AudioManager::formattedDuration() const 0741 { 0742 return m_kformat.formatDuration(duration()); 0743 } 0744 0745 QString AudioManager::formattedLeftDuration() const 0746 { 0747 qreal rate = 1.0; 0748 if (SettingsManager::self()->adjustTimeLeft()) { 0749 rate = playbackRate(); 0750 rate = (rate > 0.0) ? rate : 1.0; 0751 } 0752 qint64 diff = duration() - position(); 0753 return m_kformat.formatDuration(diff / rate); 0754 } 0755 0756 QString AudioManager::formattedPosition() const 0757 { 0758 return m_kformat.formatDuration(position()); 0759 } 0760 0761 qint64 AudioManager::sleepTime() const 0762 { 0763 if (d->m_sleepTimer) { 0764 return d->m_sleepTime; 0765 } else { 0766 return -1; 0767 } 0768 } 0769 0770 qint64 AudioManager::remainingSleepTime() const 0771 { 0772 if (d->m_sleepTimer) { 0773 return d->m_remainingSleepTime; 0774 } else { 0775 return -1; 0776 } 0777 } 0778 0779 void AudioManager::setSleepTimer(qint64 duration) 0780 { 0781 if (duration > 0) { 0782 if (d->m_sleepTimer) { 0783 stopSleepTimer(); 0784 } 0785 0786 d->m_sleepTime = duration; 0787 d->m_remainingSleepTime = duration; 0788 0789 d->m_sleepTimer = new QTimer(this); 0790 connect(d->m_sleepTimer, &QTimer::timeout, this, [this]() { 0791 (d->m_remainingSleepTime)--; 0792 if (d->m_remainingSleepTime > 0) { 0793 Q_EMIT remainingSleepTimeChanged(remainingSleepTime()); 0794 } else { 0795 pause(); 0796 stopSleepTimer(); 0797 } 0798 }); 0799 d->m_sleepTimer->start(1000); 0800 0801 Q_EMIT sleepTimerChanged(duration); 0802 Q_EMIT remainingSleepTimeChanged(remainingSleepTime()); 0803 } else { 0804 stopSleepTimer(); 0805 } 0806 } 0807 0808 void AudioManager::stopSleepTimer() 0809 { 0810 if (d->m_sleepTimer) { 0811 d->m_sleepTime = -1; 0812 d->m_remainingSleepTime = -1; 0813 0814 delete d->m_sleepTimer; 0815 d->m_sleepTimer = nullptr; 0816 0817 Q_EMIT sleepTimerChanged(-1); 0818 Q_EMIT remainingSleepTimeChanged(-1); 0819 } 0820 } 0821 0822 QString AudioManager::formattedRemainingSleepTime() const 0823 { 0824 qint64 timeLeft = remainingSleepTime() * 1000; 0825 if (timeLeft < 0) { 0826 timeLeft = 0; 0827 } 0828 return m_kformat.formatDuration(timeLeft); 0829 }