Реализация потокового воспроизведения видео (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 дней.







