video-dev / hls.js

HLS.js is a JavaScript library that plays HLS in browsers with support for MSE.
https://hlsjs.video-dev.org/demo
Other
14.89k stars 2.58k forks source link

Playing a Widevine DRM video on a Chromecast Receiver #5589

Closed ralfcarneborn closed 1 year ago

ralfcarneborn commented 1 year ago

What do you want to do with Hls.js?

I have a Widevine DRM video that i can play successfully in my web app using hls.js with DRM enabled. When i deploy similar code to my Custom Chromecast Receiver web app, using the exact same config and license server etc i get the error: [log] > [eme] Failed to obtain access to key-system "com.widevine.alpha": NotSupportedError: Unsupported keySystem or supportedConfigurations.

What have you tried so far?

I have created the most simple Create React App and added Chromecast Receiver SDK and hls.js 1.4.4 to it so i can cast, and deployed it to Firebase so that it has https.

I load the video using this code:

playerManager.setMessageInterceptor(
      cast.framework.messages.MessageType.LOAD,
      async (loadRequestData) => {
        const {
          customData: {
            content: { contentId },
          },
        } = loadRequestData;
        var video = document.getElementById("video");
        const asset = await getAsset(contentId);
        const {
          playbackItem: { license, manifestUrl },
        } = asset;

        const config = getHlsJsDrmConfig(
          license?.castlabsServer,
          license?.castlabsToken
        );

        var hls = new Hls(config);
        hls.loadSource(manifestUrl);
        hls.attachMedia(video);
        hls.on(Hls.Events.MANIFEST_PARSED, function () {
          castDebugLogger.debug("MyCustomTag", "manifest parsed");
          video.play();
        });
        hls.on(Hls.Events.ERROR, function (event, data) {
          castDebugLogger.debug(
            "MyCustomTag",
            `hls error ${JSON.stringify(event)} ${JSON.stringify(data)}`
          );
        });

        return loadRequestData;
      }
    );

This is currently the config that i use on my webapp and that works fine to play the DRM stream with hls.js. Casting a non-DRM stream by the way works perfectly fine by the way with this setup, its only DRM that is an issue at the moment. castlabsServer/castlabsToken is values i get from my backend for the license server.

{
    emeEnabled: true,
    debug: true,
    drmSystems: {
      "com.widevine.alpha": {
        licenseUrl: castlabsServer,
      },
      "com.microsoft.playready": {
        licenseUrl: castlabsServer,
      },
    },
    licenseXhrSetup: (xhr, _url, keyContext, licenseChallenge) => {
      const { keySystem } = keyContext;
      if (!castlabsToken) return licenseChallenge;

      if (
        keySystem === "com.widevine.alpha" ||
        keySystem === "com.microsoft.playready"
      ) {
        xhr.setRequestHeader("x-dt-auth-token", castlabsToken);
      }

      return licenseChallenge;
    },
    licenseResponseCallback: (xhr, _url) => {
      const { response } = xhr;
      return response;
    },
    liveSyncDuration: 15,
  }

The relevant logs are these i suppose:

