Skip to content

WebRTC Video

A live webcam preview built on navigator.mediaDevices.getUserMedia({ video: true }), with the resulting MediaStream assigned to a <video> element’s srcObject.

That single assignment is all the platform-specific code there is. In a browser it lights up a real <video>. On GJS it flows through @gjsify/video’s VideoBridge into a GTK 4 Gtk.Picture fed by GStreamer’s gtk4paintablesink. Live media crosses from capture to display without your code knowing which platform it is on.

The shared module is about ten lines:

TypeScript
export async function startVideo(video: HTMLVideoElement, log: LogFn = console.log): Promise<MediaStream> {
log('Requesting webcam...');
const stream = await navigator.mediaDevices.getUserMedia({ video: true, audio: false });
log(`Got MediaStream with ${stream.getVideoTracks().length} video track(s)`);
video.srcObject = stream;
log('Video playing');
return stream;
}

This showcase is not in the gjsify showcase catalog. It needs a camera and an extra GStreamer plugin, so it runs from a checkout of the gjsify repo instead.

You need:

  • GJS 1.86 with GTK 4.
  • gtk4paintablesink from gst-plugins-rs: sudo dnf install gstreamer1-plugin-gtk4 on Fedora, sudo apt install gstreamer1.0-gtk4 on Ubuntu and Debian. Confirm with gst-inspect-1.0 gtk4paintablesink.
  • A webcam, or a virtual device such as v4l2loopback, plus PipeWire or PulseAudio.

gjsify system-check reports GJS and the optional GStreamer pieces.

It builds two bundles, --app gjs and --app browser, and no --app node one, because the capture side is a GStreamer pipeline reached through a typelib. From showcases/dom/webrtc-video/:

Terminal
gjsify run build # gjs + browser bundles and the assets
gjsify run start # opens the GTK 4 window

A GTK 4 window opens with an Adw.ToolbarView whose body is the VideoBridge widget. Once getUserMedia resolves, the stream is handed to the bridge, the GStreamer pipeline goes to PLAYING, and the picture appears. Closing the window quits the main loop and the process exits, which frees the camera.

@gjsify/webrtc picks a capture source by trying pipewiresrc, then v4l2src, then autovideosrc. If none of them exist it falls back to videotestsrc, so SMPTE colour bars instead of your face means no real device was found.

Terminal
gjsify run start:browser # serves dist/ on localhost:8080

The browser build uses the browser’s own getUserMedia and a real <video> element.

To embed it in a page of your own, import mount() from src/browser/browser.ts. The package is private and not published to npm, so there is no @gjsify/example-dom-webrtc-video/browser specifier to import. Copy the file, or point at your checkout.

TypeScript
const handle = mount(container, { title: 'Webcam' });
handle.pause(); // stop status updates, keep the stream running
handle.resume();
handle.stop(); // release the webcam and remove the DOM
PackageWhat it needs
@gjsify/webrtcnavigator.mediaDevices.getUserMedia, MediaStream, MediaStreamTrack, and the GStreamer capture source behind them
@gjsify/videoHTMLVideoElement.srcObject, and VideoBridge rendering into a Gtk.Picture
@gjsify/webrtc-nativeThe Vala bridge that moves GStreamer’s streaming-thread callbacks onto the GLib main context
gst-1.0, gtk4paintablesinkCapture and paintable rendering on GJS
LibadwaitaAdw.ApplicationWindow, Adw.HeaderBar and Adw.ToolbarView around the bridge

In a longer-lived app, call track.stop() on each track when you are done with the preview. That releases the GStreamer source so a later getUserMedia call can acquire the camera again. The showcase gets away without it because closing its window ends the process; the browser build’s handle.stop() shows the explicit version.

showcases/dom/webrtc-video/

  • src/video-demo.ts is startVideo(video, log), shown above. No platform imports.
  • src/gjs/gjs.ts is the Adwaita window wiring the VideoBridge.
  • src/browser/browser.ts exports mount(); browser-main.ts runs it full-page.
  • webrtc-loopback is the data half of the same stack: peer connections and a data channel, no media.
  • Bridge widgets shows how VideoBridge ties a <video> element to a GTK 4 widget, alongside the canvas, WebGL and iframe bridges.
  • Web API packages lists what @gjsify/webrtc covers.