# webrtc-streamer: turning RTSP, V4L2 and screen capture into a browser WebRTC stream

> mpromonet/webrtc-streamer is a C++ signalling server that republishes RTSP, RTMP, MKV, V4L2 and desktop capture sources as WebRTC. Here is how it works, how to start it with Docker, and where it stops being the right tool.

**mpromonet/webrtc-streamer** — WebRTC streamer for V4L2 capture devices, RTSP sources and Screen Capture

- Repository: https://github.com/mpromonet/webrtc-streamer
- Website: https://webrtcstreamer.agreeabletree-365b9a90.canadacentral.azurecontainerapps.io/?layout=2x2
- Stars: 3,585 · Forks: 678
- Language: C++
- License: Unlicense
- Published: 2026-09-23 · Updated: 2026-09-23 · Language: en
- Canonical page: https://hysenlabs.com/projects/mpromonet-webrtc-streamer

## The gap webrtc-streamer fills: browsers cannot open an RTSP URL

A browser has no RTSP client and no way to read a V4L2 device. Cameras, DVRs and capture cards speak RTSP, RTMP or a raw device node, so putting one on a web page normally means transcoding it into HLS or DASH first. That adds latency and a segmenter. webrtc-streamer takes the other route: it terminates the source protocol itself and republishes the media over WebRTC, so the browser gets a peer connection instead of a playlist.

The project describes itself as an experimentation, and the README is explicit that it is a signalling mechanism rather than a full platform. The audience that follows from that is narrow and practical: people integrating an existing camera or capture device into a web UI, running on Linux, Windows or macOS, who do not want to build a media pipeline from scratch. The repository layout backs this up. There is a src/ tree, a civetweb submodule for the HTTP server, live555helper for RTSP and MKV, libv4l2cpp for capture devices, and prometheus-cpp plus a grafana/ directory for metrics. It is a single binary with an HTTP control surface, not a framework.

## How the signalling and media path actually work

The binary starts an HTTP server, by default bound to 0.0.0.0:8000, and that server is the signalling channel. A client asks for a stream by name, and the name determines how the source is opened. The README lists the scheme mapping directly: an rtsp:// URL goes through a live555-based RTSP capturer, file:// goes through an MKV capturer, rmtp:// through a librmtp capturer, screen:// and window:// through webrtc::DesktopCapturer, v4l2:// through a V4L2 capturer, and videocap:// and audiocap:// through device-name capture. An alias registered with -n and -u resolves to the same mechanism.

Once the source is open, the frames are pushed into a WebRTC peer connection. The number of simultaneous connections is capped by -m. ICE candidates are gathered by the built-in STUN or TURN options, or by an external server passed with -s or -t. The UDP port range is pinned with -R, which matters more than it looks: the docker-compose.yml in the repository publishes 50000-50010/udp alongside port 8000, and passes -R 50000:50010 and -e host.docker.internal so that candidates advertise a reachable address from inside a container.

There is a second interface. The README states the project is compatible with WHEP, the draft WebRTC HTTP ingest protocol, which means a client that speaks WHEP can negotiate without the project's own JavaScript. The HTML helper, webrtcstreamer.html, lives in a separate repository and takes the stream name as a query parameter, so a page can be built by passing either an alias like Bunny or a full rtsp:// URL.

The interesting design choice is the -o flag. It uses webrtc::VideoFrameBuffer::Type::kNative to carry already-encoded H264 frames from a V4L2 device or an RTSP stream straight through, with the README noting it "uses less CPU, but has less features (resize, codec, and bandwidth are disabled)". That is a real trade: you keep the camera's own encoder and lose the server's ability to adapt the stream.

## Installing webrtc-streamer and streaming your first RTSP source

The README points at two distribution channels: binary packages on the GitHub releases page, and container images on Docker Hub under mpromonet/webrtc-streamer. Building from source is possible, and the repository Dockerfile shows what that costs: it installs depot_tools, runs fetch --nohooks webrtc, and builds against a full WebRTC checkout before compiling the project with cmake and make. That is a heavy build, so the container image is the sane starting point.

The compose file in the repository is the shortest path to a running instance. It sets the command, the UDP range and the extra host entry:

```yaml
services:
  webrtc-streamer:
    image: mpromonet/webrtc-streamer:latest
    extra_hosts:
    - "host.docker.internal:host-gateway"
    command: ["-C", "config.json", "-R", "50000:50010", "-e", "host.docker.internal"]
    ports:
      - 8000:8000
      - 50000-50010:50000-50010/udp
```

Bring it up with docker compose up and the HTTP server answers on port 8000. If you would rather skip the config file, the README's own example starts the binary with a JSON config:

```bash
./webrtc-streamer -C config.json
```

The config file registers named streams so a page can request an alias instead of a URL. To try a public RTSP source without touching the config, pass the URL as a stream name through the HTML helper, as the README does with a Wowza demo stream:

```bash
./webrtc-streamer -H 0.0.0.0:8000 -R 50000:50010
```

Then open webrtcstreamer.html with the source as the query string, for example webrtcstreamer.html?rtsp://wowzaec2demo.streamlock.net/vod/mp4:BigBuckBunny_115k.mov. What you should see is a video element playing the source over a peer connection, with the negotiation handled by the page's JavaScript. If nothing appears, check the UDP range before anything else: WebRTC will not fall back to TCP just because the port is closed.

For a grid of several streams, the README documents a layout parameter in the form layout=<lines>x<columns>, and the project's own demo page uses layout=2x2.

## Where the design bites: UDP ranges, CPU and the null codec trade

