A WebRTC fan-out demo built with Next.js, GStreamer and LiveKit. It shows how to inject external video into the browser via WebRTC, supporting N producers and N consumers.
This is a fork of webrtc-grid-demo. The original wires every peer together in a WebRTC mesh; this version instead uses LiveKit as an SFU (Selective Forwarding Unit). My goal was to learn LiveKit.
- No signaling server. LiveKit handles signaling, ICE and the media routing, so the agnostic socket.io server is gone.
gst-producergot much simpler. The mesh version had to spin up awebrtcbinbranch and renegotiate for each consumer that connected. With an SFU the producer publishes a single track once; the SFU fans it out to every consumer. No per-consumer branches, notee, no dynamic linking.- Still N producers and N consumers. But now thanks to the SFU instead of a full mesh.
- Consumer: initial screen for consumers (
/). Subscribes to the room and renders every published camera track in a grid. - Producer (
/producer): open in another tab to publish a browser webcam into the room.
Both roles join the same LiveKit room and fetch their access token from the LiveKit token server.
Python process using GStreamer and the LiveKit Python SDK (livekit==1.1.9) to publish a synthetic video track into the room.
The LiveKit Python SDK at this version does not accept already-encoded payloads, so the pipeline produces raw RGBA frames and pushes them as rtc.VideoFrame into a rtc.VideoSource. LiveKit then handles encoding and publishing. GStreamer runs on a GLib.MainLoop while the LiveKit room runs on an asyncio loop in a separate thread.
docker-compose.yml starts three of these, each with a different videotestsrc pattern (ball, smpte, spokes) to populate the consumer grid.
Both services get their access tokens from a LiveKit Cloud project's token server. Enable it in the dashboard under Settings → Options → Token server and copy its ID into the variables below.
Both services then read their LiveKit connection details from environment files. Create them before running:
gst-producer/.env
LIVEKIT_URL=wss://<your-project>.livekit.cloud
LIVEKIT_TOKEN_SERVER_ID=<your-token-server-id>web/.env
NEXT_PUBLIC_LIVEKIT_URL=wss://<your-project>.livekit.cloud
NEXT_PUBLIC_LIVEKIT_TOKEN_SERVER_ID=<your-token-server-id>docker compose build
docker compose upThen open http://localhost:3000 for the consumer grid and http://localhost:3000/producer for the browser producer.

