Skip to content

feat(vision-mixer): stinger transitions - #755

Draft
wagenet wants to merge 1 commit into
Eyevinn:mainfrom
wagenet:feat/stinger-transitions
Draft

feat(vision-mixer): stinger transitions#755
wagenet wants to merge 1 commit into
Eyevinn:mainfrom
wagenet:feat/stinger-transitions

Conversation

@wagenet

@wagenet wagenet commented Sep 3, 2026

Copy link
Copy Markdown
Contributor

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 TransitionController already 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:

resolution armed after a clip switch
1920x1080 0.4 ms 14.1 ms
3840x2160 0.5 ms 28.4 ms (max 30.9)

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_ms says when the transition beneath starts and duration_ms how 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 — add measurably returns what over returns — 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_videomixer returns 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 if compositor is missing.

stinger_preroll_bench is #[ignore]d — a measurement harness, not a guard, following vimage/bench.rs. Run with cargo 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

@wagenet
wagenet force-pushed the feat/stinger-transitions branch from 5ed2c4f to b3326be Compare September 3, 2026 20:37
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
wagenet force-pushed the feat/stinger-transitions branch from b3326be to cd9b383 Compare September 3, 2026 22:02
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant