Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 30 additions & 0 deletions src/cli/CliRecordRunner.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,36 @@ describe("CliRecordRunner", () => {
expect(recorder.startRecordingImmediately).not.toHaveBeenCalled();
});

it("fails, and stops the starting capture, when the camera turns on but cannot be opened", async () => {
vi.mocked(window.electronAPI.cliGetRequest).mockResolvedValueOnce({ ...request, camera: true });
recorder.setWebcamEnabled.mockImplementationOnce(async () => {
recorder.webcamEnabled = true;
return true;
});
try {
const { rerender } = render(<CliRecordRunner />);
await waitFor(() => expect(recorder.startRecordingImmediately).toHaveBeenCalledTimes(1));
// The acquire failed (another app holds the camera): the recorder turns it back off.
recorder.webcamEnabled = false;
rerender(<CliRecordRunner />);
await waitFor(() =>
expect(window.electronAPI.cliDone).toHaveBeenCalledWith(
expect.objectContaining({
success: false,
error: expect.stringContaining("camera could not be opened"),
}),
),
);
recorder.recording = true;
rerender(<CliRecordRunner />);
await waitFor(() => expect(recorder.toggleRecording).toHaveBeenCalledTimes(1));
expect(window.electronAPI.cliLog).not.toHaveBeenCalledWith("info", "Recording started");
} finally {
recorder.webcamEnabled = false;
recorder.recording = false;
}
});

it("leaves the camera off unless asked", async () => {
recorder.setWebcamEnabled.mockClear();
render(<CliRecordRunner />);
Expand Down
19 changes: 19 additions & 0 deletions src/cli/CliRecordRunner.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -253,9 +253,28 @@ export function CliRecordRunner() {
recorder.cursorCaptureMode,
]);

// A camera that cannot be opened (held by another app, OpenScreen's own HUD among them on
// Linux, or unplugged) turns itself back off in the recorder, which then records without it.
// A take asked to have the camera fails instead of reporting success without it (#1303).
// biome-ignore lint/correctness/useExhaustiveDependencies: fail is stable
useEffect(() => {
if (!requestReady?.camera || recorder.webcamEnabled) return;
if (phaseRef.current === "done" || recordingStartedAtRef.current !== null) return;
void fail(
new Error(
"The camera could not be opened: another app is using it (OpenScreen itself, when it is running), or it was disconnected",
),
);
}, [requestReady, recorder.webcamEnabled]);

// Recording state transitions: report start and arm the duration timer.
useEffect(() => {
const request = requestRef.current;
// The take failed while its capture was starting: stop it, nobody is waiting for it.
if (recording && phaseRef.current === "done" && recordingStartedAtRef.current === null) {
toggleRecordingRef.current();
return;
}
if (recording && recordingStartedAtRef.current === null) {
recordingStartedAtRef.current = Date.now();
setStatus("Recording…");
Expand Down
Loading