Skip to content

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.

The downloads screen: an item with its own progress and controls.
The demo app on an iPhone 17 Pro Max, running the published SDK.
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).

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.

  • 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: 7100 download failed, 7101 storage full, 7102 offline license fetch failed, and 4006 offline license expired (at playback time).
  • Android — Widevine persistent licenses. Deleting a DRM’d download with the item supplied 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 expiresAt is usually nil and an expired license surfaces reactively as error 4006 when playback is attempted. Deleting erases the keys locally (FairPlay has no server release round-trip).
  • React Native — the full manager is exposed as the OGDownloads module; downloads and playback pickup run in the native SDKs underneath.
  • Flutter — the same manager is the OGDownloads class (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; a tokenProvider is invoked once in Dart before each call crosses to native. On iOS, downloads that finish while the app is not running need your AppDelegate to forward handleEventsForBackgroundURLSession to OgplayerFlutterPlugin.handleDownloadBackgroundEvents.