Уявіть: це 2010 рік. У світі стрімінгу хаос:
Результат: Розробники змушені кодувати відео три рази для різних платформ.
Рішення: MPEG Industry Forum створив DASH (Dynamic Adaptive Streaming over HTTP) — відкритий стандарт ISO/IEC 23009-1.
Якщо HLS — це "зміст книги" (лінійний список сегментів), то DASH — це XML-маніфест, схожий на детальну інструкцію Lego:
Переваги такого підходу:
Маніфест у DASH називається .mpd (Media Presentation Description). Це XML файл.
Чому XML, а не текст як у HLS?
✅ Переваги XML
❌ Недоліки XML
Розшифровка:
<?xml version="1.0" encoding="UTF-8"?>
<MPD xmlns="urn:mpeg:dash:schema:mpd:2011"
type="static"
mediaPresentationDuration="PT10M30S"
minBufferTime="PT2S">
<Period id="main" duration="PT10M30S">
<!-- Video Adaptation Set -->
<AdaptationSet
id="video"
mimeType="video/mp4"
codecs="avc1.4d401f"
width="1920"
height="1080"
frameRate="30">
<!-- 1080p Representation -->
<Representation
id="video_1080p"
bandwidth="5000000"
width="1920"
height="1080">
<BaseURL>video/1080p/</BaseURL>
<SegmentTemplate
media="segment_$Number$.m4s"
initialization="init.mp4"
timescale="90000"
duration="540000"
startNumber="1"/>
</Representation>
<!-- 720p Representation -->
<Representation
id="video_720p"
bandwidth="2500000"
width="1280"
height="720">
<BaseURL>video/720p/</BaseURL>
<SegmentTemplate
media="segment_$Number$.m4s"
initialization="init.mp4"
timescale="90000"
duration="540000"
startNumber="1"/>
</Representation>
<!-- 480p Representation -->
<Representation
id="video_480p"
bandwidth="1000000"
width="854"
height="480">
<BaseURL>video/480p/</BaseURL>
<SegmentTemplate
media="segment_$Number$.m4s"
initialization="init.mp4"
timescale="90000"
duration="540000"
startNumber="1"/>
</Representation>
</AdaptationSet>
<!-- Audio Adaptation Set (English) -->
<AdaptationSet
id="audio_en"
mimeType="audio/mp4"
codecs="mp4a.40.2"
lang="en"
audioSamplingRate="48000">
<Representation
id="audio_en_128k"
bandwidth="128000">
<BaseURL>audio/en/</BaseURL>
<SegmentTemplate
media="segment_$Number$.m4s"
initialization="init.mp4"
timescale="48000"
duration="288000"
startNumber="1"/>
</Representation>
</AdaptationSet>
<!-- Audio Adaptation Set (Spanish) -->
<AdaptationSet
id="audio_es"
mimeType="audio/mp4"
codecs="mp4a.40.2"
lang="es"
audioSamplingRate="48000">
<Representation
id="audio_es_128k"
bandwidth="128000">
<BaseURL>audio/es/</BaseURL>
<SegmentTemplate
media="segment_$Number$.m4s"
initialization="init.mp4"
timescale="48000"
duration="288000"
startNumber="1"/>
</Representation>
</AdaptationSet>
</Period>
</MPD>
Пояснення ключових атрибутів:
static: VOD (весь контент доступний одразу)dynamic: Live (сегменти додаються в реальному часі)<SegmentTemplate
media="segment_$Number$.m4s"
initialization="init.mp4"
timescale="90000"
duration="540000"
startNumber="1"/>
Як плеєр будує URL:
https://cdn.example.com/video/1080p/init.mp4$Number$ замінюється на номер сегментаsegment_1.m4s, segment_2.m4s, segment_3.m4s, ...Повні URL:
https://cdn.example.com/video/1080p/init.mp4 ← Завантажується 1 раз
https://cdn.example.com/video/1080p/segment_1.m4s
https://cdn.example.com/video/1080p/segment_2.m4s
https://cdn.example.com/video/1080p/segment_3.m4s
...
| Характеристика | HLS (Apple) | DASH (MPEG) |
|---|---|---|
| Розробник | Apple Inc. | MPEG Industry Forum |
| Стандарт | RFC 8216 (IETF) | ISO/IEC 23009-1 |
| Маніфест | .m3u8 (текстовий, M3U Extended) | .mpd (XML) |
| Кодеки | Обмежені (H.264/H.265/AAC обов'язкові для Safari) | Codec-agnostic (VP9, AV1, Opus, будь-що) |
| Сегменти | .ts (MPEG-TS) або .m4s (fMP4) | .m4s (fragmented MP4) |
| DRM | FairPlay (тільки Apple екосистема) | CENC (Common Encryption) → Widevine, PlayReady |
| Нативна підтримка iOS | ✅ Так (Safari, AVPlayer) | ❌ Потрібен dash.js або Shaka Player |
| Нативна підтримка Android | ⚠️ Частково (потрібен ExoPlayer або hls.js) | ✅ Так (через MSE - Media Source Extensions) |
| Латентність (традиційна) | 20-40 сек | 10-30 сек (краще через менший segment overhead) |
| Латентність (Low Latency) | 2-6 сек (LL-HLS, iOS 14+) | 3-5 сек (LL-DASH) |
| CDN friendly | ✅ Відмінно | ✅ Відмінно |
| Multi-audio | ⚠️ Складно (потрібен окремий плейліст) | ✅ Просто (AdaptationSet з lang атрибутом) |
| Multi-subtitle | ⚠️ Окремий WebVTT плейліст | ✅ Вбудовано в MPD |
| Екосистема | Більша (більше CDN підтримки) | Менша, але зростає |
DASH має Period — HLS ні
У DASH можна мати кілька Period (періодів) в одному MPD:
<MPD>
<Period id="preroll_ad" duration="PT30S">
<!-- 30-секундна реклама -->
</Period>
<Period id="main_content" duration="PT2H">
<!-- Основний фільм -->
</Period>
<Period id="credits" duration="PT5M">
<!-- Титри -->
</Period>
</MPD>
Переваги:
HLS еквівалент: Потрібен окремий .m3u8 для кожного періоду або використання #EXT-X-DISCONTINUITY.
DASH:
<AdaptationSet lang="en" mimeType="audio/mp4">
<Representation bandwidth="128000">...</Representation>
</AdaptationSet>
<AdaptationSet lang="es" mimeType="audio/mp4">
<Representation bandwidth="128000">...</Representation>
</AdaptationSet>
<AdaptationSet lang="uk" mimeType="audio/mp4">
<Representation bandwidth="128000">...</Representation>
</AdaptationSet>
Плеєр автоматично вибирає мову на основі browser locale.
HLS: Потрібен #EXT-X-MEDIA у Master Playlist:
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",LANGUAGE="en",NAME="English",URI="audio_en.m3u8"
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio",LANGUAGE="es",NAME="Spanish",URI="audio_es.m3u8"
Висновок: DASH природно підтримує багатомовність.
DASH + CENC (Common Encryption):
<ContentProtection> для кожної системи:<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed" value="Widevine">
<cenc:pssh>BASE64_WIDEVINE_PSSH</cenc:pssh>
</ContentProtection>
<ContentProtection schemeIdUri="urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95" value="PlayReady">
<cenc:pssh>BASE64_PLAYREADY_PSSH</cenc:pssh>
</ContentProtection>
HLS + FairPlay:
Висновок: Netflix використовує DASH саме через DRM flexibility.
ffmpeg -i input.mp4 \
-c:v libx264 -preset medium -crf 23 \
-c:a aac -b:a 128k \
-f dash \
-seg_duration 6 \
-use_template 1 \
-use_timeline 0 \
output.mpd
Пояснення параметрів:
<SegmentTemplate> замість <SegmentList> (економить місце в MPD)<SegmentTimeline> (простіший MPD для VOD)Результат:
output.mpd
init-stream0.m4s ← Ініціалізаційний сегмент (відео)
init-stream1.m4s ← Ініціалізаційний сегмент (аудіо)
chunk-stream0-00001.m4s
chunk-stream0-00002.m4s
chunk-stream0-00003.m4s
...
chunk-stream1-00001.m4s ← Аудіо сегменти
chunk-stream1-00002.m4s
...
Створимо 3 варіанти якості: 480p, 720p, 1080p.
#!/bin/bash
INPUT="movie.mp4"
OUTPUT_DIR="dash_output"
mkdir -p "$OUTPUT_DIR"
ffmpeg -i "$INPUT" \
-map 0:v -map 0:v -map 0:v -map 0:a \
\
-c:v:0 libx264 -b:v:0 1000k -s:v:0 854x480 -profile:v:0 main \
-c:v:1 libx264 -b:v:1 2500k -s:v:1 1280x720 -profile:v:1 main \
-c:v:2 libx264 -b:v:2 5000k -s:v:2 1920x1080 -profile:v:2 high \
\
-c:a aac -b:a 128k -ar 48000 \
\
-f dash \
-seg_duration 6 \
-use_template 1 \
-use_timeline 0 \
-init_seg_name 'init-$RepresentationID$.m4s' \
-media_seg_name 'chunk-$RepresentationID$-$Number%05d$.m4s' \
-adaptation_sets "id=0,streams=v id=1,streams=a" \
"$OUTPUT_DIR/manifest.mpd"
Пояснення:
-map 0:v -map 0:v -map 0:v: Створює 3 відео-потоки-map 0:a: Один аудіо-потік-c:v:0, -c:v:1, -c:v:2: Окремі налаштування для кожного відео-adaptation_sets: Групування (AdaptationSet 0 = відео, AdaptationSet 1 = аудіо)Результат:
<MPD>
<Period>
<AdaptationSet id="0">
<Representation id="0" bandwidth="1000000" width="854" height="480">...
<Representation id="1" bandwidth="2500000" width="1280" height="720">...
<Representation id="2" bandwidth="5000000" width="1920" height="1080">...
</AdaptationSet>
<AdaptationSet id="1">
<Representation id="3" bandwidth="128000">...
</AdaptationSet>
</Period>
</MPD>
ffmpeg -i movie.mp4 -i audio_spanish.mp3 \
-map 0:v -map 0:a -map 1:a \
\
-c:v libx264 -b:v 2500k \
-c:a aac -b:a 128k \
\
-f dash \
-seg_duration 6 \
-adaptation_sets "id=0,streams=v id=1,streams=a,lang=en id=2,streams=a,lang=es" \
output.mpd
Результат:
<AdaptationSet id="1" lang="en">
<Representation id="audio_en">...</Representation>
</AdaptationSet>
<AdaptationSet id="2" lang="es">
<Representation id="audio_es">...</Representation>
</AdaptationSet>
Чому Shaka Player?
Встановлення:
<!DOCTYPE html>
<html>
<head>
<script src="https://cdn.jsdelivr.net/npm/shaka-player@4.7/dist/shaka-player.compiled.js"></script>
</head>
<body>
<video id="video" width="640" controls></video>
<script>
const video = document.getElementById('video')
const player = new shaka.Player(video)
// DASH manifest URL
const manifestUri = 'https://cdn.example.com/manifest.mpd'
player
.load(manifestUri)
.then(() => {
console.log('Video loaded successfully!')
})
.catch((error) => {
console.error('Error loading video:', error)
})
// Відслідковування змін якості
player.addEventListener('adaptation', () => {
const tracks = player.getVariantTracks()
const activeTrack = tracks.find((t) => t.active)
console.log(`Quality: ${activeTrack.height}p, Bandwidth: ${activeTrack.bandwidth}`)
})
</script>
</body>
</html>
dash.js — reference implementation від Dash Industry Forum.
<script src="https://cdn.dashjs.org/latest/dash.all.min.js"></script>
<video id="video" controls></video>
<script>
const url = 'https://cdn.example.com/manifest.mpd'
const player = dashjs.MediaPlayer().create()
player.initialize(document.querySelector('#video'), url, true)
// Налаштування ABR
player.updateSettings({
streaming: {
abr: {
fetchThroughputCalculationMode: 'ewma',
bandwidthSafetyFactor: 0.9,
useDefaultABRRules: true,
},
},
})
</script>
Проблема без CENC:
У минулому кожна DRM система вимагала окремого шифрування:
video_widevine.m4svideo_playready.m4svideo_fairplay.m4sРезультат: 3x storage, 3x bandwidth.
Рішення: CENC (Common Encryption)
Один шифрований файл, але з метаданими для кількох DRM:
<ContentProtection schemeIdUri="urn:mpeg:dash:mp4protection:2011" value="cenc" cenc:default_KID="ABCD1234..."/>
<ContentProtection schemeIdUri="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed">
<!-- Widevine PSSH -->
<cenc:pssh>AAAAW3Bzc2gAAAAA7e+LqXnWSs6jyCfc...</cenc:pssh>
</ContentProtection>
<ContentProtection schemeIdUri="urn:uuid:9a04f079-9840-4286-ab92-e65be0885f95">
<!-- PlayReady PSSH -->
<cenc:pssh>AAACYHBzc2gAAAAAmgTweZhAQoarkuZb...</cenc:pssh>
</ContentProtection>
Як це працює:
Приклад з Shaka Player:
const player = new shaka.Player(video)
player.configure({
drm: {
servers: {
'com.widevine.alpha': 'https://license.example.com/widevine',
'com.microsoft.playready': 'https://license.example.com/playready',
},
},
})
player.load('https://cdn.example.com/encrypted.mpd')
Відмінності Live від VOD:
<MPD type="static" mediaPresentationDuration="PT2H15M">
<Period>
<AdaptationSet>
<Representation bandwidth="5000000">
<SegmentTemplate media="seg_$Number$.m4s" startNumber="1"/>
</Representation>
</AdaptationSet>
</Period>
</MPD>
mediaPresentationDuration фіксований<MPD type="dynamic"
availabilityStartTime="2024-02-17T12:00:00Z"
publishTime="2024-02-17T12:30:00Z"
minimumUpdatePeriod="PT5S"
timeShiftBufferDepth="PT2M">
<Period start="PT0S">
<AdaptationSet>
<Representation bandwidth="5000000">
<SegmentTemplate media="seg_$Number$.m4s"
timescale="90000"
duration="540000"
startNumber="1"/>
</Representation>
</AdaptationSet>
</Period>
</MPD>
Ключові атрибути:
availabilityStartTime: Коли почалася трансляціяpublishTime: Коли був оновлений MPD (timestamps для кешування)minimumUpdatePeriod: Як часто плеєр має перевіряти оновлення (5 сек)timeShiftBufferDepth: DVR window (2 хвилини назад користувач може перемотати)Як плеєр працює з Live:
availableSegmentNumber на основі availabilityStartTime та поточного часуminimumUpdatePeriod секунд оновлює MPDСтандартна DASH латентність: 10-30 секунд
LL-DASH латентність: 3-5 секунд
Ключові зміни:
📦 Chunked Transfer Encoding
Сегменти передаються по частинах (chunks) до того, як вони повністю закодовані.
Transfer-Encoding: chunked
0A\r\n
[chunk 1 data]\r\n
0B\r\n
[chunk 2 data]\r\n
...
Плеєр починає decode одразу після отримання першого chunk.
⚡ Shorter Segments
🔄 Server Push
Приклад MPD для LL-DASH:
<MPD type="dynamic"
availabilityStartTime="2024-02-17T12:00:00Z"
suggestedPresentationDelay="PT2S"
minBufferTime="PT1S">
<ServiceDescription>
<Latency target="2000" min="1000" max="4000"/>
</ServiceDescription>
<Period>
<AdaptationSet>
<Representation>
<SegmentTemplate media="seg_$Number$.m4s" duration="90000" timescale="90000"/>
<!-- duration=90000/90000 = 1 секунда -->
</Representation>
</AdaptationSet>
</Period>
</MPD>
Проблема: Різні формати сегментів для HLS (.ts або .m4s) та DASH (.m4s).
Рішення: CMAF — єдиний формат, сумісний з обома.
Переваги CMAF:
Як створити CMAF за допомогою FFmpeg:
ffmpeg -i input.mp4 \
-c:v libx264 -c:a aac \
-f hls \
-hls_segment_type fmp4 \
-hls_playlist_type vod \
output.m3u8
Результат:
output.m3u8 ← HLS manifest
output0.m4s
output1.m4s
output2.m4s
...
Створюємо DASH MPD вручну:
<MPD>
<Period>
<AdaptationSet>
<Representation>
<BaseURL>./</BaseURL>
<SegmentTemplate media="output$Number$.m4s" startNumber="0"/>
</Representation>
</AdaptationSet>
</Period>
</MPD>
Або використовуємо Shaka Packager:
packager \
in=input.mp4,stream=video,output=video.mp4 \
in=input.mp4,stream=audio,output=audio.mp4 \
--hls_master_playlist_output master.m3u8 \
--mpd_output manifest.mpd
Проблема: Init segment завантажується для кожної якості при переключенні.
Рішення: Об'єднаний init segment для всіх representations.
ffmpeg \
-f dash \
-single_file 1 \
-init_seg_name 'init.mp4' \
output.mpd
Результат:
init.mp4 для всіх якостейЗамість окремих файлів можна зберігати всі сегменти в одному файлі та використовувати HTTP Range headers:
<SegmentList>
<Initialization sourceURL="stream.mp4" range="0-1024"/>
<SegmentURL mediaRange="1025-500000"/>
<SegmentURL mediaRange="500001-950000"/>
<SegmentURL mediaRange="950001-1400000"/>
</SegmentList>
Переваги:
Недоліки:
Великий MPD (кілька MB) може сповільнити start time.
Рішення: Генерувати MPD динамічно на сервері:
// Node.js приклад
app.get('/manifest.mpd', (req, res) => {
const userAgent = req.headers['user-agent']
const isMobile = /Mobile/.test(userAgent)
// Для мобільних: тільки 480p, 720p
// Для desktop: 720p, 1080p, 4K
const qualities = isMobile ? ['480p', '720p'] : ['720p', '1080p', '4k']
const mpd = generateMPD(qualities)
res.set('Content-Type', 'application/dash+xml')
res.send(mpd)
})
Економія: MPD розміром 500 KB замість 2 MB.
Те саме, що в HLS. Додайте CORS headers:
location ~ \.(mpd|m4s|mp4)$ {
add_header Access-Control-Allow-Origin * always;
add_header Access-Control-Allow-Methods 'GET, HEAD, OPTIONS' always;
add_header Access-Control-Allow-Headers 'Range' always;
if ($request_method = OPTIONS) {
return 204;
}
}
Проблема: Safari НЕ має нативної підтримки DASH.
Рішення:
const video = document.getElementById('video')
if (video.canPlayType('application/vnd.apple.mpegurl')) {
// Safari: використовуємо HLS
video.src = 'https://cdn.example.com/stream.m3u8'
} else if (shaka.Player.isBrowserSupported()) {
// Chrome/Firefox: використовуємо DASH
const player = new shaka.Player(video)
player.load('https://cdn.example.com/manifest.mpd')
}
Симптом (Live DASH):
Failed to load segment: 404 Not Found
chunk-stream0-00125.m4s
Причина: Плеєр розрахував, що сегмент вже має бути доступний, але encoder ще не створив його.
Діагностика:
Перевірте suggestedPresentationDelay у MPD:
<MPD suggestedPresentationDelay="PT10S">
Це означає: плеєр має відтворювати з затримкою 10 секунд від "live edge".
Рішення:
Збільшіть suggestedPresentationDelay до 15-20 секунд.
Причина: Різні timescale для аудіо та відео.
Діагностика:
ffprobe -show_streams chunk-stream0-00001.m4s | grep time_base
# Відео: time_base=1/90000
ffprobe -show_streams chunk-stream1-00001.m4s | grep time_base
# Аудіо: time_base=1/48000
Рішення:
У MPD переконайтесь, що timescale правильний:
<!-- Відео -->
<SegmentTemplate timescale="90000" duration="540000"/>
<!-- 540000/90000 = 6 секунд -->
<!-- Аудіо -->
<SegmentTemplate timescale="48000" duration="288000"/>
<!-- 288000/48000 = 6 секунд -->
Обидва мають бути 6 секунд.
🧪 DASH Validator
Upload ваш .mpd та отримайте звіт про помилки.
📊 Shaka Player Stats
const player = new shaka.Player(video)
setInterval(() => {
const stats = player.getStats()
console.log('Bandwidth:', stats.estimatedBandwidth)
console.log('Buffer:', video.buffered.end(0) - video.currentTime)
console.log('Dropped frames:', stats.droppedFrames)
}, 1000)
🔍 Chrome Media Internals
Відкрийте chrome://media-internals/ та натисніть Play на DASH відео.
Ви побачите:
Один набір сегментів = 50% економії.
location ~ \.mpd$ {
add_header Cache-Control "public, max-age=10";
}
location ~ \.(m4s|mp4)$ {
add_header Cache-Control "public, max-age=31536000, immutable";
}
suggestedPresentationDelay >= 10sЗахищає від 404 на сегментах.
Один шифрований файл для Widevine + PlayReady.
<AdaptationSet lang="en">...</AdaptationSet>
<AdaptationSet lang="es">...</AdaptationSet>
Мета: Перетворити movie.mp4 на DASH з 3 якостями (480p, 720p, 1080p) та 2 мовами (англійська, іспанська).
Кроки:
audio_spanish.mp3Критерії успіху:
lang атрибутамиМета: Створити один набір сегментів, який працює і з HLS, і з DASH.
Кроки:
master.m3u8)manifest.mpd)Проблема: Live stream має затримку 45 секунд замість очікуваних 10.
Завдання:
suggestedPresentationDelaytimeShiftBufferDepthA: Safari НЕ підтримує DASH нативно. Варіанти:
A:
segment_$Number$.m4s). Компактний MPD.Рекомендація: SegmentTemplate для більшості випадків.
A:
<MPD type="dynamic" timeShiftBufferDepth="PT10M">
timeShiftBufferDepth=PT10M = користувач може перемотати на 10 хвилин назад.
Плеєр зберігає старі сегменти в буфері. Після 10 хвилин вони видаляються.
🎯 Що таке DASH
.mpd)📐 Архітектура
MPD
└─ Period
└─ AdaptationSet (Video)
├─ Representation (1080p)
├─ Representation (720p)
└─ Representation (480p)
└─ AdaptationSet (Audio EN)
└─ AdaptationSet (Audio ES)
⚙️ Створення
ffmpeg -i input.mp4 \
-c:v libx264 -c:a aac \
-f dash \
output.mpd
🌐 Браузер
Shaka Player або dash.js
Safari: fallback на HLS