Skip to main content

Download To Go

The Player library supports download of both clear and DRM protected contents.

Custom License Request Handling

Similar to playing DRM Protected contents, license request can be handled while downloading DRM protected contents as well as follows.

dwldManager.drmDelegate = dwldManager.drmDelegate = object : DRMDelegate {
override fun onKeyResponseRequired(
assetURL: String,
request: DRMKeyRequest,
callback: Callback<ByteArray, Error>
) {
// Add custom logic
}
}
note
  • The license will have a specific validity and can be renewed in background using the requestLicenseInfo and renewLicense APIs in the DownloadManager.
  • To ensure that there are no disruptions in Offline Playback , it is recommended to configure a DRMDelegate with the Player created for offline playback and handle the license request on demand. (This would work only if the device is connected to the internet).
    The Platform Player library provides a DefaultDRMDelegate which can be created as below:
val drmDelegate = DefaultDRMDelegate(coroutineScope)

Download Manager

Download Requirements

DownloadRequirements is an enum of all possible requirements available to configure downloads. The only default requirement is DeviceRequirements.NETWORK.

NameDescription
NETWORKRepresents the requirement that the device should have a network connection.
NETWORK_UNMETEREDRepresents the requirement that the device should have an unmetered network connection.
DEVICE_IDLERepresents the requirement that the device should be idle.
DEVICE_CHARGINGRepresents the requirement that the device should be charging.
DEVICE_STORAGE_NOT_LOWRepresents the requirement that the device's available storage should not be low. This is generally the point where the user is given a "low storage" warning by the Android OS.
val downloadProperties = listOf(
DownloadRequirements.NETWORK //Pass all preferred requirements
)
note

When the DEVICE_STORAGE_NOT_LOW requirement is not fulfilled i.e. the device does not have enough storage, ongoing / new downloads are put in DownloadState.QUEUED (Refer Download States) and resume only when the available storage is not low.

Download Properties

DownloadProperties is a collection of configurations that dictate how downloads should be queued and processed.

NameTypeDefault valueDescription
maxDownloadTasksInt50The maximum number of tasks queued for download. (Must be greater than 0)
maxParallelDownloadsInt3The maximum number of tasks parallel downloads. (Must be greater than 0)
minRetryCountInt5The minimum number of times that a download will be retried. A download will fail if the specified number of retries is exceeded without any progress being made.
requirementsList<DownloadRequirements>DownloadRequirements.NETWORKThe list of DownloadRequirements to be fulfilled for every download task.

All the above-mentioned properties are optional, with the defaults being the best suited for most cases. The application can override the defaults by passing the preferred values to the constructor.

Creating a Download Manager

The DownloadManager can be initialized by passing app context on Application's onCreate() lifecycle method.

DownloadManager.create(
this,
logger,
drmDelegate, // Refer Custom License Request handling section
downloadProperties
).resumeDownloads()

Creating a Download Request

// The preferred video bitrate (quality) to be downloaded
val preferredVideoBitrate = 100_000_000

// Creating a Clear Content Download Request
val clearContentDownloadRequest = DownloadRequest.Builder()
.mediaURL(contentURL)
.mediaType(MediaType.DASH)
.drmLicenseURL("")
.drmScheme(DRMScheme.NONE)
.preferredVideoBitrate(preferredVideoBitrate)
.addPreferredAudioLanguage("en")
.addPreferredTextLanguage("en")
.metadata(contentName)
.id(contentURL)
.build()

// Creating a DRM Content Download Request
val drmContentDownloadRequest = DownloadRequest.Builder()
.mediaURL(contentURL)
.mediaType(MediaType.DASH)
.drmLicenseURL(license)
.drmScheme(DRMScheme.WIDEVINE)
.preferredVideoBitrate(preferredVideoBitrate)
.addPreferredAudioLanguage("en")
.addPreferredTextLanguage("en")
.id(contentURL)
.metadata(contentName)
.build()
note
  • Application developers must ensure that the Download ID passed during DownloadRequest creation is unique.
  • Application developers may also pass any metadata that they want to preserve as part of downloads.
  • Application developers may pass any desired preferredVideoBitrate to download. The matching algorithm will automatically choose the closest available variant.

Enqueueing a Download Request

Before Enqueueing a download, it's a good practice to verify if there is already an existing download corresponding to the passed ID.

//It's assumed that DownloadManager.create is called beforehand
val downloadManager = DownloadManager.instance
val downloads = downloadManager.getAllDownloads()
val existingDownload = downloads.find { request.id == it.id }
if (existingDownload == null) {
val result = downloadManager.enqueueDownload(request)
}

The enqueueDownload API, returns a result which can either be a Success (or) Failure. Application developers can obtain the result and update the UI accordingly.

Pausing and Resuming Downloads

Any ongoing download can be paused and resumed.

fun toggleDownload(downloadID: String) {
val downloads = downloadManager.getAllDownloads()
val chosenDownload = downloads.find { it.id == downloadID } ?: return
if (chosenDownload.state == DownloadState.DOWNLOADING) {
downloadManager.pauseDownload(chosenDownload)
} else {
downloadManager.resumeDownload(chosenDownload)
}
}

Purging Downloads

Any ongoing (or) completed Download can be purged.

val downloads = downloadManager.getAllDownloads()
val chosenDownload = downloads.find { it.id == downloadID }
downloadManager.purgeDownload(chosenDownload)

Listening to Download Events

A DownloadManager.Listener object can be implemented and attached to the DownloadManager to get notified about the different changes in download tasks.

val downloadListener = object : DownloadManager.Listener {
override fun onDownloadStateChanged(download: Download) {
logger.info { "download state changed to :${download.state}" }
}

override fun onDownloadProgress(download: Download) {
logger.info { "download progressed percent :${download.progressPercent}" }
}

override fun onDownloadRemoved(download: Download) {
logger.info { "download removed :${download.id}" }
}

override fun onWaitingForRequirementsChanged() {
logger.error { "Waiting for requirements to be met." }
}
}
downloadManager.addListener(downloadListener)

Download States

The DownloadState represents the state of a particular Download

  • QUEUED - Download request is enqueued but has not started yet due to policy restrictions such as maximum parallel download limit, specified requirements not met, etc.,
  • DOWNLOADING - corresponding download task is active and the corresponding Media resources are downloaded.
  • PAUSED - corresponding download task is suspended and will not be made active until explicitly resumed.
  • FAILED - download has failed because of chunk unavailability / device storage unavailability / other server failures.
  • COMPLETED - Download is complete and ready for offline playback.
  • REMOVING - a transient state which indicates that the download is getting removed from the system.
  • STALE - Indicates the download went into a stale state. This currently has no significance in android and is present only to be inline with iOS states.

Offline Playback

To play a downloaded content , one needs to configure the download ID in the PlayerRequest builder.

val downloads = downloadManager.getAllDownloads()
val completedDownload = downloads.find {
it.id == downloadID && it.state == DownloadState.COMPLETED
} ?: return

val player = PlayerBuilder()
.mediaURL(completedDownload.mediaURL)
.mediaType(MediaType.DASH)
.drmScheme(DRMScheme.WIDEVINE)
.drmLicenseURL("") //can be empty
.downloadID(completedDownload.id)
.build(applicationContext)

player.drmDelegate = DefaultDRMDelegate(coroutineScope)