Skip to content

feat(vision mixer): report take morphs and per-input media age - #803

Merged
srperens merged 20 commits into
Eyevinn:mainfrom
wagenet:wagenet/producer-switching-signals
Oct 19, 2026
Merged

srperens merged 20 commits into
Eyevinn:mainfrom
wagenet:wagenet/producer-switching-signals

Conversation

@wagenet

@wagenet wagenet commented Sep 11, 2026 •

Copy link
Copy Markdown
Contributor

What

Vision mixer changes from exercising the producer switching workflow:

  1. actual_transition_type reports morph when a take moved or re-cropped a shared
    source instead of dissolving.
  2. Vision mixer state carries input_media_age_ms, so a frozen input can be told apart
    from a motionless one.
  3. A take with an unknown transition name is refused before it changes anything, on the
    PiP path too (400, as between plain inputs).

Why

The take reported a move as a dissolve. The PiP-aware path animates pads, not
pictures: a source in both compositions moves and re-crops into its new box, and only
sources exclusive to one side cross-fade. Taking a four-up to that participant full
frame reads as a zoom-in; taking a punch-in back to the plain source reads as a zoom-out.
That is deliberate, but the path reported fade for every non-cut take, so a caller
asking for a dissolve was told it got one. Behaviour is unchanged; only the report is
accurate. A slide_* request on this path is still downgraded, and now reports morph
rather than fade when a source is shared. morph is a report only, not a request type.
An effect take (glitch, flash, whip) out of a PiP lays its envelope over the pad move and
reported the move; it now reports the effect, as the plain-input path does. A
transition name the mixer does not know is now refused on this path with the same error
as between plain inputs; before, it ran as a dissolve and was reported as one. On both
paths the refusal now comes before the take cancels fade-to-black, which left a black
program marked as not faded, so the next FTB press did nothing visible. The bundled
control page no longer calls a fade that ran as a morph a downgrade; a slide, push
or wipe that ran as one still reads as downgraded. Known gap: a take between two buses
that both show the same PiP still reports fade.

A dropped participant stays on air as a still frame. The compositor repeats an
input's last frame indefinitely, so a lost connection looks exactly like someone sitting
motionless and persists after the ingest session is reaped. input_media_age_ms gives
milliseconds since each input last delivered a frame:

"input_media_age_ms": [ 24, 24, 7727, 24, null ]
//                       ^live ^live  ^^^^ frozen 7.7s   ^never published

Stamping at the mixer's own sink pads covers every input type. It measures arrival, not
display: when an input's timestamps jump backwards (an encoder restarting its clock), the
mixer discards frames older than the one it holds, so the tile freezes for the length of
the jump while its age reads live. The age has to be read against the source's own frame
interval: a source that only sends frames when its picture changes (a web graphic on a
static page) ages for seconds while healthy. Every input ageing together means the
mixer's output stalled, not that every source stopped: the mixer takes in frames only as
fast as it puts them out. The guide and the field doc say all three. On a five-seat WHIP rig, killing one publisher
produced 1.8 s → 29.9 s on that input while three live neighbours stayed under 30 ms, and
the age returned to 25 ms on rejoin. That rig run predates the take fix; the probe code is unchanged since.

Risk the reviewer should weigh

These are BUFFER probes, the hottest path in the pipeline. The callback reads the
monotonic clock and stores one relaxed atomic: no lock, no allocation, no formatting, no
state lookup. The overlay state is captured as an Arc, not looked up per buffer, and is
not a GStreamer object, so the closure forms no reference cycle with the pipeline. Five
inputs at 30 fps is 150 stores per second. The cost was not A/B measured.

Tests