[log] > Debug logs enabled for "Hls instance" in hls.js version 1.4.4
main.65ac7d8a.js:2 [log] > stopLoad
main.65ac7d8a.js:2 [log] > loadSource:https://vod.streaming.a2d.tv/c5740a45-6646-49ad-841b-458bef98c14c/b8a773d0-63d1-11ed-ac83-abe2ac99ce9a_20341341.ism/.m3u8
main.65ac7d8a.js:2 [log] > [stream-controller]: Trigger BUFFER_RESET
main.65ac7d8a.js:2 [log] > attachMedia
cast_receiver_framework.js:121  [  1.700s] [cast.receiver.MediaManager] No need to prefetch more for now. 
main.65ac7d8a.js:2 [log] > [level-controller]: manifest loaded, 4 level(s) found, first bitrate: 455000
main.65ac7d8a.js:2 [log] > 2 bufferCodec event(s) expected
main.65ac7d8a.js:2 [log] > startLoad(-1)
main.65ac7d8a.js:2 [log] > [level-controller]: Switching to level 0 from level -1
main.65ac7d8a.js:2 [log] > [audio-track-controller]: Updating audio tracks, 1 track(s) found in group:audio-aacl-128
main.65ac7d8a.js:2 [log] > [audio-track-controller]: Switching to audio-track 0 "audio" lang:undefined group:audio-aacl-128
main.65ac7d8a.js:2 [log] > [audio-stream-controller]: Reset loading state
main.65ac7d8a.js:2 [log] > [audio-stream-controller]: STOPPED->IDLE
main.65ac7d8a.js:2 [log] > [subtitle-track-controller]: Updating subtitle tracks, 1 track(s) found in "textstream" group-id
main.65ac7d8a.js:2 [log] > [level-controller]: Loading level index 0 with URI 1/1 https://vod.streaming.a2d.tv/c5740a45-6646-49ad-841b-458bef98c14c/b8a773d0-63d1-11ed-ac83-abe2ac99ce9a_20341341.ism/b8a773d0-63d1-11ed-ac83-abe2ac99ce9a_20341341-video=300000.m3u8
main.65ac7d8a.js:2 [log] > [stream-controller]: STOPPED->IDLE
main.65ac7d8a.js:2 [log] > [audio-track-controller]: loading audio-track playlist 0 "audio" lang:undefined group:audio-aacl-128
main.65ac7d8a.js:2 [log] > [audio-stream-controller]: IDLE->STOPPED
main.65ac7d8a.js:2 [log] > [audio-stream-controller]: STOPPED->WAITING_TRACK
main.65ac7d8a.js:2 [log] > [subtitle-stream-controller]: STOPPED->IDLE
main.65ac7d8a.js:2 [log] > [eme] Selecting key-system from session-keys urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed, com.microsoft.playready, com.apple.streamingkeydelivery
main.65ac7d8a.js:2 [log] > [eme] Requesting encrypted media "com.widevine.alpha" key-system access with config: [{"initDataTypes":["cenc"],"persistentState":"not-allowed","distinctiveIdentifier":"not-allowed","sessionTypes":["temporary"],"audioCapabilities":[{"contentType":"audio/mp4; codecs=\"mp4a.40.2\"","robustness":"","encryptionScheme":null}],"videoCapabilities":[{"contentType":"video/mp4; codecs=\"avc1.4D400D\"","robustness":"","encryptionScheme":null},{"contentType":"video/mp4; codecs=\"avc1.4D401E\"","robustness":"","encryptionScheme":null},{"contentType":"video/mp4; codecs=\"avc1.4D401F\"","robustness":"","encryptionScheme":null}]}]
main.65ac7d8a.js:2 [log] > [eme] Failed to obtain access to key-system "com.widevine.alpha": NotSupportedError: Unsupported keySystem or supportedConfigurations.
main.65ac7d8a.js:2 [log] > [stream-controller]: Level 0 loaded [1,1672][part-1672--1], cc [0, 0] duration:6686.28
main.65ac7d8a.js:2 [log] > [buffer-controller]: Media source opened
main.65ac7d8a.js:2 [log] > [buffer-controller]: Updating Media Source duration to 6686.280
main.65ac7d8a.js:2 [log] > [subtitle-track-controller]: Switching to subtitle-track 0 "Svenska" lang:sv group:textstream

im currently stuck, any suggestions what im doing wrong or is this not supported or a bug?

ralfcarneborn commented 1 year ago

This is the manifest that is loaded by the way

