재생 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_id | yes | |
episode_id | yes | 해당 타이틀에 속해야 함 |
viewer_id_hash | yes | 파트너 측 SHA-256(stable_viewer_id + salt). 원시 PII 금지. |
country | production yes / sandbox no | ISO 3166-1 alpha-2. 프로덕션에서는 필수이며 계약 지역과 대조됩니다. 샌드박스 요청에서는 생략할 수 있으며, 존재할 경우 공개된 권리 지역과 대조됩니다. |
device | no | 예: web, ios, android |
origin | conditional | 키에 allowed_origins가 설정된 경우 필수이며, 해당 origin 중 하나와 일치해야 함. |
preferences | no | 웹뷰 플레이어를 위한 비PII 고객 취향 신호. 아래 참고. |
expires_in | no | 토큰 TTL(초). 기본값 1800, 최대 7200. |
preferences (고객 취향 신호)
선택적 객체입니다. 호스팅형 웹뷰 플레이어는 이 값들을 적용하며(자막/오디오 기본값, 자동 재생, 추천), 분석을 위해 세션에도 저장됩니다. 스키마는 엄격하며, 알 수 없는 키는 거부됩니다(422). 값에는 원시 PII가 포함되어서는 안 됩니다. @ 또는 공백이 포함된 값은 거부됩니다(422 validation_failed).
| 필드 | 타입 | 비고 |
|---|---|---|
preferred_languages | string[] (≤20) | BCP-47 / ISO 언어 코드, 선호 순서 |
preferred_genres | string[] (≤50) | 파트너 측 장르 태그 |
subtitle_language | string | 기본 자막 트랙 |
audio_language | string | 기본 오디오/더빙 트랙 |
autoplay_next | boolean | 다음 에피소드 자동 재생 |
maturity_rating | string | 시청자의 최대 관람 등급 |
이 엔드포인트는 선택적 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을 기록합니다.
- API 키가 유효하고
playback:token을 보유함. 키의 환경이 카탈로그 및 세션 접근 범위를 결정합니다. - 타이틀이 해당 환경에 공개되어 있고, 에피소드가 타이틀에 속함. 그렇지 않으면
resource_not_found(404). - 권리 패키지의
status ∈ {CLEAR, RESTRICTED},apiStreamingAllowed = true, 만료되지 않음. - 에피소드 상태가
READY이며, 승인되었고 삭제되지 않은 비디오 에셋이 HLS 준비 완료 상태로 존재하고 에셋 권리가CLEAR또는RESTRICTED임. - 프로덕션:
country가 필수이며,ACTIVE라이선스 계약이 타이틀 + 지역을 포함하고,production_api_enabled = true이며, 동일한 준비된 비디오 에셋이accessLevel = stream으로 라이선스된 에셋에 포함됨. 샌드박스: 라이선스 불필요.country가 존재하면 공개된 권리 패키지 지역과 대조 검증함. - 키에
allowed_origins가 있으면origin이 필수이며 일치해야 함.allowed_ips가 설정된 경우 IP가 그 안에 있어야 함.allowed_ips가 설정되면 토큰도 IP에 바인딩되며(ip_bound클레임), CDN 엣지가 시청자의 접속 IP와 다시 대조합니다. - 동시 세션 상한: 계약(프로덕션) 또는 타이틀(샌드박스)별로
viewer_id_hash당 최대PLAYBACK_MAX_CONCURRENT_SESSIONS개(기본 3)의 활성 세션. 초과 발급은BLOCKED처리되어playback_concurrency_limit(429)를 반환합니다.
오류 사유
| error.code | HTTP | 원인 |
|---|---|---|
resource_not_found | 404 | 타이틀이 해당 환경에 비공개이거나, 에피소드를 찾을 수 없음 / 타이틀에 속하지 않음 |
missing_scope | 403 | 키에 playback:token이 없음 |
license_not_active | 403 | ACTIVE 계약이 없음 / production_api_enabled가 false이거나, 공개된 스트리밍 가능 권리 패키지가 없음 |
territory_not_allowed | 403 | 프로덕션 country 누락, country가 계약/권리 지역에 없음, 필수 origin 누락, 또는 origin/IP 차단됨 |
episode_not_licensed | 403 | 에피소드가 READY 아님, 준비된 비디오 에셋 없음, 또는 에셋이 계약의 라이선스된 에셋에 없음 |
validation_failed | 422 | viewer_id_hash가 원시 PII로 보이거나, preferences에 알 수 없는 키 / 원시 PII('@'/공백)가 포함된 값이 있음 |
playback_concurrency_limit | 429 | 이 시청자의 계약/타이틀에 대한 활성 세션이 너무 많음 |
conflict | 409 | 처리 중인 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를 통해 보고하세요.