Playing protected content with Sigma DRM

1. Overview

Nearly every license provider requires a few special pieces of information to be sent to the DRM license server or responds with a proprietary format. Instead of integrating a few license providers into the core of our player, we decided to provide necessary configuration options via the player configuration.

2. License encryption feature

We provide license encryption for Widevine DRM. To use the license encryption feature, you need to install the Sigma Packer SDK into your application:

  • Install SigmaPacker SDK

    <script src="sigma_packer.js"></script>
    
  • Initialize the SigmaPacker instance

window.sigmaPacker = new SigmaPacker();
window.sigmaPacker.onload = () => {
  console.log('SigmaPacker loaded');
};
window.sigmaPacker.init();

3. Widevine

const source = {
  dash: DASH_MANIFEST_URL,
  drm: {
    widevine: {
      LA_URL: WIDEVINE_LICENSE_SERVER_URL,
    },
  },
};

// The configuration object using init bitmovin player instance
const config = {
  network: {
    preprocessHttpRequest: function (type, request) {
      const HttpRequestType = bitmovin.player.HttpRequestType;
      if (type === HttpRequestType.DRM_LICENSE_WIDEVINE) {
        const customData = {
          merchantId: MERCHANT_ID,
          appId: APP_ID,
          userId: USER_ID,
          sessionId: SESSION_ID,
        };
        // FIXME: If you use the license encryption feature, you need to uncomment the code below
        /** 
          const packInfo = window.sigmaPacker.getDataPacker(request.body) || {};
          customData.reqId = packInfo.requestId;
          customData.deviceInfo = packInfo.deviceInfo;
        */
        request.headers['Content-Type'] = 'application/octet-stream';
        request.headers['custom-data'] = btoa(JSON.stringify(customData));
      }
      return Promise.resolve(request);
    },
    preprocessHttpResponse: function (type, response) {
      const HttpRequestType = bitmovin.player.HttpRequestType;
      if (type === HttpRequestType.DRM_LICENSE_WIDEVINE) {
        // This is the wrapped license, which is a JSON string.
        try {
          const wrappedString = new TextDecoder().decode(response.body);
          // Parse the JSON string into an object.
          const wrapped = JSON.parse(wrappedString);

          // FIXME: If you use the license encryption feature, you need to uncomment the code below
          /** 
            if (response.headers['client-info']) {
              window.sigmaPacker.update(atob(response.headers['client-info']));
            } else if (wrapped.clientInfo) {
              window.sigmaPacker.update(JSON.stringify(wrapped.clientInfo));
            }
          */

          // This is a base64-encoded version of the raw license.
          const rawLicenseBase64 = wrapped.license;
          // Decode that base64 string into a Uint8Array and replace the response
          // data.  The raw license will be fed to the Widevine CDM.
          response.body = Uint8Array.from(atob(rawLicenseBase64), (c) =>
            c.charCodeAt(0)
          );
        } catch (error) {}
      }
    },
  },
};

4. PlayReady

const source = {
  dash: DASH_MANIFEST_URL,
  drm: {
    playready: {
      LA_URL: PLAYREADY_LICENSE_SERVER_URL,
    },
  },
};

// The configuration object using init Bitmovin player instance
const config = {
  network: {
    preprocessHttpRequest: function (type, request) {
      const HttpRequestType = bitmovin.player.HttpRequestType;
      if (type === HttpRequestType.DRM_LICENSE_PLAYREADY) {
        const customData = {
          merchantId: MERCHANT_ID,
          appId: APP_ID,
          userId: USER_ID,
          sessionId: SESSION_ID,
        };
        request.headers['Content-Type'] = 'application/octet-stream';
        request.headers['custom-data'] = btoa(JSON.stringify(customData));
      }
      return Promise.resolve(request);
    },
    preprocessHttpResponse: function (type, response) {
      const HttpRequestType = bitmovin.player.HttpRequestType;
      if (type === HttpRequestType.DRM_LICENSE_PLAYREADY) {
        // This is the wrapped license, which is a JSON string.
        try {
          const wrappedString = new TextDecoder().decode(response.body);
          // Parse the JSON string into an object.
          const wrapped = JSON.parse(wrappedString);
          // This is a base64-encoded version of the raw license.
          const rawLicenseBase64 = wrapped.license;
          // Decode that base64 string into a Uint8Array and replace the response data.
          response.body = Uint8Array.from(atob(rawLicenseBase64), (c) =>
            c.charCodeAt(0)
          );
        } catch (error) {}
      }
    },
  },
};

5. FairPlay

