iOS / tvOS FairPlay SDK (Standalone)

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 JW Player Support to request access.



Requirements

  • 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.

sudo gem install cocoapods

Edit the Podfile

  1. In a text editor, open Podfile.
  2. 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
  1. Save Podfile and close the text editor.

Install the SDK

  1. At the Terminal prompt navigate to your project or the demo project directory.
cd <path-to-your-project>/<your-project>/

  1. 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


  1. 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.

PropertyDescription
content_idUnique 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_protectedIndicates if the Stream object is DRM-protected

This setting is not needed if all content is protected since all assets can be set as protected by default in the source code.
nameName 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_urlURL to correctly prepared content
renewal_intervalOptional 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-tokenToken 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

  1. Invoke an instance of StudioDRM and call for an application certificate.
self.drm = StudioDRM()
let applicationCertificate = try drm!.requestApplicationCertificate(token: self.studioDRMToken, contentID: contentID)

  1. 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
}

  1. Set the contentType before calling finishLoading() on the resourceLoadingRequest. This ensures that the contentType matches the key response.
resourceLoadingRequest.contentInformationRequest?.contentType = AVStreamingKeyDeliveryContentKeyType
resourceLoadingRequest.finishLoading()



Offline Playback

  1. Invoke an instance of StudioDRM and call for an application certificate.
self.drm = StudioDRM()
let applicationCertificate = try drm!.requestApplicationCertificate(token: self.studioDRMToken, contentID: contentID)

  1. 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)

  1. Write the persistent key to disk.
try writePersistableContentKey(contentKey: persistentKey, withContentKeyIdentifier: contentID)

  1. Make the content key response to make protected content available for processing before calling finishLoading() on the resourceLoadingRequest.
resourceLoadingRequest.dataRequest?.respond(with: persistentKey)
resourceLoadingRequest.finishLoading()

β˜†Β Β Β β˜†Β Β Β β˜†

ContentKeyDelegate

Online Playback

  1. Invoke an instance of StudioDRM and call for an application certificate.
self.drm = StudioDRM()
let applicationCertificate = try drm!.requestApplicationCertificate(token: self.studioDRMToken, contentID: contentID)

  1. 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

  1. Invoke an instance of StudioDRM and call for an application certificate.
self.drm = StudioDRM()
let applicationCertificate = try drm!.requestApplicationCertificate(token: self.studioDRMToken, contentID: contentID)

  1. 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)

  1. Write the persistent key to disk.
try strongSelf.writePersistableContentKey(contentKey: persistentKey, withContentKeyIdentifier: self!.contentID)

  1. 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.

TokenDescription
FairPlay LeaseUsed 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Β PersistUsed to stream online content and play offline (downloaded) content

Example type template:
{"type": "p","duration_persist": 3600}
FairPlay RentalUsed 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

AttributesDescriptions
content_key_id_list stringRetrieved by parsing the manifest with getContentKeyIDList() in the AssetListTableViewController
is_protected booleanIndicates if the content is DRM-protected

This value can be overridden in the source if all content is protected.
name stringDisplay name

This value may differ from the content_id.
renewal_internalΒ integerRepresents 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 the override func tableView(_ tableView: UITableView, accessoryButtonTappedForRowWith indexPath: IndexPath) function of the AssetListTableViewController.


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.

  • The SDK does not support playback of protected content when running in the iOS Simulator. 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 PropertyDescription
Content IDPlease 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.
TokensYou 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.