fix: addresses review - differentiate webcam error types and handle stream acquisition

This commit is contained in:
dheerajmr01
2026-04-04 12:12:00 -05:00
parent 20b0899c05
commit 954b99e962
5 changed files with 38 additions and 4 deletions
+1
View File
@@ -25,6 +25,7 @@ dist-ssr
*.sw?
release/**
*.kiro/
.claude/
# npx electron-builder --mac --win
# Playwright
+34 -4
View File
@@ -103,6 +103,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
const allowAutoFinalize = useRef(false);
const discardRecordingId = useRef<number | null>(null);
const restarting = useRef(false);
const webcamReady = useRef(false);
const selectMimeType = () => {
const preferred = [
@@ -182,6 +183,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
let cancelled = false;
let acquiredStream: MediaStream | null = null;
webcamReady.current = false;
const acquire = async () => {
try {
@@ -217,11 +219,21 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
};
});
webcamStream.current = stream;
webcamReady.current = true;
} catch (cameraError) {
if (!cancelled) {
console.warn("Failed to get webcam access:", cameraError);
setWebcamEnabledState(false);
toast.error(t("recording.cameraBlocked"));
const isDeviceError =
cameraError instanceof DOMException &&
[
"NotFoundError",
"DevicesNotFoundError",
"OverconstrainedError",
"NotReadableError",
].includes(cameraError.name);
toast.error(t(isDeviceError ? "recording.cameraNotFound" : "recording.cameraBlocked"));
webcamReady.current = true;
}
}
};
@@ -230,6 +242,7 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
return () => {
cancelled = true;
webcamReady.current = false;
if (acquiredStream) {
acquiredStream.getTracks().forEach((track) => track.stop());
webcamStream.current = null;
@@ -464,9 +477,26 @@ export function useScreenRecorder(): UseScreenRecorderReturn {
}
}
if (webcamEnabled && !webcamStream.current) {
setWebcamEnabledState(false);
toast.error(t("recording.cameraDenied"));
if (webcamEnabled) {
if (!webcamReady.current) {
await new Promise<void>((resolve) => {
const interval = setInterval(() => {
if (webcamReady.current) {
clearInterval(interval);
resolve();
}
}, 50);
setTimeout(() => {
clearInterval(interval);
resolve();
}, 5000);
});
}
if (!webcamStream.current) {
// The useEffect already showed the appropriate error toast
// (cameraNotFound or cameraBlocked), so just disable the state.
setWebcamEnabledState(false);
}
}
stream.current = new MediaStream();
+1
View File
@@ -31,6 +31,7 @@
"microphoneDenied": "Microphone access denied. Recording will continue without audio.",
"cameraDenied": "Camera access denied. Recording will continue without webcam.",
"cameraDisconnected": "Webcam disconnected.",
"cameraNotFound": "Camera not found.",
"permissionDenied": "Recording permission denied. Please allow screen recording."
}
}
+1
View File
@@ -31,6 +31,7 @@
"microphoneDenied": "Acceso al micrófono denegado. La grabación continuará sin audio.",
"cameraDenied": "Acceso a la cámara denegado. La grabación continuará sin cámara web.",
"cameraDisconnected": "Cámara web desconectada.",
"cameraNotFound": "Cámara no encontrada.",
"permissionDenied": "Permiso de grabación denegado. Por favor permite la grabación de pantalla."
}
}
+1
View File
@@ -31,6 +31,7 @@
"microphoneDenied": "麦克风权限被拒绝。录制将继续,但不包含音频。",
"cameraDenied": "摄像头权限被拒绝。录制将继续,但不包含摄像头画面。",
"cameraDisconnected": "摄像头已断开连接。",
"cameraNotFound": "未找到摄像头。",
"permissionDenied": "录屏权限被拒绝。请允许屏幕录制。"
}
}