마지막 업데이트 2026-07-22
조기 도착으로 미니게임 대기실을 거쳐 자동 입장한 아동에게서, 수업 페이지가 실물 카메라 대신 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에 근본적으로 강하다.