Skip to content

Let candidates save local interview recordings - #469

Open
Hipsterthomas wants to merge 5 commits into
sysprog21:mainfrom
Hipsterthomas:local-recording
Open

Hipsterthomas wants to merge 5 commits into
sysprog21:mainfrom
Hipsterthomas:local-recording

Conversation

@Hipsterthomas

@Hipsterthomas Hipsterthomas commented Oct 24, 2026 •

Copy link
Copy Markdown

Candidates can save a practice interview without recording infrastructure. Capture the selected CodeTrial tab and mix its audio with the existing interview microphone into a local WebM, without another microphone request or LiveKit subscription. Recording errors leave the interview running.

An explicit Start & record option starts capture before joining Jim so the opening is included. End recording, browser Stop sharing, and End interview finalize and automatically download one file. Reuse the existing self-view while recording and keep the result entirely in the browser until downloaded.

Validation: ./scripts/test.sh passed on macOS after rebasing onto upstream main b0059b2: 1,940 Rust tests, 1,753 browser tests, and three server/browser tests passed. The 113 focused recording and related checks also passed with no skips. make build and local HTTP smoke checks passed. The Linux-only Clang check and cargo-audit were not run locally, and 23 existing Rust tests remained ignored.

Screenshot 2026-10-24 at 10 46 46 PM Screenshot 2026-10-24 at 10 46 57 PM Screenshot 2026-10-24 at 10 47 24 PM

Closes #424

jserv

This comment was marked as outdated.

@jserv

jserv commented Oct 24, 2026

Copy link
Copy Markdown
Contributor

Add screenshots to the pull request description to illustrate your work, as @ColtenOuO suggests. Example: #349

Comment thread web/interview.js
Comment thread web/local-recorder.js Outdated
@Hipsterthomas

Copy link
Copy Markdown
Author

Add screenshots to the pull request description to illustrate your work, as @ColtenOuO suggests. Example: #349

Screenshots of the local recording controls have been added to the pull request description.

@jserv jserv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Check CONTRIBUTING.md carefully and enforce the rules for consistent commit style.

Never write commit messages such as Update web/interview.js, as Git always tracks changed files.

@jserv
jserv requested review from ColtenOuO and alanhc October 24, 2026 15:40

@jserv jserv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Before asking someone to review, you MUST resolve any regressions flagged by GitHub Actions.

Comment thread web/local-recorder.js Outdated
Comment thread web/local-recorder.js Outdated
Comment thread web/interview.html
>
Thinking
</button>
<div id="local-recording" class="recording-row" hidden>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

With this row visible the dock wraps onto another line, which is why local recording controls preserve the original problem reading space fails in CI. At 1280x720 the problem panel is 332.2px with the row hidden and 292.2px with it shown, in both the idle and the recording state. The 40px is one more wrapped row (a 2rem button plus the 0.5rem gap), so Start recording takes the reading space the test says it must keep. Fit the controls on the existing row at that width, or move them out of the wrapping row, and rerun the test. The loop stops at the first width, so the 390px case has not run yet.

Comment thread web/interview.js
if (nodes.screenRecording.checked) return;
detachSelfView();
if (active && !nodes.meetPresentation.checked)
detachSelfView = attachSelfView(nodes.selfView, state.localUserStream);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Local recording now paints the candidate's self view without the "I am recording this interview with a screen recorder" tick, but the README still says that without the tick the interview page shows no self view. It also never mentions Start recording or Start & record. Update that README paragraph in this PR so it describes both paths.

@jserv jserv left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Rebase onto the latest main branch, fix any regressions detected by GitHub Actions, and address the review comments.

Comment thread web/interview.js Outdated
Hipsterthomas and others added 5 commits October 26, 2026 10:38
Practice interviews need a personal recording without extra recording
services. Keep capture and microphone mixing in the browser so
candidates can download WebM files while recording failures leave
interviews usable.

Closes sysprog21#424
Devices can change while the capture picker is open. Recheck setup
before joining so capture permission cannot bypass device readiness
or a return to the lobby.

Co-authored-by: Jim Huang <jserv.tw@gmail.com>
Device changes and recorder state transitions must not leave a local
recording silent or its controls unreachable. Preserve interview media
ownership and the existing reading space while making recording
notices and the documented capture choices available to candidates.
System fonts can make the recording timer wrap the turn control and
reduce the problem's reading space. Keep the existing dock height
when recording starts, including on narrow screens.
Connection failures still offer offline practice. Keep local recording
available there without another microphone request, while leaving
camera and room cleanup intact. Release the borrowed microphone when
the session really ends.
Comment thread web/interview.js
state.localUserStream = pool.stream;
await localRecording.start();
if (finished) return;
if (!sampleReadiness().ready) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If sampleReadiness() turns not-ready while the picker is open, this returns to editable setup with the recorder already running. Picking another microphone then makes the device pool stop the original track, but the recorder keeps its snapshot of that stopped track, while the interview joins with the replacement. A second "Start & record" click is a no-op because recording is active, so the saved file silently has no candidate voice. Stop the recording before returning to setup (and start afresh once readiness passes), or rebind the mic source when the pool replaces the track.

Comment thread web/local-recorder.js
clearInterval(timer);
paint(failure);
};
video.addEventListener("ended", stopRecording, { once: true });

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The video.readyState check happens before await context.resume(), but the ended listener is only attached here, after it. If the user clicks "Stop sharing" during that await, startup still reports "Recording locally" with an ended video track, and the mixed-audio destination stays live so the recorder never stops itself. The timer, recorder and AudioContext then run until the user stops manually. Attach the ended listener right after the video check, and re-check video.readyState after the await, cancelling startup (release()) if capture already ended.

@jserv
jserv dismissed their stale review October 26, 2026 03:55

No merge conflicts at the current head.

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.

Let candidates save interview recordings locally

2 participants