Self-hosted live game streaming

Sub-500 ms
glass to glass,
in a browser tab.

A broadcaster shares their screen. Viewers join with a 6-character code and watch — no accounts, no plugins, no native app required. Roughly 50 ms measured on a hardware encode/decode path.

WebTransport + WebCodecs · Go relay · Apache-2.0 · Chromium-first

The pipeline

screen → encode → packets → relay → decode → screen

01
Capture
The broadcaster's screen, grabbed by the browser or by a native app
02
Encode
Turned into video by the graphics card, not the CPU
03
Split
Chopped into packets small enough to never queue up
04
Relay
One Go server copies the packets out to every viewer
05
Reassemble
The viewer puts them back in order and skips what got lost
06
Show
Decoded and drawn straight onto the page, newest frame first

Drops beat stalls

If a packet goes missing you lose one frame, which you won't notice. You never get a spinner or a stream that quietly falls behind. The frames that matter most travel on a guaranteed channel.

The server never touches the video

It copies packets, nothing more — no re-encoding. It keeps the last full frame handy, so anyone who joins late sees a picture right away.

Why not WebRTC?

A deliberate choice. Building lower down means owning the whole path instead of inheriting a heavy stack, and it was a real chance to use newer browser APIs. The trade-offs are written down in the docs.

The viewer decides what to trade

Speed and reliability pull against each other, and the right answer depends on the connection you're on. So it isn't baked in: every viewer picks, from the player's own settings, and can switch mid-stream.

Lowest latency

Live edge

Stay as close to now as the network allows. Anything that arrives late is skipped rather than waited for. Best on a good connection, and the default.

Guaranteed delivery

Deep buffer

Sit a little further back and let the server re-send whatever went missing. You give up some latency and get a picture that holds together on flaky Wi-Fi or mobile.

Both modes are one control in the player, not a deployment setting. In between there are opt-ins that buy a bit of resilience for almost no delay — extra recovery data sent alongside the video, and the stream spread over several connections so one bad path can't take the picture down.

Any server, any viewer

Viewers land on whichever server is free. If that one isn't hosting the broadcast, it fetches the stream from the one that is. Add servers and the audience grows with them — hundreds of broadcasts at once, and around a thousand people on one popular stream.

Broadcasters
Browser · WebCodecs
gawk-broadcast · Linux, GPU
gawk-broadcast.exe · Windows, GPU
Relay servers · Go
Load balancer
hosting server
other server
fetches from the host
Viewers
Browser tab · joined by code
~1,000 on one stream

How fast is it, really

≈50ms

From the broadcaster's screen to the viewer's, measured on machines where the graphics card does the video work. The target is anything under 500 ms. If either end falls back to doing it in software it gets much slower — we've seen up to 2.5 seconds. That's what decides the number, more than the network.

Both ends sync their clocks over the connection, so this is measured rather than estimated. How it was measured is written up in the docs.

Order of magnitude
gawk on a graphics card≈50 ms
The target< 500 ms
Falling back to softwareup to ≈2.5 s

All three numbers are our own measurements. We haven't put gawk up against other streaming stacks, so there's no comparison here.

Run it

gawk.ioio.fi runs exactly this. The repository has everything you need to stand up your own.

Try it in five minutes
$ git clone https://github.com/Tuhis/gawk && cd gawk
$ docker compose up

Open localhost:8080/#/broadcast, pick a screen, and you get a 6-character code. Open the join link in a second tab and it connects. No browser flags to set, and the code still works after a restart. You need Docker and a Chromium browser.

Host it for real
$ helm upgrade --install gawk-server \
    oci://ghcr.io/tuhis/charts/gawk-server \
    --version <X.Y.Z> -n gawk -f relay-values.yaml
$ helm upgrade --install gawk-app \
    oci://ghcr.io/tuhis/charts/gawk-app \
    --version <X.Y.Z> -n gawk -f app-values.yaml

Two charts, two images, versions always in step. Read the self-hosting guide first: your DNS and certificates decide whether it works at all, because this is fussier about them than a normal website. The compose stack above is for trying things out, not for production.

Get the app

Broadcasting doesn't have to happen in a browser tab. The native apps use your graphics card directly, can share one window along with just that app's audio, and on Linux they're the only way to broadcast with hardware encoding. Viewers still need nothing but a browser.

Linux

x86-64 · tar.gz

Latest release

Download for Linux

Unpack it and run gawk-broadcast-gui. Needs glibc 2.34 or newer, a Wayland session and a hardware H.264 encoder; the INSTALL.md inside has the checklist and the list of shared libraries.

Windows

x86-64 · portable .exe

Latest release

Download for Windows

One file, nothing to install. It's unsigned, so SmartScreen will object the first time: More info → Run anyway, or untick Unblock in the file's properties. Needs Windows 10 or newer and a hardware H.264 encoder.

The Linux and Windows binaries aren't signed, so for them the checksum is the only integrity check you get; the Mac app is signed and notarized by Apple. Each release also ships a SHA256SUMS file: sha256sum -c SHA256SUMS on Linux, Get-FileHash on Windows, shasum -a 256 -c SHA256SUMS on a Mac. Older versions and release notes are on the releases page.

Hosting a LAN party? The experimental party relay keeps the party's streams off the venue's internet line.

Browser support

Chromium first, and if a browser can't join, the app says so on load instead of failing quietly.

EngineViewingNotes
Chromium Full The main target. Watching and broadcasting, everything on.
Firefox Works Works. A few things take a different route internally.
WebKitSafari everywhere, all iOS browsers Works Viewers join on macOS and iOS. The iPhone takes its own playback route, written up in docs/27.

Broadcasting doesn't have to happen in a browser at all: the native apps for Linux and Windows get more out of your graphics card, and on Linux they're the way to broadcast, since browsers can't use the graphics card to encode there. The design notes explain why.

Also in the box

Sound

Your whole system's audio, or just one app's, kept in step with the picture.

Holds up on bad Wi-Fi

Extra recovery data, a re-send buffer on the server, and the option to spread the stream over several connections.

Diagnostics

Per-session stats and a dashboard, for when you need to know why a stream misbehaved.

Moderation

Kill a broadcast across every server, and ban by an ID or an IP address.

Metrics

Prometheus metrics on a private port, plus a stats overlay on both ends you can copy in one click.

Tested in a real browser

Every change runs a headless Chrome that actually decodes relayed video, plus a two-server cluster test.

Every decision is written down

For each piece of work there's a document: what was decided, what was rejected, and why. Start with how a frame gets from one screen to another.

It's also the fastest way to bring an AI up to speed — the whole design is spelled out in prose, which is far easier to absorb than reading the code.