재생 API

한 명의 시청자에게 하나의 에피소드를 시청하도록 권한을 부여하고, 호스팅형 웹뷰 재생 링크(플레이어 연동 없이 WebView에서 바로 열기)와 함께, 자체 플레이어를 운영하는 파트너를 위한 동등한 서명된 HLS/DASH 매니페스트를 반환받습니다. 웹뷰 플레이어를 개인화하기 위해 선택적으로 비PII 고객 preferences를 전달할 수 있습니다. playback:token 스코프가 필요합니다.

엔드포인트

메서드경로용도
POST/playback/tokens에피소드 권한 부여 → 웹뷰 링크 + 서명된 매니페스트
GET/playback/sessions/{session_id}재생 세션 조회
POST/playback/sessions/{session_id}/revoke활성 세션 종료

토큰 요청

curl -X POST "https://nu-signal-partners.vercel.app/v1/playback/tokens" \
  -H "Authorization: Bearer nsp_live_xxx" \
  -H "X-NU-Partner-Id: org_acme" \
  -H "X-NU-Request-Id: req_playback_0001" \
  -H "Content-Type: application/json" \
  -d '{
    "title_id": "ttl_abc",
    "episode_id": "ep_001",
    "viewer_id_hash": "9f86d081884c7d659a2feaa0c55ad015...",
    "country": "KR",
    "device": "web",
    "origin": "https://app.acme.example",
    "preferences": {
      "preferred_languages": ["ko", "en"],
      "preferred_genres": ["romance", "thriller"],
      "subtitle_language": "ko",
      "audio_language": "ko",
      "autoplay_next": true,
      "maturity_rating": "15"
    },
    "expires_in": 1800
  }'
필드필수비고
title_idyes
episode_idyes해당 타이틀에 속해야 함
viewer_id_hashyes파트너 측 SHA-256(stable_viewer_id + salt). 원시 PII 금지.
countryproduction yes / sandbox noISO 3166-1 alpha-2. 프로덕션에서는 필수이며 계약 지역과 대조됩니다. 샌드박스 요청에서는 생략할 수 있으며, 존재할 경우 공개된 권리 지역과 대조됩니다.
deviceno예: web, ios, android
originconditional키에 allowed_origins가 설정된 경우 필수이며, 해당 origin 중 하나와 일치해야 함.
preferencesno웹뷰 플레이어를 위한 비PII 고객 취향 신호. 아래 참고.
expires_inno토큰 TTL(초). 기본값 1800, 최대 7200.

preferences (고객 취향 신호)

선택적 객체입니다. 호스팅형 웹뷰 플레이어는 이 값들을 적용하며(자막/오디오 기본값, 자동 재생, 추천), 분석을 위해 세션에도 저장됩니다. 스키마는 엄격하며, 알 수 없는 키는 거부됩니다(422). 값에는 원시 PII가 포함되어서는 안 됩니다. @ 또는 공백이 포함된 값은 거부됩니다(422 validation_failed).

필드타입비고
preferred_languagesstring[] (≤20)BCP-47 / ISO 언어 코드, 선호 순서
preferred_genresstring[] (≤50)파트너 측 장르 태그
subtitle_languagestring기본 자막 트랙
audio_languagestring기본 오디오/더빙 트랙
autoplay_nextboolean다음 에피소드 자동 재생
maturity_ratingstring시청자의 최대 관람 등급

이 엔드포인트는 선택적 X-NU-Request-Id 헤더를 통해 멱등하게 동작합니다. 동일한 요청 id로 재시도하면 원래 응답을 반환하고, 아직 처리 중인 동시 중복 요청은 conflict(409)를 반환합니다.

응답

{
  "data": {
    "playback_session_id": "pbs_123",
    "expires_at": "2026-06-24T00:30:00Z",
    "webview_url": "https://nu-signal-partners.vercel.app/w/pbs_123?t=...",
    "manifest": {
      "hls": "https://nu-signal-partners.vercel.app/media/hls/pbs_123/signal-city/ep001/master.m3u8?token=...",
      "dash": "https://nu-signal-partners.vercel.app/media/dash/pbs_123/signal-city/ep001/manifest.mpd?token=..."
    },
    "tracking": {
      "event_endpoint": "https://nu-signal-partners.vercel.app/v1/events",
      "required_events": [
        "EPISODE_STARTED",
        "PLAYBACK_PROGRESS",
        "EPISODE_COMPLETED"
      ]
    }
  }
}

