peertube

peertube · sources

PEERTUBE — federated-video SOURCE. Search the open PeerTube fediverse, pick a video, and its picture streams in as a CLEAN (untainted) WebGL video texture plus stereo audio — a real downstream-usable source, NOT play-only. MODEL: a debounced search box queries Sepia Search (sepiasearch.org, the official PeerTube meta-index — CORS-open + anonymous, NO proxy needed); results list title, channel@host, duration, a LIVE badge, and a thumbnail. Click a result → the node's tuner fetches that video's per-instance public API (https://<host>/api/v1/videos/{uuid}) and resolves a playable stream: it PREFERS the HLS master playlist (streamingPlaylists[0].playlistUrl → attached via hls.js for adaptive playback) and FALLS BACK to the highest-resolution progressive MP4 (files[].fileUrl, attached as a plain <video src>). The stream attaches to a NODE-owned <video crossorigin="anonymous"> (owned by $lib/ui/media/node-hls-source-registry on graph lifetime, not by any surface) → the engine samples it into the FBO (the `video` output) + taps stereo audio (audio_l / audio_r via MediaElementSource → ChannelSplitter, with the shared keep-alive so an unpatched source keeps decoding at full rate). WHY THE TEXTURE + AUDIO ARE CLEAN (verified): PeerTube sends Access-Control-Allow-Origin:* on the FINAL media hop (master .m3u8 + the fragmented-mp4 / mpeg-ts segments) under a favorable `credentialless` COEP posture, so a crossorigin <video> fed by hls.js both PLAYS and yields an untainted texture — unlike archive.org video (play-only). IO — Inputs: play_trigger (gate, edge=trigger — a rising edge toggles play/pause), next_trigger (gate, edge=trigger — a rising edge loads the next search result, wrapping). Outputs: video (the live frame texture), audio_l / audio_r (stereo, silent ConstantSource placeholders until a stream attaches), loaded (trigger — one pulse when a new video finishes loading), ended (trigger — pulses when the video reaches its end), playing (gate — HIGH while actually playing), playhead (CV — 0..1 normalized position). CV/PATCHING: all inputs + outputs live in the faceplate's jack rail (top-left/top-right affordances → INPUT/OUTPUT → grouped Gates / CV / Audio / Video rows) — there are NO raw side jacks (#767 standard). Trigger inputs are main-thread edge-detected the established video-module way (a single bridge-written cv-param read per tick — never a whole-AnalyserNode rescan). USAGE: type a term, press Enter (or wait for the debounce), pick a video, and route VIDEO into a mixer/keyer/OUTPUT and audio_l/audio_r into AUDIO OUT or a SCOPE/SYNESTHESIA for audio-reactive visuals; clock the next_trigger from a sequencer to channel-surf the fediverse. GRACEFUL: ~1/6 instances misconfigure CORS (raw S3 with no ACAO) → the element taints / the HLS load fails fatally → the surface degrades to "display unavailable", shows a clear status, and AUTO-SKIPS to the next result (it never crashes, taints the texture, or hangs on loading). Only { searchTerm, uuid, name, selectedHost } persist on the node + sync to rack-mates (everyone resolves + plays the same video locally); transient playback state stays render-local (never a per-frame synced-store write). The AUDIO TRAP: the <video> is created muted for autoplay, then un-muted ONLY after the MediaElementSource tap succeeds, so audio routes into WebAudio without native speaker double-output. LEGAL: federated PUBLIC videos, not hosted by patchtogether — an in-surface disclaimer + a per-video attribution link to the creator's watch page + attribution to PeerTube + Sepia Search ship with it.

A federated-video SOURCE that streams public videos from the PeerTube fediverse. Type a term into the search box (debounced ~350ms, rate-limited to ~50 calls / 10s) and the node's tuner queries Sepia Search — the CORS-open meta-index of the PeerTube federation — for a results list (title, channel@host, duration, thumbnail). Click a result and it hits that instance's video-details API to resolve the HLS master playlist (.m3u8), then attaches it via hls.js (or progressive MP4 / Safari-native HLS) to a NODE-owned crossorigin video element (the element, its demuxer, the engine attach, the audio wire, the catalogue and both trigger polls belong to $lib/ui/media/node-hls-source-registry on GRAPH lifetime — no surface owns any of it). The engine samples that element into a WebGL framebuffer; the fragment shader is a straight passthrough of the live frame (and draws a dim near-black idle gradient when nothing is loaded), so the video out is a genuine downstream-usable texture, not play-only. Unlike ARCHIVIST (which is VIDEO-ONLY because archive.org's final media hop is CORS-tainted, so its audio jacks are dead), PeerTube sends Access-Control-Allow-Origin:* on the master playlist + segments, so the texture is UNTAINTED and the stereo audio track is fully extractable and patchable on audio_l/audio_r (split from a MediaElementSource via a 2-channel splitter, with a silent gain-0 keep-alive bridged to the destination that keeps the element decoding at full rate even when audio isn't patched). Frames upload at the source's decode cadence (requestVideoFrameCallback; Firefox falls back to a currentTime-advance check) downscaled to the engine resolution so a high-bitrate stream doesn't flood GPU texture traffic. Only the selection (selected host, video uuid, name) plus the last search term are persisted to the node so rack-mates resolve the SAME stream; ~1/6 instances misconfigure CORS (raw S3, no ACAO) and a stream that taints, errors, or stalls past a 14s timeout is marked "display unavailable" and auto-skips to the next result after a short beat rather than hanging. The faceplate ranks ONE control (GAIN, as a fader) and carries everything else on its dock full-view body (none of these are module params): the search input, a "↻ next" button, the live 16:9 preview of the module's OWN engine output with a SCREEN on/off switch, a Play/Pause transport with a playhead bar, the scrollable results list, an attribution link to the selected video's watch page on its home instance, and the Sepia Search / PeerTube attribution disclaimer. Usage: drop it as a video source and feed its output into a mixer or any video module; patch a clock into next_trigger to channel-surf the results hands-free, and patch the loaded/ended event outs or playhead CV downstream to sequence around the playback.

the faceplate

peertubeplay_triggergatenext_triggergatevideovideoaudio_laudioaudio_raudioloadedgateendedgateplayinggateplayheadcvaudiocvgatepitch
2 inputs · 7 outputs · 3 params

inputs

idcablewhat it does
play_triggergateTrigger (gate cable, edge:'trigger'): a rising edge toggles the current video between play and pause. Routed through the CV bridge as the synthetic cv_play_trigger param, which the faceplate polls + edge-detects (<0.5 -> >=0.5); it fires once per rising edge, not while held. Patch a clock or any gate here for hands-free transport.
gate / trigger; modulates cv_play_trigger (summed directly (the destination DSP scales it)); trigger — fires once per rising edge
next_triggergateTrigger (gate cable, edge:'trigger'): a rising edge advances to the NEXT result in the search list (wrapping to the first; if the list is empty it re-runs the last search). Routed through the CV bridge as the synthetic cv_next_trigger param and edge-detected by the card; fires once per rising edge, not while held. Patch a clock here to channel-surf the results in time.
gate / trigger; modulates cv_next_trigger (summed directly (the destination DSP scales it)); trigger — fires once per rising edge

outputs

idcablewhat it does
videovideoThe loaded stream's live frame as a video texture — untainted and downstream-usable because PeerTube sends CORS/ACAO on its media. Dim near-black idle gradient when nothing is loaded; goes to the auto-skip 'unavailable' fallback for a CORS-misconfigured instance.
RGB video stream
audio_laudioLeft channel of the stream's stereo audio (output 0 of the channel splitter fed by the video element's MediaElementSource — working audio, unlike ARCHIVIST). A silent ConstantSource (offset 0) placeholder until a stream attaches and the audio tap is wired.
audio signal
audio_raudioRight channel of the stream's stereo audio (output 1 of the same channel splitter). A silent ConstantSource (offset 0) placeholder until a stream attaches and the audio tap is wired.
audio signal
loadedgateTrigger out (gate cable, edge:'trigger'): one short ~5ms rising-edge pulse each time a newly selected video finishes loading and its audio is wired. Patch into a downstream clock/reset/sample-and-hold to react to a track change.
gate / trigger; trigger — fires once per rising edge
endedgateTrigger out (gate cable, edge:'trigger'): one short ~5ms rising-edge pulse when the playing video reaches its end. Patch into next_trigger (self or another source) to auto-advance, or into any downstream trigger consumer.
gate / trigger; trigger — fires once per rising edge
playinggateGate out (gate cable, edge:'gate'): held HIGH while the video is actually playing, LOW while paused, loading, idle, or unavailable. Use as a run/transport gate for downstream modules.
gate / trigger; gate — acts while the level is high (reacts to both edges)
playheadcvCV out: the normalized playback position, 0..1 (currentTime / duration), updated as the video plays. Patch into any CV destination to scrub or modulate downstream gear in sync with the video timeline.
control voltage (CV)

params

idlabelrangedefaultcurve
gainGain0..2—linear
cv_play_triggerPlay trigger0..10linear
cv_next_triggerNext trigger0..10linear

controls

controlwhat it does
Next triggerNext trigger (hidden synthetic param, 0 to 1, default 0): the CV bridge writes the next_trigger input's gate level here; the faceplate polls readParam and edge-detects a rising edge to load the next search result. Not a user-facing knob.
Play triggerPlay trigger (hidden synthetic param, 0 to 1, default 0): the CV bridge writes the play_trigger input's gate level here; the faceplate polls readParam and edge-detects a rising edge (<0.5 -> >=0.5) to toggle play/pause. Not a user-facing knob.
GainGain — output level for the picture (0 to 2, linear; default 1.0). The passthrough shader multiplies the sampled RGB by this (the uGain uniform), so 0 blacks the output, 1.0 is the identity and 2 doubles it (clipped at full scale by the 8-bit framebuffer). It scales the VIDEO only — the audio_l/audio_r outs are unaffected.

source

peertube.ts on GitHub.

Generated from packages/web/src/lib/{audio,video}/module-registry.ts · repo