Free, no-extra-hardware live view of your Blackmagic PYXIS 6K in any browser over plain Ethernet — no ATEM Streaming Bridge needed. A plug-and-play system that turns the camera into a sub-half-second live view in your browser
- WebRTC primary — camera SRT →
mediaMTX→ browser, ~0.2–0.4s glass-to-glass - fMP4/MSE fallback — automatic when WebRTC can't be used (~0.4–0.8s)
- Pure passthrough on the low-latency path — no re-encode, no fragment buffering
- No hardware decoders, no ATEM boxes, no cloud — just the camera, a PC and a browser (Chrome/Edge)
What this replaces: Blackmagic's official route for on-set monitoring is an ATEM Streaming Bridge box. This project does the same job entirely in software on a Windows PC you already own — free, no purchase, no extra hardware.
┌──────────┐ SRT caller ┌──────────┐ ffmpeg -f whip ┌──────────┐ WHEP ┌─────────┐
│ PYXIS │ ──────────────────► │ PC app │ ────────────────► │ mediaMTX │ ────────► │ Browser │
│ 6K │ 192.168.x.x:9000 │ (Node) │ (H.264 copy) │ :8889 │ │ video │
└──────────┘ └──────────┘ └──────────┘ └─────────┘
│ └─ fMP4 pipe:1 ── WS relay ──► MSE fallback
└─ camera REST proxy (/cam/*)
What you get: a web page at http://localhost:9090 that live-views your PYXIS over
the LAN — no internet, no cloud, no SDKs. The app manages ffmpeg + mediaMTX for you,
auto-respawns either if it crashes, and force-restarts ffmpeg if it silently wedges on a
cold start (WHIP vs. just-booted mediaMTX race). Other devices on your network can open the
same page (see Multiple viewers below).
-
Install ffmpeg (>= 8.x, needs the native
whipmuxer and libsrt):winget install Gyan.FFmpeg— or download from gyan.dev and make sureffmpegis on PATH. -
Download mediaMTX v1.20.x (single exe) from github.com/bluenviron/mediamtx/releases and place it at
mediamtx/mediamtx.exe(it sits next tomediamtx.yml).mediamtx.ymlis verified against v1.20.x; newer major releases may rename config keys — if mediaMTX fails to start, check its changelog before upgrading. -
Edit
mediamtx/mediamtx.yml→webrtcAdditionalHostsis empty by default (auto-detected from your PC's NIC). If browser clients can't connect, set your PC's LAN IP, e.g.[192.168.1.50]. -
Install the app:
npm install -
Run it — double-click
Start-PyxisLive.cmd(it asks for the camera IP + login once, remembers them on your PC, opens the page in Chrome by itself — the stream needs Chrome's WebRTC, not your default browser; keep the launcher's window open while you use the app). Or from a terminal:$env:CAMERA="PUT_YOUR_CAMERA_IP" # your camera's IP — this is required npm start -
Open the page in Chrome or Edge:
http://localhost:9090 -
Configure the camera once (next section), then start streaming on the camera — the page auto-opens the SRT listener and the picture appears on its own.
The app auto-starts mediaMTX on boot and auto-respawns the SRT listener if the camera drops — no other services to babysit.
The app never starts or stops the camera. After the platform XML is uploaded and selected, you must press Start/Stream on the camera yourself. Once streaming, the camera's SRT caller reconnects on its own if the PC listener restarts (see docs/TROUBLESHOOTING.md).
The camera's livestream settings are a "custom platform" XML file. A ready one ships in this
repo as streaming.xml ("Pyxis Local SRT", 1080p24, H.264, 16 Mbps, SRT caller to your PC).
- Power the camera, connect it to the same network as the PC, note its IP.
- Edit the SRT target in
streaming.xml— the<url>valuesrt://PUT_YOUR_PC_IP:9000?mode=callermust point at the PC running this app (keep:9000unless you changedSRT_PORT). - Set
fpsin the<config>line to whatever frame rate you normally stream at (fps="24",fps="30", …) — the camera caps the live stream to that rate. - Upload the platform with curl (self-signed HTTPS cert is expected) — replace the
placeholders first (
PUT_YOUR_CAMERA_USER/PUT_YOUR_CAMERA_PASS= your camera's REST login,PUT_YOUR_CAMERA_IP= your camera's address):curl.exe -ku PUT_YOUR_CAMERA_USER:PUT_YOUR_CAMERA_PASS -X PUT -H "Content-Type: application/xml" --data-binary "@streaming.xml" \ "https://PUT_YOUR_CAMERA_IP/control/api/v1/livestreams/customPlatforms/Pyxis_SRT.xml" - On the camera: Setup → Live Streaming → Platform → "Pyxis Local SRT", ensure the profile "Streaming High" / server "Primary" is selected (or just follow the camera's on-screen streaming flow and pick your custom platform).
- Open the page in Chrome or Edge — it auto-opens the SRT listener on this PC, and the browser view goes live on its own as soon as the camera is streaming.
Set CAMERA_USER / CAMERA_PASS to your own camera's REST credentials — replace the
placeholders (PUT_YOUR_CAMERA_USER, PUT_YOUR_CAMERA_PASS, PUT_YOUR_CAMERA_IP) below with
your actual values. The PYXIS REST API has no documented factory-default password (it
needs the web media manager enabled in Blackmagic Camera Setup, and authentication is whatever
the owner has configured — so use the login YOU set on your camera, not anything written here).
| Env var | Default | Purpose |
|---|---|---|
PORT |
9090 |
Web page / REST API / WS relay |
CAMERA |
(required — set it) | Camera IP |
CAMERA_USER |
(required — set it) | Camera REST user |
CAMERA_PASS |
(required — set it) | Camera REST password |
FFMPEG |
ffmpeg |
ffmpeg binary (needs whip muxer) |
MEDIAMTX |
mediamtx/mediamtx.exe |
mediaMTX binary |
SRT_PORT |
9000 |
SRT listener port (camera's caller URL target) |
MY_IP |
(auto-detected) | PC address advertised to the camera + viewers. Auto-detect skips VPN/tunnel adapters and prefers a 192.168/172.16 LAN address; override if you need a specific NIC (e.g. 192.168.1.50) |
CAMERA_CONTROL_DIR |
(unset) | Dev-only: local folder with the camera's documentation.html + spec YAMLs, so the Camera tab's discovery runs without the camera on the network |
| Port | Protocol | Purpose |
|---|---|---|
9090 |
TCP | page, REST API, WS relay |
9000 |
SRT | camera → PC ingest |
8889 |
TCP | mediaMTX HTTP (WHIP in / WHEP out) |
8189 |
UDP | WebRTC media plane |
Same-PC viewing needs no firewall changes. The first time you run the app, Windows
Firewall pops a security alert for each of node.exe, ffmpeg.exe and mediamtx.exe —
accept ("Allow access") each prompt and LAN viewers are covered automatically, no
per-port rules needed. You'll get the same prompts for the camera's SRT ingest on 9000.
If the prompts were declined (or firewall rules were removed), LAN viewers then need these inbound rules in Windows Firewall:
| Port | Why a viewer needs it |
|---|---|
9090 |
page, REST API, WS relay |
8889 |
mediaMTX WHEP handshake |
8189 |
WebRTC media (UDP) |
9000 (SRT) is only ever used by the camera connecting to this PC, never by browser
viewers.
The same page runs on any device on your network — a desktop, a laptop on WiFi, a phone — all watching the PYXIS at once. Each browser runs its own WebRTC (WHEP) session, so there's no channel limit; viewers that can't ride WebRTC auto-fall back to the slightly-delayed MSE path.
- Address: open
http://<PC_IP>:9090on each device, where<PC_IP>is this PC's LAN address (same one you put instreaming.xml/mediamtx.yml, or seehttp://localhost:9090/api/config). - Firewall: if you accepted the first-run Windows prompts, nothing to do. Otherwise
whitelist
8889/8189/9090inbound — see Ports used. - WiFi: works, but wireless adds jitter. A WiFi viewer may occasionally trip the WebRTC health check and slide to the MSE fallback (~0.4–0.8s) until it stabilizes. Wired is steadier for the SRT ingest; each WiFi client just monitors.
- Bandwidth: each WebRTC viewer is one extra stream from mediaMTX to that browser. On a 1 Gb/s switch a handful of 1080p viewers is trivial; over WiFi expect per-client headroom to matter more.
The page has a Stop server button (top-right). It shuts down this Node app on this
PC — ffmpeg, mediaMTX, and the web page — and you're prompted to confirm first. It only
stops the PC side: your camera keeps streaming on its own (the camera is always
controlled from the camera itself, never from this page). Restart the app anytime with
npm start. Anyone with the page open and a confirmed click can stop the server.
Work in progress. The Camera tab mirrors the camera's menus and applies changes live over the REST API, but it is still under active development — verify anything critical on the camera itself.
Alongside the live view, the page has a second tab — Camera — that mirrors the
camera's own on-screen menu system (Exposure, Lens, Color, Record, Media, Monitor, Audio,
Setup, Presets, Livestream) and drives the same controls over the camera's REST API.
Instead of a hard-coded list of controls (which drifts as the firmware changes), it does
dynamic discovery: the server fetches the camera's own OpenAPI/AsyncAPI YAML specs
from /control/documentation.html, parses them, and the page builds itself from that
registry (GET /api/cam/registry).
- Camera-style menus. Sections map to the camera's real on-screen menus. Each section is built from whatever the connected camera exposes; per-display monitor settings and per-channel audio settings are generated from what the camera reports.
- Apply on change. Dropdowns, toggles and fields apply the moment you change them — no Apply buttons — with a toast: a green toast on success, a red toast with the camera's reason on failure (and the control reverts to the camera's last known value).
- Live values + belt. Every row shows the camera's current value, polling while that
section is open. A status belt across the top shows
Streaming · 1080p24 · 8 Mbpsand glows green/orange/red with the livestream state. - "Supported" seeding. Endpoints like
/video/supportedISOsfill the dropdowns on the fly (ISO 100–25600, Gain −12 … +36 when the camera says so). - Raw API left in Advanced. The Advanced rail lists every endpoint the camera
exposes, verbatim (
GETon expand,PUT/POSTwith generated forms,DELETEbuttons), so nothing discoverable is hidden from power users. - Honest about camera quirks (verified PYXIS firmware behavior): the Livestream section
notes that
PUT /livestreams/0/stopalways 403s while Streaming and that the camera cannot be remote-stopped — there is deliberately no fake "stop stream" button. Rebooting the camera asks for confirmation before it drops your SRT session. - All reads/writes go through the same
/cam/*proxy, so the Content-Length fix, the env-only credentials and the self-signed-cert handling all apply. The browser never talks to the camera directly, and no IPs/credentials live in this page's code.
Deep-link to it directly: http://localhost:9090/?tab=control.
- WebRTC path: ~0.2–0.4s glass-to-glass. The browser rides the live edge at 24.0 fps with ~0 frames dropped and ~7ms jitter. Remaining latency is almost entirely SRT buffering (both peers) + one decode frame.
- MSE fallback: ~0.4–0.8s. Only used when WebRTC isn't available.
- Measure ground truth with a stopwatch / running-clock video in front of the lens.
Use Chrome or Edge (both Chromium). Firefox can't ride the WebRTC path here and will show a noticeably delayed MSE image instead.
This system is video-only by design. The camera is configured to send audio in its SRT stream, but it is dropped on the PC:
- WebRTC leg — the WHIP muxer in ffmpeg refuses to start without a stereo audio track, so a silent audio track is fed to it; the browser ignores it and only wires the video.
- MSE fallback leg — fully silent.
If you ever want the camera's own audio, swap the silent shim for the real track in
spawnFfmpeg() (-map 1:a:0 → -map 0:a:0, drop the anullsrc input). See
docs/DESIGN.md.
server.js Node app: SRT->WHIP+fMP4 pipeline, supervisors, camera proxy, WS relay, OpenAPI discovery
public/ Browser page: Live tab (WHEP-first player + MSE fallback) + Camera tab (camera-menu control)
public/control.js Camera tab: mirrors the camera's on-screen menus, built from the OpenAPI/AsyncAPI registry
mediamtx/mediamtx.yml WebRTC-only mediaMTX config (download mediamtx.exe here)
streaming.xml Example camera "custom platform" (SRT caller, 1080p24)
docs/DESIGN.md Deep dive: protocol flow, latency budget, WHIP/SPS-PPS gotchas
docs/TROUBLESHOOTING.md Wedged streams, black video, restarts, Firefox, and more
- docs/DESIGN.md — how it works end-to-end: WHEP handshake, why the
WHIP leg must be
-c:v copy(and never re-encoded), the silent-stereo-shim requirement, the supervisor design, code map. - docs/TROUBLESHOOTING.md — the failure modes we hit and their cures: black video / PLI storms, the "stream is active to a different destination" wedge, restart procedures, browser gotchas.
This project is GPL-3.0. In plain terms:
- Use it, modify it, build on it — free, for personal or open-source projects.
- Share improvements — if you distribute a modified/derived version, you must release your source under the same copyleft terms.
- Paid / closed-source use — everyone is free to license it as they see fit, but anyone who wants to use this work as the basis of a paid or proprietary product must either keep it open under GPL or pay the copyright holder for a commercial license (contact the author). You can't silently bundle it into a closed paid product without paying.
The repo's npm dependencies (ws, js-yaml) are MIT, which is compatible with GPL-3.0.
ffmpeg and mediaMTX are separately-installed third-party binaries and not part of this
codebase.
Looking to use this in a paid or closed-source product? See COMMERCIAL.md for a commercial license.