Integrate Studio DRM Standalone with your iOS or tvOS app.
The Studio DRM FairPlay SDK enables Apple’s AVPlayer
from AVFoundation
to securely request licenses from JW Player's Studio DRM cloud-based DRM platform.
Studio DRM uses AVPlayer
and AVPlayerViewController
to present users with the platform default skins, with DRM content, or with your own created video player based on AVPlayer
.
The deployment scenario will allow Online Playback / Download and Offline Playback using an associated rental or persist token.
This SDK has a number of key benefits:
- Complete control over the entire
AVPlayer
lifecycle - Numerous optimizations to enable faster playback
- Bitcode support
- Support for online and offline playback using an associated rental or persist token
- Compatible with both Swift and Objective-C applications
Multiple asset demo applications written in Swift and based on Apple's FairPlay SDK example applications are available upon request.
Please contact JWP Support to request access.
Prerequisites
- Minimum deployment target of iOS 10.0 and tvOS 9.0 or higher
- Xcode 12.4
- Swift 5.3
- Cocoapods
Xcode Integration
Studio DRM is distributed using Cocoapods. Projects set up with Cocoapods use an Xcode workspace (.xcworkspace) file. It is important that projects with Cocoapods are always opened using the .xcworkspace file instead of the project file.
Our simple demo applications demonstrate the required setup for Cocoapods.
If you are integrating Studio DRM into your own project, ensure that the project has been set up correctly for Cocoapods.
SDK Installation
Update Cocoapods
It is important that the latest version of Cocoapods is installed before the demo project or your project can be opened without error.
From a new shell in the Terminal application, install the latest version of Cocoapods.
gem install cocoapods
Edit the Podfile
-
In a text editor, open Podfile.
-
Add StudioDRM as a dependency.
source 'https://bitbucket.org/vualtomobile/studiodrm-pods.git' target 'DemoiOS' do # Comment the next line if you don't want to use dynamic frameworks use_frameworks! # Pods for StudioDRM iOS Demo pod 'StudioDRMKit' end target 'DemotvOS' do # Comment the next line if you don't want to use dynamic frameworks use_frameworks! # Pods for StudioDRM tvOS Demo pod 'StudioDRMKit-tvos' end
-
Save Podfile and close the text editor.
Install the SDK
- At the Terminal prompt navigate to your project or the demo project directory.
cd <path-to-your-project>/<your-project>/
- Install the
StudioDRMKit
.
pod install
If Podfile has been previously installed, run
pod update
.If Podfile has been previously installed and this error is returned:
[!] Unable to find a specification for 'StudioDRMKit'
Cocoapods error,run pod install --repo-update
- Open the .xcworkspace file for your project to launch Xcode.
You can now learn more about using the demo applications.
Information about application transport security (ATS)
iOS/tvOS 9 introduces Application Transport Security (ATS) which restricts the use of insecure HTTP. In order to permit playback of content over insecure HTTP exemptions need to be added into your application's Info.plist. For example to disable ATS for any connection you would add the following into your application's Info.plist.
<dict>
<key>NSAppTransportSecurity</key>
<dict>
<key>NSAllowsArbitraryLoads</key> <true/>
</dict>
</dict>
More information about ATS can be found in Apple’s TechNote.
Preparation
Instances of Studio DRM require some configuration which would normally be populated with an API. Our demo projects, which are based on Apple's FairPlay SDK example applications, are configured using the projects Streams.plist file.
To provide complete configuration for each Stream
object, you must include the properties listed in the following table.
Property | Description |
---|---|
content_id | Unique identifier for the content This value is the same content ID with which the content was prepared. This value will always be the last path component of the stream content_key_id_list or skd:// URI entry.Our demo application shows how to retrieve and parse the content_key_id_list . However, the content_id always needs to be provided for use with offline assets. |
is_protected | Indicates if the Stream object is DRM-protectedThis setting is not needed if all content is protected since all assets can be set as protected by default in the source code. |
name | Name for the content This is the readable display name. It is not used by the SDK. This value may differ from the content_id . |
playlist_url | URL to correctly prepared content |
renewal_interval | Optional value that represents the lease renewal interval in seconds This value is only used where the token policy uses duration_lease .See: FairPlay Lease |
skd:// | An sdk:// URI entry in the content_key_id_list The correct URI can be obtained by retrieving the manifest and parsing out the URI from the EXT-X-SESSION-KEY or EXT-X-KEY :• Code Approach • cURL command Approach: In the Terminal app, run curl https://eample.cloudfront.net/example-demo/examplecontent/examplecontent.ism/.m3u8 |
studiodrm-token | Token that must be correctly generated for a specific type of instance you wish to create |
Error Handling
For added convenience, Studio DRM also bubbles up notifications of errors and progress during the licensing request.
Errors can be intercepted.
@objc func handleStudioDRMDidError(_ notification: Notification)
{
if let data = notification.userInfo as? [String: String]
{
for (function, error) in data
{
print("StudioDRM - \(function) reported error \(error)!")
}
}
}
And, progress can be monitored.
@objc func handleStudioDRMProgressUpdate(_ notification: Notification)
{
if let data = notification.userInfo as? [String: String]
{
for (function, message) in data
{
print("StudioDRM - \(function) reported progress \(message).")
}
}
}
Example Framework Usage
We strongly recommend referring to our example iOS / tvOS multiple asset demo application.
Recommended Implementations
Apple recommends two implementations: AssetResourceLoaderDelegate (AVAssetResourceLoader API) or ContentKeyDelegate (Contentkey API). When using either implementation, Studio DRM performs the two required licensing network tasks:
- Acquiring an application certificate
- Requesting a FairPlay license for use either online or offline
An application certificate is always required to initialize a request for a FairPlay license.
AssetResourceLoaderDelegate
Online Playback
- Invoke an instance of
StudioDRM
and call for an application certificate.
self.drm = StudioDRM()
let applicationCertificate = try drm!.requestApplicationCertificate(token: self.studioDRMToken, contentID: contentID)
- Using the application certificate, call for a license.
let spcData = try resourceLoadingRequest.streamingContentKeyRequestData(forApp: applicationCertificate!,contentIdentifier: assetIDData, options: nil)
let ckcData = try self.drm!.requestContentKeyFromKeySecurityModule(spcData: spcData, token: studioDRMToken, assetID: self.contentID, licenseURL: licenseUrl)
if ckcData != nil {
resourceLoadingRequest.dataRequest?.respond(with: ckcData!)
} else {
print("Failed to get CKC for the request object of the resource.")
return
}
- Set the
contentType
before callingfinishLoading()
on theresourceLoadingRequest
. This ensures that thecontentType
matches the key response.
resourceLoadingRequest.contentInformationRequest?.contentType = AVStreamingKeyDeliveryContentKeyType
resourceLoadingRequest.finishLoading()
Offline Playback
- Invoke an instance of
StudioDRM
and call for an application certificate.
self.drm = StudioDRM()
let applicationCertificate = try drm!.requestApplicationCertificate(token: self.studioDRMToken, contentID: contentID)
- Using the application certificate, call for a license.
let spcData = try resourceLoadingRequest.streamingContentKeyRequestData(forApp: applicationCertificate!, contentIdentifier: assetIDData, options: [AVAssetResourceLoadingRequestStreamingContentKeyRequestRequiresPersistentKey: true])
let ckcData = try self.drm!.requestContentKeyFromKeySecurityModule(spcData: spcData, token: self.studioDRMToken, assetID: self.contentID, licenseURL: self.licenseUrl, renewal: self.renewalInterval)
let persistentKey = try resourceLoadingRequest.persistentContentKey(fromKeyVendorResponse: ckcData!, options: nil)
- Write the persistent key to disk.
try writePersistableContentKey(contentKey: persistentKey, withContentKeyIdentifier: contentID)
- Make the content key response to make protected content available for processing before calling
finishLoading()
on theresourceLoadingRequest
.
resourceLoadingRequest.dataRequest?.respond(with: persistentKey)
resourceLoadingRequest.finishLoading()
ContentKeyDelegate
Online Playback
- Invoke an instance of
StudioDRM
and call for an application certificate.
self.drm = StudioDRM()
let applicationCertificate = try drm!.requestApplicationCertificate(token: self.studioDRMToken, contentID: contentID)
- Using the application certificate, call for a license.
let completionHandler = { [weak self] (spcData: Data?, error: Error?) in
guard let strongSelf = self else { return }
if let error = error {
keyRequest.processContentKeyResponseError(error)
return
}
guard let spcData = spcData else { return }
do {
guard let ckcData = try strongSelf.drm!.requestContentKeyFromKeySecurityModule(spcData: spcData, token: studioDRMToken, assetID: self!.contentID, licenseURL: licenseUrl, renewal: self.renewalInterval) else { return }
let keyResponse = AVContentKeyResponse(fairPlayStreamingKeyResponseData: ckcData)
keyRequest.processContentKeyResponse(keyResponse)
} catch {
keyRequest.processContentKeyResponseError(error)
}
}
keyRequest.makeStreamingContentKeyRequestData(forApp: applicationCertificate!, contentIdentifier: assetIDData, options: [AVContentKeyRequestProtocolVersionsKey: [1]], completionHandler: completionHandler)
Offline Playback
- Invoke an instance of
StudioDRM
and call for an application certificate.
self.drm = StudioDRM()
let applicationCertificate = try drm!.requestApplicationCertificate(token: self.studioDRMToken, contentID: contentID)
- Using the application certificate, call for a license.
let ckcData = try self!.drm!.requestContentKeyFromKeySecurityModule(spcData: spcData, token: self!.studioDRMToken, assetID: self!.contentID, licenseURL: self!.licenseUrl, renewal: self.renewalInterval)
let persistentKey = try keyRequest.persistableContentKey(fromKeyVendorResponse: ckcData!, options: nil)
- Write the persistent key to disk.
try strongSelf.writePersistableContentKey(contentKey: persistentKey, withContentKeyIdentifier: self!.contentID)
- Make the content key response to make protected content available for processing.
let keyResponse = AVContentKeyResponse(fairPlayStreamingKeyResponseData: persistentKey)
keyRequest.processContentKeyResponse(keyResponse)
Tokens and Licensing
Requests for playback or download will result in a license request for the content from the license server, based on the type of token presented for each StudioDRM
request. The tokens may be FairPlay Lease, FairPlay Persist, or FairPlay Rental.
Token | Description |
---|---|
FairPlay Lease | Used to stream online content only Example type template: {"type": "l","duration_lease": 3600} The duration_lease should always be greater than 360 seconds.When the renew_interval is defined, be aware of the following:• The renewal_interval should either be 0 or greater than 300 seconds. To prevent license server overload and unexpected overheads, lease renewals will fail when the renewal_interval is set a value between 0 and 360 will fail.• We recommend setting the duration_lease to be at least 60 seconds greater than the renewal_interval to allow time for the license to be retrieved and processed by the OS.Please refer to our demo application for an example, and do not hesitate to contact us to discuss the use of Fairplay Lease. |
FairPlay Persist | Used to stream online content and play offline (downloaded) content Example type template: {"type": "p","duration_persist": 3600} |
FairPlay Rental | Used to stream online content only Example type template: {"type": "r","duration_rental": 3600} |
The content ID should be unique to each asset. The content ID and the
playlist_url
are used together both to create a path to and identify offline assets and their associated content keys.There are significant limitations using AirPlay to stream any content to an Apple TV that has been downloaded to the user’s device.
The tvOS platform does not support downloading or offline playback.
Demo Applications
JW Player offers two demo applications: one using AssetResourceLoaderDelegate
for iOS 10 and above and a newer one using ContentKeyDelegate
for iOS 11.2 and above.
Both applications have the following features:
- Targets both iOS and tvOS platforms using shared source code
- Ensures each target platform references its own framework version
- Supports iOS 11.1+ (Support can be added for iOS 10+.)
- Is based upon Apple's FairPlay SDK examples
StudioDRMKit Framework
With the StudioDRMKit framework installed, both the example demo applications read entries in the Streams.plist file to configure the content and DRM. The Streams.plist value can be edited. At minimum, Streams.plist must include a content_id
, playlist_url
, renew_internal
, and studiodrm_token
. These required values are explained in the Preparation section.
Additional Streams.plist Attributes
Attributes | Descriptions |
---|---|
content_key_id_list string | Retrieved by parsing the manifest with getContentKeyIDList() in the AssetListTableViewController |
is_protected boolean | Indicates if the content is DRM-protected This value can be overridden in the source if all content is protected. |
name string | Display name This value may differ from the content_id . |
renewal_internal integer | Represents the lease renewal interval in seconds See: FairPlay Lease |
Online Streaming
In both target platforms, the AssetResourceLoaderDelegate
or ContentKeyDelegate
class handles license requests for online streaming.
The call to make a request for an online license from the framework is made by iOS when a protected asset is requested for playback in the override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath)
function of the AssetListTableViewController
. This corresponds to the specific stream. An Asset
object (with associated AVURLAsset
) is initialized for the stream by the returned table cell.
Offline Playback
In iOS, only the AssetResourceLoaderDelegate+Persistable
or ContentKeyDelegate+Persistable
extension handles license requests for iOS offline playback.
Each demo uses a different call to make a request for an offline license from the framework. Each call corresponds to the specific stream Download button.
-
AssetResourceLoaderDelegate demo:
ContentKeyManager.shared.assetResourceLoaderDelegate.requestPersistableContentKeys(forAsset: asset)
-
ContentKeyDelegate demo:
ContentKeyManager.shared.contentKeyDelegate.requestPersistableContentKeys(forAsset: asset)
in theoverride func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath)
function of theAssetListTableViewController
.
In iOS, a call is made upon launch by the AppDelegate
class to the AssetPersistenceManager
class to restore any persisted content. The call AssetPersistenceManager.sharedManager.restorePersistenceManager()
asks the AssetPersistenceManager
class to check any already mapped asset against any outstanding AVAssetDownloadTask
.
Limitations
-
The SDK does not support AirPlay with protected offline content. Apple TV cannot play protected offline content with a persist content key because the key cannot be written to the playback device.
Attempting to play protected offline content may result in unexpected behaviors, crashes referring to the content key type, or the content may not load.
Protected content may be transmitted with AirPlay using a streaming content key whenever the Apple TV has a network connection. -
Since FairPlay is a device-only technology, the iOS Simulator does not support playback of protected content. Attempting to do so will result in an invalid
KEYFORMAT
error and the content will not load.
Troubleshooting
-
Most issues are content related. You can use our demo application to test your own content by updating it with your
Stream
configurations. -
Errors may also arise because the
Stream
configuration is not correct.
Configuration Property | Description |
---|---|
Content ID | Please ensure that the content_id corresponds to that which was used when preparing your content.The content_id should always be unique for each Stream instance. |
Tokens | You can easily eliminate token issues by beginning with a default FairPlay token policy. Ensure your token is formatted correctly and validates. For the avoidance of doubt, where tokens use dates, the dates should always be in the future. For further information about tokens please refer to our token documentation. |
If you are not able to play your content after checking it in our demo application please contact JW Player Support with the demo application logs and the stream configuration used.