마지막 업데이트 2026-07-24
iPad(iPadOS 26.3 + Chrome/CriOS 150 = WebKit) 게스트가 인트로 영상(오프닝송, video_dubutiki_long_introsong)을 재생하는데 소리가 나오지 않았다. 영상 화면과 재생 시간(currentTime)은 정상적으로 흐르고 muted:false·volume:1인데도 무음이었고, 아동이 도움 메뉴에서 '🙉 소리가 안 들려요'를 신고했다(재생 시작 약 121초 시점).
iPad에서는 마이크 EC(voice-processing) 활성 중 <video> 직접 재생이 시스템 덕킹으로 감쇠되는 문제(PPI-1101)를 피하기 위해, 영상 오디오를 WebRTC loopback(통화 카테고리, 덕킹 면제)으로 우회 출력한다. iOS 계열 게스트에서만 활성화된다.
영상 loopback은 AI 세션과 같은 공유 AudioContext(ensureSharedTtsAudioContext)를 탄다. 그런데 이 ctx를 resume하는 트리거가 ①명시적 호출(warmup/attach) ②pointerdown/touchend 제스처 뿐이고, 포그라운드 복귀(visibilitychange)에는 아무 것도 없었다. iOS는 백그라운드 진입 시 AudioContext를 suspend하는데, 복귀 후 아동이 탭하지 않으면 ctx가 suspended로 남아 loopback 그래프가 무음이 된다.
| 신호 | 값 | 의미 |
|---|---|---|
| ISSUE_DIAG media[0] | video · paused:false · muted:false · volume:1 · currentTime:76.4 · srcKind:blob | 인트로 영상은 재생 중(시간 흐름) — 렌더는 정상, 오디오만 우회됨 |
| 복귀 이벤트(286.10s) | tab visible / track unmuted 만 기록 | ctx resume / attach / tap 트리거 전무 → suspended 유지 |
| 286~297s | video play/pause 이벤트 0건 | attach()도 안 불려 ctx resume 기회 없음 |
| 워치독 경고 | 0건 (silent/lost/failed 없음) | source·remote 동시 무음 = 사각지대라 미발동 |
| AUDIO_PROBE | suspectedSilent:false · ctxStateBefore:suspended | 판정 무의미 — 프로브는 매번 별도 throwaway ctx를 새로 만듦(공유 loopback ctx 상태와 무관, OS 출력계층 동작만 증명) |
신뢰도. 공유 ctx의 실제 state가 로그에 직접 안 찍혀 "처음부터 무음" 가능성을 100% 배제하진 못한다. 다만 신고가 백그라운드→포그라운드 복귀(239→286s) 직후이고 복귀 이벤트에 resume 트리거가 전무하다는 점이 "복귀 시 ctx 미복구 → loopback 무음"을 가장 강하게 가리킨다. (후속 개선 ③으로 diag에 공유 ctx state를 함께 기록하면 다음부턴 즉시 확정 가능.)
| 파일 | 변경 |
|---|---|
| video-audio-loopback.ts | resume() 추가 — suspended 공유 ctx resume + 백그라운드 중 정지된 loopback <audio> 재생 복구. 멱등. |
| meet-video.tsx | visibilitychange→visible 시 loopback.resume() + 재생 중이면 attach() 재확인. (비-iOS는 컨트롤러 없어 no-op) |
| use-ai-session.ts | 공유 ctx 소유자에서 visibilitychange→visible 시 ctx resume — 영상뿐 아니라 TTS 무음까지 커버. |
| video-audio-loopback.test.ts | resume() 동작 검증 3건 (suspended 복구 / running 중복 미트리거 / dispose 후 no-op). |
// video-audio-loopback.ts — 유일한 복구 경로: ctx를 running으로 resume(): void { if (this.disposed) return; const ctx = this.options.getAudioContext(); if (ctx && ctx.state === "suspended") { void ctx.resume().catch(() => {}); } const audio = this.audioElement; if (audio && audio.srcObject && audio.paused) { void Promise.resolve(audio.play()).catch(() => { /* resume 실패 로깅 */ }); } }
검증: loopback 유닛 테스트 전체 통과(신규 3건 포함) · tsc --noEmit 에러 없음. 실기기 검증은 dev 배포 후 진행.
기기: iPad + Chrome(CriOS) 우선(원 로그가 CriOS 150), Safari 병행.
관련 문서. loopback 도입 배경 iOS 덕킹 — WebRTC loopback 우회 (PPI-1101), loopback sink 라우팅 MeetVideo loopback 오디오 출력 sink 라우팅 원인분석·수정, 무음 관측 공백 전반 아동측 미디어·세션 이상 원인 판별용 로깅 추가 범위, clone 무음 계열 iOS WebKit clone track 무음 (PPI-1121).