The purpose of this sample application is to provide a reference code that can help using Axinom DRM with AVFoundation framework to play FairPlay protected HTTP Live Streams (HLS) hosted on remote servers as well as giving an example of persisting FairPlay protected and non-protected HLS streams on disk for offline playback.
You can use the example code provided in this sample to build your own application that integrates the Axinom DRM with AVFoundation.
Another major usage of this sample raises from its capability of tracing the steps performed during the playback of protected and non-protected assets such as FairPlay content protection related activity, DRM license acquisition from Axinom DRM licensing server, as well as AVPlayerItem
and AVPlayer
statuses, buffer events, and Access log and Error log events associated with AVPlayerItem
.
Sample application's Player View has a togglable Console overlay, that allows user to observe verbose logging of these steps. Console output can be cleared and copied to the device clipboard. Player buttons behind the Console overlay can be clicked only when the Console overlay is hidden.
Build and run the sample on an actual device running iOS 13.1 or later using Xcode. The APIs demonstrated in this sample do not work on the iOS Simulator.
This sample provides a list of HLS Streams that you can playback by tapping on the UITableViewCell corresponding to the stream. If you wish to cancel an already running AVAggregateAssetDownloadTask
or delete an already downloaded HLS stream from disk, you can accomplish this by tapping on the accessory button on the UITableViewCell
corresponding to the stream you wish to manage.
If you wish to download an HLS stream initiating an AVAggregateAssetDownloadTask
, you can accomplish this by tapping the multifunction button (Save/Delete/Cancel) button on Player View Controller. Canceling and deleting downloaded stream actions are can also be performed on Player View Controller by tapping the multifunction button (Save/Delete/Cancel).
When the sample creates and initializes an AVAggregateAssetDownloadTask
for the download of an HLS stream, only the default selections for each of the media selection groups will be used (these are indicated in the HLS playlist EXT-X-MEDIA
tags by a DEFAULT attribute of YES).
If you wish to add your own HLS streams to test with using this sample, you can do this by adding an entry into the Streams.json that is part of the Xcode Project. There are two important keys you need to provide values for:
title: What the display name of the HLS stream should be in the sample, also used as a file name for the downloaded asset and for storage of the persistent key.
videoUrl: The URL of the HLS stream's master playlist.
licenseServer: Axinom DRM License Server URL.
fpsCertificateUrl: FairPlay Streaming Certificate URL.
licenseToken: License Token for Content Key Request.
If any of the streams you add are not hosted securely, you will need to add an Application Transport Security (ATS) exception in the Info.plist. More information on ATS and the relevant plist keys can be found in Apple documentation:
Information Property List Key Reference - NSAppTransportSecurity: https://developer.apple.com/library/ios/documentation/General/Reference/InfoPlistKeyReference/Articles/CocoaKeys.html#//apple_ref/doc/uid/TP40009251-SW33
Saving HLS streams for offline playback is only supported for VOD streams. If you try to save a live HLS stream, the system will throw an exception.
AssetDownloader.swift:
AssetDownloader
demonstrates how to manage the downloading of HLS streams. It includes APIs for starting and canceling downloads, deleting existing assets of the user's device, and monitoring the download progress and status.
ContentKeyManager.swift:
ContentKeyManager
class configures the instance of AVContentKeySession to use for requesting Content Keys securely for playback or offline use.
PlayerViewController.swift:
- The
PlayerViewController
uses a native AVPlayer as a base and provides a Video Player user interface together with capabilities of managing the downloading process, deleting downloaded media together with the Content Key associated with an asset. Togglable Console view allows user to see verbose logging of the steps performed during the playback of protected and non-protected assets, Fairplay content protection related activity, as well as AVPlayerItem and AVPlayer statuses, buffer events, and Access log and Error log events associated with AVPlayerItem. Console output can be cleared and copied to the device clipboard.
Asset.swift:
Asset
is a class that holds information about an Asset and adds its AVURLAsset as a recipient to the Playback Content Key Session in a protected playback/download use case. DownloadState extension is used to track the download states of Assets, Keys extension is used to define a number of values to use as keys in dictionary lookups.
This sample application allows to play protected and non-protected HTTP Live Streams and preserve them on disk for offline playback.
After the user requests playback of the protected HLS asset from AVFoundation m3u8 playlist will be downloaded from the Internet and parsed by AVFoundation.
The following image demonstrates further steps performed to playback FairPlay protected media in an online scenario.
During parsing of the m3u8 playlist provided by the client, AVFoundation determines that the content is encrypted (m3u8 playlist contains KEY tag).
As a result AVContentKeySessionDelegate
will provide AVContentKeyRequest
object by invoking the following delegate callback.
func contentKeySession(_ session: AVContentKeySession, didProvide keyRequest: AVContentKeyRequest) {
handleOnlineContentKeyRequest(keyRequest: keyRequest)
}
Received AVContentKeyRequest
object will allow performing FairPlay streaming specific operations like creating SPC - Server Playback Context (Content Key Request) and then sending it to a Key Server. For sake of simplicity we can name SPC a Content Key Request.
In handleOnlineContentKeyRequest
method we check whether the Application Certificate is available, and if not, it will be requested from fpsCertificateUrl
url, defined in Streams.json.
As a next step, in the provideOnlineKey(withKeyRequest: keyRequest, contentIdentifier: contentIdentifierData)
method we ask AVFoundation to prepare a Content Key Request (SPC) for a specific combination of application and content (Content Identifier previously parsed from m3u8 playlist).
keyRequest.makeStreamingContentKeyRequestData(forApp: self.fpsCertificate,
contentIdentifier: contentIdentifierData,
options: [AVContentKeyRequestProtocolVersionsKey: [1]],
completionHandler: completionHandler)
Now in completionHandler
Content Key Request (SPC) returned by makeStreamingContentKeyRequestData
method will be sent to a Key Server.
let ckcData = try strongSelf.requestContentKeyFromKeySecurityModule(spcData: spcData)
Key Server responds back with CKC - Content Key Context, for sake of simplicity we can name it Content Key Response. AVContentKeyResponse
class object will be used to represent the data returned from the Key Server.
let keyResponse = AVContentKeyResponse(fairPlayStreamingKeyResponseData: ckcData)
Now the app will provide the Content Key Response (CKC) to AVFoundation to make protected content available for processing.
keyRequest.processContentKeyResponse(keyResponse)
Finally, AVFoundation can start decryption and playback.
AVContentKeyRequest
provided by AVContentKeySessionDelegate
(step 1 in the online playback and key delivery scenario) is saved and used for renewing the license using
renewExpiringResponseData(for contentKeyRequest: AVContentKeyRequest)
function. For renewing the license in the application, "Renew" button on Player View Controller has to be clicked.
The following image demonstrates steps performed to deliver the persistable key that will be used to playback Fairplay protected content in an offline scenario.
Key loading process in initiated by calling processContentKeyRequest
method on AVContentKeySession
instance which in current sample app is wrapped into requestPersistableContentKeys
method.
func requestPersistableContentKeys(forAsset asset: Asset) {
contentKeySession.processContentKeyRequest(withIdentifier: asset.contentKeyId,
initializationData: nil,
options: nil)
}
Once this method is called AVContentKeySession
will initiate online key loading first by invoking the following delegate callback
func contentKeySession(_ session: AVContentKeySession, didProvide keyRequest: AVContentKeyRequest) {
handleOnlineContentKeyRequest(keyRequest: keyRequest)
}
Once handleOnlineContentKeyRequest
method is called, downloadRequestedByUser
parameter previously set to true, will determine that initial intention was to initiate Persistable Content Key loading process. As a result Persistable Content Key will be initiated by respondByRequestingPersistableContentKeyRequestAndReturnError
method:
keyRequest.respondByRequestingPersistableContentKeyRequestAndReturnError()
Once this method is called AVContentKeySession
will send AVPersistableContentKeyRequest
object by invoking the following delegate callback:
func contentKeySession(_ session: AVContentKeySession, didProvide keyRequest: AVPersistableContentKeyRequest) {
handlePersistableContentKeyRequest(keyRequest: keyRequest)
}
Received AVPersistableContentKeyRequest object will allow performing FairPlay streaming specific operations like creating SPC - Server Playback Context (Content Key Request) and sending it to a Key Server.
In handlePersistableContentKeyRequest
method we check whether the Application Certificate is available, and if not, it will be requested from fpsCertificateUrl
URL, defined in Streams.json.
As a next step, we ask AVFoundation to prepare a Content Key Request (SPC) for a specific combination of application and content (Content Identifier parsed from m3u8 playlist).
keyRequest.makeStreamingContentKeyRequestData(forApp: self.fpsCertificate,
contentIdentifier: contentIdentifierData,
options: [AVContentKeyRequestProtocolVersionsKey: [1]],
completionHandler: completionHandler)
Now in completionHandler
Content Key Request (SPC) returned by makeStreamingContentKeyRequestData
method will be sent to a Key Server.
let ckcData = try strongSelf.requestContentKeyFromKeySecurityModule(spcData: spcData)
Upon receiving the Content Key Response - CKC is passed to AVFoundation to create a persistable content key, that will be used to decrypt Fairplay protected content in offline usage.
let persistentKey = try keyRequest.persistableContentKey(fromKeyVendorResponse: ckcData, options: nil)
Now Persistable Content Key is delivered and will be saved to the device.
try strongSelf.writePersistableContentKey(contentKey: persistentKey, withAssetName: strongSelf.asset.name)
AVContentKeyResponse
class object will be used to represent the data returned from the Key Server.
let keyResponse = AVContentKeyResponse(fairPlayStreamingKeyResponseData: ckcData)
Now the app will provide the Content Key Response (CKC) to AVFoundation to make protected content available for processing.
keyRequest.processContentKeyResponse(keyResponse)
The Persistable Content Key is now ready to be used to decrypt Fairplay protected content and AVFoundation start downloading the stream to the device.
For that .HasAvailablePersistableContentKey
notification will be sent.
NotificationCenter.default.post(name: .HasAvailablePersistableContentKey, object: nil, userInfo: nil)
After dispatching the .HasAvailablePersistableContentKey
notification corresponding handleContentKeyDelegateHasAvailablePersistableContentKey
handler method will be called.
And if an asset is now already previously saved the downloading process will be initiated by
calling downloadStream()
method, which is a wrapper to Downloader
class method
download(asset: Asset)
All downloading related work is handled AVFoundation. Following classes are used for this purpose:
AVAggregateAssetDownloadTask
- the sample creates and initializes an AVAggregateAssetDownloadTask for the download of an HLS stream. Only the default media selections for each of the asset’s media selection groups are downloaded (these are indicated in the HLS playlist EXT-X-MEDIA tags by a DEFAULT attribute of YES).
AVAssetDownloadURLSession
- a URL session that supports the creation and execution of asset download tasks.
Following protocols are implemented to handle the download process:
This protocol handles download-related events.
Following methods are implemented to notify the app of download progress, completion events, and download location:
urlSession(_:aggregateAssetDownloadTask:willDownloadTo:)
The method asks the delegate for the asset download location.
NOTE: This delegate callback should only be used to save the location URL somewhere in your application. Any additional work should be done in URLSessionTaskDelegate.urlSession(_:task:didCompleteWithError:)
.
-
urlSession(_:aggregateAssetDownloadTask:didLoad:totalTimeRangesLoaded:timeRangeExpectedToLoad:for:)
Method to adopt to subscribe to progress updates of a download task -
urlSession(_:aggregateAssetDownloadTask:didCompleteFor:)
The method called when a child AVAssetDownloadTask completes for each media selection.
Find out more about AVAssetDownloadDelegate
:
https://developer.apple.com/documentation/avfoundation/avassetdownloaddelegate
A protocol that defines methods that URL session instance calls on their delegates to handle task-level events.
The following method is implemented to notify the app that the task finished transferring data as well as to provide download related error handling:
urlSession(_:task:didCompleteWithError:)
Find out more about URLSessionTaskDelegate
:
https://developer.apple.com/documentation/foundation/urlsessiontaskdelegate
Downloaded content can be obtained and accessed as follows:
- Open Devices and Simulators screen in Xcode (Window -> Devices and Simulators).
- From the list of connected devices, choose the one that has Axinom DRM Sample Player installed.
- In the opened window under installed apps section, select Axinom DRM Sample Player and then choose Download Container option accessible from the App container actions menu indicated by three period signs at the bottom).
- To view the downloaded application container, right click on it and select Show Package Contents. While doing that, make sure that hidden files are shown in Finder (shortcut: "Command + Shift + period" to toggle hiding/showing hidden files) because some files or folders might not be visible otherwise.
The following screenshot shows how the downloaded file structure looks like on an iOS device. Downloaded Fairplay keys are stored inside .keys folder. Audio and video content is located under .movpkg folder. Contents of that can also be viewed by right clicking on it and selecting the Show Package Contents option. Video fragments are saved into a subfolder which name starts with "0" and the audio fragments folder name starts with "1". The third folder named "Data" contains the HLS master playlist. Finally, boot.xml describes the .movpkg folder content.
The following resources available on the Apple Developer website contain helpful information that you may find useful
- General information regarding HLS on supported Apple devices and platforms:
- For information regarding topics specific to FairPlay Streaming as well as the latest version of the FairPlay Streaming Server SDK, please see:
- Information regarding authoring HLS content for devices and platforms:
- Information regarding error handling on the server side and with AVFoundation on supported Apple devices and platforms:
Xcode 11.0 or later; iOS 13.0 SDK or later
iOS 13.1 or later.