Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Integrate HLS with FairPlay.
class FairPlayer: AVPlayer {
private let queue = DispatchQueue(label: "com.icapps.fairplay.queue")
func play(asset: AVURLAsset) {
// Set the resource loader delegate to this class. The `resourceLoader`'s delegate will be
// triggered when FairPlay handling is required.
asset.resourceLoader.setDelegate(self, queue: queue)
// Load the asset in the player.
let item = AVPlayerItem(asset: asset)
// Set the current item in this player instance.
replaceCurrentItem(with: item)
// Start playing the item. From the moment the `play` is triggered the `resourceLoader` will
// do the rest of the work.
play()
}
}
extension FairPlayer: AVAssetResourceLoaderDelegate {
func resourceLoader(_ resourceLoader: AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest: AVAssetResourceLoadingRequest) -> Bool {
// We first check if a url is set in the manifest.
guard let url = loadingRequest.request.url else {
print("🔑", #function, "Unable to read the url/host data.")
loadingRequest.finishLoading(with: NSError(domain: "com.icapps.error", code: -1, userInfo: nil))
return false
}
print("🔑", #function, url)
// When the url is correctly found we try to load the certificate date. Watch out! For this
// example the certificate resides inside the bundle. But it should be preferably fetched from
// the server.
guard
let certificateURL = Bundle.main.url(forResource: "certificate", withExtension: "der"),
let certificateData = try? Data(contentsOf: certificateURL) else {
print("🔑", #function, "Unable to read the certificate data.")
loadingRequest.finishLoading(with: NSError(domain: "com.icapps.error", code: -2, userInfo: nil))
return false
}
// Request the Server Playback Context.
let contentId = "hls.icapps.com"
guard
let contentIdData = contentId.data(using: String.Encoding.utf8),
let spcData = try? loadingRequest.streamingContentKeyRequestData(forApp: certificateData, contentIdentifier: contentIdData, options: nil),
let dataRequest = loadingRequest.dataRequest else {
loadingRequest.finishLoading(with: NSError(domain: "com.icapps.error", code: -3, userInfo: nil))
print("🔑", #function, "Unable to read the SPC data.")
return false
}
// Request the Content Key Context from the Key Server Module.
let ckcURL = URL(string: "https://hls.icapps.com/ckc")!
var request = URLRequest(url: ckcURL)
request.httpMethod = "POST"
request.httpBody = spcData
let session = URLSession(configuration: URLSessionConfiguration.default)
let task = session.dataTask(with: request) { data, response, error in
if let data = data {
// The CKC is correctly returned and is now send to the `AVPlayer` instance so we
// can continue to play the stream.
dataRequest.respond(with: data)
loadingRequest.finishLoading()
} else {
print("🔑", #function, "Unable to fetch the CKC.")
loadingRequest.finishLoading(with: NSError(domain: "com.icapps.error", code: -4, userInfo: nil))
}
}
task.resume()
return true
}
}
@sahilchaddha

This comment has been minimized.

Copy link

commented Nov 11, 2017

Hi, I have been able to use this snippet for my Player. It works for ios 11. But breaks for ios 10(10.3.3). AVPlayer throws error

Error Domain=AVFoundationErrorDomain Code=-11819 \"Cannot Complete Action\" UserInfo={NSLocalizedDescription=Cannot Complete Action, NSLocalizedRecoverySuggestion=Try again later.})
@amirpervaiz086

This comment has been minimized.

Copy link

commented Jan 9, 2018

Thanks :)

@amirpervaiz086

This comment has been minimized.

Copy link

commented Jan 9, 2018

I found apple has changed implementation in their sample project by using AVContentKey (*iOS 10.3.3) before AVAssetResourceLoaderDelegate was being used. But I would stick with AVAssetResourceLoader implementation.

@mangaloreglitz

This comment has been minimized.

Copy link

commented Mar 21, 2018

I have been trying to implement the FairPlay streaming DRM in my project. I'm struck in getting the Content from the CKC data. I'm getting the below errors.
Error Domain=AVFoundationErrorDomain Code=-11800 "The operation could not be completed" UserInfo={NSLocalizedFailureReason=An unknown error occurred (-16152), NSLocalizedDescription=The operation could not be completed, NSUnderlyingError=0x10bc54770 {Error Domain=NSOSStatusErrorDomain Code=-16152 "(null)"}}
Error Domain=AVFoundationErrorDomain Code=-11835 "Cannot Open" UserInfo={NSUnderlyingError=0x17025c860 {Error Domain=NSOSStatusErrorDomain Code=-42681 "(null)"}, NSLocalizedFailureReason=This content is not authorized., NSLocalizedDescription=Cannot Open}

How to validate the implementation step by step?

Thanks

@jovanpreet

This comment has been minimized.

Copy link

commented May 7, 2018

Same here. It works on ios11 but fails on ios10. any solution?

@veereshkumbargs

This comment has been minimized.

Copy link

commented Jun 19, 2018

can u provide any demo url asset so that i can pass it as a argument to the FairPlayer.play() method

@xtabbas

This comment has been minimized.

Copy link

commented Nov 2, 2018

So setting delegate using asset.resourceLoader.setDelegate(self, queue: queue) automatically invokes the resourceLoader method? I am new to swift and I am still trying to wrap my head around how that works.

@inickt

This comment has been minimized.

Copy link

commented Jan 4, 2019

Have you had any luck doing something like this with AirPlay?

@abesmon

This comment has been minimized.

Copy link

commented Mar 29, 2019

It's seems that this demo is not working now. I found out, that Apple's demo is better showing how to make this works:
https://developer.apple.com/services-account/download?path=/Developer_Tools/FairPlay_Streaming_Server_SDK/FairPlay_Streaming_Server_SDK_v4.2.0.zip

For me it was an issue with line #49, where streamingContentKeyRequestData appeared. I dont know exactly, but it seems that calling this function took too many time, so task fails before it could resolve.

In apple's demo they wrap all this async stuff in async block on separate queue, and it works well for me. I wrapped everything inside resourceLoader(...) in async block except return true and now it works

@tiwariammit

This comment has been minimized.

Copy link

commented Apr 2, 2019

after implementing the above methods, I got data with empty size then I called finishedLoading() method by implementing dataRequest.respond(with: data) and got below error from AVPlayer.
Error Domain=AVFoundationErrorDomain Code=-11835 "Cannot Open" UserInfo={NSLocalizedFailureReason=This content is not authorized., NSLocalizedDescription=Cannot Open, NSUnderlyingError=0x106f2afa0 {Error Domain=NSOSStatusErrorDomain Code=-42681 "(null)"} from the AVPlayer.
I am confuse on how to get contentId, which is used to create content Identifier. Can anybody help me?

@tiwariammit

This comment has been minimized.

Copy link

commented Apr 4, 2019

@mangaloreglitz Have you got the solution?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.