iOS App Developer's Guide to Video Downloading and Offline Playback with HLS in the Brightcove Player SDK for iOS, version 7.0.1.10
The Brightcove Native Player SDK allows you to download and play back HLS videos, including those protected with FairPlay encryption. Downloaded videos can be played back with or without a network connection.
- Brightcove Native Player SDK v6.6.2+
- Brightcove Account with Dynamic Delivery
iOS does not allow FairPlay-protected video to display in a simulator, nor does it allow video downloads to a simulator, so it's important to develop on an actual device.
When you request a video download, you will be given a token that is used to keep track of both the active download and the offline video in device storage. The token is of type BCOVOfflineVideoToken
, and is referred to as a token rather than an object because it can be persisted, and is valid across instances of the app.
Videos are stored in the app's Library directory, in a subdirectory determined by iOS. Apple specifically requires that these videos stay in this location. Be aware that videos can be deleted by iOS under low-storage conditions, and users can delete downloaded videos directly from the iOS Settings app. Also, videos and their associated metadata are not backed up to iCloud.
A FairPlay license can be requested either when the video download is requested, or preloaded ahead of time with a separate method call. You can specify either a purchase license, or a rental license with an associated rental duration. The license rental duration begins from the moment the request is made with the license server.
When a download begins, you can receive notification of progress and completion by setting your own class as a delegate of the BCOVOfflineVideoManager
singleton object. Be sure that your object inherits the BCOVOfflineVideoManagerDelegate
protocol.
By default, downloads are only allowed over WiFi connections, but this can be changed to enable downloads over cellular as well. Similarly, during playback, iOS may request some additional resources (like subtitle tracks if they were not downloaded). This will only happen on WiFi, but can be changed as well.
The BCOVOfflineVideoManager
is a singleton class that manages all video downloads. Before using any BCOVOfflineVideoManager
methods, you should call
BCOVOfflineVideoManager.initializeOfflineVideoManager(with: self,
options: [kBCOVOfflineVideoManagerAllowsCellularDownloadKey : true])
The options dictionary may be nil, but this example shows how to allow downloads over cellular networks.
After initialization, you can refer to the Offline Video Manager with the BCOVOfflineVideoManager.sharedManager
property.
As part of setup, you also need to create and assign an authorization proxy to handle the details of FairPlay key exchange. You can use the supplied Brightcove FairPlay proxy to do this work:
// Publisher/application IDs not required for Dynamic Delivery
authProxy = BCOVFPSBrightcoveAuthProxy(withPublisherId: nil,
applicationId: nil)
BCOVOfflineVideoManager.shared().authProxy = authProxy
If you want to change the kBCOVOfflineVideoManagerAllowsCellularDownloadKey
value later on in your app to allow or disallow cellular access, you can call BCOVOfflineVideoManager.initializeOfflineVideoManager(with:options:)
again with a new options dictionary and/or delegate.
NOTE: Changing the value of kBCOVOfflineVideoManagerAllowsCellularDownloadKey
will not have an effect on active downloads, only downloads initialized after the value has been changed.
In your user interface, you should indicate if a video is eligible for download. To do this, examine the BCOVVideo.canBeDownloaded
property. This Boolean property checks the status of the video's offline_enabled
flag that is settable through the Brightcove CMS API.
There are two approaches to download a video. The first utilizes an array of AVMediaSelection
objects and the second utilizes AVAssetDownloadConfiguration
which was introduced in iOS 15.
BCOVOfflineVideoManager.shared()?.requestVideoDownload(video,
mediaSelections: nil,
parameters: parameters,
completion: { (offlineVideoToken: String?,
error: Error?) in
// store offlineVideoToken or handle error
})
BCOVOfflineVideoManager.shared()?.requestVideoDownload(video,
downloadConfiguration: downloadConfiguration,
parameters: parameters,
completion: { (offlineVideoToken: String?,
error: Error?) in
// store offlineVideoToken or handle error
})
The video parameter is a normal BCOVVideo
object that you get by querying the BCOVPlaybackService
. You should make sure that its canBeDownloaded
property is YES
before downloading.
The parameters argument is an NSDictionary
in which you specify the terms for the offline FairPlay license, preferred bitrate, and other values.
The following are the valid parameter keys that you may use when requesting a video download:
- kBCOVFairPlayLicensePlayDurationKey
- kBCOVFairPlayLicensePurchaseKey
- kBCOVFairPlayLicenseRentalDurationKey
- kBCOVOfflineVideoManagerRequestedBitrateKey
Here are some examples of video download parameters for common license-types:
// A purchased video download
// In this case only `kBCOVFairPlayLicensePurchaseKey` is required
// but you may also specify the bitrate you'd like to download
let parameters: [String:Any] = [
kBCOVFairPlayLicensePurchaseKey: true,
// kBCOVOfflineVideoManagerRequestedBitrateKey: 1000000
]
// A rented video valid for 30 days
// You could also specify the bitrate as in the purchased video example
let parameters: [String:Any] = [
// 30 days in seconds
kBCOVFairPlayLicenseRentalDurationKey: (60 * 60 * 24 * 30)
]
// A rented video valid for 30 days or 24 hours after playback begins
// This is known as "Dual Expiry" or a "Rental Profile"
// You could also specify the bitrate as in the purchased video example
let parameters: [String:Any] = [
// 30 days in seconds
kBCOVFairPlayLicenseRentalDurationKey: (60 * 60 * 24 * 30),
// 24 hours in seconds
kBCOVFairPlayLicensePlayDurationKey: (60 * 60 * 24)
]
NOTE: If a license expires during playback of a video the video will not stop, but attempting to reload the video will result in a "license expired" error.
The completion handler is where you asynchronously receive the offline video token or the error. You can store a reference to this offline video token if the error is nil. You will receive notficiation of progress and completion thorugh delegate methods.
If you plan to download multiple FairPlay-protected videos, it's a good idea to preload all the FairPlay licenses beforehand, because FairPlay license exchange cannot happen while the app is in the background. Preload a FairPlay license with a similar call:
BCOVOfflineVideoManager.shared()?.preloadFairPlayLicense(video,
parameters: parameters,
completion: { (offlineVideoToken: String?,
error: Error?) in
// store offlineVideoToken or handle error
})
An offline video token will be established for the download at this point. After the license has been acquired, you can then request the video download without any parameters:
BCOVOfflineVideoManager.shared()?.requestVideoDownload(video,
mediaSelections: nil,
parameters: nil,
completion: { (offlineVideoToken: String?,
error: Error?) in
// store offlineVideoToken or handle error
})
BCOVOfflineVideoManager.shared()?.requestVideoDownload(video,
downloadConfiguration: downloadConfiguration,
parameters: nil,
completion: { (offlineVideoToken: String?,
error: Error?) in
// store offlineVideoToken or handle error
})
At this point, the download will continue even if the user sends the app to the background. Keep in mind, however, that a FairPlay license rental duration begins from the time when the license is requested.
Subtitle, caption and audio tracks for a language are known collectively as a Media Selection and are represented by Apple's AVMediaSelection
class. A video can have multiple media selections, for example, English, French and Spanish. The language settings of the device determine the preferred media selection, for example, English.
Media Selection are properties of the AVAsset
class. BCOVOfflineVideoManager
provides the utility method BCOVOfflineVideoManager.shared().urlAsset(for:)
to help you assemble AVMediaSelection
objects of interest. Refer to the "Finding Media Selections" methods of AVAsset
.
Internally, the SDK manages the download of a video and its secondary tracks using a single AVAggregateAssetDownloadTask
.
Downloading for offline viewing involves these basic steps:
- Choose the media selections to be downloaded.
- Create an
[AVMediaSelection]
array of your media selections, and pass it toBCOVOfflineVideoManager.shared().requestVideoDownload(_,mediaSelections:parameters:)
or passnil
to automatically download the preferredAVMediaSelection
objects. - Track download progress using the
offlineVideoToken:aggregateDownloadTask:didProgressTo:forMediaSelection:
of theBCOVOfflineVideoManagerDelegate
protocol. Note that when downloading additional media selections, progress callbacks are made for each downloaded item individually, with each ranging in progress from 0% to 100%.
The Discover how to download and play HLS offline session from WWDC 2020 covers reccomended methods of gathering the media selections (4:55) you'd like to download in addition to handling download progress for an AVAggregateAssetDownloadTask
(5:22).
Internally, the SDK manages the download of a video and its secondary tracks using a single AVAssetDownloadTask
.
Downloading for offline viewing involves these basic steps:
- Choose the media selections to be downloaded.
- Create an
[AVMediaSelection]
array of your media selections, and set as the value forprimaryContentConfiguration.mediaSelections
on yourAVAssetDownloadConfiguration
- You can use the
offlineVideoToken:assetDownloadTask:willDownloadVariants:
delegate method to verify which variants will be downloaded. - Track download progress using the
offlineVideoToken:assetDownloadTask:didProgressTo:
of theBCOVOfflineVideoManagerDelegate
protocol.
The Explore HLS variants in AVFoundation session from WWDC 2021 covers selecting primary and auxilary media selections on an AVAssetDownloadConfiguration
.
The PlayerUI built-in controls will automatically detect and present your available Sideband Subtitles in the same, familiar closed caption control for you, so don't need to do anything if you are using a standard BCOVPUIPlayerView
.
If you are creating your own user interface, you should check the downloaded BCOVVideo
for a kBCOVOfflineVideoUsesSidebandSubtitleKey
property. If set to YES, you should then get the available subtitle languages from the kBCOVOfflineVideoManagerSubtitleLanguagesKey
. This is an array of standard language codes that can be converted to display format to the end user.
To present the subtitles for a particular language, set the kBCOVOfflineVideoManagerPlaybackSubtitleLanguageKey
in the active BCOVPlaybackController
's options dictionary with your selected language as the value.
If the kBCOVOfflineVideoUsesSidebandSubtitleKey
is missing or NO, you can assume that the video is using standard iOS subtitles, and you can set your media selection on the AVPlayer
as always.
If you do not specify a bitrate for your download, you will get the lowest rendition that has a video track. To pick a specific variant based on the bitrate or resolution, you can call:
BCOVOfflineVideoManager.shared().variantAttributesDictionaries(for: video) { (variantAttributesDictionariesArray: [[AnyHashable : Any]]?,
error: Error?) in
// Check attributes of each variant
}
"Variants" are video renditions with different bitrates and/or video dimensions.
The variantAttributesDictionariesArray
array will contain an array of dictionaries, one for each variant in the HLS manifest. Each dictionary contains a variety of attributes, the most important of which are the "BANDWIDTH" and "RESOLUTION values. You can use these to determine the best variant for your application.
Once you have chosen the best variant, you can use its "BANDWIDTH" value as your preferred bitrate.
If all you need are the available bitrates for a video, you can call:
BCOVOfflineVideoManager.shared().variantBitrates(for: video) { (bitrates: [NSNumber]?,
error: Error?) in
// Check list of available bitrates
}
Then you can pass your preferred bitrate as the value for the kBCOVOfflineVideoManagerRequestedBitrateKey
key.
let preferredBitrate = 1996500 // value in bits per second
var parameters: [String:Any] = [
// Purchase license
kBCOVFairPlayLicensePurchaseKey: true,
// Specify variant using the bitrate
kBCOVOfflineVideoManagerRequestedBitrateKey: preferredBitrate
]
BCOVOfflineVideoManager.shared().requestVideoDownload(video,
mediaSelections: mediaSelections,
parameters: parameters) { (offlineVideoToken: String?,
error: Error?) in
// store offlineVideoToken or handle error
}
You can select a specific AVAssetVariant
or can use an NSPredicate
to pick an appropriate variant.
// Selecting a specific variant
let desiredVariantQualifier = AVAssetVariantQualifier(variant: desiredVariant)
let downloadConfiguration = AVAssetDownloadConfiguration(asset: avURLAsset, title: videoName)
downloadConfiguration.primaryContentConfiguration.variantQualifiers = [desiredVariantQualifier]
// Using an NSPredicate
let peakBitRatePredicate = NSPredicate(format: "peakBitRate > 1268300")
let predicateQualifier = AVAssetVariantQualifier(predicate: peakBitRatePredicate)
let downloadConfiguration = AVAssetDownloadConfiguration(asset: avURLAsset, title: videoName)
downloadConfiguration.primaryContentConfiguration.variantQualifiers = [predicateQualifier]
Before a download is requested, it's a good idea to make sure there is enough space in device storage for the download.
The method BCOVOfflineVideoManager.shared().estimateDownloadSize(_,options:completion:)
can be used to get an estimate of how much space, in megabytes, the download will require. If there is not enough space for a download, it will result in an error.
iOS needs a cushion to run properly, so you should not allow downloads to fill up all the free space on the device. If you use up all the free storage, the system may start deleting cached data in other apps, and may eventually remove a downloaded video from your own app.
The Brightcove SDK does not enforce any free space requirements, but we suggest making sure you leave at least 1 GB free to prevent storage cleanup policies from taking effect.
The BCOVOfflineVideoManager
has an offlineVideoTokens
property that returns an array of all the current offline video tokens. This includes tokens for videos that are currently preloaded or downloading, and tokens for videos that are already in storage and have finished downloading. You can examine this property at any time to present to the user a list of videos that are downloading, or a list of videos that have been downloaded and are available for offline playback.
For any given offline video token, you can determine its status by calling BCOVOfflineVideoManager.shared().offlineVideoStatus(forToken:)
. This method returns a BCOVOfflineVideoStatus
object that contains various bits of useful data about the download:
- Download state (requested, started, suspended, completed, canceled, or failed)
- License request time
- Start time
- End time (if finished)
- Progress (percentage)
- Associated
AVAssetDownloadTask
(if the download is still in progress)
This call is essential for providing feedback in your user interface about what each download is doing.
To get information about the status of all offline videos at once, use the BCOVOfflineVideoManager.offlineVideoStatus
property. This returns an array of BCOVOfflineVideoStatus
objects, one for each offline video.
You can find out when the FairPlay license for an offline video expires by querying the Offline Video Manager as follows:
let licenseExpirationDate: Date = BCOVOfflineVideoManager.shared().fairPlayLicenseExpiration(offlineVideoToken)
The method returns the expiration of the FairPlay license as an NSDate
object. If the FairPlay license is a purchase license, or the video is not encrypted with FairPlay, NSDate.distantFuture
is returned. If the FairPlay license is missing, nil
is returned.
BCOVVideo
objects contain a host of metadata stored in a properties dictionary. When working with offline video tokens, you can access this associated metadata by first converting the token to a BCOVVideo
object, and then accessing the properties directly.
This example shows how to get the file path to the offline poster image associated with the downloaded video token:
let video = BCOVOfflineVideoManager.shared().videoObject(fromOfflineVideoToken: offlineVideoToken)
if let posterPathString = video.properties[kBCOVOfflineVideoPosterFilePathPropertyKey] as? String {
let posterImage = UIImage(contentsOfFile: posterPathString)
}
Here are some of the more useful dictionary keys for the offline-specific properties from the BCOVVideo
's properties dictionary:
kBCOVOfflineVideoTokenPropertyKey
Offline video token for this video.
kBCOVOfflineVideoLicenseRequestTimePropertyKey
Time that the FairPlay license was requested.
This time is stored as an NSNumber representing the of seconds since the standard Date
reference date.
You can generate an Date
object from this number with: Date.init(timeIntervalSinceReferenceDate:)
.
kBCOVOfflineVideoDownloadStartTimePropertyKey
Time that the offline video download was requested.
This time is stored as an NSNumber representing the number of seconds since the standard Date
reference date.
This value is useful when sorting by the time each download process started.
You can generate an Date
object from this number with: Date.init(timeIntervalSinceReferenceDate:)
.
kBCOVOfflineVideoDownloadEndTimePropertyKey
Time that the offline video download completed (whether normally, cancelled, or failed).
This time is stored as an NSNumber representing the number of seconds since the standard Date
reference date. This value is useful when sorting by the time each download completes. This value is not set until the download completes.
You can generate an Date
object from this number with: Date.init(timeIntervalSinceReferenceDate:)
.
kBCOVOfflineVideoLicenseAbsoluteExpirationTimePropertyKey
Time that the offline video download will expire if using a FairPlay license. This value is only stored with version 6.2.2 and later of the iOS Native Player SDK.
This time is stored as an NSNumber representing the number of seconds since the standard NSDate
reference date. This value is set every time a FairPlay license is acquired.
You can generate an Date
object from this number with: Date.init(timeIntervalSinceReferenceDate:)
.
kBCOVOfflineVideoOnlineSourceURLPropertyKey
URL of the original online video that was downloaded to storage for this BCOVVideo
object, as an NSString
.
Note that this URL may have expired, so this is primarily useful as a reference.
kBCOVOfflineVideoThumbnailNamePropertyKey
Name of the downloaded thumbnail image file in device storage, as an NSString
.
This name is used to generate the full path of the file at runtime.
kBCOVOfflineVideoThumbnailFilePathPropertyKey
Full path to the downloaded thumbnail image file in device storage, as an NSString
.
Even if the path is present, the image file may not be present. Due to app sandboxing, this path may change when the app is launched, so you should not store the full path.
kBCOVOfflineVideoPosterNamePropertyKey
Name of the downloaded poster image file in device storage, as an NSString
.
This name is used to generate the full path of the file at runtime.
kBCOVOfflineVideoPosterFilePathPropertyKey
Full path to the downloaded poster image file in device storage, as an NSString
.
Even if the path is present, the image file may not be present. Due to app sandboxing, this path may change when the app is launched, so you should not store the full path.
kBCOVOfflineVideoRelativeFilePathPropertyKey
Relative file path of the downloaded FairPlay video bundle in device storage, as an NSString
.
This name is used to generate the full path of the file at runtime.
kBCOVOfflineVideoFilePathPropertyKey
Full path to the downloaded FairPlay video bundle in device storage, as an NSString
.
Due to app sandboxing, this path may change when the app is launched, so you should not store the full path.
kBCOVOfflineVideoUsesSidebandSubtitleKey
Boolean value, stored as an NSNumber, that is set to YES when this video is using Sideband Subtitles.
When using Sideband Subtitles, you can get the array of downloaded languages from the kBCOVOfflineVideoManagerSubtitleLanguagesKey
property.
When playing back the video, you can select the subtitle to present by setting it as the value for the kBCOVOfflineVideoManagerPlaybackSubtitleLanguageKey
key in the BCOVPlaybackController
's options property.
To play an offline video, convert the offline video token to a BCOVVideo
object, and then play the video as you would any normal online video object. Since videos can be deleted without warning by iOS, or the user, it's a good idea to check that the video is playable.
Here's how you might create the playback controller in your setup code for playing FairPlay videos:
let sdkManager = BCOVPlayerSDKManager.sharedManager()
// Create standard authorization proxy. No pub/app id needed for Dynamic Delivery.
// If desired, you can use the same authProxy that you created when you initialized the Offline Video Manager.
let authProxy = BCOVFPSBrightcoveAuthProxy(withPublisherId: nil,
applicationId: nil)
// Set up the session provider chain
let psp = sdkManager.createBasicSessionProvider(withOptions: nil)
let fps = sdkManager.createFairPlaySessionProvider(withAuthorizationProxy: authProxy,
upstreamSessionProvider: psp)
// Create the playback controller
let playbackController = sdkManager.createPlaybackController(withSessionProvider: fps,
viewStrategy: nil)
// Configure and save
playbackController.isAutoAdvance = true
playbackController.isAutoPlay = true
playbackController.delegate = self
self.playbackController = playbackController
Then, to play the video, convert the token to a BCOVVideo
object, check its availability, and pass to the playback controller:
if let video = BCOVOfflineVideoManager.shared().videoObject(fromOfflineVideoToken: offlineVideoToken) {
if video.playableOffline {
playbackController?.setVideos([video])
} else {
// alert user that the video needs to be re-downloaded
}
}
Note that video.playableOffline
indicates whether a video is fully downloaded and stored locally and can therefore be played without a network connection. A video download in progress can be played even if video.playableOffline
is false
. Refer to the section "Playback During Download".
When a video is being downloaded, you can pause, resume, or cancel the download using the following BCOVOfflineVideoManager
methods:
offlineVideoManager.pauseVideoDownload(_)
offlineVideoManager.resumeVideoDownload(_)
offlineVideoManager.cancelVideoDownload(_)
You should use these methods rather than operating on the internal AVAssetDownloadTask
itself.
Starting with version 6.2.2 of the iOS Native Player SDK, you can renew the FairPlay license for a downloaded video without re-downloading the video. The device must be online for license renewal to succeed.
To renew a license, call -renewFairPlayLicense:video:parameters:completion:
with the offline video token for your previously-downloaded video, and a parameter dictionary specifying the new license terms. The parameter dictionary must not contain bitrate or subtitle language information.
You should also specify a BCOVVideo
object for the video
argument. This video object should be the same video that you originally downloaded, but freshly retrieved from the Playback API, either directly, or through the BCOVPlaybackService
class. This video object's data are not re-downloaded; the object is used to refresh your license exchange URLs in case the original ones used to download the video have expired. If the referenced video is no longer available through the Playback API, you can use a substantially similar video, meaning the video should be from the same account, and also have the same FairPlay configuration.
For example, here is how you can renew a FairPlay license with a new 30-day rental license:
// Get fresh video object for this offline video
let configuration = [
BCOVPlaybackService.ConfigurationKeyAssetID: offlineVideoID
]
playbackService.findVideo(withConfiguration: configuration,
queryParameters: nil) { (video: BCOVVideo?,
jsonResponse: Any?,
error: Error?) in
if let video {
let parameters = [
// Renew license with a 30-day rental
kBCOVFairPlayLicenseRentalDurationKey: ( 30 * 24 * 60 * 60 ) // 30 days in seconds
]
// Request license renewal
BCOVOfflineVideoManager.shared().renewFairPlayLicense(offlineVideoToken,
video: video,
parameters: parameters) { (offlineVideoToken: String?,
error: Error?) in
// Handle Errors
}
}
}
If you are using the Playback Authorization Service you'll need to use the renew FairPlay license method that accepts an authorization token:
// Request license renewal
BCOVOfflineVideoManager.shared().renewFairPlayLicense(offlineVideoToken,
video: video,
authToken: authToken,
parameters: parameters) { (offlineVideoToken: String?,
error: Error?) in
// handle errors
}
When license renewal is finshed, the completion block will be called with the same offline video token that was passed in, plus an NSError
indicating any problem that occurred (or nil
if no error). This block is not called on any specific thread.
As part of Dynamic Delivery, a reference to the FairPlay application certificate associated with each video is stored inside each FairPlay-encrypted BCOVVideo
object. The certificate is retrieved from a remote server as needed, so you do not need to load or set any FairPlay application certificates on your own.
The same application certificate is used across each Brightcove account, so there will often be a single application certificate used for all your videos. Application certificates are cached for re-use so they will typically be retrieved only once.
iOS allows HLS video downloads to run in a background thread, even if an app is suspended or terminated under certain conditions.
When an app is relaunched, BCOVOfflineVideoManager
will restore downloads whenever possible, but it is important to understand the limitations.
After an app is terminated, the SDK can recover downloads in progress in the following cases:
- App termination due to memory pressure in foreground
- App termination due to high memory usage when the app is suspended
- App crashes (null pointers, exceptions, etc.)
- App termination while suspended due to limited system resources
The SDK can't recover downloads in progress when:
- Running with Xcode (when Xcode terminates the process)
- Termination via the App Switcher (e.g., on devices with a home button, a double-home-press and slide up)
- Termination while the app is suspended and the device reboots
Downloads are restored once during the call to initializeOfflineVideoManagerWithDelegate:options:
. Downloads that cannot be recovered will be reported immediately through the normal delegate methods; at this point you can delete them like any other video.
When downloading multiple videos, you can download them all at once, or with a download queue.
With either technique, it's a good idea to preload any FairPlay licenses before starting any downloads, because although downloads can run in the background, FairPlay license exchange cannot happen while the app is in the background.
Concurrent Downloads
It's possible to download multiple videos at the same time, but performance may take a hit, so it's probably not a good idea to download more than a couple of videos at the same time.
When you request a video download, the first thing that happens is FairPlay license generation and key exchange, if needed. After this, the download proceeds to completion.
If you request multiple downloads at the same time, it may take a while for the licence and key requests for some of the videos to begin processing and/or complete. It's important that the key exchange for all the videos happen before allowing the user to move the app to the background.
If the app moves to the background before all the key exchanges are complete, FairPlay may not get a chance to make its callbacks in a timely fashion, and the download with an incomplete license will result in an error.
There are several ways to address this:
- Only allow the user to download one or two videos at a time. This ensures that the license is secured as soon as possible, and that the video will download successfully.
- If allowing multiple concurrent downloads, inform the user that they should not move the app to the background until all the downloads have started to report progress. You could simply put up a "please wait" notice until you detect progress from all the downloads.
You may want to limit the number of allowed concurrent downloads for practical reasons. The more downloads you begin at once, the longer it takes for them to begin and complete. You should test with your own videos to see what works best for your app.
Sequential Downloads
You can preload FairPlay licenses for a set of videos, and then create a queue for downloading videos one at a time until they are all complete.
When doing this it's a good idea to enable the "Background Fetch" mode for the application so that you can be woken up periodically to handle download completions and kick off new downloads.
Please note that iOS does not wake up your application from the background immediately when a download has completed. This can take 10 to 15 minutes or longer; iOS has complete control over the timing and seems to minimize this activity for maximum energy efficiency. In some cases, when downloading multiple videos in the background, iOS may not complete your download at all in the background, opting instead to finish the download when the application has been brought back to the foreground. For the most reliable results, you may want to preload a single video license, download one video, and then post a notification to the user to return to the foreground to kick off another download.
While a movie is downloading, you can play the movie by passing a BCOVVideo
object created from the offline video token to the BCOVPlaybackController
just like any other movie. We do this by using an internal reference stored in the currently active AVAssetDownloadTask
(this is the method recommended by Apple to make the download and playback as efficient as possible).
Doing this has several potentially unexpected side effects:
-
When you start playing, progress on the download task may stop. The
AVPlayer
appears to take over all network activity to give priority to buffering and displaying the playing video. This is normal behavior. -
When you stop playing, the download task may not resume. This will happen if the video's session is still active in the
BCOVPlaybackController
. To clear out the current session, you can play a new video, or delete the playback controller. Be sure you don't store the current session in a strong property.
You cannot stream an offline HLS video to an AirPlay device for playback. This is an AVFoundation limitation.
Unexpected network activity can occur when playing the same offline video two times in a row in a certain way. If you play a video with one playback controller, and then immediately play the same video with another playback controller, the AVPlayer
for the second video attempts to retrieve its video data from the Internet rather than from local storage. If the device is not online then the AVPlayer
will not play the second video.
This happens when two AVPlayer
s and their associated objects are in memory together for a brief time referencing the same video in local storage. It's unknown exactly why the AVPlayer
switches to playing the online version; we have isolated this behavior and submitted a bug report to Apple (39354411).
When possible, a better way to play the same video twice is to simply seek the same playback controller to timecode zero and play again.
If this is not possible, and you still need to create another playback controller, another alternative is to remove the first AVPlayer
's current AVPlayerItem
when you are done with the current session, and just before you destroy the playback controller. You can accomplish this with a single call:
session.player.removeAllItems()
Users can directly examine and optionally delete downloaded videos using the Settings app.
The displayed video name is taken from the "name" property of the BCOVVideo
object. If the "name" property is not present, the offline video token's value will be used instead.
To provide a more customized experience, you can set a display name for the video asset in the options dictionary passed to -requestVideoDownload:mediaSelections:parameters:completion:
(or when preloading the license). Use the kBCOVOfflineVideoManagerDisplayNameKey
key to set an NSString
as the new asset display name.
Note that iOS uses the asset name string as part of the downloaded video's file path, so you should avoid characters that would not be valid in a POSIX path, like a "/" character.
You may choose to replace these with more standardized names using the kBCOVOfflineVideoManagerDisplayNameKey
option.
The displayed video name is taken from value you use for title
argument when initializing AVAssetDownloadConfiguration
.
You can also provide a value for the artworkData
property on AVAssetDownloadConfiguration
. This will display an image next to the video name in the Settings app.
if let thumbnailURLStr = video.properties[BCOVVideo.PropertyKeyThumbnail] as? String, let thumbnailURL = URL(string: thumbnailURLStr) {
do {
let artworkData = try Data(contentsOf: thumbnailURL)
downloadConfiguration.artworkData = artworkData
} catch {
// Handle Error
}
}