[iOS] HLS 에 대하여

이상진·2025년 12월 12일

HLS, DASH 학습 기록

목록 보기
2/4
post-thumbnail

개요

VOD(Video On Demand) 의 핵심은 다양한 네트워크 환경에서도 끊김 없이 고품질의 영상을 제공하는 기술이다. 특히 사용자의 네트워크 상태는 시간에 따라 계속 변화하기 때문에, 고정된 품질의 스트리밍은 사용자 경험을 저하시킬 수 있다. 이를 해결하기 위한 대표적인 방법으로 Adaptive Bitrate Streaming(ABR)이며, HTTP Live Streaming(HLS)은 이러한 기술을 실제 서비스에 적용하기 위한 핵심적인 프로토콜이다.


HLS 란?

HLS
HLS is designed for reliability and dynamically adapts to network conditions by optimizing playback for the available speed of wired and wireless connections.

HLS는 Apple이 개발한 적응형 비트레이트 스트리밍 프로토콜이다. 사용자가 실시간으로 스트리밍을 보고 있을 때 계속해서 동일한 네트워크 속도로 유지가 되면 좋겠지만, 실제 사용자의 네트워크 속도는 자주 변경된다. 이에 따라 다양한 환경에서 끊임없는 재생을 지원하기 위해 고안된 것이 ABR 인데, 이는 사용자의 bandwidth, CPU capacity 를 실시간으로 감지하고 이에 따라 미디어 스트림 품질을 조정함으로써 동작하게 된다.

HLS는 세그먼트 기반 전송으로, 전체 비디오를 작은 세그먼트(일반적으로 6~10초 길이)로 나누어 전송한다. 각 세그먼트는 .ts (MPEG-2 Transport Stream) 또는 .fmp4 (Fragmented MP4) 형식으로 인코딩되며, 독립적으로 디코딩 가능한 완전한 미디어 파일이다.


.m3u8 톺아보기

.m3u8 은 M3U(Playlist) + UTF-8 인코딩으로, 비디오 스트리밍용 플레이리스트로 만든 파일 확장자이다. 이는 두 가지 타입이 있다.

- Master Playlist (Multivariant Playlist)
	- HLS 스트리밍의 진입점(Entry Point)
	- 사용 가능한 모든 옵션의 인덱스/카탈로그
	- 실제 미디어 데이터 없음, 단지 선택지만 제공
	- 파일명 예시: master.m3u8, playlist.m3u8
    
- Media Playlist
	- 실제 비디오/오디오 세그먼트 파일들의 목록
	- 재생에 필요한 구체적인 정보 포함
	- Master Playlist에서 선택된 특정 옵션의 상세 정보
	- 파일명 예시: v5/prog_index.m3u8, a1/prog_index.m3u8

글로만 보면 이해가 잘 되지 않아서 다음 파일을 직접 까보도록 하자. 아래 파일은 Master Playlist로, fMP4 방식의 파일이다.

#EXTM3U
#EXT-X-VERSION:6
#EXT-X-INDEPENDENT-SEGMENTS


#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=2168183,BANDWIDTH=2177116,CODECS="avc1.640020,mp4a.40.2",RESOLUTION=960x540,FRAME-RATE=60.000,CLOSED-CAPTIONS="cc1",AUDIO="aud1",SUBTITLES="sub1"
v5/prog_index.m3u8
#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=7968416,BANDWIDTH=8001098,CODECS="avc1.64002a,mp4a.40.2",RESOLUTION=1920x1080,FRAME-RATE=60.000,CLOSED-CAPTIONS="cc1",AUDIO="aud1",SUBTITLES="sub1"
v9/prog_index.m3u8
#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=6170000,BANDWIDTH=6312875,CODECS="avc1.64002a,mp4a.40.2",RESOLUTION=1920x1080,FRAME-RATE=60.000,CLOSED-CAPTIONS="cc1",AUDIO="aud1",SUBTITLES="sub1"
v8/prog_index.m3u8
#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=4670769,BANDWIDTH=4943747,CODECS="avc1.64002a,mp4a.40.2",RESOLUTION=1920x1080,FRAME-RATE=60.000,CLOSED-CAPTIONS="cc1",AUDIO="aud1",SUBTITLES="sub1"
v7/prog_index.m3u8
#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=3168702,BANDWIDTH=3216424,CODECS="avc1.640020,mp4a.40.2",RESOLUTION=1280x720,FRAME-RATE=60.000,CLOSED-CAPTIONS="cc1",AUDIO="aud1",SUBTITLES="sub1"
v6/prog_index.m3u8
...
(생략)

