Skip to content

Instantly share code, notes, and snippets.

@AhnMo
Forked from fousa/FairPlayer.swift
Created December 17, 2020 09:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save AhnMo/d4eb06ef3ab0b56d6b65bcfeba069266 to your computer and use it in GitHub Desktop.
Save AhnMo/d4eb06ef3ab0b56d6b65bcfeba069266 to your computer and use it in GitHub Desktop.
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
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment