SSAI를 켜면 m3u8 세그먼트 경로가 왜 갈라지는가
1. 두 플레이리스트 비교
규칙을 외우기 전에 실제로 눈에 보이는 모양부터 확인하는 편이 빠릅니다.
1.1 일반 StreamPackage 요청 (SSAI 없음)
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:4
#EXT-X-MEDIA-SEQUENCE:30925514
#EXTINF:4.000,
30925514.ts
#EXTINF:4.000,
30925515.ts
#EXTINF:4.000,
30925516.ts세그먼트 URI에 호스트도 슬래시도 없습니다. 파일 이름만 있습니다. 플레이어는 이 값을 지금 받아온 media playlist URL을 기준으로 해석합니다.
media playlist https://edge.example.com/live/ch01/ch01_720P.m3u8
^^^^^^^^^^ 기준 디렉터리
세그먼트 30925514.ts
최종 요청 https://edge.example.com/live/ch01/30925514.ts1.2 SSAI 요청
#EXTM3U
#EXT-X-VERSION:7
#EXT-X-TARGETDURATION:6
#EXT-X-MEDIA-SEQUENCE:30925514
#EXT-X-DISCONTINUITY-SEQUENCE:0
#EXTINF:4.000,
https://origin.example.com/live/ch01/30925514.ts
#EXTINF:4.000,
https://origin.example.com/live/ch01/30925515.ts
#EXT-X-DISCONTINUITY
#EXTINF:6.000,
/v1/..../adseg-0.ts
#EXTINF:6.000,
/v1/..../adseg-1.ts
#EXT-X-DISCONTINUITY
#EXTINF:4.000,
https://origin.example.com/live/ch01/30925516.ts한 플레이리스트 안에 두 종류의 URI가 함께 있습니다.
- 라이브 세그먼트:
https://로 시작하는 완전한 절대경로 - 광고 세그먼트:
/v1/로 시작하는 경로만 있는 상대경로
참고: 광고 세그먼트의
/v1/뒤에 붙는 하위 경로는 SSAI가 광고 에셋마다 배정하는 값입니다. 규칙으로 잡아둬야 하는 것은 "반드시/v1/로 시작한다"는 접두어 하나뿐입니다.
2. 규칙 세 줄 정리
| 구분 | 세그먼트 종류 | URI 형태 | 시작 패턴 | 해석 기준 |
|---|---|---|---|---|
| 일반 StreamPackage | 라이브 | 상대경로 | 세그먼트 파일명 | media playlist URL의 디렉터리 |
| SSAI | 라이브 | 절대경로 | Content Source 경로, 또는 segment prefix | 없음 (그 자체가 완전한 URL) |
| SSAI | 광고 | 상대경로(루트 기준) | /v1/ | media playlist URL의 스킴과 호스트 |
표에서 가장 헷갈리는 칸은 SSAI 광고입니다. 슬래시로 시작해 절대경로처럼 보이지만, 호스트가 없으므로 엄밀히는 **루트 상대경로(root-relative)**입니다. 기준이 되는 호스트는 manifest 요청에서 물려받습니다.
3. 왜 이렇게 갈라지는가
핵심은 "플레이리스트를 발행하는 주체"와 "세그먼트를 실제로 들고 있는 주체"가 같은지 다른지입니다.
3.1 일반 StreamPackage
플레이리스트와 세그먼트가 같은 트리 아래에 있으므로 상대경로가 가장 단순하고 안전한 표기입니다. 도메인을 베타에서 운영으로 바꿔도 manifest만 바꾸면 세그먼트는 그대로 따라옵니다.
3.2 SSAI 적용
SSAI는 원본 라이브 플레이리스트를 가져다가 자기 경로에서 다시 발행하는 구조입니다. 발행 위치(/v1/ssai/master/...)와 원본 세그먼트 위치(Content Source)는 서로 다른 트리입니다. 이 상태에서 라이브 세그먼트를 상대경로로 적으면, 플레이어는 SSAI 경로 아래에서 세그먼트를 찾습니다. 거기에는 아무것도 없으므로 절대경로가 강제됩니다.
반대로 광고 세그먼트는 SSAI 서비스 자체가 보관하는 파일입니다. manifest를 발행한 바로 그 엔드포인트 루트 아래 /v1/에 마운트되어 있으므로, 루트 상대경로 하나로 어떤 도메인에서든 깔끔하게 붙습니다.
4. 라이브 세그먼트 절대경로가 조립되는 방식
절대경로는 임의로 만들어지지 않습니다. 정해진 접두어를 붙인 결과입니다.
최종 라이브 세그먼트 URI
= (segment prefix가 설정되어 있으면 그 값,
없으면 Content Source에 입력한 경로)
+ 원본 플레이리스트에 있던 세그먼트 상대경로| 설정 상태 | Content Source 입력값 | segment prefix | 만들어지는 라이브 세그먼트 URI |
|---|---|---|---|
| 기본 | https://origin.example.com/live/ch01/ | 없음 | https://origin.example.com/live/ch01/30925514.ts |
| prefix 지정 | https://internal-origin/live/ch01/ | https://edge-cdn.example.com/live/ch01/ | https://edge-cdn.example.com/live/ch01/30925514.ts |
| 경로에 쿼리 포함 | https://origin.example.com/live/ch01/?key=xxx | 없음 | 쿼리가 섞여 있으면 접두어로 그려지는 형태를 실물 manifest로 한 번 확인해야 합니다 |
주의: Content Source를 내부 오리진 주소로 넣고 segment prefix를 따로 지정하지 않으면, 그 내부 주소가 플레이리스트에 그대로 노출됩니다. 시청자 플레이어는 그 주소로 세그먼트를 받으러 갑니다. 실제로 공인망에서 풀리는 주소인지 반드시 확인해야 합니다.
5. 광고 세그먼트 /v1/의 성질
루트 상대경로에는 성질이 두 가지 있습니다.
성질 1. 도메인 이식성
main.m3u8 요청 https://beta.example.com/v1/ssai/master/.../main.m3u8
광고 세그먼트 /v1/..../adseg-0.ts
실제 요청 https://beta.example.com/v1/..../adseg-0.ts
main.m3u8 요청 https://prod.example.com/v1/ssai/master/.../main.m3u8
광고 세그먼트 /v1/..../adseg-0.ts
실제 요청 https://prod.example.com/v1/..../adseg-0.tsmanifest를 어떤 도메인으로 받든 광고 세그먼트는 같은 도메인으로 따라갑니다. 베타 도메인 테스트, 운영 도메인 전환, CDN 추가 같은 작업에서 광고 경로를 따로 손댈 필요가 없습니다.
성질 2. 쿼리스트링은 물려받지 않는다
RFC 8216의 상대참조 해석 규칙상, 상대 URI를 기준 URI에 맞춰 해석할 때 경로는 기준에서 가져오지만 쿼리는 물려받지 않습니다. manifest에 토큰을 붙여서 요청해도, 광고 세그먼트 요청에는 그 토큰이 붙지 않습니다.
main.m3u8 요청 https://edge.example.com/v1/ssai/master/.../main.m3u8?hdntl=abc
광고 세그먼트 /v1/..../adseg-0.ts
실제 요청 https://edge.example.com/v1/..../adseg-0.ts (토큰 없음)확인 필요: 쿼리스트링 기반 인증(hdntl, hdnts, px 계열)을 쓰는 채널이라면 광고 구간 진입 시 403이 나는지 실물로 검증해야 합니다. 경로 형태가 다르면 인증을 거는 지점도 달라지므로,
/v1/경로에 별도 인증 예외나 토큰 전달 설정이 필요한지가 갈립니다.
6. 실무에서 실제로 터지는 지점
| # | 상황 | 무슨 일이 생기는가 | 조치 |
|---|---|---|---|
| 1 | manifest는 HTTPS, Content Source를 http://로 입력 | 브라우저 기반 플레이어가 mixed content로 차단합니다 | Content Source를 HTTPS로 통일합니다 |
| 2 | Content Source가 클라우드 콘솔 기본 오리진 도메인으로 설정됨 | manifest는 열리지만 세그먼트가 전부 실패합니다. 공인 DNS에서 풀리지 않는 경우가 실측됩니다 | 공인에서 풀리는 도메인으로 segment prefix를 지정합니다 |
| 3 | 라이브 세그먼트가 절대경로라서 SSAI 엣지를 안 탐 | 시청자 트래픽이 Content Source로 직행합니다. 오리진 RPS가 그대로 찍힙니다 | Content Source를 CDN/엣지 호스트로 두거나, 요청 collapse 설정을 확인합니다 |
| 4 | 쿼리스트링 토큰 인증 사용 | 광고 세그먼트에 토큰이 붙지 않아 403이 날 수 있습니다 | 5번 항목을 검증한 뒤 인증 예외 경로를 설정합니다 |
| 5 | /v1/ 광고 경로에 CDN 캐시 키가 설정되지 않음 | 광고 세그먼트가 매번 오리진으로 가거나, 반대로 잘못 캐시됩니다 | /v1/ 경로의 캐시 규칙을 확인합니다 |
| 6 | 광고 구간에서만 재생이 끊김 | 대개 광고 세그먼트 URL 자체가 아니라 DISCONTINUITY 처리 문제입니다 | SSAI 관련 DISCONTINUITY 문서를 참고합니다 |
2번은 실제로 겪은 사례입니다. 라이브 세그먼트가 절대경로라는 규칙 때문에 내부용 오리진 주소가 시청자에게 그대로 배달되는 구조입니다.
7. 지금 운영 중인 채널에서 확인하는 방법
master에서 media로 한 단계씩 내려가서 세그먼트 줄의 앞부분만 보면 됩니다.
# 1) master
curl -s "https://edge.example.com/v1/ssai/master/<ssai-id>/v1/<channel>/<out>/main.m3u8"
# 2) variant 하나 골라서 media playlist
curl -s "https://edge.example.com/v1/ssai/master/<ssai-id>/v1/<channel>/<out>/<variant>.m3u8"
# 3) 세그먼트 URI 형태만 분류해서 보기
curl -s "<media playlist URL>" | grep -E '^(https?://|/)' | awk '{print substr($0,1,60)}'판정 기준은 이렇습니다.
https:// 로 시작 → 라이브 세그먼트 (Content Source 또는 segment prefix)
/v1/ 로 시작 → 광고 세그먼트
확장자만 있는 줄 → 일반 StreamPackage 모드 (SSAI 미적용)광고가 없을 때는 광고 세그먼트가 보이지 않는 것이 정상입니다. 광고 구간이 실제로 삽입된 시점의 manifest를 잡아야 두 형태가 함께 보입니다. 광고 스케줄을 짧게 걸고 광고 교체를 반복 실행하면서 동시에 curl로 manifest를 긁으면 타이밍을 맞추기 쉽습니다.
8. 이 편 요약
- 일반 StreamPackage는 라이브 세그먼트가 상대경로입니다. manifest와 같은 트리에 있기 때문입니다.
- SSAI 라이브 세그먼트는 절대경로입니다. Content Source 경로(또는 segment prefix)로 시작하며, manifest 발행 트리와 원본 트리가 다르기 때문입니다.
- SSAI 광고 세그먼트는
/v1/로 시작하는 루트 상대경로입니다. SSAI 엔드포인트 루트 아래에 마운트되어 있기 때문입니다. - 루트 상대경로는 도메인 이식성이 좋지만 쿼리스트링은 물려받지 않습니다.
- 가장 많이 터지는 지점은 Content Source를 내부 오리진 주소로 넣었을 때입니다. 공인 접근이 가능한 주소인지, HTTPS인지, CDN을 타는지 세 가지는 항상 확인해야 합니다.