Defer initial playback-rate seek to ASYNC_DONE

An early seek right after set_state(PLAYING) raced the pipeline's
preroll and GStreamer silently dropped it, so a configured custom
rate only took effect after the first EOS loop-restart. Instead,
flag the pending seek and fire it from the first ASYNC_DONE bus
message, once the pipeline is actually ready to seek.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-11 17:41:35 +02:00
co-authored by Claude Sonnet 5
parent 27f102d594
commit 603b9f9ded
+30 -8
View File
@@ -80,6 +80,7 @@ export class LiveWallpaperManager {
this._busWatchId = 0; this._busWatchId = 0;
this._eosSeekTimeoutId = 0; this._eosSeekTimeoutId = 0;
this._lastEosSeekTimeMs = 0; this._lastEosSeekTimeMs = 0;
this._pendingInitialRateSeek = false;
this._monitorsChangedId = 0; this._monitorsChangedId = 0;
this._fullscreenChangedId = 0; this._fullscreenChangedId = 0;
this._upowerProxy = null; this._upowerProxy = null;
@@ -279,11 +280,17 @@ export class LiveWallpaperManager {
this._applyMute(); this._applyMute();
const stateChangeResult = this._playbin.set_state(Gst.State.PLAYING); const stateChangeResult = this._playbin.set_state(Gst.State.PLAYING);
this._logger.debug(`playbin.set_state(PLAYING) returned ${stateChangeResult}`); this._logger.debug(`playbin.set_state(PLAYING) returned ${stateChangeResult}`);
// set_state(PLAYING) alone always starts at native rate 1.0 — // Seeking here immediately would race the pipeline's preroll —
// without this, a configured custom speed only ever took effect // GStreamer can only seek once a state change has actually
// once the first EOS loop-restart seek ran. // completed, so an early seek is silently dropped and playback
if (this._settings.get_double(SettingsKey.LIVE_WALLPAPER_PLAYBACK_RATE) !== 1.0) // sticks at native 1.0x. Instead, defer to the first ASYNC_DONE
this._seekToLoopStart(); // bus message (handled in _onBusMessage), which fires once the
// pipeline is actually ready to seek.
const initialRate = this._settings.get_double(SettingsKey.LIVE_WALLPAPER_PLAYBACK_RATE);
this._pendingInitialRateSeek = initialRate !== 1.0;
this._logger.debug(
`Live wallpaper: configured playback rate ${initialRate}, ` +
`deferring initial seek to ASYNC_DONE: ${this._pendingInitialRateSeek}`);
this._frameCount = 0; this._frameCount = 0;
this._pollCount = 0; this._pollCount = 0;
@@ -409,6 +416,14 @@ export class LiveWallpaperManager {
case Gst.MessageType.EOS: case Gst.MessageType.EOS:
this._loopPlayback(); this._loopPlayback();
break; break;
case Gst.MessageType.ASYNC_DONE:
this._logger.debug(
`Live wallpaper: ASYNC_DONE received (pending initial rate seek: ${this._pendingInitialRateSeek})`);
if (this._pendingInitialRateSeek) {
this._pendingInitialRateSeek = false;
this._seekToLoopStart();
}
break;
case Gst.MessageType.ERROR: { case Gst.MessageType.ERROR: {
const [error, debug] = message.parse_error(); const [error, debug] = message.parse_error();
this._logger.error(error, `Live wallpaper playback error (${debug ?? 'no debug info'})`); this._logger.error(error, `Live wallpaper playback error (${debug ?? 'no debug info'})`);
@@ -466,9 +481,11 @@ export class LiveWallpaperManager {
this._lastEosSeekTimeMs = GLib.get_monotonic_time() / 1000; this._lastEosSeekTimeMs = GLib.get_monotonic_time() / 1000;
const Gst = this._Gst; const Gst = this._Gst;
const rate = this._settings.get_double(SettingsKey.LIVE_WALLPAPER_PLAYBACK_RATE); const rate = this._settings.get_double(SettingsKey.LIVE_WALLPAPER_PLAYBACK_RATE);
this._playbin.seek( const ok = this._playbin.seek(
rate, Gst.Format.TIME, Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT, rate, Gst.Format.TIME, Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT,
Gst.SeekType.SET, 0, Gst.SeekType.NONE, -1); Gst.SeekType.SET, 0, Gst.SeekType.NONE, -1);
this._logger.debug(
`Live wallpaper: loop-restart seek to rate ${rate} ${ok ? 'succeeded' : 'FAILED (seek() returned false)'}`);
} }
_stateName(state) { _stateName(state) {
@@ -488,12 +505,17 @@ export class LiveWallpaperManager {
const Gst = this._Gst; const Gst = this._Gst;
const rate = this._settings.get_double(SettingsKey.LIVE_WALLPAPER_PLAYBACK_RATE); const rate = this._settings.get_double(SettingsKey.LIVE_WALLPAPER_PLAYBACK_RATE);
const [ok, position] = this._playbin.query_position(Gst.Format.TIME); const [ok, position] = this._playbin.query_position(Gst.Format.TIME);
if (!ok) if (!ok) {
this._logger.debug('Live wallpaper: applyPlaybackRate aborted, query_position() failed');
return; return;
}
this._playbin.seek( const seekOk = this._playbin.seek(
rate, Gst.Format.TIME, Gst.SeekFlags.FLUSH | Gst.SeekFlags.ACCURATE, rate, Gst.Format.TIME, Gst.SeekFlags.FLUSH | Gst.SeekFlags.ACCURATE,
Gst.SeekType.SET, position, Gst.SeekType.NONE, -1); Gst.SeekType.SET, position, Gst.SeekType.NONE, -1);
this._logger.debug(
`Live wallpaper: applyPlaybackRate to ${rate} at position ${position} ` +
`${seekOk ? 'succeeded' : 'FAILED (seek() returned false)'}`);
} }
/** /**