#EXTM3U
#EXT-X-VERSION:6
## Created with Unified Streaming Platform (version=1.11.23-28141)
#EXT-X-SESSION-KEY:METHOD=SAMPLE-AES,KEYID=0xd3c849ce49aa3c35ba0d4f4b9fed79d4,URI="data:text/plain;base64,AAAAdXBzc2gAAAAA7e+LqXnWSs6jyCfc1R0h7QAAAFUSENPISc5Jqjw1ug1PS5/tedQaCGNhc3RsYWJzIihleUpoYzNObGRFbGtJam9pZEhadFpXUnBZUzB5TURNME1UTTBNU0o5MgdkZWZhdWx0SPPGiZsG",KEYFORMAT="urn:uuid:edef8ba9-79d6-4ace-a3c8-27dcd51d21ed",KEYFORMATVERSIONS="1"
#EXT-X-SESSION-KEY:METHOD=SAMPLE-AES,URI="data:text/plain;charset=UTF-16;base64,KgMAAAEAAQAgAzwAVwBSAE0ASABFAEEARABFAFIAIAB2AGUAcgBzAGkAbwBuAD0AIgA0AC4AMwAuADAALgAwACIAIAB4AG0AbABuAHMAPQAiAGgAdAB0AHAAOgAvAC8AcwBjAGgAZQBtAGEAcwAuAG0AaQBjAHIAbwBzAG8AZgB0AC4AYwBvAG0ALwBEAFIATQAvADIAMAAwADcALwAwADMALwBQAGwAYQB5AFIAZQBhAGQAeQBIAGUAYQBkAGUAcgAiAD4APABEAEEAVABBAD4APABQAFIATwBUAEUAQwBUAEkATgBGAE8APgA8AEsASQBEAFMAPgA8AEsASQBEACAAVgBBAEwAVQBFAD0AIgB6AGsAbgBJADAANgBwAEoATgBUAHkANgBEAFUAOQBMAG4AKwAxADUAMQBBAD0APQAiACAAQQBMAEcASQBEAD0AIgBBAEUAUwBDAEIAQwAiACAALwA+ADwALwBLAEkARABTAD4APAAvAFAAUgBPAFQARQBDAFQASQBOAEYATwA+ADwATABBAF8AVQBSAEwAPgBoAHQAdABwAHMAOgAvAC8AbABpAGMALgBkAHIAbQB0AG8AZABhAHkALgBjAG8AbQAvAGwAaQBjAGUAbgBzAGUALQBwAHIAbwB4AHkALQBoAGUAYQBkAGUAcgBhAHUAdABoAC8AZAByAG0AdABvAGQAYQB5AC8AUgBpAGcAaAB0AHMATQBhAG4AYQBnAGUAcgAuAGEAcwBtAHgAPAAvAEwAQQBfAFUAUgBMAD4APABMAFUASQBfAFUAUgBMAD4AaAB0AHQAcABzADoALwAvAHAAbABhAHkAcgBlAGEAZAB5AC0AdQBpAC4AZQB4AGEAbQBwAGwAZQAuAGMAbwBtADwALwBMAFUASQBfAFUAUgBMAD4APABEAEUAQwBSAFkAUABUAE8AUgBTAEUAVABVAFAAPgBPAE4ARABFAE0AQQBOAEQAPAAvAEQARQBDAFIAWQBQAFQATwBSAFMARQBUAFUAUAA+ADwALwBEAEEAVABBAD4APAAvAFcAUgBNAEgARQBBAEQARQBSAD4A",KEYFORMAT="com.microsoft.playready",KEYFORMATVERSIONS="1"
#EXT-X-SESSION-KEY:METHOD=SAMPLE-AES,URI="skd://drmtoday?assetId=tvmedia-20341341&variantId&keyId=d3c849ce49aa3c35ba0d4f4b9fed79d4",KEYFORMAT="com.apple.streamingkeydelivery",KEYFORMATVERSIONS="1"
#EXT-X-SESSION-KEY:METHOD=SAMPLE-AES,KEYID=0xd3c849ce49aa3c35ba0d4f4b9fed79d4,URI="data:text/plain;base64,AAAAn3Bzc2gAAAAAPV5tNZuaQei4Q908bnLELAAAAH97InZlcnNpb24iOiJWMS4wIiwia2lkcyI6WyIwOGhKemttcVBEVzZEVTlMbisxNTFBPT0iXSwiY29udGVudElEIjoiZXlKaGMzTmxkRWxrSWpvaWRIWnRaV1JwWVMweU1ETTBNVE0wTVNKOSIsImVuc2NoZW1hIjoiY2JjcyJ9",KEYFORMAT="urn:uuid:3d5e6d35-9b9a-41e8-b843-dd3c6e72c42c",KEYFORMATVERSIONS="1"

