BirdDog PLAY firmware package generator
Builds an installable .fw carrying an SSH key, Tailscale, an
NDI KVM endpoint, a USB media player, a UVC converter, a streaming gateway — and any
modules of your own. Everything is assembled in this browser —
nothing is uploaded, and no existing firmware file is needed.
Access
Installed to /root/.ssh/authorized_keys.
SSH on the PLAY listens on port 9031.
Payload
/userdata/tailscale, with a
systemd unit. No auth key is baked in — it would sit in cleartext in
the archive. Instead this adds a Tailscale panel to the System page of the
PLAY's own web UI, so you sign the device in from a browser and never need
SSH for it. Changing anything there needs a birdUI login; reading status does not.
You can still tailscale up over SSH on port 9031 if you prefer.
Stock firmware has no /dev/net/tun and ships no kernel modules at all, so
you get userspace-networking. Inbound still works — SSH, the web UI and the
:8080 API are all reachable over the tailnet, measured at ~195 Mbps
against a 920 Mbps wired baseline. Enough for NDI|HX and SRT, not for full-bandwidth
NDI.bdkvm to /userdata/bd-kvm under its own
self-supervising unit, rather than behind BirdDog's 1.0.34-only hooks (which
bdup.sh deletes on every vendor update). Needs a USB keyboard or mouse
attached and an NDI source selected to do anything.http://<play>:8091/.
Selecting USB stops BirdDog's decoder to take the display, and switching back to NDI restores it. This is the only option here that edits a firmware file: it adds one
<option> to videoset.html, keeps a pristine backup
beside it, and bdplay -unpatch-ui restores it byte-for-byte.
Why beta: two known rough edges, both measured rather than guessed. Repeatedly switching between USB and NDI destabilises BirdDog's own decoder — its
birddog-runner aborts during NDI teardown, and on the test unit that went
from 0 occurrences to 30 over an afternoon of switching. It restarts each time, and the
player clears a failed state and checks the decoder came back, but expect the odd
dropout if you flick between sources constantly; treat it as a mode you set. And
playback has been proven from internal storage and a loopback exFAT volume, but
not yet with a physical stick hotplugged.
http://<play>:8091/'s sibling, :8090.
A UVC 1.5 camera is bound automatically. This kernel's
uvcvideo matches
only protocol 0 and ignores 1.5 devices silently — no device node, nothing in the log —
so without that they simply never appear.
Why beta: it runs on a test unit and every claim here is measured, but two things are worth knowing. NDI|HX uses a compressed-packet layout reconstructed from the Advanced SDK's documentation rather than a header, so a future libndi could change it; the symptom would be a picture fault, not a crash. And at heights that are not a multiple of 16 — 1080 among them — the bottom 8 rows are cropped, because H.264 pads to a macroblock boundary and this decoder ignores the crop the standard puts in the stream, showing the padding as a green band.
Selecting HDMI directly stops BirdDog's decoder to take the display; routing HDMI through the decoder instead keeps the OSD, web UI and tally alive, and needs NDI|HX.
libndi.rtmp://<play>:1935/live, pull an RTSP camera or an HLS playlist,
publish WHIP from a browser, receive MPEG-TS or RTP over UDP — and put any of it on the
HDMI output. Out: serve the UVC converter's camera over RTSP, RTMP,
HLS, WebRTC and SRT, and push it to YouTube, Twitch or any RTMP(S), RTSP, SRT or WHIP
destination. Settings live in a Streaming tab in the web UI.
The box runs MediaMTX as the hub; the picture reaches the screen through the PLAY's own decoder, which is pointed at the chosen path as an SRT source on loopback — the three files the stock AV Setup page writes, plus a restart. Nothing takes the display, so the OSD, tally and web UI stay as they are, and if the hub dies the PLAY behaves like a PLAY.
Why beta: this one has not yet run on a PLAY. Everything it relies on is read out of the firmware or measured by the projects beside it, and its configuration is validated against the real MediaMTX in CI — but the edge the whole inbound side rests on, the PLAY's decoder reading MediaMTX's SRT over loopback, is untested until the test unit is back. The decode ceilings are the chip's: H.264 to 1080p60 or 2160p30, HEVC to 2160p60; WebRTC publishers must send H.264. YouTube refuses video without audio, and the UVC converter has none yet.
Ports 1935, 8554, 8888, 8889 and 8890/udp open with no credentials — the LAN or the tailnet is the boundary, exactly as for the stock API on
:8080. The tab itself is gated on your birdUI login, because it holds
stream keys. Adds about 29 MB to the package; 62 MB on the device.
BirdDogRunner itself, so video comes back without a reboot.Build
Names the file and is logged on the device as the installed build.
Leave empty to fetch the current stable arm64 release and verify it against
the published SHA-256. Supply tailscale_<version>_arm64.tgz yourself to
pin a version or build offline.
Only used with the streaming gateway. Leave empty to fetch the pinned release
and verify it against the checksums.sha256 GitHub publishes beside it. Supply
mediamtx_v<version>_linux_arm64.tar.gz yourself to build offline — the
gateway's configuration is written for the pinned version, so another may not load it.
Add your own payloads. A module is a .tgz with a
module.conf and an install script; it goes into the package at
modules/<name>/ and the installer runs install
as root after the payloads above. Each file is read and checked here the
moment you choose it — structure only, never what install does.
Read what you package. How to build one, or start
from bd-play-module-template.
- File
- Size
- SHA-256
Installing it
- Open the PLAY's web UI and upload the
.fwon the firmware page, exactly as you would a vendor release. - Read the first-boot report at
http://<play-ip>/static/bd-probe.txt— it works even if the SSH key did not take, and reports whether/dev/net/tunexists, free space, USB input topology, and one line per custom module with its exit code. The full install log, including everything your modules printed, is at/tmp/bd-custom-install.logon the device. ssh -p 9031 root@<play-ip>, then/userdata/tailscale/tailscale --socket=/run/bd-tailscaled.sock up.- Delete the probe report when you are done:
rm /srv/birddog-web-ui/static/bd-probe.txt.
Before you put it on a tailnet
The PLAY's REST API on :8080 has no authentication of any kind
and sets Access-Control-Allow-Origin: *. Joining a tailnet does not change that —
anyone who can reach the device on the tailnet can reconfigure it without credentials. Scope
an ACL for this node; the device has no access control of its own to fall back on.
Undoing it
/userdata/bd-tailscale-ui/bdts --unpatch-ui && systemctl restart BirdDogWebUI
systemctl disable --now bd-tailscaled bd-kvm bd-tailscale-ui
rm -rf /userdata/tailscale /userdata/bd-kvm /userdata/bd-tailscale-ui /etc/systemd/system/bd-{tailscaled,kvm,tailscale-ui}.service
Take the first line before the others: it puts the System page back exactly as
it shipped. A pristine copy is also kept beside it as
settings.html.bdts-stock, and bdts --restore-ui restores from
that.
The streaming gateway: clear Show on HDMI in its tab first, so the
decoder is handed back, then
/userdata/bd-gw/bdgw --unpatch-ui && systemctl restart BirdDogWebUI,
systemctl disable --now bd-mtx bd-gw, and
rm -rf /userdata/bd-gw /etc/systemd/system/bd-{mtx,gw}.service.
A custom module built from the template leaves its own
uninstall beside its payload: bash /userdata/bd-<name>/uninstall.
One without it is whatever its author documented — /userdata/bd-modules.log
says what was installed and when.
Then reflash stock firmware if you want a clean unit. Recovery-mode flashing of
the factory image is a proven path, but it restores factory state — not this unit's
provisioned serial, hostname or /userdata.