Each guard below fails when its behaviour is reverted (the revert in brackets).

  • take.rs unit tests: moved or re-cropped shared source → morph; disjoint, or shared
    and still → fade [classifier ignoring moves; ignoring crop].
  • vision_mixer/tests.rs: age accessors, and the probe helper on a bare compositor
    [no stamp; wrong pad names].
  • vision_mixer_signals_test (real CPU mixer): live / stalled / unlinked ages [CPU builder
    not installing probes]; punch-in to its plain source reports morph [response hardcoding
    fade]; morph requested out of a PiP is refused, buses and fade-to-black unchanged
    [unknown name run as a fade; FTB cancelled before the check]; the same refusal between
    plain inputs keeps fade-to-black [FTB cancelled before the check].
  • vision_mixer_fx_test (real GPU mixer): glitch_cut out of a punch-in reports
    glitch_cut [effect name dropped]; live / stalled / unlinked ages, waiting up to 10 s
    for the stalled input to age [GPU builder not installing probes]. These render through GL; CI runs them on macOS and on Linux
    under Xvfb, both with STROM_REQUIRE_GL=1.
  • backend/static/tests/vision_mixer_take_note.test.js: the page's take status note
    [every morph called a plain move; a punch-in described as a slide].

Run on macOS: cargo test --lib (690), vision_mixer_signals_test and
vision_mixer_fx_test with STROM_REQUIRE_GL=1 (5 parallel runs each under
taskpolicy -b), openapi_test, node --test backend/static/tests, cargo fmt --check, cargo clippy --all-targets -D warnings. The OpenAPI snapshot changed intentionally: one additive optional field and the
actual_transition_type description.

Not run locally: the rest of the integration suite. CI on Linux runs it.

