fix: emit MPRIS Seeked, so an already-open widget stops extrapolating

The owner's observation is what separated the two halves of this: "el
contador del seekbar del widget solo empieza a contar hasta que lo abro."
A client reads Position ONCE, when it opens, and extrapolates from there —
it does not poll. So two different things were broken and only one was fixed
by keeping the stored position fresh.

Fresh storage fixes what a client sees when it OPENS. `Seeked` is the only
thing that reaches one that is ALREADY open: it tells the client to stop
extrapolating and re-read. Without it, a seek moved the music and the widget
kept counting from where it had been.

So the seek path emits Seeked instead of re-asserting the playback state,
which was the previous commit's guess at a cure inside an API that had no
signal. `mpris_server::Signal::Seeked` was there all along.

Defaulted on the trait to `set_position`, so a backend without the signal
still gets the value right on the next read and no other consumer changes:
cargo check passes for qbz-app and qbzd.

cc
This commit is contained in:
vicrodh committed 2026-08-04 14:05:05 -06:00
1 parent f45b703c64
commit 7ba07ec6f8
4 files changed
+48 -15

No files matched your search

+16 -1
View File
@@ -20,7 +20,7 @@ use std::sync::{
use mpris_server::zbus::{self, fdo};
use mpris_server::{
LoopStatus, Metadata, PlaybackRate, PlaybackStatus as MprisStatus, PlayerInterface, Property,
RootInterface, Server, Time, TrackId, Volume,
RootInterface, Server, Signal, Time, TrackId, Volume,
};
use crate::inhibit::SleepInhibitor;
@@ -58,6 +58,11 @@ enum Update {
/// `PropertiesChanged` by design, so there is no signal to send; the point
/// is purely that the next on-demand read is not stale.
Position(Time),
/// A DISCONTINUOUS jump. Stores like `Position` and additionally emits the
/// `Seeked` signal, which is the ONE thing that tells an already-open
/// client to stop extrapolating and re-read. Without it a widget keeps
/// counting from wherever it was until it happens to poll again.
Seeked(Time),
}
/// The cloneable handle returned to the app. Pushing state is a non-blocking
@@ -87,6 +92,12 @@ impl MediaIntegration for LinuxHandle {
.tx
.try_send(Update::Position(Time::from_micros(position.as_micros() as i64)));
}
fn seeked(&self, position: std::time::Duration) {
let _ = self
.tx
.try_send(Update::Seeked(Time::from_micros(position.as_micros() as i64)));
}
}
fn map_status(s: PlaybackStatus) -> MprisStatus {
@@ -297,6 +308,10 @@ async fn apply(server: &Server<QbzMpris>, state: &Arc<Mutex<State>>, update: Upd
Update::Position(p) => {
state.lock().unwrap().position = p;
}
Update::Seeked(p) => {
state.lock().unwrap().position = p;
let _ = server.emit(Signal::Seeked { position: p }).await;
}
}
}
+14
View File
@@ -91,4 +91,18 @@ pub trait MediaIntegration: Send + Sync {
fn set_position(&self, position: Duration) {
let _ = position;
}
/// Report a DISCONTINUOUS jump — a user seek, not the ordinary advance of
/// playback.
///
/// This is the one thing that reaches a client which is already open and
/// extrapolating: `Seeked` tells it to stop and re-read. Keeping the
/// stored position fresh (above) fixes what a client sees when it OPENS;
/// this fixes what it sees while it is already watching.
///
/// Defaulted to `set_position`, so a backend without the signal still gets
/// the value right on the next read.
fn seeked(&self, position: Duration) {
self.set_position(position);
}
}