해당 파일안에는 v5/prog_index.m3u8, v9/prog_index.m3u8 이런식으로 상대 주소가 적혀 있는데, 바로 이것들이 Media Playlist 이다.

Master Playlist URL
-> https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/master.m3u8

Media Playlist URL
-> https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v5/prog_index.m3u8
-> https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v9/prog_index.m3u8

이미 Master Playlist 파일은 직접 다운로드해서 확인을 했으니, 해당 파일 안에 Media Playlist 파일을 까보기 위해 터미널에서 요청 날린 결과는 다음과 같았다.

(Terminal)
curl https://devstreaming-cdn.apple.com/videos/streaming/examples/img_bipbop_adv_example_fmp4/v5/prog_index.m3u8

#EXTM3U
#EXT-X-TARGETDURATION:6
#EXT-X-VERSION:7
#EXT-X-MEDIA-SEQUENCE:1
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-INDEPENDENT-SEGMENTS
#EXT-X-MAP:URI="main.mp4",BYTERANGE="719@0"
#EXTINF:6.00000,
#EXT-X-BYTERANGE:1508000@719
main.mp4
#EXTINF:6.00000,
#EXT-X-BYTERANGE:1510244@1508719
main.mp4
...
#EXTINF:6.00000,
#EXT-X-BYTERANGE:1504803@148977509
main.mp4
#EXT-X-ENDLIST

총 100 개의 main.mp4 를 확인할 수 있는데, 이는 해당 Media Playlist 는 100개의 비디오 세그먼트로 이루어져 있는 것을 확인할 수 있다. 다시 말해 하나의 main.mp4 파일을 100 개의 세그먼트들로 쪼개서 A 부터 B까지 바이트 범위를 설정해서 받아오는 것이다. 위와 같은 방식이 바로 "BYTERANGE 방식"이고 해당 방식에서 사용하는 태그에 대한 설명은 다음과 같다.

  • #EXTM3U
    - 모든 M3U8 파일의 필수 시작 태그, 해당 파일은 M3U 플레이리스트임을 선언

  • #EXT-X-TARGETDURATION:6
    - 모든 세그먼트의 최대 길이 (초 단위) -> 여기서는 6초라는 뜻
    - 플레이어가 버퍼 크기를 결정하는 데 사용

  • #EXT-X-VERSION
    - v3: 기본 HLS
    - v4: #EXT-X-BYTERANGE, I-Frame 플레이리스트
    - v6: #EXT-X-MAP (fMP4 초기화 세그먼트)
    - v7: fMP4 완전 지원, 부동소수점 EXTINF 값

  • #EXT-X-MEDIA-SEQUENCE
    - 첫 번째 세그먼트의 시퀀스 번호를 뜻함
    - VOD에서는 보통 0 또는 1 이지만 라이브 스트리밍에서는 계속 증가하기 때문에 "어떤 세그먼트부터 시작하는지" 를 해당 시퀀스 번호를 통해 알 수 있다

  • #EXT-X-PLAYLIST-TYPE
    - VOD(Video On Demand), EVENT(라이브 이벤트), LIVE or 없음(일반 라이브) 3가지로 구성
    - VOD 의 경우 플레이리스트가 변경되지 않고 모든 세그먼트가 처음부터 존재하지만, EVENT의 경우 라이브가 진행될수록 세그먼트가 계속 뒤에 추가된다. 즉 라이브가 끝나면 사실상 VOD 가 된다.(더 이상 추가되지 않기 때문에) LIVE는 세그먼트가 “윈도우” 기반으로 관리되는데 예를 들면 마지막 3분만 유지하고 이전 건 제거하는 식으로 플레이리스트가 계속 밀려난다.

  • #EXT-X-INDEPENDENT-SEGMENTS
    - 모든 세그먼트가 독립적으로 디코딩 가능함을 명시
    - 각 세그먼트는 다른 세그먼트에 의존하지 않음을 뜻함 -> 언제든지 다른 화질로 변환이 가능

  • #EXT-X-MAP:URI="main.mp4",BYTERANGE="719@0"
    - fMP4의 초기화 세그먼트 정의 (MPEG-TS에는 없음)
    - 비디오 메타데이터 포함 (실제 미디어 데이터는 없음)

  • 초기화 세그먼트의 내용
    - 파일 타입 (ftyp box)
    - 무비 헤더 (moov box)

    • 비디오 코덱: H.264
    • 오디오 코덱: AAC
    • 해상도: 960x540
    • 프레임레이트: 60fps
    • 샘플레이트: 48kHz
    • 기타 메타데이터
  • BYTERANGE="719@0"
    - 형식: 길이@시작위치
    - 719: 초기화 세그먼트 크기 (719 바이트)
    - 0: 파일의 시작 위치 (0 바이트부터)

  • #EXTINF:6.00000
    - 다음 세그먼트의 재생 시간 (초 단위)

  • #EXT-X-BYTERANGE:1508000@719
    - 다음 세그먼트의 바이트 범위 지정

