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 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019RDqbdjsiisSU7CbQke4g8
This commit is contained in:
Claude
2026-08-04 00:50:24 +00:00
parent f7c05c2c76
commit 2688568fac
+46 -4
View File
@@ -48,6 +48,9 @@ export class LiveWallpaperManager {
this._fullscreenChangedId = 0; this._fullscreenChangedId = 0;
this._upowerProxy = null; this._upowerProxy = null;
this._upowerSignalId = 0; this._upowerSignalId = 0;
this._frameCount = 0;
this._noFrameWatchdogId = 0;
} }
get isAvailable() { get isAvailable() {
@@ -155,7 +158,20 @@ export class LiveWallpaperManager {
this._busWatchId = bus.connect('message', (_bus, message) => this._onBusMessage(message)); this._busWatchId = bus.connect('message', (_bus, message) => this._onBusMessage(message));
this._applyMute(); 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._setActive(true);
this._paused = false; this._paused = false;
@@ -169,7 +185,7 @@ export class LiveWallpaperManager {
_onNewSample(sink) { _onNewSample(sink) {
const Gst = this._Gst; const Gst = this._Gst;
const sample = sink.emit('pull-sample'); const sample = sink.pull_sample();
if (!sample) if (!sample)
return Gst.FlowReturn.OK; return Gst.FlowReturn.OK;
@@ -185,6 +201,11 @@ export class LiveWallpaperManager {
try { try {
this._updateFrame(mapInfo.data, width, height); 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 { } finally {
buffer.unmap(mapInfo); buffer.unmap(mapInfo);
} }
@@ -221,12 +242,28 @@ export class LiveWallpaperManager {
Gst.Format.TIME, Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT, 0); Gst.Format.TIME, Gst.SeekFlags.FLUSH | Gst.SeekFlags.KEY_UNIT, 0);
break; break;
case Gst.MessageType.ERROR: { case Gst.MessageType.ERROR: {
const [error] = message.parse_error(); const [error, debug] = message.parse_error();
this._logger.error(error, 'Live wallpaper playback error'); this._logger.error(error, `Live wallpaper playback error (${debug ?? 'no debug info'})`);
this._stop(); this._stop();
break; 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() { _applyMute() {
@@ -326,6 +363,11 @@ export class LiveWallpaperManager {
_stop() { _stop() {
this._disconnectPowerWatches(); this._disconnectPowerWatches();
if (this._noFrameWatchdogId) {
GLib.source_remove(this._noFrameWatchdogId);
this._noFrameWatchdogId = 0;
}
if (this._playbin) { if (this._playbin) {
const bus = this._playbin.get_bus(); const bus = this._playbin.get_bus();
if (this._busWatchId) { if (this._busWatchId) {