마지막 업데이트 2026-07-29
조기 입장(대기실) 경로에서 카메라 deviceId 유실 → 가상 카메라(Mirametrix) 캡쳐 원인분석의 핵심을 수사반장식으로 먼저 안내합니다. 기술적 결론과 원문 근거는 아래 본문에 보존되어 있습니다.
조기 도착으로 미니게임 대기실을 거쳐 자동 입장한 아동에게서, 수업 페이지가 실물 카메라 대신 Mirametrix Virtual Camera(LG gram 번들 Glance 시선추적 SW의 가상 카메라)를 잡아 아동 영상이 캡쳐되지 않은 사례. 근본 원인은 두 겹이다 — ① 대기실 자동입장 콜백(handleWaitingRoomComplete)의 의존성 배열 누락으로 인한 stale closure가 선택한 카메라 deviceId를 null로 저장하고, ② 수업 페이지는 PROBLEMATIC_CAMERAS 필터를 다시 적용하지 않고 deviceId가 없으면 그대로 OS 기본 카메라로 폴백한다. 브라우저(Edge) 문제가 아니며, 제시간에 바로 입장하는 경로에서는 재현되지 않는다.
LG Camera (0bda:5641), 문제의 가상 카메라는 Mirametrix Virtual Camera.LG gram은 Glance by Mirametrix(시선추적)를 기본 번들로 설치하며, 이것이 OS에 "Mirametrix Virtual Camera"를 등록하고 종종 기본 카메라가 된다. 이 가상 카메라는 백그라운드 앱이 실제 프레임을 넣지 않으면 검은/멈춘 화면을 낸다. 트랙은 readyState:"live"·enabled:true라 앱은 "카메라 켜짐"으로 인식한다.
guest-entry.tsx:395-412의 handleWaitingRoomComplete는 의존성 배열이 [roomId]뿐이라, roomId가 마운트 시(SavedUser) 먼저 세팅된 뒤 콜백이 그 render의 tryV2Flow를 고정한다. 그 시점 selectedVideo는 아직 null. 이후 auto-select로 selectedVideo가 LG 카메라가 되어도 roomId가 안 바뀌므로 콜백은 갱신되지 않는다. 8분 뒤 대기실 카운트다운이 끝나 이 옛 콜백이 실행되면 selectedVideo?.deviceId || null = null이 저장된다.
use-device-manager.ts:33-46 | PROBLEMATIC_CAMERAS(=Mirametrix) 필터 — 입장 페이지 드롭다운 목록만 손봄 |
guest-entry.tsx:520 / 533 | auto-select: setSelectedVideo(prev || videoDevices[0]) + VideoDeviceAutoSelected 트래킹 |
guest-entry.tsx:395-412 | handleWaitingRoomComplete = useCallback(…, [roomId]) → tryV2Flow(roomId) (stale) |
guest-entry.tsx:114-121 | tryV2Flow: sessionStorage["client-guest-device-ids"] = { videoDeviceId: selectedVideo?.deviceId || null, … } |
수업 페이지는 PROBLEMATIC_CAMERAS 필터를 다시 돌리지 않고, 저장된 deviceId 문자열만 신뢰해 getUserMedia에 넘긴다. deviceId가 없으면 video: true(=아무 카메라나)로 폴백하므로 OS 기본값인 Mirametrix가 그대로 잡힌다. 즉 필터 결과는 "deviceId 문자열"로만 이어지며, 그 문자열이 null이면 방어막이 전혀 없다.
session/page.tsx:59-71 / 133 | client-guest-device-ids 읽어 videoDeviceId를 GuestLayout에 전달 |
local-media-constraints.ts:33-38 | videoDeviceId 없으면 video: { width, height, facingMode } — deviceId 미지정 → 기본 카메라 |
use-local-media-stream.ts:118-178 | getUserMedia(constraints) 후 획득 트랙 = Mirametrix, OverconstrainedError 없이 성공(재획득/필터 없음) |
이 버그는 "조기 도착 → 미니게임 대기실 → 자동 입장" 경로 + Mirametrix 같은 가상 카메라가 OS 기본 카메라인 기기에서만 재현된다. 제시간에 바로 입장하면 tryV2Flow가 현재 render에서 직접 호출되어(guest-entry.tsx:274 / 356) LG 카메라 deviceId가 정상 저장되므로 가상 카메라에 물리지 않는다.
handleWaitingRoomComplete가 최신 선택값을 읽도록 — 선택 device를 ref로 두고 tryV2Flow가 ref.current를 읽거나, 의존성 배열을 보강한다. (근본 원인 ① 해소)getUserMedia 획득 후 비디오 트랙 label이 PROBLEMATIC_CAMERAS에 걸리면 실물 카메라로 재획득하거나 경고를 노출. (deviceId가 null/유실돼도 최후 방어)PROBLEMATIC_CAMERAS 상수를 getUserMedia 경로와 공유해 필터 정의를 단일화.백로그 제목 제안: 조기 입장(대기실 자동입장) 경로에서 선택한 카메라 deviceId가 stale closure로 유실되어 기본 가상 카메라(Mirametrix)로 물리는 버그 수정
3222e299)원인 ①(stale closure)을 guest-entry.tsx의 handleWaitingRoomComplete 의존성 배열에 선택 device 상태를 추가하는 1줄 변경으로 해소했다.
}, [roomId]);
+ }, [roomId, selectedVideo, selectedAudio, selectedAudioOutput]);
이제 selectedVideo가 auto-select로 LG 카메라가 되면 의존성이 변해 handleWaitingRoomComplete가 재생성되고, 그 콜백은 최신 selectedVideo를 캡쳐한 tryV2Flow를 들게 된다.
이 부모 측 수정이 효과를 내려면 자식 WaitingRoom이 최신 콜백을 호출해야 하는데, waiting-room.tsx의 카운트다운 effect가 onCountdownComplete를 의존성에 포함하고 있어 두 조각이 올바르게 맞물린다.
부모 guest-entry.tsx:409 | deps에 selectedVideo/Audio/AudioOutput 추가 → 선택값 변경 시 콜백 재생성(최신 tryV2Flow 캡쳐) |
자식 waiting-room.tsx:22-28 | useEffect(…, [isComplete, onCountdownComplete]) → 콜백 갱신마다 effect 재실행, isComplete 전환 시 최신 콜백을 setTimeout으로 캡쳐 |
label이 PROBLEMATIC_CAMERAS인지 검사·재획득하는 방어막이 없다. 이번 시나리오는 ①만 고쳐도 증상이 사라지지만, 다른 경로로 deviceId가 비면(기기 분리, 아래 레이스) 기본 카메라 폴백이 재발할 수 있어 2차 방어(label 필터) 추가를 권장.isComplete=true가 뜨면(calledRef로 1회만 호출) 그 순간 selectedVideo가 아직 null일 수 있다. 대기실은 보통 수 분 일찍 온 경우라 현실성은 낮지만, ②의 방어막이 있으면 함께 막힌다.tryV2Flow가 ref로 최신 선택값을 읽게 하면 의존성 배열 방식보다 stale closure에 근본적으로 강하다.