2022-05-19 07:29:25 -06:00
|
|
|
import AVFoundation
|
|
|
|
|
2023-12-07 00:47:40 -07:00
|
|
|
enum RCTVideoDRM {
|
2022-05-19 07:29:25 -06:00
|
|
|
static func fetchLicense(
|
|
|
|
licenseServer: String,
|
|
|
|
spcData: Data?,
|
|
|
|
contentId: String,
|
2023-12-07 00:47:40 -07:00
|
|
|
headers: [String: Any]?
|
2024-04-04 05:23:44 -06:00
|
|
|
) async throws -> Data {
|
2023-12-07 00:47:40 -07:00
|
|
|
let request = createLicenseRequest(licenseServer: licenseServer, spcData: spcData, contentId: contentId, headers: headers)
|
|
|
|
|
2024-04-04 05:23:44 -06:00
|
|
|
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)
|
2022-05-19 07:29:25 -06:00
|
|
|
}
|
2024-04-04 05:23:44 -06:00
|
|
|
|
|
|
|
guard let decodedData = Data(base64Encoded: data, options: []) else {
|
|
|
|
throw RCTVideoErrorHandler.noDataFromLicenseRequest
|
|
|
|
}
|
|
|
|
|
|
|
|
return decodedData
|
2022-05-19 07:29:25 -06:00
|
|
|
}
|
2023-12-07 00:47:40 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
static func createLicenseRequest(
|
|
|
|
licenseServer: String,
|
|
|
|
spcData: Data?,
|
|
|
|
contentId: String,
|
2023-12-07 00:47:40 -07:00
|
|
|
headers: [String: Any]?
|
2022-05-19 07:29:25 -06:00
|
|
|
) -> URLRequest {
|
|
|
|
var request = URLRequest(url: URL(string: licenseServer)!)
|
|
|
|
request.httpMethod = "POST"
|
2023-12-07 00:47:40 -07:00
|
|
|
|
2024-01-04 12:16:23 -07:00
|
|
|
if let headers {
|
2022-05-19 07:29:25 -06:00
|
|
|
for item in headers {
|
|
|
|
guard let key = item.key as? String, let value = item.value as? String else {
|
|
|
|
continue
|
|
|
|
}
|
|
|
|
request.setValue(value, forHTTPHeaderField: key)
|
|
|
|
}
|
|
|
|
}
|
2023-12-07 00:47:40 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
let spcEncoded = spcData?.base64EncodedString(options: [])
|
2023-12-07 00:47:40 -07:00
|
|
|
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)
|
2022-05-19 07:29:25 -06:00
|
|
|
request.httpBody = postData
|
2023-12-07 00:47:40 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
return request
|
|
|
|
}
|
2023-12-07 00:47:40 -07:00
|
|
|
|
2022-05-19 07:29:25 -06:00
|
|
|
static func fetchSpcData(
|
|
|
|
loadingRequest: AVAssetResourceLoadingRequest,
|
|
|
|
certificateData: Data,
|
|
|
|
contentIdData: Data
|
2024-04-04 05:23:44 -06:00
|
|
|
) 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
|
|
|
|
}
|
2023-12-07 00:47:40 -07:00
|
|
|
|
2024-04-04 05:23:44 -06:00
|
|
|
return spcData
|
|
|
|
#endif
|
2022-05-19 07:29:25 -06:00
|
|
|
}
|
|
|
|
|
2024-04-04 05:23:44 -06:00
|
|
|
static func createCertificateData(certificateStringUrl: String?, base64Certificate: Bool?) throws -> Data {
|
|
|
|
guard let certificateStringUrl,
|
|
|
|
let certificateURL = URL(string: certificateStringUrl.addingPercentEncoding(withAllowedCharacters: .urlFragmentAllowed) ?? "") else {
|
|
|
|
throw RCTVideoErrorHandler.noCertificateURL
|
|
|
|
}
|
2023-12-07 00:47:40 -07:00
|
|
|
|
2024-04-04 05:23:44 -06:00
|
|
|
var certificateData: Data?
|
|
|
|
do {
|
|
|
|
certificateData = try Data(contentsOf: certificateURL)
|
|
|
|
if base64Certificate != nil {
|
|
|
|
certificateData = Data(base64Encoded: certificateData! as Data, options: .ignoreUnknownCharacters)
|
2022-05-19 07:29:25 -06:00
|
|
|
}
|
2024-04-04 05:23:44 -06:00
|
|
|
} catch {}
|
2023-12-07 00:47:40 -07:00
|
|
|
|
2024-04-04 05:23:44 -06:00
|
|
|
guard let certificateData else {
|
|
|
|
throw RCTVideoErrorHandler.noCertificateData
|
2022-05-19 07:29:25 -06:00
|
|
|
}
|
2024-04-04 05:23:44 -06:00
|
|
|
|
|
|
|
return certificateData
|
2022-05-19 07:29:25 -06:00
|
|
|
}
|
2023-12-07 00:47:40 -07:00
|
|
|
|
|
|
|
static func handleWithOnGetLicense(loadingRequest: AVAssetResourceLoadingRequest, contentId: String?, certificateUrl: String?,
|
2024-04-04 05:23:44 -06:00
|
|
|
base64Certificate: Bool?) throws -> Data {
|
2022-05-19 07:29:25 -06:00
|
|
|
let contentIdData = contentId?.data(using: .utf8)
|
2023-12-07 00:47:40 -07:00
|
|
|
|
2024-04-04 05:23:44 -06:00
|
|
|
let certificateData = try? RCTVideoDRM.createCertificateData(certificateStringUrl: certificateUrl, base64Certificate: base64Certificate)
|
2023-12-07 00:47:40 -07:00
|
|
|
|
2024-04-04 05:23:44 -06:00
|
|
|
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
|
|
|
|
)
|
2022-05-19 07:29:25 -06:00
|
|
|
}
|
2023-12-07 00:47:40 -07:00
|
|
|
|
|
|
|
static func handleInternalGetLicense(
|
|
|
|
loadingRequest: AVAssetResourceLoadingRequest,
|
|
|
|
contentId: String?,
|
|
|
|
licenseServer: String?,
|
|
|
|
certificateUrl: String?,
|
|
|
|
base64Certificate: Bool?,
|
|
|
|
headers: [String: Any]?
|
2024-04-04 05:23:44 -06:00
|
|
|
) async throws -> Data {
|
2022-05-19 07:29:25 -06:00
|
|
|
let url = loadingRequest.request.url
|
2023-12-07 00:47:40 -07:00
|
|
|
|
2024-03-26 06:55:11 -06:00
|
|
|
let parsedContentId = contentId != nil && !contentId!.isEmpty ? contentId : nil
|
|
|
|
|
|
|
|
guard let contentId = parsedContentId ?? url?.absoluteString.replacingOccurrences(of: "skd://", with: "") else {
|
2024-04-04 05:23:44 -06:00
|
|
|
throw RCTVideoError.invalidContentId as! Error
|
2022-05-19 07:29:25 -06:00
|
|
|
}
|
2023-12-07 00:47:40 -07:00
|
|
|
|
|
|
|
let contentIdData = NSData(bytes: contentId.cString(using: String.Encoding.utf8), length: contentId.lengthOfBytes(using: String.Encoding.utf8)) as Data
|
2024-04-04 05:23:44 -06:00
|
|
|
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
|
|
|
|
}
|
2023-12-07 00:47:40 -07:00
|
|
|
|
2024-04-04 05:23:44 -06:00
|
|
|
return try await RCTVideoDRM.fetchLicense(
|
|
|
|
licenseServer: licenseServer,
|
|
|
|
spcData: spcData,
|
|
|
|
contentId: contentId,
|
|
|
|
headers: headers
|
|
|
|
)
|
2022-05-19 07:29:25 -06:00
|
|
|
}
|
|
|
|
}
|