Split out of #242, #244 and #245, none of which mentions it.
What
The app can show exactly two capture feeds. Three open features each need more than two, and
each is written as though the feeds were the easy part. This is the work underneath all three, and
it is almost certainly larger than any of them.
Current state
Two <video> elements, #left-video and #right-video
(index.html:17 and
:41), and "left or right" is not a parameter —
it is the shape of the renderer:
|
Count |
state fields keyed by side |
10 (leftDeviceId/rightDeviceId, leftStream, leftAudioGain, leftAudioSource, leftVolume, and their right-hand twins) |
elements entries keyed by side |
10 (leftFeed, leftVideo, leftBorder, leftInputList, leftLabel, ×2) |
functions taking a side argument |
9 |
side === 'left' ? a : b ternaries |
15 |
objects literally shaped { left, right } |
3 (mockStreams, noSignalBoards, swapToken) |
getUniqueActiveDevices() hand-enumerates left then right rather than iterating feeds, so
detection, the no-signal overlays, the split-flap boards and the swap-fade tokens all inherit the
same two-slot shape.
The GPU compositor is a partial head start, with a trap in it
gpu-compositor.js already takes an array — draw(feeds), each with its own offset and scale
in normalised target space (gpu-compositor.js:139).
That is the right shape and it did not have to be.
But it allocates exactly two uniform buffers:
const uniforms = [0, 1].map(() => device.createBuffer({ size: 32, ... }))
and then, per feed:
if (i >= uniforms.length) return
A third feed is silently dropped. No error, no warning — it just does not draw. Anyone wiring
up multi-view against this compositor would get two panels working and a third that is black for no
stated reason. Worth fixing on its own merits even if none of the three features are built:
either allocate from feeds.length or log what was discarded.
Note this is behind gpuCompositing in settings and off by default, so the CSS path is the one
that actually runs on the wall today.
What is genuinely unknown
Whether the hardware can do it at all. Four simultaneous getUserMedia streams from four
capture devices is not obviously fine: capture cards sharing a USB controller contend for
bandwidth, and several will renegotiate to a lower resolution or drop frames rather than fail
loudly. Nobody has tried it here. This should be measured before any of #242/#244/#245 is designed
around it, because the answer could change what they look like — a thumbnails panel that has to
poll inputs one at a time is a different feature from one showing four live streams.
#248 makes that measurement cheap on the software side. --mock=8 synthesises eight inputs as
real MediaStreams from canvas.captureStream(), so the renderer work can be built and driven
with no capture hardware present. It cannot answer the USB-bandwidth question, which needs real
cards, but it separates "does our code handle N feeds" from "does the hardware".
Why this is one issue rather than three
#242 (thumbnails panel), #244 (multi-view) and #245 (picture-in-picture) are genuinely different
features — a transient picker, a per-panel layout, and a persistent inset. They are not duplicates
and this is not a proposal to merge them.
They just share one dependency, and doing it three times would produce three incompatible ways to
hold more than two feeds. #243 (auto-switch) is orthogonal and does not need this.
Acceptance criteria
Split out of #242, #244 and #245, none of which mentions it.
What
The app can show exactly two capture feeds. Three open features each need more than two, and
each is written as though the feeds were the easy part. This is the work underneath all three, and
it is almost certainly larger than any of them.
Current state
Two
<video>elements,#left-videoand#right-video(index.html:17 and
:41), and "left or right" is not a parameter —
it is the shape of the renderer:
statefields keyed by sideleftDeviceId/rightDeviceId,leftStream,leftAudioGain,leftAudioSource,leftVolume, and their right-hand twins)elementsentries keyed by sideleftFeed,leftVideo,leftBorder,leftInputList,leftLabel, ×2)sideargumentside === 'left' ? a : bternaries{ left, right }mockStreams,noSignalBoards,swapToken)getUniqueActiveDevices()hand-enumerates left then right rather than iterating feeds, sodetection, the no-signal overlays, the split-flap boards and the swap-fade tokens all inherit the
same two-slot shape.
The GPU compositor is a partial head start, with a trap in it
gpu-compositor.jsalready takes an array —draw(feeds), each with its ownoffsetandscalein normalised target space (gpu-compositor.js:139).
That is the right shape and it did not have to be.
But it allocates exactly two uniform buffers:
and then, per feed:
A third feed is silently dropped. No error, no warning — it just does not draw. Anyone wiring
up multi-view against this compositor would get two panels working and a third that is black for no
stated reason. Worth fixing on its own merits even if none of the three features are built:
either allocate from
feeds.lengthor log what was discarded.Note this is behind
gpuCompositingin settings and off by default, so the CSS path is the onethat actually runs on the wall today.
What is genuinely unknown
Whether the hardware can do it at all. Four simultaneous
getUserMediastreams from fourcapture devices is not obviously fine: capture cards sharing a USB controller contend for
bandwidth, and several will renegotiate to a lower resolution or drop frames rather than fail
loudly. Nobody has tried it here. This should be measured before any of #242/#244/#245 is designed
around it, because the answer could change what they look like — a thumbnails panel that has to
poll inputs one at a time is a different feature from one showing four live streams.
#248 makes that measurement cheap on the software side.
--mock=8synthesises eight inputs asreal
MediaStreamsfromcanvas.captureStream(), so the renderer work can be built and drivenwith no capture hardware present. It cannot answer the USB-bandwidth question, which needs real
cards, but it separates "does our code handle N feeds" from "does the hardware".
Why this is one issue rather than three
#242 (thumbnails panel), #244 (multi-view) and #245 (picture-in-picture) are genuinely different
features — a transient picker, a per-panel layout, and a persistent inset. They are not duplicates
and this is not a proposal to merge them.
They just share one dependency, and doing it three times would produce three incompatible ways to
hold more than two feeds. #243 (auto-switch) is orthogonal and does not need this.
Acceptance criteria
left/rightfields, with the count not fixed inthe markup
collection instead of enumerating two sides
D/Sand the existing dual/single behaviour unchanged — this is a refactor with nouser-visible change on its own
what happens at the limit — recorded here
--mock=8that the renderer handles more feeds than there are number keys