📁 File6. 스트리밍08 — Player Engines (hls.js / Shaka / Video.js / ExoPlayer / AVPlayer)

08 — Player Engines (hls.js / Shaka / Video.js / ExoPlayer / AVPlayer)

이 문서가 답하는 질문: HLS·DASH·DRM 표준을 실제로 처리하는 코드는 무엇이고, 어떤 엔진을 어디에 써야 하나? 핵심 한 줄: 플레이어 엔진은 매니페스트를 읽고, ABR을 결정하고, MSE/EME를 운전하는 클라이언트 측 OS다. 디바이스마다 기본 엔진이 다르고, 지원 표준이 다르다.


한 줄 답 (Pyramid Top)

플레이어 엔진은 다섯 가지 — hls.js(Web HLS), Shaka Player(Web 통합), Video.js(UI shell), ExoPlayer/Media3(Android), AVPlayer(iOS). 어느 디바이스에서 무엇을 받느냐가 엔진 선택을 결정한다 — 자유롭게 고르는 게 아니다.


Why — 왜 플레이어 엔진이 별도 레이어인가

브라우저의 <video src="...">progressive downloadiOS의 native HLS만 지원한다. 그 외:

  • DASH — 브라우저가 처음부터 지원 안 함.
  • HLS on Chrome/Firefox — 마찬가지로 native 미지원.
  • DRM — EME API는 있지만 각 DRM별 메시지 처리는 라이브러리가 필요.
  • ABR — 자동 비트레이트 전환 알고리즘은 플레이어가 구현.

→ 그래서 플레이어 엔진MSE + EME + 매니페스트 파서 + ABR + 자막 + thumbnail을 합쳐서 하나의 추상화로 노출.


How — Web 엔진 비교

hls.jsShaka PlayerVideo.js
만든 이Dailymotion/커뮤니티GoogleBrightcove
HLS✅ (전문)(플러그인 필요)
DASH(플러그인 필요)
MSS (Smooth Streaming)(플러그인)
DRMEME 직접 (제한적)✅ Widevine/FairPlay/PlayReady✅ (플러그인)
LL-HLS✅ (1.x+)✅ (4.x+)✅ (플러그인 따라)
번들 크기~150KB~400KB~250KB (코어)
위치엔진 only엔진 onlyUI shell + 엔진 wrapper
iOS SafariiPad에서 가능, iPhone은 native HLS fallback동일동일
typical 사용HLS만 필요 + 가벼움DASH 또는 multi-DRM 필요빠른 UI 구축 + 광범위 호환

1) hls.js

가벼움 + HLS 전문. MSE에 fMP4/TS를 demux해 append하는 일이 핵심.

import Hls from 'hls.js';
 
if (Hls.isSupported()) {
  const video = document.querySelector('video');
  const hls = new Hls({
    abrEwmaFastLive: 3.0,
    abrEwmaSlowLive: 9.0,
    abrBandWidthFactor: 0.95,
    abrBandWidthUpFactor: 0.7,
    maxBufferLength: 30,
    maxMaxBufferLength: 60,
    enableWorker: true,
    lowLatencyMode: true,
    backBufferLength: 30,
  });
  hls.loadSource('https://cdn.example.com/master.m3u8');
  hls.attachMedia(video);
 
  hls.on(Hls.Events.LEVEL_SWITCHED, (_, data) => {
    console.log('quality →', data.level);
  });
} else if (video.canPlayType('application/vnd.apple.mpegurl')) {
  // iOS native HLS
  video.src = 'https://cdn.example.com/master.m3u8';
}

전형 패턴: 지원되면 hls.js, 아니면 native. iOS Safari는 항상 후자로 떨어진다 (iPhone Safari).

강점

  • HLS만 필요할 때 최소 비용 솔루션.
  • worker로 main thread 부담 ↓.
  • LL-HLS 1.x부터 안정.

약점

  • DASH 안 함 → 두 매니페스트 운영하면 결국 Shaka 추가.
  • DRM은 Widevine만 잘 됨. FairPlay/PlayReady는 setup 복잡.