여기까지가 하나의 파일을 여러 범위로 나누어 받아오는 "BYTERANGE" 방식이고, 여러 파일을 받아오는 방식도 있다.
Apple 공식문서의 예제에 따르면

  • 개별 파일 방식
#EXTM3U
#EXT-X-PLAYLIST-TYPE:VOD
#EXT-X-TARGETDURATION:10
#EXT-X-VERSION:4
#EXT-X-MEDIA-SEQUENCE:0
#EXTINF:10.0,
fileSequenceA.ts
#EXTINF:10.0,
fileSequenceB.ts
#EXTINF:10.0,
fileSequenceC.ts
#EXTINF:9.0,
fileSequenceD.ts
#EXT-X-ENDLIST

이런식으로 각 세그먼트가 개별 파일들로 구성되어 있는 것을 확인할 수 있다. 보통 .ts 확장자는 MPEG 표준을 따르고, 각 TS 파일에 메타데이터를 포함하고 있기 때문에 초기화 세그먼트가 불필요하다.

.ts 확장자 이외에도 .m4s 또는 .mp4 확장자를 이용하기도 한다. 이는 TS 파일과 다르게 메타데이터를 포함하고 있지 않아서 초기화 세그먼트(init.mp4)가 별도로 필요한 것 이외에 동일하게 동작한다.


Master Playlist

지금까지 Media Playlist 를 알아보았으니 다시 돌아와서 Master Playlist를 구성하는 태그를 알아보자.

  • #EXT-X-STREAM-INF
#EXT-X-STREAM-INF:AVERAGE-BANDWIDTH=2168183,BANDWIDTH=2177116,CODECS="avc1.640020,mp4a.40.2",
RESOLUTION=960x540,FRAME-RATE=60.000,CLOSED-CAPTIONS="cc1",AUDIO="aud1",SUBTITLES="sub1"

1. BANDWIDTH
- 최대 비트레이트
- 클라이언트가 스트림 선택 시 사용되는 기준

2. AVERAGE-BANDWIDTH
- 평균 비트레이트

3. CODECS
- 비디오와 오디오 코덱 명시
- 보통 쉼표로 구분(ex. avc1.640020,mp4a.40.2)

4. RESOLUTION
- 비디오 해상도
- 클라이언트가 화면 크기에 맞는 스트림 선택

5. FRAME-RATE
- 초당 프레임 수
- 부드러운 움직임 vs 데이터 절약 선택에 사용

6. CLOSED-CAPTIONS
- 자막 그룹 ID 참조
- 비디오 스트림 내부에 인코딩된 자막
- 나중에 #EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS 태그에서 정의됨
- 비디오 스트림 내부에 인코딩

7. AUDIO
- 오디오 그룹 ID 참조
- 이 비디오 스트림과 함께 재생할 오디오 트랙 지정
- 나중에 #EXT-X-MEDIA:TYPE=AUDIO 태그에서 정의됨

8. SUBTITLES
- 자막 그룹 ID 참조
- 외부 자막 파일
- 나중에 #EXT-X-MEDIA:TYPE=SUBTITLES 태그에서 정의됨
- 자막이 담긴 별도의 파일임(CLOSED-CAPTIONS 와 차이점)

