11 KiB
Bitvid — AI Agent Guide (AGENTS.md)
This document tells AI agents how Bitvid works now and how to extend it going forward. It focuses on the newly adopted URL‑first playback with WebTorrent fallback strategy, minimal code touchpoints, and reliable interop with the Nostr ecosystem.
As you work in the repo, please add detailed comments to help others understand what and why your code is doing.
1) Mission & Product Direction
Goal: Stream videos reliably and cheaply.
- Primary transport: a hosted video URL (MP4/WebM/HLS/DASH) that plays directly in the browser.
- Secondary transport (fallback & cost control): a WebTorrent magnet enhanced with
ws=(web seed) andxs=(.torrent URL). - Behavior: Try
urlfirst. If it’s missing or unreachable, fallback to WebTorrent using the same post’s magnet. If the magnet includesws/xs, WebTorrent can bootstrap via HTTP and/or peers. - Nostr interop: Keep existing kind
30078events for Bitvid’s app data; optionally mirror a NIP‑94 kind1063event so other clients can discover the same hosted video URL. - Resilience: Light link‑health probing to hide/flag dead URLs and avoid broken playback.
2) High‑Level Architecture
-
Frontend app (vanilla JS + HTML/CSS)
- Upload modal accepts: Title (required), URL or Magnet (at least one required), Thumbnail, Description. Optional fields for
wsandxshelp P2P bootstrap. - Feed / Cards render video posts, each carrying both
data-play-urlanddata-play-magnetattributes. - Player uses
playVideoWithFallback({ url, magnet }): probeurl; if OK, play via<video>; else calltorrentClient.streamVideo(magnet, videoEl).
- Upload modal accepts: Title (required), URL or Magnet (at least one required), Thumbnail, Description. Optional fields for
-
Nostr client
- Publishes Bitvid’s kind
30078events with content{ version, title, url?, magnet?, thumbnail?, description?, mode }. - Subscribes to Bitvid video events and (optionally) NIP‑94
kind 1063to interop with external clients.
- Publishes Bitvid’s kind
-
Optional Upload API (future): If adding server‑side upload (NIP‑96/98), the client would POST to an uploader, receive an HTTPS URL, then publish events.
3) Data Contracts
3.1 Bitvid app event (kind 30078)
Content JSON (superset; some fields optional):
{
"version": 2,
"title": "<string>",
"url": "https://cdn.example.com/video.mp4", // optional but recommended
"magnet": "magnet:?xt=urn:btih:...&tr=...", // optional but recommended
"thumbnail": "https://.../thumb.jpg", // optional
"description": "<string>", // optional
"mode": "live" | "dev" // optional
}
Validation:
titlerequired; at least one ofurlormagnetrequired.- If
magnetpresent and uploader suppliedwsand/orxs, append them to the magnet before publish.
3.2 NIP‑94 mirror (kind 1063, optional but encouraged)
Tags (typical):
["url", "https://cdn.example.com/video.mp4"]["m", "video/mp4"](orvideo/webm,application/x-mpegURL, etc.)["thumb", "https://.../thumb.jpg"](optional)["alt", "<title or description>"](optional)["size", "<bytes>"](optional)["x", "<sha256-of-file>"](optional integrity)["magnet", "magnet:?xt=...&ws=...&xs=..."](optional for P2P aware clients)
Agents SHOULD keep Bitvid’s 30078 as the source of truth, and publish a lossless mirror into 1063 when possible.
4) UI & UX Flows
4.1 Upload Flow (author)
- Author opens Upload modal.
- Inputs Title, plus either Hosted URL or Magnet (or both). Optionally adds Web seed base (ws) and .torrent URL (xs).
- Client augments magnet with
ws/xsand publishes a kind30078event (and optionally a NIP‑941063).
4.2 Consumption Flow (viewer)
-
Feed renders cards with
data-play-url+data-play-magnet. -
On click:
playVideoWithFallback()probeUrl(url)→ If reachable, set<video>.src = urland play.- Else:
torrentClient.streamVideo(magnet, videoEl).
-
UI hides or badges items whose
probeUrl()fails consistently (cached temporarily).
5) Key Client Functions (reference)
5.1 normalizeAndAugmentMagnet(raw, { webSeed?, torrentUrl?, xs?, extraTrackers? })
- Accepts a magnet URI or bare hex info hash and returns a sanitized magnet.
- Fixes accidentally encoded
xt=urn%3Abtih:segments and tolerates bare hashes by promoting them tomagnet:?xt=urn:btih:<hash>. - Appends only browser-safe WSS trackers plus optional extras, avoiding mixed content.
- Adds HTTPS web seeds (
ws=) and torrent hints (xs=) when provided, skipping insecurehttp://web seeds unless the app itself is served via HTTP. - Preserves existing parameters in their original form—no
new URL()usage and no percent re-encoding of thextpayload.
5.2 probeUrl(url)
HEADrequest to check availability; expects2xxand (ideally)Accept-Ranges: bytes.- Fallback small
GETwithRange: bytes=0-1023can be used if certain hosts blockHEAD. - Returns boolean
ok.
5.3 playVideoWithFallback({ url, magnet })
- Try
playHttp(url)ifprobeUrl(url)passes. - Else try
playViaWebTorrent(magnet). - Show UI error if neither source available.
5.4 playViaWebTorrent(magnet)
- Uses existing
torrentClient.streamVideo(magnet, videoEl)under the hood. - Benefits if magnet contains
ws/xsfor bootstrap.
6) Markup Contracts
-
Cards: add both attributes where applicable:
data-play-url="<encoded URL or empty>"data-play-magnet="<raw magnet or empty>"
-
Click delegate: look for a closest element bearing either attribute; decode both
data-play-urlanddata-play-magnet(if encoded upstream) before passing them to playback logic. Never double-encode magnets when writing them into the DOM.
7) Main vs Unstable — legacy WebTorrent compatibility lessons
Legacy Bitvid posts (v1 / magnet-only) still play on Main. They broke on Unstable because of double-encoded magnets, URL constructors, and strict filters. Apply these rules to stay compatible:
- Raw magnets end-to-end. Store the literal
magnet:?xt=urn:btih:...in the DOM and feed that same string into WebTorrent. If you must encode for transport, be sure to decode once when handling clicks. - No
new URL()for magnets. Appending params withURLSearchParamsornew URLpercent-encodes thextvalue and can corrupt legacy hashes. When you need to add trackers or cache-busting params, concatenate plain text (or usenormalizeAndAugmentMagnet, which already preserves the payload). - Converters should allow magnet-only posts. A note is playable when it exposes
either a usable
magnetor a directurl. Don’t requireversion >= 2to keep legacy uploads alive. - Browser-safe announce list. Ship WSS trackers for browser playback. Skip UDP
trackers in the client path and only add
ws=hints when they’re HTTPS on HTTPS pages (mixed content gets blocked anyway).
8) Server Requirements for Hosted URLs
- CORS: allow the app origin (or
*). - Range requests: ensure
Accept-Ranges: bytes. - Correct MIME:
video/mp4,video/webm,application/x-mpegURL, etc. - Optional: expose
Content-Lengthfor better UX/progress.
9) P2P Notes for Agents
ws=(web seed): HTTP(S) base path where the file is fetchable; enables WebTorrent to download pieces even with zero peers.xs=: HTTP(S) URL of the.torrentmetadata so the client can start promptly without DHT/peer discovery.- Trackers: Keep WSS trackers for peer discovery; they reduce CDN load when peers exist.
10) Interop & Extensions
- Live streams: future option via HLS URLs (MIME
application/x-mpegURL). For Nostr live presence, consider NIP‑53 events alongside VOD 1063 entries. - Adaptive Bitrate + P2P: if ABR is needed with P2P, consider a segment‑sharing overlay (e.g., p2p segment loaders) rather than WebTorrent.
- Uploads (optional): NIP‑96/NIP‑98 flow to obtain a hosted URL, then publish Bitvid/NIP‑94 events.
11) Testing Checklist (agents)
- Form validation: title present; ensure
(url || magnet)is true;wsandxsappended only for magnet. - Probe: healthy URL returns
ok=true; dead URL hides/badges card; cache prevents re‑probing every render. - Playback: URL path plays successfully; disconnect network to force WebTorrent path; verify video plays via magnet.
- CORS & Range: verify no console CORS errors; confirm seeking works.
- Events: verify kind
30078containsurland/ormagnet; optional 1063 mirror created correctly. - Cards: both
data-play-urlanddata-play-magnetpresent; click handler calls fallback player.
12) Guardrails & Do/Don’t
Do
- Fail soft: when URL fails, fall back to magnet without blocking UI.
- Prefer standards: URL + NIP‑94 mirror when possible.
- Degrade gracefully when
ws/xsmissing; torrent should still work with peers.
Don’t
- Don’t require magnet if URL is provided (and vice versa).
- Don’t assume CORS/Range are configured—probe and message helpfully.
- Don’t block the main thread with large HEAD/GETs; keep probes lightweight with timeouts.
13) Quick Reference (snippets)
Bitvid content object (30078)
const content = {
version: 2,
title,
url, // optional but preferred
magnet, // optional but preferred
thumbnail, // optional
description, // optional
mode // "live" | "dev"
};
Magnet normalization & augmentation
import { normalizeAndAugmentMagnet } from "./js/magnetUtils.js";
const { magnet } = normalizeAndAugmentMagnet(inputMagnetOrInfoHash, {
webSeed: hostedUrl ? [hostedUrl] : [],
torrentUrl: xsHint,
extraTrackers: ["wss://tracker.openwebtorrent.com"],
});
// Feed `magnet` directly to playVideoWithFallback / WebTorrent.
Playback with fallback
async function playVideoWithFallback({ url, magnet }) {
if (url && await probeUrl(url)) return playHttp(url);
if (magnet) return playViaWebTorrent(magnet);
throw new Error("No playable source");
}
14) Roadmap Hooks (where to extend)
- Analytics: Keep using your existing 30078 analytics/logging. Add fields for
play_source: "url"|"torrent"to measure cost savings. - Integrity: Optionally compute SHA‑256 of the hosted file (off‑thread) and publish as NIP‑94
["x", hash]tag. - Moderation: Add a background task to periodically probe and mark dead links; publish a lightweight status event so other Bitvid clients learn from each other.
End of AGENTS.md