2) Shaka Player

Google의 통합 엔진. DASH·HLS·MSS 다 다루고 DRM 3종 모두 잘 지원.

import shaka from 'shaka-player';
 
shaka.polyfill.installAll();
 
const video = document.querySelector('video');
const player = new shaka.Player(video);
 
player.configure({
  abr: {
    enabled: true,
    bandwidthUpgradeTarget: 0.85,
    bandwidthDowngradeTarget: 0.95,
  },
  streaming: {
    bufferingGoal: 10,
    rebufferingGoal: 2,
    bufferBehind: 30,
  },
  drm: {
    servers: {
      'com.widevine.alpha': 'https://wv.example.com/license',
      'com.apple.fps.1_0': 'https://fps.example.com/license',
    },
  },
});
 
player.addEventListener('error', (e) => console.error(e.detail));
 
await player.load('https://cdn.example.com/manifest.mpd');
// 또는
await player.load('https://cdn.example.com/master.m3u8');

강점

  • HLS와 DASH 모두. 매니페스트 형식만 바꾸면 같은 코드.
  • DRM 3종 모두 공식 지원.
  • LL-HLS, LL-DASH 모두.
  • Subtitle / thumbnail track / WebVTT thumbnails / multi-audio.
  • 큰 OTT가 검증.

약점

  • 번들 크기 (~400KB).
  • 자체 UI는 기본적이라 커스텀 UI 필요 시 직접 구현.
  • API surface가 크다 — 학습 곡선.

shaka.ui.Overlay (기본 UI)

Shaka는 shaka.ui.Overlay로 기본 컨트롤을 자동 제공. Pause/Play, ABR menu, subtitle menu, fullscreen 등.

const ui = new shaka.ui.Overlay(player, container, video);
ui.configure({
  controlPanelElements: ['play_pause', 'time_and_duration', 'spacer', 'mute', 'volume', 'fullscreen', 'overflow_menu'],
  overflowMenuButtons: ['captions', 'quality', 'language', 'playback_rate'],
});

3) Video.js

UI 중심. 엔진 부분은 플러그인에 위임.

import videojs from 'video.js';
import 'videojs-contrib-quality-levels';
import 'videojs-http-streaming';   // HLS/DASH 통합 엔진
 
const player = videojs('my-video', {
  html5: {
    vhs: {
      overrideNative: true,
      enableLowInitialPlaylist: true,
    }
  }
});
 
player.src({
  src: 'https://cdn.example.com/master.m3u8',
  type: 'application/x-mpegURL'
});

강점

  • 플러그인 생태계 — 분석, 광고, UI 테마 등 수백 개.
  • UI customization이 쉽다 (테마, 스킨).
  • 빠른 prototyping.

약점

  • 엔진 자체 구현이 아니라 wrapper — DRM/LL 같은 엣지에서 플러그인 호환이 흔들린다.
  • HLS/DASH 모두 잘 되려면 VHS(videojs-http-streaming) 플러그인 필요 — 그게 사실상 내부 엔진.

How — Native 엔진 (모바일/TV 앱)

웹이 아니라 이라면 엔진이 디바이스 native다.

4) ExoPlayer / Media3 (Android)

Google이 만든 Android의 de facto 미디어 엔진. AndroidX Media3로 통합 (2022~).

val player = ExoPlayer.Builder(context).build()
val mediaItem = MediaItem.fromUri("https://cdn.example.com/master.m3u8")
player.setMediaItem(mediaItem)
player.prepare()
player.play()
// DRM
val drmConfig = MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID)
    .setLicenseUri("https://widevine.example.com/license")
    .setLicenseRequestHeaders(mapOf("Authorization" to "Bearer $jwt"))
    .build()
 
val mediaItem = MediaItem.Builder()
    .setUri("https://cdn.example.com/manifest.mpd")
    .setMimeType(MimeTypes.APPLICATION_MPD)
    .setDrmConfiguration(drmConfig)
    .build()

