react-native-video/ios/Video/Features/RCTVideoDRM.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

169 lines
6.9 KiB
Swift

import AVFoundation
import Promises
struct RCTVideoDRM {
@available(*, unavailable) private init() {}
static func fetchLicense(
licenseServer: String,
spcData: Data?,
contentId: String,
headers: [String:Any]?
) -> Promise<Data> {
let request = createLicenseRequest(licenseServer:licenseServer, spcData:spcData, contentId:contentId, headers:headers)
return Promise<Data>(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 = 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<Data> {
return Promise<Data>(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 = spcData else {
reject(RCTVideoErrorHandler.noSPC)
return
}
fulfill(spcData)
}
}
static func createCertificateData(certificateStringUrl:String?, base64Certificate:Bool?) -> Promise<Data> {
return Promise<Data>(on: .global()) { fulfill, reject in
guard let certificateStringUrl = 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 = certificateData else {
reject(RCTVideoErrorHandler.noCertificateData)
return
}
fulfill(certificateData)
}
}
static func handleWithOnGetLicense(loadingRequest: AVAssetResourceLoadingRequest, contentId:String?, certificateUrl:String?, base64Certificate:Bool?) -> Promise<Data> {
let contentIdData = contentId?.data(using: .utf8)
return RCTVideoDRM.createCertificateData(certificateStringUrl:certificateUrl, base64Certificate:base64Certificate)
.then{ certificateData -> Promise<Data> in
guard let contentIdData = 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<Data> {
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<Data> in
guard let licenseServer = licenseServer else {
throw RCTVideoError.noLicenseServerURL as! Error
}
return RCTVideoDRM.fetchLicense(
licenseServer: licenseServer,
spcData: spcData,
contentId: contentId,
headers: headers
)
}
}
}