diff --git a/core/include/JellyfinQt/viewmodel/playbackmanager.h b/core/include/JellyfinQt/viewmodel/playbackmanager.h index d38d4a4..81613ed 100644 --- a/core/include/JellyfinQt/viewmodel/playbackmanager.h +++ b/core/include/JellyfinQt/viewmodel/playbackmanager.h @@ -209,6 +209,7 @@ private slots: void mediaPlayerMediaStatusChanged(QMediaPlayer::MediaStatus newStatus); void mediaPlayerError(QMediaPlayer::Error error); void mediaPlayerDurationChanged(qint64 newDuration); + void mediaPlayerSeekableChanged(bool seekable); /** * @brief updatePlaybackInfo Updates the Jellyfin server with the current playback progress etc. */ @@ -264,6 +265,8 @@ private: */ bool m_autoOpen = false; + bool m_seekToResumedPosition = false; + QMediaPlayer::State m_oldState = QMediaPlayer::StoppedState; /// State of the playbackManager. While the internal media player stops after a /// song has ended, this will not do so. @@ -274,8 +277,7 @@ private: Model::Playlist *m_queue = nullptr; int m_queueIndex = 0; - bool m_resumePlayback = true; - + bool m_resumePlayback = false; bool m_handlePlaystateCommands = true; // Helper methods @@ -300,6 +302,7 @@ private: /// Time in ms at what moment this playbackmanager should start loading the next item. const qint64 PRELOAD_DURATION = 15 * 1000; + QTimer m_forceSeekTimer; }; } // NS ViewModel diff --git a/core/src/viewmodel/playbackmanager.cpp b/core/src/viewmodel/playbackmanager.cpp index a09f36c..9b36530 100644 --- a/core/src/viewmodel/playbackmanager.cpp +++ b/core/src/viewmodel/playbackmanager.cpp @@ -61,10 +61,27 @@ PlaybackManager::PlaybackManager(QObject *parent) connect(m_mediaPlayer, &QMediaPlayer::durationChanged, this, &PlaybackManager::mediaPlayerDurationChanged); connect(m_mediaPlayer, &QMediaPlayer::mediaStatusChanged, this, &PlaybackManager::mediaPlayerMediaStatusChanged); connect(m_mediaPlayer, &QMediaPlayer::videoAvailableChanged, this, &PlaybackManager::hasVideoChanged); - connect(m_mediaPlayer, &QMediaPlayer::seekableChanged, this, &PlaybackManager::seekableChanged); + connect(m_mediaPlayer, &QMediaPlayer::seekableChanged, this, &PlaybackManager::mediaPlayerSeekableChanged); // I do not like the complicated overload cast connect(m_mediaPlayer, SIGNAL(error(QMediaPlayer::Error)), this, SLOT(mediaPlayerError(QMediaPlayer::Error))); + m_forceSeekTimer.setInterval(500); + m_forceSeekTimer.setSingleShot(false); + + // Yes, this is a very ugly way of forcing the video player to seek to the resume position + connect(&m_forceSeekTimer, &QTimer::timeout, this, [this]() { + if (m_seekToResumedPosition && m_mediaPlayer->isSeekable()) { + qCDebug(playbackManager) << "Trying to seek to the resume position" << (m_resumePosition / MS_TICK_FACTOR); + if (m_mediaPlayer->position() > m_resumePosition / MS_TICK_FACTOR - 500) { + m_seekToResumedPosition = false; + m_forceSeekTimer.stop(); + } else { + m_mediaPlayer->setPosition(m_resumePosition / MS_TICK_FACTOR); + } + } + + }); + } void PlaybackManager::setApiClient(ApiClient *apiClient) { @@ -96,12 +113,17 @@ void PlaybackManager::setItem(QSharedPointer newItem) { m_displayItem->setData(QSharedPointer::create()); } else { m_displayItem->setData(newItem); + if (!newItem->userData().isNull()) { + this->m_resumePosition = newItem->userData()->playbackPositionTicks(); + } } emit itemChanged(m_displayItem); emit hasNextChanged(m_queue->hasNext()); emit hasPreviousChanged(m_queue->hasPrevious()); + this->m_seekToResumedPosition = m_resumePlayback; + if (m_apiClient == nullptr) { qCWarning(playbackManager) << "apiClient is not set on this MediaSource instance! Aborting."; @@ -161,12 +183,23 @@ void PlaybackManager::mediaPlayerPositionChanged(qint64 position) { void PlaybackManager::mediaPlayerStateChanged(QMediaPlayer::State newState) { if (m_oldState == newState) return; + + if (newState == QMediaPlayer::PlayingState) { + if (m_seekToResumedPosition) { + if (m_mediaPlayer->isSeekable()) { + qCDebug(playbackManager) << "Resuming playback by seeking to " << (m_resumePosition / MS_TICK_FACTOR); + m_mediaPlayer->setPosition(m_resumePosition / MS_TICK_FACTOR); + m_seekToResumedPosition = false; + } + } + } + if (m_oldState == QMediaPlayer::StoppedState) { // We're transitioning from stopped to either playing or paused. // Set up the recurring timer m_updateTimer.start(); postPlaybackInfo(Started); - } else if (newState == QMediaPlayer::StoppedState) { + } else if (newState == QMediaPlayer::StoppedState && m_playbackState == QMediaPlayer::StoppedState) { // We've stopped playing the media. Post a stop signal. m_updateTimer.stop(); postPlaybackInfo(Stopped); @@ -181,11 +214,7 @@ void PlaybackManager::mediaPlayerMediaStatusChanged(QMediaPlayer::MediaStatus ne emit mediaStatusChanged(newStatus); if (m_playbackState == QMediaPlayer::StoppedState) return; if (newStatus == QMediaPlayer::LoadedMedia) { - m_mediaPlayer->play(); - if (m_resumePlayback) { - qCDebug(playbackManager) << "Resuming playback by seeking to " << (m_resumePosition / MS_TICK_FACTOR); - m_mediaPlayer->setPosition(m_resumePosition / MS_TICK_FACTOR); - } + m_mediaPlayer->play(); } else if (newStatus == QMediaPlayer::EndOfMedia) { if (m_queue->hasNext() && m_queue->totalSize() > 1) { next(); @@ -204,6 +233,21 @@ void PlaybackManager::mediaPlayerDurationChanged(qint64 newDuration) { } } +void PlaybackManager::mediaPlayerSeekableChanged(bool newSeekable) { + emit seekableChanged(newSeekable); + if (m_seekToResumedPosition) { + m_forceSeekTimer.start(); + } + /*if (m_seekToResumedPosition && newSeekable) { + qCDebug(playbackManager) << "Trying to seek to the resume position"; + + m_mediaPlayer->setPosition(m_resumePosition / MS_TICK_FACTOR); + if (m_mediaPlayer->position() > 1000) { + m_seekToResumedPosition = false; + } + }*/ +} + void PlaybackManager::mediaPlayerError(QMediaPlayer::Error error) { emit errorChanged(error); emit errorStringChanged(m_mediaPlayer->errorString()); @@ -403,7 +447,7 @@ void PlaybackManager::postPlaybackInfo(PlaybackInfoType type) { break; } case Stopped: - progress.setPositionTicks(m_mediaPlayer->position() * MS_TICK_FACTOR); + progress.setPositionTicks(m_stopPosition * MS_TICK_FACTOR); break; } @@ -428,7 +472,7 @@ void PlaybackManager::postPlaybackInfo(PlaybackInfoType type) { } void PlaybackManager::componentComplete() { - if (m_apiClient == nullptr) qWarning() << "No ApiClient set for PlaybackManager"; + if (m_apiClient == nullptr) qCWarning(playbackManager) << "No ApiClient set for PlaybackManager"; m_qmlIsParsingComponent = false; } @@ -513,11 +557,11 @@ void PlaybackManager::handlePlaybackInfoResponse(QString itemId, QString mediaTy } - qDebug() << "Media source: " << source.name() << "\n" - << "Prefer transcoding: " << transcodePreferred << "\n" - << "DirectPlay supported: " << source.supportsDirectPlay() << "\n" - << "DirectStream supported: " << source.supportsDirectStream() << "\n" - << "Transcode supported: " << source.supportsTranscoding(); + qCDebug(playbackManager()) << "Media source: " << source.name() << "\n" + << "Prefer transcoding: " << transcodePreferred << "\n" + << "DirectPlay supported: " << source.supportsDirectPlay() << "\n" + << "DirectStream supported: " << source.supportsDirectStream() << "\n" + << "Transcode supported: " << source.supportsTranscoding(); if (source.supportsDirectPlay() && QFile::exists(source.path())) { resultingUrl = QUrl::fromLocalFile(source.path()); @@ -535,16 +579,16 @@ void PlaybackManager::handlePlaybackInfoResponse(QString itemId, QString mediaTy + "/stream." + source.container() + "?" + query.toString(QUrl::EncodeReserved)); playMethod = PlayMethod::DirectStream; } else if (source.supportsTranscoding() && !source.transcodingUrlNull() && transcodingAllowed) { - qDebug() << "Transcoding url: " << source.transcodingUrl(); + qCDebug(playbackManager) << "Transcoding url: " << source.transcodingUrl(); resultingUrl = QUrl(m_apiClient->baseUrl() + source.transcodingUrl()); playMethod = PlayMethod::Transcode; } else { - qDebug() << "No suitable sources for item " << itemId; + qCDebug(playbackManager) << "No suitable sources for item " << itemId; } if (!resultingUrl.isEmpty()) break; } if (resultingUrl.isEmpty()) { - qWarning() << "Could not find suitable media source for item " << itemId; + qCWarning(playbackManager) << "Could not find suitable media source for item " << itemId; onItemErrorReceived(itemId, tr("Could not find a suitable media source.")); } else { emit playMethodChanged(playMethod);