import AVFoundation import Promises 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 _localSourceEncryptionKeyScheme: String? private var _reactTag: NSNumber? private var _onVideoError: RCTDirectEventBlock? private var _onGetLicense: RCTDirectEventBlock? init( asset: AVURLAsset, drm: DRMParams?, localSourceEncryptionKeyScheme: String?, 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 _localSourceEncryptionKeyScheme = localSourceEncryptionKeyScheme } 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 setLicenseResult(_ license:String!) { guard let respondData = RCTVideoUtils.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:Error!) -> Bool { if let _loadingRequest = _loadingRequest, let error = error { _loadingRequest.finishLoading(with: error as! NSError) _onVideoError?([ "error": [ "code": NSNumber(value: (error as NSError).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 handleEmbeddedKey(loadingRequest) { return true } if _drm != nil { return handleDrm(loadingRequest) } return false } func handleEmbeddedKey(_ loadingRequest:AVAssetResourceLoadingRequest!) -> Bool { guard let url = loadingRequest.request.url, let _localSourceEncryptionKeyScheme = _localSourceEncryptionKeyScheme, let persistentKeyData = RCTVideoUtils.extractDataFromCustomSchemeUrl(from: url, scheme: _localSourceEncryptionKeyScheme) else { return false } loadingRequest.contentInformationRequest?.contentType = AVStreamingKeyDeliveryPersistentContentKeyType loadingRequest.contentInformationRequest?.isByteRangeAccessSupported = true loadingRequest.contentInformationRequest?.contentLength = Int64(persistentKeyData.count) loadingRequest.dataRequest?.respond(with: persistentKeyData) loadingRequest.finishLoading() return true } func handleDrm(_ loadingRequest:AVAssetResourceLoadingRequest!) -> Bool { if _requestingCertificate { return true } else if _requestingCertificateErrored { return false } _loadingRequest = loadingRequest guard let _drm = _drm, let drmType = _drm.type, drmType == "fairplay" else { return finishLoadingWithError(error: RCTVideoErrorHandler.noDRMData) } var promise: Promise if _onGetLicense != nil { let contentId = _drm.contentId ?? loadingRequest.request.url?.host promise = RCTVideoDRM.handleWithOnGetLicense( loadingRequest:loadingRequest, contentId:contentId, certificateUrl:_drm.certificateUrl, base64Certificate:_drm.base64Certificate ) .then{ spcData -> Void in self._requestingCertificate = true self._onGetLicense?(["licenseUrl": self._drm?.licenseServer ?? "", "contentId": contentId, "spcBase64": spcData.base64EncodedString(options: []), "target": self._reactTag]) } } else { promise = RCTVideoDRM.handleInternalGetLicense( loadingRequest:loadingRequest, contentId:_drm.contentId, licenseServer:_drm.licenseServer, certificateUrl:_drm.certificateUrl, base64Certificate:_drm.base64Certificate, headers:_drm.headers ) .then{ data -> Void in guard let dataRequest = loadingRequest.dataRequest else { throw RCTVideoErrorHandler.noCertificateData } dataRequest.respond(with:data) loadingRequest.finishLoading() } } promise.catch{ error in self.finishLoadingWithError(error:error) self._requestingCertificateErrored = true } return true } }