Offline downloads
OGPlayer can download HLS VOD (and DASH on Android) for fully offline playback, including offline DRM: the persistent license is acquired during the download through the same token flow you already use for streaming. Available on Android, iOS and React Native.
Playback pickup is transparent: a download is keyed by its media URL, and
loading an OGMediaItem whose URL has a completed download plays from local
storage automatically — same code path, zero network, no token calls.
val downloads = OGDownloads.manager(context)
// Queue a download (idempotent per URL). For DRM items the persistent// Widevine license is fetched alongside, via the item's DrmConfig/token// provider — identical to streaming.downloads.add( item, OGDownloadConfig.Builder() .setMaxVideoHeight(720) // omit for highest .setAudioLanguages(listOf("en", "nl")) .setSubtitleLanguages(listOf("nl")) .build(),)
// Observe:downloads.addListener(object : OGDownloadListener { override fun onDownloadStateChanged(download: OGDownload) { render() } override fun onDownloadProgress(download: OGDownload) { render() } override fun onDownloadFailed(download: OGDownload, error: OGPlayerError) { show(error) }})
// Transport:downloads.pause(url); downloads.resume(url)downloads.pauseAll(); downloads.resumeAll()
// Play offline — either load the original item, or:player.load(downloads.getDownload(url)!!.toMediaItem())
// Delete media + release/erase the stored license. For DRM'd downloads// pass the item (same drm config as add) so the release request to the// license server authenticates and frees this device's offline slot:downloads.removeDownload(url, item)
// Proactively refresh an expiring license (suspend):val info = downloads.renewLicense(item)Downloads run in an SDK-provided foreground service — no app-side service
or manifest setup. Notification channel name/icon and network policy are
configurable once via OGDownloads.initialize(context, OGDownloadsConfig)
before first use (optional; sensible defaults apply).
// @MainActor ObservableObject — drive SwiftUI straight from it.@ObservedObject var downloads = OGDownloadManager.shared
// Queue (idempotent per URL); FairPlay items persist their keys alongside.downloads.add(item, config: OGDownloadConfig(maxVideoHeight: 720))
// downloads.downloads is @Published — list UIs update automatically.ForEach(downloads.downloads) { download in DownloadRow(download) // state, progressPercent, bytesDownloaded…}
// Transport:downloads.pause(url: url); downloads.resume(url: url)downloads.pauseAll(); downloads.resumeAll()
// Play offline: just load the same item — local pickup is automatic.player.load(item)
// Delete media + erase the stored keys:downloads.removeDownload(url: url)
// Proactively refresh a license:let info = try await downloads.renewLicense(for: item)Downloads continue in the background via a system background URL session.
One AppDelegate hook is required so the system gets its completion
callback (without it, iOS progressively deprioritizes your app’s
background transfers):
func application(_ application: UIApplication, handleEventsForBackgroundURLSession identifier: String, completionHandler: @escaping () -> Void) { OGDownloadManager.handleBackgroundEvents(identifier: identifier, completionHandler: completionHandler)}import { OGDownloads } from "ogplayer-react-native";
// Queue (idempotent per URL). DRM note below.await OGDownloads.add(item, { maxVideoHeight: 720 });
// Observe — one subscription for all downloads:useEffect(() => { const sub = OGDownloads.addListener((event) => { // { type: 'stateChanged' | 'progress' | 'failed', download, error? } refresh(); }); return () => sub.remove();}, []);
const all = await OGDownloads.list(); // OGDownload[] snapshots
OGDownloads.pause(url); OGDownloads.resume(url);OGDownloads.pauseAll(); OGDownloads.resumeAll();
// Delete media + release the license (pass the item for DRM'd downloads// so the Widevine release request can authenticate — Android):await OGDownloads.remove(url, item);
// Proactively refresh a license:const info = await OGDownloads.renewLicense(item);
// Play offline: render <OGPlayerView source={item}> as usual — a// completed download for that URL plays from local storage automatically.DRM note: if the item carries a drm.tokenProvider, it is invoked once,
before the request crosses to native, and the returned headers are used to
fetch the persistent license during the download (and again per
renewLicense call, with renewal: true).
import 'package:ogplayer_flutter/ogplayer_flutter.dart';
// Queue (idempotent per URL). DRM note below.await OGDownloads.add(item, const OGDownloadConfig(maxVideoHeight: 720));
// Observe — one broadcast stream for all downloads:final sub = OGDownloads.events.listen((event) { // OGDownloadStateChanged / OGDownloadProgress / OGDownloadFailed, // each carrying the OGDownload snapshot refresh();});// later: await sub.cancel();
final all = await OGDownloads.list(); // List<OGDownload> snapshots
await OGDownloads.pause(url); await OGDownloads.resume(url);await OGDownloads.pauseAll(); await OGDownloads.resumeAll();
// Delete media + release the license (pass the item for DRM'd downloads// so the Widevine release request can authenticate — Android):await OGDownloads.remove(url, item);
// Proactively refresh a license:final info = await OGDownloads.renewLicense(item);
// Play offline: build OGPlayerView(source: item) as usual — a completed// download for that URL plays from local storage automatically.DRM note: if the item carries a drm.tokenProvider, it is invoked once,
before the request crosses to native, and the returned headers are used to
fetch the persistent license during the download (and again per
renewLicense call, with request.renewal == true).
iOS: forward handleEventsForBackgroundURLSession in your AppDelegate to
OgplayerFlutterPlugin.handleDownloadBackgroundEvents so downloads that
finish while the app is not running complete.
Download lifecycle
Section titled “Download lifecycle”Every download is an immutable snapshot (OGDownload) keyed by its media
URL, moving through:
QUEUED → DOWNLOADING → COMPLETED — with PAUSED, FAILED (retryable via
resume) and REMOVING along the way. progressPercent is 0–100 (−1 while
unknown) and bytesDownloaded tracks size on disk.
For DRM’d downloads, download.license exposes the persistent license:
expiresAtMs / remainingMs / isExpired — all null/false when the
license server does not expose an expiry.
Offline DRM
Section titled “Offline DRM”- The persistent license is fetched during the download, through the item’s existing DRM config and token provider — nothing new to wire.
- Playback of a completed download makes zero network and zero token calls; the stored license is used directly.
- Renewal:
renewLicense(item)fetches a fresh persistent license (e.g. on app start while online, for content the viewer will watch on a flight). - Errors are part of the stable taxonomy:
7100download failed,7101storage full,7102offline license fetch failed, and4006offline license expired (at playback time).
Notes per platform
Section titled “Notes per platform”- Android — Widevine persistent licenses. Deleting a DRM’d download with
the
itemsupplied also releases the license server-side, freeing the device’s offline slot on token-authorized deployments — always pass it when you have it. - iOS — FairPlay persistable keys. FairPlay servers typically don’t
expose expiry to the client, so
expiresAtis usuallyniland an expired license surfaces reactively as error4006when playback is attempted. Deleting erases the keys locally (FairPlay has no server release round-trip). - React Native — the full manager is exposed as the
OGDownloadsmodule; downloads and playback pickup run in the native SDKs underneath. - Flutter — the same manager is the
OGDownloadsclass (static methods plus an events stream); downloads and playback pickup run in the native SDKs underneath.remove(url, item)releases a Widevine license server-side when the item is supplied, as on Android; atokenProvideris invoked once in Dart before each call crosses to native. On iOS, downloads that finish while the app is not running need yourAppDelegateto forwardhandleEventsForBackgroundURLSessiontoOgplayerFlutterPlugin.handleDownloadBackgroundEvents.