영상 재생 후 종료 이벤트 미발생 분석 & NAVER 인앱 브라우저 대응

마지막 업데이트 2026-07-22

PPI 게스트(아동) 수업 — [MEET_VIDEO] 영상 종료 후 다음 스텝 자동 전환 실패 원인 분석 및 수정 기록
작성일 2026-06-01 분석 대상: LogRocket 세션 로그 3건 영역: V2 client-guest / MeetVideo 커밋 76a54a1d
목차

01문제 정의

게스트(아동) 수업 중 일부 단말에서 영상이 재생된 뒤 종료 이벤트가 발생하지 않아, 다음 스텝으로 자동 전환되지 못하고 멈추는 현상이 보고되었다. 호스트(치료사)가 수동으로 다음 스텝으로 넘겨야 진행이 되었다.

분석은 [MEET_VIDEO] 로그 태그(apps/web/shared/ui/meet-video.tsx)를 중심으로, LogRocket 세션 JSON 3건을 시계열로 재구성하여 진행했다.

02핵심 원인 — DECODE_OVERLOAD

DECODE_OVERLOADonWaiting 핸들러(diagnoseWaitingCase)에서만 찍히는 로그로, 다음을 의미한다.

// meet-video.tsx — diagnoseWaitingCase (요지)
if (document.visibilityState === "hidden") return "TAB_BACKGROUNDED";
if (isRecentSeek)                          return "SEEK_REBUFFER";
if (corruptedVideoFrames > 0)             return "CODEC_CORRUPT";
if (bufferedAheadSec > 1 && droppedVideoFrames > 10) return "DECODE_OVERLOAD";
if (bufferedAheadSec > 1)                  return "DECODE_OVERLOAD";  // ← 데이터 있는데 디코드 정체
...
return isBlob ? "BLOB_READ_STALL" : "NETWORK_STALL";
즉 아동 화면에서는 영상이 한 장면에 얼었다가 다시 재생됐다가를 반복(버퍼링처럼 보이지만 원인은 단말 디코딩 과부하)했고, 재생 구간에서도 프레임 드랍으로 끊겼을 가능성이 높다.

03영상 종료(onEnd) 처리 3경로와 실패 메커니즘

MeetVideo는 영상 종료(→ 다음 스텝 전환)를 다음 3경로로 보장하려 한다. 그런데 세 경로 모두 currentTime이 영상 끝에 도달해야 동작한다.

경로트리거 조건stall 시 한계
ended 이벤트영상이 실제로 끝까지 재생됨stall로 끝에 못 가면 미발생
② near-end (timeUpdate)currentTime ≥ duration − 0.1currentTime이 안 늘면 미발생
③ duration-timeout fallback재생 시작 시 타이머 예약재스케줄 설계 결함 (아래)

③ duration-timeout fallback의 결함

const remaining = video.duration - video.currentTime;
if (remaining > 0.3 && attempt < 5) {
  scheduleCheck(remaining * 1000, attempt + 1);  // ← 매번 "남은 재생시간"만큼 또 대기
  return;
}

영상이 stall되어 currentTime이 거의 안 늘면 remaining도 안 줄어, 매 시도마다 다시 영상 길이만큼 대기한다. 최대 5회 재시도라 발동까지 걸리는 실제 시간이 ≈ (시도 횟수) × remaining으로 늘어나 — 이 폴백이 막아야 할 바로 그 stall 상황에서 발동이 한참 뒤로 밀린다. 그 사이 호스트가 먼저 수동 전환하면 폴백은 발동 기회를 잃는다.

04케이스별 분석

케이스같은 이슈?근거
아동324 6회기 맞음 전환영상(video_tiki_bridge_end_event_0)에서 32초간 DECODE_OVERLOAD 반복 → 종료 이벤트 3경로 모두 미발생 → 호스트 수동 전환(17:46:15)
아동040 31회기 맞음 인트로영상에서 DECODE_OVERLOAD stall → 종료 이벤트 미발생 → 호스트 수동 전환(19:32:24). 아동324와 동일 메커니즘
아동308 4회기 아님(현상만 유사) DECODE_OVERLOAD는 동일 발생했으나, 영상 종료 fallback(duration-timeout / near-end)이 정상 작동해 자동 진행. 첫인사 stall은 AI 대화 스텝이라 영상 종료에 의존하지 않음

아동324 — 문제 구간 타임라인 (KST)

시각이벤트
17:45:36.426Video play (이후 play 이벤트 없음)
17:45:36 ~ 17:46:08Video waiting [DECODE_OVERLOAD]canplay 6회 반복 (0.5~4초씩 정지)
ended / near-end / duration-timeout 전부 없음
17:46:15.413[GUEST_PAGE_SESSION] Step changed by host → 호스트 수동 전환