# AUDIO groups
#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID="audio-aacl-128",NAME="audio",DEFAULT=YES,AUTOSELECT=YES,CHANNELS="2",URI="b8a773d0-63d1-11ed-ac83-abe2ac99ce9a_20341341-audio=128000.m3u8"

# SUBTITLES groups
#EXT-X-MEDIA:TYPE=SUBTITLES,GROUP-ID="textstream",LANGUAGE="sv",NAME="Svenska",DEFAULT=YES,AUTOSELECT=YES,URI="b8a773d0-63d1-11ed-ac83-abe2ac99ce9a_20341341-textstream_swe=1000.m3u8"

# variants
#EXT-X-STREAM-INF:BANDWIDTH=455000,CODECS="mp4a.40.2,avc1.4D400D",RESOLUTION=384x216,FRAME-RATE=25,AUDIO="audio-aacl-128",SUBTITLES="textstream",CLOSED-CAPTIONS=NONE
b8a773d0-63d1-11ed-ac83-abe2ac99ce9a_20341341-video=300000.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=985000,CODECS="mp4a.40.2,avc1.4D401E",RESOLUTION=640x360,FRAME-RATE=25,AUDIO="audio-aacl-128",SUBTITLES="textstream",CLOSED-CAPTIONS=NONE
b8a773d0-63d1-11ed-ac83-abe2ac99ce9a_20341341-video=800000.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=1727000,CODECS="mp4a.40.2,avc1.4D401E",RESOLUTION=768x432,FRAME-RATE=25,AUDIO="audio-aacl-128",SUBTITLES="textstream",CLOSED-CAPTIONS=NONE
b8a773d0-63d1-11ed-ac83-abe2ac99ce9a_20341341-video=1500000.m3u8
#EXT-X-STREAM-INF:BANDWIDTH=2787000,CODECS="mp4a.40.2,avc1.4D401F",RESOLUTION=1024x576,FRAME-RATE=25,AUDIO="audio-aacl-128",SUBTITLES="textstream",CLOSED-CAPTIONS=NONE
b8a773d0-63d1-11ed-ac83-abe2ac99ce9a_20341341-video=2500000.m3u8

# keyframes
#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=40000,CODECS="avc1.4D400D",RESOLUTION=384x216,URI="keyframes/b8a773d0-63d1-11ed-ac83-abe2ac99ce9a_20341341-video=300000.m3u8"
#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=106000,CODECS="avc1.4D401E",RESOLUTION=640x360,URI="keyframes/b8a773d0-63d1-11ed-ac83-abe2ac99ce9a_20341341-video=800000.m3u8"
#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=199000,CODECS="avc1.4D401E",RESOLUTION=768x432,URI="keyframes/b8a773d0-63d1-11ed-ac83-abe2ac99ce9a_20341341-video=1500000.m3u8"
#EXT-X-I-FRAME-STREAM-INF:BANDWIDTH=332000,CODECS="avc1.4D401F",RESOLUTION=1024x576,URI="keyframes/b8a773d0-63d1-11ed-ac83-abe2ac99ce9a_20341341-video=2500000.m3u8"
robwalch commented 1 year ago

Hi @ralfcarneborn,

You'll need to figure out why the device is rejecting the call to requestMediaKeySystemAccess. My guess would be that it has to do with the MediaKeySystemConfiguration generated by HLS.js in getSupportedMediaKeySystemConfigurations.

