OPWS Developers

진행 구독 (SSE)

개요

oe.broadcast.subscribe()로 TTS·MIC 방송 세션의 진행 상태를 Server-Sent Events(SSE)로 실시간 구독합니다. 연결 직후 현재 스냅샷을 1회 받고, 이후 상태가 바뀔 때마다 새 스냅샷을 받습니다. terminal: true인 스냅샷을 받으면 더 이상 이벤트가 오지 않으므로 구독을 종료하세요.

시작하기 전에

  • 파트너 앱에 HISTORY capability grant 가 있어야 합니다.
  • 구독할 sessionId 가 호출 앱 소유의 방송 세션이어야 합니다. TTS 방송send()/fromContent()가 반환하는 sessionId, 또는 MIC 방송start()가 반환하는 MicSession.sessionId를 사용하세요.

방송 진행 구독 — oe.broadcast.subscribe()

방송 세션의 진행 상태를 Server-Sent Events(SSE)로 실시간 구독합니다.

기본 정보

메서드앱 capability참조 handle 조건반환 타입
oe.broadcast.subscribe(sessionId, options)HISTORYsessionId 가 호출 앱 소유의 방송 세션이어야 함{ close(): void }

옵션

이름타입설명기본값필수
onPhase(snapshot: PhaseSnapshot) => void진행 스냅샷 수신 콜백-O
onError(err: OpenEchoError) => void에러 수신 콜백-X
onClose() => void구독 종료 통지 콜백. 원인과 무관하게 정확히 1회 호출됨-X

세션 객체

subscribe() 는 호출 즉시 세션 객체를 반환합니다.

이름타입설명
close()() => void구독을 수동으로 종료합니다. 멱등 — 여러 번 호출해도 안전합니다

이벤트

이벤트호출 시점페이로드
onPhase스냅샷 수신마다(연결 직후 현재 스냅샷 1회 포함)PhaseSnapshot
onError복구 불가능한 에러 발생 시(throw 되지 않음)OpenEchoError
onClose구독 종료 시(terminal 수신·에러·close() 호출 — 원인 무관 1회)없음

참고: terminal: truePhaseSnapshot 을 수신하면 구독을 종료해야 합니다.

예제

ts
const sub = oe.broadcast.subscribe(sessionId, {
  onPhase: (snapshot) => {
    console.log(snapshot.phase, snapshot.terminal);
    if (snapshot.terminal) {
      sub.close();
    }
  },
  onError: (err) => {
    console.error(err.code, err.httpStatus, err.message);
  },
});

// 구독 수동 종료
sub.close();

대표 에러

에러HTTP 상태원인해결 방법
TicketUnauthorized401SSE 스트림 티켓 인증 실패새로 구독을 시작하세요
SessionExpired410세션의 이벤트 보존 기간이 지남재연결로 복구할 수 없습니다. 새 방송을 시작하세요

PhaseSnapshot 필드

필드타입설명
sessionIdstring방송 세션 ID
phasestring현재 방송 단계
terminalbooleantrue이면 더 이상 이벤트가 발생하지 않는 최종 상태
reasonstring | null실패 시 사유
lastEventIdnumberSSE 재연결 마커. SDK가 내부적으로 Last-Event-ID 헤더에 사용합니다
receiptBroadcastReceipt | nullterminal 시점의 방송 결과(resolvedDeviceCount·skipped 등)
devicesDeviceProgress[]디바이스별 진행 상태입니다. 제공되지 않을 수 있으므로 항상 snapshot.devices ?? []처럼 처리하세요
skippedAtDispatchnumber송출 시점에 skip 된 대상 수입니다. 제공되지 않을 수 있습니다

phase 값 의미

phaseterminal설명
SYNTHESIZINGfalseTTS 음성 합성 진행 중(MIC 세션에는 나타나지 않음)
PLAYINGfalse디바이스에서 재생 중
DONEtrue모든 대상 방송 완료
PARTIALtrue부분 완료. 일부 디바이스가 제외되었거나 실패했습니다. 제외 디바이스는 receipt.skipped로 확인하세요
FAILEDtrue방송 실패
STOPPEDtruestop()으로 중단됨

terminal: true가 수신되면 구독을 종료해야 합니다.

디바이스별 진행 — devices