The most common failure is not a code bug. It is a network one. WebRTC needs the media to arrive on the UDP ports the server advertised, and -R fixes that range. Behind Docker, behind NAT, or behind a firewall that only allows 443, the peer connection will negotiate and then stay black. The -e flag exists precisely for this, adding an extra ICE candidate address for Docker and NAT setups, and the compose file uses host.docker.internal for it. If you cannot open a UDP range at all, the answer is a TURN relay via -t or the embedded TURN server started with -T, which moves the traffic to a port you control.

The second constraint is CPU. Every peer connection that is not using -o has to decode and re-encode, and -m caps how many can exist at once. The -o null codec path avoids that work but the README is blunt about the cost: resize, codec and bandwidth control are disabled. For a fixed-resolution camera on a LAN that is a good deal. For a browser on a mobile connection that needs the server to drop resolution under congestion, it is the wrong setting.

Platform support is uneven in ways worth knowing before you commit. The README marks the v4l2:// scheme as not supported on Windows, which is expected since V4L2 is a Linux kernel interface. The repository carries separate Dockerfiles for arm64, Raspberry Pi and Windows, which suggests those targets are maintained but not identical. And the README carries a notice that the live demos are stopped while the author migrates to a European web hosting provider, so the hosted demo may not be available when you try it.

## How it compares with go2rtc and MediaMTX

The closest alternatives in the same space are go2rtc and MediaMTX. The difference is the centre of gravity. webrtc-streamer is a C++ binary built directly on the WebRTC native stack, and its distinguishing feature is the breadth of local capture inputs: V4L2 devices, screen and window capture, audio capture device names, plus RTSP, RTMP and MKV files. If your source is a capture card or a desktop, that list is the reason to pick it.

go2rtc and MediaMTX are written in Go and approach the problem from the protocol side. They are strong at ingesting many RTSP and RTMP streams and republishing them in several formats at once, and they are typically easier to deploy as a single static binary without a WebRTC checkout. What they do not offer is the same direct access to Linux capture devices and desktop capture through the WebRTC native desktop capturer API.

So the choice is not quality, it is input shape. Camera-only deployments with a dozen RTSP feeds are usually better served by the Go tools. A rack with a V4L2 capture card, or a machine that needs to share its own screen into a WebRTC room, is where webrtc-streamer's scheme list pays off. Note also that the project ships joinjanusvideoroom.js and joinxmpproom.js with strophe.js and strophe.jingle dependencies in package.json, so there is an existing path into Janus and XMPP rooms if that is your signalling environment.

## Licence, releases and what upgrades cost you

The project is released under the Unlicense, which is a public-domain dedication rather than a permissive licence with attribution terms. For most commercial use that removes the usual licence review step, but it also means there is no warranty and no contributor patent grant of the kind Apache 2.0 provides. If your legal team cares about patent language, that difference is worth raising with them rather than assuming; this is a description of the licence text, not advice.

The dependency situation is the real cost. The Dockerfile pulls a full WebRTC checkout via depot_tools and builds against it, and the repository carries civetweb, live555helper, libv4l2cpp and prometheus-cpp as submodules. That means an upgrade is not a package bump. Moving to a newer release can mean a newer WebRTC revision underneath, which changes the native API the code compiles against. The release history shows this cadence: v0.8.15 in February 2026, v0.8.19 in July 2026, v0.8.20 in August 2026. Frequent patch releases are a good sign for fixes, but each one is a rebuild, not a drop-in swap. If you deploy the container image, pin a tag rather than tracking latest, because the underlying WebRTC revision can move between them.

On maintenance: the repository is not archived and the last push was on 2026-09-23, so the codebase is being touched. That is a statement about commit activity, not a promise about any particular feature.

## Conclusion

Adopt webrtc-streamer when you have RTSP or V4L2 sources that must appear in a browser without rewriting them, and you can open a UDP port range and run a TURN server. Do not adopt it if you need an SDK inside your own process, or if the source is a plain HTTP camera with no RTSP endpoint. Before rolling it out, verify that the -R range you choose survives your NAT and that the machine has enough CPU for the number of peer connections you expect, since the -o null codec path trades resize, codec and bandwidth control away.

## FAQ

### What are the downsides of using WebRTC?

In this project the documented downsides are network and CPU. Media arrives on the UDP range set by -R, so a blocked range or NAT leaves the peer connection black until you add a TURN relay with -t or -T, and the -o null codec path that saves CPU disables resize, codec and bandwidth control.

### Is WebRTC a security risk?

The README does not analyse WebRTC's security model. What it documents on this front is access control for the HTTP server: -A takes a password file and -D sets the authentication domain, and -X disables the X-Frame-Options header. A separate SECURITY.md file exists at the repository root.

### Is WebRTC free or paid?

The repository is released under the Unlicense, a public-domain dedication, and its packages and container images are published publicly. The README describes no pricing tier or paid edition.

### Which apps use WebRTC?

The README does not list applications that use WebRTC. It does show how this project connects to other WebRTC environments: the repository ships joinjanusvideoroom.js and joinxmpproom.js, with strophe.js and strophe.jingle listed as dependencies in package.json.

## Sources

- [License: Unlicense](https://github.com/mpromonet/webrtc-streamer/blob/master/LICENSE)
- [mpromonet/webrtc-streamer on GitHub](https://github.com/mpromonet/webrtc-streamer)
- [Project website](https://webrtcstreamer.agreeabletree-365b9a90.canadacentral.azurecontainerapps.io/?layout=2x2)
- [README](https://github.com/mpromonet/webrtc-streamer/blob/master/README.md)
- [Releases](https://github.com/mpromonet/webrtc-streamer/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/mpromonet-webrtc-streamer
