From 27f102d594f9abd34209a806df9d16503fe016bd Mon Sep 17 00:00:00 2001 From: Quinta0 <0pietroquintavalle0@gmail.com> Date: Tue, 11 Aug 2026 17:13:51 +0200 Subject: [PATCH] Fix EOS loop freeze and playback rate not sticking on start Throttle the EOS loop-restart seek so a fast-looping clip can't fire back-to-back blocking flush seeks on gnome-shell's main loop, which was severe enough to freeze the whole desktop. Also apply the configured playback rate immediately on start instead of only from the first loop restart. Co-Authored-By: Claude Sonnet 5 --- lib/liveWallpaper.js | 62 +++++++++++++++++++++++++++++++++++++++----- 1 file changed, 56 insertions(+), 6 deletions(-) diff --git a/lib/liveWallpaper.js b/lib/liveWallpaper.js index 739ca89..5205c02 100644 --- a/lib/liveWallpaper.js +++ b/lib/liveWallpaper.js @@ -78,6 +78,8 @@ export class LiveWallpaperManager { this._settingsSignals = []; this._busWatchId = 0; + this._eosSeekTimeoutId = 0; + this._lastEosSeekTimeMs = 0; this._monitorsChangedId = 0; this._fullscreenChangedId = 0; this._upowerProxy = null; @@ -277,6 +279,11 @@ export class LiveWallpaperManager { this._applyMute(); const stateChangeResult = this._playbin.set_state(Gst.State.PLAYING); this._logger.debug(`playbin.set_state(PLAYING) returned ${stateChangeResult}`); + // set_state(PLAYING) alone always starts at native rate 1.0 — + // without this, a configured custom speed only ever took effect + // once the first EOS loop-restart seek ran. + if (this._settings.get_double(SettingsKey.LIVE_WALLPAPER_PLAYBACK_RATE) !== 1.0) + this._seekToLoopStart(); this._frameCount = 0; this._pollCount = 0; @@ -399,13 +406,9 @@ export class LiveWallpaperManager { _onBusMessage(message) { const Gst = this._Gst; switch (message.type) { - case Gst.MessageType.EOS: { - const rate = this._settings.get_double(SettingsKey.LIVE_WALLPAPER_PLAYBACK_RATE); - this._playbin.seek( - rate, Gst.Format.TIME, Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT, - Gst.SeekType.SET, 0, Gst.SeekType.NONE, -1); + case Gst.MessageType.EOS: + this._loopPlayback(); break; - } case Gst.MessageType.ERROR: { const [error, debug] = message.parse_error(); this._logger.error(error, `Live wallpaper playback error (${debug ?? 'no debug info'})`); @@ -426,6 +429,48 @@ export class LiveWallpaperManager { } } + /** + * Restarts playback at EOS. `seek()` with GST_SEEK_FLAG_FLUSH blocks + * the calling thread until the pipeline flushes — and since this whole + * extension runs on gnome-shell's own main loop, that blocks the entire + * desktop for as long as it takes. At rate 1.0 that's one blocking call + * per full playthrough, tolerable; at higher rates a short clip loops + * (and EOS fires) many times a second, turning it into a sustained + * back-to-back stall that reads as a hard freeze. Throttling to at most + * one restart per EOS_SEEK_MIN_INTERVAL_MS gives the main loop room to + * breathe between them. + */ + _loopPlayback() { + const nowMs = GLib.get_monotonic_time() / 1000; + const elapsedMs = nowMs - this._lastEosSeekTimeMs; + const minIntervalMs = 150; + + if (elapsedMs >= minIntervalMs) { + this._seekToLoopStart(); + return; + } + + if (this._eosSeekTimeoutId) + return; + this._eosSeekTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, minIntervalMs - elapsedMs, () => { + this._eosSeekTimeoutId = 0; + this._seekToLoopStart(); + return GLib.SOURCE_REMOVE; + }); + } + + _seekToLoopStart() { + if (!this._playbin) + return; + + this._lastEosSeekTimeMs = GLib.get_monotonic_time() / 1000; + const Gst = this._Gst; + const rate = this._settings.get_double(SettingsKey.LIVE_WALLPAPER_PLAYBACK_RATE); + this._playbin.seek( + rate, Gst.Format.TIME, Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT, + Gst.SeekType.SET, 0, Gst.SeekType.NONE, -1); + } + _stateName(state) { const Gst = this._Gst; return Object.keys(Gst.State).find(name => Gst.State[name] === state) ?? String(state); @@ -610,6 +655,11 @@ export class LiveWallpaperManager { this._pollTimeoutId = 0; } + if (this._eosSeekTimeoutId) { + GLib.source_remove(this._eosSeekTimeoutId); + this._eosSeekTimeoutId = 0; + } + if (this._playbin) { const Gst = this._Gst; const bus = this._playbin.get_bus();