동일한 승인된 세션을 재생하는 두 가지 동등한 방법이 제공됩니다. 하나를 선택하세요.

  • webview_url (권장). NU가 호스팅하는 재생 전용 페이지입니다. WebView(WKWebView / android.webkit.WebView) 또는 <iframe>에서 엽니다. 이 페이지는 매니페스트를 내부적으로 해석하고 세션에 저장된 preferences(자막/오디오 기본값, 자동 재생, 추천)를 적용합니다. 파트너는 매니페스트 URL이나 플레이어 연동을 직접 다루지 않습니다. 이 링크는 세션 id와 서명된 토큰(?t=)을 담고 있으므로, 비밀로 취급하고 expires_at 이후에는 캐시하지 마세요.
  • manifest (자체 플레이어 사용). 자체 플레이어를 운영하는 파트너를 위한 서명된 HLS/DASH URL입니다. 매니페스트나 세그먼트가 제공되기 전에 각 요청은 토큰으로 검증됩니다(서명, TTL, 세션 id, 승인된 미디어 경로 검증). 원본 마스터 파일은 절대 반환되지 않습니다. 기본적으로 검증 게이트는 앱 자체이며 (/media에서 제공), 대신 Cloudflare 엣지 워커에서 실행될 수도 있습니다. 어느 방식이든 URL 규약은 동일합니다.

두 방식 모두 동일한 playback_session_id에 바인딩되며, 이벤트는 어느 쪽이든 동일한 방식으로 보고합니다.

테스트 웹뷰

실제 세션을 확보하기 전에 WebView/iframe 연동을 검증하려면 테스트 플레이어를 여세요. 토큰, 세션, 미디어 설정 없이 메인 화면의 Episode 009 Confession 9:16 프리뷰를 즉시 재생합니다.

https://signal-partners.newunivers.ai/w/test?subtitle=ko&audio=ko&lang=ko&autoplay=1

이 테스트 플레이어는 webview_url과 완전히 동일한 플레이어를 사용하므로, 세로형 샘플이 WebView에서 재생되면 실제 webview_url도 같은 9:16 화면 정책으로 재생됩니다.

검증 순서

검사는 순서대로 실행됩니다. 타이틀 또는 에피소드가 없으면 resource_not_found(404)를 반환합니다. 타이틀/에피소드가 해석된 뒤, 인가 검사에 실패하면 403을 반환하고 BLOCKED 세션에 block_reason을 기록합니다.

  1. API 키가 유효하고 playback:token을 보유함. 키의 환경이 카탈로그 및 세션 접근 범위를 결정합니다.
  2. 타이틀이 해당 환경에 공개되어 있고, 에피소드가 타이틀에 속함. 그렇지 않으면 resource_not_found(404).
  3. 권리 패키지의 status ∈ {CLEAR, RESTRICTED}, apiStreamingAllowed = true, 만료되지 않음.
  4. 에피소드 상태가 READY이며, 승인되었고 삭제되지 않은 비디오 에셋이 HLS 준비 완료 상태로 존재하고 에셋 권리가 CLEAR 또는 RESTRICTED임.
  5. 프로덕션: country가 필수이며, ACTIVE 라이선스 계약이 타이틀 + 지역을 포함하고, production_api_enabled = true이며, 동일한 준비된 비디오 에셋이 accessLevel = stream으로 라이선스된 에셋에 포함됨. 샌드박스: 라이선스 불필요. country가 존재하면 공개된 권리 패키지 지역과 대조 검증함.
  6. 키에 allowed_origins가 있으면 origin이 필수이며 일치해야 함. allowed_ips가 설정된 경우 IP가 그 안에 있어야 함. allowed_ips가 설정되면 토큰도 IP에 바인딩되며(ip_bound 클레임), CDN 엣지가 시청자의 접속 IP와 다시 대조합니다.
  7. 동시 세션 상한: 계약(프로덕션) 또는 타이틀(샌드박스)별로 viewer_id_hash당 최대 PLAYBACK_MAX_CONCURRENT_SESSIONS개(기본 3)의 활성 세션. 초과 발급은 BLOCKED 처리되어 playback_concurrency_limit(429)를 반환합니다.

