Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix/fairplay different key per asset #3261

Conversation

facugu1998
Copy link
Contributor

Summary

As described in #3227, when a content has mutliple asset and each asset has different key, for example, skd://{key}/AUDIO for audio playlist, skd://{key}/HD and skd://{key}/SD for video playlist, the content is not being played.

Problem found

In ios/Video/Features/RCTResourceLoaderDelegate.swift, for every contentId the function handleDrm() is called. For every call, the _loadingRequest object was set to the current call, so the reference to the previous _loadingRequest is lost. Also, when the license is retrieved from the getLicense function, the setLicenseResult is called, and sets the current _loadingRequest to finished, event though it was already finished.

Solution description

We replaced the _loadingRequest object to a dictionary of AVAssetResourceLoadingRequest with key the contentId of the request. So, when the license is resolved, it looks for the request in the dictionary and finishes it with the corresponding value. Then, it removes it from the dictionary.

In order to get that working, we had to add the parameter contentId to the native functions setLicenseResult and setLicenseResultError. Also, we had to pass the contentId as loadingRequest.request.url?.absoluteString, in _onGetLicense inside handleDRM, so, the getLicense function now receives the full request url and not only the request's host.

Video Sample

You can test the problem and solution with the following content:

URL: https://cdnw1-stg.zetatv.com.uy/testzapp/bigbuckbunny/playlist.m3u8
licenseServer: https://pm-stg.zetatv.com.uy:8443/api/fairplay/get_license/fbdd541b-0036-4b4c-afef-351b7627ba81/
certificateUrl: https://pm-stg.zetatv.com.uy:8443/fairplay/cert.crt

Requirements for testing

The onGetLicense function defined on the video component should be the following:

import { Base64 } from "js-base64";
getLicense: async (spc, skd) => {
          return fetch(licenseServer, {
            method: "POST",
            headers: {
              "Content-Type": "application/octet-stream",
              keyId: skd,
            },
            body: Base64.toUint8Array(spc),
          })
            .then((response) => response.blob())
            .then(async (response) => {
              const base_64 = await blobToBase64(response);
              return base_64.split(",")[1];
            })
            .catch((error) => {
              console.error("Error", error);
            });
        },
      },

Video.js Outdated
@@ -267,15 +267,15 @@ export default class Video extends Component {
const getLicensePromise = Promise.resolve(getLicenseOverride); // Handles both scenarios, getLicenseOverride being a promise and not.
getLicensePromise.then((result => {
if (result !== undefined) {
NativeModules.VideoManager.setLicenseResult(result, findNodeHandle(this._root));
NativeModules.VideoManager.setLicenseResult(result, data.contentId, findNodeHandle(this._root));
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do this change impact client app ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should not affect the client app because setLicenseResult and setLicenseError are not called by the client.

@freeboub
Copy link
Collaborator

Thank you for this great enhancement !
I just have doubt on api changes done (as you see in my comments).
As you change api, can you also update documentation to explicite changes you made.

I think you update https://github.com/react-native-video/react-native-video/blob/master/docs/DRM.md#getlicense function
and I am not sure, but the setLicence result changes also need application update ? will / can it be backward compatible with previous version ?

Thank you

@facugu1998
Copy link
Contributor Author

Hello @freeboub, thanks for your comments. We decided to take in account your comment #2881 (comment) and kept contentId as is, and use the licenseServer instead, to forward the skd url to the client.

The getLicense function passed on the drm object receives the same parameters as before, the difference is that if the client does not specify the licenseServer, the getLicense function receives the skd://{key} url.

@freeboub
Copy link
Collaborator

@facugu1998 Thank you for the changes, can you please complete DRM.md file, at least to add the new prop: licenseUrl
Thank you

@facugu1998
Copy link
Contributor Author

@freeboub Actually, the parameters were available before the PR. The docs were not updated, how can we describe the function? We thought something like this:

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];.

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:

getLicense: (spcString, contentId, licenseUrl) => {
  const base64spc = Base64.encode(spcString);
  const formData = new FormData();
  formData.append('spc', base64spc);
  return fetch(`https://license.pallycon.com/ri/licenseManager.do`, {
      method: 'POST',
      headers: {
          'pallycon-customdata-v2': 'd2VpcmRiYXNlNjRzdHJpbmcgOlAgRGFuaWVsIE1hcmnxbyB3YXMgaGVyZQ==',
          'Content-Type': 'application/x-www-form-urlencoded',
      },
      body: formData
  }).then(response => response.text()).then((response) => {
      return response;
  }).catch((error) => {
      console.error('Error', error);
  });
}

@freeboub
Copy link
Collaborator

freeboub commented Oct 2, 2023

@facugu1998 Yes that's Ok for me, thank you. (I agree DRM doc is not really clear in general ;) )

@facugu1998
Copy link
Contributor Author

Hello @freeboub, we updated the DRM documentation. Should we change something else?

@freeboub freeboub merged commit f4acacc into TheWidlarzGroup:master Oct 5, 2023
1 check passed
@freeboub
Copy link
Collaborator

@facugu1998 can you please chack following PR please ? #3578 I changed the api as it is causing some regressions on existing implementation, do you agree with that ?

@facugu1998
Copy link
Contributor Author

@freeboub hello, I think it's perfect. But in order to make it work I had to do the changes I detailed here #3417 (comment).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants