OPWS Developers

MIC 방송

개요

oe.broadcast.mic.start()는 WebRTC 기반 실시간 마이크 방송 세션을 시작합니다. 세션은 stop()으로 종료하고, 진행 상태는 oe.broadcast.subscribe()로 구독해 확인합니다.

시작하기 전에

MIC 방송을 시작하기 전에 아래를 준비하세요.

  • 파트너 앱에 MIC_EXECUTE capability grant 가 있어야 합니다. 없으면 CapabilityDenied 에러가 발생합니다.
  • 브라우저가 WebRTC(RTCPeerConnection)를 지원해야 합니다.
  • 사이트의 CSP(Content Security Policy)가 아래 요구사항을 충족해야 합니다.
  • 방송 대상이 될 디바이스 또는 그룹의 handle 이 필요합니다.

대상 선택

방송 대상은 TTS 방송과 동일하게 targetHandle(디바이스 또는 그룹 handle) 또는 deviceHandles(디바이스 handle 다중)로 지정합니다.

ts
const [devices, groups] = await Promise.all([
  oe.targets.list(),
  oe.groups.list(),
]);

각 항목의 handle 값을 대상 지정에 사용합니다. 그룹에 방송하면 그룹에 속한 ASSIGNED 상태 디바이스 전체가 대상이 됩니다.

CSP 요구사항

MIC를 사용하는 사이트의 Content-Security-Policy 헤더에 다음 지시문을 추가하세요.

script-src  'self';
connect-src 'self' https://openecho.example.com wss://*.livekit.cloud;
worker-src  blob:;
지시문이유
script-srcself-host UMD·MIC 코드가 자기 origin 에서 로드됩니다('self'). 번들러 경로에서 nonce 기반 CSP 를 쓰면 createClient({ scriptNonce })로 nonce 를 주입하세요
connect-srcOpen Echo API 와 실시간 오디오 스트리밍 서버(WSS)에 연결합니다
worker-src blob:미디어 엔진 내부 Worker 를 사용합니다

CSP 가 요구사항을 충족하지 않으면 MIC 코드 로드가 차단되어 MicChunkLoadFailed 에러가 발생합니다(번들러 경로).

MIC 코드가 로드되는 방식

킷 동봉 self-host UMD(openecho-sdk.<version>.umd.js)는 MIC 를 인라인으로 포함합니다. 파일 하나만 로드하면 되고, micChunkUrl·scriptNonce 옵션도 필요 없습니다.

html
<script src="/assets/openecho-sdk.<version>.umd.js"></script>

번들러(npm tarball + Vite/webpack) 경로에서는 oe.broadcast.mic.start() 호출 시 SDK 가 MIC 모듈을 동적으로 로드합니다. 번들러가 이를 별도 청크로 코드 스플릿하므로, TTS 전용 앱은 core 번들에 실시간 미디어 엔진이 포함되지 않아 번들 크기 증가를 피할 수 있습니다. 추가 설정은 필요 없습니다.

createClient({ micChunkUrl })createClient({ scriptNonce })는 번들러 경로 전용 옵션입니다. MIC 코드의 URL을 직접 지정하거나 CSP nonce 를 주입할 때 사용하세요. self-host UMD 에서는 둘 다 무의미합니다.

WebRTC 지원 확인과 프리로드

지원 여부는 화면 진입 시 한 번 oe.broadcast.mic.isSupported()로 확인하세요. start() 직전 지연을 줄이려면 oe.broadcast.mic.preload()로 MIC 코드를 미리 로드할 수 있습니다. 중복 호출해도 안전합니다.

기본 정보

메서드앱 capability참조 handle 조건반환 타입
oe.broadcast.mic.isSupported()없음(클라이언트 전용 — 서버 호출 없음, 동기)없음boolean
oe.broadcast.mic.preload()없음(클라이언트 전용 — MIC 코드 사전 로드, single-flight)없음Promise<void>

요청

두 메서드 모두 입력 파라미터가 없습니다.

응답

isSupported()는 WebRTC(RTCPeerConnection) 지원 여부를 즉시 반환합니다(동기, Promise 아님). preload()는 로드가 끝나면 resolve 되는 Promise<void>이며 반환값은 없습니다.

예제

