왜 LL-HLS가 필요할까요?
여러분은 실시간 스트리밍을 보다가 이런 경험 해보신 적 있나요?
- LCK를 유튜브 라이브로 보는데 채팅창에 "GG"가 도배되고 나서야 화면에서 넥서스가 터지는 장면을 보게 되는 경우
- 또는 월드컵을 보는데, 케이블TV 보는 친구가 카톡으로 "골!!!"이라고 먼저 알려주는 상황
Roger Pantos(Apple)가 WWDC2019에서 발표한 바와 같이, "스포츠를 시청할 때 종종 Apple TV에서 골을 보기 전에 아파트 벽 너머로 골 소리를 먼저 듣게 되는" 문제가 바로 이것입니다.
이런 20-30초의 지연시간이 실시간 스트리밍의 몰입감을 크게 떨어뜨리는 주범입니다.
그래서 탄생한 게 바로 LL-HLS입니다.
Low-Latency HLS(LL-HLS)는 기존 HLS의 지연시간을 1-2초로 줄여서 정말 "실시간"에 가까운 스트리밍을 가능하게 만든 기술입니다. 2009년부터 사용되어온 안정적인 HLS의 모든 장점은 그대로 유지합니다.
WWDC2020에서 발표된 바와 같이, LL-HLS는 베타를 탈출하여 iOS 14, tvOS 14, macOS에서 정식 지원됩니다. 비트레이트 스위칭, FairPlay Streaming, fMP4 CMAF 등 모든 HLS 기능을 지원하며, 네이티브 애플리케이션에서 별도 인타이틀먼트 없이 사용할 수 있습니다.
그런데 어떻게 20-30초 지연을 1-2초로 줄일 수 있었을까요?
Apple 개발팀이 LL-HLS를 설계할 때 이런 원칙들을 세웠다고 합니다.
- HTTP가 여전히 최선: 수십만 명에게 동시에 미디어를 전달하는 최적의 방식
- CDN과의 협력: CDN의 본질적 특성(HTTP 프록시 캐시)을 거스르지 않고 활용
- 효율적인 비트레이트 스위칭: 라이브 에지 근처에서는 버퍼가 매우 작으므로 스위칭 메커니즘의 효율성이 중요
이런 고민 끝에 Apple이 발표한 LL-HLS의 5가지 핵심은 아래와 같습니다.
- 발행 지연시간 감소: 영상을 잘게 쪼개서 바로바로 보내기
- 세그먼트 발견 최적화: 새로운 영상 조각을 더 빨리 찾기
- 라운드 트립 제거: 불필요한 요청-응답 과정 없애기
- 플레이리스트 전송 최적화: 영상 목록 파일을 효율적으로 보내기
- 비트레이트 스위칭 가속화: 화질 변경을 더 빠르게 하기
각각 어떤 건지 하나씩 살펴보겠습니다.
Traditional HLS Architecture
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ Encode │───▶│Complete │───▶│ Publish │───▶│ Player │
│ 6s GOP │ │ Segment │ │ to CDN │ │ Buffer │
└─────────┘ └─────────┘ └─────────┘ └─────────┘
6s 0s 0.5s 18-30s
Total Latency: 24-36 seconds
LL-HLS Architecture
┌─────────┐ ┌─────────┐ ┌─────────┐ ┌─────────┐
│ Encode │───▶│ Partial │───▶│Immediate│───▶│Optimized│
│ 1s GOP │ │Segments │ │ Publish │ │ Buffer │
└─────────┘ └─────────┘ └─────────┘ └─────────┘
1s 0.2s 0.1s 1-2s
Total Latency: 2-5 seconds
1. 부분 세그먼트(Partial Segments)
문제: 6초 세그먼트를 실시간으로 인코딩하면 CDN에 올릴 수 있는 콘텐츠가 생기기까지 6초가 소요됩니다.
해결책: 메인 세그먼트가 준비되기 전에 작은 부분들을 먼저 발행할 수 있도록 허용합니다.
부분 세그먼트(Partial Segments)는 정규 세그먼트의 서브셋으로, 부모 세그먼트 내 미디어의 일부를 포함합니다.
HLS: 6초 기다리기 → 파일 완성 → 전송 시작
LL-HLS: 0.2초마다 → 작은 조각 즉시 전송
-
GOP (Group of Pictures): 비디오 인코딩 레벨
- 키프레임(I-frame) 간격을 1-2초로 단축
- 인코딩 지연시간 최소화
-
CMAF (Common Media Application Format): 컨테이너 레벨
- fMP4 기반 세그먼트 포맷
- 청크 단위로 분할 가능
-
부분 세그먼트: 전송 레벨
- 200-500ms 크기의 전송 단위
- CMAF 청크를 활용하여 생성
💡 참고: 부분 세그먼트는 주로 라이브 에지에서 유용하며, 라이브 에지에서 멀어지면 플레이리스트에서 제거되어 플레이리스트를 간결하게 유지합니다.
2. LL-HLS 핵심 프로토콜 태그
EXT-X-SERVER-CONTROL
서버의 블로킹 리로드 및 델타 업데이트 지원을 나타냅니다.
#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=1.0,CAN-SKIP-UNTIL=24.0
- CAN-BLOCK-RELOAD: 서버가 블로킹 플레이리스트 요청을 지원하는지 여부
- PART-HOLD-BACK: 라이브 에지로부터 최소 거리(초)
- CAN-SKIP-UNTIL: 델타 업데이트를 통해 건너뛸 수 있는 콘텐츠 지속시간
EXT-X-PART-INF & EXT-X-PART
부분 세그먼트에 대한 정보를 제공합니다.
#EXT-X-PART-INF:PART-TARGET=0.33334
#EXT-X-PART:DURATION=0.33334,URI="filePart271.0.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart271.1.mp4",INDEPENDENT=YES
#EXT-X-PART:DURATION=0.33334,URI="filePart271.2.mp4"
INDEPENDENT 속성은 해당 부분이 키프레임을 포함하여 디코딩을 시작할 수 있음을 의미합니다.
EXT-X-PRELOAD-HINT
사전 세그먼트 요청을 가능하게 합니다.
#EXT-X-PRELOAD-HINT:TYPE=PART,URI="filePart273.3.mp4"
#EXT-X-PRELOAD-HINT:TYPE=MAP,URI="init.mp4"
클라이언트가 미리 GET 요청을 발행할 수 있어 불필요한 라운드 트립을 제거합니다. 이는 2020년 업데이트에서 HTTP/2 Push를 대체하는 핵심 메커니즘입니다.
EXT-X-RENDITION-REPORT
렌디션 스위칭 정보를 제공합니다.
#EXT-X-RENDITION-REPORT:URI="../1M/waitForMSN.php",LAST-MSN=273,LAST-PART=2
#EXT-X-RENDITION-REPORT:URI="../4M/waitForMSN.php",LAST-MSN=273,LAST-PART=1
원래는 클라이언트가 특정 렌디션 리포트를 요청할 수 있었지만, 이는 동일한 플레이리스트 업데이트를 참조하는 서로 다른 요청 URL의 조합 폭발을 일으켜 캐싱 효율성을 떨어뜨렸습니다. 따라서 report delivery directive를 제거하고, 대신 모든 플레이리스트 업데이트에 모든 Rendition Report를 포함하도록 변경되었습니다.
최소한의 라운드 트립으로 효율적인 ABR을 가능하게 하며, 다른 렌디션의 현재 상태를 보고합니다.
Gap Signaling (2020년 추가)
LL-HLS 스트림에서 인코딩 중단을 더 잘 처리하기 위해 2020년에 Gap Signaling이 추가되었습니다.
#EXT-X-PART:DURATION=0.33334,URI="filePart271.0.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart271.1.mp4",GAP=YES
#EXT-X-PART:DURATION=0.33334,URI="filePart271.2.mp4"
#EXT-X-RENDITION-REPORT:URI="../1M/waitForMSN.php",LAST-MSN=273,LAST-PART=2,GAP=YES
GAP 속성을 통해 클라이언트는 특정 part나 rendition에서 미디어 데이터가 없음을 알 수 있고, 이를 통해 라이브 스트림 중단에 더 적절히 대응할 수 있습니다.
3. HLS Origin API 및 5가지 핵심 기술
LL-HLS의 모든 기능을 작동시키기 위해 클라이언트는 서버에게 새로운 기능(델타 업데이트, 블로킹 플레이리스트 리로드 등) 사용 의사를 알려야 합니다. 이를 위해 HLS Origin API를 사용합니다.
2. 블로킹 플레이리스트 리로드 (Blocking Playlist Reload)
문제: 기존 HLS는 폴링(Polling) 메커니즘으로 인해 클라이언트가 새 세그먼트 발견에 최대 6초까지 소요될 수 있습니다.
해결책: 클라이언트가 다음 플레이리스트 업데이트를 실제로 준비되기 전에 미리 요청할 수 있도록 허용합니다.
HLS
플레이어: "새 영상 있나요?"
서버: "아직 없어요" (304 응답)
플레이어: (몇 초 기다림...) "지금은 어때요?"
서버: "아직 없어요" (304 응답)
플레이어: (또 기다림...) 😫
LL-HLS
플레이어: "영상 273번 준비되면 바로 주세요!" (미리 주문)
서버: (영상 273번이 준비될 때까지 기다렸다가...)
서버: "준비됐습니다!" (200 응답과 함께 즉시 전달) ⚡
이렇게 하면 새로운 영상 조각이 만들어지자마자 바로 받을 수 있게 됩니다.
요청 플로우 비교
3. Blocking Preload Hints를 통한 라운드 트립 제거
문제: 플레이리스트를 받은 후 세그먼트를 요청하기 위한 추가 라운드 트립(Round Trip)이 필요했습니다.
예시를 들어보겠습니다.
- 플레이어: "영상 목록 주세요!"
- 서버: "여기 목록이요!" (플레이리스트 전달)
- 플레이어: "그럼 이제 영상 273번 주세요!"
- 서버: "네, 여기 영상이요!" (영상 파일 전달)
이렇게 총 두 번의 요청-응답이 필요했고, 이것도 지연시간의 원인이었습니다.
초기 해결책 (2019): HTTP/2 Push를 사용하여 플레이리스트와 함께 세그먼트를 동시에 전송.
2020년 개선된 해결책: Blocking Preload Hints
WWDC2020에서 발표된 바와 같이, HTTP/2 Push는 Blocking Preload Hints로 대체되었습니다. 이는 Push 방식이 많은 콘텐츠 전달 방식과 호환되지 않았고, 특히 광고 지원 콘텐츠 전달과 호환되지 않았기 때문입니다.
Blocking Preload Hints의 작동 방식:
- 클라이언트: "다음 part가 준비되면 바로 주세요!" (미리 주문)
- 서버: (part가 준비될 때까지 요청을 보류...)
- 서버: "준비됐습니다!" (200 응답과 함께 즉시 전달) ⚡
이는 Blocking Playlist Reload와 유사하지만, 플레이리스트 대신 세그먼트 part를 요청합니다.
CDN에서의 장점:
Blocking Preload Hints는 실제로 HTTP/2 Push보다 CDN에서 성능이 더 좋습니다. 클라이언트에서 요청을 주도하면 추가 라운드 트립 없이 CDN 캐시 채우기가 자동으로 트리거되기 때문입니다. 또한 CDN에서 Push 지원을 요구하지 않으므로 구현이 더 간단합니다 (HTTP/2 지원은 여전히 필요).
전달 지시자(Delivery Directives)
⚠️ 중요: HLS에서 처음으로 쿼리 매개변수가 명세에 포함되었습니다.
_HLS
로 시작하는 모든 쿼리 매개변수는 프로토콜 전용으로 예약됩니다.
_HLS_msn=<M>: 미디어 시퀀스 번호 M 이상을 포함하는 플레이리스트 요청
_HLS_part=<N>: 지정된 MSN의 부분 N 요청 (_HLS_msn 필요)
_HLS_skip=YES|v2: EXT-X-SKIP 태그를 포함한 델타 업데이트 요청
요청 예시
GET playlist.m3u8?_HLS_msn=1803&_HLS_part=1&_HLS_push=YES
4. 지연시간 분석 및 측정
Glass-to-Glass 지연시간 구성요소
구성요소 | HLS | LL-HLS |
---|---|---|
인코딩 | 5초 | 1-2초 |
세그멘테이션 | 6-10초 | 0.2-0.5초 |
CDN 전파 | 가변적 | 가변적 |
플레이어 버퍼 | 18-30초 | 1-3초 |
디코딩/렌더링 | <1초 | <1초 |
총합 | 25-40초 | 2-5초 |
측정 방법론
클래퍼보드 방법 - 가장 정확한 glass-to-glass 측정
┌─────────────┐ ┌─────────────┐
│ 소스 │ │ 플레이어 │
│ 타임스탬프 │ │ 타임스탬프: │
│ 13:50:29 │────▶│ 13:50:34 │
└─────────────┘ └─────────────┘
Glass-to-Glass 지연시간: 5초
PDT 태그 방법 - EXT-X-PROGRAM-DATE-TIME을 사용한 동기화
#EXT-X-PROGRAM-DATE-TIME:2025-07-04T12:00:00.000Z
#EXTINF:6.0,
segment1000.ts
5. 네트워크 효율성 및 CDN 동작
HTTP 요청 패턴 분석
LL-HLS는 부분 세그먼트와 블로킹 요청으로 인해 매우 다른 네트워크 트래픽 패턴을 생성합니다.
메트릭 | HLS | LL-HLS |
---|---|---|
분당 플레이리스트 요청 | 10 | 120-180 |
세그먼트 크기 | 2-10 MB | 200-500 KB |
요청 패턴 | 순차적 | 동시적 |
연결 타입 | 단기 | 지속적 |
HTTP 버전 | HTTP/1.1+ | HTTP/2 |
4. 델타 업데이트 (Delta Updates)
문제: 3-5시간 분량의 세그먼트가 포함된 플레이리스트를 초당 3-4회 전송하면 gzip을 사용해도 상당한 오버헤드가 발생합니다.
해결책: 클라이언트가 이미 가지고 있는 플레이리스트 정보를 활용하여 변경된 부분만 전송합니다.
첫 번째 플레이리스트 요청에서는 전체 플레이리스트를 받지만, 이후 요청에서는 라이브 에지 근처의 변경된 부분만 포함하는 델타 업데이트를 받을 수 있습니다.
EXT-X-SKIP을 사용한 플레이리스트 델타 업데이트는 대역폭을 60-80% 절약하며, 종종 단일 네트워크 패킷에 맞출 수 있습니다.
긴 DVR 창에서 많은 date-range 태그를 사용하는 제공업체를 위해 date-range 태그를 Playlist Delta Updates에 포함하는 방식이 추가되었습니다. 이를 통해 업데이트에는 가장 최근 태그만 포함됩니다.
전체 플레이리스트 (100KB) 델타 업데이트 (5KB)
#EXTM3U #EXTM3U
#EXT-X-VERSION:3 #EXT-X-VERSION:10
#EXT-X-MEDIA-SEQUENCE:100 #EXT-X-MEDIA-SEQUENCE:264
#EXTINF:6.0, #EXT-X-SKIP:SKIPPED-SEGMENTS=3
segment100.ts #EXTINF:6.0,
#EXTINF:6.0, segment267.mp4
segment101.ts #EXTINF:6.0,
... (97개 더 많은 세그먼트) segment268.mp4
#EXTINF:6.0, #EXTINF:6.0,
segment199.ts segment269.mp4
#EXT-X-PART:DURATION=0.33334...
#EXT-X-DATERANGE:ID="recent-ad",START-DATE="2020-01-01T00:00:00Z"
5. 렌디션 리포트 (Rendition Reports)
문제: 비트레이트 스위칭 시 다른 렌디션의 최신 상태를 파악하기 위해 추가 요청이 필요합니다.
해결책: 현재 플레이리스트 업데이트에 다른 비트레이트 티어의 최신 정보를 포함시킵니다.
클라이언트가 특정 비트레이트 플레이리스트의 최신 버전을 로드할 때, 해당 업데이트는 클라이언트가 다음 1-2초 내에 스위칭할 가능성이 있는 다른 렌디션들에 대한 정보를 함께 전달할 수 있습니다.
프리로드 힌트와 연결 관리
프리로드 힌트는 클라이언트가 다가오는 콘텐츠를 요청할 수 있게 하여 라운드 트립을 제거합니다.
Blocking Preload Hints를 포함한 연결 플로우
┌────────┐ ┌────────┐
│ Client │ │ Server │
└───┬────┘ └────┬───┘
│ GET playlist │
│◀────────────playlist with───────────│
│ preload hint │
│ │
│ GET hinted resource (blocks) │
│─────────────────────────────────────▶
│ (server produces media) │
│◀────────────200 OK──────────────────│
│ (immediate playback) │
6. 완전한 LL-HLS 플레이리스트 예제
Apple 공식 문서를 기반으로 한 완전한 예제
#EXTM3U
#EXT-X-TARGETDURATION:4
#EXT-X-VERSION:6
#EXT-X-SERVER-CONTROL:CAN-BLOCK-RELOAD=YES,PART-HOLD-BACK=1.0,CAN-SKIP-UNTIL=24.0
#EXT-X-PART-INF:PART-TARGET=0.33334
#EXT-X-MEDIA-SEQUENCE:264
#EXT-X-PROGRAM-DATE-TIME:2019-02-14T02:13:28.106Z
#EXT-X-MAP:URI="init.mp4"
#EXTINF:4.00008,
fileSequence270.mp4
#EXT-X-PART:DURATION=0.33334,URI="filePart271.0.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart271.1.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart271.2.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart271.3.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart271.4.mp4",INDEPENDENT=YES
#EXT-X-PART:DURATION=0.33334,URI="filePart271.5.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart271.6.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart271.7.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart271.8.mp4",INDEPENDENT=YES
#EXT-X-PART:DURATION=0.33334,URI="filePart271.9.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart271.10.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart271.11.mp4"
#EXTINF:4.00008,
fileSequence271.mp4
#EXT-X-PROGRAM-DATE-TIME:2019-02-14T02:14:00.106Z
#EXT-X-PART:DURATION=0.33334,URI="filePart272.a.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart272.b.mp4"
#EXT-X-PART:DURATION=0.33334,URI="filePart272.c.mp4"
#EXT-X-PRELOAD-HINT:TYPE=PART,URI="filePart273.3.mp4"
#EXT-X-RENDITION-REPORT:URI="../1M/waitForMSN.php",LAST-MSN=273,LAST-PART=2
#EXT-X-RENDITION-REPORT:URI="../4M/waitForMSN.php",LAST-MSN=273,LAST-PART=1
7. 실제 성능 벤치마크
실제 구현에서 일관된 지연시간 개선을 보여줍니다.
- StreamShark: 5초 (LL-HLS) vs 20-30초 (HLS)
- Apple 테스트: 3초 glass-to-glass
- AWS 구현: 5-10초 end-to-end
- Twitch Community HLS: ~5초
상용 플레이어 테스트 결과
AWS Media Services에서 수행한 실제 테스트 결과
첫 번째 시나리오는 지연시간을 우선시하고, 두 번째는 인코딩 효율성을 우선시합니다.
8. CDN 캐싱 전략
Roger Pantos가 WWDC2019에서 설명한 바와 같이, 기존 HLS에서 CDN 캐싱은 지연시간의 주요 원인 중 하나였습니다.
기존 HLS의 캐싱 문제점
기존 HLS에서는 다음과 같은 문제가 발생했습니다. 오리진 서버에 3개의 세그먼트(1,2,3)가 포함된 플레이리스트가 있다고 가정해봅시다. 첫 번째 클라이언트가 요청하면 CDN 엣지 서버에 캐시된 것이 없어서 오리진에서 최신 플레이리스트를 가져와 전달합니다.
그런데 1-2초 후 오리진에서 새로운 세그먼트 4가 추가되어 플레이리스트가 업데이트됩니다.
이때 두 번째 클라이언트가 요청하면 CDN 엣지는 캐시된 구버전 플레이리스트(1,2,3만 포함)를 반환합니다. 클라이언트는 세그먼트 4의 존재조차 알 수 없게 됩니다.
왜 CDN이 매번 오리진을 확인하지 않을까요? 무작위로 들어오는 클라이언트 요청마다 오리진을 확인한다면 "오리진이 녹아버릴" 것이기 때문입니다. 따라서 CDN은 TTL(Time To Live) 동안 반드시 캐시해야 하고, 이 TTL이 길수록 지연시간이 더욱 늘어납니다.
LL-HLS의 혁신적 해결책
LL-HLS는 이 문제를 "캐시 버스팅(cache busting)" 방식으로 해결합니다. 각각의 플레이리스트 업데이트마다 서로 다른 URL을 사용하는 것입니다.
첫 번째 클라이언트가 특정 업데이트를 요청하면 CDN은 "이건 처음 보는 URL이네"라고 판단하고 오리진으로 전달합니다. 오리진은 "아직 만들어지지 않았어"라고 응답하고, 준비되면 CDN을 통해 클라이언트에게 전달합니다.
다음 클라이언트가 같은 업데이트를 요청하면 CDN은 URL을 인식하고 캐시에서 즉시 제공합니다. 하지만 그 다음 업데이트를 원하는 클라이언트는 완전히 다른 URL로 요청하므로, CDN은 이것이 새로운 요청임을 즉시 알 수 있습니다. 구버전을 제공하지 않고 오리진으로 직접 요청을 전달합니다.
이런 방식으로 새로운 플레이리스트 업데이트 요청은 본질적으로 캐시 버스팅 기능을 갖게 되며, CDN에서 캐싱이 전반적으로 더 효율적으로 작동합니다.
9. 플레이어 지원 현황
Apple 생태계
- AVPlayer: macOS, iOS17/iPadOS17, tvOS17에서 안정적 재생
- Safari Mobile: 직접 재생은 권장하지 않음 (비트레이트 업스위칭, 플레이헤드 포지셔닝 예측성, 드리프트 보상 개선 필요)
Android ExoPlayer
Google ExoPlayer 팀의 블로그 글에서도 언급된 바와 같이, Low-latency Streams는 별도 설정 없이 동작합니다. (물론 예시는 ll-dash이긴 합니다.)
내부 구현
실제 AndroidX Media3 ExoPlayer HLS 구현을 살펴보면, 다음과 같은 파일들이 존재합니다.
HlsPlaylistParser.java
: LL-HLS 태그 파싱 및 블로킹 리로드 로직HlsChunkSource.java
: 부분 세그먼트 로딩 및 델타 업데이트 처리HlsMediaPeriod.java
: Live offset 자동 조정 및 버퍼링 전략HlsPlaylistTracker.java
: 블로킹 플레이리스트 요청 관리
자동 감지 메커니즘
// HlsPlaylistParser에서 EXT-X-SERVER-CONTROL 태그 감지
if (serverControlTag != null && serverControlTag.canBlockReload) {
// 블로킹 플레이리스트 리로드 활성화
playlistTracker.enableBlockingPlaylistReload()
}
// HlsChunkSource에서 부분 세그먼트 처리
if (playlistSnapshot.partList.isNotEmpty()) {
// 부분 세그먼트 기반 청크 생성
return createPartialSegmentChunk(playlistSnapshot.partList)
}
블로킹 요청 구현
// HlsPlaylistTracker에서 블로킹 요청 처리
fun maybeThrowPrimaryPlaylistRefreshError() {
if (playlistBundles[primaryPlaylistIndex]?.playlistError != null) {
// 블로킹 요청 중 에러 발생 시 처리
throw playlistBundles[primaryPlaylistIndex]?.playlistError!!
}
}
지연시간을 더욱 최적화하려면 다음 설정을 추가할 수 있습니다.
// 전역 live streaming 설정
val player = ExoPlayer.Builder(context)
.setMediaSourceFactory(
DefaultMediaSourceFactory(context)
.setLiveTargetOffsetMs(2000) // 2초 타겟 오프셋
)
.build()
// MediaItem별 설정
val mediaItem = MediaItem.Builder()
.setUri(llHlsUrl)
.setLiveConfiguration(
MediaItem.LiveConfiguration.Builder()
.setTargetOffsetMs(2000) // 타겟 지연시간
.setMinOffsetMs(1000) // 최소 지연시간
.setMaxOffsetMs(5000) // 최대 지연시간
.setMinPlaybackSpeed(0.97f) // 최소 재생 속도
.setMaxPlaybackSpeed(1.03f) // 최대 재생 속도
.build()
)
.build()
player.setMediaItem(mediaItem)
player.prepare()
핵심 포인트
- 자동 인식: LL-HLS 매니페스트를 자동으로 감지하고 최적화 적용
- 자동 조정: 네트워크 상황에 따라 live offset과 재생 속도 자동 조정
- HTTP/2 지원: LL-HLS의 블로킹 요청과 멀티플렉싱 자동 활용
웹 기반 플레이어
- hls.js: JavaScript HLS 클라이언트 (LL-HLS 실험적 지원)
- dash.js: JavaScript DASH 클라이언트 (Low-Latency DASH 지원)
- Shaka Player: Google의 오픈 소스 플레이어 (LL-HLS 실험적 지원)
상용 플레이어
- THEOplayer: LL-HLS 성공적 검증 완료
- JW Player: LL-HLS 지원 및 검증 완료
10. 바이트레인지 주소 지정 부분
더 효율적인 전달을 위해 LL-HLS는 바이트레인지 주소 지정도 지원합니다.
#EXTINF:4.08,
fs270.mp4
#EXT-X-PART:DURATION=1.02,URI="fs271.mp4",BYTERANGE="20000@0"
#EXT-X-PART:DURATION=1.02,URI="fs271.mp4",BYTERANGE="23000@20000"
#EXT-X-PART:DURATION=1.02,URI="fs271.mp4",BYTERANGE="18000@43000"
#EXT-X-PRELOAD-HINT:TYPE=PART,URI="fs271.mp4",BYTERANGE-START=61000
이 방식을 통해 하나의 파일에서 여러 부분을 효율적으로 참조할 수 있습니다.
11. 서버 참조 구현 업데이트 (2020년)
Apple의 Low-Latency 서버 참조 구현이 크게 개선되었습니다.
CMAF 지원
fMP4 CMAF 패키징: 2019년 말부터 미디어를 fragmented MPEG-4로 패키징하는 옵션이 추가되어 CMAF(Common Media Application Format)와 호환됩니다.
# CMAF 호환 LL-HLS 스트림 생성
./mediafilesegmenter -f /path/to/output -B playlist -t 4 -S 3 -D \
--generate-variant-plist \
--iframe-only-playlist \
--low-latency \
--fmp4-fragment-duration 0.33334 \
input.mov
웹 서버 간편화
Go 스크립트 통합: 기존에는 별도의 웹 서버를 구성하고 PHP 스크립트를 연결해야 했지만, 이제는 단일 Go 스크립트를 실행하면 전달 지시자(delivery directives)와 HTTP/2 웹 서버가 하나의 패키지에서 구현됩니다.
# 기존 방식 (복잡)
# 1. Apache/Nginx 설정
# 2. PHP 스크립트 구성
# 3. 연결 및 테스트
# 2020년 방식 (간단)
go run hls-ll-server.go --content-dir /path/to/segments --port 8080
통합 도구 패키지
단일 다운로드: Low-Latency 도구들이 정규 HLS 도구 패키지에 통합되어 이제 모든 것을 하나의 다운로드로 제공합니다.
12. HTTP 전송 최적화
LL-HLS는 멀티플렉싱 이점을 활용하기 위해 CDN 측에서 HTTP/2가 필요합니다.
📡 향후 발전 방향: Roger Pantos가 공개 hls-interest 메일링 리스트에서 발표한 바에 따르면, iOS 17에서 HTTP/3 지원이 추가되며, HTTP/3를 통한 LL-HLS 전송에는 RFC 9218(Extensible Prioritization Scheme for HTTP)에 설명된 서버 정의 우선순위를 사용하여 플레이리스트 전송을 우선시해야 합니다.
표준화 완료
2020년부터 Low-Latency 확장이 IETF의 HLS 사양 핵심 부분에 포함되었습니다. 또한 Low-Latency Server Configuration Profile과 CDN tune-in 알고리즘을 설명하는 두 개의 새로운 부록도 추가되었습니다.
마무리하며
LL-HLS는 HTTP 기반 스트리밍의 확장성을 유지하면서 기존 HLS 대비 80% 지연시간 감소를 달성한 혁신적인 프로토콜입니다. 2-5초의 end-to-end 지연시간으로 방송 품질의 지연시간에 근접하며, 라이브 스포츠, 게임 스트리밍, 실시간 상호작용이 중요한 애플리케이션에서 새로운 가능성을 열어줍니다.
2020년 정식 출시와 함께 도입된 Blocking Preload Hints, Gap Signaling, CMAF 지원 등의 개선사항은 더욱 안정적이고 효율적인 저지연 스트리밍 환경을 제공합니다. 향후 HTTP/3 지원과 서버 정의 우선순위(RFC 9218) 등의 추가 개선사항이 도입된다면 더욱 최적화된 저지연 스트리밍 환경이 구축될 것으로 예상됩니다. 특히 실시간 상호작용이 중요한 라이브 커머스, 스포츠 중계, 게임 스트리밍 등의 분야에서 혁신적인 사용자 경험을 제공할 수 있을 것으로 기대가 됩니다.
참고 자료
- HTTP Live Streaming 2nd Edition - IETF Draft
- Apple Developer - Enabling Low-Latency HLS
- WWDC2019 - Introducing Low-Latency HLS
- WWDC2020 - What's New in Low-Latency HLS
- AWS - How to configure LL-HLS workflow
- RFC 9218 - Extensible Prioritization Scheme for HTTP
- AndroidX Media3 - Low-latency live streaming with ExoPlayer