# Apple FairPlay

Note

This feature is available only to ByteArk Video Cloud for Business customers. Please contact sales@byteark.com if you would like to use it.

ByteArk Player SDK for iOS supports Apple FairPlay Streaming (FPS) (opens new window) for DRM-protected playback. To enable it, implement the ByteArkPlayerDrmDataSource protocol. When the SDK detects FairPlay-encrypted content, it asks your app for the data and keys needed to decrypt.

Note

FairPlay requires an HLS (.m3u8) source.

Caution

FairPlay decryption works only on physical devices, not on the Simulator.

ByteArkPlayerDrmDataSource Protocol

// A dataSource that provides the data required to decrypt Apple FairPlay DRM content
@objc public protocol ByteArkPlayerDrmDataSource: AnyObject {
  /// Called when the player needs a content identifier (content id) to decrypt protected content.
  ///
  /// - Parameters:
  ///   - url: The URL of the resource being loaded.
  ///   - completionHandler: A block used to deliver the content identifier back to ByteArk Player.
  func contentIdentifier(for url: URL, completionHandler handler: @escaping (_ contentIdentifier: Data?) -> Void)
  /// Called when the player needs an application certificate to decrypt protected content.
  ///
  /// - Parameters:
  ///   - url: The URL of the resource being loaded.
  ///   - completionHandler: A block used to deliver the Application Certificate data
  ///     (received after registering an FPS playback app) back to ByteArk Player.
  func applicationCertificate(for url: URL, completionHandler handler: @escaping (_ certificate: Data?) -> Void)
  /// Called when the player needs a content key to decrypt protected content.
  ///
  /// - Parameters:
  ///   - spcData: SPC (Server Playback Context) data which must be sent to the Key Server
  ///     to obtain the CKC (Content Key Context).
  ///   - completionHandler: A block used to deliver the CKC back to ByteArk Player.
  func contentKey(with spcData: Data, completionHandler handler: @escaping (_ ckcData: Data?) -> Void)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27

# Implementation

  1. Implement contentIdentifier(for url: URL, completionHandler:) in your class. This method asks for the identifier of the protected content.
func contentIdentifier(for url: URL, completionHandler handler: @escaping (Data?) -> Void) {
  // Extract the content identifier from the URL
  if #available(iOS 16.0, *) {
    handler(url.host()?.data(using: .utf8))
  } else {
    handler(url.host?.data(using: .utf8))
  }
}
1
2
3
4
5
6
7
8
  1. Implement applicationCertificate(for url: URL, completionHandler:). This method requests the Application Certificate that must be passed back through the completion block.
func applicationCertificate(for url: URL, completionHandler handler: @escaping (Data?) -> Void) {
  // Request the application certificate from your server
  var request = URLRequest(url: URL(string: "APPLICATION_CERTIFICATE_URL")!)
  request.httpMethod = "GET"
  let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
    handler(data)
  }
  task.resume()
}
1
2
3
4
5
6
7
8
9
10
11
  1. Implement contentKey(with spcData: Data, completionHandler:). The SPC data must be sent to your Key Server to obtain the CKC, which is returned through the completion block.
func contentKey(with spcData: Data, completionHandler handler: @escaping (Data?) -> Void) {
  var request = URLRequest(url: URL(string: "APPLE_FAIRPLAY_KEY_SERVER_URL")!)
  request.httpMethod = "POST"
  request.setValue("application/json", forHTTPHeaderField: "Content-Type")
  // Encode SPC as base64 and set the JSON body
  let json: [String: Any] = [ "spc": spcData.base64EncodedString() ]
  let jsonData = try? JSONSerialization.data(withJSONObject: json)
  request.httpBody = jsonData
  let task = URLSession.shared.dataTask(with: request) {  (data, response, error) in
    guard let data = data,
          let json = try? JSONSerialization.jsonObject(with: data) as? [String: Any],
          let ckcString = json["ckc"] as? String,
          let ckcData = Data(base64Encoded: ckcString)
    else {
      handler(nil)
      return
    }
    handler(ckcData)
  }
  task.resume()
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
  1. Set your ByteArkPlayerDrmDataSource implementation on ByteArkPlayerItemBuilder.
do {
  let item = try ByteArkPlayerItemBuilder()
    .title("FairPlay Source")
    .media(URL(string: "<HLS_M3U8_URL>")!)
    .drmDataSource(YOUR_DRM_DATA_SOURCE_IMPLEMENTATION)
    .build()
  let config = try ByteArkPlayerConfigBuilder()
    .autoplay(true)
    .item(item)
    .build()
  bytearkPlayerViewController.player.configure(with: config)
} catch {
  print(error.localizedDescription)
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16

# Send custom headers to the license server

If the license server requires an Authorization header (or other custom headers), set them on the license request inside contentKey(with:completionHandler:):

func contentKey(with spcData: Data, completionHandler handler: @escaping (Data?) -> Void) {
  var request = URLRequest(url: URL(string: "APPLE_FAIRPLAY_KEY_SERVER_URL")!)
  request.httpMethod = "POST"
  request.setValue("application/json", forHTTPHeaderField: "Content-Type")
  request.setValue("Bearer <YOUR_TOKEN>", forHTTPHeaderField: "Authorization")
  // ...
}
1
2
3
4
5
6
7
8

Note

If you use ByteArk Multi-DRM, see Generating an Authorization Token for the DRM License Server (Thai) to learn how to create the JWT token for the Authorization header.