Реалізація офлайн-відтворення медіа в мобільному застосунку
Завантажити серіал у літак — базовий користувацький сценарій. Реалізувати його правильно — нетривіально: потрібно управління хранилищем, відображення прогресу завантаження, підтримка паузи та відновлення, а якщо контент захищений — DRM з офлайн-ліцензією.
Завантаження: підходи та інструменти
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: AVURLAsset.assetCache?.isPlayableOffline — флаг готовності, розмір через FileManager.attributesOfItem(atPath:)[.size]. Android: DownloadHelper.getDownloadedBytes(download).
Ручне видалення та автоматична очистка старих файлів (більше N днів не відтворювалися). Квота: попереджуємо, якщо менше 500 МБ вільного місця (UIDevice.current.freeDiskSpaceInBytes / StatFs).
DRM: офлайн-ліцензії
Без DRM розділ спрощується. З DRM — додається значний пласт інтеграції.
FairPlay (iOS). Офлайн-відтворення вимагає офлайн-ліцензії: AVContentKeyRequest з makeStreamingContentKeyRequestData(forApp:contentIdentifier:options:). Ліцензія завантажується від сервера (KSM) та зберігається в захищеному хранилищі. При відтворенні офлайн — AVContentKeySession використовує збережену ліцензію.
Widevine (Android). ExoPlayer + DefaultDrmSessionManager. Офлайн-ліцензія: OfflineLicenseHelper.downloadLicense(drmInitData), зберігаємо keySetId. При відтворенні офлайн — setLicenseUri + keySetId у MediaItem.DrmConfiguration.
Відтворення завантаженого контенту
Після завантаження на 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).
Орієнтири за часом
Завантаження без DRM з управління хранилищем — 3–4 дні. З DRM (FairPlay або Widevine) — плюс 3–4 дні на інтеграцію з ліцензійним сервером. Повна реалізація для iOS + Android — 7–10 днів.







