Download To Go
HLS/Fairplay Downloads feature is available from iOS 10.3+.
Download Manager
The DownloadManager
singleton instance available with Quickplay Player library provides all download functionalities under one umbrella. One could fetch all downloads tasks as well as enqueue and purge download tasks.
Download Task
The DownloadTask
with Quickplay Player library is analogous to a AVAssetDownloadTask
on AVFoundation
. A download task could be created and enqueued with download manager. Upon creating a download task, the task must be resumed to start download.
Download Task States
The following are the download task states
Suspended
- while the task is paused, this is also the initial stateRunning
- while task is executingCancelled
- while the task has been cancelledCompleted
- while the task has completedFailed
- while the task has failedStale
- while the task's asset has been deleted by OS or User
The download task exposes state and progress property which could be observed for state changes.
// Observing on download task's state property
downloadTask.state.add(self) { (oldValue, newValue) in
// handle state change
}
// Remove observers on download task complete, failed or Cancelled
downloadTask.state.remove(self)
Get All Download Tasks
To get all download tasks read allTasks
property with DownloadManager
. To get notified on task additions or removal, the same property can be observed for changes.
let allTasks = FLPlayerFactory.downloadManager.allTasks
Create Download Task
A download task can be created providing content id, name and preferrred bitrate for download. The contentId is to uniquely identify the download task and name is used to show the downloaded asset with the Settings app on iOS. Upon creating a download task, the task must be resumed to start download.
// Creating Player
if let contentURL = URL(string: contentUrl) {
let avURLAsset = AVURLAsset(url: contentURL)
// creating download request
let request = DownloadRequest(contentId: contentId,
contentUrl: avURLAsset.url,
title: name,
preferredVideoBitrate: 50_000)
// enqueue request
let downloadTaskResult = FLPlayerFactory.downloadManager.enqueue(request: request)
switch downloadTaskResult {
case let .success(downloadTask):
// resume task to start download
downloadTask.resume()
case let .failure(error):
// handle failure
}
}
Resume, Pause & Purge
When a download task has been created, it would be in suspended state. The same needs to be resumed to start the download. You could pause or cancel a running task. Purging a download task removes the download task as well its downloaded asset. Please note the persistent drm key if any associated with downloaded asset will not be deleted on purge of download task.
Download Progress
The progress of a download task could be tracked in two ways.
- Observing on
progress
property ofDownloadTask
- Implementing
DownloadManagerDelegate
protocol to get notified on progress and state changes
Listening to Download Task Events
One could listen to download task state changes and downoad task delegate callbacks conforming to DownloadManagerDelegate
protocol and setting the delegate with DownloadManager
.
// yourController must conform to DownloadManagerDelegate protocol
FLPlayerFactory.downloadManager.delegate = yourController;
Offline Playback
To play a downloaded asset, read the avURLAsset
property of the download task, which could be used with Player for playback. While playing fairplay protected downloaded asset, a dummy implementation of LicenseFetcherDelegate
must be updated with LicenseFetcher
to indicate that this content requires a persistent key for playback.
// Offline playback LicenseFetcherDelegate implementation
public class DownloadDelegate: FairplayLicenseFetcherDelegate {
public var skd: String
public var keyDeliveryType: KeyDeliveryType
init(skd: String, keyDeliveryType: KeyDeliveryType) {
self.skd = skd
self.keyDeliveryType = keyDeliveryType
}
public func didProvideApplicationCertificateRequest(for _: FairplayKeyLoadingRequest) {
// No-op
}
public func didProvideLicenseRequest(for _: FairplayKeyLoadingRequest, with _: Data, assetID _: String) {
// No-op
}
}
// delegate creation
let offlinePlaybackDelegate = DownloadDelegate(skd:"<SKD>", keyDeliveryType: .persistentStreamingKey)
// Create a license fetcher to play a downloaded stream
let fetcher = FLPlatformPlayerFactory.fairplaylicenseFetcher()
let avURLAsset = downloadTask.urlAsset
// Update License Fetcher to process persistent key fetching for an AVURLAsset
fetcher.updateLicenseFetcherDelegate(delegate, for: avURLAsset)
// Create a player and play
let player = FLPlatformPlayerFactory.player(asset: avURLAsset)
player.play()
Progressive Download
To play a downloading asset, use the AVURLAsset
instance available with the download task to instantiate the player.
The download task would be paused on playback start. Similarly the task would be resumed automatically when playback stops.
Managing Persistent Keys for Asset
The license key for offline playback can be fetched and persisted using LicenseFetcher
. It is suggested to persist the key prior to download of an asset. The key management is decoupled from downloaded asset, to facilitate manipulating (add, renew or delete) the keys independently. This is because technically a downloaded asset could be played using a valid key, irrespective of key being a persistent or on-demand key. Also, when a key expires, the asset need not be deleted and a new license could be fetched and persisted for the same asset.
if let contentURL = URL(string: playbackAsset.contentUrl) {
let avURLAsset = AVURLAsset(url: contentURL)
// Create License Fetcher Delegate for download
let delegate = FLPlayerFactory.fairplayLicenseFetcherDelegate(
applicationCertificate: appCert,
licenseUrl: licenseURL,
skd: skd,
keyDeliveryType: .download)
// Create a License Fetcher
let fetcher = FLPlatformPlayerFactory.fairplaylicenseFetcher()
// Update License Fetcher to process persistent key fetching for an AVURLAsset
fetcher.updateLicenseFetcherDelegate(delegate, for: avURLAsset)
// Fetch persistent license
fetcher.fetchFairplayLicense(for: skd) { (result: Result<Void, Error>) in
switch result {
case .success:
// create download task and resume
case let .failure(error):
// handle failure
}
}
}
Download in Background
The application must include Background fetch
capability to enable download in background. iOS allows download in background when app is backgrounded or suspended or terminated, but there are certain system limitations on recovery of downloads.
Downloads cannot be recovered in following cases:
- Downloading with XCode Debugger attached
- App killed by pressing home button and swipe up
Retry Download
If a download task fails due to a recoverable error, the application can retry the download. Only tasks in the Failed
state are eligible for retry.
FLPlayerFactory.downloadManager.retry(downloadTask) {
case .success():
// Retry attempt is successful, the task will start downloading
case let .failure(error):
// Retry attempt is not successful
}
Concurrent Downloads
There is no limitations on concurrent downloads. But please note the time for download completion depends on various factors like number of concurrent downloads, network bandwidth and bitrate of downloading content.
Support for Dual Expiry offline Keys
We already support limited validity for persistent offline keys—typically 30 days or until the content lease expires, whichever comes first. This means that downloaded content licenses expire after a specified duration (e.g., X days or hours) from the license start date.
Now, the countdown to expiry also starts when you play the downloaded content for the first time. Once offline playback is initiated, a secondary expiry period—usually 24 or 48 hours—is enforced. So while the initial license remains valid for 30 days, once the content is played offline, the license validity transitions to the configured shorter duration (e.g., 24 or 48 hours). After this secondary window, the content will no longer be accessible offline.
How to Enable Dual Expiry
Prerequisites for QP Edge
- The Quickplay license URL must be configured with the secondary expiry duration (in seconds) using the
afpLicenseCondition.playbackDuration
field. - The Quickplay Play-Auth service must return the
playbackDuration
value as anInteger
, representing the secondary expiry period.
Prerequisites for Applications
-
Read the secondary expiry duration from the
playbackExpiryDuration
field in thePlaybackAsset
. -
Include the
playbackExpiryDuration
when creating the download request:let downloadRequest = DownloadRequest(
contentId: contentId,
contentUrl: contentURL,
title: title,
preferredVideoBitrate: 50000,
metadata: "Fairplay",
expiration: rentalExpiryTime,
playbackExpiryDuration: TimeInterval(playbackExpiryDuration),
skd: skd
) -
Monitor the expiration property for changes after the first playback:
downloadTask.expiration.add(self) { oldExpiration, newExpiration in
// This gets called when the expiration updates.
// Once playback is attempted for the first time, the expiration will switch to the secondary expiry value.
// Update the UI to reflect the new expiration time.
}Also, implementing
DownloadManagerDelegate
protocol to get notified expiration property changes.warningNew license fetcher should be created for each offline playback.
Auto Purge on Expiry
The DownloadManager supports an auto-purge feature that automatically removes expired downloads from storage, eliminating the need for manual cleanup.
To enable this feature, set the following property to true:
FLPlayerFactory.downloadManager.downloadAutoPurgeOnExpiry = true
The default value of
downloadAutoPurgeOnExpiry
is false.To receive notifications when download tasks are purged, implement the
DownloadManagerDelegate
protocol. This allows you to track and respond to purge events as needed.