해당 파일을 보면 같은 Media Playlist 가 여러개 있는 것을 확인할 수 있다. 다음 사진에서는 예를 들어 v5/prog_index.m3u8 이라는 상대 주소가 3개 있는 것을 볼 수 있는데, 이는 뒤에 CODECS 태그에서 오디오 코덱과 AUDIO 태그가 다르다. 이러한 이유로는 사용자 환경에 따라 최적 오디오 선택하기 위함이다. 예를 들면 스테레오 헤드폰에서는 aud1 (AAC), 홈시어터 시스템에서는 aud2 또는 aud3 (서라운드) 이런식으로 플레이어가 자동으로 디바이스에 따라 오디오를 선택함으로써 최적의 영상을 보고 들을 수 있는 것이다. 결국 해당 파일에서는 총 8개의 비디오 화질과 각 비디오 화질마다 3개의 오디오 옵션이 있어 총 24개의 스트림 조합을 확인할 수 있다.

▲ 그림 1. 동일 비디오, 다른 오디오 조합

  • #EXT-X-I-FRAME-STREAM-INF
#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=183689,BANDWIDTH=187492,CODECS="avc1.64002a",RESOLUTION=1920x1080,URI="v7/iframe_index.m3u8"
#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=132672,BANDWIDTH=136398,CODECS="avc1.640020",RESOLUTION=1280x720,URI="v6/iframe_index.m3u8"
#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=97767,BANDWIDTH=101378,CODECS="avc1.640020",RESOLUTION=960x540,URI="v5/iframe_index.m3u8"
#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=75722,BANDWIDTH=77818,CODECS="avc1.64001e",RESOLUTION=768x432,URI="v4/iframe_index.m3u8"
#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=63522,BANDWIDTH=65091,CODECS="avc1.64001e",RESOLUTION=640x360,URI="v3/iframe_index.m3u8"
#EXT-X-I-FRAME-STREAM-INF:AVERAGE-BANDWIDTH=39678,BANDWIDTH=40282,CODECS="avc1.640015",RESOLUTION=480x270,URI="v2/iframe_index.m3u8"

해당 태그는 키프레임(I-Frame)만 포함하는 특수 플레이리스트이다. 여기서는 CODECS 태그에 비디오 코덱만 포함하고, 일반 스트림과는 다르게 속성에 URI 라는 해당 I-Frame 을 받아올 수 있는 상대주소가 적혀있다.

  • #EXT-X-MEDIA:TYPE=AUDIO
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud1",LANGUAGE="en",NAME="English",AUTOSELECT=YES,DEFAULT=YES,CHANNELS="2",URI="a1/prog_index.m3u8"
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud2",LANGUAGE="en",NAME="English",AUTOSELECT=YES,DEFAULT=YES,CHANNELS="6",URI="a2/prog_index.m3u8"
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="aud3",LANGUAGE="en",NAME="English",AUTOSELECT=YES,DEFAULT=YES,CHANNELS="6",URI="a3/prog_index.m3u8"


- GROUP-ID="aud1"
이 오디오 트랙의 그룹 식별자로, `#EXT-X-STREAM-INF`의 `AUDIO="aud1"`과 연결됨

- LANGUAGE="en"
ISO 639 언어 코드, 다국어 지원 시: `ko`, `ja`, `zh` 등

- AUTOSELECT=YES
플레이어가 자동으로 선택 가능, 사용자 언어 설정에 맞춰 자동으로 선택된다

- CHANNELS="2"
오디오 채널 수를 뜻한다. "2"는 스테레오, "6"는 5.1 서라운드
  • #EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS
#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,GROUP-ID="cc1",LANGUAGE="en",NAME="English",AUTOSELECT=YES,DEFAULT=YES,INSTREAM-ID="CC1"

이는 비디오 내부에 인코딩된 자막을 뜻한다.

따라서 마스터 플레이리스트의 전체 구조는 다음과 같다.

