import AVFoundation import Promises enum RCTVideoDRM { static func fetchLicense( licenseServer: String, spcData: Data?, contentId: String, headers: [String: Any]? ) -> Promise { let request = createLicenseRequest(licenseServer: licenseServer, spcData: spcData, contentId: contentId, headers: headers) return Promise(on: .global()) { fulfill, reject in let postDataTask = URLSession.shared.dataTask( with: request as URLRequest, completionHandler: { (data: Data!, response: URLResponse!, error: Error!) in let httpResponse: HTTPURLResponse! = (response as! HTTPURLResponse) guard error == nil else { print("Error getting license from \(licenseServer), HTTP status code \(httpResponse.statusCode)") reject(error) return } guard httpResponse.statusCode == 200 else { print("Error getting license from \(licenseServer), HTTP status code \(httpResponse.statusCode)") reject(RCTVideoErrorHandler.licenseRequestNotOk(httpResponse.statusCode)) return } guard data != nil, let decodedData = Data(base64Encoded: data, options: []) else { reject(RCTVideoErrorHandler.noDataFromLicenseRequest) return } fulfill(decodedData) } ) postDataTask.resume() } } static func createLicenseRequest( licenseServer: String, spcData: Data?, contentId: String, headers: [String: Any]? ) -> URLRequest { var request = URLRequest(url: URL(string: licenseServer)!) request.httpMethod = "POST" if let 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) } } 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 return request } static func fetchSpcData( loadingRequest: AVAssetResourceLoadingRequest, certificateData: Data, contentIdData: Data ) -> Promise { return Promise(on: .global()) { fulfill, reject in var spcError: NSError! var spcData: Data? do { spcData = try loadingRequest.streamingContentKeyRequestData(forApp: certificateData, contentIdentifier: contentIdData as Data, options: nil) } catch _ { print("SPC error") } if spcError != nil { reject(spcError) } guard let spcData else { reject(RCTVideoErrorHandler.noSPC) return } fulfill(spcData) } } static func createCertificateData(certificateStringUrl: String?, base64Certificate: Bool?) -> Promise { return Promise(on: .global()) { fulfill, reject in guard let certificateStringUrl, let certificateURL = URL(string: certificateStringUrl.addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed) ?? "") else { reject(RCTVideoErrorHandler.noCertificateURL) return } var certificateData: Data? do { certificateData = try Data(contentsOf: certificateURL) if base64Certificate != nil { certificateData = Data(base64Encoded: certificateData! as Data, options: .ignoreUnknownCharacters) } } catch {} guard let certificateData else { reject(RCTVideoErrorHandler.noCertificateData) return } fulfill(certificateData) } } static func handleWithOnGetLicense(loadingRequest: AVAssetResourceLoadingRequest, contentId: String?, certificateUrl: String?, base64Certificate: Bool?) -> Promise { let contentIdData = contentId?.data(using: .utf8) return RCTVideoDRM.createCertificateData(certificateStringUrl: certificateUrl, base64Certificate: base64Certificate) .then { certificateData -> Promise in guard let contentIdData else { throw RCTVideoError.invalidContentId as! Error } return RCTVideoDRM.fetchSpcData( loadingRequest: loadingRequest, certificateData: certificateData, contentIdData: contentIdData ) } } static func handleInternalGetLicense( loadingRequest: AVAssetResourceLoadingRequest, contentId: String?, licenseServer: String?, certificateUrl: String?, base64Certificate: Bool?, headers: [String: Any]? ) -> Promise { let url = loadingRequest.request.url guard let contentId = contentId ?? url?.absoluteString.replacingOccurrences(of: "skd://", with: "") else { return Promise(RCTVideoError.invalidContentId as! Error) } let contentIdData = NSData(bytes: contentId.cString(using: String.Encoding.utf8), length: contentId.lengthOfBytes(using: String.Encoding.utf8)) as Data return RCTVideoDRM.createCertificateData(certificateStringUrl: certificateUrl, base64Certificate: base64Certificate) .then { certificateData in return RCTVideoDRM.fetchSpcData( loadingRequest: loadingRequest, certificateData: certificateData, contentIdData: contentIdData ) } .then { spcData -> Promise in guard let licenseServer else { throw RCTVideoError.noLicenseServerURL as! Error } return RCTVideoDRM.fetchLicense( licenseServer: licenseServer, spcData: spcData, contentId: contentId, headers: headers ) } } }