마지막 업데이트 2026-07-22
currentTime이 약 1.8초 → 33.8초로 순간 점프(약 32초 건너뜀)하면서, 위치 시계가 끝(52.97s)에 실제 재생 시간보다 훨씬 일찍 도달. 그 시점에 near-end fallback이 onEnd을 호출해 다음 스텝으로 자동 전환됨. 아동은 영상 중간부(약 2~34초 구간)를 보지 못함.
11:39:44.736 활동 1→2 자동 전환 (AI 작별 발화 "…그러면 안녕." 직후) → full:video 스텝 진입 └ 재접속/새로고침 없음, 정상 auto-transition 11:39:47.485 RESOURCE_CACHE cached → blob buffered [0, 52.97] 전체 버퍼링 (네트워크 아님) 11:39:47.546 loadedmetadata: duration=52.97 11:39:47.719 play, currentTime=0 → watchdog.arm (firstDelay≈52.97s → 첫 tick ~11:40:40 예약) 11:39:48.168 AUDIO_PROBE currentTimeSec=0.277 (정상) 11:39:49.723 Video audio health currentTime=1.802 (정상 1x, 2초 경과 = 2초 재생) 11:39:49.741 AI_SESSION Deferred connection cleanup (safety timeout) ┐ 스텝 전환 직후 11:39:51.097 mic-audio outbound stats (producer-created) ┘ AI teardown 경합 ★ 이 3초 사이(49.7→52.7) currentTime 1.8s → 33.8s 점프 (약 32초) 11:39:52.722 WARN Video waiting [SEEK_REBUFFER] currentTime=33.826 buffered [0,52.97] · droppedFrames=4 / totalVideoFrames=149 · RAM 4GB / 8코어 11:39:52.987 canplay currentTime=33.826 waitDurationMs=268 11:39:52.9~ 33.8s → 52.97s 정상 1x 재생 (약 19.4초) 11:40:12.373 Video reached near-end threshold currentTime=52.97 (≥ duration−0.1) 11:40:12.375 VideoEndWatchdog silenced (disarm, reason=near-end-fallback) 11:40:12.376 Video onEnd via near-end fallback → 다음 스텝 자동 전환 실제 벽시계 소요: 24.66초 / 정상 재생 시: 52.97초 → 약 28초 조기 종료
SEEK_REBUFFER 시점의 getVideoPlaybackQuality() 수치가 결정적이다.
play 시작: 11:39:47.719 SEEK_REBUFFER: 11:39:52.722 → 벽시계 경과 = 5.0초 totalVideoFrames = 149 → 30fps × 5.0s ≈ 150프레임 = 디코더는 정상 5초치만 생성 그런데 currentTime = 33.826 → 위치 시계가 디코딩·벽시계보다 약 29초 앞섬
currentTime)만 33.8초로 튀었다. 벽시계 3초 동안 위치가 32초 진행한 것은 배속 재생이 아니라 불연속 seek(위치 재배치)이며, native seeking 이벤트가 발생해 SEEK_REBUFFER로 분류된 것이 그 증거다.
diagnoseWaitingCase()는 "최근 3초 내 seeking 이벤트가 있었으면"(lastSeekTimeRef) waiting을 SEEK_REBUFFER로 분류한다. 즉 "건너뛰어서(seek) 그 뒤 재버퍼링이 SEEK_REBUFFER로 찍힌 것"이지, "SEEK_REBUFFER 때문에 건너뛴 것"이 아니다.
| 후보 | 판정 | 근거 |
|---|---|---|
| 앱 / 소켓 / 호스트 seek | ❌ 배제 | guest 경로의 seek는 monitor 전용(meet-video.tsx:772, requestVideoElapsed)뿐 → "Monitor video seeking" 로그 0건. 소켓/호스트의 guest 영상 위치 조작 경로 없음 |
| 영상 파일 결함 | ❌ 배제 | ffprobe 정상: 30fps · 1589프레임 · start_time 0 · aac. edit list는 media_time 2000/30000 = 0.067초 미세 오프셋뿐 |
| 네트워크 | ❌ 배제 | blob 전체 버퍼링 [0, 52.97], bufferedAhead 19.1s+ |
| 탭 백그라운드 | ❌ 배제 | 점프 구간 visibilityState=visible. hidden 이벤트는 11:43 이후에만 존재 |
| 메인스레드 프리즈 | ❌ 배제 | 점프 구간(49.7→52.7)에 다른 로그(49.741·51.097)가 정상 발생 → JS 스레드 살아있음 |
| 기기 미디어 파이프라인 clock/seek 이상 | ✅ 유력 | 위 전부 배제 후 남는 원인. 저사양 Android(4GB)+Chrome, totalVideoFrames=149 vs currentTime 33.8s 괴리 |
Video waiting 이벤트 9건이 전부 SEEK_REBUFFER(11:39·11:42·11:43·11:50대). 이 기기 미디어 파이프라인이 반복적으로 seek/clock 이상을 냈다는 뜻.
VideoEndWatchdog.arm()은 play 시점(currentTime≈0)에 firstDelay ≈ (duration−currentTime)×1000 ≈ 52.97s 뒤로 첫 tick을 예약했다(→ ~11:40:40). 그러나 그 전에 handleTimeUpdate의 near-end fallback(currentTime ≥ duration−0.1)이 11:40:12.373에 먼저 조건을 만족해 onEnd을 호출하고 워치독을 disarm("near-end-fallback")했다.
currentTime을 신뢰하도록 설계됐고, 그 currentTime 자체가 기기 쪽에서 오염됐다.
currentTime ≥ duration−0.1만으로 종료를 확정하지 말고, "arm 이후 벽시계 경과가 예상 재생 시간에 근접했는가"를 함께 요구. 점프로 인한 조기 onEnd을 방지. 단, 점프 자체는 앱이 막기 어렵고, 뒷부분(33.8→52.97)은 실제 재생되므로 "종료 시점만 늦추는" 보정에 가깝다.