259 lines
11 KiB
Swift
259 lines
11 KiB
Swift
|
import AVFoundation
|
||
|
|
||
|
class RCTResourceLoaderDelegate: NSObject, AVAssetResourceLoaderDelegate, URLSessionDelegate {
|
||
|
|
||
|
private var _loadingRequest:AVAssetResourceLoadingRequest?
|
||
|
private var _requestingCertificate:Bool = false
|
||
|
private var _requestingCertificateErrored:Bool = false
|
||
|
private var _drm: DRMParams?
|
||
|
private var _reactTag: NSNumber?
|
||
|
private var _onVideoError: RCTDirectEventBlock?
|
||
|
private var _onGetLicense: RCTDirectEventBlock?
|
||
|
|
||
|
|
||
|
init(
|
||
|
asset: AVURLAsset,
|
||
|
drm: DRMParams?,
|
||
|
onVideoError: RCTDirectEventBlock?,
|
||
|
onGetLicense: RCTDirectEventBlock?,
|
||
|
reactTag: NSNumber
|
||
|
) {
|
||
|
super.init()
|
||
|
let queue = DispatchQueue(label: "assetQueue")
|
||
|
asset.resourceLoader.setDelegate(self, queue: queue)
|
||
|
_reactTag = reactTag
|
||
|
_onVideoError = onVideoError
|
||
|
_onGetLicense = onGetLicense
|
||
|
_drm = drm
|
||
|
}
|
||
|
|
||
|
deinit {
|
||
|
_loadingRequest?.finishLoading()
|
||
|
}
|
||
|
|
||
|
func resourceLoader(_ resourceLoader:AVAssetResourceLoader, shouldWaitForRenewalOfRequestedResource renewalRequest:AVAssetResourceRenewalRequest) -> Bool {
|
||
|
return loadingRequestHandling(renewalRequest)
|
||
|
}
|
||
|
|
||
|
func resourceLoader(_ resourceLoader:AVAssetResourceLoader, shouldWaitForLoadingOfRequestedResource loadingRequest:AVAssetResourceLoadingRequest) -> Bool {
|
||
|
return loadingRequestHandling(loadingRequest)
|
||
|
}
|
||
|
|
||
|
func resourceLoader(_ resourceLoader:AVAssetResourceLoader, didCancel loadingRequest:AVAssetResourceLoadingRequest) {
|
||
|
NSLog("didCancelLoadingRequest")
|
||
|
}
|
||
|
|
||
|
func base64DataFromBase64String(base64String:String?) -> Data? {
|
||
|
if let base64String = base64String {
|
||
|
return Data(base64Encoded:base64String)
|
||
|
}
|
||
|
return nil
|
||
|
}
|
||
|
|
||
|
func setLicenseResult(_ license:String!) {
|
||
|
guard let respondData = self.base64DataFromBase64String(base64String: license),
|
||
|
let _loadingRequest = _loadingRequest else {
|
||
|
setLicenseResultError("No data from JS license response")
|
||
|
return
|
||
|
}
|
||
|
let dataRequest:AVAssetResourceLoadingDataRequest! = _loadingRequest.dataRequest
|
||
|
dataRequest.respond(with: respondData)
|
||
|
_loadingRequest.finishLoading()
|
||
|
}
|
||
|
|
||
|
func setLicenseResultError(_ error:String!) {
|
||
|
if _loadingRequest != nil {
|
||
|
self.finishLoadingWithError(error: RCTVideoErrorHandler.fromJSPart(error))
|
||
|
}
|
||
|
}
|
||
|
|
||
|
func finishLoadingWithError(error:NSError!) -> Bool {
|
||
|
if let _loadingRequest = _loadingRequest, let error = error {
|
||
|
let licenseError:NSError! = error
|
||
|
_loadingRequest.finishLoading(with: licenseError)
|
||
|
|
||
|
_onVideoError?([
|
||
|
"error": [
|
||
|
"code": NSNumber(value: error.code),
|
||
|
"localizedDescription": error.localizedDescription == nil ? "" : error.localizedDescription,
|
||
|
"localizedFailureReason": ((error as NSError).localizedFailureReason == nil ? "" : (error as NSError).localizedFailureReason) ?? "",
|
||
|
"localizedRecoverySuggestion": ((error as NSError).localizedRecoverySuggestion == nil ? "" : (error as NSError).localizedRecoverySuggestion) ?? "",
|
||
|
"domain": (error as NSError).domain
|
||
|
],
|
||
|
"target": _reactTag
|
||
|
])
|
||
|
|
||
|
}
|
||
|
return false
|
||
|
}
|
||
|
|
||
|
func loadingRequestHandling(_ loadingRequest:AVAssetResourceLoadingRequest!) -> Bool {
|
||
|
if _requestingCertificate {
|
||
|
return true
|
||
|
} else if _requestingCertificateErrored {
|
||
|
return false
|
||
|
}
|
||
|
_loadingRequest = loadingRequest
|
||
|
|
||
|
let url = loadingRequest.request.url
|
||
|
guard let _drm = _drm else {
|
||
|
return finishLoadingWithError(error: RCTVideoErrorHandler.noDRMData)
|
||
|
}
|
||
|
|
||
|
var contentId:String!
|
||
|
let contentIdOverride:String! = _drm.contentId
|
||
|
if contentIdOverride != nil {
|
||
|
contentId = contentIdOverride
|
||
|
} else if (_onGetLicense != nil) {
|
||
|
contentId = url?.host
|
||
|
} else {
|
||
|
contentId = url?.absoluteString.replacingOccurrences(of: "skd://", with:"")
|
||
|
}
|
||
|
|
||
|
let drmType:String! = _drm.type
|
||
|
guard drmType == "fairplay" else {
|
||
|
return finishLoadingWithError(error: RCTVideoErrorHandler.noDRMData)
|
||
|
}
|
||
|
|
||
|
let certificateStringUrl:String! = _drm.certificateUrl
|
||
|
guard let certificateStringUrl = certificateStringUrl, let certificateURL = URL(string: certificateStringUrl.addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed) ?? "") else {
|
||
|
return finishLoadingWithError(error: RCTVideoErrorHandler.noCertificateURL)
|
||
|
}
|
||
|
DispatchQueue.global().async { [weak self] in
|
||
|
guard let self = self else { return }
|
||
|
var certificateData:Data?
|
||
|
if (_drm.base64Certificate != nil) {
|
||
|
certificateData = Data(base64Encoded: certificateData! as Data, options: .ignoreUnknownCharacters)
|
||
|
} else {
|
||
|
do {
|
||
|
certificateData = try Data(contentsOf: certificateURL)
|
||
|
} catch {}
|
||
|
}
|
||
|
|
||
|
guard let certificateData = certificateData else {
|
||
|
self.finishLoadingWithError(error: RCTVideoErrorHandler.noCertificateData)
|
||
|
self._requestingCertificateErrored = true
|
||
|
return
|
||
|
}
|
||
|
|
||
|
var contentIdData:NSData!
|
||
|
if self._onGetLicense != nil {
|
||
|
contentIdData = contentId.data(using: .utf8) as NSData?
|
||
|
} else {
|
||
|
contentIdData = NSData(bytes: contentId.cString(using: String.Encoding.utf8), length:contentId.lengthOfBytes(using: String.Encoding.utf8))
|
||
|
}
|
||
|
|
||
|
let dataRequest:AVAssetResourceLoadingDataRequest! = loadingRequest.dataRequest
|
||
|
guard dataRequest != nil else {
|
||
|
self.finishLoadingWithError(error: RCTVideoErrorHandler.noCertificateData)
|
||
|
self._requestingCertificateErrored = true
|
||
|
return
|
||
|
}
|
||
|
|
||
|
var spcError:NSError!
|
||
|
var spcData: Data?
|
||
|
do {
|
||
|
spcData = try loadingRequest.streamingContentKeyRequestData(forApp: certificateData, contentIdentifier: contentIdData as Data, options: nil)
|
||
|
} catch let spcError {
|
||
|
print("SPC error")
|
||
|
}
|
||
|
// Request CKC to the server
|
||
|
var licenseServer:String! = _drm.licenseServer
|
||
|
if spcError != nil {
|
||
|
self.finishLoadingWithError(error: spcError)
|
||
|
self._requestingCertificateErrored = true
|
||
|
}
|
||
|
|
||
|
guard spcData != nil else {
|
||
|
self.finishLoadingWithError(error: RCTVideoErrorHandler.noSPC)
|
||
|
self._requestingCertificateErrored = true
|
||
|
return
|
||
|
}
|
||
|
|
||
|
// js client has a onGetLicense callback and will handle license fetching
|
||
|
if let _onGetLicense = self._onGetLicense {
|
||
|
let base64Encoded = spcData?.base64EncodedString(options: [])
|
||
|
self._requestingCertificate = true
|
||
|
if licenseServer == nil {
|
||
|
licenseServer = ""
|
||
|
}
|
||
|
_onGetLicense(["licenseUrl": licenseServer,
|
||
|
"contentId": contentId,
|
||
|
"spcBase64": base64Encoded,
|
||
|
"target": self._reactTag])
|
||
|
|
||
|
|
||
|
} else if licenseServer != nil {
|
||
|
self.fetchLicense(
|
||
|
licenseServer: licenseServer,
|
||
|
spcData: spcData,
|
||
|
contentId: contentId,
|
||
|
dataRequest: dataRequest
|
||
|
)
|
||
|
}
|
||
|
}
|
||
|
return true
|
||
|
}
|
||
|
|
||
|
func fetchLicense(
|
||
|
licenseServer: String,
|
||
|
spcData: Data?,
|
||
|
contentId: String,
|
||
|
dataRequest: AVAssetResourceLoadingDataRequest!
|
||
|
) {
|
||
|
var request = URLRequest(url: URL(string: licenseServer)!)
|
||
|
request.httpMethod = "POST"
|
||
|
|
||
|
// HEADERS
|
||
|
if let headers = _drm?.headers {
|
||
|
for item in headers {
|
||
|
guard let key = item.key as? String, let value = item.value as? String else {
|
||
|
continue
|
||
|
}
|
||
|
request.setValue(value, forHTTPHeaderField: key)
|
||
|
}
|
||
|
}
|
||
|
|
||
|
if (_onGetLicense != nil) {
|
||
|
request.httpBody = spcData
|
||
|
} else {
|
||
|
let spcEncoded = spcData?.base64EncodedString(options: [])
|
||
|
let spcUrlEncoded = CFURLCreateStringByAddingPercentEscapes(kCFAllocatorDefault, spcEncoded as? CFString? as! CFString, nil, "?=&+" as CFString, CFStringBuiltInEncodings.UTF8.rawValue) as? String
|
||
|
let post = String(format:"spc=%@&%@", spcUrlEncoded as! CVarArg, contentId)
|
||
|
let postData = post.data(using: String.Encoding.utf8, allowLossyConversion:true)
|
||
|
request.httpBody = postData
|
||
|
}
|
||
|
|
||
|
let postDataTask = URLSession.shared.dataTask(with: request as URLRequest, completionHandler:{ [weak self] (data:Data!,response:URLResponse!,error:Error!) in
|
||
|
guard let self = self else { return }
|
||
|
let httpResponse:HTTPURLResponse! = response as! HTTPURLResponse
|
||
|
guard error == nil else {
|
||
|
print("Error getting license from \(licenseServer), HTTP status code \(httpResponse.statusCode)")
|
||
|
self.finishLoadingWithError(error: error as NSError?)
|
||
|
self._requestingCertificateErrored = true
|
||
|
return
|
||
|
}
|
||
|
guard httpResponse.statusCode == 200 else {
|
||
|
print("Error getting license from \(licenseServer), HTTP status code \(httpResponse.statusCode)")
|
||
|
self.finishLoadingWithError(error: RCTVideoErrorHandler.licenseRequestNotOk(httpResponse.statusCode))
|
||
|
self._requestingCertificateErrored = true
|
||
|
return
|
||
|
}
|
||
|
|
||
|
guard data != nil else {
|
||
|
self.finishLoadingWithError(error: RCTVideoErrorHandler.noDataFromLicenseRequest)
|
||
|
self._requestingCertificateErrored = true
|
||
|
return
|
||
|
}
|
||
|
|
||
|
if (self._onGetLicense != nil) {
|
||
|
dataRequest.respond(with: data)
|
||
|
} else if let decodedData = Data(base64Encoded: data, options: []) {
|
||
|
dataRequest.respond(with: decodedData)
|
||
|
}
|
||
|
self._loadingRequest?.finishLoading()
|
||
|
})
|
||
|
postDataTask.resume()
|
||
|
}
|
||
|
}
|