fix: MPRIS reported a frozen position — Position is read on demand

The owner measured QBZ at 4:03 with Plasma's widget stuck at 0:14. The
earlier seek fix was right about seeks and wrong about the shape of the
problem: the position was not drifting, it was FROZEN.

MPRIS `Position` is a read-on-demand property — the spec deliberately keeps
it out of `PropertiesChanged`, so a client reads it when it wants it. The
Linux backend serves whatever it last stored, and it only ever stored a
position when `set_playback` carried one: at a track start (zero) and at a
play/pause edge. Between those, every read returned the same stale number
for as long as the track ran.

This is in the SHARED crate, so the shipping Slint build has it identically —
nobody had watched the widget's clock there.

`MediaIntegration::set_position` stores and emits nothing, so the Qt poll
loop refreshes it on the tick it already computes the position for: one
channel send per second, no bus signal, so it does not become the per-tick
work the perf rules refuse. The method is DEFAULTED, so no other consumer has
to change — `cargo check` passes for qbz-app and qbzd, and the Slint frontend
needs no edit (it cannot be built here, per the qbz-ui wall, so it is
verified statically as R4 requires).

cc
This commit is contained in:
vicrodh committed 2026-08-04 13:51:19 -06:00
1 parent 0372b4d66f
commit f45b703c64
4 files changed
+54

No files matched your search

+17
View File
@@ -191,6 +191,23 @@ pub(crate) fn push_playback_state(is_playing: bool, position_secs: u64) {
mc.set_playback(status, Some(Duration::from_secs(position_secs)));
}
/// Keep the reported position current, once per playback tick.
///
/// MPRIS `Position` is READ ON DEMAND — the spec keeps it out of
/// `PropertiesChanged` — so the backend serves whatever it last stored, and
/// before this it only stored one at a track start and at a play/pause edge.
/// A desktop widget therefore showed a value that could be minutes stale:
/// measured 2026-08-04 with QBZ at 4:03 and Plasma's widget stuck at 0:14.
///
/// `set_position` stores and emits nothing, so this is a channel send per
/// second and never a bus signal — it does not become the per-tick work
/// §7-M12 refuses.
pub(crate) fn push_position(position_secs: u64) {
if let Some(mc) = handle() {
mc.set_position(Duration::from_secs(position_secs));
}
}
/// Compare-and-record the metadata de-dupe key. `true` when `key` differs from
/// the last pushed value (→ caller pushes now), recording it as the new
/// last-pushed value. A poisoned lock falls back to pushing.
+3
View File
@@ -2255,6 +2255,9 @@ pub fn start_poll_loop(runtime: Arc<AppRuntime<LoggingAdapter>>) {
cache,
track_id != 0,
);
// The desktop's copy of the same number. Read-on-demand, so it has
// to be kept fresh rather than signalled — see push_position.
crate::media_controls_qt::push_position(position);
// DELIVERED stream params -> the downgrade arrow + tooltip cause.
// Deduped inside, so a steady stream costs no Qt-thread hop.