강점

  • HLS, DASH, MSS, ProgressiveMP4 모두.
  • DRM은 MediaDrm API 직결 — Widevine L1 자연스럽게.
  • Android의 모든 코덱(HEVC/AV1/VP9) hardware decode.
  • 4K HDR 정상 동작.

특징

  • Renderer-based 구조: AudioRenderer, VideoRenderer, TextRenderer가 각 트랙을 책임.
  • DataSource 추상화로 캐싱·재시도·인증을 끼워넣기 쉬움.
  • OfflineLicenseHelper로 다운로드 콘텐츠 라이선스 관리.

5) AVPlayer (iOS / macOS / tvOS)

iOS의 native 미디어 엔진. AVFoundation 프레임워크.

import AVKit
 
let url = URL(string: "https://cdn.example.com/master.m3u8")!
let player = AVPlayer(url: url)
let playerVC = AVPlayerViewController()
playerVC.player = player
present(playerVC, animated: true)
player.play()
// FairPlay
class ContentKeyDelegate: NSObject, AVContentKeyDelegate {
    func contentKeySession(_ session: AVContentKeySession,
                           didProvide keyRequest: AVContentKeyRequest) {
        // 1. App Cert 받기
        // 2. SPC 만들기
        keyRequest.makeStreamingContentKeyRequestData(forApp: appCert,
                                                       contentIdentifier: contentId.data(using: .utf8),
                                                       options: nil) { spc, error in
            // 3. 라이선스 서버에 SPC 보내기
            // 4. CKC 받아 keyRequest.processContentKeyResponse(...)
        }
    }
}

강점

  • HLS native — 매니페스트가 미디어로 그냥 받아짐.
  • FairPlay 통합 — Secure Enclave에서 직접 키 처리.
  • HDR / Dolby Atmos / spatial audio 모두 시스템 통합.
  • AirPlay / picture-in-picture 자동.

약점

  • DASH 미지원 — iOS 앱에서 DASH 하려면 별도 엔진(Shaka iOS 시도 또는 자체 구현).
  • 디버깅 도구 부족 — 매니페스트 파싱 에러를 상세히 알기 어렵다.
  • API가 imperative하고 KVO에 많이 의존 (SwiftUI보다는 UIKit 친화).

What — 한 OTT 서비스가 실제로 가져가는 조합

HLS + DASH 두 매니페스트를 항상 만든다. CMAF로 segment 공유하니 비용은 거의 안 든다.


What — ABR 알고리즘의 차이 (엔진별)

엔진기본 ABR튜닝
hls.jsEWMA fast/slow의 최솟값, throughput-basedabrEwma*, abrBandWidth*Factor
Shaka단순 throughput + 히스테리시스abr.bandwidthUpgrade/Downgrade Target
Video.js (VHS)hls.js 기반부분 노출
ExoPlayerDefaultLoadControl + DefaultBandwidthMeterLoadControl, TrackSelectionParameters
AVPlayer블랙박스 — Apple 알고리즘preferredPeakBitRate, preferredMaximumResolution

AVPlayer는 ABR 알고리즘을 튜닝 못한다. 상한만 줄 수 있다 (preferredPeakBitRate=2_000_000). 그래서 라이브 OTT가 Apple TV에서 의도치 않은 4K로 폭주하는 사고가 있을 수 있다 — 항상 cap을 명시.


What — 엔진 별 LL-HLS 지원 (2024~ 기준)

엔진LL-HLS비고
AVPlayer (iOS 14+)✅ Nativepartial segment, blocking GET 모두
Shaka 4.x+configure로 활성화
hls.js 1.x+lowLatencyMode: true
ExoPlayerMedia3 1.0+
Video.js (VHS)부분적VHS 버전에 따라

What-if — 잘못 고르면 어떻게 깨지는가

