Play DRM-protected content (iOS v3)

Learn how to enable digital rights management (DRM) protected playback for an iOS app.


FairPlay Streaming (FPS) securely delivers keys to Apple mobile devices, enabling the playback of encrypted video content. FPS allows a content provider to securely deliver an AES 128-bit content key from the provider’s key server. The content provider encrypts the H.264 video content with the content key. Then, FPS decrypts the encrypted video content with the content key.

🚧

The JWP SDK does not provide the following:

  • Encrypt streams
  • Implement a key server
  • Support offline DRM

Google Chromecast does not support FairPlay DRM.


The JWP SDK does not encrypt streams for you and does not implement a key server. The JWP SDK detects when a stream is FairPlay encrypted and asks your app to provide the necessary information and key to decrypt the content. This approach gives you the flexibility to encrypt your content and manage your keys the way you prefer.

In order for you to be FPS-compliant you must:

  • Write a Key Security Module that is installed in a key server's software.
  • Add code to allow your app to communicate with the server that can deliver the key needed to decrypt the content.
  • Create the formatting and encryption software for the media content server. This software prepares the encrypted content stream according to the Apple HTTP Live Streaming (HLS) specification.

For more information on being FPS-compliant, please refer to Apple's FairPlay documentation.



Implementation

In order to play FairPlay encrypted HLS, the class in your app which communicates with your FairPlay Key Server must adhere to the JWDrmDataSource protocol and be set to the JWPlayerController's drmDataSource property, as in: self.player.drmDataSource = self;.

When an FPS-specific tag is included in the playlist of a media stream that the Apple device is asked to play, the JWP SDK will ask your application for the necessary information to prepare an encrypted key request:

  • the fetchContentIdentifierForRequest delegate method gets called, requesting that the content identifier (also known as Asset ID) be passed in through its completion block.
- (void)fetchContentIdentifierForRequest:(NSURL *)loadingRequestURL forEncryption:(JWEncryption)encryption withCompletion:(void (^)(NSData *))completion
{
        if(encryption == JWEncryptionFairPlay) {
            NSString *assetId;
            // obtain asset Id here.
            NSData *assetIdData = [NSData dataWithBytes: [assetId cStringUsingEncoding:NSUTF8StringEncoding]
                                                 length: [assetId lengthOfBytesUsingEncoding:NSUTF8StringEncoding]];
            completion(assetIdData);
        }
}
func fetchContentIdentifier(forRequest loadingRequestURL: URL?, for encryption: JWEncryption, withCompletion completion: @escaping (Data?) -> Void) 
{
        if encryption == .fairPlay {
            // obtain asset Id here.
            let assetId: String = "YOUR_ASSET_ID"
            let assetIdData = Data(bytes: assetId.cString(using: .utf8),
                                   count: assetId.lengthOfBytes(using: .utf8))
            completion(assetIdData)
        }
}
  • the fetchAppIdentifierForRequest delegate method is called, prompting for an Application Certificate which must get passed via the completion block. The Application Certificate must be encoded with the X.509 standard with distinguished encoding rules (DER). It is obtained when registering an FPS playback app with Apple, by supplying an X.509 Certificate Signing Request linked to your private key.
- (void)fetchAppIdentifierForRequest:(NSURL *)loadingRequestURL forEncryption:(JWEncryption)encryption withCompletion:(void (^)(NSData *))completion
    {
        if (encryption == JWEncryptionFairPlay) {
            NSData *applicationId; 
            // obtain application Id here.
            completion(applicationId);
        }
    }
func fetchAppIdentifier(forRequest loadingRequestURL: URL?, for encryption: JWEncryption, withCompletion completion: @escaping (Data?) -> Void) {
        if encryption == .fairPlay {
            // obtain application Id here.
            let applicationId: Data? = YOUR_APPLICATION_ID
            completion(applicationId)
        }
}

When the key request is ready:

  • the fetchContentKeyWithRequest delegate method is called, providing you with the key request data (also known as SPC - Server Playback Context message) which you need to retrieve the CKC (Content Key Context) message from your key server. The CKC message must be returned via the completion block, under the response parameter. When your app sends the request to the server, the FPS code on the server wraps the required key in an encrypted message and sends it to the app. Your app then provides the JWP SDK with the encrypted message, which is used to unwrap the message and decrypt the stream, so the iOS device can play the media.
    • For resources that may expire, you can specify a renewal date in the completion block.
    • We suggest also specifying the content type (the UTI indicating the type of data contained by the response) when a renewal date is set.
- (void)fetchContentKeyWithRequest:(NSData *)requestBytes forEncryption:(JWEncryption)encryption withCompletion:(void (^)(NSData *, NSDate *, NSString *))completion
    {
        if(encryption == JWEncryptionFairPlay) {
            NSData *key = [self.serverLink keyFromRequest:requestBytes]; // obtain key here from server by providing the request.
            NSDate *renewalDate; // (optional)
            NSString *contentType = @"application/octet-stream"; // (optional)
            completion(key, renewalDate, contentType);
        } 
    }
func fetchContentKey(withRequest requestBytes: Data?, for encryption: JWEncryption, withCompletion completion: @escaping (Data?, Date?, String?) -> Void) 
{
        if encryption == .fairPlay {
            let key: Data? = serverLink.key(fromRequest: requestBytes) // obtain key here from server by providing the request.
            let renewalDate: Date? // (optional)
            let contentType = "application/octet-stream" // (optional)
            completion(key, renewalDate, contentType)
        }
}

πŸ“˜

FairPlay decryption only works on a physical device; it will not work on a simulator.