Реалізація потокового відеотрансляції (HLS/DASH) в мобільному застосунку
HLS та DASH — протоколи адаптивного потокового передавання. Плеєр завантажує не один файл, а маніфест із сегментами та динамічно переключається між якостями залежно від швидкості сети. Це не проста загрузка MP4 — тут є буферизація, ABR-алгоритми та обробка помилок сети.
HLS vs DASH: що вибрати
| Параметр | HLS | DASH |
|---|---|---|
| Рідна підтримка iOS | Так (AVFoundation) | Ні (потрібна бібліотека) |
| Рідна підтримка Android | Ні (потрібен ExoPlayer) | Ні (потрібен ExoPlayer) |
| Затримка (стандарт) | 6–30 с | 2–10 с |
| Low-Latency HLS/DASH | LL-HLS: ~2 с | LL-DASH: ~1–3 с |
| Підтримка DRM | FairPlay (iOS), Widevine | Widevine, PlayReady |
На практиці: якщо аудиторія iOS-dominant та немає DRM — HLS без сторонніх бібліотек. Якщо крос-платформа та потрібна мінімальна затримка — DASH через ExoPlayer/Shaka.
iOS: AVPlayer + HLS
AVPlayer відтворює HLS нативно. Створюємо AVPlayerItem(url: m3u8Url):
let asset = AVURLAsset(url: hlsURL, options: [
"AVURLAssetHTTPHeaderFieldsKey": ["Authorization": "Bearer \(token)"]
])
let item = AVPlayerItem(asset: asset)
player = AVPlayer(playerItem: item)
Моніторимо буферизацію — KVO на item.isPlaybackLikelyToKeepUp та item.loadedTimeRanges. Коли isPlaybackLikelyToKeepUp == false — показуємо спіннер, при true — ховаємо.
Передзавантаження наступного відео: створюємо AVPlayerItem заздалегідь, додаємо в AVQueuePlayer — перший сегмент наступного відео почне завантажуватися у фоні.
Android: ExoPlayer + HLS/DASH
val player = ExoPlayer.Builder(context).build()
// HLS
val hlsItem = MediaItem.Builder()
.setUri("https://example.com/stream.m3u8")
.build()
// DASH
val dashItem = MediaItem.Builder()
.setUri("https://example.com/manifest.mpd")
.build()
player.setMediaItem(hlsItem)
player.prepare()
player.play()
ExoPlayer автоматично вибирає HlsMediaSource або DashMediaSource за розширенням URL. Для явного вказання або користувацьких заголовків:
val dataSourceFactory = DefaultHttpDataSource.Factory()
.setDefaultRequestProperties(mapOf("Authorization" to "Bearer $token"))
val hlsSource = HlsMediaSource.Factory(dataSourceFactory)
.createMediaSource(MediaItem.fromUri(uri))
Адаптивний бітрейт
За замовчуванням ExoPlayer використовує AdaptiveTrackSelection — переключає якість на межах сегментів (зазвичай кожні 2–10 с). Встановлення мінімальної якості:
val trackSelector = DefaultTrackSelector(context)
trackSelector.parameters = trackSelector.buildUponParameters()
.setMaxVideoSizeSd() // не вище 480p на слабких пристроях
.build()
На iOS — AVPlayerItem.preferredPeakBitRate = 2_000_000 обмежує верхній бітрейт, корисно для економії трафіку.
Обробка помилок сети
Сеть нестабільна — сегмент не завантажився, маніфест повернув 403, CDN дав 5xx. ExoPlayer повторює запити автоматично з експоненціальною затримкою (DefaultLoadErrorHandlingPolicy). Користувацька політика:
class RetryPolicy : DefaultLoadErrorHandlingPolicy() {
override fun getRetryDelayMsFor(loadErrorInfo: LoadErrorInfo) =
if (loadErrorInfo.errorCount < 5) 1000L * loadErrorInfo.errorCount else RETRY_DELAY_UNSET
}
На iOS: AVPlayerItem.status == .failed → player.currentItem?.error — читаємо NSError, показуємо кнопку «Повторити».
Орієнтири за часом
HLS-плеєр на одній платформі з ABR та обробкою помилок — 2 дні. Крос-платформний (iOS + Android) з DASH, DRM та ручним вибором якості — 4–5 днів.







