diff --git a/lib/liveWallpaper.js b/lib/liveWallpaper.js index 5205c02..6487590 100644 --- a/lib/liveWallpaper.js +++ b/lib/liveWallpaper.js @@ -80,6 +80,7 @@ export class LiveWallpaperManager { this._busWatchId = 0; this._eosSeekTimeoutId = 0; this._lastEosSeekTimeMs = 0; + this._pendingInitialRateSeek = false; this._monitorsChangedId = 0; this._fullscreenChangedId = 0; this._upowerProxy = null; @@ -279,11 +280,17 @@ 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(); + // Seeking here immediately would race the pipeline's preroll — + // GStreamer can only seek once a state change has actually + // completed, so an early seek is silently dropped and playback + // sticks at native 1.0x. Instead, defer to the first ASYNC_DONE + // 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._pollCount = 0; @@ -409,6 +416,14 @@ export class LiveWallpaperManager { case Gst.MessageType.EOS: this._loopPlayback(); 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: { const [error, debug] = message.parse_error(); 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; const Gst = this._Gst; 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, 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) { @@ -488,12 +505,17 @@ export class LiveWallpaperManager { const Gst = this._Gst; const rate = this._settings.get_double(SettingsKey.LIVE_WALLPAPER_PLAYBACK_RATE); 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; + } - this._playbin.seek( + const seekOk = this._playbin.seek( rate, Gst.Format.TIME, Gst.SeekFlags.FLUSH | Gst.SeekFlags.ACCURATE, 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)'}`); } /**