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 }