[eme] Requesting encrypted media "com.widevine.alpha" key-system access with config: [{"initDataTypes":["cenc"],"persistentState":"not-allowed","distinctiveIdentifier":"not-allowed","sessionTypes":["temporary"],"audioCapabilities":[{"contentType":"audio/mp4; codecs=\"mp4a.40.2\"","robustness":"","encryptionScheme":null}],"videoCapabilities":[{"contentType":"video/mp4; codecs=\"avc1.4D400D\"","robustness":"","encryptionScheme":null},{"contentType":"video/mp4; codecs=\"avc1.4D401E\"","robustness":"","encryptionScheme":null},{"contentType":"video/mp4; codecs=\"avc1.4D401F\"","robustness":"","encryptionScheme":null}]}]
[eme] Failed to obtain access to key-system "com.widevine.alpha": NotSupportedError: Unsupported keySystem or supportedConfigurations.

Configuring audioRobustness, videoRobustness, audioEncryptionScheme and videoEncryptionScheme in drmSystems["com.widevine.alpha"] might make a difference.

If not, it could be an issue with the array of video codecs extracted from the manifest. You can set requestMediaKeySystemAccessFunc in the config to function that wraps navigator.requestMediaKeySystemAccess. This will let you transform the request as needed to satisfy the device.

ralfcarneborn commented 1 year ago

Thank you so much for having a look at this!

I checked the documentation and I assume that I would set the drmSystemOptions to update the config. It gives an example of this in the docs

{
  audioRobustness: 'SW_SECURE_CRYPTO',
  videoRobustness: 'SW_SECURE_CRYPTO',
  audioEncryptionScheme: null,
  videoEncryptionScheme: null,
  persistentState: 'not-allowed';
  distinctiveIdentifier: 'not-allowed';
  sessionTypes: ['temporary'];
  sessionType: 'temporary';
}

But I am uncertain on what type of values audioEncryptionSchemeand videoEncryptionScheme would accept. Is there any example on these values or any code somewhere that you know of that uses these values that I can copy from?

robwalch commented 1 year ago

But I am uncertain on what type of values audioEncryptionScheme and videoEncryptionScheme would accept. Is there any example on these values or any code somewhere that you know of that uses these values that I can copy from?

That would translate to the encryptionScheme in the audio and video configurations. You can probably omit this as it's optional and not supported on some devices. https://github.com/WICG/encrypted-media-encryption-scheme/blob/main/explainer.md

ralfcarneborn commented 1 year ago

Thank you for getting me on the right path with this, i eventually ended up setting the audioEncryptionScheme/videoEncryptionScheme to cbcs instead of cenc. And I dont understand this fully yet but i had to remove other props than those from the default config, otherwise it would not work. But now it works at least and plays my DRM content, thank you!

drmSystems: {
                "com.widevine.alpha": {
                    licenseUrl: castlabsServer,
                },
                "com.microsoft.playready": {
                    licenseUrl: castlabsServer,
                },
            },
            drmSystemOptions: {
                audioEncryptionScheme: 'cbcs',
                videoEncryptionScheme: 'cbcs',
            },
            licenseXhrSetup: (xhr, _url, keyContext, licenseChallenge) => {
                const { keySystem } = keyContext;
                if (!castlabsToken) return licenseChallenge;

                if (
                    keySystem === "com.widevine.alpha" ||
                    keySystem === "com.microsoft.playready"
                ) {
                    xhr.setRequestHeader("x-dt-auth-token", castlabsToken);
                }

                return licenseChallenge;
            },
            licenseResponseCallback: (xhr, _url) => {
                const { response } = xhr;
                return response;
            },
            requestMediaKeySystemAccessFunc: (
                keySystem,
                supportedConfigurations
            ) => {
                const essentialConfigurations = supportedConfigurations.map(config => ({
                    audioCapabilities: config.audioCapabilities,
                    videoCapabilities: config.videoCapabilities,
                }));
                return navigator.requestMediaKeySystemAccess(keySystem, essentialConfigurations);
            },