Master Playlist
│
├─ 비디오 스트림 (24개)
│   ├─ v2 (270p) × 3개 오디오
│   ├─ v3 (360p) × 3개 오디오
│   ├─ v4 (432p) × 3개 오디오
│   ├─ v5 (540p) × 3개 오디오
│   ├─ v6 (720p) × 3개 오디오
│   ├─ v7 (1080p) × 3개 오디오
│   ├─ v8 (1080p mid) × 3개 오디오
│   └─ v9 (1080p high) × 3개 오디오
│
├─ I-Frame 스트림 (6개)
│   └─ 빠른 탐색용
│
├─ 오디오 트랙 (3개)
│   ├─ aud1: AAC 2채널
│   ├─ aud2: AC-3 5.1채널
│   └─ aud3: E-AC-3 5.1채널
│
└─ 자막 (2개)
    ├─ cc1: Closed Captions (인스트림)
    └─ sub1: Subtitles (외부 파일)

Model 정의

그럼 이제 위에서 계속 사용했던 마스터 플레이리스트 파일을 기준으로 "BYTERANGE" 기법을 활용해 AVPlayer 에서 해당 비디오를 재생하는 것을 만들어보려고 한다. 모델은 총 4개로 정의했다.

Player/Models/HLS/Master
- HLSMasterPlaylist.swift
- HLSMediaInfo
- HLSStreamInfo

Player/Models/HLS/Media
- HLSMediaPlaylist.swift

위에서 언급한대로 HLS 은 Master Playlist 와 Media Playlist 두 가지 타입을 사용하기 때문에 다음과 같이 선언했고, HLSMediaInfoHLSStreamInfoHLSMasterPlaylist의 프로퍼티이다.


HLSParser

해당 파서는 문자열로 들어오는 응답을 Master Playlist 혹은 Media Playlist로 파싱하기 위한 유틸 객체이다.
public 함수로는 두 플레이리스톨 파싱하는 함수를 기준으로 private 함수로는 다음과 같이 구성하였다.

Utilities/HLSParser.swift
1. #EXT-X-VERSION 파싱
2. #EXT-X-STREAM-INF 파싱
3. #EXT-X-MEDIA 파싱
4. #EXT-X-MAP 파싱 (초기화 세그먼트)
5. 속성 파싱 (KEY=VALUE 또는 KEY="VALUE" 형식)

HLSDownloadManager

  • loadMasterPlaylist
    Master Playlist(.m3u8)는 HLS 스트리밍의 진입점이다. 클라이언트는 먼저 이 Master Playlist를 다운로드해서 사용 가능한 스트림 옵션들을 파악한다.
/// Master Playlist 로드 및 파싱
func loadMasterPlaylist(from url: URL) async throws -> HLSMasterPlaylist {
    let content = try await downloadPlaylist(from: url)
    let playlist = try parser.parseMasterPlaylist(content, baseURL: url)
    self.masterPlaylist = playlist
    return playlist
}
  • selectStream
    사용자의 현재 네트워크 대역폭을 측정한 값을 기준으로 적절한 화질을 선택한다. 해당 기능이 ABR 에서도 사용되는 기능이다. bandwidth <= 현재대역폭 조건으로 재생 가능한 최고 화질을 선택하는 로직이다.
/// 특정 대역폭에 맞는 스트림 선택
func selectStream(bandwidth: Int) throws -> HLSStreamInfo {
    guard let masterPlaylist = masterPlaylist else {
        throw DownloadError.noAvailableStream
    }

    // 대역폭 이하의 가장 높은 품질 선택
    let sortedStreams = masterPlaylist.sortedStreams
    let selectedStream = sortedStreams.last { $0.bandwidth <= bandwidth }
        ?? sortedStreams.first // 최소 품질이라도 선택

    guard let stream = selectedStream else {
        throw DownloadError.noAvailableStream
    }

    self.currentStreamInfo = stream
    return stream
}
  • loadMediaPlaylist
    Media Playlist는 실제 비디오 세그먼트 파일들의 목록이므로 각 세그먼트의 URL, 재생 시간, 순서 등의 정보 포함하고 있다.
