Download To Go
Note: The HLS/FairPlay Downloads feature is available from iOS 10.3 and later.
Download manager
The DownloadManager singleton instance available with the Quickplay Player
library provides all download functionalities. You can fetch all download
tasks and enqueue and purge download tasks.
Download task
The DownloadTask with the Quickplay Player library is analogous to an
AVAssetDownloadTask on AVFoundation. You can create and enqueue a download
task with the download manager. After creating a download task, you must
resume the task to start the download.
Download task states
A download task can be in one of the following states:
- Suspended - The task is paused (also the initial state)
- Running - The task is executing
- Cancelled - The task has been canceled
- Completed - The task has completed
- Failed - The task has failed
- Stale - The task's asset has been deleted by the OS or user
The download task exposes state and progress properties that you can
observe for state changes.
// Observe the download task's state property
downloadTask.state.add(self) { (oldValue, newValue) in
// Handle state change
}
// Remove observers when the download task completes, fails, or is canceled
downloadTask.state.remove(self)
Get all download tasks
To get all download tasks, read the allTasks property with DownloadManager.
To get notified of task additions or removals, observe the same property for
changes.
let allTasks = FLPlayerFactory.downloadManager.allTasks
Create download task
You can create a download task by providing content ID, name, and preferred bitrate for download. The content ID uniquely identifies the download task, and the name is used to show the downloaded asset in the Settings app on iOS. After creating a download task, you must resume the task to start the 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
progressproperty ofDownloadTask - Implementing
DownloadManagerDelegateprotocol 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.playbackDurationfield. - The Quickplay Play-Auth service must return the
playbackDurationvalue as anInteger, representing the secondary expiry period.
Prerequisites for Applications
-
Read the secondary expiry duration from the
playbackExpiryDurationfield in thePlaybackAsset. -
Include the
playbackExpiryDurationwhen 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
DownloadManagerDelegateprotocol 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 = trueThe default value of
downloadAutoPurgeOnExpiryis false.To receive notifications when download tasks are purged, implement the
DownloadManagerDelegateprotocol. This allows you to track and respond to purge events as needed.Offline Playback Behavior at Expiration
- Auto-Purge Disabled : Ongoing playback is unaffected. However, if the user seeks after expiration, playback will fail with
Error Code: 400208. Offline Playback Behavior at Expiration - Auto-Purge Enabled : Ongoing playback is not interrupted. Expiration only affects future playback sessions, which will fail with
Error Code: 400208.
- Auto-Purge Disabled : Ongoing playback is unaffected. However, if the user seeks after expiration, playback will fail with