# TASK 65 — Replay clips: buffer the last 15 seconds, show it, save it > Catalog: [`TASKS.md`](../TASKS.md) — status and requirements live here. > Spec settled with the creator 2026-11-09. **Not a Twitch-clip clone:** the platform owns viewer > clips; this is the creator-side replay buffer YouTube does not give you. ## The rule **15 seconds is a clip.** Options 10 / 15 / 20, default **15**, no slider, no 30. A clip is a moment, not a segment — 3-minute Shorts are the platform's problem, not ours. ## Why the ring holds encoded bytes, not frames A 1080p BGRA composite frame is 8.3 MB; fifteen seconds at 60 fps is **~7.4 GB** of raw frames. The 15 MB figure is *after* compression. So the ring keeps **fragmented MP4** (`frag_keyframe+empty_moov`) that ffmpeg writes to a pipe, in a **byte ring in RAM** — never on disk, never raw. - Fragments are **1 s** (not 2): the ring can only hand back whole fragments, so a longer fragment means the save can miss the punchline. - The buffer is capped **by bytes**, not seconds — bitrate moves. When 15 s would exceed the cap, the clip is simply shorter. - The ring is a member of the process: it dies with the app, so a crash leaves no debris and needs no sweep. **Nothing touches the disk until F10.** ## Trigger **F10** saves the clip (F1–F9 are taken; F11 is the fallback if a game steals F10). No focus switch, no mouse. Deferred deliberately: **voice triggering** (always-listening in a streaming app is a privacy liability, and the game's own audio produces false positives) and **`!clip`** (only for viewers/mods, never the creator, who cannot reach a keyboard mid-moment). ## The replay layer **Scope settled 2026-11-10:** the immediate replay of a fresh clip happens **only in the Live scene**. While that replay is playing, **F10 is inert** ("replay in progress") — that is what replaces the hall-of-mirrors exclusion, which is impossible with one encoder and two muxes: the clip *is* what the stream got. One layer type, added by the creator to the scenes they want; behaviour comes from the scene: | Scene | Behaviour | |---|---| | **Live** | plays the new clip **once**, then **hides** — you're there to see it or you're not | | Every other scene | loops the last **1–3** saved clips **in order** (highlight reel), configurable | - **Audio off by default**, toggleable per layer. The show's sound design is the creator's; a replay should not re-mix it uninvited. - **No exclusion, no second encode.** The clip is a second mux of the frames the stream already got, so it contains whatever viewers saw. Double-clipping is prevented instead: F10 does nothing while the Live replay is playing. ## Storage and retention - Saved to `clips\` beside recordings, as a normal `.mp4`. - **The three most recent are kept**; the fourth deletes the oldest. - **Auto-clip on monetization events** (off by default): a Super Chat / membership / raid saves the preceding 15 s hands-free. Same retention, so it cannot accumulate. - **Disk discipline:** no steady writes while live (the ring is RAM); the free-space guard that already protects REC also disables the ring when space is low — **the recording always wins**. ## Privacy Named in `PRIVACY.md`: clips are local only, the three most recent are kept, older ones are deleted automatically, and nothing is uploaded. The first save says the same in one line, so the policy is not only a document. ## Shipped 2026-11-10 Six commits: ring (`b93744e`), clip mux (`5066d81`), library (`f7d3d13`), F10 + settings (`2865791`), layer model (`a5c2061`), layer playback (`5fcfa54`). 608 tests. Length **10/15 (default 10)**, keep **3/4/5 (default 3)**, off by default, guard while replaying. **Known limits, stated on purpose:** the clip is a second mux of the program, so it contains whatever viewers saw; there is no clean feed and no editor. The feature is a capture. ## Tests - Ring: fragment boundaries respected, byte cap enforced, save emits a playable MP4, oldest pruned. - Layer: once-then-hide in Live, in-order loop elsewhere, silent by default, layer excluded from the clip. - Failure: save with an empty ring (just started) says so instead of writing a 0-byte file.