/// Media Playlist 로드 및 파싱
func loadMediaPlaylist(for stream: HLSStreamInfo) async throws -> HLSMediaPlaylist {
    guard let masterPlaylist = masterPlaylist else {
        throw DownloadError.noAvailableStream
    }

    guard let mediaPlaylistURL = masterPlaylist.absoluteURL(for: stream.uri) else {
        throw DownloadError.invalidURL
    }

    let content = try await downloadPlaylist(from: mediaPlaylistURL)
    let playlist = try parser.parseMediaPlaylist(content, baseURL: mediaPlaylistURL)
    self.currentMediaPlaylist = playlist
    return playlist
}
  • downloadAndCombine
    해당 함수는 플레이리스트를 받아와서 재생까지 검증을 하기 위해 만든 임시 함수이다. 원래는 세그먼트를 받아오는대로 실시간으로 영상에 재생하지만, 이는 다음 포스트에서 다룰 것이므로 이번 포스트에서는 간단하게 받아온 세그먼트들을 결합하여 임시로 저장했다가 해당 파일을 AVPlayer 를 이용해 재생해보려고 한다.
/// [Phase 1 검증용] 모든 세그먼트를 다운로드하고 결합해서 임시 파일로 저장
/// - Returns: 결합된 비디오 파일의 임시 URL
func downloadAndCombine() async throws -> URL {
    guard let mediaPlaylist = currentMediaPlaylist else {
        throw DownloadError.noAvailableStream
    }

    var combinedData = Data()

    // 1. 초기화 세그먼트 추가
    if let initData = try await downloadInitializationSegment() {
        combinedData.append(initData)
    }

    // 2. 모든 미디어 세그먼트 다운로드 & 결합
    for segment in mediaPlaylist.segments {
        let data = try await downloadSegment(at: segment.index)
        combinedData.append(data)
    }

    // 3. 임시 파일로 저장
    let tempURL = FileManager.default.temporaryDirectory
        .appendingPathComponent(UUID().uuidString + ".mp4")

    do {
        try combinedData.write(to: tempURL)
        return tempURL
    } catch {
        throw DownloadError.networkError(error)
    }
}

HLS 다운로드 테스트

  • VideoPlayerViewController.swift
    loadTestVideo() 함수에서 기존의 AVPlayer 방식을 주석처리 하고, 다음과 같이 위에서 받아온 segment들을 가지고 AVPlayer 에 재생을 테스트해볼 수 있다.
    private func loadTestVideo() {
        // Phase 1: HLS 다운로드 & 결합 테스트
        testHLSDownload()

        // 기존 AVPlayer 방식
        // guard let url = URL(string: testStreamURL) else {
        //     return
        // }
        // streamPlayerManager.loadStream(url: url)
        // playerView.player = streamPlayerManager.player
    }

    /// [Phase 1 검증] HLS 다운로드 및 재생 테스트
    private func testHLSDownload() {
        Task {
            do {
                guard let masterURL = URL(string: testStreamURL) else { return }

                let downloadManager = HLSDownloadManager()

                // 1. Master Playlist 로드
                let masterPlaylist = try await downloadManager.loadMasterPlaylist(from: masterURL)
                
                // 2. 스트림 선택 (3Mbps로 가정)
                let stream = try await downloadManager.selectStream(bandwidth: 3_000_000)

                // 3. Media Playlist 로드
                let mediaPlaylist = try await downloadManager.loadMediaPlaylist(for: stream)

                // 4. 모든 세그먼트 다운로드 & 결합
                let videoURL = try await downloadManager.downloadAndCombine()

                // 5. AVPlayer로 재생
                await MainActor.run {
                    streamPlayerManager.loadStream(url: videoURL)
                    playerView.player = streamPlayerManager.player
                }
            } catch {
                print("Error: \(error.localizedDescription)")
            }
        }
    }

실행 결과는 다음과 같이 재생이 잘 되는 것을 확인할 수 있다. 다만 위 코드는 모든 segment들의 sequnce를 받아온 뒤 비디오를 재생하기 때문에 재생되기까지 시간이 걸리고, 메모리에 해당 비디오의 크기만큼 차지하는 문제가 있다. 우선 지금까지 단계의 목적은 단순히 다운로드한 비디오 Segment들이 (1) 파싱이 잘 되는지, (2) Sequence로 쭉 연결했을 때 재생이 잘 되는지 확인이 목적이었기 때문에 이 다음으로는 실시간으로 비디오 segment들을 재생하고 버퍼링을 최소화하며 ABR 을 적용하고자 한다.

▲ 그림 . Segment 다운로드 후 실행 결과


Reference

[Apple Docs]

[Blog]

profile
모바일 개발에 관하여 이것, 저것 다 합니다.

0개의 댓글