镜像站点 · 本页由第三方 GitHub 只读镜像提供,非 GitHub 官方站点,不接受任何登录或凭据输入。前往 github.com
Skip to content

feat(huddle): add opt-in camera video sharing - #8373

Draft
DanielProkhorovSnap wants to merge 13 commits into
block:mainfrom
Snap-Fusion-GmbH:feat/huddle-camera-sharing
Draft

DanielProkhorovSnap wants to merge 13 commits into
block:mainfrom
Snap-Fusion-GmbH:feat/huddle-camera-sharing

Conversation

@DanielProkhorovSnap

@DanielProkhorovSnap DanielProkhorovSnap commented Oct 15, 2026 •

Copy link
Copy Markdown

Summary

  • Add explicit webcam enable/disable and device selection to desktop huddles.
    Live video replaces the participant avatar; only self-preview is mirrored.
    A client without a webcam can still receive video.
  • Send bounded raw RGBA over binary Tauri IPC to native OpenH264, targeting
    30 FPS at up to 720p. Publish signed, ephemeral kind 24812 through the
    existing relay, with the same H.264 validation, tenant/member/private-huddle
    checks, and no-persistence guarantees as screen video.
  • Keep camera, screen, and audio lifetimes independent. Bound encoding and
    transport queues, use background worker clocks, and fence stop, replacement,
    leave, signer changes, and community changes.
  • Bound accumulated camera/screen playback latency, publish bootstrap/recovery
    keyframes immediately, flush sparse capture batches on a deadline, and pipeline
    at most four acknowledged publications. Retire stale video dependencies and
    recover fresh keyframes without reacquiring the source.
  • Make camera discovery demand-driven and single-flight in the capture-owning
    main webview. Companion pickers request that window's device IDs through
    channel-scoped, correlated local events with a five-second deadline and
    explicit retry, rather than enumerating a permission-redacted device list. Huddle entry,
    opening Camera settings, explicit refresh, and successful acquisition refresh
    the list; idle mounting and devicechange storms do not. This avoids adding
    the WebKitGTK enumeration feedback loop reported in fix(desktop): make media device enumeration demand-driven to break WebKitGTK FD-leak loop #7866.

Dependency And Scope

Depends on #8321, the existing screen-sharing PR. This branch includes that
prerequisite commit until it merges; the
camera commit
and the picker follow-up b3d0eedef are the original camera-only review scope.
The latency follow-up eb4d20f3f also changes shared screen playback and
publication. KaTeX/formula rendering is not included. The screen-sharing PR is
synced separately and does not include camera code.

Related to #6384. This adds the desktop camera portion, not mobile video,
recording, simulcast, or an external SFU. No new HTTP endpoint, production
deployment, database migration, or machine-specific runtime patch is included.

Upgrade both sending and receiving desktop clients. A screen-sharing-only
client cannot display camera video. Upgrade the relay for camera-specific
payload and huddle-admission validation; older relays can forward the
ephemeral kind without those guards.

