Passing HLS AES token to CAF Receiver?

Hi there,

I have a working player with HLS and a aestoken. Now I need to pass that aestoken to the CAF Receiver and add it to the request. Maybe somebody can help.

Here is my (JavaScript) code on the Sender:

window.setupBitmovinHLSPlayer = function (playername, videoname, urlStreamingLink, aestoken, posterurl) {
    var conf = {
        key: 'xxxxx',
        cast: {
            enable: true
        },
        remotecontrol: {
            type: 'googlecast',
            receiverVersion: 'v3',
            receiverApplicationId: 'xxxxx'
        },
        network: {
            preprocessHttpRequest: (type, request) => {
                if (type === bitmovin.player.HttpRequestType.KEY_HLS_AES) {
                    request.headers['Authorization'] = 'Bearer ' + aestoken;
                }
                return Promise.resolve(request);
            }
        }
    };

    var source = {
        title: videoname,
        hls: urlStreamingLink,
        poster: posterurl,
        metadata: {
            aesToken: aestoken,
        },
    };

    var player = new bitmovin.player.Player(document.getElementById(playername), conf);
    player.load(source);
};

On the Receiver I have (TypeScript):

import { CastReceiverContext, ContentProtection, NetworkRequestInfo, PlayerManager } from 'chromecast-caf-receiver/cast.framework';
import { LoadRequestData } from 'chromecast-caf-receiver/cast.framework.messages';
import { CAFDrmConfig, CAFMediaInfoCustomData, CAFSourceOptions } from 'bitmovin-player';

const CAST_MESSAGE_NAMESPACE = 'urn:x-cast:com.bitmovin.player.caf';

export default class CAFReceiver {
  private readonly player: PlayerManager;
  private readonly context: CastReceiverContext;

  constructor() {
    this.context = cast.framework.CastReceiverContext.getInstance();
    this.player = this.context.getPlayerManager();
  }

  public init() {
    // cast.framework.CastReceiverContext.getInstance().setLoggerLevel(cast.framework.LoggerLevel.DEBUG);

    this.attachEvents();
    this.context.start();
  }

  private attachEvents() {
    this.player.setMessageInterceptor(cast.framework.messages.MessageType.LOAD, this.onLoad);
    this.context.addCustomMessageListener(CAST_MESSAGE_NAMESPACE, this.onCustomMessage);
  }

  private readonly onLoad = (loadRequestData: LoadRequestData): LoadRequestData => {
    const customData = loadRequestData.media.customData as CAFMediaInfoCustomData;

    if (customData?.options) {
      this.setWithCredentials(customData.options);
    }

    if (customData?.drm) {
      this.setDRM(customData.drm);
    }

    if (loadRequestData.media.customData && loadRequestData.media.customData.metadata) {
        console.info('received some metadata from the Bitmovin Player',
            loadRequestData.media.customData.metadata);
    }

    return loadRequestData;
  };

  private setDRM({ protectionSystem, licenseUrl, headers, withCredentials }: CAFDrmConfig): void {
    this.context.getPlayerManager().setMediaPlaybackInfoHandler((_loadRequest, playbackConfig) => {
      playbackConfig.licenseUrl = licenseUrl;
      playbackConfig.protectionSystem = protectionSystem as ContentProtection;

      if (typeof headers === 'object') {
        playbackConfig.licenseRequestHandler = (requestInfo) => {
          requestInfo.headers = headers;
        };
      }

      if (withCredentials) {
        playbackConfig.licenseRequestHandler = setWithCredentialsFlag;
      }

      return playbackConfig;
    });
  }

  private setWithCredentials(options: CAFSourceOptions): void {
    const playerManager = this.context.getPlayerManager();
    const playbackConfig = Object.assign(new cast.framework.PlaybackConfig(), playerManager.getPlaybackConfig());

    if (options.withCredentials) {
      playbackConfig.segmentRequestHandler = setWithCredentialsFlag;
      playbackConfig.captionsRequestHandler = setWithCredentialsFlag;
    }

    if (options.manifestWithCredentials) {
      playbackConfig.manifestRequestHandler = setWithCredentialsFlag;
    }

    playerManager.setPlaybackConfig(playbackConfig);
  }

  private readonly onCustomMessage = (message: cast.framework.system.Event) => {
    console.log('Received custom channel message', message);
  };
}

function setWithCredentialsFlag(requestInfo: NetworkRequestInfo): void {
  requestInfo.withCredentials = true;
}

Can somebody show me how to add the metadata.easToken to the request?

Many thanks in advance.
Peter

1 Like

hello Peter,
Just to confirm, do you already receive succesfully the aestoken in the LoadRequestListener.
If that is the case, I believe you can do something similar to what is done in the setDRM function, using the playbackConfig object.
In this case, assigning the headers to playbackConfig. manifestRequestHandler
Please refer to CAF documentation for this object specifics.

it would be something like:

playbackConfig.manifestRequestHandler = (requestInfo) => {
          requestInfo.headers = headers;
        };

Could you please try this and let us know if it works for you?

thanks

1 Like

Hello Thomas,