ts
if (!oe.broadcast.mic.isSupported()) {
  // "이 브라우저는 WebRTC 를 지원하지 않습니다" 안내를 표시하고 시작 UI 는 렌더하지 않습니다.
} else {
  await oe.broadcast.mic.preload();
}

대표 에러

에러HTTP 상태원인해결 방법
MicChunkLoadFailed-preload() 중 MIC 코드 청크 다운로드 실패(번들러 경로) — 네트워크 또는 CSP 차단CSP script-src 설정을 먼저 확인한 뒤 재시도하세요(isSupported()는 동기 함수로 에러를 던지지 않음)

MIC 시작 — oe.broadcast.mic.start()

마이크 권한을 요청하고 실시간 오디오 룸에 연결한 뒤 방송 세션을 시작합니다.

기본 정보

메서드앱 capability참조 handle 조건반환 타입
oe.broadcast.mic.start(input)MIC_EXECUTEtargetHandle 또는 deviceHandles 가 유효한 노출 대상이어야 함(그룹은 ASSIGNED 디바이스 전체로 전개)Promise<MicSession>

옵션

이름타입설명기본값필수
targetHandlestring대상 디바이스 또는 그룹 handle-조건부
deviceHandlesstring[]대상 디바이스 handle 다중 지정-조건부
partialboolean일부 대상이 사용 중이어도 나머지 대상으로 시작할지 여부-X
idempotencyKeystring중복 방송 방지 키-O
onSessionEnded(reason: string) => void서버가 세션을 종료했거나 인증이 끊겼을 때 최대 1회 호출되는 콜백-X

주의: targetHandledeviceHandles 는 동시에 지정할 수 없습니다. 둘 중 정확히 하나만 지정하세요.

세션 객체

start()는 마이크 확보와 실시간 오디오 룸 연결을 마친 뒤 세션 객체를 반환합니다.

이름타입설명
sessionIdstring방송 세션 ID
resolvedDeviceCountnumber대상으로 확정된 디바이스 수
stop()() => Promise<void>세션을 종료합니다. 멱등 — 여러 번 호출해도 안전합니다

이벤트

이벤트호출 시점페이로드
onSessionEnded서버가 세션을 종료했거나 인증이 연속 실패로 끊겼을 때. 파트너 코드가 직접 stop()을 호출한 경우에는 호출되지 않음reason: string(서버 종착 사유 코드 또는 "AUTH_FAILED")

참고: reason 값 집합은 확장될 수 있으므로, 알려진 값만 분기하지 말고 모든 값을 종료로 처리하세요.

예제

ts
const session = await oe.broadcast.mic.start({
  targetHandle,
  idempotencyKey: crypto.randomUUID(),
});

const session2 = await oe.broadcast.mic.start({
  deviceHandles,
  idempotencyKey: crypto.randomUUID(),
});

대표 에러

에러HTTP 상태원인해결 방법
CapabilityDenied403앱에 MIC_EXECUTE capability grant 가 없음Open Echo 지원 채널로 grant 를 요청하세요
TargetUnresolved404targetHandle/deviceHandles 를 해석할 수 없음대상 handle 이 유효한지 대상 목록을 다시 조회해 확인하세요
IdempotencyKeyReused409동일한 idempotencyKey 로 다른 페이로드를 재전송함새 방송마다 새 키를 생성하세요
MicUnavailable-마이크 획득 실패(권한 거부·장치 없음 등)아래 주의 박스를 참고해 err.reason으로 안내하세요
MicChunkLoadFailed-MIC 코드 청크 다운로드 실패(번들러 경로) — 네트워크 또는 CSP 차단CSP script-src 설정을 먼저 확인하세요

주의: MicUnavailable은 서버에 요청이 도달하기 전, 로컬 마이크 확보 단계에서 발생합니다 — 세션도 디바이스 점유도 생성되지 않습니다. err.reason으로 원인을 구분해 안내하세요: "permission-denied"(마이크 권한 거부) · "not-found"(마이크 장치 없음) · "other"(그 외). 동일한 idempotencyKey로 재시도해도 마이크 실패 시에는 서버 결과가 재현되지 않고 다시 MicUnavailable이 반환됩니다.

조인 상태 확인 — devices[]

start()가 반환하는 MicSession은 상태를 스스로 밀어주지 않습니다. 진행 상황은 oe.broadcast.subscribe()로 구독해서 확인하세요.

