Poll appsink instead of relying on the new-sample signal
The last round of testing ruled out the pipeline construction itself: even with the sink bin rebuilt from explicit, individually-checked link() calls (no thrown errors, playbin reaches PLAYING), the 'new-sample' signal still never fired inside gnome-shell — while an equivalent pipeline worked fine standalone via gst-launch-1.0. The remaining, gnome-shell-specific difference: appsink emits 'new-sample' from GStreamer's own streaming thread, not the main thread. GJS's JS engine isn't safe to call into from an arbitrary background thread, and a cross-thread signal emission can be silently dropped rather than invoked or crashed on — which looks exactly like "the signal never fires" from here, even though the pipeline is actually running. Replaces the signal entirely with polling: a ~30fps GLib.timeout_add() on the main thread calls appsink.try_pull_sample(0), which is explicitly documented as safe to call from any thread since we are now the ones calling into GStreamer rather than the reverse. Also updates the no-frames watchdog to report the poll count instead of a signal fire count. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_019RDqbdjsiisSU7CbQke4g8
This commit is contained in:
+34
-20
@@ -50,7 +50,8 @@ export class LiveWallpaperManager {
|
|||||||
this._upowerSignalId = 0;
|
this._upowerSignalId = 0;
|
||||||
|
|
||||||
this._frameCount = 0;
|
this._frameCount = 0;
|
||||||
this._newSampleCallCount = 0;
|
this._pollCount = 0;
|
||||||
|
this._pollTimeoutId = 0;
|
||||||
this._noFrameWatchdogId = 0;
|
this._noFrameWatchdogId = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -154,7 +155,11 @@ export class LiveWallpaperManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
capsfilter.set_property('caps', Gst.Caps.from_string('video/x-raw,format=RGBA'));
|
capsfilter.set_property('caps', Gst.Caps.from_string('video/x-raw,format=RGBA'));
|
||||||
appsink.set_property('emit-signals', true);
|
// emit-signals is deliberately left off: appsink's 'new-sample' fires
|
||||||
|
// from GStreamer's streaming thread, not gnome-shell's main thread,
|
||||||
|
// and a cross-thread call into the JS engine can be silently dropped
|
||||||
|
// rather than invoked. We poll with try_pull_sample() from a main
|
||||||
|
// thread GLib timer instead, which is safe to call from any thread.
|
||||||
appsink.set_property('max-buffers', 2);
|
appsink.set_property('max-buffers', 2);
|
||||||
appsink.set_property('drop', true);
|
appsink.set_property('drop', true);
|
||||||
appsink.set_property('sync', true);
|
appsink.set_property('sync', true);
|
||||||
@@ -199,7 +204,6 @@ export class LiveWallpaperManager {
|
|||||||
this._layoutActor();
|
this._layoutActor();
|
||||||
|
|
||||||
const sinkBin = this._buildSinkBin();
|
const sinkBin = this._buildSinkBin();
|
||||||
this._appsink.connect('new-sample', sink => this._onNewSample(sink));
|
|
||||||
|
|
||||||
this._playbin = Gst.ElementFactory.make('playbin', 'benthicbloom-live-wallpaper');
|
this._playbin = Gst.ElementFactory.make('playbin', 'benthicbloom-live-wallpaper');
|
||||||
this._playbin.set_property('video-sink', sinkBin);
|
this._playbin.set_property('video-sink', sinkBin);
|
||||||
@@ -214,16 +218,20 @@ export class LiveWallpaperManager {
|
|||||||
this._logger.debug(`playbin.set_state(PLAYING) returned ${stateChangeResult}`);
|
this._logger.debug(`playbin.set_state(PLAYING) returned ${stateChangeResult}`);
|
||||||
|
|
||||||
this._frameCount = 0;
|
this._frameCount = 0;
|
||||||
this._newSampleCallCount = 0;
|
this._pollCount = 0;
|
||||||
|
// ~30fps polling of the appsink from the main thread. See the
|
||||||
|
// comment on appsink's properties above for why this replaces
|
||||||
|
// a 'new-sample' signal handler.
|
||||||
|
this._pollTimeoutId = GLib.timeout_add(GLib.PRIORITY_DEFAULT, 33, () => this._pollForSample());
|
||||||
|
|
||||||
this._noFrameWatchdogId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 4, () => {
|
this._noFrameWatchdogId = GLib.timeout_add_seconds(GLib.PRIORITY_DEFAULT, 4, () => {
|
||||||
this._noFrameWatchdogId = 0;
|
this._noFrameWatchdogId = 0;
|
||||||
if (this._active && this._frameCount === 0) {
|
if (this._active && this._frameCount === 0) {
|
||||||
this._logger.warn(
|
this._logger.warn(
|
||||||
`Live wallpaper: no frames received 4s after starting playback ` +
|
`Live wallpaper: no frames received 4s after starting playback ` +
|
||||||
`(new-sample signal fired ${this._newSampleCallCount} times). ` +
|
`(polled appsink ${this._pollCount} times). If that count is 0, the poll timer ` +
|
||||||
'If that count is 0, the signal itself never fired (check for a stalled ' +
|
'itself never ran; if it is nonzero, try_pull_sample() keeps returning nothing ' +
|
||||||
'pipeline above); if it is nonzero, pull_sample()/buffer processing is failing ' +
|
'— check for a "Failed to poll live wallpaper frame" error above.');
|
||||||
'— check for a "Failed to process live wallpaper frame" error above.');
|
|
||||||
}
|
}
|
||||||
return GLib.SOURCE_REMOVE;
|
return GLib.SOURCE_REMOVE;
|
||||||
});
|
});
|
||||||
@@ -238,18 +246,19 @@ export class LiveWallpaperManager {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
_onNewSample(sink) {
|
_pollForSample() {
|
||||||
|
if (!this._appsink)
|
||||||
|
return GLib.SOURCE_REMOVE;
|
||||||
|
|
||||||
const Gst = this._Gst;
|
const Gst = this._Gst;
|
||||||
this._newSampleCallCount = (this._newSampleCallCount ?? 0) + 1;
|
this._pollCount++;
|
||||||
if (this._newSampleCallCount === 1)
|
if (this._pollCount === 1)
|
||||||
this._logger.debug('Live wallpaper: new-sample signal fired for the first time');
|
this._logger.debug('Live wallpaper: appsink polling started');
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const sample = sink.pull_sample();
|
const sample = this._appsink.try_pull_sample(0);
|
||||||
if (!sample) {
|
if (!sample)
|
||||||
this._logger.debug('Live wallpaper: pull_sample() returned no sample');
|
return GLib.SOURCE_CONTINUE;
|
||||||
return Gst.FlowReturn.OK;
|
|
||||||
}
|
|
||||||
|
|
||||||
const buffer = sample.get_buffer();
|
const buffer = sample.get_buffer();
|
||||||
const structure = sample.get_caps().get_structure(0);
|
const structure = sample.get_caps().get_structure(0);
|
||||||
@@ -259,7 +268,7 @@ export class LiveWallpaperManager {
|
|||||||
const [ok, mapInfo] = buffer.map(Gst.MapFlags.READ);
|
const [ok, mapInfo] = buffer.map(Gst.MapFlags.READ);
|
||||||
if (!ok) {
|
if (!ok) {
|
||||||
this._logger.debug('Live wallpaper: buffer.map() failed');
|
this._logger.debug('Live wallpaper: buffer.map() failed');
|
||||||
return Gst.FlowReturn.OK;
|
return GLib.SOURCE_CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -273,10 +282,10 @@ export class LiveWallpaperManager {
|
|||||||
buffer.unmap(mapInfo);
|
buffer.unmap(mapInfo);
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
this._logger.error(e, 'Failed to process live wallpaper frame (new-sample handler)');
|
this._logger.error(e, 'Failed to poll live wallpaper frame');
|
||||||
}
|
}
|
||||||
|
|
||||||
return Gst.FlowReturn.OK;
|
return GLib.SOURCE_CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
_updateFrame(data, width, height) {
|
_updateFrame(data, width, height) {
|
||||||
@@ -431,6 +440,11 @@ export class LiveWallpaperManager {
|
|||||||
this._noFrameWatchdogId = 0;
|
this._noFrameWatchdogId = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this._pollTimeoutId) {
|
||||||
|
GLib.source_remove(this._pollTimeoutId);
|
||||||
|
this._pollTimeoutId = 0;
|
||||||
|
}
|
||||||
|
|
||||||
if (this._playbin) {
|
if (this._playbin) {
|
||||||
const Gst = this._Gst;
|
const Gst = this._Gst;
|
||||||
const bus = this._playbin.get_bus();
|
const bus = this._playbin.get_bus();
|
||||||
|
|||||||
Reference in New Issue
Block a user