Files
bitvid/docs/bitvid-bridge-plan.md
T
thePR0M3TH3ANandClaude Opus 4.8 50f1a7c2aa [docs] Document static-first priority principle + the recommended build sequence
Maintainer principle: put as much effort into the static bitvid client as
possible before branching into any separate application.

- bitvid-bridge-plan: prominent "PRIORITY PRINCIPLE — static bitvid first" banner
  up top; the Bridge (a second app) is deliberately DEFERRED until the static
  client's live features (Go-Live Tier-1/2 MVP) ship and are maximally developed;
  build it only when live proves demand for its sovereignty/power features.
- live-publish-plan: new "Build order & the static-first principle" section with
  the recommended sequence — 1) Shorts (#16b), 2) Live ingest (#16 watch
  foundation), 3) Go-Live MVP (#16c P0-1, Tiers 1&2, client-side only, no
  Bridge/VPS), then 4) the Bridge later only if warranted.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-07-01 13:53:10 -04:00

336 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# BitVid Bridge — cross-platform streaming helper app — Dev Plan
Companion to **`docs/live-publish-plan.md`** (#16c) — this expands its **Phase 7
(Local Bridge app)** into its own plan. The Bridge is the piece that fills the one
gap a static web client can't: **receiving and routing live media** on the
creator's machine.
Status: **FUTURE / exploratory — all design decisions LOCKED** (maintainer
accepted every recommendation), but **DELIBERATELY DEFERRED** — see the priority
principle below.
> **⛔ PRIORITY PRINCIPLE — static bitvid first (maintainer).** Put as much effort
> into the **static bitvid client** as possible before branching into any *separate
> application*. The Bridge is a second app (own repo, toolchain, cross-platform
> packaging, code-signing cost), so **do not start it** until the static client's
> live features have shipped and are maximally developed — specifically the Go-Live
> **Tier-1/2 MVP** (external-provider + serverless, zero infra; see the publish
> plan's provider spectrum). Rule of thumb: **exhaust what the static client can do
> before writing a second app.** Build the Bridge only once live has shipped *and*
> shown real demand for the sovereignty/power features it adds (local recording +
> full-WebTorrent VOD, no-third-party, multi-restream, hardware/appliance, capture).
Goal (maintainer, *when it's time*): a **super-simple, easy-to-install,
easy-to-use** cross-platform app (Windows / macOS / Linux) that makes going live on
bitvid turnkey.
---
## Core decision
The Bridge is a **local media receiver + router**, not a Nostr identity holder.
It pairs a **native media core** (receive/record/restream/upload) with an
**embedded bitvid "Studio" webview** that does all Nostr signing. The two stay
logically separated inside one app:
```
BitVid Bridge (one installable app)
├── Native media core → RTMP/SRT/WHIP ingest, HLS/WebRTC out, record, restream,
│ S3 upload (never sees the Nostr key)
└── Embedded Studio webview → bitvid client that signs NIP-53 + publishes VOD
(talks to the core over in-app IPC, not localhost HTTP)
```
**Why an embedded webview instead of "the website controls a localhost server":**
it solves two hard problems at once —
1. **No browser↔localhost pain.** An HTTPS page calling `http://127.0.0.1` hits
mixed-content + Private-Network-Access preflight/permission rules. In-app IPC
(Tauri commands / Wails bindings) sidesteps all of it.
2. **Signing stays in the web layer.** The webview is a bitvid client, so it signs
exactly as the browser does; the native core never touches the key. This
honors the publish plan's Rule 1 ("BitVid signs; Bridge routes") while still
being one self-contained app.
**Where the Bridge sits (see the publish plan's three-tier provider spectrum):**
- **Tier 1 (platform embed)** and **Tier 2 (serverless RTMP→HLS: Livepeer/Mux/
Cloudflare)** need **no Bridge and no VPS** — Tier 2 is the recommended
"no-server, native playback" default. So the Bridge is the **Tier-3 (advanced /
full-sovereignty)** option, not the default on-ramp.
- **The Bridge earns its place** by: no third party at all, **local recording +
full-WebTorrent VOD**, multi-restream hub, hardware/appliance input, turnkey UX,
and later built-in capture (no OBS).
**Hybrid that avoids a VPS entirely (important):** a **local** Bridge can
**record locally + restream to a Tier-2 serverless provider** which serves the
public HLS. The home Bridge then **never needs to be publicly reachable** (no VPS,
no tunnel/port-forward — it's *outbound* to the provider), yet the creator still
gets local recording + a full-WebTorrent VOD, and viewers get native HLS. This is
the sweet spot for most sovereignty-minded creators: **local Bridge for
record/WebTorrent/control + serverless for reach.**
---
## Decisions — ✅ ALL LOCKED
The maintainer accepted every recommendation below; each *Recommendation* line is
now the **locked choice**. Summary:
| # | Decision | Locked |
|---|----------|--------|
| 1 | App framework | **Wails (Go)** — one Go binary for desktop + headless node (ties to D7); Tauri was the close alt |
| 2 | Control model | **Embedded Studio webview** primary; optional localhost API later |
| 3 | Capture vs receive | **Receiver-first MVP**; built-in capture as a fast-follow (Phase 5) |
| 4 | Webview signer | **NIP-46 + encrypted-nsec**, NIP-46 preferred |
| 5 | Ingest protocols | **RTMP for MVP**; SRT + WHIP later |
| 6 | Recording upload | **Bundled rclone** (multi-provider, resumable) |
| 7 | One codebase, two modes | **Yes** — desktop Bridge == self-hosted Media Node run `--headless` |
| 8 | Transcode | **Pass-through only** (`-c copy`) |
| 9 | Code signing | **Project owns** Apple notarization + Windows cert (budgeted) |
| 10 | License / telemetry | **Match bitvid; no telemetry by default** |
D1 resolves to **Go/Wails** specifically because D7 (one codebase, two modes) is a
"yes": a single Go binary serves the desktop Bridge (with UI) and the headless
self-hosted Media Node — Go being MediaMTX's own language makes that clean.
> **DECISION 1 — App framework.** Go/**Wails** vs **Tauri** vs Electron.
> *Recommendation: **Wails (Go)** or **Tauri (Rust)** — both give a web UI (reuse
> bitvid's components/tokens) and small binaries; Electron is ~100MB+ and heavy,
> against "simple install." Slight lean **Wails/Go**: the media stack is Go-native
> and **MediaMTX is written in Go**, so it's one language, one small static binary,
> trivial cross-compile. Tauri is a very close second with a more mature app-shell
> + updater. Pick based on team comfort (Go vs Rust).*
> **DECISION 2 — Control model.** Embedded Studio webview (self-contained app) vs
> website-drives-localhost-API vs both.
> *Recommendation: **embedded webview primary** (dodges PNA/mixed-content + keeps
> signing in the web layer). Optionally expose a **localhost API later** for people
> who want to drive it from the bitvid website (with the PNA/CORS work done then).*
> **DECISION 3 — Capture vs receive.** MVP receives (OBS/hardware → Bridge) vs push
> built-in screen/camera capture earlier.
> *Recommendation: **receiver-first MVP** — works with the OBS/encoders people
> already have and ships fast. **Built-in capture (no OBS)** is the "super simple"
> headline but is effectively a mini-OBS (encoder is hard) → make it a fast-follow
> phase, not MVP.*
> **DECISION 4 — Signer support in the Bridge webview.** Since the webview signs,
> which login methods? **NIP-46 remote signer** (no key on device — safest), the
> **encrypted-nsec** flow the web app already has (key encrypted on-device), or
> both. NIP-07 extensions generally aren't available inside an app webview.
> *Recommendation: **both NIP-46 + encrypted-nsec**, NIP-46 preferred/first. Reuse
> the web app's existing session/signer code so there's nothing new to audit.*
> **DECISION 5 — Ingest protocols for MVP.** RTMP only, or RTMP + SRT + WHIP.
> *Recommendation: **RTMP for MVP** (universal — OBS/hardware all speak it), add
> **SRT** (better over lossy networks) and **WHIP** (WebRTC ingest, low latency)
> in a later phase. MediaMTX supports all three, so it's mostly UI/config.*
> **DECISION 6 — Recording upload mechanism.** Bundle **rclone** (one binary,
> speaks R2/B2/S3/MinIO + resumable) vs a native S3 SDK in the core vs reuse
> bitvid's browser S3 upload from the webview.
> *Recommendation: **rclone** bundled as a sidecar — dead-simple multi-provider
> support + resumable uploads of large recordings, matches the storage providers
> bitvid already supports. Native SDK only if we want to drop the extra binary.*
> **DECISION 7 — One codebase, two modes?** The server-side **Media Node** (#16c)
> and the local **Bridge** are ~the same thing (MediaMTX + session API + upload +
> restream). Build **one codebase that runs headless (Media Node) or with the
> embedded UI (Bridge)?**
> *Recommendation: **yes** — "self-host the Media Node" (publish-plan DECISION 1b)
> becomes literally "run the Bridge in `--headless` mode on a VPS." Big reuse win;
> steers DECISION 1 toward Go (server + desktop from one Go binary).*
> **DECISION 8 — Transcode.** Pass-through (`-c copy`) only, or offer local
> transcode profiles?
> *Recommendation: **pass-through only** (same as the publish plan). Require
> H.264/AAC in; the Bridge repackages/forwards without re-encoding — keeps CPU low
> and the app light. Transcoding is a later, opt-in power feature.*
> **DECISION 9 — Code signing + distribution ownership.** "Easy install" *requires*
> **Apple notarization** (Developer ID — or macOS Gatekeeper blocks it) and a
> **Windows Authenticode cert** (or SmartScreen scares users). Who holds the certs
> and pays (~$99/yr Apple + Windows cert)? Linux = AppImage + `.deb`/`.rpm`
> (no signing gate). *Recommendation: the project owns the certs; budget for them —
> this is the true cost of "easy to install," not optional.*
> **DECISION 10 — License / telemetry.** Open-source license (match bitvid's) and
> **no telemetry by default** (opt-in crash reports at most). *Recommendation:
> same license as bitvid; privacy-first, opt-in only.*
---
## Architecture
```
BitVid Bridge
├── App shell (Tauri/Rust or Wails/Go) DECISION 1
│ └── Embedded Studio webview (bitvid client, signs NIP-53/VOD) DECISION 2,4
├── Native media core
│ ├── MediaMTX (bundled sidecar) → ingest RTMP/SRT/WHIP, HLS/WebRTC out, record
│ ├── FFmpeg (bundled) → copy-forward restreams (-c copy) DECISION 8
│ └── rclone (bundled) → upload recordings to R2/B2/S3/MinIO DECISION 6
├── Session/state manager → LiveSession model, status, tokens
├── IPC bridge → webview ↔ core (Tauri cmds / Wails bindings)
└── (optional) localhost API → for website control, later DECISION 2
```
- **Studio webview sourcing.** The webview loads the **instance's deployed bitvid
web app** (remote URL, cached for offline resilience) rather than a bundled copy.
Benefit: it's always current and **auto-inherits the instance's flags** (so
`FEATURE_LIVE_PUBLISH` off ⇒ no go-live, invariant 6) with no app update needed;
a trimmed bundled fallback covers the offline/first-run case. The instance URL is
configurable (a user may use more than one bitvid instance).
- **Media core = a local Media Node.** Same engine as the server-side node (#16c),
which is why DECISION 7 (one codebase, two modes) is attractive.
- **Stream keys / ingest URLs never leave the device** and never go into Nostr; the
webview only ever publishes the *playback* URL.
- **Status → Nostr:** MediaMTX hooks fire `input_connected / input_live /
input_lost / recording_finalized / upload_complete`; the core forwards these to
the webview over IPC; the **webview signs** the corresponding NIP-53 update
(planned → live → ended + recording).
---
## UX flow (the "super simple" target)
```
1. Install (signed installer / AppImage). DECISION 9
2. Open app → sign in (NIP-46 or encrypted nsec) in the Studio webview. DECISION 4
3. Fill title/thumbnail/tags → "Prepare stream" (publishes status=planned).
4. App shows one of:
a. RECEIVE MODE: local ingest URL + key to paste into OBS/encoder. DECISION 3
b. (later) CAPTURE MODE: pick a screen/window/camera → "Go Live".
5. Encoder connects → core detects input → webview publishes status=live.
6. (optional) toggle restream outputs (YouTube/Twitch/zap.stream). DECISION 5
7. "End" → status=ended → core finalizes recording → uploads → webview
offers "Publish recording as a BitVid video" (archive→VOD).
```
The whole point: **one window, sign in, go live, done** — and the recording
becomes a normal bitvid video automatically.
---
## Cross-platform packaging
- **Windows:** signed `.msi`/`.exe` (Authenticode). Auto-update.
- **macOS:** signed **+ notarized** `.dmg` (Developer ID) — mandatory. Universal
(arm64 + x86_64).
- **Linux:** **AppImage** (zero-install portable) + `.deb`/`.rpm`; consider Flatpak.
- **Bundled binaries per-platform:** MediaMTX, FFmpeg, rclone. FFmpeg is the size
hog (~50–80MB) — ship a **minimal build** (only the muxers/protocols we use:
flv/rtmp/mpegts/hls/webrtc, aac/h264 passthrough) to keep the installer lean.
- **Auto-updater** (both Tauri and Wails have one) with signed release artifacts.
---
## Phases
- **Phase 0 — Shell + media core (foundational).** App skeleton (DECISION 1),
bundle + supervise MediaMTX, embedded Studio webview, webview↔core IPC, signed
dev builds on all three OS. Sign-in works (DECISION 4).
- **Phase 1 — Receive → serve (the MVP).** Local RTMP ingest → serve HLS. For
public viewers the playback origin must be **reachable**, so either run the
Bridge **headless on a reachable host** (VPS — the self-hosted node) or
copy-forward from a local Bridge to that reachable node / an external provider
(never a bitvid-run node). Publish planned→live→ended NIP-53 from the webview via
MediaMTX input hooks. "OBS → your node → bitvid, one window."
*(NAT note: a home-only Bridge can't serve public HLS directly → either run
headless on a VPS, or use the hybrid: forward to a Tier-2 serverless provider
that serves public HLS — see Phase 3.)*
- **Phase 2 — Record → archive → VOD.** Local recording, finalize, rclone upload
to the user's storage, manifest → webview publishes a normal bitvid video
(reuses the `s`-tag/info.json/30078 flow). Optional torrent/webseed for full
WebTorrent VOD.
- **Phase 3 — Multi-restream (`FEATURE_LIVE_RESTREAM`).** One input → many outputs
(YouTube/Twitch/zap.stream/**serverless providers Livepeer/Mux/Cloudflare**/
custom RTMP/SRT), copy-only, per-output status + retry/backoff. Keys stay local;
optional public mirror links as `r` tags. **The serverless preset enables the
no-VPS hybrid** — a local Bridge records locally + forwards to Livepeer/Mux,
which serves the public HLS (the `streaming` URL in the NIP-53 event). This is
arguably the flagship Bridge workflow for home creators.
- **Phase 4 — Turnkey UX + packaging.** One-click go-live, OBS auto-config or
OBS-WebSocket control, signed/notarized installers + AppImage, auto-update.
- **Phase 5 — Built-in capture (no OBS).** Screen/window/camera capture + encode
inside the app (`getDisplayMedia`/`getUserMedia` → WebCodecs/FFmpeg). The "super
simple" headline: go live with no external encoder.
- **Phase 6 — SRT/WHIP ingest + WebRTC low-latency** (DECISION 5).
- **Phase 7 — Headless "Media Node" mode + hardware / "BitVid Box".** Run the same
binary headless as the self-hosted Media Node (DECISION 7); harden for
appliance/headless use — the seed of a physical device.
---
## Engineering rules (invariants)
1. **The native core never sees the Nostr key.** Only the webview signs.
2. **Ingest URLs/keys stay on the device; only playback URLs reach Nostr.**
3. **Pass-through first** — copy H.264/AAC, don't transcode (DECISION 8).
4. **Reuse bitvid's web client** for the Studio webview (session, signer, publish,
upload-modal-style VOD flow) — minimize net-new signing/auth code.
5. **Optional, not required** — a creator can still stream OBS → an external
provider without the Bridge; the Bridge *adds* self-hosting + convenience/power
(and bitvid never runs a node — blanket rule).
6. **Respect the connected instance's config.** The embedded Studio webview loads
a specific bitvid instance, so it inherits that instance's flags — if the
instance has `FEATURE_LIVE_PUBLISH` **off**, the Bridge shows go-live as
unavailable for it (nothing to publish to). The Bridge is only useful pointed
at an instance that has publishing enabled; surface that clearly rather than
failing opaquely.
7. **Bind the local ingest to loopback only.** The RTMP/SRT receiver listens on
`127.0.0.1` (not `0.0.0.0`) by default so it isn't exposed to the LAN;
appliance/headless mode (Phase 7) can opt into a wider bind with auth.
---
## Risks / watch-items
- **Code signing is the real "easy install" cost (DECISION 9).** Apple
notarization + Windows cert are non-negotiable for a frictionless install and
cost money + setup. Budget for them up front.
- **Bundle size vs simplicity.** MediaMTX + FFmpeg + rclone add up; a minimal
FFmpeg build and per-platform packaging keep it reasonable, but it won't be
tiny. Trade-off vs "super light."
- **Built-in capture is a mini-OBS (DECISION 3/Phase 5).** Reliable capture +
encode across GPUs/OSes is genuinely hard — don't let it block the receiver MVP.
- **Signing during long streams.** Same issue as the publish plan (DECISION 2
there): the webview must stay running to sign status updates + heartbeats;
handle sleep/crash gracefully (NIP-53 treats no-update-in-~1h as ended).
- **Webview signer support.** NIP-07 extensions typically aren't present in an app
webview → lean on NIP-46 / encrypted-nsec (DECISION 4).
- **Encoder compatibility.** Pass-through needs H.264/AAC + sane keyframes;
detect + warn on incompatible input rather than failing silently.
- **Scope creep.** This app can sprawl (capture, transcode, appliance). Ship the
receiver MVP (Phase 0–1) first; everything else is additive.
- **Instance without publishing enabled.** If a user points the Bridge at an
instance that has `FEATURE_LIVE_PUBLISH` off, go-live must degrade gracefully
(clear "publishing isn't enabled on this instance" state), not error obscurely
(invariant 6).
- **Local ingest exposure.** A misconfigured wide bind would expose an unauth'd
RTMP receiver on the network — default to loopback (invariant 7); require auth
for any non-loopback bind.
- **Reachability / NAT (self-hosting reality) — largely solved by the hybrid.** A
**home-only Bridge behind NAT can't *serve* public HLS**. Two clean answers:
(1) the **hybrid** — the local Bridge *forwards* (outbound) to a Tier-2
serverless provider that serves the public HLS, so the home box needs no public
reachability at all (recommended for laptops/home); (2) run the Bridge
**headless on a reachable VPS** for full self-hosting (no third party). Only (2)
needs a VPS; document both. This is why headless mode (Phase 7 / DECISION 7) and
restream-to-serverless (Phase 3 preset) both matter.
---
## Sources
- **MediaMTX** (ingest/playback/record/forward/control API):
https://mediamtx.org/docs/kickoff/introduction
- **OBS WebSocket** (built into OBS 28+):
https://obsproject.com/kb/remote-control-guide
- App frameworks: [Tauri](https://tauri.app/) · [Wails (Go)](https://wails.io/) ·
packaging binaries (MediaMTX, [FFmpeg](https://ffmpeg.org/),
[rclone](https://rclone.org/)).
- Companion plans: `docs/live-publish-plan.md` (#16c, this expands its Phase 7),
`docs/live-ingest-plan.md` (#16, watch side + P2P-assisted HLS).
- bitvid code reused by the Studio webview: session/signer (NIP-46 + encrypted
nsec), the upload/publish flow, `js/utils/storagePointer.js` (`s`-tag →
`.info.json`) for archive→VOD.