react-native-video/ios/Video/Features/RCTResourceLoaderDelegate.swift
Nick Fujita 68b9db4d11
iOS Swift Conversion (#2527)
Converts iOS implementation from Objective-c to Swift

# During the conversion process some updates to the code structure were also made
- Modularize codebase from single file to smaller focused files
- Untangled large nested IF statements
- Added more null checks, since Swift is more strict with null pointers
- Added property to allow for decoding of local video sources with self contained key for offline playback
- Updates example apps to test react-native 0.63.4 and uses auto native dependency imports for android and ios
2022-05-19 22:29:25 +09:00

168 lines
6.6 KiB
Swift

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<Data>
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
}
}