Testing

  • Full just ci passed for the original camera feature, including formatting, both Tauri Clippy graphs,
    static/security checks, Rust and desktop/native/mobile tests, and
    desktop/web builds. Linux checks used the pinned Hermit toolchain and native
    library sysroot, with serialized Rust tests.
  • Isolated just test (all 103 runner steps) passed on the camera feature commit.
    After the earlier upstream sync, the complete integration lane, the real-Redis
    ephemeral camera quota regression, and all four real-relay video E2E tests
    passed. Coverage includes independent
    camera/screen tracks, tenant and membership boundaries, zero stored frames,
    and 130-second sustained screen-video delivery. The owned database and
    media-store container were removed afterward.
  • All 13 scoped camera, screen-sharing, and microphone Playwright tests passed.
    They cover opt-in,
    raw IPC geometry, decoded participant video, stop cleanup, unavailable
    cameras, explicit picker refresh, and existing media flows. Their native
    bridge and relay are mocked.
  • Eleven regressions render the production camera provider and companions:
    owner-only enumeration, consistent owner/companion IDs, startup handshakes,
    deadlines, stale/foreign reply fencing, visible failure/retry, and preservation
    of capture errors. No idle or devicechange-storm probing is introduced.
    The original enumeration-loop guard was also mutation-tested previously.
  • For the picker follow-up b3d0eedef, normal pre-push checks passed:
    Rust, desktop lint/typecheck/unit tests, both Tauri Clippy feature graphs and
    native tests, mobile analysis/tests, and the repository file-size gate.
  • A native Tauri/WebKit owner-and-companion regression reproduced the old
    System-default-only menu, then verified the fixed picker exposes the owner's
    inventory, retains monitor selection, makes zero companion hardware probes,
    and leaves capture off. Its inventories were injected fixtures; the actual
    cross-window event bridge and controls were exercised, not OS permissions.
  • Separately, the physical S65VC monitor webcam delivered 150 decoded
    1280x720 frames in approximately 5.24 seconds and reached EOS cleanly. No
    camera images were saved or sent to the relay.
  • Native Linux generated-video verification previously exercised capture,
    binary IPC, OpenH264, a local relay, and a second native player. Physical
    Linux capture was also exercised, including a hidden owner, with the
    dependency workaround described below.
  • An Apple Silicon 0.5.28 release build and deep/strict ad-hoc signature checks
    passed. On macOS 26.4, 187 native huddle tests and 35 focused JavaScript tests
    passed before this cleanup. A separate native Tauri/WKWebView harness using
    the production camera pipeline and controlled local transport sustained
    generated 720p video at 25.5 presented FPS for a minute, followed by
    stop/restart, stale-session, and simulated-disconnection cleanup.
  • The human confirmed live Linux-camera video displayed on the Mac after
    upgrading the receiver. On October 16, 2026, the human also confirmed the
    final owner-mediated picker detects and selects the monitor webcam in the
    installed Linux build.

Platform Limits

WebKitGTK 2.52.6 with GStreamer 1.28 has an upstream first-frame freeze:
WebKit bug 324582 / WebKit PR 74373. Physical Linux testing on that affected
combination used an app-local compatibility workaround. This PR does not bundle
that workaround or claim the unpatched affected runtime works.

Physical Mac camera capture/permissions, Windows capture, physical camera
indicator release, and minimized-window behavior need further platform checks.
Generated canvas streams do not establish minimized physical-camera behavior.

Manual Check

  1. Build this PR's desktop and relay using a compatible media runtime.
  2. Join without a webcam: receive another participant's camera, and verify
    audio, screen sharing, and leaving remain usable.
  3. Plug in a webcam, open Camera settings (or choose Refresh cameras),
    and close the picker. The camera must remain off until explicitly enabled.
  4. Enable it, grant permission, and verify sustained moving video on a second
    upgraded client. Switch cameras, stop/restart, and share camera/screen together.
  5. Deny/cancel permission, unplug the device, disconnect, leave/end, and switch
    identity/community. Verify visible recovery, released tracks, and no stale
    share revival.

Review Status

  • Agent reviewed the change against the product contract and recurring
    review rules; the enumeration-loop guard and companion-owner follow-up were
    checked with no remaining blocking findings.
  • Agent exercised native, browser, and live-relay behavior within the
    scopes described above.
  • Human confirmed the owner-mediated picker change on Linux on
    October 16, 2026.
  • Human retested the subsequent latency fix (eb4d20f3f).

The earlier picker confirmation remains recorded, but this PR is a draft until
the subsequent latency change has current human confirmation. The prerequisite
and remaining platform-coverage limits are documented above.
Architecture, compatibility, and the complete runbook:
docs/huddle-camera-sharing.md.

Camera UI

These mock-bridge screenshots use generated H.264 video decoded by the browser.
They contain no physical webcam image and do not establish native permissions
or live-relay behavior.

Camera Off

The participant keeps their avatar until video is explicitly enabled.

huddle-camera-before

Camera Video

Timed H.264 video replaces the participant avatar independently of screen sharing.

huddle-camera-after

Camera Settings

Opening the picker refreshes the available cameras without enabling capture.

huddle-camera-settings

Latency Follow-Up

