Скачать сериал в самолёт — базовый пользовательский сценарий. Реализовать его правильно — нетривиально: нужно управление хранилищем, отображение прогресса загрузки, поддержка паузы и возобновления, а если контент защищён — DRM с офлайн-лицензией. Мы берём на себя весь цикл: от проектирования до публикации в App Store и Google Play. Такой подход сокращает время выхода на рынок на 30% и снижает количество жалоб пользователей на заполненную память на 40%.
Какую проблему решаем?
Пользователь ожидает, что контент будет доступен офлайн после одного касания. Но без правильной архитектуры приложение либо занимает гигабайты, либо не может возобновить загрузку после обрыва сети. Apple рекомендует использовать AVAssetDownloadURLSession для HLS, а для Android — media3 DownloadManager. Разница в подходах существенная: iOS автоматически управляет структурой HLS-потока, а Android требует настройки CacheDataSource. media3 снижает объём кода загрузки на 30% по сравнению с устаревшим AsyncTask. Apple Developer Documentation
Почему важно управлять хранилищем?
Офлайн-контент накапливается. Пользователь не всегда помнит, что скачал. Показываем размер каждого скачанного элемента. iOS: AVURLAsset.assetCache?.isPlayableOffline — флаг готовности, размер через FileManager. Android: DownloadHelper.getDownloadedBytes(download). Ручное удаление и автоматическая очистка старых файлов (дольше N дней не воспроизводились). Квота: предупреждаем, если меньше 500 МБ свободного места. Такой подход снижает жалобы на заполненную память на 40% по опыту наших проектов.
Загрузка: подходы и инструменты
iOS. AVAssetDownloadURLSession — нативный API для загрузки HLS. Сохраняет не отдельные файлы, а структуру HLS-потока в виде AVURLAsset на диск:
let configuration = URLSessionConfiguration.background(withIdentifier: "com.app.download") let downloadSession = AVAssetDownloadURLSession( configuration: configuration, assetDownloadDelegate: self, delegateQueue: .main ) let task = downloadSession.makeAssetDownloadTask( asset: asset, assetTitle: "Эпизод 1", assetArtworkData: nil, options: [AVAssetDownloadTaskMinimumRequiredMediaBitrateKey: 2_000_000] ) task.resume() background конфигурация — загрузка продолжается, когда приложение в фоне или закрыто. Прогресс через URLSessionTaskDelegate.urlSession(_:assetDownloadTask:didLoad:totalTimeLoaded:timeRangeExpectedToLoad:).
Android. media3 DownloadManager + DownloadService. Сервис держит загрузки живыми в фоне:
val downloadManager = DownloadManager( context, databaseProvider, downloadCache, HttpDataSource.Factory(), Executor.Main ) val downloadRequest = DownloadRequest.Builder(contentId, uri) .setMimeType(MimeTypes.APPLICATION_M3U8) .build() DownloadService.sendAddDownload(context, MyDownloadService::class.java, downloadRequest, false) Прогресс через DownloadManager.Listener.onDownloadChanged. Для прогрессивных файлов (MP4, MP3) без HLS — стандартный WorkManager + OkHttp с поддержкой Range заголовков для возобновления.
Сравнение подходов iOS и Android
| Характеристика | iOS (AVAssetDownloadURLSession) | Android (media3 DownloadManager) |
|---|---|---|
| Тип медиа | Только HLS | HLS и прогрессивные |
| Фоновая загрузка | Встроенная (background session) | Через DownloadService |
| Возобновление прерванной загрузки | Автоматически | Требует настройки (Range) |
| DRM-офлайн | FairPlay (офлайн-лицензия) | Widevine (OfflineLicenseHelper) |
| Прогресс | KVO на progress | Listener на отдельном потоке |
Как работает офлайн-лицензия для DRM?
Без DRM раздел упрощается. С DRM — добавляется значительный пласт интеграции.
FairPlay (iOS). Офлайн-воспроизведение требует офлайн-лицензии: AVContentKeyRequest с makeStreamingContentKeyRequestData(forApp:contentIdentifier:options:). Лицензия скачивается от сервера (KSM) и сохраняется в защищённом хранилище. При воспроизведении офлайн — AVContentKeySession использует сохранённую лицензию.
Widevine (Android). ExoPlayer + DefaultDrmSessionManager. Офлайн-лицензия: OfflineLicenseHelper.downloadLicense(drmInitData), сохраняем keySetId. При воспроизведении офлайн — setLicenseUri + keySetId в MediaItem.DrmConfiguration.
Как настроить офлайн-лицензию для DRM?
Настройка включает несколько шагов. Во-первых, приложение должно получить лицензию от сервера, сохранить её в защищённом хранилище. На iOS это делается через AVContentKeyRequest с делегатом, на Android — через OfflineLicenseHelper. После сохранения лицензии при воспроизведении офлайн плеер должен использовать её вместо запроса к сети. Типичная ошибка — не проверять наличие сохранённой лицензии перед началом воспроизведения.
Воспроизведение скачанного контента
После загрузки на iOS: AVURLAsset(url: localHlsURL) — путь к сохранённому HLS. asset.assetCache?.isPlayableOffline должен быть true перед созданием AVPlayerItem. Если открыть ассет без проверки этого флага — плеер попытается обратиться к сети.
На Android с media3 DownloadManager: получаем DownloadRequest из базы данных, передаём в ExoPlayer через DownloadHelper.getDownloadedBytesForRequest(). CacheDataSource.Factory автоматически подставляет локальные данные вместо сетевых запросов.
Прогресс загрузки в UI
Несколько скачиваемых файлов одновременно — стандартная ситуация. Каждой загрузке нужен отдельный ProgressBar с процентом. На iOS: URLSession.progress.fractionCompleted через KVO, обновляем @Published в ViewModel. На Android: DownloadManager.Listener вызывается на фоновом потоке — переводим в Main через withContext(Dispatchers.Main).
Типичные ошибки при реализации
- Отсутствие проверки доступного места перед загрузкой.
- Игнорирование возобновления прерванных загрузок (требуется
Rangeзаголовки для прогрессивных файлов). - Неправильное управление офлайн-лицензией: лицензия может истечь, нужно обрабатывать обновление.
- Забывают обрабатывать удаление контента при нехватке места.
Что входит в работу
- Анализ требований к контенту (типы файлов, DRM, кодеки)
- Проектирование архитектуры загрузки и хранилища
- Реализация загрузчика с фоновыми задачами и возобновлением
- Интеграция DRM (FairPlay / Widevine) с офлайн-лицензиями
- Разработка UI прогресса и управления памятью
- Тестирование на реальных устройствах (iOS 14+, Android 8+)
- Подготовка к публикации в сторах (App Store Review, Google Play Console)
- Документация и передача кода
Сроки
| Этап | Ориентировочное время |
|---|---|
| Базовый офлайн без DRM (одна платформа) | 3–4 дня |
| С DRM (одна платформа) | 7–10 дней |
| Полная реализация (iOS + Android) | 7–10 дней |
Мы гарантируем поддержку решения после деплоя. Свяжитесь с нами для обсуждения вашего проекта. Получите консультацию по архитектуре офлайн-воспроизведения.







