fix(ios): fairplay different key per asset (#3261)
* [Fix] Replace _loadingRequest instance with _loadingRequests dictionary to support multiple concurrent requests * Remove stored finished requests from dictionary * Keep contentId as is, and send loadingRequest.url in licenseUrl. * Update DRM.md --------- Co-authored-by: Facundo Gutierrez <facundo.gutierrez@tcc.com.uy>
This commit is contained in:
parent
c6ee294403
commit
f4acaccd80
8
Video.js
8
Video.js
@ -279,15 +279,15 @@ export default class Video extends Component {
|
|||||||
const getLicensePromise = Promise.resolve(getLicenseOverride); // Handles both scenarios, getLicenseOverride being a promise and not.
|
const getLicensePromise = Promise.resolve(getLicenseOverride); // Handles both scenarios, getLicenseOverride being a promise and not.
|
||||||
getLicensePromise.then((result => {
|
getLicensePromise.then((result => {
|
||||||
if (result !== undefined) {
|
if (result !== undefined) {
|
||||||
NativeModules.VideoManager.setLicenseResult(result, findNodeHandle(this._root));
|
NativeModules.VideoManager.setLicenseResult(result, data.licenseUrl, findNodeHandle(this._root));
|
||||||
} else {
|
} else {
|
||||||
NativeModules.VideoManager.setLicenseError && NativeModules.VideoManager.setLicenseError('Empty license result', findNodeHandle(this._root));
|
NativeModules.VideoManager.setLicenseError && NativeModules.VideoManager.setLicenseError('Empty license result', data.licenseUrl, findNodeHandle(this._root));
|
||||||
}
|
}
|
||||||
})).catch((error) => {
|
})).catch((error) => {
|
||||||
NativeModules.VideoManager.setLicenseError && NativeModules.VideoManager.setLicenseError(error, findNodeHandle(this._root));
|
NativeModules.VideoManager.setLicenseError && NativeModules.VideoManager.setLicenseError(error, data.licenseUrl, findNodeHandle(this._root));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
NativeModules.VideoManager.setLicenseError && NativeModules.VideoManager.setLicenseError('No spc received', findNodeHandle(this._root));
|
NativeModules.VideoManager.setLicenseError && NativeModules.VideoManager.setLicenseError('No spc received', data.licenseUrl, findNodeHandle(this._root));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,12 +32,15 @@ Platforms: iOS
|
|||||||
### `getLicense`
|
### `getLicense`
|
||||||
|
|
||||||
`licenseServer` and `headers` will be ignored. You will obtain as argument the `SPC` (as ASCII string, you will probably need to convert it to base 64) obtained from your `contentId` + the provided certificate via `[loadingRequest streamingContentKeyRequestDataForApp:certificateData contentIdentifier:contentIdData options:nil error:&spcError];`.
|
`licenseServer` and `headers` will be ignored. You will obtain as argument the `SPC` (as ASCII string, you will probably need to convert it to base 64) obtained from your `contentId` + the provided certificate via `[loadingRequest streamingContentKeyRequestDataForApp:certificateData contentIdentifier:contentIdData options:nil error:&spcError];`.
|
||||||
You should return on this method a `CKC` in Base64, either by just returning it or returning a `Promise` that resolves with the `CKC`.
|
|
||||||
|
Also, you will receive the `contentId` and a `licenseUrl` URL defined as `loadingRequest.request.URL.absoluteString ` or as the `licenseServer` prop if it's passed.
|
||||||
|
|
||||||
|
You should return on this method a `CKC` in Base64, either by just returning it or returning a `Promise` that resolves with the `CKC`.
|
||||||
|
|
||||||
With this prop you can override the license acquisition flow, as an example:
|
With this prop you can override the license acquisition flow, as an example:
|
||||||
|
|
||||||
```js
|
```js
|
||||||
getLicense: (spcString) => {
|
getLicense: (spcString, contentId, licenseUrl) => {
|
||||||
const base64spc = Base64.encode(spcString);
|
const base64spc = Base64.encode(spcString);
|
||||||
const formData = new FormData();
|
const formData = new FormData();
|
||||||
formData.append('spc', base64spc);
|
formData.append('spc', base64spc);
|
||||||
|
@ -3,7 +3,7 @@ import Promises
|
|||||||
|
|
||||||
class RCTResourceLoaderDelegate: NSObject, AVAssetResourceLoaderDelegate, URLSessionDelegate {
|
class RCTResourceLoaderDelegate: NSObject, AVAssetResourceLoaderDelegate, URLSessionDelegate {
|
||||||
|
|
||||||
private var _loadingRequest:AVAssetResourceLoadingRequest?
|
private var _loadingRequests: [String: AVAssetResourceLoadingRequest?] = [:]
|
||||||
private var _requestingCertificate:Bool = false
|
private var _requestingCertificate:Bool = false
|
||||||
private var _requestingCertificateErrored:Bool = false
|
private var _requestingCertificateErrored:Bool = false
|
||||||
private var _drm: DRMParams?
|
private var _drm: DRMParams?
|
||||||
@ -32,7 +32,9 @@ class RCTResourceLoaderDelegate: NSObject, AVAssetResourceLoaderDelegate, URLSes
|
|||||||
}
|
}
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
_loadingRequest?.finishLoading()
|
for request in _loadingRequests.values {
|
||||||
|
request?.finishLoading()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func resourceLoader(_ resourceLoader:AVAssetResourceLoader, shouldWaitForRenewalOfRequestedResource renewalRequest:AVAssetResourceRenewalRequest) -> Bool {
|
func resourceLoader(_ resourceLoader:AVAssetResourceLoader, shouldWaitForRenewalOfRequestedResource renewalRequest:AVAssetResourceRenewalRequest) -> Bool {
|
||||||
@ -47,42 +49,60 @@ class RCTResourceLoaderDelegate: NSObject, AVAssetResourceLoaderDelegate, URLSes
|
|||||||
RCTLog("didCancelLoadingRequest")
|
RCTLog("didCancelLoadingRequest")
|
||||||
}
|
}
|
||||||
|
|
||||||
func setLicenseResult(_ license:String!) {
|
func setLicenseResult(_ license:String!,_ licenseUrl: String!) {
|
||||||
guard let respondData = RCTVideoUtils.base64DataFromBase64String(base64String: license),
|
|
||||||
let _loadingRequest = _loadingRequest else {
|
// Check if the loading request exists in _loadingRequests based on licenseUrl
|
||||||
setLicenseResultError("No data from JS license response")
|
guard let loadingRequest = _loadingRequests[licenseUrl] else {
|
||||||
|
setLicenseResultError("Loading request for licenseUrl \(licenseUrl) not found", licenseUrl)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
let dataRequest:AVAssetResourceLoadingDataRequest! = _loadingRequest.dataRequest
|
|
||||||
|
// Check if the license data is valid
|
||||||
|
guard let respondData = RCTVideoUtils.base64DataFromBase64String(base64String: license) else {
|
||||||
|
setLicenseResultError("No data from JS license response", licenseUrl)
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
let dataRequest: AVAssetResourceLoadingDataRequest! = loadingRequest?.dataRequest
|
||||||
dataRequest.respond(with: respondData)
|
dataRequest.respond(with: respondData)
|
||||||
_loadingRequest.finishLoading()
|
loadingRequest!.finishLoading()
|
||||||
|
_loadingRequests.removeValue(forKey: licenseUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setLicenseResultError(_ error:String!) {
|
func setLicenseResultError(_ error:String!,_ licenseUrl: String!) {
|
||||||
if _loadingRequest != nil {
|
// Check if the loading request exists in _loadingRequests based on licenseUrl
|
||||||
self.finishLoadingWithError(error: RCTVideoErrorHandler.fromJSPart(error))
|
guard let loadingRequest = _loadingRequests[licenseUrl] else {
|
||||||
}
|
print("Loading request for licenseUrl \(licenseUrl) not found. Error: \(error)")
|
||||||
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
func finishLoadingWithError(error:Error!) -> Bool {
|
self.finishLoadingWithError(error: RCTVideoErrorHandler.fromJSPart(error), licenseUrl: licenseUrl)
|
||||||
if let _loadingRequest = _loadingRequest, let error = error {
|
}
|
||||||
_loadingRequest.finishLoading(with: error as! NSError)
|
|
||||||
|
|
||||||
|
func finishLoadingWithError(error: Error!, licenseUrl: String!) -> Bool {
|
||||||
|
// Check if the loading request exists in _loadingRequests based on licenseUrl
|
||||||
|
guard let loadingRequest = _loadingRequests[licenseUrl], let error = error as NSError? else {
|
||||||
|
// Handle the case where the loading request is not found or error is nil
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
loadingRequest!.finishLoading(with: error)
|
||||||
|
_loadingRequests.removeValue(forKey: licenseUrl)
|
||||||
_onVideoError?([
|
_onVideoError?([
|
||||||
"error": [
|
"error": [
|
||||||
"code": NSNumber(value: (error as NSError).code),
|
"code": NSNumber(value: error.code),
|
||||||
"localizedDescription": error.localizedDescription == nil ? "" : error.localizedDescription,
|
"localizedDescription": error.localizedDescription ?? "",
|
||||||
"localizedFailureReason": ((error as NSError).localizedFailureReason == nil ? "" : (error as NSError).localizedFailureReason) ?? "",
|
"localizedFailureReason": error.localizedFailureReason ?? "",
|
||||||
"localizedRecoverySuggestion": ((error as NSError).localizedRecoverySuggestion == nil ? "" : (error as NSError).localizedRecoverySuggestion) ?? "",
|
"localizedRecoverySuggestion": error.localizedRecoverySuggestion ?? "",
|
||||||
"domain": (error as NSError).domain
|
"domain": error.domain
|
||||||
],
|
],
|
||||||
"target": _reactTag
|
"target": _reactTag
|
||||||
])
|
])
|
||||||
|
|
||||||
}
|
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
func loadingRequestHandling(_ loadingRequest:AVAssetResourceLoadingRequest!) -> Bool {
|
func loadingRequestHandling(_ loadingRequest:AVAssetResourceLoadingRequest!) -> Bool {
|
||||||
if handleEmbeddedKey(loadingRequest) {
|
if handleEmbeddedKey(loadingRequest) {
|
||||||
return true
|
return true
|
||||||
@ -118,10 +138,13 @@ class RCTResourceLoaderDelegate: NSObject, AVAssetResourceLoaderDelegate, URLSes
|
|||||||
} else if _requestingCertificateErrored {
|
} else if _requestingCertificateErrored {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
_loadingRequest = loadingRequest
|
|
||||||
|
var requestKey: String = loadingRequest.request.url?.absoluteString ?? ""
|
||||||
|
|
||||||
|
_loadingRequests[requestKey] = loadingRequest
|
||||||
|
|
||||||
guard let _drm = _drm, let drmType = _drm.type, drmType == "fairplay" else {
|
guard let _drm = _drm, let drmType = _drm.type, drmType == "fairplay" else {
|
||||||
return finishLoadingWithError(error: RCTVideoErrorHandler.noDRMData)
|
return finishLoadingWithError(error: RCTVideoErrorHandler.noDRMData, licenseUrl: requestKey)
|
||||||
}
|
}
|
||||||
|
|
||||||
var promise: Promise<Data>
|
var promise: Promise<Data>
|
||||||
@ -134,8 +157,8 @@ class RCTResourceLoaderDelegate: NSObject, AVAssetResourceLoaderDelegate, URLSes
|
|||||||
base64Certificate:_drm.base64Certificate
|
base64Certificate:_drm.base64Certificate
|
||||||
) .then{ spcData -> Void in
|
) .then{ spcData -> Void in
|
||||||
self._requestingCertificate = true
|
self._requestingCertificate = true
|
||||||
self._onGetLicense?(["licenseUrl": self._drm?.licenseServer ?? "",
|
self._onGetLicense?(["licenseUrl": self._drm?.licenseServer ?? loadingRequest.request.url?.absoluteString ?? "",
|
||||||
"contentId": contentId,
|
"contentId": contentId ?? "",
|
||||||
"spcBase64": spcData.base64EncodedString(options: []),
|
"spcBase64": spcData.base64EncodedString(options: []),
|
||||||
"target": self._reactTag])
|
"target": self._reactTag])
|
||||||
}
|
}
|
||||||
@ -158,7 +181,7 @@ class RCTResourceLoaderDelegate: NSObject, AVAssetResourceLoaderDelegate, URLSes
|
|||||||
|
|
||||||
|
|
||||||
promise.catch{ error in
|
promise.catch{ error in
|
||||||
self.finishLoadingWithError(error:error)
|
self.finishLoadingWithError(error:error, licenseUrl: requestKey)
|
||||||
self._requestingCertificateErrored = true
|
self._requestingCertificateErrored = true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1030,12 +1030,12 @@ class RCTVideo: UIView, RCTVideoPlayerViewControllerDelegate, RCTPlayerObserverH
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setLicenseResult(_ license:String!) {
|
func setLicenseResult(_ license:String!, _ licenseUrl: String!) {
|
||||||
_resouceLoaderDelegate?.setLicenseResult(license)
|
_resouceLoaderDelegate?.setLicenseResult(license, licenseUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
func setLicenseResultError(_ error:String!) {
|
func setLicenseResultError(_ error:String!, _ licenseUrl: String!) {
|
||||||
_resouceLoaderDelegate?.setLicenseResultError(error)
|
_resouceLoaderDelegate?.setLicenseResultError(error, licenseUrl)
|
||||||
}
|
}
|
||||||
|
|
||||||
func dismissFullscreenPlayer(_ error:String!) {
|
func dismissFullscreenPlayer(_ error:String!) {
|
||||||
|
@ -69,9 +69,11 @@ RCT_EXTERN_METHOD(save:(NSDictionary *)options
|
|||||||
rejecter:(RCTPromiseRejectBlock)reject)
|
rejecter:(RCTPromiseRejectBlock)reject)
|
||||||
|
|
||||||
RCT_EXTERN_METHOD(setLicenseResult:(NSString *)license
|
RCT_EXTERN_METHOD(setLicenseResult:(NSString *)license
|
||||||
|
licenseUrl:(NSString *)licenseUrl
|
||||||
reactTag:(nonnull NSNumber *)reactTag)
|
reactTag:(nonnull NSNumber *)reactTag)
|
||||||
|
|
||||||
RCT_EXTERN_METHOD(setLicenseResultError(NSString *)error
|
RCT_EXTERN_METHOD(setLicenseResultError:(NSString *)error
|
||||||
|
licenseUrl:(NSString *)licenseUrl
|
||||||
reactTag:(nonnull NSNumber *)reactTag)
|
reactTag:(nonnull NSNumber *)reactTag)
|
||||||
|
|
||||||
RCT_EXTERN_METHOD(setPlayerPauseState:(nonnull NSNumber *)paused
|
RCT_EXTERN_METHOD(setPlayerPauseState:(nonnull NSNumber *)paused
|
||||||
|
@ -24,26 +24,26 @@ class RCTVideoManager: RCTViewManager {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc(setLicenseResult:reactTag:)
|
@objc(setLicenseResult:licenseUrl:reactTag:)
|
||||||
func setLicenseResult(license: NSString, reactTag: NSNumber) -> Void {
|
func setLicenseResult(license: NSString, licenseUrl:NSString, reactTag: NSNumber) -> Void {
|
||||||
bridge.uiManager.prependUIBlock({_ , viewRegistry in
|
bridge.uiManager.prependUIBlock({_ , viewRegistry in
|
||||||
let view = viewRegistry?[reactTag]
|
let view = viewRegistry?[reactTag]
|
||||||
if !(view is RCTVideo) {
|
if !(view is RCTVideo) {
|
||||||
RCTLogError("Invalid view returned from registry, expecting RCTVideo, got: %@", String(describing: view))
|
RCTLogError("Invalid view returned from registry, expecting RCTVideo, got: %@", String(describing: view))
|
||||||
} else if let view = view as? RCTVideo {
|
} else if let view = view as? RCTVideo {
|
||||||
view.setLicenseResult(license as String)
|
view.setLicenseResult(license as String, licenseUrl as String)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc(setLicenseResultError:reactTag:)
|
@objc(setLicenseResultError:licenseUrl:reactTag:)
|
||||||
func setLicenseResultError(error: NSString, reactTag: NSNumber) -> Void {
|
func setLicenseResultError(error: NSString, licenseUrl:NSString, reactTag: NSNumber) -> Void {
|
||||||
bridge.uiManager.prependUIBlock({_ , viewRegistry in
|
bridge.uiManager.prependUIBlock({_ , viewRegistry in
|
||||||
let view = viewRegistry?[reactTag]
|
let view = viewRegistry?[reactTag]
|
||||||
if !(view is RCTVideo) {
|
if !(view is RCTVideo) {
|
||||||
RCTLogError("Invalid view returned from registry, expecting RCTVideo, got: %@", String(describing: view))
|
RCTLogError("Invalid view returned from registry, expecting RCTVideo, got: %@", String(describing: view))
|
||||||
} else if let view = view as? RCTVideo {
|
} else if let view = view as? RCTVideo {
|
||||||
view.setLicenseResultError(error as String)
|
view.setLicenseResultError(error as String, licenseUrl as String)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user