Skip to content
On this page

Hướng dẫn sử dụng Video.js

1. Giới thiệu

Phần này sẽ cung cấp thông tin để tích hợp hệ thống SigmaMultiDRM vào trình phát Video.js player:

::: Thông tin

Trong đó, MERCHANT_ID và APP_ID có thể được lấy từ hệ thống Dashboard như hình dưới đây.

get_customer_info :::

2. Yêu cầu

::: Thông tin yêu cầu

Trình duyệt hỗ trợ Html5:

Trình duyệtWidevinePlayReadyFairPlayHỗ trợ mã hóa license
Chrome (Window, MacOS, Android, ChromeOS, Linux)YesNoNoYes
Firefox (Window, MacOS, Linux)YesNoNoYes
Microsoft Edge (Window, MacOS, Android)YesYesNoNo
Safari (Safari 8+ on MacOS, Safari on iOS 11.2+)NoNoYesNo
iOS Browser (Chrome, Cốc Cốc, Microsoft Edge, Firefox, Opera)NoNoYesNo
Opera (Window, MacOS)YesNoNoYes
Internet Explorer (Window 8.1+)NoYesYesNo
  • Smart TVs:
Smart TVsWidevinePlayReadyFairPlayHỗ trợ mã hóa license
SamSung Tizen (2016-2017, 2018+ Models)YesYesNoNo
SamSung Tizen&Orsay (2010-2015 Models)NoYesNoNo
LG (WebOS 3.0+)YesYesNoNo
LG (WebOS 1.2 & Netcast)NoYesNoNo
Smart TV Alliance (LG, Philips, Toshiba, Panasonic)YesYesNoNo
Android TVYesYesNoNo

:::

3. Tích hợp Sigma MultiDRM vào Video.js Player

Installing the SDK

  • Địa chỉ: sigma_packer.js

  • Thêm mã nguồn:

    html
    <!-- FIXME: If you user license encrypt feature then please uncomment the lines below -->
    <!-- <script src="sigma_packer.js"></script> -->
    <script src="https://vjs.zencdn.net/8.16.1/video.min.js"></script>
    <script src="https://cdn.jsdelivr.net/npm/videojs-contrib-eme@5.5.1/dist/videojs-contrib-eme.min.js"></script>

Chú ý: Việc cài đặt theo từng bước dưới đây

3.2 Init application

javascript
var player;
var keySystems = {};

document.addEventListener('DOMContentLoaded', function () {
  // FIXME: If you user license encrypt feature then please uncomment the lines below
  // window.sigmaPacker = new SigmaPacker();
  // window.sigmaPacker.onload = () => {
  //   console.log('SigmaPacker loaded');
  // };
  // window.sigmaPacker.init();

  var video = document.getElementById('VIDEO_ID'); // media element
  player = videojs(video, {
    controls: true,
    autoplay: true,
    preload: 'auto',
  });

  player.eme();
  player.src({
    src: MANIFEST_URI,
    type:
      MANIFEST_URI.includes('.mpd') &&
      (MANIFEST_URI.endsWith('.mpd') || MANIFEST_URI.includes('.mpd?'))
        ? 'application/dash+xml'
        : 'application/x-mpegURL',
    keySystems,
  });
});

3. Xử lý yêu cầu cấp license

3.1. FairPlay

javascript
player.eme.initLegacyFairplay();
keySystems['com.apple.fps.1_0'] = {
  licenseUri: FAIRPLAY_LICENSE_SERVER_URL, // See heading 1
  certificateUri: FAIRPLAY_CERTIFICATE_URI, // See heading 1
  getContentId: function (emeOptions, contentId) {
    return contentId.includes('skd://')
      ? 'https://' + contentId.split('skd://')[1]
      : contentId;
  },
  getLicense: function (emeOptions, contentId, keyMessage, callback) {
    var licenseUri = contentId;

    fetch(licenseUri, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/json',
        'custom-data': btoa(
          JSON.stringify({
            merchantId: MERCHANT_ID,
            appId: APP_ID,
            userId: USER_ID,
            sessionId: SESSION_ID,
          })
        ),
      },
      body: JSON.stringify({
        spc: uint8ToBase64(keyMessage),
        assetId: new URL(licenseUri).searchParams.get('assetId'),
      }),
    })
      .then((response) => response.json())
      .then((response) => {
        try {
          var licenseBuffer = base64ToArrayBuffer(response.license);
          callback(null, licenseBuffer);
        } catch (error) {
          callback(error, null);
        }
      })
      .catch((error) => {
        console.error('❌ License request failed', error);
        callback(error, null);
      });
  },
};

3.2. Widevine

javascript
keySystems['com.widevine.alpha'] = {
  licenseUri: WIDEVINE_LICENSE_SERVER_URL, // See heading 1
  getLicense: function (emeOptions, keyMessage, callback) {
    var customData = {
      merchantId: MERCHANT_ID,
      appId: APP_ID,
      userId: USER_ID,
      sessionId: SESSION_ID,
    };

    // FIXME: If you user license encrypt feature then please uncomment the lines below
    // var packInfo = window.sigmaPacker.getDataPacker(keyMessage) || {};
    // customData = {
    //   ...customData,
    //   reqId: packInfo.requestId,
    //   deviceInfo: packInfo.deviceInfo,
    // };

    fetch(WIDEVINE_LICENSE_SERVER_URL /* See heading 1 */, {
      method: 'POST',
      headers: {
        'Content-Type': 'application/octet-stream',
        'custom-data': btoa(JSON.stringify(customData)),
      },
      body: keyMessage,
    })
      .then((response) => response.json())
      .then((response) => {
        try {
          // FIXME: If you user license encrypt feature then please uncomment the lines below
          // if (needEncryptLicense && response['client-info']) {
          //   window.sigmaPacker.update(atob(response['client-info']));
          // } else if (response.clientInfo) {
          //   window.sigmaPacker.update(JSON.stringify(response.clientInfo));
          // }

          var licenseBuffer = base64ToArrayBuffer(response.license);
          callback(null, licenseBuffer);
        } catch (error) {
          callback(error, null);
        }
      })
      .catch((error) => {
        console.error('❌ License request failed', error);
        callback(error, null);
      });
  },
};