snapshot.devices는 세션 내 각 디바이스의 진행 상태입니다. 같은 phase 안에서도 디바이스 상태가 바뀌면 새 스냅샷이 전달됩니다.

TTS 방송에서 statusPENDING·PLAYING·DONE·FAILED 중 하나이며 실시간 재생 상태를 반영합니다.

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

MIC 방송의 devices 어휘와 해석은 MIC 방송을 참조하세요. TTS와 다른 값 집합을 쓰며, 재생 상태가 아니라 조인 결과를 나타냅니다.

종결 판정 — terminal 스냅샷 처리

구독을 시작하면 현재 스냅샷을 1회 즉시 받고, 이후 상태가 바뀔 때마다 새 스냅샷을 받습니다. terminal: true인 스냅샷을 받으면 구독을 종료하세요.

receipt.skippedreceipt.resolvedDeviceCount를 함께 보면 부분 완료 여부를 판정할 수 있습니다.

ts
let sub: { close(): void };
sub = oe.broadcast.subscribe(sessionId, {
  onPhase: (snapshot) => {
    const partial =
      snapshot.terminal &&
      snapshot.receipt != null &&
      snapshot.receipt.resolvedDeviceCount > 0 &&
      (snapshot.receipt.skipped?.length ?? 0) > 0;

    console.log(snapshot.phase, snapshot.terminal, partial);

    if (snapshot.terminal) {
      sub.close();
    }
  },
});

에러 처리 — onError 콜백

SSE 에러는 throw되지 않고 onError 콜백으로 전달됩니다. 에러 객체는 OpenEchoError를 상속하며 codehttpStatus를 보유합니다.

ts
oe.broadcast.subscribe(sessionId, {
  onPhase: (snap) => {},
  onError: (err) => {
    console.error(err.code, err.httpStatus);
  },
});

종료 통지 — onClose 콜백

onClose는 구독이 끝날 때, 즉 terminal phase 수신·복구 불가 에러·close() 호출 중 무엇이 원인이든 정확히 1회 호출됩니다. 원인별 분기(onPhase의 terminal / onError)와 별개로 종료 시 정리 로직을 한곳에 두고 싶을 때 사용하세요.

ts
const sub = oe.broadcast.subscribe(sessionId, {
  onPhase: (snap) => {},
  onError: (err) => {},
  onClose: () => {
    console.log("구독 종료");
  },
});

terminal phase 수신 시 SDK는 내부적으로 스트림을 정리하고 onClose를 호출합니다. 이후 close()를 추가로 호출해도 안전합니다(멱등).

세션 만료 — SessionExpired

방송 세션의 이벤트 보존 기간이 지난 후 구독하면 SessionExpired 에러가 onError로 전달됩니다. 이 경우 재연결해도 동일한 세션을 복구할 수 없습니다.

자동 재연결

일시적인 네트워크 단절이나 SSE 연결 중단 시 SDK가 lastEventIdLast-Event-ID 헤더에 실어 자동으로 재연결합니다. 애플리케이션 코드에서 별도 처리가 필요하지 않습니다.

단, SessionExpiredonError로 전달된 경우는 서버가 이벤트를 더 이상 보존하지 않는 것이므로 자동 재연결 대상이 아닙니다.

TTS·MIC 방송과의 연계

TTS 방송 또는 MIC 방송을 시작한 직후 반환되는 sessionId로 바로 구독하는 것이 일반적인 패턴입니다.

ts
const receipt = await oe.broadcast.tts.send({
  targetHandle,
  text: "출입을 통제합니다",
  language: "ko",
  idempotencyKey: crypto.randomUUID(),
});

if (!receipt.sessionId) {
  console.warn("방송 거부:", receipt.denyReason);
} else {
  oe.broadcast.subscribe(receipt.sessionId, {
    onPhase: (snap) => {
      console.log(snap.phase, snap.terminal, snap.receipt);
    },
    onError: (err) => {
      console.error(err.code, err.httpStatus);
    },
  });
}

기존 세션 ID만 있으면 방송을 시작한 화면이 아니어도 oe.broadcast.subscribe(sessionId, ...)로 사후 조회·디버깅 구독이 가능합니다.

참고 코드

레퍼런스 앱의 구독 통합 패턴은 partner-app/src/features/tts/TtsBroadcast.tsx에서 확인할 수 있습니다.

더보기