let licenseURL = ''; // The variable to store license request url
// SourceConfig object to be passed to Bitmovin Player instance
const source = {
  hls: HLS_MANIFEST_URL,
  drm: {
    fairplay: {
      LA_URL: FAIRPLAY_LICENSE_SERVER_URL,
      certificateURL: FAIRPLAY_CERTIFICATE_URL,
      prepareLicense: (license) => {
        return new Uint8Array(license);
      },
      prepareMessage: (event, session) => {
        return JSON.stringify({
          spc: event.messageBase64Encoded,
          assetId: session.contentId,
        });
      },
      prepareContentId: (contentId) => {
        const pattern = 'skd://';
        const idx = contentId.indexOf(pattern);

        if (idx > -1) {
          licenseURL = contentId.substring(idx + pattern.length);
          licenseURL = 'https://' + licenseURL;
          return new URL(licenseURL).searchParams.get('assetId');
        }
        return '';
      },
    },
  },
};

// The configuration object is used to initialize the Bitmovin Player instance
const config = {
  network: {
    preprocessHttpRequest: function (type, request) {
      const HttpRequestType = bitmovin.player.HttpRequestType;
      if (type === HttpRequestType.DRM_LICENSE_FAIRPLAY) {
        const customData = {
          merchantId: MERCHANT_ID,
          appId: APP_ID,
          userId: USER_ID,
          sessionId: SESSION_ID,
        };
        request.url = licenseURL;
        request.headers['Content-Type'] = 'application/json';
        request.headers['custom-data'] = btoa(JSON.stringify(customData));
      }
      return Promise.resolve(request);
    },
    preprocessHttpResponse: function (type, response) {
      const HttpRequestType = bitmovin.player.HttpRequestType;
      if (type === HttpRequestType.DRM_LICENSE_FAIRPLAY) {
        // This is the wrapped license, which is a JSON string.
        try {
          const wrapped = JSON.parse(response.body);
          // Parse the JSON string into an object.
          // This is a base64-encoded version of the raw license.
          const rawLicenseBase64 = wrapped.license;
          // Decode that base64 string into a Uint8Array and replace the response
          // data.  The raw license will be fed to the FairPlay.
          response.body = Uint8Array.from(atob(rawLicenseBase64), (c) =>
            c.charCodeAt(0)
          );
        } catch (error) {}
      }
    },
  },
};

6. Complete example for Widevine, PlayReady and FairPlay

// FIXME: If you use the license encryption feature, you need to uncomment the code below
/**
(function initApp() {
  window.sigmaPacker = new SigmaPacker();
  window.sigmaPacker.onload = () => {
    console.log('SigmaPacker loaded');
  };
  window.sigmaPacker.init();
})();
*/

let licenseURL = ''; // The variable to store license request url
// SourceConfig object to be passed to Bitmovin Player instance
const source = {
  dash: DASH_MANIFEST_URL,
  hls: HLS_MANIFEST_URL,
  drm: {
    widevine: {
      LA_URL: WIDEVINE_LICENSE_SERVER_URL,
    },
    playready: {
      LA_URL: PLAYREADY_LICENSE_SERVER_URL,
    },
    fairplay: {
      LA_URL: FAIRPLAY_LICENSE_SERVER_URL,
      certificateURL: FAIRPLAY_CERTIFICATE_URL,
      prepareLicense: (license) => {
        return new Uint8Array(license);
      },
      prepareMessage: (event, session) => {
        return JSON.stringify({
          spc: event.messageBase64Encoded,
          assetId: session.contentId,
        });
      },
      prepareContentId: (contentId) => {
        const pattern = 'skd://';
        const idx = contentId.indexOf(pattern);

        if (idx > -1) {
          licenseURL = contentId.substring(idx + pattern.length);
          licenseURL = 'https://' + licenseURL;
          return new URL(licenseURL).searchParams.get('assetId');
        }
        return '';
      },
    },
  },
};

