Segfault when connecting to GStreamer Playbin signals from JS code
There seems to be a segfault-inducing bug with the GStreamer bindings. This segfault occurs every time I connect to any signal of the Playbin element, when that specific signal is being emitted.
Based on studying example code for other languages, I don't see much wrong in my approach.
I am running on Arch Linux with latest updates from their testing
repo included.
GJS version is reported as 2:1.64.2-1
.
Basic proof-of-concept app is provided as an attachment. It should run by simply gjs app.js
.
Attachments
Segfault message
[1] 40628 segmentation fault (core dumped) ./app.js
How the app should work
- File starts playing.
- After one second, duration of the song is queried.
- After three seconds, the player skips to two seconds before the end of the song.
- Wait two seconds, the song will end.
- There should be
PLAYBACK STOPPED
printed into the terminal.
What goes wrong
Instead of PLAYBACK STOPPED
being printed, there is an error message reporting a segfault.
What seems to be the issue
When we comment out lines 43-45 (connecting to "about-to-finish"), the application will not crash at the end but will instead finish successfully.
In the same code block there are a couple other signal connections commented out. If these connections are enabled, the app will crash when those signals are being emitted in the early phase of the playback.
#!env gjs
/*
* Define some audio file to play.
*/
const DEMO_AUDIO_FILE = 'file://path/to/demo/audio.flac'
imports.package.require({
GLib: '2.0',
Gtk: '3.0',
Gst: '1.0'
})
const { Gio, Gst, Gtk } = imports.gi
const Mainloop = imports.mainloop
imports.package.run({
main (argv) {
Gtk.init(null)
Gst.init(null)
const app = new Gtk.Application({
application_id: 'foo.bar.crash-me',
flags: Gio.ApplicationFlags.FLAGS_NONE
})
app.connect('activate', () => {
/*
* Create a hidden window to keep the event loop spinning.
*/
const window = new Gtk.ApplicationWindow()
window.set_application(app)
const playbin = Gst.ElementFactory.make('playbin', null)
{{{{{
/*
* NOTE: Connecting to any signal of the playbin will cause a segfault
* once that signal is emitted.
*/
// Comment this out and the playback will finish successfully.
playbin.connect('about-to-finish', () => {
log('ABOUT TO FINISH')
})
// playbin.connect('audio-tags-changed', () => {
// log('AUDIO TAGS CHANGED')
// })
//
// playbin.connect('audio-changed', () => {
// log('AUDIO CHANGED')
// })
}}}}}
/*
* Note that it's possible to connect to bus signals without crashing...
*/
playbin.get_bus().connect('message', (sender, message) => {
if (message && message.type === Gst.MessageType.EOS) {
/*
* This message should be printed when the song has finished playing.
*/
log('PLAYBACK STOPPED')
}
})
/**
* Enables the above signal listener.
*/
playbin.get_bus().add_signal_watch()
playbin.set_property('uri', DEMO_AUDIO_FILE)
playbin.set_state(Gst.State.PLAYING)
log('START AUDIO')
let SONG_DURATION = null
Mainloop.timeout_add(1000, () => {
const [ok, duration] = playbin.query_duration(Gst.Format.TIME)
SONG_DURATION = duration
log('GOT DURATION: ' + duration)
})
Mainloop.timeout_add(3000, () => {
const seekTo = SONG_DURATION - Math.pow(10, 6) * 2
log(`SEEK TO END: ${seekTo} / ${SONG_DURATION}`)
/*
* Seek near the end and the "about-to-finish" signal will emit soon.
*/
playbin.seek_simple(Gst.Format.TIME, Gst.SeekFlags.FLUSH | Gst.SeekFlags.TRICKMODE, seekTo)
})
/*
* Avoid garbage collection
*/
app.window = window
app.playbin = playbin
})
return app.run(argv)
}
})