Thank you for helping me further. I’m close, but I somehow don’t manage to get it fully working.

Below my Receiver script and I do attach an image of de debug of the Receiver.

Can you help me out?

Many thanks,
Peter

import { CastReceiverContext, ContentProtection, NetworkRequestInfo, PlayerManager } from 'chromecast-caf-receiver/cast.framework';
import { LoadRequestData } from 'chromecast-caf-receiver/cast.framework.messages';
import { CAFDrmConfig, CAFMediaInfoCustomData, CAFMetadata, CAFSourceOptions } from 'bitmovin-player';

const CAST_MESSAGE_NAMESPACE = 'urn:x-cast:com.bitmovin.player.caf';

export default class CAFReceiver {
  private readonly player: PlayerManager;
  private readonly context: CastReceiverContext;

  constructor() {
    this.context = cast.framework.CastReceiverContext.getInstance();
    this.player = this.context.getPlayerManager();
  }

  public init() {
    // cast.framework.CastReceiverContext.getInstance().setLoggerLevel(cast.framework.LoggerLevel.DEBUG);

    this.attachEvents();
    this.context.start();
  }

    private attachEvents() {
        this.player.setMessageInterceptor(cast.framework.messages.MessageType.LOAD, this.onLoad);
        this.context.addCustomMessageListener(CAST_MESSAGE_NAMESPACE, this.onCustomMessage);
    }

    private readonly onLoad = (loadRequestData: LoadRequestData): LoadRequestData => {
        const customData = loadRequestData.media.customData as CAFMediaInfoCustomData;

        if (customData?.options) {
            this.setWithCredentials(customData.options);
        }

        if (customData?.drm) {
            this.setDRM(customData.drm);
        }

        if (customData?.metadata) {
            console.info('aesToken = ' + customData.metadata.aesToken);
            this.setHLSWithToken(customData.metadata);
        }

        return loadRequestData;
    };

    private setDRM({ protectionSystem, licenseUrl, headers, withCredentials }: CAFDrmConfig): void {
        this.context.getPlayerManager().setMediaPlaybackInfoHandler((_loadRequest, playbackConfig) => {
            playbackConfig.licenseUrl = licenseUrl;
            playbackConfig.protectionSystem = protectionSystem as ContentProtection;

            if (typeof headers === 'object') {
            playbackConfig.licenseRequestHandler = (requestInfo) => {
                requestInfo.headers = headers;
            };
            }

            if (withCredentials) {
            playbackConfig.licenseRequestHandler = setWithCredentialsFlag;
            }

            return playbackConfig;
        });
    }

    private setHLSWithToken({aesToken}: CAFMetadata): void {
        this.context.getPlayerManager().setMediaPlaybackInfoHandler((_loadRequest, playbackConfig) => {

            console.info('aesToken2 = ' + aesToken);
            playbackConfig.manifestRequestHandler = (requestInfo) => {
                requestInfo.headers['Authorization'] = 'Bearer ' + aesToken;
            };

            return playbackConfig;
        });
    }

    private setWithCredentials(options: CAFSourceOptions): void {
        const playerManager = this.context.getPlayerManager();
        const playbackConfig = Object.assign(new cast.framework.PlaybackConfig(), playerManager.getPlaybackConfig());

        if (options.withCredentials) {
            playbackConfig.segmentRequestHandler = setWithCredentialsFlag;
            playbackConfig.captionsRequestHandler = setWithCredentialsFlag;
        }

        if (options.manifestWithCredentials) {
            playbackConfig.manifestRequestHandler = setWithCredentialsFlag;
        }

        playerManager.setPlaybackConfig(playbackConfig);
    }

    private readonly onCustomMessage = (message: cast.framework.system.Event) => {
        console.log('Received custom channel message', message);
    };
}

function setWithCredentialsFlag(requestInfo: NetworkRequestInfo): void {
  requestInfo.withCredentials = true;
}

1 Like

hello Peter, could you please try then instead, a similar implementation like in setWithCredentials function?


const playerManager = this.context.getPlayerManager();
    const playbackConfig = Object.assign(new cast.framework.PlaybackConfig(), playerManager.getPlaybackConfig());

 playbackConfig.manifestRequestHandler = (requestInfo) => {
          requestInfo.headers = headers;
        };

        playerManager.setPlaybackConfig(playbackConfig);

disclaimer, I haven’t test this myself. But I think we could give it a try?

thanks

1 Like

Hi Thomas,

Same result. Here is my code and screenshot of the debug of the Receiver.

I think the issue is with:
requestInfo.headers[‘Authorization’] = 'Bearer ’ + aesToken;

Any ideas?
Thanks,
Peter

private setHLSWithToken({ aesToken }: CAFMetadata): void {
        const playerManager = this.context.getPlayerManager();
        const playbackConfig = Object.assign(new cast.framework.PlaybackConfig(), playerManager.getPlaybackConfig());

        console.info('aesTokenCHECK = ' + aesToken);
        playbackConfig.manifestRequestHandler = (requestInfo) => {
            requestInfo.headers['Authorization'] = 'Bearer ' + aesToken;
        };

        playerManager.setPlaybackConfig(playbackConfig);

    }

