mirror of
https://github.com/PR0M3TH3AN/bitvid.git
synced 2026-10-27 07:52:24 +00:00
[docs] Dev plan for BitVid Live publishing / "Go Live" (#16c)
Third live/short plan, sequenced after #16 (ingest) + #16b (shorts). Turns the maintainer's BitVid Live design into a repo-accurate, convention-aligned plan: - Core decision: Nostr-native control layer, NOT a hosting backend. Static client stays static (signs, no keys, no server); the one thing a browser can't do (receive/route live media) is isolated in an OPTIONAL server-side Media Node/Bridge (MediaMTX). External-provider-only mode (Phase 1) needs zero infra. - Cross-linked to docs/live-ingest-plan.md (#16): the watch side (Live tab, player, chat, zaps, hls.js) is a dependency and reused; this doc adds the builders + Studio + Media Node/Bridge. Shared Phase 0 schema in nostrEventSchemas. - Verified against real code: NIP-98 HTTP-Auth (kind 27235) builder exists for bridge auth; s-tag + js/utils/storagePointer.js -> .info.json is exactly how archive->VOD maps (ended stream becomes an ordinary kind-30078 video + 34235 mirror); canonical video kind 30078. - Decisions surfaced: hosting model, signing-during-stream (tab-open vs NIP-46), pass-through vs transcode, WebRTC scope, local Bridge scope, flag reconciliation (FEATURE_LIVE_INGEST/PUBLISH/BRIDGE/RESTREAM). - 9 phases (0 flags+schema → 1 external-provider no-infra → 2 mock bridge → 3 Media Node MVP → 4 recording/VOD → 5 restream → 6 OBS companion → 7 local Bridge → 8 WebRTC), 5 engineering-rule invariants, risks, sources. Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
c5f91ce683
commit
18d4d29616
2 files changed
+313
No files matched your search
@@ -0,0 +1,310 @@
|
||||
# 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.** Sequenced **after** #16 (live ingest) and #16b
|
||||
(shorts) ship. This is the largest of the three plans and the only one that
|
||||
introduces **server-side media infrastructure** — read the Core Decision first.
|
||||
|
||||
---
|
||||
|
||||
## Core decision
|
||||
|
||||
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, optional** service. Split:
|
||||
|
||||
```
|
||||
BitVid static client = identity, metadata, NIP-53 publishing, discovery, chat,
|
||||
zaps, player UI, archive→VOD publishing
|
||||
BitVid Media Node/Bridge = RTMP/SRT/WHIP ingest, HLS/WebRTC output, recording,
|
||||
restreaming, status API (server-side, optional)
|
||||
```
|
||||
|
||||
This keeps BitVid's architecture intact while adding broadcast. Crucially,
|
||||
**external-provider-only mode needs no BitVid infrastructure at all** — a creator
|
||||
streams to zap.stream/YouTube/Twitch and BitVid just publishes the NIP-53 event
|
||||
around it. That's Phase 1 and delivers value with zero servers.
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
|
||||
---
|
||||
|
||||
## Decisions needed
|
||||
|
||||
> **DECISION 1 — Media hosting model.** Who runs the Media Node? (a) a **managed
|
||||
> BitVid Media Node** (bitvid-operated infra — easiest for creators, but ops/cost
|
||||
> and least "self-sovereign"); (b) **self-hosted** (creator/instance runs their
|
||||
> own node — most aligned, higher friction); (c) **external-provider-only** (no
|
||||
> node; stream to zap.stream/YouTube and just publish the NIP-53 event).
|
||||
> *Recommendation: ship (c) first — zero infra, immediate utility — then offer (b)
|
||||
> self-host with a reference node, and consider (a) managed as an opt-in later.*
|
||||
|
||||
> **DECISION 2 — Signing during a live stream.** The Media Node can detect
|
||||
> start/stop, but must NOT hold the creator's Nostr key. How do status updates get
|
||||
> signed? (A) **BitVid tab stays open**, browser signs status=live/ended;
|
||||
> (B) **NIP-46 remote signer** so the node can request signatures; (C) node
|
||||
> publishes *provider-status* under its **own** pubkey while the creator's canonical
|
||||
> 30311 stays user-signed; (D) managed publishing account (least sovereign).
|
||||
> *Recommendation: A for MVP, explore B (NIP-46) later. Never D.*
|
||||
|
||||
> **DECISION 3 — Transcode now or pass-through only?** v1 should be **stream-copy
|
||||
> only** (`-c copy`, no transcode) — require OBS to send H.264/AAC, ~2s keyframes.
|
||||
> Transcoding (multi-bitrate ladders) adds heavy CPU/GPU + cost. *Recommendation:
|
||||
> pass-through v1; transcode profiles much later, if ever.*
|
||||
|
||||
> **DECISION 4 — WebRTC (low-latency) in scope for v1?** HLS (~6–30s latency) is
|
||||
> the safe default and works everywhere via hls.js (from #16). WebRTC (<500ms) is
|
||||
> nicer but more complex. *Recommendation: HLS first; WebRTC as a later phase.*
|
||||
|
||||
> **DECISION 5 — Local Bridge app in scope, or Media Node only?** The local Bridge
|
||||
> (desktop helper for hardware encoders / multi-restream / local recording) is
|
||||
> powerful but a separate packaged app. *Recommendation: NOT required for MVP —
|
||||
> OBS → public Media Node covers most creators; Bridge is a later phase and the
|
||||
> seed of a future "BitVid Box".*
|
||||
|
||||
> **DECISION 6 — Flag naming reconciliation.** #16 already established
|
||||
> `FEATURE_LIVE_INGEST` (watch) + `FEATURE_LIVE_CHAT_POST`. This plan adds
|
||||
> `FEATURE_LIVE_PUBLISH` (Studio / Go Live), `FEATURE_LIVE_BRIDGE` (Media
|
||||
> Node/Bridge integration), `FEATURE_LIVE_RESTREAM` (external outputs). Confirm we
|
||||
> keep the specific `*_INGEST` / `*_PUBLISH` split rather than a single umbrella
|
||||
> `FEATURE_LIVE`. *Recommendation: keep the specific flags; publishing needs
|
||||
> ingest on (you watch your own stream), so `FEATURE_LIVE_PUBLISH` implies
|
||||
> `FEATURE_LIVE_INGEST`.*
|
||||
|
||||
---
|
||||
|
||||
## Config flags (off = no trace)
|
||||
|
||||
All default `false`, threaded `config/instance-config.js` → `js/config.js` →
|
||||
`js/constants.js` like `FEATURE_NIP71_INGEST`:
|
||||
|
||||
- `FEATURE_LIVE_INGEST` — watch side (from #16; dependency).
|
||||
- `FEATURE_LIVE_PUBLISH` — the Studio / Go-Live publishing UI.
|
||||
- `FEATURE_LIVE_BRIDGE` — Media Node / Bridge session API integration.
|
||||
- `FEATURE_LIVE_RESTREAM` — external restream output management.
|
||||
|
||||
When `FEATURE_LIVE_PUBLISH` is off: no Studio, no "Go Live" entry point, no My
|
||||
Videos → Live sub-tab, no Bridge/session UI, no live-event **builders** wired into
|
||||
any surface. Bridge/restream UI additionally gated on their own flags. Config
|
||||
flags are the only footprint.
|
||||
|
||||
---
|
||||
|
||||
## 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.
|
||||
- **BitVid Media Node** *(server-side; new)* — RTMP/SRT/WHIP ingest → HLS/WebRTC
|
||||
out, recording, S3-compatible upload, status API, optional restream. **Built on
|
||||
[MediaMTX](https://mediamtx.org/docs/kickoff/introduction)** (handles the
|
||||
protocol matrix, recording, hooks, control API) — not from scratch.
|
||||
- **BitVid Bridge** *(local helper; optional, later)* — local RTMP/SRT receiver
|
||||
for hardware encoders, one-input→many-outputs restream, local recording, archive
|
||||
upload, OBS-WebSocket control bridge. Foundation for 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).**
|
||||
|
||||
---
|
||||
|
||||
## 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 — External-provider publishing (small; NO infra).** "Create Live
|
||||
Stream" modal: title/summary/thumbnail/tags/starts + a paste-in `streaming` URL;
|
||||
publish planned→live→ended; chat + zaps against the live address (reusing #16).
|
||||
*This is BitVid Live v0 and needs zero servers — DECISION 1(c).*
|
||||
- **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 — Media Node MVP (large, server-side).** `bitvid-media-node` around
|
||||
MediaMTX: session API, NIP-98-verified auth, token'd RTMP ingest, HLS playback,
|
||||
start/stop detection, status endpoint. OBS → node → bitvid watches. No keys in
|
||||
Nostr. *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, localhost API + pairing. *BitVid Live v2 / "BitVid Box" seed.*
|
||||
- **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
|
||||
|
||||
- **Departure from static-only.** The Media Node/Bridge is real server infra with
|
||||
real ops + cost — the biggest philosophical + practical shift. Mitigated by
|
||||
keeping it **optional** (Phase-1 external mode needs none) and off by default.
|
||||
- **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 node's origin must allow the
|
||||
bitvid origin (same class as the storage/edge CORS work).
|
||||
- **Cost & abuse of a managed node (DECISION 1a)** — bandwidth/storage cost,
|
||||
transcode temptation, and moderation of who can ingest. Self-host (1b) pushes
|
||||
this to the operator.
|
||||
- **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).
|
||||
@@ -1353,6 +1353,9 @@ Threat model / cautions (write these into the feature + docs):
|
||||
populate `streaming`/`recording`, and HLS playback support in the player.
|
||||
|
||||
### 16c. Publish live streams — "Go Live" from bitvid (like zap.stream)
|
||||
> **Dev plan: `docs/live-publish-plan.md`** (FUTURE — after #16 + #16b; static
|
||||
> client + optional Media Node/Bridge split; MediaMTX; NIP-98 bridge auth;
|
||||
> archive→VOD reuses the s-tag/info.json model. Decisions open.)
|
||||
- [ ] Let users **broadcast their own stream** (camera / desktop / arbitrary source)
|
||||
to Nostr via bitvid, the way zap.stream does: publish a NIP-53 kind-30311 live
|
||||
event (status live → ended), push the media to a streaming endpoint (HLS), and
|
||||
|
||||
Reference in new issue
Block a user