Known gap, not from this PR: two flows whose mixers share a block id read each other's
state, including these ages (#887).

🤖 Generated with Claude Code

@srperens

Copy link
Copy Markdown
Collaborator

Noted as a draft, so I am holding off on a full review until it is marked ready for
review. If you want one before then, say so here or request me as a reviewer.

@srperens srperens mentioned this pull request Sep 14, 2026
@wagenet
wagenet force-pushed the wagenet/producer-switching-signals branch from 7308808 to 314cbdd Compare September 23, 2026 04:51
wagenet added a commit to wagenet/strom that referenced this pull request Sep 27, 2026
…ma change

Eyevinn#806 made trigger_transition's indices optional; the tests Eyevinn#803 adds
still passed bare integers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@wagenet
wagenet force-pushed the wagenet/producer-switching-signals branch from 4da92ae to 82bc7be Compare September 28, 2026 04:36
@wagenet
wagenet marked this pull request as ready for review September 28, 2026 13:56
wagenet added a commit to wagenet/strom that referenced this pull request Sep 28, 2026
…usal tests

Integration fix-up: Eyevinn#806 makes trigger_transition's input indices
optional, and Eyevinn#803's two FTB-refusal tests still pass bare integers.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@srperens srperens left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Verdict: Comment. The mechanism is right and the BUFFER probe meets the CLAUDE.md constraints. What holds this back from Approve is CI: this repo has never run the two GPU-path guards. Both skip green on Linux, and the macOS job that would render them did not run.

Requested changes

  1. Maintainer: add the ci:macos label so that vision_mixer_fx_test renders at this head. The alternative is to merge #809 first, which makes the Linux job render GL on every PR, and then rebase this one. Either way, gpu_mixer_reports_media_age_per_input and master_fx_take_out_of_a_pip_reports_the_effect then execute instead of returning early.

Claims

Claim Verdict Evidence
The probe does no lock, allocation or formatting per buffer CONFIRMED The probe body (see Diagnosis) calls only backend/src/blocks/builtin/vision_mixer/overlay.rs:244 — let ms = self.instant_base.elapsed().as_millis() as u64; plus one relaxed store. It reads the clock through Instant, not SystemTime
The closure forms no reference cycle CONFIRMED It captures only an Arc<VisionMixerOverlayState>, and that struct has no gst:: field. The setup closure keeps the existing dist_weak.upgrade() pattern
An unknown name is refused before FTB is cancelled CONFIRMED backend/src/gst/pipeline/effects/take.rs:76 — `let trans_type = transition_type.parse::().map_err(
The GPU builder installs the probes, and a master-FX take out of a PiP reports the effect UNVERIFIED backend/tests/vision_mixer_fx_test.rs:712 — async fn gpu_mixer_reports_media_age_per_input() { and :626 both return early when there is no GL. In job 108793044875 the whole file reports 4 passed ... finished in 0.06s, which is the skip signature. .github/workflows/ci.yml:689 — STROM_REQUIRE_GL: 1 is set on the macOS job only, and Build (macOS) is skipping

Diagnosis. The probe is installed per input:

backend/src/blocks/builtin/vision_mixer/activity.rs:40 — pad.add_probe(gst::PadProbeType::BUFFER, move |_pad, _info| {

Coverage is ABSOLUTE for arrival: the stamp sits on the dist compositor's sink_i, so every input type is covered, including hidden ones. Frames the compositor discards after a backwards timestamp jump are the one blind spot, and the PR body and the field doc both state it.

Radius. SHARED. Every vision mixer in every flow now carries num_inputs BUFFER probes, on both builders. actual_transition_type has also gained a new value. A client that flags a downgrade whenever actual != requested will now flag every PiP fade that ran as morph. The bundled page was updated for this. External clients were not, and nothing in this repo can find them.

Overlaps

  • #806 changes the same prologue of trigger_transition. It adds an index check that also runs before the FTB cancel, and it rewrites the old_pgm/from_input fallback right next to the parse block this PR removes. The two will conflict textually. #806 is older and its semantics are independent, so land it first and rebase this one.
  • #809: see requested change 1.
  • #887 (registry keyed by block id) is the known gap the body names. This PR does not settle it.

Tests & CI. At 2993a45, Check (Linux), both Build (Linux) jobs, WASM, API Contract Check and Static JS Tests are green. The CPU guards executed: media_age_separates_live_stalled_and_unlinked_inputs, punch_in_taken_to_plain_input_reports_morph, the four take::tests, and both vision_mixer::tests probe tests.

Confidence: HIGH

wagenet added a commit to wagenet/strom that referenced this pull request Sep 29, 2026
…ma change

Eyevinn#806 made trigger_transition's indices optional; the tests Eyevinn#803 adds
still passed bare integers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 7972650)
wagenet added a commit to wagenet/strom that referenced this pull request Sep 29, 2026
…usal tests

Integration fix-up: Eyevinn#806 makes trigger_transition's input indices
optional, and Eyevinn#803's two FTB-refusal tests still pass bare integers.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit d20b5d9)
wagenet added a commit to wagenet/strom that referenced this pull request Sep 29, 2026
… flags

Integration fix-up: upstream Eyevinn#789 adds two structured-logging flags to
EventBroadcaster::new and AppState::new. Tests from Eyevinn#797, Eyevinn#803, Eyevinn#809,
Eyevinn#810 and Eyevinn#844 still call the old signatures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
wagenet added a commit to wagenet/strom that referenced this pull request Sep 29, 2026
…ma change

Eyevinn#806 made trigger_transition's indices optional; the tests Eyevinn#803 adds
still passed bare integers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 7972650)
wagenet added a commit to wagenet/strom that referenced this pull request Sep 29, 2026
…usal tests

Integration fix-up: Eyevinn#806 makes trigger_transition's input indices
optional, and Eyevinn#803's two FTB-refusal tests still pass bare integers.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit d20b5d9)
wagenet added a commit to wagenet/strom that referenced this pull request Sep 29, 2026
… flags

Integration fix-up: upstream Eyevinn#789 adds two structured-logging flags to
EventBroadcaster::new and AppState::new. Tests from Eyevinn#797, Eyevinn#803, Eyevinn#809,
Eyevinn#810 and Eyevinn#844 still call the old signatures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
wagenet added a commit to wagenet/strom that referenced this pull request Oct 1, 2026
…ma change

Eyevinn#806 made trigger_transition's indices optional; the tests Eyevinn#803 adds
still passed bare integers.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
(cherry picked from commit 7972650)
wagenet added a commit to wagenet/strom that referenced this pull request Oct 1, 2026
…usal tests

Integration fix-up: Eyevinn#806 makes trigger_transition's input indices
optional, and Eyevinn#803's two FTB-refusal tests still pass bare integers.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
(cherry picked from commit d20b5d9)
wagenet added a commit to wagenet/strom that referenced this pull request Oct 1, 2026
… flags

Integration fix-up: upstream Eyevinn#789 adds two structured-logging flags to
EventBroadcaster::new and AppState::new. Tests from Eyevinn#797, Eyevinn#803, Eyevinn#809,
Eyevinn#810 and Eyevinn#844 still call the old signatures.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@wagenet
wagenet force-pushed the wagenet/producer-switching-signals branch from 2993a45 to a437f8a Compare October 1, 2026 19:17
@wagenet
wagenet force-pushed the wagenet/producer-switching-signals branch from c15b597 to ac097dd Compare October 2, 2026 15:48
@wagenet
wagenet force-pushed the wagenet/producer-switching-signals branch from ac097dd to 8e02af3 Compare October 9, 2026 16:14
wagenet added a commit to wagenet/strom that referenced this pull request Oct 9, 2026
# Conflicts:
#	backend/src/blocks/builtin/vision_mixer/builder/pipeline_gpu.rs
wagenet added a commit to wagenet/strom that referenced this pull request Oct 9, 2026
srperens
srperens previously approved these changes Oct 14, 2026

@srperens srperens left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Supersedes my review of 2026-09-29 at 2993a45. That review's verdict was Comment. It is now Approve, because the blocker it raised has been resolved.

Verdict: Approve. At 8e02af3 this is a rebase onto dcccdd7 plus one test-only commit. That commit adapts the guards to the per-flow overlay registry (#1015) and to either trigger_transition index signature. The mechanism is unchanged from what I confirmed last time, and the GPU guards that I left UNVERIFIED have now executed in this repo's CI.

Claims

Claim Verdict Evidence
The probe does no lock, allocation or formatting per buffer CONFIRMED backend/src/blocks/builtin/vision_mixer/activity.rs:40 — pad.add_probe(gst::PadProbeType::BUFFER, move calls only backend/src/blocks/builtin/vision_mixer/overlay.rs:271 — let ms = self.instant_base.elapsed().as_millis() as u64; and one relaxed store. The captured Arc<VisionMixerOverlayState> still has no gst:: field, so it forms no reference cycle.
An unknown name is refused before FTB is cancelled CONFIRMED backend/src/gst/pipeline/effects/take.rs:76 — `let trans_type = transition_type.parse::().map_err(
The GPU builder installs the probes, and a master-FX take out of a PiP reports the effect CONFIRMED #977 made the Linux job render GL: .github/workflows/ci.yml:174 — STROM_REQUIRE_GL: 1. With that variable set, gl_available asserts instead of skipping: backend/tests/common/mod.rs:165 — strom_types::env::var_opt("STROM_REQUIRE_GL").is_none(),. In Check (Linux) job 113912231249, master_fx_take_out_of_a_pip_reports_the_effect ... ok and gpu_mixer_reports_media_age_per_input ... ok ran, and the file took finished in 4.97s instead of showing the old 0.06 s skip signature.

Radius. SHARED, unchanged from my last review. Every vision mixer gets num_inputs BUFFER probes, on both builders, and actual_transition_type gains the value morph. The bundled page handles morph. External clients cannot be found from this repo.

Overlaps

  • #806: the idx() helper (backend/tests/vision_mixer_fx_test.rs:573 — fn idx<T: From<usize>>(i: usize) -> T {) compiles against either signature, and git merge-tree of the two heads is clean. Either can land first.
  • #809, #1056: textual conflicts only. Against #809, pipeline_gpu.rs has adjacent let lines. Against #1056, vision-mixer.html has the version string. Whichever PR lands second resolves its conflict in one line.
  • #887, which the body lists as a known gap, has been closed by #1015, and this head already tests against it. The PR body's last line is stale.

Tests & CI. At 8e02af3, Check (Linux), both Build (Linux) jobs, WASM, API Contract Check and Static JS Tests all pass. These guards executed: the four take::tests, both vision_mixer::tests probe tests, all four tests in vision_mixer_signals_test, and both new GPU tests. Build (macOS) and Build (Windows) were skipped, but the diff contains no platform-specific code. The base is 7 commits behind main. git merge-tree against origin/main is clean, and the only change main makes to openapi.json is the version line, which is disjoint from this diff's schema hunk.

Confidence: HIGH

@srperens

srperens commented Oct 19, 2026 •

Copy link
Copy Markdown
Collaborator

Since this PR was opened, main gained shared test setup in backend/tests/common (#935, #936, #1083). After those, no test on main builds an AppState or a PipelineManager by hand except port_pool_test.

This PR adds three hand-built PipelineManagers with the same arguments as common::manager::build:

  • backend/tests/vision_mixer_fx_test.rs:607 and :729. The rest of vision_mixer_fx_test.rs on main already uses the helper.
  • backend/tests/vision_mixer_signals_test.rs:111

Each becomes let mut manager = common::manager::build(&flow).expect("build GPU pipeline"); (build(flow) in vision_mixer_signals_test, where flow is already a reference).

  • common::manager::build(&flow) builds a PipelineManager the way start_flow does (EventBroadcaster::with_capacity(10), which equals new(10, false, false), media under the system temp dir). common::manager::build_with(&flow, events, media_path) takes your own broadcaster or media path. Neither calls detect_gpu_capabilities(), so keep that call where you have it. The registry temp file is not needed after construction.

Correction: I wrote here that this compiles against main as it is. It did not: #935 removed the imports those copies use, so vision_mixer_fx_test failed to build on a merge with main even though git reported no conflict. A maintainer has rebased this branch onto main and folded all three copies into common::manager::build in cf48e776, so nothing is left for you to do here.

Posted by Claude (AI) for the maintainers.

wagenet and others added 20 commits October 19, 2026 16:34
The PiP-aware take path animates pads, not pictures. A source that appears in
both the outgoing and incoming composition is treated as moving, so it slides
and scales into its new box while only the sources exclusive to one side
cross-fade. Taking a four-up to that same participant full frame therefore
reads on air as a zoom, not a dissolve.

The response reported "fade" for every non-cut take in this path, so a caller
asking for a dissolve was told it got one. Report "morph" when any PGM pad
moves. Behaviour is unchanged; only actual_transition_type is now accurate.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
When an input stops delivering, the compositor repeats its last frame
indefinitely. A participant whose connection drops therefore stays on air as a
still picture, indistinguishable from one sitting motionless, and stays there
after the ingest session is reaped. The program output cannot tell the two
apart.

Stamp each input's last buffer arrival on the dist compositor's sink pads and
report the age in the vision mixer state as input_media_age_ms: tens of
milliseconds for a live source, growing without bound for a frozen one, null
for an input that has never delivered a frame. Stamping at the mixer's own
pads covers every input type, not just WHIP seats, and measures exactly what
decides the picture.

These are per-buffer probes. The callback reads the monotonic clock and stores
one relaxed atomic, with the overlay state captured as an Arc rather than
looked up per buffer. The state is not a GStreamer object, so the closure
forms no reference cycle with the pipeline.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The frozen-seat figure is also sharper: the counter puts the last frame about
a quarter of a second after the publisher dies, where frame capture alone
could only bound it at two seconds.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The accessor test stamps the state directly, so it passes whether or not the
probes are attached to anything. This one runs a real compositor and asserts
the age is stamped from its buffers, and fails if the probes are installed on
the wrong pad names or not at all.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A PiP-aware take animates the crop of a source present on both sides as
well as its box. Taking a punch-in back to the plain source keeps the box
full frame and eases the crop out, which reads on air as a zoom-out, but
the classifier compared boxes only and reported "fade".

Count a shared source whose crop differs between the two sides as a morph.
The actual_transition_type description now names "morph" and says it is a
report only, not an accepted request type.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The existing unit tests exercise the classifier and the probe helper
directly, so they still pass if the take response stops using the
classifier or the mixer builder stops installing the probes. Both tests
here build a CPU vision mixer flow through PipelineManager: one checks that
a live, a stalled and an unlinked input read young, old and null, the other
that a punch-in taken through trigger_transition reports "morph".

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A source that only sends a frame when its picture changes, such as a web
graphic on a static page, ages for seconds between frames while healthy.
A fixed one-second rule would have an operator pull it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A glitch, flash or whip take out of a PiP runs through the PiP-aware
path, which lays the full-frame envelope over the pad morph. The take
reported the morph (before that, "fade"), so a control surface labelled
an on-air glitch as a move. Report the effect when its envelope was
applied, as the plain-input path already does.

The guard renders through GL, so it runs on the macOS CI job, not Linux.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Mixer state is registered per block id, and the other tests in
vision_mixer_fx_test run in parallel under the shared id. Sharing it let
either test replace the other's mixer state: the new test saw zero PiPs,
or the letterbox wipe test failed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The PiP-aware take ran a name it could not parse as a fade and reported
it as one, where the plain-input path refuses it. "morph", which the
take only reports, was accepted as a request this way. Both paths now
return the same error, and the buses stay where they were.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The CPU test cannot see the GPU builder's install call. This one runs a
real GPU mixer with a live, a stalled and an unlinked input, and fails
if the GPU builder stops installing the probes. It renders through GL,
so it runs on the macOS CI job and skips on Linux.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
An input whose timestamps jump backwards freezes on air for the length
of the jump, because the mixer discards frames older than the one it
holds, while its age still reads live. The guide and the field doc now
say so.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A take cancelled fade-to-black and reset the FX slots before it parsed
the transition name. A refused name then left the program black with
FTB marked off, so the operator's next FTB press did nothing visible.
The name is now checked once, first, for both the PiP and plain paths.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
On the macOS CI runner frames queued past the valve kept reaching the
mixer for about 1.4 s, so the fixed 2.5 s wait read the stalled input
at 1.1 s. Poll for the 1.5 s gap, up to 10 s, instead.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A take between compositions that share a source reports morph. The page
labelled every report that differed from the request as a downgrade, so
an ordinary fade read as one. Name the move plainly instead.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Only a fade that morphed is an ordinary take between compositions that
share a source. A slide, push or wipe that morphed did not run, and the
page said "ran as a move" for those too. They now read as downgraded to
morph. The note no longer repeats the server message it is appended to.
The note is a pure function, tested from the page source.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A punch-in taken to or from its plain source reports morph, but the box
stays full frame and only the crop changes, so the audience sees a zoom.
The note said the source slid into its new box. It now says it moved or
zoomed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The mixer takes in frames only as fast as it puts them out, so a stalled
program consumer ages every input together. Say so in the guide and the
field doc, so a dashboard does not read it as every source dropping out.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Eyevinn#935 moved PipelineManager setup into common::manager and removed the
imports the hand-built copies used, so the two new tests in
vision_mixer_fx_test no longer compiled against main. They, and the
start helper in vision_mixer_signals_test, now use
common::manager::build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@srperens
srperens force-pushed the wagenet/producer-switching-signals branch from 8e02af3 to cf48e77 Compare October 19, 2026 14:42
@srperens
srperens merged commit 4ce4f3f into Eyevinn:main Oct 19, 2026
9 checks passed
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.

2 participants