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 <noreply@anthropic.com>
This commit is contained in:
2026-08-11 17:41:35 +02:00
co-authored by Claude Sonnet 5
parent e39d8973d5
commit 27f102d594
+56 -6
View File
@@ -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();