Files
bitvid/docs/live-publish-plan.md
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

459 lines
25 KiB
Markdown
Raw Permalink 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 Live — PUBLISH / "Go Live" — Dev Plan
TODO ref: **#16c** in `todo/TODO_2026-06-20_pre-launch.md` (this doc expands the
stub). Companion to **`docs/live-ingest-plan.md`** (#16, watch-only) — the *watch*
side (Live tab, player, chat, zaps) is built there and **reused** here; this doc
adds the *broadcast* side.
Status: **FUTURE / exploratory — all six decisions LOCKED** (see Core Decision's
blanket rule: bitvid hosts nothing; self-host or external only). Sequenced
**after** #16 (live ingest) and #16b (shorts). The largest plan, and the only one
touching **(self-hosted, never bitvid-run) media infrastructure**.
---
## Core decision
> **BLANKET RULE (maintainer, non-negotiable): bitvid.network hosts NOTHING but a
> static client — no servers, no media infrastructure, ever.** All media
> receiving/routing is **self-hosted by the creator** (the Bridge run headless — a
> single self-hosted binary, see `docs/bitvid-bridge-plan.md`) or handled by an
> **external provider** (zap.stream/YouTube/Twitch). There is no bitvid-operated
> Media Node and no managed publishing — those options are off the table.
Build **BitVid Live** as a **Nostr-native live-stream control layer**, not a
video-hosting backend. BitVid the client stays exactly what it is — a static site
that signs client-side, holds no keys, runs no server. The one thing a static
browser app genuinely *cannot* do (receive + route live media) is isolated into a
**separate, self-hosted-or-external** service (never bitvid-operated). Split:
```
BitVid static client = identity, metadata, NIP-53 publishing, discovery, chat,
zaps, player UI, archive→VOD publishing
Media Node / Bridge = RTMP/SRT/WHIP ingest, HLS/WebRTC output, recording,
(self-hosted OR restreaming, status API
external provider)
```
This keeps BitVid's architecture intact while adding broadcast. Crucially,
**most of these modes need no bitvid infrastructure at all** — see the three-tier
spectrum next.
---
## Provider options — three tiers (bitvid hosts none of them)
The creator chooses who receives their media; bitvid only ever **publishes the
resulting `streaming` URL and plays it**. A VPS is only the *most sovereign* tier,
not a requirement — most creators never run a server.
| Tier | Setup / cost | Playback in bitvid | Trade-off | bitvid work |
|------|--------------|--------------------|-----------|-------------|
| **1. Platform embed** — StreamYard/OBS → YouTube/Twitch | free, **zero infra** | their **iframe** embed | their rules/ads; no native playback, no P2P/WebTorrent | external-embed adapter |
| **2. Serverless RTMP→HLS API** — Livepeer / Mux / Cloudflare Stream | ~free–cheap, **no server** | **native HLS** (+ P2P-assist possible) | small cost at scale; a 3rd-party account | native HLS (from #16) |
| **3. Self-hosted node** — the Bridge run headless on a VPS | most effort/cost | native HLS | full **sovereignty + WebTorrent VOD + restream** | native HLS + Bridge |
- **Recommended default "no-VPS but native": Tier 2, Livepeer** — decentralized,
crypto-native, cheapest-aligned; the creator points OBS/StreamYard at Livepeer's
RTMP ingest and pastes the HLS URL into bitvid. [Mux](https://www.mux.com/live)
(free delivery allotment; live needs a paid quality tier) and
[Cloudflare Stream](https://developers.cloudflare.com/stream/pricing/) (free
ingest/encoding, cheap delivery) are alternatives.
- **StreamYard is a studio, not a CDN** — it broadcasts *out* to
YouTube/Twitch/custom-RTMP (custom RTMP is paid), so with bitvid it's a Tier-1
encoder feeding YouTube/Twitch, or a Tier-2/3 encoder feeding a serverless
API / your node.
- **The same NIP-53 event + player serve all three tiers** — the only per-tier
code is the playback adapter (external embed vs native HLS). So supporting the
spectrum is cheap, and the VPS/Bridge is the *advanced* tier, not the default.
---
## Relationship to the other two plans
- **#16 (live-ingest) is a hard dependency.** It builds the NIP-53 **parsers**
(30311/1311), the **Live tab**, `LiveCard`, `LivePlayer`, `LiveChatPanel`, HLS
playback (hls.js), and host zaps. This plan **reuses** all of that to *watch*
streams and adds the **builders** + Studio + Media Node/Bridge to *create* them.
- **Shared Phase 0 schema.** #16 adds `parseLiveStreamEvent`/`parseLiveChatMessageEvent`;
this plan adds `buildLiveStreamEvent`/`buildLiveChatMessageEvent`/
`updateLiveStreamStatusEvent`. Do them in `js/nostrEventSchemas.js` together so
the catalogue stays consistent — coordinate so they aren't duplicated.
- **My Videos "Live" sub-tab** (noted in the shorts plan) belongs to *this*
feature — managing your own streams only makes sense once you can publish.
---
## Why BitVid Live — advantages over the incumbents
Lean into what only bitvid can offer; these are the reasons a creator would pick
BitVid Live over zap.stream/Twitch/YouTube.
- **P2P bandwidth offload (WebTorrent-flavored live).** Classic WebTorrent can't
carry a live stream (its infohash needs the *complete* file), but **P2P-assisted
HLS** can: hls.js + [P2P Media Loader](https://github.com/novage/p2p-media-loader)
(historically WebTorrent-tracker-based) lets concurrent viewers share HLS
segments over WebRTC, so each extra viewer helps *serve* the stream. This
directly cuts the Media Node's egress — the biggest cost of running live infra —
and scales *better* the more popular a stream gets. (Enable when concurrency
warrants; see the ingest plan.)
- **Recordings become full WebTorrent VODs.** The moment a stream ends, its
recording is an ordinary bitvid video: complete file → infohash → **seed +
webseed**, playable P2P + URL like everything else. Twitch/YouTube VODs are
origin-only; bitvid VODs are P2P-distributable and self-hostable.
- **Nostr-native = portable + no lock-in.** The stream is a signed NIP-53 event on
relays, not a row in a platform DB. It's discoverable by *any* Nostr app, the
creator owns it, and it can't be de-platformed by a single company.
- **Zaps built in.** Instant Lightning value-for-value to the host during the
stream (bitvid's existing NWC/zap system) — no ads, no 50% platform cut, no
payout thresholds.
- **Bring-your-own everything.** Own storage (R2/B2/S3/MinIO) for recordings, own
relays, self-hostable Media Node, and an **external-provider-only mode that needs
zero bitvid infra**. No mandatory middleman.
- **One canonical stream, many mirrors.** Restream to YouTube/Twitch/zap.stream
simultaneously while bitvid stays the sovereign home — reach without lock-in.
- **Archive is a first-class video, not a throwaway.** VODs flow into the same
feeds, search, moderation, comments, and zaps as every other bitvid video (the
`s`-tag/info.json/30078 model) — no separate "past broadcasts" ghetto.
- **Self-sovereign identity.** The creator signs; no BitVid account, no bridge
ever holds their key.
---
## Decisions needed
> **DECISION 1 — Media hosting model. ✅ LOCKED by the blanket rule: a three-tier
> provider spectrum — bitvid hosts NONE of them.** The creator picks who receives
> their media; bitvid just publishes the resulting `streaming` URL. **A VPS is only
> the *most sovereign* tier, not a requirement.** See "Provider options" below:
> 1. **Platform embed** (StreamYard/OBS → YouTube/Twitch) — free, zero infra;
> bitvid embeds their player. **Ships first (Phase 1).**
> 2. **Serverless RTMP→HLS API** (Livepeer / Mux / Cloudflare Stream) — no server
> to run; the creator gets a real HLS URL bitvid plays **natively**. The
> recommended "no-VPS but native" default. **Livepeer** is the most aligned.
> 3. **Self-hosted node** (the Bridge run headless on a VPS) — full sovereignty +
> WebTorrent VOD + restream control; most effort. The advanced tier.
>
> The managed-BitVid-node option is **removed**. bitvid's job (publish the event,
> play the `streaming` URL) is **identical across all three** — the only code
> difference is a per-tier playback adapter (external embed vs native HLS).
> **DECISION 2 — Signing during a live stream. ✅ LOCKED: A for MVP, B (NIP-46)
> later; D removed by the blanket rule.** The node must NOT hold the creator's key.
> **(A) BitVid tab/app stays open** and signs status=live/ended (matches the
> Bridge's embedded-webview design) — MVP. **(B) NIP-46 remote signer** for a
> hands-off experience — later. **(C)** a *self-hosted* node may also publish
> provider-status under its own pubkey while the canonical 30311 stays user-signed
> — available to self-hosters. **(D) managed publishing account is off the table**
> (blanket rule).
> **DECISION 3 — Transcode. ✅ LOCKED: pass-through only (`-c copy`) in v1.**
> Require H.264/AAC + ~2s keyframes in; no transcode ladders. Transcoding is a
> heavy, self-hoster-only opt-in much later, if ever.
> **DECISION 4 — WebRTC low-latency. ✅ LOCKED: HLS first, WebRTC later.** HLS
> (~6–30s) works everywhere via #16's hls.js; WebRTC (<500ms) is a later phase.
> **DECISION 5 — Local Bridge in MVP. ✅ LOCKED: not required for the external-
> provider MVP; it IS the self-hosting path (DECISION 1b).** With the blanket rule,
> self-hosting a Media Node **is** running the Bridge headless, so the Bridge isn't
> an optional extra for self-hosters — it's *the* mechanism. The external-provider
> MVP (Phase 1) still needs neither. Full plan: `docs/bitvid-bridge-plan.md`.
> **DECISION 6 — Flag naming. ✅ LOCKED: keep the specific split.**
> `FEATURE_LIVE_INGEST` / `_PUBLISH` / `_BRIDGE` / `_RESTREAM` (+ `_CHAT_POST` from
> #16). No umbrella `FEATURE_LIVE`. `FEATURE_LIVE_PUBLISH` implies ingest is on.
---
## Config flags (off = no trace)
Every "Go Live" surface is instance-config gated and defaults **off**, threaded
`config/instance-config.js` → `js/config.js` → `js/constants.js` like
`FEATURE_NIP71_INGEST`. An operator can ship the code and have publishing be
completely invisible until they opt in.
- `FEATURE_LIVE_INGEST` — watch side (from #16; a dependency of publish).
- `FEATURE_LIVE_PUBLISH` — the Studio / Go-Live publishing UI (the master
publish switch).
- `FEATURE_LIVE_BRIDGE` — Media Node / Bridge session API integration.
- `FEATURE_LIVE_RESTREAM` — external restream output management.
**Independent of watching.** These are separate from `FEATURE_LIVE_INGEST`, so an
instance can enable *watching* live streams without enabling *broadcasting* (or
vice-versa). `FEATURE_LIVE_PUBLISH` implies ingest is on (you watch your own
stream); it does not force ingest on for everyone if the operator wants
watch-only.
When `FEATURE_LIVE_PUBLISH` is off, **all** of the following are absent — no tab,
no button, no trace (as if never added):
1. **"Go Live" / Studio entry points** — no Studio launch anywhere (sidebar,
upload modal, profile).
2. **My Videos → Live sub-tab** — not rendered (shorts-plan sub-tab treatment).
3. **Live-event builders** — `buildLiveStreamEvent` / `updateLiveStreamStatusEvent`
are not wired into any UI (the schema definitions can exist dormant; nothing
*invokes* them).
4. **Bridge/session UI** — pairing, ingest settings, health dashboard (also gated
on `FEATURE_LIVE_BRIDGE`).
5. **Restream UI** — output manager (also gated on `FEATURE_LIVE_RESTREAM`).
6. **Archive→VOD "publish recording"** live-origin flow (a normal video upload is
unaffected).
7. **Routes** — any `#view=live-studio` (or equivalent) falls back to default.
8. **Config surface** — the flags are the only footprint.
Bridge and restream have their own sub-flags so an operator can allow, e.g.,
publishing to their own Media Node **without** exposing external restreams.
**Who may go live (operator control).** Beyond the on/off flags, publishing on an
instance should respect the existing **whitelist/WoT** model — i.e. an operator
can allow only whitelisted creators to broadcast on their instance (reusing
`accessControl`), the same way the feeds are gated. Decide whether go-live is
whitelist-gated by default (recommended: yes on whitelist-mode instances) so a
public instance isn't an open ingest relay.
---
## Event model (NIP-53 — shared with #16)
Canonical live address = `30311:<creator-pubkey>:<d-tag>` (an addressable
replaceable event; dedupe by pubkey + `d`, exactly like bitvid treats video
`d`-tags). Key tags: `d`, `title`, `summary`, `image`, `streaming` (HLS/WebRTC
playback URL — the canonical first entry), `recording` (VOD when ended), `starts`,
`ends`, `status` (`planned`/`live`/`ended`), `relays`, `t` (hashtags), `p` (host +
participants), `service`.
Live chat = **kind 1311**, anchored to the activity via
`['a', '30311:<pubkey>:<d>', '<relay>', 'root']` — so bitvid live chat is just
Nostr, reusing #16's chat pipeline.
Schema work (in `js/nostrEventSchemas.js`, coordinated with #16):
- `NOTE_TYPES.LIVE_STREAM` (30311), `NOTE_TYPES.LIVE_CHAT_MESSAGE` (1311).
- Builders: `buildLiveStreamEvent`, `updateLiveStreamStatusEvent`,
`buildLiveChatMessageEvent`. Parsers: from #16.
- Helper: `getLiveAddress(pubkey, d)` → the canonical `a` coordinate.
**Safety invariant:** the public 30311 event carries **only** the playback URL and
public metadata. **Never** publish RTMP/SRT ingest URLs or stream keys into Nostr.
---
## Auth & key custody (the non-negotiables)
1. **BitVid signs; the Bridge routes.** The Media Node/Bridge never receives the
creator's Nostr private key. It controls *media sessions*, not *identity*.
2. **Bridge auth via NIP-98 HTTP-Auth (kind 27235).** bitvid already has this
builder (`buildHttpAuthEvent`, `js/nostrEventSchemas.js`). The client signs a
27235 event ("I, pubkey X, authorize creating session Y at URL Z"); the node
verifies the signature, mints a stream key, returns ingest/playback URLs.
3. **Stream keys stay out of Nostr** — held node-side (encrypted config / secret
store) or, in local-Bridge mode, in the OS keychain. Public event gets the HLS
URL only.
---
## Architecture — four pieces
- **BitVid Live Client** *(static app; reuses #16)* — browse/watch, render NIP-53,
play HLS/WebRTC/external, chat + zaps, planned/live/ended state, and the
**archive→VOD** convert flow.
- **BitVid Live Studio** *(static app; new, this plan)* — create live-event
metadata, publish planned→live→ended NIP-53 updates, show OBS/encoder ingest
settings, connect to Bridge/Node, monitor health, manage restream outputs,
publish the recording.
- **Media Node** *(server-side; **self-hosted by the creator**, never bitvid-run)*
— RTMP/SRT/WHIP ingest → HLS/WebRTC out, recording, S3-compatible upload, status
API, optional restream. **Built on
[MediaMTX](https://mediamtx.org/docs/kickoff/introduction)** — not from scratch.
Per DECISION 7 it's the **same binary as the Bridge, run headless**.
- **BitVid Bridge** *(the self-hosting vehicle — desktop app or headless node)* —
local RTMP/SRT receiver for hardware encoders, one-input→many-outputs restream,
local recording, archive upload, OBS-WebSocket control. **This is how a creator
self-hosts** (DECISION 1b) and the seed of a future "BitVid Box".
New client modules (matching repo style): `js/services/liveEventService.js`,
`liveChatService.js`, `liveBridgeClient.js`, `livePlaybackService.js` (or extend
#16's), `liveArchiveService.js`; UI `js/ui/views/LiveStudioView.js`,
`js/ui/components/{LiveBridgeSetup,LiveRestreamOutputs}.js` (LiveCard/LivePlayer/
LiveChatPanel come from #16).
---
## Media Node & Bridge API
One stable path per session: `/live/<creator-pubkey>/<d-tag>`. Ingest via RTMP /
SRT / WHIP (key/token kept secret); playback via HLS (`/index.m3u8`) / WebRTC. The
client publishes only the playback URL into the 30311 `streaming` tag.
Bridge API surface (session lifecycle + SSE/WebSocket status):
`POST /sessions`, `GET /sessions/:id`, `.../start`, `.../end`, `.../outputs*`,
`GET /sessions/:id/status|manifest`, `GET /events` (SSE stream of
`input_connected|input_live|input_lost|recording_finalized|upload_complete|…`).
The client listens and decides when to sign+publish NIP-53 updates (DECISION 2).
---
## Outgoing streams — three distinct concepts
- **A. Primary BitVid output** — OBS/encoder → Media Node → HLS/WebRTC → the URL
that goes in the 30311 `streaming` tag.
- **B. External restream outputs** *(`FEATURE_LIVE_RESTREAM`)* — the node forwards
the same encoded stream to YouTube/Twitch/zap.stream/custom RTMP/SRT.
**Stream-copy only** in v1 (`-c copy`, no transcode — MediaMTX `runOnReady` +
FFmpeg forward). Output keys stay node-private; public mirrors optionally added
as `['r', <public-watch-url>]`.
- **C. Archive / VOD output** — on end, MediaMTX records fMP4/TS → finalize →
upload to R2/B2/S3/MinIO → produce `recording.mp4` + `.info.json` (+ optional
thumbnail/torrent). This maps **directly onto bitvid's existing storage-pointer
model**: the `['s', '<provider>:<prefix>']` tag + `js/utils/storagePointer.js`
already derives `<prefix>.info.json`, so the archived stream becomes an ordinary
bitvid video with zero new playback/metadata code.
---
## Archive → VOD publishing (reuses everything)
When the Bridge returns a recording manifest (url, thumbnail, duration, sha256,
infoJson, optional torrent/magnet/webseed), the Studio shows **"Publish recording
as a BitVid video."** On click bitvid publishes:
1. **Updated 30311** — `status=ended` + `recording=<url>`.
2. **Canonical bitvid video** — **kind 30078** (Content Schema v3, the same event
the upload modal produces) with the hosted URL + `s` pointer.
3. **Optional NIP-71 mirror** — bitvid's addressable **34235** (normal) via the
existing mirror service, so other apps see the VOD (per the nip71 plan; note
bitvid mirrors to 34235/34236, not raw kind 21).
This is a specialized upload-modal flow, not a new publishing system.
---
## Playback strategy
Live: **HLS first** (works everywhere via #16's hls.js), then WebRTC later
(DECISION 4), then external-embed fallback. Mirror bitvid's URL-first ethos:
**primary live URL → external mirror → ended recording (VOD).** Optional
**P2P-assisted HLS** (P2P Media Loader) offloads Media Node egress once a stream
has enough concurrent viewers — see the ingest plan + Advantages above. The ended
**recording gets full classic WebTorrent** (seed + webseed), like any bitvid video.
---
## Build order & the static-first principle
> **PRIORITY (maintainer): pour effort into the static bitvid client before
> branching into any separate app.** Everything through the Go-Live MVP below lives
> **entirely in the static client** — no Bridge, no VPS, no bitvid infra. The
> Bridge (a second application) is deliberately deferred until this is shipped and
> maximally developed. See `docs/bitvid-bridge-plan.md`.
Recommended sequence across the live/short work:
```
1. Shorts (#16b) ← smallest, ready to build, unrelated to live
2. Live ingest (#16) ← the WATCH foundation (Live tab, player, hls.js,
chat, zaps) — a hard dependency of publishing
3. Go-Live MVP (#16c P0–P1) ← Tiers 1 & 2 (external embed + serverless HLS);
CLIENT-SIDE ONLY, no Bridge / no VPS / no infra
── ship it; validate that people actually want live ──
4. (only if warranted) the Bridge app ← Tier-3 sovereignty/power features;
its own repo/toolchain/code-signing — a later track
```
The Go-Live MVP (steps 1–3) is the "as much static bitvid as possible" target: a
real, working live experience where the creator brings their own provider
(StreamYard→YouTube, or OBS→Livepeer/Mux) and bitvid does the Nostr-native part —
publish, discover, watch, chat, zap, archive→VOD. Only after that proves out does
a second app (the Bridge) earn the investment.
---
## Phases
- **Phase 0 — Flags + schema (small).** `FEATURE_LIVE_PUBLISH/BRIDGE/RESTREAM`;
30311/1311 **builders** + `updateLiveStreamStatusEvent` + `getLiveAddress`
(coordinate with #16's parsers); `docs/live.md`; schema-doc update; unit tests.
- **Phase 1 — Provider-agnostic publishing (small; NO infra — Tiers 1 & 2).**
"Create Live Stream" modal: title/summary/thumbnail/tags/starts + a paste-in
`streaming` URL / platform link; publish planned→live→ended; chat + zaps against
the live address (reusing #16). Playback adapter picks **external embed**
(Tier 1: YouTube/Twitch) or **native HLS** (Tier 2: Livepeer/Mux/Cloudflare).
*This is BitVid Live v0, needs zero servers, and covers the recommended no-VPS
path (Tier 2 / Livepeer).*
- **Phase 2 — Bridge API client + mock backend (medium).** `liveBridgeClient`,
Pair-Bridge UI, mocked `/sessions`, setup wizard, status dashboard,
recording-ready flow — builds the client contract before real infra exists.
- **Phase 3 — Self-hosted Media Node MVP (large, server-side).** A **reference
node the creator runs themselves** (never bitvid-operated) around MediaMTX:
session API, NIP-98-verified auth, token'd RTMP ingest, HLS playback, start/stop
detection, status endpoint. OBS → the creator's node → bitvid watches. No keys in
Nostr. Per DECISION 7 (Bridge plan) this is the **same binary as the Bridge, run
headless** — so Phase 3 and Phase 7 share code. *This is BitVid Live v1.*
- **Phase 4 — Recording → archive → VOD (medium).** MediaMTX recording → finalize
→ S3 upload → manifest → the archive→VOD publish flow above.
- **Phase 5 — Restream outputs (medium; `FEATURE_LIVE_RESTREAM`).** Output manager,
provider presets (YouTube/Twitch/zap.stream/custom), copy-only forward,
retry/backoff, per-output status. One canonical `streaming` URL; mirrors are
optional `r` links.
- **Phase 6 — OBS Companion (medium).** Start/stop OBS + read bitrate/dropped
frames via OBS-WebSocket (built into OBS 28+). Prefer routing through the local
Bridge to avoid browser↔localhost/mixed-content pain.
- **Phase 7 — Local Bridge app (large; `FEATURE_LIVE_BRIDGE`).** Packaged desktop
helper: local RTMP/SRT receiver, forward to Node + external, local record +
archive upload, embedded Studio webview (signs) + optional localhost API.
*BitVid Live v2 / "BitVid Box" seed.* **Full plan: `docs/bitvid-bridge-plan.md`**
(cross-platform app; note DECISION 7 there — the Bridge and this Media Node can
share one codebase running headless vs with-UI).
- **Phase 8 — WebRTC low-latency (later, DECISION 4).**
---
## Engineering rules (invariants)
1. **BitVid signs; the Bridge routes** — the node never owns the creator's Nostr
identity.
2. **Public playback URLs are safe; ingest URLs are secret** — publish the
`.m3u8`, never `rtmp://…?key=SECRET`.
3. **Pass-through first, transcode later** — copy the H.264/AAC stream in v1.
4. **One canonical stream, many optional mirrors** — the 30311 has one `streaming`
URL; external platforms are outputs, not the source of truth (unless the creator
picks an external provider as primary in Phase-1 mode).
5. **Archived live streams become normal BitVid videos** — no separate archive
system; reuse the `s`-tag / info.json / 30078 model.
---
## Risks / watch-items
- **bitvid stays static; infra is the creator's.** The Media Node/Bridge is real
server infra, but per the **blanket rule it's never bitvid-operated** — it's
self-hosted by the creator or replaced by an external provider. So the ops/cost
burden lives with whoever self-hosts, not bitvid.network. The static client is
unaffected; Phase-1 external mode needs no infra at all.
- **Self-hoster burden (DECISION 1b).** Bandwidth/storage cost, encoder config, and
keeping the node reachable fall on the self-hoster. Good docs + a turnkey
headless Bridge (one binary) are the mitigation.
- **Signing during long streams (DECISION 2).** Tab-open (A) is fragile for long
broadcasts; NIP-53 lets clients treat a live event with no update in ~1h as
ended, so plan a heartbeat and a graceful "stale → ended" story.
- **CORS / cross-origin HLS** — segments served by the (self-hosted) node's origin
must allow the bitvid origin (same class as the storage/edge CORS work).
- **Encoder compatibility** — pass-through requires H.264/AAC + sane keyframes;
document encoder settings; reject or warn on incompatible input.
- **Scope discipline** — publishing is genuinely large; ship Phase 1 (no infra)
first and treat the Media Node as a separate service milestone.
---
## Sources
- **NIP-53** (Live Activities; kind 30311, live chat 1311):
https://nips.nostr.com/53
- **MediaMTX** (ingest/playback/record/forward/control API):
https://mediamtx.org/docs/kickoff/introduction ·
[forward](https://mediamtx.org/docs/usage/forward) ·
[record](https://mediamtx.org/docs/usage/record)
- **OBS**: [SRT guide](https://obsproject.com/kb/srt-protocol-streaming-guide) ·
[WebSocket remote control](https://obsproject.com/kb/remote-control-guide)
- Existing bitvid code this reuses: `js/nostrEventSchemas.js` (HTTP_AUTH/27235,
30078 video), `js/utils/storagePointer.js` (`s` tag → `.info.json`),
`docs/live-ingest-plan.md` (#16 watch side), `docs/nip71-migration-plan.md`
(mirror to 34235/34236), `js/constants.js` (`FEATURE_*` flags).