오류 사유

error.codeHTTP원인
resource_not_found404타이틀이 해당 환경에 비공개이거나, 에피소드를 찾을 수 없음 / 타이틀에 속하지 않음
missing_scope403키에 playback:token이 없음
license_not_active403ACTIVE 계약이 없음 / production_api_enabled가 false이거나, 공개된 스트리밍 가능 권리 패키지가 없음
territory_not_allowed403프로덕션 country 누락, country가 계약/권리 지역에 없음, 필수 origin 누락, 또는 origin/IP 차단됨
episode_not_licensed403에피소드가 READY 아님, 준비된 비디오 에셋 없음, 또는 에셋이 계약의 라이선스된 에셋에 없음
validation_failed422viewer_id_hash가 원시 PII로 보이거나, preferences에 알 수 없는 키 / 원시 PII('@'/공백)가 포함된 값이 있음
playback_concurrency_limit429이 시청자의 계약/타이틀에 대한 활성 세션이 너무 많음
conflict409처리 중인 X-NU-Request-Id의 동시 중복 요청

세션

curl "https://nu-signal-partners.vercel.app/v1/playback/sessions/pbs_123" \
  -H "Authorization: Bearer nsp_live_xxx" -H "X-NU-Partner-Id: org_acme"
{
  "data": {
    "playback_session_id": "pbs_123",
    "title_id": "ttl_abc",
    "episode_id": "ep_001",
    "status": "issued",
    "block_reason": null,
    "preferences": { "subtitle_language": "ko", "autoplay_next": true },
    "expires_at": "2026-06-24T00:30:00Z",
    "started_at": null,
    "completed_at": null
  }
}

API는 저장된 세션 상태를 소문자로 반환합니다. 상태는 issued → started → completed | expired | revoked | blocked 순으로 흐릅니다. 토큰 발급은 issued 세션을 생성하고, EPISODE_STARTED 이벤트는 이를 started 전진시키며(started_at 채움), EPISODE_COMPLETED completed로 전진시킵니다(completed_at). 토큰 TTL이 지난 세션을 읽으면 expired가 저장되고, 인가 실패는 block_reason이 포함된 blocked 세션을 생성합니다. 잘못되었거나 알 수 없는 세션 id는 invalid_playback_session(400)을 반환합니다.

세션 폐기

curl -X POST "https://nu-signal-partners.vercel.app/v1/playback/sessions/pbs_123/revoke" \
  -H "Authorization: Bearer nsp_live_xxx" -H "X-NU-Partner-Id: org_acme"

활성(issued/started) 세션을 revoked로 표시하여 더 이상 동시 세션 상한에 포함되지 않도록 합니다. 종료 상태의 세션은 변경되지 않습니다. 이미 발급된 짧은 TTL의 CDN 토큰은 엣지에서 취소되지 않으므로 TTL을 짧게 유지하세요.

viewer_id_hash & 만료

  • viewer_id_hash는 안정적인 시청자 id와 서버 측에서 보관하는 salt를 결합해 계산한 SHA-256이어야 합니다. NU는 원시 PII를 일절 수락하거나 저장하지 않으며, 이는 preferences에도 적용됩니다(@/공백이 포함된 값은 거부됨).
  • 토큰은 수명이 짧습니다(기본 1800초, 최대 7200초). 만료 시 재발급하고, webview_url이나 매니페스트 URL을 expires_at 이후로 캐시하지 마세요. webview_url에는 서명된 토큰이 포함되므로 비밀로 취급하세요(로그에 남기거나 공유 가능한 링크에 넣지 마세요).
  • 키를 폐기하거나 계약을 비활성화해도 이미 발급된 재생 토큰은 취소되지 않습니다. TTL을 짧게 유지하고 만료 시 재발급하세요.

재생 활동은 반환된 playback_session_id를 사용하여 이벤트 API를 통해 보고하세요.