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
		
			
				
	
	
		
			168 lines
		
	
	
		
			6.6 KiB
		
	
	
	
		
			Swift
		
	
	
	
	
	
			
		
		
	
	
			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
 | |
|     }
 | |
| }
 |