// The configuration object is used to initialize the Bitmovin Player instance
const config = {
  network: {
    preprocessHttpRequest: function (type, request) {
      const HttpRequestType = bitmovin.player.HttpRequestType;
      if (type === HttpRequestType.DRM_LICENSE_WIDEVINE) {
        const customData = {
          merchantId: MERCHANT_ID,
          appId: APP_ID,
          userId: USER_ID,
          sessionId: SESSION_ID,
        };
        // FIXME: If you use the license encryption feature, you need to uncomment the code below
        /** 
          const packInfo = window.sigmaPacker.getDataPacker(request.body) || {};
          customData.reqId = packInfo.requestId;
          customData.deviceInfo = packInfo.deviceInfo;
        */
        request.headers['Content-Type'] = 'application/octet-stream';
        request.headers['custom-data'] = btoa(JSON.stringify(customData));
      }
      if (type === HttpRequestType.DRM_LICENSE_PLAYREADY) {
        const customData = {
          merchantId: MERCHANT_ID,
          appId: APP_ID,
          userId: USER_ID,
          sessionId: SESSION_ID,
        };
        request.headers['Content-Type'] = 'application/octet-stream';
        request.headers['custom-data'] = btoa(JSON.stringify(customData));
      }
      if (type === HttpRequestType.DRM_LICENSE_FAIRPLAY) {
        const customData = {
          merchantId: MERCHANT_ID,
          appId: APP_ID,
          userId: USER_ID,
          sessionId: SESSION_ID,
        };
        request.url = licenseURL;
        request.headers['Content-Type'] = 'application/json';
        request.headers['custom-data'] = btoa(JSON.stringify(customData));
      }
    },
    preprocessHttpResponse: function (type, response) {
      const HttpRequestType = bitmovin.player.HttpRequestType;
      if (type === HttpRequestType.DRM_LICENSE_WIDEVINE) {
        // This is the wrapped license, which is a JSON string.
        try {
          const wrappedString = new TextDecoder().decode(response.body);
          // Parse the JSON string into an object.
          const wrapped = JSON.parse(wrappedString);

          // FIXME: If you use the license encryption feature, you need to uncomment the code below
          /** 
            if (response.headers['client-info']) {
              window.sigmaPacker.update(atob(response.headers['client-info']));
            } else if (wrapped.clientInfo) {
              window.sigmaPacker.update(JSON.stringify(wrapped.clientInfo));
            }
          */

          // This is a base64-encoded version of the raw license.
          const rawLicenseBase64 = wrapped.license;
          // Decode that base64 string into a Uint8Array and replace the response
          // data.  The raw license will be fed to the Widevine CDM.
          response.body = Uint8Array.from(atob(rawLicenseBase64), (c) =>
            c.charCodeAt(0)
          );
        } catch (error) {}
      }
      if (type === HttpRequestType.DRM_LICENSE_PLAYREADY) {
        // This is the wrapped license, which is a JSON string.
        try {
          const wrappedString = new TextDecoder().decode(response.body);
          // Parse the JSON string into an object.
          const wrapped = JSON.parse(wrappedString);
          // This is a base64-encoded version of the raw license.
          const rawLicenseBase64 = wrapped.license;
          // Decode that base64 string into a Uint8Array and replace the response data.
          // The raw license will be fed to the PlayReady.
          response.body = Uint8Array.from(atob(rawLicenseBase64), (c) =>
            c.charCodeAt(0)
          );
        } catch (error) {}
      }
      if (type === HttpRequestType.DRM_LICENSE_FAIRPLAY) {
        // This is the wrapped license, which is a JSON string.
        try {
          const wrapped = JSON.parse(response.body);
          // Parse the JSON string into an object.
          // This is a base64-encoded version of the raw license.
          const rawLicenseBase64 = wrapped.license;
          // Decode that base64 string into a Uint8Array and replace the response data.
          // The raw license will be fed to the FairPlay.
          response.body = Uint8Array.from(atob(rawLicenseBase64), (c) =>
            c.charCodeAt(0)
          );
        } catch (error) {}
      }
    },
  },
};

const player = new bitmovin.player.Player(
  document.getElementById('VIDEO_CONTAINER_ELEMENT'),
  config
);
player.load(source);

Please replace the following placeholders in the code:

PropsTypeDescription
DASH_MANIFEST_URLStringThe URL to the DASH manifest (MPD) file.
HLS_MANIFEST_URLStringThe URL to the HLS Multivariant Playlist (M3U8) file.
WIDEVINE_LICENSE_SERVER_URLStringThe URL to SigmaMultiDRM's Widevine license server.
PLAYREADY_LICENSE_SERVER_URLStringThe URL to SigmaMultiDRM's PlayReady license server.
FAIRPLAY_LICENSE_SERVER_URLStringThe URL to SigmaMultiDRM's FairPlay license server.
FAIRPLAY_CERTIFICATE_URLStringThe URL to retrieve the FairPlay certificate.
MERCHANT_IDStringThe ID of SigmaMultiDRM's merchant
APP_IDStringThe ID of merchant's application
USER_IDStringThe ID merchant's user
SESSION_IDStringThe session of merchant's user