HLS 입문 모험기: 비디오를 음식 배달처럼 폰으로 배달하기
비디오가 어떻게 폰에서 끊김 없이 재생되는지 궁금하셨나요? 이 글에서는 생생한 비유를 통해 HLS 프로토콜의 핵심 원리를 기초부터 설명합니다. 슬라이싱 기술부터 M3U8 재생 목록, 그리고 실전 라이브 서버 배포까지, HLS 기술의 정수를 한 번에 마스터할 수 있는 초보자용 가이드입니다.
HLS 입문 모험기: 비디오를 음식 배달처럼 폰으로 배달하기
시작하며: 비디오 세상을 바꾼 애플의 이야기
상상해 보세요. 2007년 어느 오후, 애플의 엔지니어들은 골치 아픈 문제로 고민하고 있었습니다. “어떻게 하면 아이폰에서 비디오를 끊김 없이 재생할 수 있을까?” 당시 Flash 기술은 모바일 기기에서 끔찍한 성능을 보였고, 배터리는 구멍 난 양동이처럼 줄어들었으며, 성능은 참담했습니다. 그래서 애플은 대담한 결정을 내렸습니다. “우리가 직접 만들자!”
2년 후인 2009년, HLS(HTTP Live Streaming)가 세상에 나왔습니다. 그 핵심 아이디어는 무릎을 탁 칠 만큼 간단합니다. “한 번에 거대한 파일을 보내는 건 너무 힘드니, 작게 잘라서 음식 배달처럼 하나씩 보내자!”
이 단순해 보이는 아이디어는 인터넷 비디오의 판도를 완전히 바꿨습니다. 오늘날 여러분이 틱톡을 넘기든, 유튜브를 보든, 넷플릭스 시리즈를 정주행하든, 그 뒤에는 HLS가 묵묵히 일하고 있을 가능성이 큽니다.
핵심 마법: 비디오를 “배달 세트”로 만들기
왼쪽은 전통적인 “대형 트럭 통운송”, 오른쪽은 HLS의 “택배 분할 배송”
슬라이싱의 기술
먼저 이야기를 하나 들려드릴게요. 이사를 가야 해서 거대한 냉장고를 옮겨야 한다고 가정해 봅시다. 두 가지 선택지가 있습니다.
플랜 A: 초대형 트럭을 불러서 냉장고 전체를 한 번에 옮긴다. 웅장하게 들리죠? 하지만 문제는:
- 그렇게 큰 트럭을 찾으려면 한참 기다려야 합니다.
- 가는 길에 차가 막히면 모든 게 끝장입니다.
- 도중에 문제가 생기면 냉장고 전체를 못 쓰게 됩니다.
플랜 B: 냉장고를 여러 부품으로 분해해서, 각 부품을 일반 택배로 나눠서 보낸다. 이렇게 하면:
- 즉시 배송을 시작할 수 있습니다.
- 어떤 소포가 지연되어도 나머지는 정상적으로 배송됩니다.
- 도로 상황에 따라 언제든지 배송 방식을 조정할 수 있습니다.
HLS가 선택한 것은 바로 플랜 B입니다! HLS는 전체 비디오 파일을 작은 조각(보통 2-10초 단위)으로 자르고, 각 조각을 독립적인 “택배 상자”처럼 취급합니다. 이 조각들은 보통 .ts 파일(MPEG-2 Transport Stream)이거나 더 현대적인 .mp4 조각입니다.
메뉴판: 신비한 M3U8
잘린 조각만으로는 충분하지 않습니다. 플레이어에게 이 조각들의 순서를 알려줘야겠죠? 이것이 바로 **M3U8 재생 목록(Playlist)**의 역할입니다. 마치 음식 배달 메뉴판처럼 다음과 같은 내용을 자세히 나열합니다.
- 어떤 “메뉴”(비디오 조각)가 있는지
- 각 “메뉴”가 어디에 있는지(URL 주소)
- 어떤 순서로 “서빙”할지(재생 순서)
- 각 “메뉴”를 “먹는” 데 얼마나 걸리는지(길이)
아주 간단한 M3U8 예시를 보겠습니다.
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:10
#EXTINF:9.5,
segment001.ts
#EXTINF:9.5,
segment002.ts
#EXTINF:9.5,
segment003.ts
#EXT-X-ENDLIST이것은 마치 메뉴판에 “첫 번째 요리 9.5초, 두 번째 요리 9.5초, 세 번째 요리 9.5초, 끝났습니다. 맛있게 드세요!”라고 써 있는 것과 같습니다.
적응형 비트레이트: 지능형 기어 변속
여기 정말 멋진 기능이 있습니다! HLS는 서로 다른 화질의 비디오 여러 벌을 동시에 준비할 수 있습니다. 마치 식당에서 소, 중, 대 사이즈 세트 메뉴를 준비하는 것처럼요.
인터넷 속도가 빠를 때는 플레이어가 자동으로 고화질 버전으로 전환하고, 속도가 느려지면 표준 화질로 낮춰서 끊김을 방지합니다. 전체 과정이 매우 매끄러워서 여러분은 전환되는 것을 거의 느끼지 못할 겁니다!
이것이 바로 **마스터 재생 목록(Master Playlist)**의 역할이며, 이렇게 생겼습니다.
#EXTM3U
#EXT-X-STREAM-INF:BANDWIDTH=800000,RESOLUTION=640x360
low/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1400000,RESOLUTION=842x480
mid/index.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2800000,RESOLUTION=1280x720
high/index.m3u8플레이어는 이 “총 메뉴판”을 보고 여러분의 인터넷 속도와 화면 크기에 맞춰 가장 적절한 “하위 메뉴”를 똑똑하게 선택합니다.
VOD vs 라이브: 녹화 방송과 생방송의 차이
VOD는 편의점 도시락(언제든 가져갈 수 있음), 라이브는 즉석 요리(실시간 조리)
VOD(주문형 비디오): 미리 만든 도시락
편의점에서 도시락을 산다고 상상해 보세요. 이 도시락은:
- ✅ 이미 다 만들어져 있어서 언제든 살 수 있습니다.
- ✅ 내용물이 고정되어 있어 변하지 않습니다.
- ✅ 먹고 싶을 때 언제든 먹을 수 있습니다.
- ✅ 마지막에 삶은 달걀이 있는지 보려고 빨리 감기 할 수 있습니다.
VOD가 바로 이렇습니다: 비디오는 이미 잘라져 있고, M3U8 목록도 생성 완료되어 서버에 누워서 여러분을 기다립니다. 재생 목록 끝에는 #EXT-X-ENDLIST 태그가 있어 플레이어에게 “형제여, 비디오는 여기서 끝이야, 후속편은 없어.”라고 알려줍니다.
라이브(Live): 즉석 요리
이제 셰프가 현장에서 요리하는 것을 보고 있다고 상상해 보세요.
- 🔴 셰프는 요리 중이고, 여러분은 보고 있습니다.
- 🔴 다음 요리는 아직 안 나왔습니다.
- 🔴 셰프의 속도를 따라가야 합니다.
- 🔴 놓치면 끝입니다(다시 보기가 없다면).
라이브 방송이 바로 이 느낌입니다! 핵심 차이점은 다음과 같습니다.
- M3U8이 계속 업데이트됩니다: 몇 초마다 서버는 새로 잘린 조각을 재생 목록에 추가합니다.
- 종료 태그가 없습니다: 라이브가 진행 중이니 당연히 “완결”이 없겠죠.
- 슬라이딩 윈도우: 재생 목록은 최근 몇 개의 조각(예: 최근 6개)만 유지하고, 너무 오래된 조각은 제거합니다.
- 플레이어가 계속 새로고침해야 합니다: 몇 초마다 서버에 가서 최신 M3U8을 가져와 새 조각이 있는지 확인합니다.
예를 들어, 라이브 중인 M3U8은 이렇게 생겼을 수 있습니다.
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:6
#EXT-X-MEDIA-SEQUENCE:12345
#EXTINF:6.0,
live_12345.ts
#EXTINF:6.0,
live_12346.ts
#EXTINF:6.0,
live_12347.ts보세요, #EXT-X-ENDLIST가 없습니다! 그리고 #EXT-X-MEDIA-SEQUENCE:12345가 있는데, 이건 “이봐, 현재 첫 번째 조각 번호는 12345야.”라고 말하는 겁니다. 다음에 플레이어가 새로고침할 때는 12346으로 시작하고, 옛날 조각은 새 조각으로 대체될 수 있습니다.
재미있는 생각 시간: 왜 라이브는 모든 조각을 유지하지 않을까요? 그러면 목록이 무한히 길어질 뿐더러, 대부분의 시청자는 “지금” 일어나는 일을 보고 싶어 하지 처음부터 보고 싶어 하지 않기 때문입니다!
HLS의 위상: 다른 고수들과의 대결
스트리밍 강호에서 HLS는 독불장군이 아닙니다. 꽤 많은 경쟁자와 형제 프로토콜이 있죠. 다른 “무림 고수”들과 비교해 봅시다.
상대 1호: MPEG-DASH(국제 표준 협객)
DASH는 누구인가?
- 풀네임: Dynamic Adaptive Streaming over HTTP
- 국제 표준 기구 MPEG가 제정한 “정통파”
- 이념은 HLS와 거의 동일: 분할 + 적응형 + HTTP
주요 차이점은?
| 특성 | HLS | MPEG-DASH |
|---|---|---|
| 출신 | 애플 집밥 | 국제 표준 정식 |
| 애플 기기 지원 | ⭐⭐⭐⭐⭐ 완벽 | ❌ 기본적으로 미지원 |
| 안드로이드 지원 | ⭐⭐⭐⭐ 훌륭 | ⭐⭐⭐⭐⭐ 완벽 |
| 재생 목록 형식 | M3U8(텍스트) | MPD(XML) |
| 조각 컨테이너 | TS 또는 fMP4 | 주로 fMP4 |
| 코덱 제한 | H.264 선호 | 코덱 자유 |
쉬운 번역: HLS는 애플의 “가문 비법”이라 아이폰/아이패드에서 물 만난 고기 같습니다. DASH는 “국제 공통 레시피”로 더 개방적이지만 애플은 사지 않습니다. 사용자가 주로 애플 기기를 쓴다면 눈 감고 HLS를 고르세요. 다양한 플랫폼을 아울러야 한다면 둘 다 준비해야 할 수도 있습니다.
상대 2호: RTMP(몰락한 왕)
RTMP의 옛 영광: Flash 시대에 RTMP(Real-Time Messaging Protocol)는 라이브계의 패왕이었습니다.
- ⚡ 지연 시간이 매우 짧음(1-3초)
- 💪 실시간성이 강함
- 🎬 Flash Player 전면 지원
하지만 시대가 변했습니다:
- 💀 Flash는 2020년에 수명을 다했습니다.
- 📱 모바일 브라우저는 전혀 지원하지 않습니다.
- 🔒 전용 스트리밍 서버가 필요합니다.
- 🚧 방화벽에 막히기 쉽습니다.
HLS vs RTMP, 마치 배달 vs 매장 식사:
| 비교 차원 | HLS(배달) | RTMP(매장 식사) |
|---|---|---|
| 지연 시간 | 10-30초(표준) 2-5초(저지연 버전) |
1-3초 |
| 커버리지 | 거의 모든 기기 | 전용 소프트웨어 필요 |
| 구축 난이도 | 간단(일반 웹 서버) | 복잡(전용 서버) |
| 네트워크 친화성 | 매우 좋음(HTTP는 다 뚫음) | 보통(차단될 수 있음) |
| 현황 | 승승장구 | 저무는 해 |
현재의 베스트 프랙티스: 스트리머는 RTMP로 서버에 **송출(Push)**하고(안정적이고 신뢰할 수 있으니까), 서버는 그걸 HLS로 바꿔서 시청자에게 **배포(Distribute)**합니다(호환성이 좋으니까). 이걸 “장점만 취한다”고 하죠!
상대 3호: WebRTC(실시간 상호작용 전문가)
WebRTC의 특기:
- 🚀 지연 시간이 무서울 정도로 짧음(수십~수백 밀리초)
- 🎤 양방향 통신 태생적 지원
- 💻 브라우저 네이티브 지원, 플러그인 불필요
- 📞 화상 회의를 위해 설계됨
HLS vs WebRTC, 마치 콘서트 중계 vs 영상 통화:
HLS가 적합한 곳:
- 한 명이 말하고 백만 명이 듣는 경우(일대다)
- 몇 초의 지연을 참을 수 있는 경우
- CDN 대규모 배포가 필요한 경우
- 예: 스포츠 경기, 콘서트, 온라인 강의 녹화
WebRTC가 적합한 곳:
- 여러 명이 서로 소통하는 경우(다대다)
- 반드시 실시간이어야 하는 경우(지연 < 1초)
- 참여 인원이 제한적인 경우
- 예: 화상 회의, 원격 진료, 라이브 방송 합방
재미있는 비유: HLS는 라디오 방송국(단방향, 넓은 커버리지), WebRTC는 전화 회의(양방향, 인원 제한).
기술 심층 탐구: HLS 내부의 비밀
자, 앞부분은 “What”(무엇)과 “Why”(왜)였습니다. 이제 “How”(어떻게)에 대해 이야기해 봅시다. 걱정 마세요, 계속 쉬운 말로 설명할게요!
인코딩 형식: 비디오의 “언어”
비디오 인코딩은 압축 파일 형식과 같습니다
친구에게 사진을 보내려는데 원본 10MB가 너무 크다고 상상해 보세요. 어떻게 하죠? 맞아요, JPEG나 WebP로 압축하죠. 비디오 인코딩도 같은 원리입니다. 거대한 원본 비디오 데이터를 작은 파일로 압축하는 거죠.
HLS에서 가장 자주 쓰는 콤비네이션은:
- 비디오 인코딩: H.264/AVC(거의 모든 기기 지원)
- 오디오 인코딩: AAC(음질 좋고 호환성 좋음)
왜 H.264를 선택할까요?
- ✅ 압축률이 높음(1시간 비디오가 1-2GB면 될 수도)
- ✅ 하드웨어 디코딩(배터리 절약, 발열 적음)
- ✅ 만국 공통(아이폰부터 안드로이드, 스마트 TV까지)
신예 H.265가 문을 두드림:
- 💪 압축률이 H.264보다 두 배 높음(같은 화질이면 용량 절반!)
- ⚠️ 하지만 호환성이 좀 떨어짐(구형 기기 미지원)
- 💰 특허료 문제도 있음
실용적인 조언: 최대 호환성을 원하면? H.264를 쓰세요. 대역폭 절약을 원하면? H.265를 시도하되, H.264 백업을 준비해 두세요.
컨테이너 형식: 비디오의 “포장 상자”
인코딩이 “어떻게 압축할까”를 해결한다면, 컨테이너는 “어떻게 담을까”를 해결합니다.
TS(Transport Stream): 클래식 베테랑
- 📦 각 작은 조각이 독립적인 상자입니다.
- 🛡️ 자체 오류 허용 능력(패킷 몇 개 잃어도 재생 가능)
- 📺 디지털 TV 기술에서 유래
- ⚖️ 하지만 오버헤드가 좀 큼(각 조각마다 완전한 헤더가 있음)
fMP4(Fragmented MP4): 신흥 인플루언서
- ✨ 더 현대적이고 효율적
- 🔗 “초기화 세그먼트”가 필요함(설명서 같은 것)
- 🤝 DASH와 호환됨(비디오 하나로 두 프로토콜 다 사용 가능)
- ⚡ 저지연 기술 지원
이미지 비유:
- TS는 발열 도시락 같습니다. 각 상자가 완결되어 있죠(그릇, 재료, 발열팩 다 있음).
- fMP4는 이케아 가구 같습니다. 먼저 설명서(초기화 세그먼트)가 있고, 그 뒤에 부품이 따로 포장(미디어 세그먼트)되어 있습니다.
M3U8의 비밀 언어
앞서 말한 M3U8 “메뉴판” 기억나시죠? 이제 이 메뉴판의 “레시피 문법”을 자세히 봅시다.
기초판 M3U8 해부:
#EXTM3U # 파일 헤더: 나 M3U8 파일이야!
#EXT-X-VERSION:3 # 프로토콜 버전 번호
#EXT-X-TARGETDURATION:10 # 최대 조각 길이는 10초를 넘지 않음
#EXT-X-MEDIA-SEQUENCE:0 # 시작 조각 번호
#EXTINF:9.9, # 첫 번째 조각: 길이 9.9초
segment0.ts # 조각 파일명
#EXTINF:9.9, # 두 번째 조각: 길이 9.9초
segment1.ts
#EXTINF:9.9, # 세 번째 조각
segment2.ts
#EXT-X-ENDLIST # 종료 태그: 후속 없음심화판: 멀티 비트레이트 마스터 재생 목록:
#EXTM3U
#EXT-X-VERSION:6
# 고화질 버전: 1920x1080, 5Mbps
#EXT-X-STREAM-INF:BANDWIDTH=5000000,RESOLUTION=1920x1080,CODECS="avc1.640028,mp4a.40.2"
high/playlist.m3u8
# 표준 화질 버전: 1280x720, 2.5Mbps
#EXT-X-STREAM-INF:BANDWIDTH=2500000,RESOLUTION=1280x720,CODECS="avc1.64001f,mp4a.40.2"
medium/playlist.m3u8
# 일반 버전: 640x360, 800Kbps
#EXT-X-STREAM-INF:BANDWIDTH=800000,RESOLUTION=640x360,CODECS="avc1.42001e,mp4a.40.2"
low/playlist.m3u8
# 오디오 전용: 64Kbps
#EXT-X-STREAM-INF:BANDWIDTH=64000,CODECS="mp4a.40.5"
audio-only/playlist.m3u8핵심 정보 해독:
BANDWIDTH: 비트레이트. 숫자가 클수록 화질이 좋지만 데이터도 많이 먹습니다.RESOLUTION: 해상도. 1920x1080이 소위 말하는 “1080p”입니다.CODECS: 코덱 정보(전문 플레이어를 위한 “식재료 성분표”).
특수 태그 대방출:
#EXT-X-KEY:METHOD=AES-128,URI="https://example.com/key.php"
# 🔐 암호화! 재생 전에 키를 가져와서 해독해야 함.
#EXT-X-DISCONTINUITY
# ⚠️ 경고: 다음 조각부터 인코딩 파라미터가 변함(예: 해상도 변경).
#EXT-X-PROGRAM-DATE-TIME:2025-12-31T14:30:00.000Z
# 📅 타임스탬프: 이 조각이 현실 세계의 어떤 시각에 해당하는지.
#EXT-X-MAP:URI="init.mp4"
# 📋 fMP4 전용: 이건 초기화 세그먼트야, 이것부터 다운로드해!프로토콜 버전 진화사
HLS는 변하지 않는 게 아닙니다. 휴대폰 OS처럼 끊임없이 업그레이드되죠.
버전 1-2(고대 시대):
- 기본적인 VOD와 라이브 기능
- 암호화 없음, 보안 우려
버전 3(성숙기):
- ➕ AES-128 암호화 추가
- ➕ 부동 소수점 길이 지원(더 정확함)
- 🎯 대부분의 간단한 애플리케이션은 이 버전이면 충분
버전 4-5(풍요기):
- 🎵 멀티 오디오 트랙 지원(중국어/영어 더빙 전환)
- 📝 자막 지원
- 🎬 I-Frame 리스트(빠른 탐색 미리보기)
버전 6-7(현대판):
- 📱 fMP4 정식 지원 추가
- 📜 RFC 8216이 표준 문서가 됨
- 🔒 더 강력한 암호화 옵션
버전 8+(미래판):
- ⚡ 저지연 HLS(LL-HLS)
- 📦 부분 세그먼트(Partial Segment)
- 🚀 지연 시간이 2-5초 수준으로 감소
버전 선택 팁: 초보자는 버전 3, fMP4나 저지연이 필요하면 버전 7+를 쓰세요.
실전 배포: HLS를 돌려보자!
이론은 끝났고, 이제 실제 조작을 해봅시다! 걱정 마세요, 하나하나 가르쳐 드릴게요.
MP4를 HLS 조각과 재생 목록으로 변환하는 FFmpeg 워크플로우
과제 1: FFmpeg로 HLS VOD 만들기
상황: movie.mp4가 있는데, 웹사이트 사용자가 볼 수 있게 HLS로 바꾸고 싶습니다.
신기 등장: FFmpeg — 오디오/비디오 처리계의 스위스 아미 나이프.
한 줄 명령어로 해결:
ffmpeg -i movie.mp4 \
-c:v libx264 -c:a aac \
-hls_time 6 \
-hls_playlist_type vod \
-hls_segment_filename "segment_%03d.ts" \
-f hls output.m3u8명령어 해독:
-i movie.mp4: 입력 파일-c:v libx264: 비디오는 H.264로 인코딩-c:a aac: 오디오는 AAC로 인코딩-hls_time 6: 각 조각은 6초-hls_playlist_type vod: 이건 VOD 파일임-hls_segment_filename: 조각 명명 규칙-f hls: 출력 형식은 HLSoutput.m3u8: 생성된 재생 목록
실행 후 얻게 되는 것:
output.m3u8 # 재생 목록
segment_000.ts # 1번째 조각
segment_001.ts # 2번째 조각
segment_002.ts # 3번째 조각
...멀티 비트레이트 버전(인터넷 속도가 다른 사용자를 위해 다른 화질 준비):
# 저화질 생성
ffmpeg -i movie.mp4 -c:v libx264 -b:v 800k -s 640x360 \
-c:a aac -b:a 96k -hls_time 6 -f hls low/stream.m3u8
# 중화질 생성
ffmpeg -i movie.mp4 -c:v libx264 -b:v 1400k -s 960x540 \
-c:a aac -b:a 128k -hls_time 6 -f hls mid/stream.m3u8
# 고화질 생성
ffmpeg -i movie.mp4 -c:v libx264 -b:v 2800k -s 1280x720 \
-c:a aac -b:a 192k -hls_time 6 -f hls high/stream.m3u8그리고 손으로 master.m3u8을 작성합니다:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-STREAM-INF:BANDWIDTH=896000,RESOLUTION=640x360
low/stream.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1528000,RESOLUTION=960x540
mid/stream.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2992000,RESOLUTION=1280x720
high/stream.m3u8과제 2: 라이브 서버 구축
상황: 라이브 서버를 만들어서 스트리머가 송출하고 시청자가 보게 하고 싶습니다.
솔루션: Nginx + RTMP 모듈
1단계: Nginx-RTMP 설치
# Ubuntu/Debian 시스템
sudo apt update
sudo apt install nginx libnginx-mod-rtmp
# 또는 직접 컴파일(생략, 인터넷에 튜토리얼 많음)2단계: Nginx 설정
/etc/nginx/nginx.conf를 편집해서 추가:
rtmp {
server {
listen 1935; # RTMP 기본 포트
chunk_size 4096;
application live {
live on;
record off;
# HLS 슬라이싱 켜기
hls on;
hls_path /var/www/hls;
hls_fragment 2s;
hls_playlist_length 10s;
}
}
}
http {
server {
listen 80;
location /hls {
types {
application/vnd.apple.mpegurl m3u8;
video/mp2t ts;
}
root /var/www;
add_header Cache-Control no-cache;
add_header Access-Control-Allow-Origin *;
}
}
}3단계: HLS 디렉터리 생성
sudo mkdir -p /var/www/hls
sudo chmod 755 /var/www/hls4단계: 서비스 시작
sudo nginx -t # 설정 테스트
sudo systemctl restart nginx5단계: 송출 및 시청
스트리머는 OBS로 여기로 송출:
rtmp://당신의서버IP:1935/live/mystream시청자는 여기로 접속:
http://당신의서버IP/hls/mystream.m3u8🎉 성공! 이제 작동하는 라이브 서버를 갖게 되었습니다!
브라우저에서 HLS 재생
문제: Chrome/Firefox는 기본적으로 HLS를 지원하지 않는데 어떡하죠?
정답: hls.js라는 신기한 도구를 쓰세요!
빠른 통합 코드:
<!DOCTYPE html>
<html>
<head>
<title>HLS 플레이어</title>
</head>
<body>
<video id="video" controls width="800"></video>
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<script>
const video = document.getElementById('video');
const videoSrc = 'https://example.com/stream.m3u8';
if (Hls.isSupported()) {
// hls.js 사용
const hls = new Hls();
hls.loadSource(videoSrc);
hls.attachMedia(video);
hls.on(Hls.Events.MANIFEST_PARSED, function() {
console.log('재생 목록 로드 완료!');
video.play();
});
hls.on(Hls.Events.ERROR, function(event, data) {
console.error('재생 오류:', data);
});
}
else if (video.canPlayType('application/vnd.apple.mpegurl')) {
// Safari 네이티브 지원
video.src = videoSrc;
}
else {
alert('당신의 브라우저는 HLS 재생을 지원하지 않습니다');
}
</script>
</body>
</html>정말 간단하죠! 코드 30줄로 크로스 플랫폼 HLS 플레이어 완성!
CDN 가속: 전 세계 시청자를 매끄럽게
왜 CDN이 필요한가?
여러분의 서버가 서울에 있는데, 미국 사용자가 라이브 방송을 본다고 상상해 보세요.
- 🐌 지연 시간 높음(물리적 거리 멂)
- 📉 대역폭 제한(국가 간 전송 비쌈)
- 💥 서버 부하 큼(모든 사람이 기계 한 대에 접속)
CDN이란 무엇인가?
CDN(콘텐츠 전송 네트워크)은 “비디오 프랜차이즈”와 같습니다.
- 🌏 전 세계에 대량의 서버 노드 배치
- 📦 여러분의 비디오를 각지에 캐시(저장)
- 🎯 사용자는 자동으로 가장 가까운 노드에 연결
- ⚡ 속도 빠르고, 지연 적고, 서버는 편안함
HLS와 CDN의 완벽한 조화:
-
세그먼트 캐시 전략:
.ts파일: 장시간 캐시(예: 1시간) ✅.m3u8파일: 캐시 안 하거나 짧게 캐시(몇 초) ⚠️
-
Nginx 캐시 설정 예시:
location ~ \.ts$ {
root /var/www/hls;
add_header Cache-Control "max-age=3600"; # 1시간 캐시
}
location ~ \.m3u8$ {
root /var/www/hls;
add_header Cache-Control "no-cache"; # 캐시 안 함
}-
주류 CDN 추천:
- Alibaba Cloud CDN, Tencent Cloud CDN(중국 내)
- Cloudflare, Akamai(국제)
- 모두 HLS 전용 최적화 옵션이 있음
돈 아끼는 팁: 초기 사용자가 적을 때는 일반 웹 서버 + Cloudflare 무료 CDN을 쓰다가, 트래픽이 커지면 전문 CDN을 쓰세요.
자주 묻는 질문 구급 상자
문제 1: 지연 시간이 너무 길어요?
증상: 라이브 지연이 30초라서 시청자가 축구 경기를 반 박자 늦게 보고, 경험이 안 좋습니다.
원인 분석:
- 각 조각 6초, 플레이어 버퍼 3조각 = 18초 기초 지연
- 네트워크 전송 + 인코딩 + CDN 배포 ≈ 다시 10-15초 추가
해결책:
플랜 A: 조각 길이 줄이기
hls_fragment 2s; # 6초에서 2초로 변경
hls_playlist_length 6s; # 3조각 유지✅ 지연 시간이 약 6-10초로 감소 ⚠️ 하지만 요청 횟수 증가, 서버 부하 증대
플랜 B: 저지연 HLS(LL-HLS) 사용
- 지원하는 인코더와 플레이어 필요
- 2-5초까지 낮출 수 있음
- 설정 복잡하지만 효과 확실함
플랜 C: 프로토콜 변경
- 반드시 1초 이내여야 한다면: WebRTC 사용
- 5초 정도면 괜찮다면: 최적화된 HLS로 충분
문제 2: 기기마다 재생 효과가 달라요
증상: 아이폰은 잘 나오는데, 안드로이드는 끊기거나 재생이 안 됩니다.
점검 리스트:
-
인코딩 형식 호환성
# 비디오 인코딩 확인 ffmpeg -i segment.ts # H.264 Main 또는 High Profile인지 확인 # 오디오가 AAC-LC인지 확인 -
Baseline Profile로 안전하게
ffmpeg -i input.mp4 \ -c:v libx264 -profile:v baseline -level 3.0 \ -c:a aac -b:a 128k \ -f hls output.m3u8압축률은 좀 떨어지지만 호환성은 최고입니다!
-
테스트 매트릭스
- ✅ iOS Safari
- ✅ Android Chrome + hls.js
- ✅ PC Chrome + hls.js
- ✅ 스마트 TV 내장 브라우저
문제 3: 암호화된 비디오 도용
상황: 유료 강의 비디오를 다른 사람이 퍼가서 자기네 사이트에 올렸습니다.
다중 방어:
1계층: Referer 검사
valid_referers none blocked yourdomain.com *.yourdomain.com;
if ($invalid_referer) {
return 403;
}2계층: AES-128 암호화
# 키 생성
openssl rand 16 > encrypt.key
# 키 정보 파일 생성
echo "https://yourdomain.com/getkey.php" > keyinfo.txt
echo "encrypt.key" >> keyinfo.txt
# FFmpeg 암호화 슬라이스
ffmpeg -i video.mp4 \
-hls_key_info_file keyinfo.txt \
-f hls encrypted.m3u83계층: 토큰 인증
# Python 예시: 토큰 포함 URL 생성
import hashlib
import time
def generate_token(file, secret, expire_time):
timestamp = int(time.time()) + expire_time
sign = hashlib.md5(f"{file}{secret}{timestamp}".encode()).hexdigest()
return f"?t={timestamp}&sign={sign}"
# URL은 이렇게 변함: /hls/video.m3u8?t=1704067200&sign=abc123...4계층: 동적 키 순환
- 사용자마다, 시청할 때마다 다른 키 사용
- 키가 주기적으로 만료됨
- 비즈니스 백엔드와 결합하여 구현
문제 4: 라이브 끊김 현상?
증상: 스트리머 송출이 중단되고 시청자 화면이 멈춥니다.
예방 조치:
-
스트리머 측 백업 송출
메인 송출: rtmp://메인서버/live/stream 백업 송출: rtmp://백업서버/live/streamOBS 같은 소프트웨어는 다중 송출을 지원합니다.
-
서버 자동 재연결
application live { live on; drop_idle_publisher 10s; # 10초간 데이터 없으면 끊기 idle_streams off; # 스트림 활성 상태 유지 } -
클라이언트 재시도 메커니즘
hls.on(Hls.Events.ERROR, function(event, data) { if (data.fatal) { switch(data.type) { case Hls.ErrorTypes.NETWORK_ERROR: console.log('네트워크 오류, 재연결 시도 중...'); hls.startLoad(); break; case Hls.ErrorTypes.MEDIA_ERROR: console.log('미디어 오류, 복구 시도 중...'); hls.recoverMediaError(); break; } } }); -
모니터링 및 알림
- 송출 상태 모니터링
- 재생 목록 업데이트 빈도 감지
- 이상 발생 시 즉시 문자/이메일 알림
문제 5: 대용량 트래픽으로 라이브 서버 다운
증상: 수천 명이 동시에 시청하자 서버 CPU가 폭발하고 심각하게 끊깁니다.
긴급 구조:
-
즉시 CDN 연동
- HLS 파일 디렉터리를 CDN에 매핑
- 시청자 트래픽은 CDN이 부담
- 원본 서버는 CDN의 오리진 요청만 처리하면 됨
-
Nginx 최적화 설정
worker_processes auto; worker_rlimit_nofile 65535; events { worker_connections 10240; use epoll; } http { sendfile on; tcp_nopush on; tcp_nodelay on; keepalive_timeout 65; # .m3u8 gzip 압축 켜기 gzip on; gzip_types application/vnd.apple.mpegurl; } -
계층형 아키텍처
스트리머 송출 → 원본 인코딩 서버 → CDN 엣지 노드 → 시청자원본 서버는 인코딩과 슬라이싱만 담당하고, 배포는 전부 CDN에게.
-
조각 크기 줄이기(화질 낮추기)
- 1080p에서 720p로
- 비트레이트 낮추기
- 일시적으로 화질을 희생해 원활함 확보
고급 팁: HLS 고수 되기
팁 1: 광고 삽입 구현
라이브 중에 광고를 넣고 싶나요? HLS에 방법이 있습니다!
#EXTM3U
#EXT-X-VERSION:3
#EXTINF:6.0,
segment1.ts
#EXTINF:6.0,
segment2.ts
# 광고 삽입 마커
#EXT-X-DISCONTINUITY
#EXTINF:15.0,
ad_1.ts
#EXT-X-DISCONTINUITY
#EXTINF:6.0,
segment3.ts#EXT-X-DISCONTINUITY는 플레이어에게 “아래 조각들은 인코딩 파라미터가 다를 수 있으니 준비해!”라고 알려줍니다.
팁 2: 다국어 오디오 트랙
시청자가 중국어/영어 더빙을 선택하게 하기:
#EXTM3U
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",NAME="Chinese",DEFAULT=YES,URI="audio_cn.m3u8"
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",NAME="English",DEFAULT=NO,URI="audio_en.m3u8"
#EXT-X-STREAM-INF:BANDWIDTH=2000000,AUDIO="audio"
video.m3u8플레이어에 언어 전환 옵션이 표시됩니다!
팁 3: 자막 지원
#EXTM3U
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="Simplified Chinese",DEFAULT=YES,URI="sub_cn.m3u8"
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="subs",NAME="English",URI="sub_en.m3u8"
#EXT-X-STREAM-INF:BANDWIDTH=2000000,SUBTITLES="subs"
video.m3u8자막도 별도의 M3U8이며, WebVTT 형식이 될 수 있습니다.
팁 4: 빠른 미리보기(I-Frame Playlist)
사용자가 진행 바를 드래그할 때 미리보기 화면 표시:
ffmpeg -i video.mp4 \
-vf "fps=1/5" \
-c:v mjpeg \
-f hls \
-hls_flags single_file \
iframes.m3u8키프레임만 포함된 목록을 생성하면 플레이어에서 매끄러운 미리보기를 구현할 수 있습니다!
미래 전망: HLS의 다음 역
저지연 HLS(LL-HLS)
애플이 내놓은 새로운 표준, 핵심 개선점:
- 📦 부분 세그먼트(Partial Segments): 2초 조각을 다시 4개의 0.5초 작은 덩어리로 나눔
- 🚀 프리로딩 힌트: 서버가 클라이언트에게 “다음 조각 곧 나온다”고 알려줌
- ⚡ HTTP/2 푸시: 서버가 능동적으로 푸시하여 클라이언트가 반복 요청할 필요 없음
효과: 지연 시간이 15-30초에서 2-5초로 감소!
CMAF 천하 통일?
Common Media Application Format은 HLS와 DASH를 통일하려고 합니다:
- 동일한 fMP4 조각
- 두 개의 재생 목록(.m3u8 및 .mpd)
- 한 번 인코딩으로 두 프로토콜 다 사용 가능
장점: 스토리지 절약, 대역폭 절약, 인코딩 비용 절약!
AI가 힘을 싣는 HLS
미래에는 다음을 볼 수 있을지도 모릅니다:
- 🤖 AI가 실시간으로 최적의 비트레이트 선택
- 🎨 AI가 저비트레이트 화질 개선
- 🔮 AI가 네트워크 지터를 예측해 미리 버퍼링
- 📊 AI가 시청자 행동을 분석해 CDN 배치 최적화
맺음말: 당신의 HLS 여행은 이제 막 시작되었습니다
여기까지 읽으신 것을 축하합니다! 이제 여러분은:
- ✅ HLS의 핵심 원리를 이해했습니다.
- ✅ HLS 서비스를 배포하는 법을 알았습니다.
- ✅ 자주 발생하는 문제를 해결할 수 있습니다.
- ✅ 고급 팁을 마스터했습니다.
다음 단계 제안:
- 직접 실습: FFmpeg로 비디오 몇 개를 변환해 보세요.
- 테스트 서버 구축: Nginx+RTMP를 돌려보세요.
- RFC 8216 문서 읽기: 각 태그를 깊이 있게 이해해 보세요.
- 커뮤니티 주목: HLS 기술은 계속 진화하고 있습니다.
마지막으로: HLS는 복잡해 보이지만 핵심 사상은 간단하고 우아합니다. 큰 문제를 작은 문제로 쪼개고, 가장 보편적인 HTTP 프로토콜을 사용해 스트리밍 전송을 해결하는 것입니다. 이런 “복잡함의 단순화” 지혜야말로 기술의 아름다움입니다.
자, 이제 여러분만의 비디오 스트리밍 애플리케이션을 만들러 가세요! 차세대 틱톡이든, 유튜브든, 개인 라이브 방송국이든, HLS는 여러분의 훌륭한 파트너가 될 것입니다. 🚀