애플이 2009년 공개한 HTTP 기반 적응형 비트레이트(ABR) 스트리밍 방식이다.
특별한 전용 프로토콜이 아니라 그냥 HTTP로 파일을받아 재생하는 구조라서 웹/모바일/미디어서버에서 널리 지원되고 지금도가장 대중적인 스트리밍 포맷 중 하나다.
HLS의 행심 아이디어는 영상 한 덩어리를 내려받는 대신,
즉, "재생목록(m3u8)을 따라가면서 segment들을 계속 가져와 재생하는 방식" 이라서
참고로 master.m3u8에는 여러 화질(variant)이 들어갈 수 있고,
플레이어는 상황에 따라 적절한 variant를 선택해 재생한다.
Safari 는 HLS를 네이티브로 재생할 수 있지만(브라우저가 m3u8을 직접 이해함)
Chrome/Edge/Firefox 는 기본 지원이 약해서 hls.js가 m3u8을 파싱하고 세그먼트를 받아 MSE(Media Source Extensions)로 video에 붙여 재생하게 된다.
<video> 에 주입해야함해당 과정을 구현해주는 라이브러리가 hls.js 이다.
라이브/스트리밍에서 HLS를 쓰는 이유는
1. 끊김을 줄이고
2. 네트워크에 맞춰 품질을 자동으로 바꾸고
3. 배포/운영을 쉽게 만들기 때문.
HLS는 "끊김 적게 + 빠른 시작 + 라이브에 최적 + CDN 운영 쉬움 + 품질/트랙 확장 쉬움" 때문에 사용한다.
그리고 그걸 브라우저에서 재생하려면 Safari는 네이티브로 되고, 나머지는 hls.js(MSE)가 필요하다.
hls.js로 재생하는 기본 패턴은 거의 고정이다.
일단, hls.js 를 설치한다.
npm i hls.js
# 또는
yarn add hls.js
pnpm add hls.js
npm i video.js
# (TS 프로젝트면 타입도 보통 같이 오지만, 필요하면 @types/video.js 확인)
하지만 hls.js 학습용 미니 프로젝트 상황에서는 굳이 설치하지 않고 순수 <video> + 커스텀 버튼으로 가서 이해하도록 하겠다.
npm i -D serve
# 또는 vite dev server로 public 폴더에 두고 테스트 가능
현재 상황에서는 해당 선택 파트를 제외하고 학습을 진행하도록 할것이다.
Hls.isSupported() (MSE 되는 브라우저인지)hls.loadSource(m3u8)hls.attachMedia(videoEl)Safari는 분기해서 네이티브로 처리한다.
import Hls from "hls.js";
function mountHls(videoEl, src) {
// Safari: native HLS
if (videoEl.canPlayType("application/vnd.apple.mpegurl")){
videoEl.src = src;
return() => {videoEl.src="";};
}
// Others: Hls.js via MSE
if (Hls.isSupported()) {
const hls = new Hls({
// 학습용 디버그.로깅하기 좋은 옵션들
enableWorker: true,
lowLatencyMode: true,
});
hls.loadSource(src);
hls.attachMedia(videoEl);
hls.on(Hls.Events.MANIFEST_PARSED, () => {
// autoplay는 브라우저 정책상 실패 가능성 있어서 사용자 제스처 필요
videoEl.play().catch(() => {});
});
return () => hls.destroy();
}
console.warn("HLS가 해당 브라우저를 지원하지 않습니다.");
return () => {};
}
HLS에 여러 화질이 있으면, hls.levels에 들어온다.
hls.currentLevel = -1hls.currentLevel = indexhls.on(Hls.Events.MANIFEST_PARSED, () => {
console.log(hls.levels.map((l, i) => ({
i,
height: l.height,
bitrate: l.bitrate,
})));
});
// ex: 720p 고정
const idx = hls.levels.findIndex(l => l.height === 720);
if (idx >= 0) hls.currentLevel = idx;
// 자동 되돌리기
hls.currentLevel = -1;
Auto + 360/720/1080 드롭다운 정도만 있어도 학습상황에서 도움이 된다.
hls.js는 에러 이벤트에서 fatal 여부를 준다
hls.startLoad() 재시도hls.recoverMediaError()destroy() 이후 새로 로드 (상단에 이미 사용한 흔적 있음)hls.on(Hls.Events.ERROR, (event, data) => {
console.log("HLS error:", data.type, data.details, "fatal:", data.fatal);
if (!data.fatal) return;
switch (data.type) {
case Hls.ErrorTypes.NETWORK_ERROR:
hls.startLoad();
break;
case Hls.ErrorTypes.MEDIA_ERROR:
hls.recoverMediaError();
break;
default:
hls.destroy();
}
})
HLS 재생이 안된다 - 대부분 이쪽에서 걸림
1) m3u8이 네트워크로 받아지나?
2) 세그먼트 요청이 이어지나?
3) CORS