04. HLS Protocol: HTTP Live Streaming у Деталях
04. HLS Protocol: HTTP Live Streaming у Деталях
1. Вступ та Контекст
Проблема: Як YouTube плавно перемикає якість відео?
Уявіть ситуацію: ви дивитесь відео на YouTube у Full HD (1080p). Раптом ваш Wi-Fi погіршується (хтось почав завантажувати великий файл). Відео не зависає! Воно автоматично перемикається на 720p, потім на 480p. Коли інтернет знову стабілізується — якість повертається до 1080p. Як це працює?
Це магія HLS (HTTP Live Streaming) — протоколу, який змінив індустрію стрімінгу назавжди.
Історична еволюція: Від RTMP до HLS
2000-ті роки: Епоха RTMP (Flash)
У часи Flash Player відео стрімилось через RTMP (Real-Time Messaging Protocol). Це був бінарний протокол, який працював через порт 1935.
Проблеми RTMP:
- Фаєрволи блокували порт 1935 (дозволений тільки HTTP 80/443)
- Не було адаптивної якості (bitrate фіксований)
- Потрібен спеціальний медіа-сервер (Adobe Media Server, Wowza)
- Flash Player помер у 2020 році
2009 рік: Apple винаходить HLS
Apple хотіла стрімити відео на iPhone 3GS. Проблема: Flash не працював на iOS. Рішення: створити протокол, який:
- Використовує звичайний HTTP (порт 80/443) — проходить крізь будь-які фаєрволи
- Працює через CDN (Content Delivery Network) — масштабується на мільйони користувачів
- Підтримує ABR (Adaptive Bitrate) — автоматично змінює якість
Результат: У червні 2009 року Apple представила HLS разом з iPhone 3GS.
2010-ті: HLS стає стандартом
- 2011: YouTube переходить з Flash на HTML5 + HLS
- 2017: RFC 8216 (офіційна специфікація HLS)
- 2020: Flash офіційно помер. HLS переміг
Чому HLS переміг?
🌐 Працює через HTTP
📊 Adaptive Bitrate
🚀 CDN-сумісність
📱 Нативний для Apple
<video>.Сценарії використання
Приклади: Twitch, YouTube Live, спортивні трансляції
Особливості:
- Playlist постійно оновлюється (кожні 2-10 секунд)
- Затримка (latency): 10-30 секунд (звичайний HLS), 2-6 секунд (Low Latency HLS)
- Сегменти видаляються після певного часу (sliding window)
Use case:
Стрімер грає в CS:GO → OBS кодує в HLS → CDN роздає 100,000 глядачам
Приклади: Netflix, Amazon Prime, YouTube (записані відео)
Особливості:
- Playlist статичний (створений один раз)
- Сегменти зберігаються назавжди (доки не видалять відео)
- Можна шукати (seek) по всьому відео
Use case:
Користувач вибирає серіал → CDN віддає готовий HLS → перегляд без буферизації
Приклади: IPTV-сервіси, спортивні трансляції з перемоткою
Особливості:
- Live стрім, але з можливістю перемотати назад
- Playlist містить старі сегменти (наприклад, останні 2 години)
- Використовується
EXT-X-PLAYLIST-TYPE:EVENT
Use case:
Футбольний матч live → глядач запізнився → перемотує на початок → дивиться з затримкою
Передумови (Prerequisites)
Перед вивченням HLS корисно розуміти:
- HTTP/HTTPS: Методи GET, коди відповідей (200, 404), заголовки (Content-Type, Cache-Control)
- Відео-кодеки: H.264/AVC, H.265/HEVC (див. попередню главу)
- Аудіо-кодеки: AAC, MP3
- Контейнери: MP4, MPEG-TS (див. анатомію відео)
<img src="photo.jpg">), ви легко зрозумієте HLS — це той самий принцип, але для відео.2. Фундаментальні Концепції
Що таке HLS? (Formal Definition)
HLS (HTTP Live Streaming) — це протокол потокової передачі медіа-контенту через HTTP, розроблений компанією Apple. Він базується на архітектурі сегментованого стрімінгу та адаптивного вибору бітрейту.
Ключові поняття:
- Playlist (Manifest): Текстовий файл у форматі M3U8, який містить метаінформацію про доступні потоки та сегменти
- Segment: Короткий фрагмент відео (зазвичай 2-10 секунд), закодований у контейнер MPEG-TS (.ts) або fMP4 (.m4s)
- Adaptive Bitrate (ABR): Механізм динамічного вибору якості потоку залежно від пропускної здатності мережі клієнта
Архітектура HLS: Огляд
Пояснення потоку:
- Encoder отримує вихідне відео і кодує його в кілька варіантів якості (1080p, 720p, 480p, 360p)
- Origin Server зберігає всі сегменти та плейлісти
- CDN кешує контент і роздає користувачам по всьому світу
- Player завантажує плейлісти, визначає швидкість інтернету і вибирає оптимальну якість
Основні компоненти HLS
- Приклад імені:
master.m3u8,index.m3u8,playlist.m3u8
- Приклад імені:
1080p/playlist.m3u8,720p/playlist.m3u8
- Формати:
.ts— MPEG-2 Transport Stream (класичний).m4s— Fragmented MP4 (сучасний, CMAF-сумісний)
Аналогія 1: Книга з перекладами
Уявіть дуже товсту книгу (фільм на 2 години):
Крок 1: Обираєте мову (Master Playlist)
Ви беріть книгу у руки і бачите на обкладинці:
"Ця книга доступна мовами:
- Українська (сторінка 1)
- English (сторінка 500)
- Deutsch (сторінка 1000)"
Це Master Playlist — він каже, які варіанти доступні.
Крок 2: Знаходите зміст (Media Playlist)
Ви відкриваєте сторінку 1 (українська версія) і бачите зміст:
Розділ 1: сторінки 10-25
Розділ 2: сторінки 26-40
Розділ 3: сторінки 41-55
Це Media Playlist — список конкретних фрагментів.
Крок 3: Читаєте по частинах (Segments)
Ви не читаєте всю книгу за раз. Ви читаєте Розділ 1 (15 сторінок), потім Розділ 2 (14 сторінок), і так далі.
Кожний розділ — це Segment (фрагмент відео).
Аналогія 2: Ресторан з меню
📋 Головне меню
Master Playlist
Ви заходите в ресторан і отримуєте головне меню:
Меню:
- Англійською (сторінка 2)
- Французькою (сторінка 10)
- Китайською (сторінка 20)
::card{title="🍕 Розділ "Піца"" icon="i-lucide-pizza"} Media Playlist
Ви обираєте англійську версію і відкриваєте розділ "Піца":
Піца:
1. Маргарита
2. Пепероні
3. Чотири сири
::
🍽️ Конкретна страва
Segment
Ви обираєте "Маргарита" і отримуєте конкретну порцію їжі. Це і є сегмент — те, що ви реально "споживаєте".
3. Архітектура та Механіка
3.1. Анатомія Master Playlist
Master Playlist — це текстовий файл UTF-8 з розширенням .m3u8. Формат базується на M3U (MP3 URL), але з додатковими тегами для HLS.
Приклад мінімального Master Playlist:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-STREAM-INF:BANDWIDTH=800000,RESOLUTION=640x360
360p/playlist.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1400000,RESOLUTION=1280x720
720p/playlist.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=5000000,RESOLUTION=1920x1080
1080p/playlist.m3u8
Розбір кожного рядка:
- BANDWIDTH (обов'язково): Пікова швидкість у бітах/сек (800000 = 800 Kbps)
- RESOLUTION: Роздільна здатність відео (ширина×висота)
- CODECS: Кодеки аудіо і відео (опціонально, але рекомендовано)
- FRAME-RATE: Частота кадрів (опціонально)
Приклад розширеного Master Playlist:
#EXTM3U
#EXT-X-VERSION:6
# Варіант 1: 1080p 60fps (High Quality)
#EXT-X-STREAM-INF:BANDWIDTH=7800000,AVERAGE-BANDWIDTH=7200000,RESOLUTION=1920x1080,FRAME-RATE=60.000,CODECS="avc1.640028,mp4a.40.2",AUDIO="audio-high"
1080p60/playlist.m3u8
# Варіант 2: 1080p 30fps (Standard)
#EXT-X-STREAM-INF:BANDWIDTH=5000000,AVERAGE-BANDWIDTH=4500000,RESOLUTION=1920x1080,FRAME-RATE=30.000,CODECS="avc1.64001f,mp4a.40.2",AUDIO="audio-high"
1080p30/playlist.m3u8
# Варіант 3: 720p (Medium Quality)
#EXT-X-STREAM-INF:BANDWIDTH=2800000,AVERAGE-BANDWIDTH=2500000,RESOLUTION=1280x720,FRAME-RATE=30.000,CODECS="avc1.64001f,mp4a.40.2",AUDIO="audio-medium"
720p/playlist.m3u8
# Варіант 4: 480p (Low Quality)
#EXT-X-STREAM-INF:BANDWIDTH=1400000,AVERAGE-BANDWIDTH=1200000,RESOLUTION=854x480,FRAME-RATE=30.000,CODECS="avc1.64001e,mp4a.40.2",AUDIO="audio-low"
480p/playlist.m3u8
# Варіант 5: 360p (Mobile)
#EXT-X-STREAM-INF:BANDWIDTH=800000,AVERAGE-BANDWIDTH=700000,RESOLUTION=640x360,FRAME-RATE=30.000,CODECS="avc1.64001e,mp4a.40.2",AUDIO="audio-low"
360p/playlist.m3u8
# Альтернативні аудіо-треки
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio-high",NAME="English",LANGUAGE="en",AUTOSELECT=YES,DEFAULT=YES,URI="audio/en/playlist.m3u8"
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio-high",NAME="Ukrainian",LANGUAGE="uk",AUTOSELECT=NO,DEFAULT=NO,URI="audio/uk/playlist.m3u8"
BANDWIDTH — це пікова швидкість (максимум). AVERAGE-BANDWIDTH — це середня швидкість. Плеєр використовує ці значення для вибору якості.Практичний приклад:- У користувача швидкість 6 Mbps
- Плеєр НЕ обере варіант 1080p60 (7.8 Mbps), бо недостатньо запасу
- Плеєр обере 1080p30 (5 Mbps) — є запас 1 Mbps для стабільності
3.2. Анатомія Media Playlist
Media Playlist містить список конкретних сегментів для відтворення.
Приклад Media Playlist (VOD):
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:10
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PLAYLIST-TYPE:VOD
#EXTINF:9.009,
segment_00001.ts
#EXTINF:9.009,
segment_00002.ts
#EXTINF:9.009,
segment_00003.ts
#EXTINF:9.009,
segment_00004.ts
#EXTINF:9.009,
segment_00005.ts
#EXT-X-ENDLIST
Розбір тегів:
TARGETDURATION=10, плеєр буферує мінімум 30 секунд (3 сегменти).#EXT-X-MEDIA-SEQUENCE:1523 ← перший сегмент має номер 1523
segment_01523.ts
segment_01524.ts
segment_01525.ts
- VOD — статичний контент (кінець файлу позначений
#EXT-X-ENDLIST) - EVENT — live з можливістю перемотування (DVR)
- Формат:
#EXTINF:<duration>,[description]
#EXTINF:10.500,This is chapter 1
segment_001.ts
- Якщо тег відсутній → це live стрім, плеєр продовжує оновлювати плейліст кожні
TARGETDURATION/2секунд.
Приклад Media Playlist (Live):
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:6
#EXT-X-MEDIA-SEQUENCE:4521
#EXTINF:6.000,
segment_04521.ts
#EXTINF:6.000,
segment_04522.ts
#EXTINF:6.000,
segment_04523.ts
#EXTINF:6.000,
segment_04524.ts
#EXTINF:6.000,
segment_04525.ts
#EXT-X-ENDLIST. Плеєр періодично оновлює цей файл (кожні 3 секунди, якщо TARGETDURATION=6), щоб отримати нові сегменти.3.3. Формати сегментів: MPEG-TS vs fMP4
MPEG-2 Transport Stream — класичний контейнер для HLS.
Структура:
┌─────────────┬─────────────┬─────────────┬─────────────┐
│ TS Packet │ TS Packet │ TS Packet │ TS Packet │
│ (188 bytes) │ (188 bytes) │ (188 bytes) │ (188 bytes) │
└─────────────┴─────────────┴─────────────┴─────────────┘
Кожен пакет:
- Header (4 bytes): Sync byte (0x47), PID (ідентифікатор потоку), counters
- Payload (184 bytes): Аудіо, відео або метадані
Переваги:
- ✅ Дуже простий формат
- ✅ Можна склеїти файли командою
cat seg1.ts seg2.ts > full.ts - ✅ Працює навіть якщо частина файлу пошкоджена (error resilience)
Недоліки:
- ❌ Багато overhead (заголовки займають ~10-15% розміру)
- ❌ Не можна використовувати для DASH (потрібен окремий набір сегментів)
Fragmented MP4 — сучасний контейнер, заснований на ISO Base Media File Format.
Структура:
init.mp4 (Header: кодеки, роздільна здатність, sample rate)
↓
segment_001.m4s (тільки відео/аудіо дані)
segment_002.m4s
segment_003.m4s
Переваги:
- ✅ Більш ефективний (менше overhead, економія 10-15% розміру)
- ✅ CMAF-сумісний: один і той же сегмент для HLS і DASH (економія місця на сервері в 2 рази!)
- ✅ Підтримка сучасних кодеків (AV1, HEVC) краща
Недоліки:
- ❌ Потрібен
init.mp4(плеєр спершу завантажує header, потім сегменти) - ❌ Складніша структура (не можна просто склеїти файли)
- Як створити:
ffmpeg -i input.mp4 \
-c:v libx264 -c:a aac \
-f hls -hls_segment_type fmp4 \
-hls_playlist_type vod \
output.m3u8
3.4. Як працює HLS під капотом
Пояснення кроків:
1. Завантаження Master Playlist
Плеєр завжди починає з завантаження master.m3u8. Якщо файл закешований на CDN — відповідь приходить за 50-100 мс.
2. Вибір початкової якості
Плеєр не знає швидкості інтернету на старті. Тому обирає середню якість (зазвичай 720p або 480p). Агресивні плеєри (YouTube) починають з найвищої якості.
3. Завантаження Media Playlist
Обравши якість (наприклад, 720p), плеєр завантажує /720p/playlist.m3u8 — список конкретних сегментів.
4. Початкова буферизація
Плеєр завантажує 3-5 сегментів (30-50 секунд відео) в буфер перед початком відтворення. Це захист від мікро-зависань.
5. Паралельне завантаження та відтворення
Поки програє перший сегмент, плеєр вже завантажує 4-й, 5-й сегменти. Це конвеєр (pipelining).
6. Вимірювання швидкості (Bandwidth Detection)
При завантаженні кожного сегмента плеєр вимірює:
Швидкість = (Розмір сегмента в бітах) / (Час завантаження)
Приклад:
- Сегмент: 2 MB = 16 Мбіт
- Час завантаження: 4 секунди
- Швидкість: 16 / 4 = 4 Mbps
7. Рішення про переключення
Кожні 3-5 сегментів плеєр аналізує:
Якщо (Середня швидкість > Поточний bitrate × 1.2):
Спробувати вищу якість
Якщо (Середня швидкість < Поточний bitrate × 0.8):
ТЕРМІНОВО знизити якість
8. Seamless Switch (безшовне переключення)
Плеєр НЕ перезавантажується. Він просто:
- Завантажує playlist нової якості (
/480p/playlist.m3u8) - Знаходить сегмент з такою самою часовою міткою (timestamp)
- Продовжує відтворення з нового потоку
Користувач бачить зміну якості, але без паузи.
3.5. Adaptive Bitrate (ABR): Серце HLS
ABR — це алгоритм автоматичного вибору якості. Без нього HLS був би звичайним завантаженням відео.
Як працює ABR
Приклад реального ABR-алгоритму (спрощений)
// Це псевдокод, що показує логіку hls.js
class ABRController {
constructor() {
this.bandwidthHistory = [] // Історія вимірювань швидкості
this.currentLevel = 2 // Поточна якість (0=lowest, 4=highest)
}
// Викликається після завантаження кожного сегмента
onSegmentLoaded(segmentSize, downloadTime) {
// 1. Виміряти швидкість
const bandwidth = (segmentSize * 8) / downloadTime // біти/сек
this.bandwidthHistory.push(bandwidth)
// Тримаємо тільки останні 5 вимірювань
if (this.bandwidthHistory.length > 5) {
this.bandwidthHistory.shift()
}
// 2. Розрахувати середню швидкість (Exponential Moving Average)
const avgBandwidth = this.calculateEMA()
// 3. Доступні якості (bitrate'и в бітах/сек)
const levels = [
{ bitrate: 500000, name: '360p' }, // 0
{ bitrate: 1000000, name: '480p' }, // 1
{ bitrate: 2500000, name: '720p' }, // 2
{ bitrate: 5000000, name: '1080p' }, // 3
{ bitrate: 8000000, name: '1080p60' }, // 4
]
// 4. Знайти оптимальну якість з запасом 20%
const targetBandwidth = avgBandwidth * 0.8 // Запас 20%
let newLevel = 0
for (let i = levels.length - 1; i >= 0; i--) {
if (levels[i].bitrate <= targetBandwidth) {
newLevel = i
break
}
}
// 5. Антифлікер: не змінювати якість занадто часто
if (Math.abs(newLevel - this.currentLevel) === 1) {
// Якщо різниця тільки 1 рівень — чекаємо ще 2 сегменти
return
}
// 6. Негайне зниження якості при проблемах
const bufferLength = this.getBufferLength()
if (bufferLength < 10) {
// Менше 10 секунд у буфері
console.warn('⚠️ Low buffer! Switching to lower quality')
newLevel = Math.max(0, this.currentLevel - 2)
}
// 7. Переключення
if (newLevel !== this.currentLevel) {
console.log(`📊 Quality switch: ${levels[this.currentLevel].name} → ${levels[newLevel].name}`)
this.currentLevel = newLevel
this.switchToLevel(newLevel)
}
}
calculateEMA() {
// Exponential Moving Average: новіші значення важать більше
let ema = this.bandwidthHistory[0]
const alpha = 0.3 // Sensitivity factor
for (let i = 1; i < this.bandwidthHistory.length; i++) {
ema = alpha * this.bandwidthHistory[i] + (1 - alpha) * ema
}
return ema
}
}
6.2. Performance Optimization (продовження)
🚀 Preloading
Браузер може завантажити перші сегменти ДО того, як користувач натисне Play.
<link rel="preload" as="fetch" href="https://cdn.example.com/master.m3u8" crossorigin />
<link rel="preload" as="fetch" href="https://cdn.example.com/720p/segment_000.ts" crossorigin />
Економія: 500-800ms швидше старт відтворення.
📦 Segment Size Tuning
Правило: Розмір сегмента = Bitrate × Duration
Приклад:
- Bitrate: 2.5 Mbps = 2,500,000 bits/sec
- Duration: 6 секунд
- Розмір: 2,500,000 × 6 ÷ 8 = 1.875 MB
Оптимальні значення:
- Mobile (3G/4G): 1-2 MB (4-6 сек по 2 Mbps)
- Desktop (Wi-Fi): 3-5 MB (6-10 сек по 5 Mbps)
🌐 CDN Caching Headers
Правильні Cache-Control headers економлять bandwidth:
# Master Playlist: короткий TTL (для Live)
location ~ master\.m3u8$ {
add_header Cache-Control "public, max-age=5, stale-while-revalidate=10";
}
# Media Playlist: середній TTL
location ~ \.m3u8$ {
add_header Cache-Control "public, max-age=10, stale-while-revalidate=30";
}
# Segments: довгий TTL (immutable)
location ~ \.(ts|m4s)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
}
⚡ HTTP/2 Server Push
Сервер може "проштовхнути" сегменти разом з плейлістом:
http2_push /720p/segment_000.ts;
http2_push /720p/segment_001.ts;
Економія: 1 RTT (Round Trip Time) на кожен сегмент.
6.3. Low Latency HLS (LL-HLS)
Traditional HLS latency: 20-40 секунд
LL-HLS latency: 2-6 секунд (як у WebRTC!)
Ключові зміни в LL-HLS:
#EXT-X-PART:DURATION=0.500,URI="segment_001_part_0.m4s"
#EXT-X-PART:DURATION=0.500,URI="segment_001_part_1.m4s"
#EXT-X-PART:DURATION=0.500,URI="segment_001_part_2.m4s"
- HTTP/2 (або HTTP/3)
- Спеціальний origin server (Apple Unified Streaming, Wowza, AWS MediaLive)
- iOS 14+ / macOS 11+ для нативної підтримки
- hls.js v1.1.0+ для інших браузерів
6.4. Demos та Експерименти
Demo 1: Ручний HLS Плеєр (Shell Script)
Напишемо плеєр на Bash, який завантажує та програє HLS вручну:
#!/bin/bash
HLS_URL="https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8"
OUTPUT_DIR="hls_download"
mkdir -p $OUTPUT_DIR
cd $OUTPUT_DIR
echo "📥 Step 1: Download Master Playlist"
curl -s "$HLS_URL" > master.m3u8
cat master.m3u8
echo -e "\n📊 Step 2: Choose quality (picking first variant)"
VARIANT_URL=$(grep -v '^#' master.m3u8 | head -1)
BASE_HLS_URL="https://test-streams.mux.dev/x36xhzz"
FULL_VARIANT_URL="$BASE_HLS_URL/$VARIANT_URL"
echo "Selected: $FULL_VARIANT_URL"
echo -e "\n📥 Step 3: Download Media Playlist"
curl -s "$FULL_VARIANT_URL" > playlist.m3u8
cat playlist.m3u8
echo -e "\n🎬 Step 4: Download first 5 segments"
BASE_URL=$(dirname "$FULL_VARIANT_URL")
SEGMENTS=$(grep -v '^#' playlist.m3u8 | head -5)
i=1
for segment in $SEGMENTS; do
echo "Downloading segment $i: $segment"
curl -s "$BASE_URL/$segment" > "segment_$i.m4s"
i=$((i+1))
done
echo -e "\n✅ Done! Downloaded 5 segments"
ls -lh *.m4s
echo -e "\n▶️ Step 5: Play (requires ffplay)"
# Concatenate segments and play
cat segment_*.m4s > combined.m4s
ffplay combined.m4s
# Or open in default video player
# open combined.m4s # macOS
# xdg-open combined.m4s # Linux
Запуск:
chmod +x hls_manual_player.sh
./hls_manual_player.sh
Що ви побачите:
📥 Step 1: Download Master Playlist
#EXTM3U
#EXT-X-VERSION:6
#EXT-X-STREAM-INF:BANDWIDTH=2227464,AVERAGE-BANDWIDTH=2218327,RESOLUTION=960x540...
📊 Step 2: Choose quality (picking first variant)
Selected: https://.../v2/prog_index.m3u8
📥 Step 3: Download Media Playlist
#EXTM3U
#EXT-X-TARGETDURATION:6
#EXTINF:6.00600,...
🎬 Step 4: Download first 5 segments
Downloading segment 1: main1.m4s
Downloading segment 2: main2.m4s
...
✅ Done! Downloaded 5 segments
-rw-r--r-- 1 user staff 1.2M main1.m4s
...
Demo 2: Симуляція Live Streaming
Створимо fake live stream з існуючого відео:
#!/bin/bash
INPUT_VIDEO="movie.mp4"
OUTPUT_DIR="live_stream"
SEGMENT_DURATION=4
mkdir -p $OUTPUT_DIR
# Крок 1: Розбити відео на сегменти
echo "🔪 Segmenting video..."
ffmpeg -i "$INPUT_VIDEO" \
-c copy \
-f segment \
-segment_time $SEGMENT_DURATION \
-reset_timestamps 1 \
"$OUTPUT_DIR/segment_%05d.ts"
# Крок 2: Симулювати live додавання сегментів до playlist
echo "🔴 Starting live simulation..."
PLAYLIST="$OUTPUT_DIR/live.m3u8"
MEDIA_SEQUENCE=0
MAX_SEGMENTS=5 # Sliding window (тримаємо тільки 5 останніх сегментів)
# Ініціалізація playlist
cat > "$PLAYLIST" <<EOF
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:$SEGMENT_DURATION
#EXT-X-MEDIA-SEQUENCE:$MEDIA_SEQUENCE
EOF
# Додаємо сегменти по одному (кожні 4 секунди)
for segment in $OUTPUT_DIR/segment_*.ts; do
SEGMENT_NAME=$(basename "$segment")
echo "➕ Adding segment: $SEGMENT_NAME (sequence: $MEDIA_SEQUENCE)"
# Додаємо новий сегмент
echo "#EXTINF:$SEGMENT_DURATION.0," >> "$PLAYLIST"
echo "$SEGMENT_NAME" >> "$PLAYLIST"
# Зберігаємо тільки останні N сегментів (sliding window)
TOTAL_SEGMENTS=$(grep -c "^segment_" "$PLAYLIST")
if [ $TOTAL_SEGMENTS -gt $MAX_SEGMENTS ]; then
# Видаляємо перший сегмент з плейліста
sed -i.bak '/#EXTINF/,+1d' "$PLAYLIST" # Видаляє перші 2 рядки (#EXTINF + filename)
MEDIA_SEQUENCE=$((MEDIA_SEQUENCE + 1))
# Оновлюємо MEDIA-SEQUENCE
sed -i.bak "s/#EXT-X-MEDIA-SEQUENCE:.*/#EXT-X-MEDIA-SEQUENCE:$MEDIA_SEQUENCE/" "$PLAYLIST"
fi
echo "📄 Current playlist:"
cat "$PLAYLIST"
echo ""
# Чекаємо 4 секунди (симуляція real-time)
sleep 4
done
# Додаємо ENDLIST (трансляція закінчилась)
echo "#EXT-X-ENDLIST" >> "$PLAYLIST"
echo "✅ Live simulation ended"
Запуск:
./simulate_live.sh
Що відбувається:
- Відео розбивається на сегменти по 4 секунди
- Кожні 4 секунди новий сегмент додається до
live.m3u8 - Старі сегменти видаляються (sliding window)
MEDIA-SEQUENCEзбільшується
Результат: Ви бачите, як плейліст live stream оновлюється в real-time!
Demo 3: Візуалізація якості ABR
Створимо HTML сторінку, яка показує зміни якості в real-time:
<!DOCTYPE html>
<html>
<head>
<title>HLS ABR Visualizer</title>
<style>
body {
font-family: Arial, sans-serif;
max-width: 1200px;
margin: 0 auto;
padding: 20px;
background: #0f172a;
color: #e2e8f0;
}
video {
width: 100%;
max-width: 800px;
border-radius: 8px;
}
.stats {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
gap: 15px;
margin-top: 20px;
}
.stat-card {
background: #1e293b;
padding: 15px;
border-radius: 8px;
border-left: 4px solid #3b82f6;
}
.stat-label {
font-size: 12px;
color: #94a3b8;
text-transform: uppercase;
}
.stat-value {
font-size: 24px;
font-weight: bold;
margin-top: 5px;
}
#quality-chart {
margin-top: 30px;
height: 200px;
background: #1e293b;
border-radius: 8px;
padding: 20px;
position: relative;
}
.quality-bar {
height: 100%;
display: flex;
align-items: flex-end;
gap: 2px;
}
.bar {
flex: 1;
background: #3b82f6;
border-radius: 2px 2px 0 0;
transition: height 0.3s;
}
</style>
</head>
<body>
<h1>🎬 HLS Adaptive Bitrate Visualizer</h1>
<video id="video" controls></video>
<div class="stats">
<div class="stat-card">
<div class="stat-label">Current Quality</div>
<div class="stat-value" id="current-quality">-</div>
</div>
<div class="stat-card">
<div class="stat-label">Bandwidth</div>
<div class="stat-value" id="bandwidth">- Mbps</div>
</div>
<div class="stat-card">
<div class="stat-label">Buffer Length</div>
<div class="stat-value" id="buffer">- sec</div>
</div>
<div class="stat-card">
<div class="stat-label">Dropped Frames</div>
<div class="stat-value" id="dropped-frames">0</div>
</div>
</div>
<div id="quality-chart">
<h3>Quality History (Last 60 seconds)</h3>
<div class="quality-bar" id="quality-bars"></div>
</div>
<script src="https://cdn.jsdelivr.net/npm/hls.js@latest"></script>
<script>
const video = document.getElementById('video')
const HLS_URL = 'https://test-streams.mux.dev/x36xhzz/x36xhzz.m3u8'
const qualityHistory = []
const MAX_HISTORY = 60
if (Hls.isSupported()) {
const hls = new Hls({
debug: false,
enableWorker: true,
})
hls.loadSource(HLS_URL)
hls.attachMedia(video)
// Оновлення статистики кожну секунду
setInterval(() => {
updateStats(hls)
}, 1000)
// Відслідковування змін якості
hls.on(Hls.Events.LEVEL_SWITCHED, (event, data) => {
const level = hls.levels[data.level]
console.log(`Quality switched to: ${level.height}p`)
qualityHistory.push(level.height)
if (qualityHistory.length > MAX_HISTORY) {
qualityHistory.shift()
}
updateQualityChart()
})
hls.on(Hls.Events.MANIFEST_PARSED, () => {
video.play()
})
}
function updateStats(hls) {
const level = hls.levels[hls.currentLevel]
if (!level) return
// Current Quality
document.getElementById('current-quality').textContent = `${level.height}p`
// Bandwidth (Mbps)
const bandwidth = (hls.bandwidthEstimate / 1000000).toFixed(2)
document.getElementById('bandwidth').textContent = `${bandwidth} Mbps`
// Buffer Length
const buffer = video.buffered.length > 0 ? (video.buffered.end(0) - video.currentTime).toFixed(1) : 0
document.getElementById('buffer').textContent = `${buffer} sec`
// Dropped Frames
const dropped = video.getVideoPlaybackQuality?.().droppedVideoFrames || 0
document.getElementById('dropped-frames').textContent = dropped
}
function updateQualityChart() {
const container = document.getElementById('quality-bars')
container.innerHTML = ''
const maxQuality = Math.max(...qualityHistory)
qualityHistory.forEach((quality) => {
const bar = document.createElement('div')
bar.className = 'bar'
const heightPercent = (quality / maxQuality) * 100
bar.style.height = `${heightPercent}%`
// Колір залежно від якості
if (quality >= 1080) bar.style.background = '#10b981'
else if (quality >= 720) bar.style.background = '#3b82f6'
else if (quality >= 480) bar.style.background = '#f59e0b'
else bar.style.background = '#ef4444'
container.appendChild(bar)
})
}
</script>
</body>
</html>
Що показує демо:
- Поточну якість відео (360p / 720p / 1080p)
- Виміряну швидкість інтернету
- Довжину буфера
- Графік змін якості за останню хвилину
7. Troubleshooting та Діагностика
7.1. Типові Проблеми
Симптом:
Access to fetch at 'https://cdn.example.com/master.m3u8' from origin 'https://mysite.com'
has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present
Причина: Сервер не віддає CORS headers.
Рішення:
Додайте в Nginx конфігурацію:
location ~ \.(m3u8|ts|m4s)$ {
add_header Access-Control-Allow-Origin * always;
add_header Access-Control-Allow-Methods 'GET, OPTIONS' always;
add_header Access-Control-Allow-Headers 'Range' always;
if ($request_method = OPTIONS) {
return 204;
}
}
Можливі причини:
- Швидкість інтернету < bitrate відео
- Перевірка: Відкрийте DevTools → Network → подивіться швидкість завантаження сегментів
- Рішення: ABR має автоматично знизити якість. Якщо ні — це баг плеєра
- CDN повільний або недоступний
- Перевірка:
curl -w "@curl-format.txt" https://cdn.example.com/segment.ts - Рішення: Змініть CDN або додайте fallback origin
- Перевірка:
- Segments занадто великі
- Перевірка: Розмір сегмента > 5 MB при тривалості 6 сек = bitrate > 6.7 Mbps (дуже високий)
- Рішення: Перекодуйте з меншим bitrate або збільшіть кількість варіантів якості
Симптом: Відео перемикається між 720p і 1080p кожні 10 секунд.
Причина: Нестабільна мережа або надто агресивний ABR алгоритм.
Рішення:
// Налаштуйте hls.js для більшої стабільності
const hls = new Hls({
abrEwmaDefaultEstimate: 1000000, // Початковий estimate (1 Mbps)
abrBandWidthFactor: 0.95, // Більш консервативний вибір (за замовчуванням 0.95)
abrBandWidthUpFactor: 0.7, // Повільніше підвищення якості
})
Симптом: Live стрім зупиняється після 5-го сегмента.
Причина: Плеєр припинив оновлювати playlist (побачив #EXT-X-ENDLIST).
Діагностика:
# Перевірте, чи є ENDLIST в playlist
curl https://cdn.example.com/live/playlist.m3u8 | grep ENDLIST
Рішення:
- Видаліть
#EXT-X-ENDLISTз Live плейліста - Переконайтеся, що encoder додає нові сегменти
Причина: Несумісний кодек.
Діагностика:
# Перевірте кодеки в плейлісті
curl https://cdn.example.com/master.m3u8 | grep CODECS
# Очікуваний результат:
# CODECS="avc1.64001f,mp4a.40.2"
# ^^^^^^^^^^^ ^^^^^^^^^
# H.264 AAC
Рішення:
- Використовуйте H.264 (AVC) для відео (не VP9, не AV1 — Safari не підтримує)
- Використовуйте AAC для аудіо (не Opus — Safari не підтримує в HLS)
Симптом:
Failed to load resource: the server responded with a status of 404 (Not Found)
https://cdn.example.com/segment_045.ts
Можливі причини:
- Неправильний базовий URL
- Плейліст містить відносні шляхи, але браузер збирає URL неправильно
- Сегменти фізично відсутні
- Encoder not створив файл або видалив занадто рано
Діагностика:
# Спробуйте завантажити сегмент вручну
curl -I https://cdn.example.com/segment_045.ts
# Якщо 404 → перевірте, чи файл існує на origin
ssh user@origin-server "ls -lh /var/www/hls/segment_045.ts"
7.2. Інструменти Діагностики
🔍 Chrome DevTools Network Tab
Як використовувати:
- Відкрийте DevTools (F12)
- Вкладка Network
- Фільтр: Залишіть тільки
.m3u8та.tsфайли - Натисніть Play на відео
Що дивитись:
- Time: Час завантаження (має бути < тривалості сегмента)
- Size: Розмір файлу (має відповідати
bitrate × duration) - Status: Код відповіді (200 = OK, 404 = не знайдено, 403 = заборонено)
📊 VLC Media Info
VLC показує детальну інформацію про стрім:
- Відкрийте HLS в VLC: Media → Open Network Stream
- Вставте
https://cdn.example.com/master.m3u8 - Tools → Codec Information (Ctrl+J)
Що дивитись:
- Кодеки (має бути H.264 + AAC)
- Bitrate (реальний vs очікуваний)
- Dropped frames (якщо > 0 — проблема з decode)
⚙️ FFprobe для аналізу сегментів
# Перевірити кодеки
ffprobe -v error -show_streams segment_001.ts
# Перевірити тривалість
ffprobe -v error -show_entries format=duration segment_001.ts
# Перевірити bitrate
ffprobe -v error -show_entries format=bit_rate segment_001.ts
Очікуваний output:
duration=6.006000
bit_rate=2534567
🌐 Online HLS Validators
Сервіси для перевірки:
Вставте URL вашого master.m3u8 і отримайте звіт про помилки.
8. HLS vs Інші Протоколи
8.1. HLS vs DASH
HTTP Live Streaming від Apple
Переваги:
- ✅ Натівна підтримка iOS/macOS/Safari
- ✅ Простіший формат (M3U8 текстові плейлісти)
- ✅ Менша затримка setup (швидше стартує)
- ✅ Краща екосистема (більше CDN provider'ів підтримують)
Недоліки:
- ❌ Формат плейліста не XML (складніше парсити програмно)
- ❌ Менше опцій для DRM (тільки FairPlay для Apple)
Dynamic Adaptive Streaming over HTTP від MPEG Industry
Переваги:
- ✅ Відкритий стандарт (ISO/IEC 23009)
- ✅ Кращий DRM (Widevine, PlayReady)
- ✅ XML-based manifest (простіше валідувати)
- ✅ Більше гнучкості (підтримка Timeline, SegmentTemplate)
Недоліки:
- ❌ Safari НЕ підтримує нативно (потрібен dash.js)
- ❌ Складніший формат (MPD XML vs M3U8)
Порівняльна таблиця:
| Характеристика | HLS | DASH |
|---|---|---|
| Розробник | Apple | MPEG |
| Формат Manifest | M3U8 (текст) | MPD (XML) |
| Нативна підтримка iOS | ✅ Так | ❌ Потрібен dash.js |
| Нативна підтримка Android | ⚠️ Частково | ✅ Так (ExoPlayer) |
| Латентність (традиційна) | 20-40 сек | 10-30 сек |
| Латентність (Low Latency) | 2-6 сек (LL-HLS) | 3-5 сек (LL-DASH) |
| DRM | FairPlay | Widevine, PlayReady |
| CDN підтримка | Відмінна | Дуже добра |
| Екосистема | Більша | Менша |
- Якщо ваша аудиторія primarily iOS/Safari → HLS
- Якщо потрібен DRM (Netflix-style protection) → DASH + Widevine
- Оптимальне рішення: CMAF (Common Media Application Format) — один набір сегментів і для HLS, і для DASH!
ffmpeg -i input.mp4 \
-c:v libx264 -c:a aac \
-f hls -hls_segment_type fmp4 \ ← fMP4 (CMAF-сумісний)
output_hls.m3u8
# Той самий набір .m4s файлів можна використати для DASH
8.2. HLS vs RTMP
| HLS | RTMP | |
|---|---|---|
| Протокол | HTTP/HTTPS | TCP (порт 1935) |
| Firewall friendly | ✅ Так (порт 80/443) | ❌ Блокується фаєрволами |
| CDN | ✅ Працює | ❌ Потрібен спеціальний сервер |
| Латентність | 20-40 сек (traditional) 2-6 сек (LL-HLS) | 2-5 сек |
| Підтримка браузерів | ✅ Всі (нативно або hls.js) | ❌ Помер з Flash |
| Use case | VOD, Live, DVR | Тільки Live (більше не використовується) |
- Ingest (OBS → сервер кодування)
- Legacy systems (старі камери відеоспостереження)
9. Практичні Завдання
Завдання 1: Створити HLS стрім з MP4
Мета: Перетворити movie.mp4 на HLS з 3 варіантами якості.
Кроки:
- Встановіть FFmpeg
- Створіть bash-скрипт, який генерує три варіанти: 360p (800 Kbps), 720p (2.5 Mbps), 1080p (5 Mbps)
- Створіть Master Playlist вручну
- Запустіть локальний HTTP сервер (Python або Nginx)
- Відкрийте в браузері
Критерії успіху:
- Плеєр перемикає якість автоматично (можна побачити в DevTools)
- Відео програє без зависань
Завдання 2: Налаштувати Nginx для HLS
Мета: Створити production-ready Nginx конфігурацію.
Вимоги:
- CORS headers (щоб працював з будь-якого домену)
- Правильні Cache-Control headers (10 сек для .m3u8, 1 рік для .ts)
- Gzip compression для плейлістів
- Логування (окремий access.log для HLS)
Перевірка:
curl -I https://your-domain.com/master.m3u8 | grep Cache-Control
# Має бути: Cache-Control: public, max-age=10
Завдання 3: Діагностика проблемного стріма
Даний buggy HLS:
https://test-broken-hls.example.com/master.m3u8
Проблеми:
- Відео зависає кожні 20 секунд
- Якість не перемикається автоматично
- У Safari працює, у Chrome — ні
Завдання:
- Використайте DevTools Network tab
- Завантажте master.m3u8 та проаналізуйте
- Завантажте перший сегмент та перевірте FFprobe
- Знайдіть та опишіть мінімум 3 проблеми
Підказка: Перевірте CORS, bitrate mismatch, codec compatibility.
10. FAQ (Часті Питання)
A: Ні, HLS має затримку 2-40 секунд (залежно від режиму). Для real-time комунікації потрібен WebRTC (латентність < 500 ms).
Use case HLS: Вебінари, де зворотній зв'язок не критичний. Use case WebRTC: Zoom, Google Meet, Discord calls.
A: Залежить від трафіку:
- AWS CloudFront: $0.085/GB (перші 10 TB)
- Cloudflare: $0.04/GB (або безкоштовно в межах тарифного плану)
- Bunny CDN: $0.01/GB (найдешевший)
Приклад: 1000 глядачів, кожен дивиться 1 годину (2 Mbps якість):
1000 користувачів × 1 година × 2 Mbps = 2,000,000 Мбіт = 250 GB
Вартість: 250 GB × $0.04 = $10
A: Використовуйте комбінацію методів:
- AES-128 encryption (базовий захист)
- Signed URLs (токени з expiration time)
- DRM (FairPlay, Widevine) — для premium контенту
::
A: Так! Потрібен:
- Кодек: H.265/HEVC або AV1 (H.264 занадто великий bitrate для 4K)
- Bitrate: 15-25 Mbps для 4K 30fps, 35-50 Mbps для 4K 60fps
- Safari підтримує HEVC нативно, Chrome потрібен апаратний декодер
Приклад:
ffmpeg -i input_4k.mp4 \
-c:v libx265 -preset medium -crf 23 \
-c:a aac -b:a 192k \
-f hls -hls_time 6 \
output_4k.m3u8
A: Використайте Service Worker або IndexedDB:
// Завантажити всі сегменти наперед
async function downloadForOffline(masterUrl) {
const response = await fetch(masterUrl)
const playlist = await response.text()
// Parse playlist, get segments URLs
const segments = extractSegmentUrls(playlist)
// Download and store in IndexedDB
for (const segmentUrl of segments) {
const segmentBlob = await fetch(segmentUrl).then((r) => r.blob())
await saveToIndexedDB(segmentUrl, segmentBlob)
}
}
iOS/Android apps: Використайте нативні API (AVAssetDownloadTask для iOS).
A: Стандартна затримка HLS складається з:
Encoder (2-4 сек) +
Segmentation (6-10 сек) +
CDN propagation (1-3 сек) +
Player buffer (15-30 сек)
─────────────────────────
= 24-47 секунд
Рішення:
- Зменшіть segment duration (4 сек замість 10)
- Зменшіть player buffer (hls.js config:
maxBufferLength: 15) - Використайте LL-HLS (Low Latency HLS) — затримка 2-6 сек
::
11. Резюме та Ключові Висновки
🎯 Що таке HLS
- Протокол стрімінгу через звичайний HTTP
- Відео нарізається на маленькі сегменти (2-10 сек)
- Текстовий плейліст (.m3u8) каже плеєру, що завантажувати
- ABR автоматично обирає якість
📐 Архітектура
- Encoder створює кілька варіантів якості
- Origin зберігає сегменти
- CDN кешує та роздає
- Player вимірює швидкість та перемикає якість
⚙️ Як створити HLS
ffmpeg -i input.mp4 \
-c:v libx264 -c:a aac \
-f hls -hls_time 6 \
output.m3u8
Для багатоякісного: кодуйте кілька варіантів + створіть master.m3u8
🔐 Безпека
- AES-128 encryption (базовий захист)
- Signed URLs (token authentication)
- DRM (FairPlay / Widevine) для premium контенту
Ключові метрики HLS:
12. Подальше Навчання
- Як YouTube перемикає якість без зависань
- Чому Netflix використовує DASH, а Twitch — HLS
- Як створити власний стрімінг-сервіс