From d1a172751d62af47c6b5bccd7545768ebd2cb2c4 Mon Sep 17 00:00:00 2001 From: outfoxxed Date: Thu, 20 Feb 2025 23:56:14 -0800 Subject: [PATCH] service/mpris: hack around more non-compliant players Mpris is currently winning the competition for least compliant clients. --- src/services/mpris/player.cpp | 31 +++++++++++++++++++++++++++++++ src/services/mpris/player.hpp | 7 +++++-- 2 files changed, 36 insertions(+), 2 deletions(-) diff --git a/src/services/mpris/player.cpp b/src/services/mpris/player.cpp index 4aac2826..b57ad48a 100644 --- a/src/services/mpris/player.cpp +++ b/src/services/mpris/player.cpp @@ -1,5 +1,6 @@ #include "player.hpp" +#include #include #include #include @@ -317,11 +318,24 @@ void MprisPlayer::onMetadataChanged() { } } + // Some players (Jellyfin) specify xesam:url or mpris:trackid + // and DON'T ACTUALLY CHANGE THEM WHEN THE TRACK CHANGES. + auto titleVariant = this->bpMetadata.value().value("xesam:title"); + if (titleVariant.isValid() && titleVariant.canConvert()) { + auto title = titleVariant.toString(); + + if (title != this->mTrackTitle) { + this->mTrackTitle = title; + trackChanged = true; + } + } + Qt::beginPropertyUpdateGroup(); if (trackChanged) { emit this->trackChanged(); this->bUniqueId = this->bUniqueId + 1; + this->trackChangedBeforeState = true; // Some players don't seem to send position updates or seeks on track change. this->pPosition.requestUpdate(); @@ -386,6 +400,23 @@ void MprisPlayer::setPlaying(bool playing) { this->togglePlaying(); } +void MprisPlayer::onPlaybackStatusUpdated() { + // Insurance - have not yet seen a player where this particular check is required that doesn't + // require the late query below. + this->pPosition.requestUpdate(); + + // For exceptionally bad players that update playback timestamps at an indeterminate time AFTER + // updating playback state. (Youtube) + QTimer::singleShot(100, this, [&]() { this->pPosition.requestUpdate(); }); + + // For exceptionally bad players that don't update length (or other metadata) until a new track actually + // starts playing, and then don't trigger a metadata update when they do. (Jellyfin) + if (this->trackChangedBeforeState) { + this->trackChangedBeforeState = false; + this->pMetadata.requestUpdate(); + } +} + bool MprisPlayer::loopSupported() const { return this->pLoopStatus.exists(); } void MprisPlayer::setLoopState(MprisLoopState::Enum loopState) { diff --git a/src/services/mpris/player.hpp b/src/services/mpris/player.hpp index 2eda305d..7f768ae5 100644 --- a/src/services/mpris/player.hpp +++ b/src/services/mpris/player.hpp @@ -242,7 +242,7 @@ public: /// Equivalent to calling @@play() if not playing or @@pause() if playing. /// /// May only be called if @@canTogglePlaying is true, which is equivalent to - /// @@canPlay or @@canPause() depending on the current playback state. + /// @@canPlay or @@canPause depending on the current playback state. Q_INVOKABLE void togglePlaying(); [[nodiscard]] bool isValid() const; @@ -391,6 +391,7 @@ private slots: private: void onMetadataChanged(); void onPositionUpdated(); + void onPlaybackStatusUpdated(); // call instead of setting bpPosition void setPosition(qlonglong position); void requestPositionUpdate() { this->pPosition.requestUpdate(); }; @@ -462,7 +463,7 @@ private: QS_DBUS_PROPERTY_BINDING(MprisPlayer, qlonglong, pPosition, bpPosition, onPositionUpdated, playerProperties, "Position", false); QS_DBUS_PROPERTY_BINDING(MprisPlayer, pVolume, bVolume, playerProperties, "Volume", false); QS_DBUS_PROPERTY_BINDING(MprisPlayer, pMetadata, bpMetadata, playerProperties, "Metadata"); - QS_DBUS_PROPERTY_BINDING(MprisPlayer, pPlaybackStatus, bpPlaybackStatus, playerProperties, "PlaybackStatus"); + QS_DBUS_PROPERTY_BINDING(MprisPlayer, void, pPlaybackStatus, bpPlaybackStatus, onPlaybackStatusUpdated, playerProperties, "PlaybackStatus", true); QS_DBUS_PROPERTY_BINDING(MprisPlayer, pLoopStatus, bpLoopStatus, playerProperties, "LoopStatus", false); QS_DBUS_PROPERTY_BINDING(MprisPlayer, pRate, bRate, playerProperties, "Rate", false); QS_DBUS_PROPERTY_BINDING(MprisPlayer, pMinRate, bMinRate, playerProperties, "MinimumRate", false); @@ -477,6 +478,8 @@ private: DBusMprisPlayer* player = nullptr; QString mTrackId; QString mTrackUrl; + QString mTrackTitle; + bool trackChangedBeforeState = false; }; } // namespace qs::service::mpris