feat(vision-mixer): stinger transitions - #755
Draft
wagenet wants to merge 1 commit into
Draft
Conversation
wagenet
force-pushed
the
feat/stinger-transitions
branch
from
September 3, 2026 20:37
5ed2c4f to
b3326be
Compare
A stinger plays a keyed clip over the program while another transition runs beneath it. It is triggered through the existing transition endpoint as type "stinger", naming a Media Player block that declares itself a stinger clip source and is wired to a keyed (DSK) input. The clip is held on its first frame while idle. That is what lets a stinger start immediately: armed start is ~0.5 ms and independent of resolution, against up to 30.9 ms at 4K for a clip that was not parked. Declaration is opt-in, so a looping graphic on a keyed input is never parked or unlooped. cut_point_ms says when the transition beneath begins and duration_ms how long it takes, shortened if it would outlast the clip. A clip that cannot play still runs the transition beneath, so a broken file costs the branding rather than the cut. A second trigger while one is in flight is refused. Straight alpha only: premultiplied clips composite about 40% too dark, no compositor operator corrects it, and premultiplication cannot be read from the file, so a binding that declares it is refused. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
wagenet
force-pushed
the
feat/stinger-transitions
branch
from
September 3, 2026 22:02
b3326be to
cd9b383
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
A stinger plays a keyed clip over the program while another transition runs beneath it. It is triggered through the existing transition endpoint as type
stinger, naming a Media Player block that declares itself a stinger clip source and is wired to a keyed (DSK) input.Why it is shaped this way
No new playback code: the Media Player already exposes play/pause/seek/goto/position/duration and a lookup registry. No new animation code either — DSK pads already carry an enable toggle and the clip's own alpha does the reveal, so the only animated thing is the transition beneath, which
TransitionControlleralready runs. A cut is the default value of that parameter rather than a special case.The clip is held on its first frame while idle, which is what lets a stinger start on time:
One frame at 30 fps is 33.3 ms. Armed start is flat in resolution because the frame is already decoded and
play()is only a state change. Declaration is opt-in, so a looping graphic on a keyed input is never parked or unlooped — arming it would pause it and disable looping.cut_point_mssays when the transition beneath starts andduration_mshow long it takes, shortened if it would outlast the clip. The cut point fires on a wall-clock timer: measured drift is 0.15 of a frame over 45 frames, so position-driven triggering would buy nothing. A clip that cannot play still runs the transition beneath, so a broken file costs the branding rather than the cut.Limitations
Straight alpha only. A premultiplied clip composites about 40% too dark (159 correct against 96), no compositor operator corrects it —
addmeasurably returns whatoverreturns — and premultiplication cannot be read from the file, so a binding that declares it is refused rather than silently darkened. No fill+key pairs and no audio from the clip; both are separate changes.Tests
stinger_test(8) — arming; that an undeclared source on a keyed input is left playing and looping; full cycle with teardown and re-arm; concurrent trigger refused; invalid requests refused with the mixer left free; clip-failure degradation; clamp reported on the started event; and an end-to-end frame test asserting under 1% keyed content while idle, over 20% during the stinger, and over 80% new source after.stinger_alpha_test(4) — per-pixel alpha through both compositor backends, alpha through clip decode, premultiplied characterisation.openapi_test,pipeline_lifecycle_test,vision_mixer_fx_test, and 555 lib tests.All of the above ran on CI (Linux) as well as locally on macOS.
alpha_survives_gl_videomixerreturns early when no GL context can be created; it executed in both, so the GL path is covered rather than skipped. The software compositor test is the guard and panics rather than skipping ifcompositoris missing.stinger_preroll_benchis#[ignore]d — a measurement harness, not a guard, followingvimage/bench.rs. Run withcargo test --test stinger_preroll_bench -- --ignored --nocapture.Also driven by hand against a running backend: bound a clip and fired it from the vision mixer operator page, with the keyed input up for exactly the clip's length.
🤖 Generated with Claude Code