service/mpris: hack around more non-compliant players

Mpris is currently winning the competition for least compliant clients.
This commit is contained in:
outfoxxed 2025-02-20 23:56:14 -08:00
parent 1eabf5b3c3
commit d1a172751d
Signed by: outfoxxed
GPG key ID: 4C88A185FB89301E
2 changed files with 36 additions and 2 deletions

View file

@ -1,5 +1,6 @@
#include "player.hpp"
#include <qtimer.h>
#include <qcontainerfwd.h>
#include <qdatetime.h>
#include <qdbusconnection.h>
@ -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<QString>()) {
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) {

View file

@ -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