fix: address coderabbit review (concurrent stream, collapsed label, unified select, test quality)

- useCameraDevices: remove getUserMedia label probe to avoid conflict with
  useScreenRecorder acquiring the real stream; use enumerateDevices only and
  fall back to 'Camera <id>' for unlabeled devices; gate effect on enabled flag
- LaunchWindow: fix selectedCameraLabel to reflect loading/error/empty states
  in the collapsed view (was always showing 'Default Camera')
- LaunchWindow: unify webcam <select> to a single always-mounted element
  (sr-only when unavailable); mirrors the mic selector pattern
- useCameraDevices.test.ts: re-seed mockGetUserMedia in beforeEach after
  vi.resetAllMocks(); update permission test to assert fallback label behavior
This commit is contained in:
Etienne Lescot
2026-03-27 15:15:43 +01:00
parent 9762448929
commit 9817c85acf
3 changed files with 22 additions and 29 deletions
+3 -13
View File
@@ -15,6 +15,7 @@ export function useCameraDevices(enabled: boolean = false) {
selectedDeviceIdRef.current = selectedDeviceId;
useEffect(() => {
if (!enabled) return;
let mounted = true;
const loadDevices = async () => {
@@ -22,19 +23,8 @@ export function useCameraDevices(enabled: boolean = false) {
setIsLoading(true);
setError(null);
// Re-request permission if labels are missing
const allDevicesBefore = await navigator.mediaDevices.enumerateDevices();
const needsPermission = allDevicesBefore.some((d) => d.kind === "videoinput" && !d.label);
if (needsPermission && enabled) {
try {
const stream = await navigator.mediaDevices.getUserMedia({ video: true });
stream.getTracks().forEach((track) => track.stop());
} catch (e) {
console.warn("Failed to get camera permission for labels:", e);
}
}
// Enumerate without requesting a second stream — the recorder handles
// the real acquisition; unlabeled devices fall back to their device ID.
const allDevices = await navigator.mediaDevices.enumerateDevices();
const videoInputs = allDevices
.filter((device) => device.kind === "videoinput")