hls.js Player
The HLS.js player is a popular JavaScript-based media player that enables adaptive streaming using HTTP Live Streaming (HLS) protocol directly in modern web browsers.
The general concept for Axinom DRM integration for DRM-compatible players, is outlined in the video Players document.
This document provides a guide on integrating Axinom Digital Rights Management (DRM) services with the HLS.js player, ensuring secure and seamless playback of protected content.
Integration​
Below you can find a sample implementation with explanation.
<!DOCTYPE html>
<html>
   <body>
      <script src="../node_modules/hls.js/dist/hls.js"></script>
      <!-- Or if you want the latest version from the main branch -->
      <!-- <script src="https://cdn.jsdelivr.net/npm/hls.js@canary"></script> -->
      <video id="video"></video>
      <script>
         var video = document.getElementById('video');
         var certificateUrl = "https://vtb.axinom.com/FPScert/fairplay.cer"; 
         <!-- This is the Axinom test certificate and you will need to change it to your production test certificate-->
         loadCertificate();
               if (Hls.isSupported()) {
                   var hls = new Hls({
                       debug: true,
                       enableWorker: true,
                       lowLatencyMode: true,
                       backBufferLength: 30,
                       emeEnabled: true,
                       drmSystems: {
                           'com.apple.fps': {
                               licenseUrl: "https://drm-fairplay-licensing.axprod.net/AcquireLicense",
                               serverCertificateUrl: "https://vtb.axinom.com/FPScert/fairplay.cer",
                           },
                       },
                       getContentId: function (emeOptions, initData) {
                                   return arrayToString(initData).replace(/^.*:\/\//, '');
                               },
                       licenseXhrSetup: function (xhr, url, keyContext, licenseChallenge) {
                           xhr.setRequestHeader('X-AxDRM-Message', 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.ewogICJ2ZXJzaW9uIjogMSwKICAiY29tX2tleV9pZCI6ICI2OWU1NDA4OC1lOWUwLTQ1MzAtOGMxYS0xZWI2ZGNkMGQxNGUiLAogICJtZXNzYWdlIjogewogICAgInR5cGUiOiAiZW50aXRsZW1lbnRfbWVzc2FnZSIsCiAgICAidmVyc2lvbiI6IDIsCiAgICAibGljZW5zZSI6IHsKICAgICAgImFsbG93X3BlcnNpc3RlbmNlIjogdHJ1ZQogICAgfSwKICAgICJjb250ZW50X2tleXNfc291cmNlIjogewogICAgICAiaW5saW5lIjogWwogICAgICAgIHsKICAgICAgICAgICJpZCI6ICIzMDJmODBkZC00MTFlLTQ4ODYtYmNhNS1iYjFmODAxOGEwMjQiLAogICAgICAgICAgImVuY3J5cHRlZF9rZXkiOiAicm9LQWcwdDdKaTFpNDNmd3YremZ0UT09IiwKICAgICAgICAgICJ1c2FnZV9wb2xpY3kiOiAiUG9saWN5IEEiCiAgICAgICAgfQogICAgICBdCiAgICB9LAogICAgImNvbnRlbnRfa2V5X3VzYWdlX3BvbGljaWVzIjogWwogICAgICB7CiAgICAgICAgIm5hbWUiOiAiUG9saWN5IEEiLAogICAgICAgICJwbGF5cmVhZHkiOiB7CiAgICAgICAgICAibWluX2RldmljZV9zZWN1cml0eV9sZXZlbCI6IDE1MCwKICAgICAgICAgICJwbGF5X2VuYWJsZXJzIjogWwogICAgICAgICAgICAiNzg2NjI3RDgtQzJBNi00NEJFLThGODgtMDhBRTI1NUIwMUE3IgogICAgICAgICAgXQogICAgICAgIH0KICAgICAgfQogICAgXQogIH0KfQ._NfhLVY7S6k8TJDWPeMPhUawhympnrk6WAZHOVjER6M');
                       },
                       drmSystemOptions: {
                           videoEncryptionScheme: "cbcs",
                           audioEncryptionScheme: "cbcs",
                       },
                   });
                   hls.loadSource('https://media.axprod.net/TestVectors/Cmaf/protected_1080p_h264_cbcs/manifest.m3u8');
                   hls.attachMedia(video);
                   hls.on(Hls.Events.MEDIA_ATTACHED, function () {
                       video.muted = true;
                       video.play();
                   });
               }
         // HLS.js is not supported on platforms that do not have Media Source
         // Extensions (MSE) enabled.
         //
         // When the browser has built-in HLS support (check using `canPlayType`),
         // we can provide an HLS manifest (i.e. .m3u8 URL) directly to the video
         // element through the `src` property. This is using the built-in support
         // of the plain video element, without using HLS.js.
         else if (video.canPlayType('application/vnd.apple.mpegurl')) {
           video.src = videoSrc;
         }
         function loadVideo(){
           video.addEventListener('webkitneedkey', loadvideo, false);
         }
         function loadvideo(event) {
                       console.log("Content is protected. Preparing to create license request.");
                       console.log(event);
                       var video = event.target;
                       var initData = event.initData;
                       console.log(event.initData);
                       var contentId = extractContentId(initData);
                       console.log("Content ID is: " + contentId);
                       initData = concatInitDataIdAndCertificate(initData, contentId, certificate);
                       if (!video.webkitKeys)
                       {
                           selectKeySystem();
                           video.webkitSetMediaKeys(new WebKitMediaKeys(keySystem));
                       }
                       if (!video.webkitKeys)
                       throw "Could not create MediaKeys";
                       var keySession = video.webkitKeys.createSession("video/mp4", initData);
                       if (!keySession)
                       throw "Could not create key session";
                       keySession.contentId = contentId;
                       waitForEvent('webkitkeymessage', licenseRequestReady, keySession);
                       waitForEvent('webkitkeyadded', onkeyadded, keySession);
                       waitForEvent('webkitkeyerror', onkeyerror, keySession);
                   }
        function extractContentId(initData) {
                       var stringarray = arrayToString(initData).replace(/^.*:\/\//, '');
                       console.log('stringarray', stringarray);
                       return stringarray;
                   }
        function arrayToString(array) {
                       var uint16array = new Uint16Array(array.buffer);
                       return String.fromCharCode.apply(null, uint16array);
                   }
        function concatInitDataIdAndCertificate(initData, id, cert) {
                       if (typeof id == "string")
                       id = stringToArray(id);
                       var offset = 0;
                       var buffersize = initData.byteLength + 4 + id.byteLength + 4 + cert.byteLength;
                       console.log('buffersize', buffersize);
                       var buffer = new ArrayBuffer(buffersize);
                       var dataView = new DataView(buffer);
                       var initDataArray = new Uint8Array(buffer, offset, initData.byteLength);
                       initDataArray.set(initData);
                       offset += initData.byteLength;
                       dataView.setUint32(offset, id.byteLength, true);
                       offset += 4;
                       var idArray = new Uint16Array(buffer, offset, id.length);
                       idArray.set(id);
                       offset += idArray.byteLength;
                       dataView.setUint32(offset, cert.byteLength, true);
                       offset += 4;
                       var certArray = new Uint8Array(buffer, offset, cert.byteLength);
                       certArray.set(cert);
                       return new Uint8Array(buffer, 0, buffer.byteLength);
                   }
        function loadCertificate() {
                       console.log("Requesting FPS certificate from " + certificateUrl)
                       var request = new XMLHttpRequest();
                       request.responseType = 'arraybuffer';
                       request.addEventListener('load', onCertificateLoaded, false);
                       request.addEventListener('error', onCertificateError, false);
                       request.open('GET', certificateUrl, true);
                       request.setRequestHeader('Pragma', 'Cache-Control: no-cache');
                       request.setRequestHeader("Cache-Control", "max-age=0");
                       request.send();
                   }
        function onCertificateLoaded(event) {
                       console.log("FPS certificate received.");
                       var request = event.target;
                       certificate = new Uint8Array(request.response);
                       loadVideo();
                   }
        function onCertificateError(event) {
                       window.console.error('FPS certificate request failed.');
                   }  
        function stringToArray(string) {
                       var buffer = new ArrayBuffer(string.length*2);
                       var array = new Uint16Array(buffer);
                       for (var i=0, strLen=string.length; i<strLen; i++) {
                           array[i] = string.charCodeAt(i);
                       }
                       return array;
                   }
      </script>
   </body>
</html>
Testing playback​
You can run the above script with your content and the token to see a successful playback.
See also​
- Create entitlement message - https://portal.axinom.com/mosaic/videos/tool-entitlement-message/portal-tool-entitlement-message
 - Hls.js player documentation - https://github.com/video-dev/hls.js/blob/master/docs/API.md