The native production pipeline was exercised with generated 720p/30 FPS video,
controlled local transport, and delayed acknowledgements. These probes do not
establish physical-camera permissions or production-network performance:

  • Linux: median decoded-pixel age improved from 731 ms to 346 ms, with 464 ms
    p95 and approximately 26 changing decoded frames per second.
  • Unlocked Apple Silicon macOS: two runs measured 404 ms and 443 ms median
    decoded-pixel age, with 23-26 FPS. Rewind recovery and stop cleanup passed.
  • The locked-Mac attempt failed its latency/smoothness gates and is not counted
    as passing evidence.

Manual retest: use upgraded sending and receiving clients, enable the webcam,
and compare local and remote motion. Sustain the call, switch cameras, stop and
restart, then share camera and screen together. Confirm playback does not drift
back to the previous approximately one-second delay and controls remain usable.

Signed-off-by: Daniel Prokhorov <295108577+DanielProkhorovSnap@users.noreply.github.com>
Signed-off-by: Daniel Prokhorov <295108577+DanielProkhorovSnap@users.noreply.github.com>
Signed-off-by: Daniel Prokhorov <295108577+DanielProkhorovSnap@users.noreply.github.com>
@DanielProkhorovSnap

Copy link
Copy Markdown
Author

Agent Code Review

Reviewed the camera-only work against issue #6384, VISION.md,
VISION_SOVEREIGN.md, TESTING.md, and the Review-Proven Rules.

Findings

Fixed blocker: unbounded camera enumeration feedback on affected WebKitGTK.
The initial provider enumerated on every devicechange. The hardware diagnosis
in #7866 establishes that enumeration can itself re-announce devicechange and
leak descriptors, making the new provider another app-wide amplifier.
Discovery is now demand-driven on huddle entry and explicit user operations,
with one pending probe, scope/generation fencing, retained last-known data,
and visible retryable errors. The production-provider regression reproduces
post-resolution re-announcements; restoring the listener in a private copy
fails with 4 probes versus 1.

No other blocking correctness, security, or agreed-contract issue found in
the current change. Cleanup removed the unnecessary participant stream cast,
kept machine-specific instrumentation out of the PR, and added native reader
and quota regressions. Native reader tests bind the actual signing/drain code;
the cancellation test waits for the reader to own the receiver before stopping.

Scores

  • Minimalism: 9/10. Camera-specific discovery/consent stays separate; existing
    transport/validation/player code is reused instead of duplicated.
  • Elegance: 9/10. One capture owner, independent track identities, a shared
    bounded native reader, and explicit permission/device-error handling.
  • Correctness: 9/10 within the documented supported/runtime-tested scope.
    Hardware/permission and upstream runtime limitations are explicit.

Readiness

Screen prerequisite #8321 remains open. The user's Linux-to-Mac confirmation
was before the demand-driven discovery behavior change. Keep the PR draft and
do not add the readiness attestation until that final workflow is human-tested.

DanielProkhorovSnap added a commit to Snap-Fusion-GmbH/buzz that referenced this pull request Oct 15, 2026
@DanielProkhorovSnap

Copy link
Copy Markdown
Author

Camera UI

These are mock-bridge UI screenshots using generated H.264 video decoded by
the browser. They do not contain a physical webcam image or establish native
permissions or live-relay behavior.

Camera Off

The participant keeps their avatar until video is explicitly enabled.

huddle-camera-before

Camera Video

Timed H.264 video replaces the participant avatar independently of screen sharing.

huddle-camera-after

Camera Settings

Opening the picker refreshes the available cameras without enabling capture.

huddle-camera-settings

@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown

🔐 Codex Security Review

Status: review required for the current range.

The current range is 6e6173acd58029bac800583c4cc7482dafed693a...9ac41df11464b893b5b050ac2c5a0318d4e8d1b7.
A new review must complete for this exact range. When manual authorization
is required, a user with write access must comment exactly
@buzz-security-review 9ac41df11464b893b5b050ac2c5a0318d4e8d1b7 to authorize a new review.
Any previous review applies only to its recorded range.