1 Like

thanks for testing Peter
yes, what I read from the logs is that either requestInfo or requestInfo.headers is null.
could you add some logging to check that?
It would be required to look deeper into Google documentation to understand why this happens. as this does not have to do with the Bitmovin player at this level

1 Like

Hi Thomas,

I seem to get a step closer. I added the line

playbackConfig.manifestRequestHandler = setWithCredentialsFlag;

But now I’m getting CORS warnings. See Console image. Can you help me there?

Best,
Peter

private setHLSWithToken({ aesToken }: CAFMetadata): void {
        const playerManager = this.context.getPlayerManager();
        const playbackConfig = Object.assign(new cast.framework.PlaybackConfig(), playerManager.getPlaybackConfig());

        console.info('aesTokenCHECK = ' + aesToken);
        playbackConfig.manifestRequestHandler = (requestInfo) => {
            requestInfo.headers['Authorization'] = 'Bearer ' + aesToken;
        };

        playbackConfig.manifestRequestHandler = setWithCredentialsFlag;

        playerManager.setPlaybackConfig(playbackConfig);

    }

function setWithCredentialsFlag(requestInfo: NetworkRequestInfo): void {
  requestInfo.withCredentials = true;
}

1 Like

Here is the path to the solution. But I still need help.

https://developers.google.com/cast/docs/reference/web_receiver/cast.framework.NetworkRequestInfo#withCredentials

1 Like

hi Peter,
I see, just to confirm, even though you are getting CORS issues, can you confirm that the token was added to the request header (you can check that from the network tab)

thanks

1 Like

and also, what happens if you don’t set the withCredentials to true?

1 Like

Hi Thomas,

Only when withCredentials = true the Authorization works.

Regarding the token being added:

I need to start the casting before I can open the debugger. That means the request is done before I can check the Network tab. And because of that I cannot look back in the Network tab.

But I guess the token is added, because it checks all the four different encodings of the file and returns four cors issues.

1 Like

hi Peter,

Is it possible for you to modify the CORS settings on your server to specify your CAF receiver as allowed origin?

1 Like

also, have you tried explicitly setting withCredentials = false or requestInfo.withCredentials = false
(instead of true or not setting anything at all)

regards

1 Like

Hi Thomas,

Yes, I did set explicitly withCredentials = false. Then the Authorization does not work.

And I’m trying to fix things on the server end in Azure. Will let you know if that worked.

Best,
Peter

1 Like

Hi Thomas,

I had to rearrange some things on the CDN in Azure. Now the CORS issues are resolved.

Next thing is that there seems to be an issue with the token/codec. See the shot from the debug below.

Valid VP9 codec string requires 4-9 fields, but codecId:mp4a.40.2 has 3fields.

Here the piece of code how it looks now on the receiver:

private setHLSWithToken({ aesToken }: CAFMetadata): void {
        const playerManager = this.context.getPlayerManager();
        const playbackConfig = Object.assign(new cast.framework.PlaybackConfig(), playerManager.getPlaybackConfig());

        console.info('aesTokenCHECK = ' + aesToken);
        playbackConfig.manifestRequestHandler = (requestInfo) => {
            requestInfo.headers['Authorization'] = 'Bearer ' + aesToken;
        };

        playbackConfig.manifestRequestHandler = setWithCredentialsFlag;

        playerManager.setPlaybackConfig(playbackConfig);

    }

I did some Google’ling but didn’t find a solution.

Do you have any experience with this?

Best, Peter

1 Like

Hi Thomas,

Related to the above I found a few links:

https://github.com/shaka-project/shaka-packager/issues/842

https://github.com/shaka-project/shaka-packager/issues/406

https://googlechrome.github.io/samples/media/vp9-codec-string.html

It seems that this has to do with the Shaka player.
Do you see a way how to deal with this through the receiver app?

Many thanks,
Peter

1 Like

hello @peter.vanvogelpoel
Might be that VP9 issue is actually a limitation on your Chromecast hardware. Please take a look at: Supported Media for Google Cast  |  Google Developers VP9 seems to be supported starting from Chromecast Ultra. Chromecast generation from 1-3 only support VP8.
Which device are you using for testing?

1 Like

I saw that too. I’m using v3. I will fetch a Ultra version for testing and let you know.

1 Like

Hi Thomas,

Some time passed. An update: Meanwhile I fetched the latest Chromecast and that did not solve the issue. Then I turned everything from HLS to DRM and that solved most of the issues. Also with older Chromecast devices no problem.
However, now things are not working on the MAUI platform again. I’m getting ‘Unable to instantiate a key system supporting the required combination’ (DRM_NO_KEY_SYSTEM).

Do you have any advise?

Best,
Peter

1 Like

thanks for the update Peter. Sure, please feel free to add your further questions as new topics on community as needed.
Are you coming to IBC in Amsterdam? if you are, it would be good to meet you there in person.
9 – 12th Sept / Amsterdam, NL / Hall 5, Stand #C68

best regards

-Thomas

1 Like