분석 Web Audio API

setSinkId() 호출 시 시스템 볼륨 슬라이더 미동작 분석

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

문서 읽는 법 · 수사반장식

이 문서는 이렇게 읽으면 됩니다

setSinkId() 호출 시 시스템 볼륨 슬라이더 미동작 분석의 핵심을 수사반장식으로 먼저 안내합니다. 기술적 결론과 원문 근거는 아래 본문에 보존되어 있습니다.

핵심 흐름 펼쳐 보기
  1. 결론부터 확인한 뒤 증상과 영향을 읽습니다.
  2. 시간순 수사 흐름에서 가설과 반증을 따라갑니다.
  3. 마지막으로 로그·코드·검증 섹션에서 근거를 확인합니다.
  • 결론 — 범인부터 공개한다
  • 1. 문제 요약
  • 2. 오디오 라우팅 경로 비교
프로젝트: PPI 작성일: 2026-05-22

결론 — 범인부터 공개한다

범인은 setSinkId() 자체다. 특정 deviceId로 오디오 출력을 직접 바인딩하는 순간 OS 기본 오디오 세션(믹서)을 우회하므로, 시스템 볼륨 슬라이더는 해당 스트림에 대한 제어권을 잃는다. 버그가 아니라 W3C Audio Output Devices API 스펙대로의 동작이며, SW로 볼륨을 제어할 유일한 수단은 HTMLMediaElement.volume이다 — 앱 내 볼륨 슬라이더 제공(커밋 id-016)이 그 배경이다.

1. 문제 요약

게스트(아동) 페이지에서 USB 헤드셋을 연결하면 setSinkId()로 오디오 출력이 헤드셋으로 강제 라우팅됩니다. 이 시점부터 macOS / Windows 시스템 볼륨 슬라이더를 조작해도 앱 내 오디오(핑퐁이 음성, 활동 영상)의 볼륨이 변하지 않습니다.

시스템 볼륨 슬라이더가 효과가 없는 것처럼 보이는 게 아닙니다. 실제로 해당 오디오 스트림에 대한 제어권이 없습니다.

2. 오디오 라우팅 경로 비교

수사 방향 — 증상이 아니라 경로를 의심하다

"슬라이더가 안 먹는다"는 증상은 앱의 볼륨 코드 버그처럼 보인다. 하지만 실제로는 해당 스트림에 대한 제어권 자체가 없는 상태다. 그래서 볼륨 코드를 뒤지는 대신, 소리가 장치까지 흘러가는 경로부터 두 경우를 나란히 놓고 비교했다.

일반 재생과 setSinkId() 사용 시의 경로 차이입니다.

▶ 일반 오디오 재생 (setSinkId 미사용)
  Browser Audio Output
    → OS Audio Session / Mixer
      → 시스템 볼륨 슬라이더 적용 ✓
        → 기본 출력 장치 (스피커 / 헤드셋)

▶ setSinkId(deviceId) 호출 후
  Browser Audio Output
    → 특정 장치 직접 연결 (OS Mixer 우회)
      → 시스템 볼륨 슬라이더 무효 ✗
        → USB 헤드셋

3. 기술적 원인

setSinkId()는 W3C Audio Output Devices API 스펙에 따라 HTMLMediaElement의 오디오 출력을 특정 deviceId로 직접 바인딩합니다. 이 경로는 OS의 기본 오디오 세션(macOS Core Audio, Windows WASAPI)이 노출하는 볼륨 컨트롤 레이어를 거치지 않습니다.

OS 시스템 볼륨 슬라이더는 "기본 출력 장치(default output device)"의 세션 볼륨을 조절합니다. setSinkId()로 지정된 스트림은 OS 입장에서 별개의 장치 스트림이기 때문에 기본 출력 슬라이더의 제어 범위 밖에 있습니다.

브라우저에서 OS 볼륨 변경 이벤트를 수신하는 표준 Web API는 존재하지 않습니다. navigator.mediaSession은 재생/정지/트랙 이동만 지원하며 볼륨 이벤트를 노출하지 않습니다.

4. 이 프로젝트에서 setSinkId()를 사용하는 이유

범인을 제거할 수 있는가

원인이 setSinkId()로 확정됐으니 다음 질문은 하나다 — 이 호출을 빼면 되지 않는가. 그래서 프로젝트가 이 API를 쓰는 이유와 적용 범위를 확인했다.

게스트 입장 시 findPreferredDevice()로 USB 헤드셋을 자동 감지하고, 감지된 deviceId를 각 오디오 엘리먼트에 적용합니다. OS 설정과 무관하게 핑퐁이 음성과 활동 영상을 확실히 헤드셋으로 출력하기 위한 목적입니다.

파일라우팅 대상
use-ai-session.ts핑퐁이 AI 음성
meet-video.tsx활동 영상 오디오
use-host-session-relay.ts호스트→게스트 relay 오디오
use-host-media-manager.ts원격 비디오 오디오

5. 볼륨 제어 가능 여부 정리

볼륨 제어 방법setSinkId 미사용setSinkId 사용 후
macOS 시스템 볼륨 슬라이더 동작 미동작
Windows 시스템 볼륨 슬라이더 동작 미동작
헤드셋 하드웨어 볼륨 버튼 동작 동작 (장치 자체 제어)
HTMLMediaElement.volume 프로퍼티 동작 동작 — 유일한 SW 제어 수단
navigator.mediaSession 볼륨 미지원 (API 없음) 미지원 (API 없음)

6. 해결 방법

우회로 수색의 결과

USB 헤드셋 강제 라우팅이라는 목적상 setSinkId()는 뺄 수 없고, 볼륨 제어 수단을 전수 점검(위 표)한 결과 남은 SW 경로는 하나뿐이었다.

setSinkId()로 라우팅된 스트림의 볼륨을 조절하는 유일한 SW 수단은 HTMLMediaElement.volume 프로퍼티 직접 조작입니다. 따라서 앱 내 볼륨 슬라이더 제공이 필수이며, 이것이 커밋 id-016의 배경입니다.

// setSinkId() 라우팅 후 볼륨 제어
audioElement.setSinkId(deviceId);
audioElement.volume = 0.8;  // 0.0 ~ 1.0, 이 방법만 유효

헤드셋 하드웨어 볼륨 버튼이 있는 경우 동작하지만 모든 기기에 해당하지 않으므로, 앱 내 슬라이더를 함께 제공하는 것이 정석입니다.

추가 작업(setSinkId 이후 OS 볼륨과 연동)으로 이 문제를 해결하는 방법은 Web 표준 범위 내에서 존재하지 않습니다.

7. 관련 분석 — iPad Pro 내장 입출력 감쇠

setSinkId()와 앱 내 볼륨 제어 이슈와 별개로, iPad Pro 내장 스피커+내장 마이크 조합에서는 Safari/WebKit 마이크 캡처 세션과 echo cancellation이 영상 오디오를 시스템 레벨에서 감쇠시킬 수 있다. 자세한 분석은 iPad Pro 내장 스피커/마이크 영상 오디오 감쇠 원인 분석을 참고한다.