@DanielProkhorovSnap
DanielProkhorovSnap marked this pull request as ready for review October 15, 2026 20:19
@DanielProkhorovSnap
DanielProkhorovSnap requested a review from a team as a code owner October 15, 2026 20:19
Signed-off-by: Daniel Prokhorov <295108577+DanielProkhorovSnap@users.noreply.github.com>
Keep camera enumeration in the webview that owns capture permission and device IDs. Correlate companion requests, fence stale replies, and provide bounded retryable discovery. Cover cross-window discovery, startup ordering, failures, and retirement in provider tests, and model the external owner in companion-only browser fixtures.

Signed-off-by: Daniel Prokhorov <295108577+DanielProkhorovSnap@users.noreply.github.com>
@DanielProkhorovSnap

Copy link
Copy Markdown
Author

Camera Picker Follow-Up Review

Added b3d0eedef to this PR. Only the camera-specific fix was cherry-picked;
KaTeX/formula rendering remains separate. The branch was also synced with
upstream main without changing the screen-sharing PR.

The companion now requests camera discovery from the main capture window.
Device IDs stay local, discovery never starts capture, overlapping owner probes
remain single-flight, and request IDs, room fences, readiness handshakes, and a
five-second deadline prevent stale results or permanently stuck controls.

Agent review of this follow-up found no blocking correctness, security, or
product-contract defects. Existing consent, native video transport, and
camera/screen/audio independence remain unchanged.

Validation:

  • Normal pre-push checks passed on this camera-only PR branch.
  • Eleven production-provider discovery regressions passed.
  • All 13 camera, screen-sharing, and microphone browser cases passed as part of
    the 18-case combined-build run. The camera-specific source and tests are
    byte-identical on this PR branch; the other five cases cover the separate KaTeX work.
  • Separate native owner/companion windows reproduced the old menu and verified
    the fix using injected inventories and the real Tauri event bridge.
  • The physical monitor camera separately produced 150 decoded 720p frames.
  • The human tested the installed Linux build and confirmed the monitor camera
    selection works on October 16, 2026.

The documented WebKitGTK/GStreamer dependency workaround and remaining physical
macOS/Windows/minimized-window coverage limitations still apply.

Signed-off-by: Daniel Prokhorov <295108577+DanielProkhorovSnap@users.noreply.github.com>
Signed-off-by: Daniel Prokhorov <295108577+DanielProkhorovSnap@users.noreply.github.com>
Signed-off-by: Daniel Prokhorov <295108577+DanielProkhorovSnap@users.noreply.github.com>
Signed-off-by: Daniel Prokhorov <295108577+DanielProkhorovSnap@users.noreply.github.com>
Signed-off-by: Daniel Prokhorov <295108577+DanielProkhorovSnap@users.noreply.github.com>
Signed-off-by: Daniel Prokhorov <295108577+DanielProkhorovSnap@users.noreply.github.com>
Signed-off-by: Daniel Prokhorov <295108577+DanielProkhorovSnap@users.noreply.github.com>
@DanielProkhorovSnap
DanielProkhorovSnap marked this pull request as draft October 20, 2026 07:50
@DanielProkhorovSnap

DanielProkhorovSnap commented Oct 20, 2026 •

Copy link
Copy Markdown
Author

Upstream Sync And Latency Follow-Up

  • Merged upstream main at 6e6173acd without rebasing or rewriting the
    submitted history. Head: 9ac41df11. The final history-only merge includes
    the synced screen prerequisite at baa6d6b30; its tree is byte-identical to
    the separately hook-validated camera head 5dbb8e25a.
  • Added eb4d20f3f: bounded live playout catch-up, immediate recovery IDRs,
    sparse-batch deadlines, stale dependency retirement and at most four
    acknowledged publications in flight. Camera and screen stay independent.
  • Classified all four live network relay tests as external infrastructure so
    the PostgreSQL-only lane does not run them without a relay.
  • Kept DM migrations, starter-agent defaults, KaTeX, managed onboarding and
    deployment changes out of this branch.

Follow-up agent review against #6384, the relay-first product contract and the
review-proven rules found no remaining blockers in the latency/sync changes.
Bounded queues/recovery, stale-generation fencing, teardown and regression
binding were checked. Minimalism, elegance and correctness: 9/10 each.

