import AVFoundation enum RCTVideoDRM { static func fetchLicense( licenseServer: String, spcData: Data?, contentId: String, headers: [String: Any]? ) async throws -> Data { let request = createLicenseRequest(licenseServer: licenseServer, spcData: spcData, contentId: contentId, headers: headers) let (data, response) = try await URLSession.shared.data(from: request) guard let httpResponse = response as? HTTPURLResponse else { throw RCTVideoErrorHandler.noDataFromLicenseRequest } if httpResponse.statusCode != 200 { print("Error getting license from \(licenseServer), HTTP status code \(httpResponse.statusCode)") throw RCTVideoErrorHandler.licenseRequestNotOk(httpResponse.statusCode) } guard let decodedData = Data(base64Encoded: data, options: []) else { throw RCTVideoErrorHandler.noDataFromLicenseRequest } return decodedData } 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 ) throws -> Data { #if os(visionOS) // TODO: DRM is not supported yet on visionOS. See #3467 throw NSError(domain: "DRM is not supported yet on visionOS", code: 0, userInfo: nil) #else guard let spcData = try? loadingRequest.streamingContentKeyRequestData( forApp: certificateData, contentIdentifier: contentIdData as Data, options: nil ) else { throw RCTVideoErrorHandler.noSPC } return spcData #endif } static func createCertificateData(certificateStringUrl: String?, base64Certificate: Bool?) throws -> Data { guard let certificateStringUrl, let certificateURL = URL(string: certificateStringUrl.addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed) ?? "") else { throw RCTVideoErrorHandler.noCertificateURL } 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 { throw RCTVideoErrorHandler.noCertificateData } return certificateData } static func handleWithOnGetLicense(loadingRequest: AVAssetResourceLoadingRequest, contentId: String?, certificateUrl: String?, base64Certificate: Bool?) throws -> Data { let contentIdData = contentId?.data(using: .utf8) let certificateData = try? RCTVideoDRM.createCertificateData(certificateStringUrl: certificateUrl, base64Certificate: base64Certificate) guard let contentIdData else { throw RCTVideoError.invalidContentId as! Error } guard let certificateData else { throw RCTVideoError.noCertificateData as! Error } return try RCTVideoDRM.fetchSpcData( loadingRequest: loadingRequest, certificateData: certificateData, contentIdData: contentIdData ) } static func handleInternalGetLicense( loadingRequest: AVAssetResourceLoadingRequest, contentId: String?, licenseServer: String?, certificateUrl: String?, base64Certificate: Bool?, headers: [String: Any]? ) async throws -> Data { let url = loadingRequest.request.url let parsedContentId = contentId != nil && !contentId!.isEmpty ? contentId : nil guard let contentId = parsedContentId ?? url?.absoluteString.replacingOccurrences(of: "skd://", with: "") else { throw RCTVideoError.invalidContentId as! Error } let contentIdData = NSData(bytes: contentId.cString(using: String.Encoding.utf8), length: contentId.lengthOfBytes(using: String.Encoding.utf8)) as Data let certificateData = try RCTVideoDRM.createCertificateData(certificateStringUrl: certificateUrl, base64Certificate: base64Certificate) let spcData = try RCTVideoDRM.fetchSpcData( loadingRequest: loadingRequest, certificateData: certificateData, contentIdData: contentIdData ) guard let licenseServer else { throw RCTVideoError.noLicenseServerURL as! Error } return try await RCTVideoDRM.fetchLicense( licenseServer: licenseServer, spcData: spcData, contentId: contentId, headers: headers ) } }