05저사양 분류는 실패했나?

아니다. 아동324 단말은 저사양으로 정상 분류되었다.

[LowPerf] result: { isLowPerformanceDevice: True,
  ua: 'Android 10; SM-P610', hardwareConcurrency: 8, deviceMemory: 4 }
단말
Samsung SM-P610 (갤럭시탭 S6 Lite), Android 10, RAM 4GB
분류 근거
Android 규칙 deviceMemory > 0 && ≤ 4 → 저사양 True
decodingInfo
데스크탑 전용 경로라 안드로이드 태블릿엔 미사용 (동기 UA/RAM 판정 사용)
발견된 별개 잠재 결함: V2 guest-layout-content.tsx에서 MeetVideoisLowPerformanceDevice prop을 전달하지 않는다(MeetAvatar에만 전달). 프리캐시 히트 시엔 480p가 반환되어 무해하나, 캐시 미스 시 풀해상도가 서빙될 수 있다.

결론: 분류·480p 다운그레이드 경로를 모두 거쳤음에도 끊긴 것 → 근본 원인은 저사양 단말이 동시 부하(영상 디코드 + mediasoup + OpenAI Realtime + AI_ENGLISH_GUARD MFCC 2599회)를 못 버틴 것.

06NAVER 인앱 웹뷰 연관성

아동324 세션은 NAVER Android 인앱 웹뷰 안에서 실행됐다(LogRocket "NAVER, Android" 판별). 이는 디코딩 악화 요인으로 작용한다.

요인인앱 웹뷰(NAVER)일반 Chrome
프로세스 구조호스트 앱 프로세스 내 단일 렌더러, 메모리 공유멀티프로세스 + 전용 GPU 프로세스
영상 하드웨어 디코딩WebView 빌드/설정 따라 제한·SW 폴백 가능풀 하드웨어 가속
동시 미디어 부하좁은 자원 안에서 경쟁분산 처리
핵심 빈틈: NAVER 인앱 웹뷰 UA에는 Chrome/…이 포함돼 기존 브라우저 판정이 이를 "Chrome"으로 오판 → 브라우저 경고도 안 뜨고, 카카오 전용 인앱 탈출 로직에도 안 걸려 — 경고·탈출 둘 다 빠져나간 채 불안정한 웹뷰 환경으로 그대로 진입했다.

로그 보강 증거: video setSinkId failed: A user gesture is required — 인앱 웹뷰의 엄격한 미디어/제스처 정책의 전형적 증상.

07수정 내용 — 인앱 브라우저 탈출 NAVER 일반화

카카오 전용이던 인앱 브라우저 탈출 로직을 일반화하여 NAVER 인앱 브라우저도 외부 브라우저(Chrome)로 리다이렉트하도록 했다. 카카오 동작은 100% 보존.

파일변경
lib/user-agent-utils.tsdetectInAppBrowser() 추가(kakaotalk/naver 판정). 인앱을 Chrome으로 오판하지 않도록 parseBrowser 선판정 → "네이버/카카오톡 인앱 브라우저"로 표기 → isRecommendedBrowser=false
hooks/use-in-app-browser-redirect.ts 신규기존 카카오 훅 일반화. 카카오+NAVER 모두 탈출. iOS Chrome 미설치 등 탈출 실패 시 "이동 중" 화면에 갇히지 않도록 폴백
client-guest/in-app-browser-redirect-guard.tsx 신규가드 컴포넌트 일반화 (구 kakao-redirect-guard 대체)
client-guest/layout.tsx, pages/guest-entry.tsx새 훅/가드로 교체
삭제 use-kakao-browser-redirect.ts, kakao-redirect-guard.tsx일반화로 대체

판정 로직

export function detectInAppBrowser(userAgent: string): InAppBrowser {
  if (!userAgent) return null;
  const ua = userAgent.toUpperCase();
  if (ua.includes("KAKAOTALK")) return "kakaotalk";
  // 정식 브라우저 NAVER Whale("Whale/")과 구분하기 위해 inapp 토큰으로만 판정
  if (ua.includes("NAVER(INAPP")) return "naver";
  return null;
}

한계 (정직하게)

08권장 후속 조치

09커밋 정보

커밋
76a54a1debadb40fc0d13424a06f7f7147894071
메시지
feat: 인앱 브라우저 탈출 로직을 NAVER까지 일반화
브랜치
origin/PPI-998 (PR용). develop은 미반영(브랜치 보호)
변경 규모
9 files, +224 / −142
참고: 커밋에는 인앱 브라우저 변경(5개 파일) 외에 테스트모드 UserAgent 복사 기능 WIP(3개 파일: page.tsx, log-user-agent.ts, logrocket-utils.ts)가 git add -A로 함께 포함되었다.