The PR is a draft because the latency behavior changed after the previous
human picker confirmation. The old completion attestation has been removed.
The PR body contains exact manual retest steps and separates generated-video
native evidence from physical-camera and production-network coverage.

Verification On October 20

  • Every normal scoped pre-push lane passed at 5dbb8e25a: file-size policy,
    desktop lint/typecheck/JS tests, workspace Rust tests, both Tauri Clippy graphs,
    native tests, and mobile analysis/tests. Hooks were not disabled.
  • The desktop native library passed 3,438 tests, with 19 intentionally ignored.
  • All 15 scoped huddle browser tests passed on the fork integration whose
    huddle sources are identical to this PR. Coverage includes 75-second MSE
    playback, independent camera/screen video, source/stop controls, microphone
    recovery and decoded-frame latency. Native IPC and transport are mocked in
    those browser tests.
  • Full just ci passed on the merged fork integration, including both native
    lint graphs, desktop/native/mobile tests, and desktop/web production builds.
    This integration has additional fork-only DM/starter-default changes; those
    changes are not in this PR.
  • All four live-relay video E2Es passed on that integration, including
    129.8-second sustained delivery, independent tracks, access controls, stops,
    and no stored video events. All three real-Redis quota regressions passed.
  • Both isolated DM schema-path regressions, the personal-feature gateway
    package, and every just test-integration step passed in serial runs.
    Shared-cache interference during initial parallel runs was corrected by
    rebuilding affected packages; no production code or test assertions were
    changed to obtain these results.

