Show the candidate their own camera
Problem
Without a Google Workspace plan there is no Meet recording, so candidates record the practice interview with a screen recorder such as OBS or Clipchamp. A screen capture of CodeTrial shows the code and Jim but never the candidate: the camera track only feeds the hidden #camera-integrity-video element. To get their face into the recording, candidates turned on the recorder's camera as well, and on Windows a second application usually cannot open a camera the browser already holds. Whichever of the two opened it second lost it, and when that was CodeTrial the preflight camera check could not pass.
Change
The media preflight gains a checkbox next to the Meet one, "I am recording this interview with a screen recorder", stored under codetrial:selfView with writeStored like MEET_PRESENTATION_KEY. When it is ticked, the interview page paints the camera track CodeTrial already holds into a visible #self-view below Jim's avatar, so a recorder only needs the screen and system audio. Without the tick the page is unchanged. Nothing new is opened either way: there is no second getUserMedia, what is published to the room is unchanged, and the integrity sampler keeps its own hidden element, whose srcObject it owns and clears on its own schedule.
The view is not mirrored. A call mirrors its preview and records the true image, but here the preview is the recording, so a mirrored view would put anything the candidate holds up into the file backwards.
The view is hidden whenever there is nothing honest to show:
- no camera, or "Continue without camera";
- "I will present this interview in Google Meet", because it attaches after
releaseCameraToPresenter, so the released track is already out of the stream; - a camera
videoTrackReadyinweb/audio-check.jscalls not ready: ended, disabled, or taken by another application, which stayslivebutmutedand would otherwise freeze on its last frame; - any stop of the camera, since
stop()fires noended. The detach lives instopPreflight, so the offline fallback after a failed connect takes it down as well as the end of the interview.
Below 1200px the avatar is hidden but the self view stays, at 120px, because it is what a recording of a narrow window shows of the candidate.
The README paragraph on recording without Meet says to tick the box, record the screen, leave the recorder's camera off, and select the microphone in the recorder, since the page never plays the candidate's voice back and system audio carries only Jim.
This complements #128, which names a busy camera in the preflight error. That PR says what went wrong; this one removes the reason to open the camera twice.
Test
tests/browser/self-view.test.jsdrives the module against fake tracks: a live camera shown and muted, no camera or an ended one cleared, a disabled camera hidden, a muted track hidden and restored, an ended track taken down, detach clearing the element and its listeners. Every element starts visible and holding a stream, so a module that did nothing fails. It also pins the wiring ininterview.js: attach after the Meet release, before the room and only behind the checkbox, and the detach instopPreflightbefore the tracks stop.- Three deliberate breakages each fail that file: attaching without the checkbox, dropping the detach from
stopPreflight, and going back to the old paint rule that ignored a disabled camera. tests/browser/dom-contract.test.jsrequires#self-viewand#screen-recordingininterview.html.- On Windows 11 with Node 24.20.0:
npm testruns 1162 tests with exactly the same failures asmainat 6b6cdfe on the same machine. They come from vendored assets this checkout has not fetched, a missing C++ toolchain, and four tests indom-contract.test.jsthatimport()an absolute Windows path; all 10 tests inself-view.test.jspass. ESLint and prettier pass on the changed files;README.mdandweb/styles.csskeep the prettier drift they already have onmain. The Rust lanes of./scripts/test.shwere not run, so CI is the evidence for those. - Layout checked in Chromium at 1440 px wide, serving
web/through the browser tests' static server and feeding the view a canvas stream in place of a camera: the box persists across reloads, and the view sits below the avatar and its Hide control, unmirrored. A recording with a real camera has not been redone since these changes.
Closes #141