증상원인처방
Android 앱에서 DASH 안 받힘DASH MIME 미명시MimeTypes.APPLICATION_MPD 명시
iOS 앱에서 4K가 안 나옴preferredMaximumResolution 작음(width, height) 충분히
Chrome에서 HLS만 만들고 hls.js 없음hls.js 미탑재hls.js 또는 Shaka 추가
iPhone Safari에서 자막이 깨짐WebVTT 형식 오류iOS HLS의 자막은 segmented WebVTT
AVPlayer에서 DRM 라이선스 폭주세션 단위로 매번 요청persistent license + KeyCache
Shaka에서 buffer가 너무 짧음bufferingGoal=2 같이 너무 낮음8~15초
ExoPlayer에서 ABR이 4K로 시작TrackSelectionParameters 미설정maxVideoSize 설정
모바일 데이터에서 4K 자동 시작네트워크 타입 감지 안 함NetworkInfo 보고 cap 동적

Insight — 흥미로운 이야기

“Shaka는 사실 YouTube의 내부 엔진이 아니다”

YouTube는 자체 플레이어를 쓴다 (오픈소스 아님). Shaka는 Google이 Chromecast 등을 위해 별도로 만든 OSS. 두 시스템은 코드 공유가 거의 없다 — 같은 회사인 것이 우연.

“hls.js의 1.0이 늦게 나온 이유”

hls.js는 2015년에 시작했지만 1.0은 2022년에야 나왔다. 그 사이 LL-HLS 표준이 굳어지고, fMP4 HLS가 보편화되고, DRM이 까다로워지면서 0.x 시리즈에서 계속 진화했다. 1.0은 LL-HLS 안정 + fMP4 안정의 신호.

“AVPlayer가 ABR 알고리즘을 노출 안 하는 이유”

Apple은 ABR을 시스템 자원 최적화의 일부로 본다. 다른 앱이 background에서 다운로드 중이면 같은 디바이스의 AVPlayer가 덜 공격적으로 동작해야 한다 → OS가 알아서. 개발자가 튜닝하면 그 시스템적 균형이 깨진다는 철학. 그 결과 예측 가능성은 낮아진다 — 같은 콘텐츠가 같은 디바이스에서도 매번 다른 순서로 quality 전환할 수 있다.

“ExoPlayer가 Media3로 이름을 바꾼 이유”

이름이 비디오 only처럼 들렸다. Media3는 Audio + Video + UI + Cast까지 통합 SDK. 호환성: ExoPlayer 코드는 거의 그대로 Media3로 마이그레이션 가능 — 패키지명만 변경.

“한 회사가 자체 엔진을 만드는 경우”

Netflix, YouTube, Disney+, Amazon Prime Video 등은 자체 엔진을 가진다. 이유: (a) 자기 콘텐츠에 fully customize (P2P CDN, 자체 ABR, 자체 분석), (b) 디바이스별로 극한 최적화, (c) 라이선스 정책 통합. 일반 OTT는 Shaka/ExoPlayer/AVPlayer로 충분.


한 단락 요약 + Mermaid

플레이어 엔진은 디바이스마다 정해져 있다 — 자유 선택이 아니다. Web은 hls.js(HLS) 또는 Shaka(통합), iOS/macOS는 AVPlayer(HLS+FairPlay), Android는 ExoPlayer/Media3, Smart TV는 디바이스 native + Shaka fallback. 한 OTT는 항상 HLS + DASH 두 매니페스트를 만들고 CMAF로 segment 공유해 모든 엔진을 커버한다. ABR 알고리즘과 LL 지원 정도가 엔진 선택의 세부 변수다.


챕터 마감

이로써 06-streaming의 8개 단원이 끝났다 —

  1. 01-abr-theory.md — 비트레이트는 바뀌나
  2. 02-hls.md — Apple의 텍스트 매니페스트
  3. 03-mpeg-dash.md — ISO의 XML 트리
  4. 04-cmaf.md — 두 표준의 segment 통합
  5. 05-low-latency.md — 지연 30s → 2s
  6. 06-segmentation-and-manifest.md — segment 길이와 GOP 정렬
  7. 07-drm.md — 누가 키를 풀어주는가
  8. 08-player-engines.md — 누가 그 모두를 처리하는가

→ 다음 챕터(07-pipeline-theory): 이 모든 것을 자동 생성하는 변환 파이프라인.