3.3. PlayReady

javascript
keySystems['com.microsoft.playready'] = {
  licenseUri: PLAYREADY_LICENSE_SERVER_URL, // See heading 1
  getLicense: function (emeOptions, keyMessage, callback) {
    var customData = {
      merchantId: MERCHANT_ID,
      appId: APP_ID,
      userId: USER_ID,
      sessionId: SESSION_ID,
    };

    try {
      var request = unpackPlayReadyRequest(keyMessage);
      // FIXME: If you user license encrypt feature then please uncomment the lines below
      // var packInfo = window.sigmaPacker.getDataPacker(request.body) || {};
      // customData = {
      //   ...customData,
      //   reqId: packInfo.requestId,
      //   deviceInfo: packInfo.deviceInfo,
      // };

      fetch(`https://${licenseDomain}${PLAYREADY_PATH}`, {
        method: 'POST',
        headers: {
          ...request.headers,
          'Content-Type': 'application/octet-stream',
          'custom-data': btoa(JSON.stringify(customData)),
        },
        body: request.body,
      })
        .then((response) => response.json())
        .then((response) => {
          try {
            // FIXME: If you user license encrypt feature then please uncomment the lines below
            // if (needEncryptLicense && response['client-info']) {
            //   window.sigmaPacker.update(atob(response['client-info']));
            // } else if (response.clientInfo) {
            //   window.sigmaPacker.update(JSON.stringify(response.clientInfo));
            // }

            var licenseBuffer = base64ToUint8(response.license);
            callback(null, licenseBuffer);
          } catch (error) {
            callback(error, null);
          }
        })
        .catch((error) => {
          console.error('❌ License request failed', error);
          callback(error, null);
        });
    } catch (error) {
      callback(error, null);
    }
  },
};

Thông tin

Thông tin cần cung cấp

PropsTypeDescription
MERCHANT_IDStringId of merchant's user
APP_IDStringId of application
USER_IDStringUserId of merchant's client
SESSION_IDStringSessionId of merchant's client

Các hàm tiện ích

javascript
function base64ToUint8(str = '') {
  const bytes = window.atob(str.replace(/-/g, '+').replace(/_/g, '/'));
  const result = new Uint8Array(bytes.length);
  for (var i = 0; i < bytes.length; ++i) {
    result[i] = bytes.charCodeAt(i);
  }
  return result;
}

function uint8ToBase64(input) {
  input = new Uint8Array(input);
  var keyStr =
    'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=';
  var output = '';
  var chr1, chr2, chr3, enc1, enc2, enc3, enc4;
  var i = 0;

  while (i < input.length) {
    chr1 = input[i++];
    chr2 = i < input.length ? input[i++] : Number.NaN; // Not sure if the index
    chr3 = i < input.length ? input[i++] : Number.NaN; // checks are needed here

    enc1 = chr1 >> 2;
    enc2 = ((chr1 & 3) << 4) | (chr2 >> 4);
    enc3 = ((chr2 & 15) << 2) | (chr3 >> 6);
    enc4 = chr3 & 63;

    if (isNaN(chr2)) {
      enc3 = enc4 = 64;
    } else if (isNaN(chr3)) {
      enc4 = 64;
    }
    output +=
      keyStr.charAt(enc1) +
      keyStr.charAt(enc2) +
      keyStr.charAt(enc3) +
      keyStr.charAt(enc4);
  }
  return output;
}

function unpackPlayReadyRequest(keyMessage) {
  const request = {
    headers: {},
    body: {},
  };
  const decoder = new TextDecoder('utf-16le');
  const xmlString = decoder.decode(keyMessage);

  if (!xmlString.includes('PlayReadyKeyMessage')) {
    console.debug('PlayReady request is already unwrapped.');
    request.headers['Content-Type'] = 'text/xml; charset=utf-8';
    return request;
  }

  const parser = new DOMParser();
  const xmlDoc = parser.parseFromString(xmlString, 'application/xml');

  if (xmlDoc.querySelector('parsererror')) {
    throw new Error('Failed to parse PlayReady XML!');
  }

  const headers = xmlDoc.querySelectorAll('HttpHeader');
  headers.forEach((header) => {
    const nameElement = header.querySelector('name');
    const valueElement = header.querySelector('value');

    if (!nameElement || !valueElement) {
      throw new Error('Malformed PlayReady headers!');
    }

    request.headers[nameElement.textContent] = valueElement.textContent;
  });

  const challengeElement = xmlDoc.querySelector('Challenge');
  if (
    !challengeElement ||
    challengeElement.getAttribute('encoding') !== 'base64encoded'
  ) {
    throw new Error('Malformed or unexpected PlayReady challenge encoding!');
  }

  request.body = Uint8Array.from(atob(challengeElement.textContent), (c) =>
    c.charCodeAt(0)
  );
  return request;
}

4. Mẫu

Mã nguồn mẫu