From 2688568fac1b43e14cf7067f28e0ddf6177545dc Mon Sep 17 00:00:00 2001 From: Claude Date: Tue, 4 Aug 2026 00:50:24 +0000 Subject: [PATCH] Add live wallpaper pipeline diagnostics A user's live wallpaper reached "started" with zero errors (Cogl and GStreamer both loading fine, actor added to _backgroundGroup) but never actually appeared on screen, and no code path here could tell us why: the bus handler only looked at EOS/ERROR, silently discarding WARNING and STATE_CHANGED messages where a stalled negotiation or preroll would actually show up. Adds: WARNING message logging, PLAYING/PAUSED/etc. state-changed logging for the playbin itself, a frame counter with first-frame and periodic debug logs, and a one-shot watchdog that logs an explicit warning if zero frames arrive within 4s of set_state(PLAYING). Also switched appsink's pull-sample from an emitted action signal to the plain pull_sample() method, which is the more directly supported GstApp.AppSink API and one less variable while debugging. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_019RDqbdjsiisSU7CbQke4g8 --- lib/liveWallpaper.js | 50 ++++++++++++++++++++++++++++++++++++++++---- 1 file changed, 46 insertions(+), 4 deletions(-) diff --git a/lib/liveWallpaper.js b/lib/liveWallpaper.js index 335c67c..566192f 100644 --- a/lib/liveWallpaper.js +++ b/lib/liveWallpaper.js @@ -48,6 +48,9 @@ export class LiveWallpaperManager { this._fullscreenChangedId = 0; this._upowerProxy = null; this._upowerSignalId = 0; + + this._frameCount = 0; + this._noFrameWatchdogId = 0; } get isAvailable() { @@ -155,7 +158,20 @@ export class LiveWallpaperManager { this._busWatchId = bus.connect('message', (_bus, message) => this._onBusMessage(message)); this._applyMute(); - 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._frameCount = 0; + this._noFrameWatchdogId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 4, () => { + this._noFrameWatchdogId = 0; + if (this._active && this._frameCount === 0) { + this._logger.warn( + 'Live wallpaper: no frames received 4s after starting playback. ' + + 'The pipeline likely stalled during caps negotiation or preroll without ' + + 'raising an error — check for GStreamer WARNING messages logged above.'); + } + return GLib.SOURCE_REMOVE; + }); this._setActive(true); this._paused = false; @@ -169,7 +185,7 @@ export class LiveWallpaperManager { _onNewSample(sink) { const Gst = this._Gst; - const sample = sink.emit('pull-sample'); + const sample = sink.pull_sample(); if (!sample) return Gst.FlowReturn.OK; @@ -185,6 +201,11 @@ export class LiveWallpaperManager { try { this._updateFrame(mapInfo.data, width, height); + this._frameCount++; + if (this._frameCount === 1) + this._logger.debug(`Live wallpaper: first frame received (${width}x${height})`); + else if (this._frameCount % 120 === 0) + this._logger.debug(`Live wallpaper: ${this._frameCount} frames rendered so far`); } finally { buffer.unmap(mapInfo); } @@ -221,12 +242,28 @@ export class LiveWallpaperManager { Gst.Format.TIME, Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT, 0); break; case Gst.MessageType.ERROR: { - const [error] = message.parse_error(); - this._logger.error(error, 'Live wallpaper playback error'); + const [error, debug] = message.parse_error(); + this._logger.error(error, `Live wallpaper playback error (${debug ?? 'no debug info'})`); this._stop(); break; } + case Gst.MessageType.WARNING: { + const [warning, debug] = message.parse_warning(); + this._logger.warn(`Live wallpaper GStreamer warning: ${warning.message} (${debug ?? 'no debug info'})`); + break; } + case Gst.MessageType.STATE_CHANGED: + if (message.src === this._playbin) { + const [, newState] = message.parse_state_changed(); + this._logger.debug(`Live wallpaper pipeline state changed to ${this._stateName(newState)}`); + } + break; + } + } + + _stateName(state) { + const Gst = this._Gst; + return Object.keys(Gst.State).find(name => Gst.State[name] === state) ?? String(state); } _applyMute() { @@ -326,6 +363,11 @@ export class LiveWallpaperManager { _stop() { this._disconnectPowerWatches(); + if (this._noFrameWatchdogId) { + GLib.source_remove(this._noFrameWatchdogId); + this._noFrameWatchdogId = 0; + } + if (this._playbin) { const bus = this._playbin.get_bus(); if (this._busWatchId) {