MIC 방송의 devices[]는 TTS 의 재생 상태와 달리 "조인 결과"입니다. 값은 PENDING·JOINED·FAILED·TIMEOUT 중 하나이며, 세션이 종결된 뒤에도 각 행은 마지막 값을 유지합니다.

ts
const session = await oe.broadcast.mic.start({
  targetHandle,
  idempotencyKey: crypto.randomUUID(),
});

const sub = oe.broadcast.subscribe(session.sessionId, {
  onPhase: (snapshot) => {
    for (const d of snapshot.devices ?? []) {
      console.log(d.deviceName, d.status);
    }

    const joined = (snapshot.devices ?? []).filter((d) => d.status === "JOINED").length;
    console.log(`${joined} / ${session.resolvedDeviceCount} 대 조인`);
  },
  onError: (err) => {
    console.error(err.code);
  },
});

// 화면 이탈 시 반드시 정리
sub.close();

session.resolvedDeviceCount(대상으로 확정된 디바이스 수)와 snapshot.devices.length(현재까지 조인 시도가 관측된 수)를 비교하면 몇 대 중 몇 대가 응답했는지 진행 표시에 쓸 수 있습니다.

devices가 빈 배열로 도착하면 snapshot.skippedAtDispatch(정수)로 원인을 구분하세요. 0보다 크면 전 대상이 송출 시점에 skip 되어 정상적으로 빈 것이고, 0이거나 없으면 devices[] 투영을 지원하지 않는 이전 버전 서버의 응답일 가능성이 있습니다.

진행 스냅샷과 이벤트 구조는 진행 구독을 참조하세요.

서버측 종료 감지 — onSessionEnded 콜백

SDK는 세션이 살아있는 동안 내부적으로 heartbeat 를 보냅니다. 서버가 세션을 종료하거나 인증이 끊기면, SDK가 콜백 지정 여부와 무관하게 마이크와 실시간 오디오 룸 연결을 즉시 정리합니다. UI를 맞추려면 onSessionEnded로 이 시점을 감지하세요.

ts
const session = await oe.broadcast.mic.start({
  targetHandle,
  idempotencyKey: crypto.randomUUID(),
  onSessionEnded: (reason) => {
    console.warn("MIC 방송이 서버 측 사유로 종료되었습니다:", reason);
  },
});

로컬에서 session.stop()을 직접 호출한 경우에는 onSessionEnded가 발화하지 않습니다. 파트너 코드가 이미 종료를 알고 있기 때문입니다. 이 콜백은 파트너가 모르는 사이 세션이 끊겼을 때만 UI를 맞추기 위한 것입니다.

종착 사유 코드 전체 목록은 에러 처리를 참조하세요.

방송 중지

MicSession.stop()으로 진행 중인 세션을 종료합니다.

기본 정보

메서드앱 capability참조 handle 조건반환 타입
session.stop()없음(소유권만)stop()을 호출한 MicSession이 담고 있는 sessionId가 호출 앱 소유의 세션이어야 함(발급받은 세션 객체에 이미 바인딩되어 있으므로 별도 인자로 넘기지 않음)Promise<void>

요청

입력 파라미터가 없습니다(session.stop() 자체가 이미 발급받은 세션에 바인딩됨).

응답

반환값이 없습니다(Promise<void>). 멱등 — 이미 종료된 세션에 다시 호출해도 안전합니다.

예제

ts
const session = await oe.broadcast.mic.start({
  targetHandle,
  idempotencyKey: crypto.randomUUID(),
});

try {
  await session.stop();
} catch {
  // stop 실패해도 UI 는 종료 상태로 전환하는 것을 권장합니다.
}

주의: 페이지를 벗어나기 전에는 반드시 stop()을 호출해 마이크가 열린 채로 남지 않도록 하세요.

대표 에러

생략(사유: stop()은 멱등 설계 — 이미 종료된(TERMINAL/STOPPING) 세션에 재호출해도 에러를 던지지 않고 조용히 무시됩니다. 세션 소유권 불일치는 발급받은 세션 객체에 이미 바인딩되어 있어 실무상 발생하지 않는 경로입니다)

참고 코드

레퍼런스 앱의 MIC 방송 화면 구현은 partner-app/src/features/mic/MicBroadcast.tsx에서 확인할 수 있습니다.

더보기