Signed-off-by: Daniel Prokhorov <295108577+DanielProkhorovSnap@users.noreply.github.com>
matt2e added a commit that referenced this pull request Oct 29, 2026
…cap (#8679)

## Summary

Fixes the two CI workflow defects that turned `main` red most often in
the 21–28 Oct 2026 audit:

- **Desktop Tauri:** the lane now caches its apt `.deb`s. It no longer
re-downloads 62 MB from a slow Ubuntu mirror on every run.
- **Windows Rust:** the lane drops a duplicate `cargo check` and gets a
60 min cap, so a cold run can save its cache.

It also updates #8613's `scripts/flake-check.test.mjs`, which breaks
once the Tauri package list moves into an env var. There are no product
code or test-behaviour changes.

This is one of seven PRs split out of the `flakey-tests` branch
(siblings below).

### Related issue

None found. #8613 touched both workflows after the audit, but not these
steps. No open PR changes how the Tauri packages are fetched, the
Windows timeout, or its Check (workspace) step:

- #8422, #8321 and #8373 only add packages to the list, and would now
add them to `TAURI_APT_PACKAGES`.
- #8414 adds two mesh-llm Tauri steps to the Windows job. If it lands,
they must fit the cold budget below.

### CI failures fixed

| Lane / test | Failing runs | Cause | Change |
|---|---|---|---|
| Desktop Tauri, "Install Tauri dependencies (Linux)"
(`_ci-desktop.yml`) | 62 jobs in 51 run attempts, 22–27 Oct. On main:
39821769576, 39822883395, 39827234118, 40333705511, 40342468627,
40348069929, 40353619362, 40368315739, 40373404267, 40374519539,
40556742929 | All 3 `ci-apt-retry.sh` attempts hit their 420 s limit
while `azure.archive.ubuntu.com` served the 62 MB at 20–65 KB/s. The
25.5 MB `libwebkit2gtk-4.1-0` alone takes a whole attempt, and each
attempt loses its partial download. | The package list moves to a
job-level `TAURI_APT_PACKAGES`. A new "Resolve Tauri apt archives" step
builds the cache key from `apt-get install --print-uris -o
Acquire::ForceHash=SHA256` (name, size and SHA256 per file).
`actions/cache/restore` uses that key. The install step copies only the
restored files whose SHA256 matches the signed index, because apt 2.8.3
reuses any same-size cached `.deb` without hashing it. A best-effort
rebuild keeps exactly the listed files. `actions/cache/save` runs only
on core-lane pushes after a successful rebuild; PR runs never save. |
| Windows Rust (`_ci-rust.yml`) | All on main: 39796569342 (job
119885795086), 39821769576 (119975358772), 39827234118 (119994286695),
39865872891 (120128351939), 40402556064 (121852634685) | Every test
passed. Cold steps take 41.3–43.5 min, so the 45 min cap cancelled the
`Swatinem/rust-cache` post-run save 1.6–3.7 min in. No cache was
written, and the next push was cold again. | Delete "Check (workspace)".
The clippy step before it covers the same `--workspace --all-targets`
set with `-D warnings`, which saves 2.0–2.8 min. Raise `timeout-minutes`
from 45 to 60, with the measured breakdown in a comment: 39–41 min of
steps, a ~4.5 min save and ~15 min of margin. |
| `scripts/flake-check.test.mjs` "repeat invocations mirror the lanes
they stand in for" | None in CI yet: it fails only with this change
applied | #8613 added this test after `flakey-tests` branched. It checks
that the advisory `flake-tauri` lane installs the same packages as the
Desktop Tauri core lane, by regex-matching the inline `apt-get install
-y --no-install-recommends \` list in `_ci-desktop.yml`. That list now
lives in `TAURI_APT_PACKAGES`, so the regex matched nothing and the test
failed with "Desktop Tauri apt packages". | Read `TAURI_APT_PACKAGES`
from `_ci-desktop.yml` and compare package names (12 on each side) with
`_ci-flake.yml`'s list. Renaming one package in `_ci-flake.yml` still
fails the test. Giving the `flake-tauri` lane the same read-only `.deb`
restore is a follow-up. |

Local evidence is from the original `flakey-tests` commit (9aa768a3b):

- The apt steps ran verbatim in Ubuntu 24.04 containers with apt 2.8.3,
with the cache restore and the save step's `if:` emulated.
- On a miss, the install fetches everything ("Need to get 254 MB"). On
an exact hit with the network down, it fetches nothing ("Need to get 0
B/254 MB").
- On a fallback with one `.deb` missing and one corrupted at the same
size, the hash check rejects the corrupt file, and apt re-fetches only
the corrupt and missing files.
- The key was identical in all four simulated jobs.
- The Windows numbers come from the jobs API for the five runs above.

On this branch, the split session ran the `scripts/` node tests:
`ci-selection`, `ci-required-gates`, `ci-apt-retry` and `flake-check` on
macOS gave 84 pass, 0 fail and 8 skipped (Linux-only). Before the
`flake-check.test.mjs` fix, that test failed 1 case. The same session
ran actionlint 1.7.12 with shellcheck, which was clean, and the
rust-cache contract scripts, which passed.

### Verification

This branch was pushed without further local re-verification, by the
user's choice: local test runs were overloading the machine and failing
for that reason. **Hosted CI is the check.** It was pushed with `git
push --no-verify` over SSH. The pre-push hook runs tests, and the HTTPS
token lacks the `workflow` scope.

Only hosted CI can show:

- The runner's real package list (about 62 MB) and the same key in all
three Tauri lanes.
- The `actions/cache` round trip. The first PR run misses, because
`main` has no key yet. After merge, the first push to `main` saves it.
- A cold Windows run saving its cache within the new cap. The next
toolchain bump or cache eviction will show it.

Remaining risk: after Ubuntu updates a large package, runs fetch it from
the mirror until a push to `main` saves a new key.

### Readiness

CI-only change. AGENTS.md's readiness steps (agent review, then human
confirmation) are still outstanding, so `buzz-review-completed` is not
set.

### Merge order

Independent of the other six. It merges cleanly onto `main`, and with
every sibling, in any order.

### Sibling PRs

Split from `flakey-tests` (one PR per area): #8676 (PostgreSQL lease +
lane), #8677 (relay E2E helper + Windows shell), #8678 (mobile), #8680
(desktop product races), #8681 (desktop E2E state gates), #8682 (desktop
E2E composer + row actions).

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Signed-off-by: Matt Toohey <contact@matttoohey.com>
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
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