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
}
}
- The license will have a specific validity and can be renewed in background using the
renewLicense
APIs in theDownloadManager
. - Downloads expiry handling is based on both the license duration and remaining playback duration. For example the downloaded asset license can be valid
for 30 days but when the first attempt of playback is made the remaining playback duration value is checked and this could be for example the next 48
hours when the asset can be played. If one of the license expiry conditions is met the appropriate error is reported.
DownloadManager.Listener
notifies the application client about the change in download's expiry date by invokingonDownloadExpiryChanged
callback. - 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 aDefaultDRMDelegate
which can be created as below:
val drmDelegate = DefaultDRMDelegate(coroutineScope)
Download Service
When QuickPlay Player SDK version 7.1.x is used, all downloads are handled by Android Download Service implementation and this service MUST be declared in application's AndroidManifest as follows:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
// other permissions omitted
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<application ...>
<service android:name="com.quickplay.vstb7.player.offline.service.DownloadService"
android:exported="false"
android:foregroundServiceType="dataSync">
<intent-filter>
<action android:name="androidx.media3.exoplayer.downloadService.action.RESTART"/>
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</service>
</application>
</manifest>
Download Manager
Download Requirements
DownloadRequirements
is an enum of all possible requirements available to configure downloads. The only default requirement is DeviceRequirements.NETWORK
.
Name | Description |
---|---|
NETWORK | Represents the requirement that the device should have a network connection. |
NETWORK_UNMETERED | Represents the requirement that the device should have an unmetered network connection. |
DEVICE_IDLE | Represents the requirement that the device should be idle. |
DEVICE_CHARGING | Represents the requirement that the device should be charging. |
DEVICE_STORAGE_NOT_LOW | Represents 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
)
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.
Name | Type | Default value | Description |
---|---|---|---|
maxDownloadTasks | Int | 50 | The maximum number of tasks queued for download. (Must be greater than 0) |
maxParallelDownloads | Int | 3 | The maximum number of tasks parallel downloads. (Must be greater than 0) |
minRetryCount | Int | 5 | The 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. |
requirements | List<DownloadRequirements> | DownloadRequirements.NETWORK | The list of DownloadRequirements to be fulfilled for every download task. |
downloadsAutoPurgeOnExpiry. | Boolean | false | Enables automatic deletion of all the downloads which expired. |
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.
On Android platform when downloadsAutoPurgeOnExpiry
is set to true then the application client MUST also add to the application's AndroidManifest.xml
the following:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
// other permissions omitted
<uses-permission android:name="android.permission.USE_EXACT_ALARM"/> // If the target application api level is 33
<uses-permission android:name="android.permission.SCHEDULE_EXACT_ALARM"> // If the target application api level is 31
<application ...>
<receiver android:name="com.quickplay.vstb7.player.offline.ExpiredDownloadAlarmReceiver"/>
</application>
</manifest>
Creating a Download Manager
The DownloadManager can be initialized by passing app context on Application
's onCreate() lifecycle method.
// When using QuickPlay Player SDK version 7.0.x
DownloadManager.create(
this,
logger,
drmDelegate, // Refer to Custom License Request handling section
downloadProperties
).resumeDownloads()
// or when using QuickPlay Player SDK version 7.1.x
DownloadManager.create(
this,
logger,
drmDelegate, // Refer to Custom License Request handling section
downloadProperties
)
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()
- 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)