Skip to content

"Cut after this airs": scheduled safe cut for match-end reactions#33

Merged
Soulhackzlol merged 2 commits into
mainfrom
feat/cut-after-this-airs
Jul 5, 2026
Merged

"Cut after this airs": scheduled safe cut for match-end reactions#33
Soulhackzlol merged 2 commits into
mainfrom
feat/cut-after-this-airs

Conversation

@Soulhackzlol

Copy link
Copy Markdown
Owner

What

A new ⏱ Cut after this airs button, visible while a delay is active. Press it the moment your match reaction ends: InstantClone records that exact live-edge timestamp, lets everything up to it reach your viewers, then fires the normal IDR-aligned cut to live on its own.

Why

The whole point of running a competitive delay is stream-snipe protection, but ending it gracefully was manual: finish the match, react, then count 30 seconds in your head before hitting Cut so the reaction isn't clipped off the delayed output. Get it wrong in one direction and viewers lose the reaction; wrong in the other and you sit on a dead delay. Now it's one press at a moment you already recognize.

How it works

  • The controller stores the live-edge input timestamp as a mark. Each egress pump's throttled cut-check calls maybe_fire_safe_cut, which fires only when the slowest live destination has aired past the mark, so a fast destination can never cut a slow one short of it. A compare-exchange makes exactly one pump the firing pump; the cut itself is the same stop_delay -> compute_delay_cut path as a manual Cut, so it inherits the IDR alignment and connection preservation.
  • The mark clears on manual Cut, on disarm, and on a fresh OBS publish session (the mark belongs to the old session's timeline and could never be reached on the new one).
  • New endpoints: POST /cut-after (409 when no delay is active) and POST /cut-after/cancel. /state gains safe_cut_pending and safe_cut_remaining_ms.

UX

  • The button lives under the Cut button and only exists while a delay is active. While a mark is pending it becomes a live countdown ("auto-cut in ~27s"), and tapping it again cancels without cutting.
  • A pure-CSS hovercard (works with keyboard focus too) explains the trick with a press -> reaction airs -> auto-cut -> live timeline.
  • The setup wizard teases the flow before the first stream, and the onboarding tour's Arm/Activate step documents it.

Verification

  • cargo fmt --check, cargo clippy --all-targets -- -D warnings, and cargo test --release all clean: 234 passed (5 new controller tests: refusal without an active delay, schedule/cancel round-trip, slowest-consumer firing gate, clearing on manual cut/disarm, clearing on publisher reconnect).
  • New e2e scenario F runs the real thing: ffmpeg publishes live, the script arms and activates a 2s delay, checks the 409 refusal, schedules and cancels without cutting, then re-schedules and asserts the proxy auto-cuts back to passthrough with the sink's connection intact (exactly one publish accept). All 6 e2e scenarios pass locally.

Streamers finishing a match on a 30s delay had to count the delay in
their head before cutting, or risk clipping their win/lose reaction off
the delayed output. Now they press one button at the moment the reaction
ends: the controller records that live-edge timestamp and fires the
normal IDR-aligned cut once the slowest live destination has aired past
it. Nothing before the press ever gets clipped, and the downstream
connection survives the cut like any manual one.

Details:

- Controller: safe_cut_input_ts mark + schedule/cancel/pending/remaining
  API. maybe_fire_safe_cut runs in each pump's throttled cut-check and
  gates on the slowest live consumer, with a compare_exchange so exactly
  one pump fires. The mark clears on manual cut, disarm, and publisher
  reconnect (a mark on the old session's timeline could never be
  reached). current_delay_ms now shares the slowest-consumer helper.
- Web: POST /cut-after (409 without an active delay) and
  POST /cut-after/cancel; /state gains safe_cut_pending +
  safe_cut_remaining_ms.
- Dashboard: the button appears under Cut only while a delay is active,
  turns into a live countdown while pending (tap again to cancel), and
  carries a hovercard explaining the press -> airs -> auto-cut flow.
  The setup wizard and the onboarding tour both introduce it.
- Tests: 5 controller tests (refusal, schedule/cancel, slowest-consumer
  gate, clears) plus e2e scenario F driving the endpoint against a live
  ffmpeg stream. 234 tests green; fmt + clippy -D warnings clean; all 6
  e2e scenarios pass locally.
The "Cut after this airs" button rendered as its own full-width row
under Cut delay, so the whole control column grew and shrank as the
delay state changed. Feedback from live use: the layout jump is worse
than the bigger button.

Now both buttons share one row at a 3:1 split. The compact button
reads "Cut after" (countdown state: "~27s x") and the full story
stays in the hovercard; the hero subtext carries the readable
countdown ("Auto-cut armed - going live in ~27s...") since it never
affects layout.

Also fixes a real bug the restyle surfaced: .cutafter-wrap declared
display:block, which overrides the UA's [hidden] rule - the wrap
would not collapse when hidden. Added an explicit [hidden] override.
@Soulhackzlol
Soulhackzlol merged commit 90d32c0 into main Jul 5, 2026
4 checks passed
@Soulhackzlol
Soulhackzlol deleted the feat/cut-after-this-airs branch July 5, 2026 12:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant