# 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)
}
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
- 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))
}
}
2
3
4
5
6
7
8
- 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()
}
2
3
4
5
6
7
8
9
10
11
- 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()
}
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
- Set your
ByteArkPlayerDrmDataSourceimplementation onByteArkPlayerItemBuilder.
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)
}
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")
// ...
}
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.