mediasoup 클라이언트 (producer/consumer·simulcast) — 코드레벨 동작 흐름 P1코드레벨

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

작성일: 2026-06-14 대상: 개발자 — V2 SFU 클라이언트 미디어 송수신 파악 핵심 파일: hooks/mediasoup/use-mediasoup-{producer,consumer,device}.ts

개요 · 범위

V2(SFU) 클라이언트의 미디어 송수신을 담당하는 mediasoup-client 래퍼 훅 3종. 게스트는 producer로 카메라/마이크/AI 음성을 SFU에 올리고, 모니터(카드뷰/포커스뷰)는 consumer로 받아 본다. 서버측은 #2 SFU 서버.

역할
use-mediasoup-deviceDevice 로드(rtpCapabilities)·send/recv transport 생성 기반(278L)
use-mediasoup-producer게스트: 카메라/마이크/AI 송출 + 재연결·통계(648L)
use-mediasoup-consumer모니터: 수신·layer 전환·재연결(838L)

Device 초기화 use-mediasoup-device.ts:29–48

const device = new Device();
await device.load({ routerRtpCapabilities: rtpCapabilities });  // JOIN_ROOM 응답의 rtpCapabilities

JOIN_ROOM(#2) 콜백으로 받은 router의 rtpCapabilities로 Device를 로드해야 send/recv transport·produce/consume가 가능. 이후 createSendTransport/createRecvTransport는 서버 CREATE_TRANSPORT로 transportOptions를 받아 생성.

송출(Producer) use-mediasoup-producer.ts

  1. 1Device load → createSendTransport. transport connect 이벤트 → 서버 CONNECT_TRANSPORT, produce 이벤트 → 서버 PRODUCE(producerId 반환).
  2. 2스트림별 transport.produce({track, appData}):
appData.type특징
camera-video3-layer simulcast(아래) + 트랙 clone(원본 스트림 보호)
mic-audio아동 마이크 → 서버 녹음 트리거(#3)
ai-audio핑퐁이 음성 → 모니터가 consume(V2 릴레이 경로)

카메라 simulcast 인코딩 L415–419

encodings: [
  { maxBitrate: 100000, scaleResolutionDownBy: 4 },  // L0 저화질(≈180p 썸네일)
  { maxBitrate: 300000, scaleResolutionDownBy: 2 },  // L1 중화질(≈360p)
  { maxBitrate: 900000 },                          // L2 고화질(원본 ≈720p)
]
한 카메라를 3개 해상도로 동시에 인코딩해 보내면, 수신측이 필요한 레이어만 골라 받는다. 모니터 다수가 같은 아동을 보더라도 SFU가 레이어별로 분배 → 대역폭/CPU 절약. (단 송신 인코딩 부하는 아동 기기에 집중 — 관련 bug)

재연결: sendTransport connectionstatechange="disconnected"MAX_RECONNECT_ATTEMPTS까지 지연 재시도. getStats로 RTT/대역폭/패킷손실 주기 수집(모니터링).

수신(Consumer) use-mediasoup-consumer.ts

  1. 1Device load → createRecvTransport.
  2. 2새 producer 통지(NEW_PRODUCER) 또는 JOIN_ROOM의 existingProducers → consume(producerId): 서버 CONSUME로 consumerOptions 수신 → recvTransport.consume(options)RESUME_CONSUMER.
  3. 3consume 직후 기본 spatialLayer 0(L304) — 저화질 썸네일로 시작(대역폭 절약).
  4. 4track을 appData.type별로 peerStream에 분리 저장: videoStream / audioStream(mic) / aiAudioStream(AI). 각각 new MediaStream([consumer.track]).
  5. 5consumer-closed 통지 시 해당 track 제거(카드뷰 정확한 cleanup).

레이어 전환 setConsumerLayer L717 / video-expansion

setConsumerLayer(peerId, spatialLayer)  →  emit SET_CONSUMER_LAYERS

포커스뷰/확대 시 use-video-expansionsetConsumerLayer(peerId, 2)고화질(L2) 요청. 축소하면 다시 저레이어로. 즉 "보이는 것만 고화질"로 끌어올리는 selective forwarding.

재연결 · 트랙 교체

함정 · 주의

파일 · 라인 레퍼런스

파일/심볼역할
use-mediasoup-device.tsDevice load·transport 생성 헬퍼
use-mediasoup-producer.ts (produce* L412/483/559)camera/mic/ai 송출·simulcast·재연결
use-mediasoup-consumer.ts (consume L295, setConsumerLayer L717)수신·layer 전환·트랙 분리
features/monitor/video-expansion/use-video-expansion.ts확대 시 layer 2 요청
sfu-socket/handlers/{transport,media}-handlers.ts서버측 짝(#2)

관련 문서