Fairplay with Safari

Tutorial on integrating Fairplay on Safari

Safari browsers on MacOS and iOS natively support playback of HLS streams which are encrypted with Fairplay. This guide gives you sample code to get started with Fairplay integration on Safari.

One you add Fairplay credentials on DRM Credentials page, you will be given these 2 URLs.

// ADAPT: You need to replace this with actual URL
window.certificate_url = "https://fairplay.gumlet.com/certificate/:org_id";
// ADAPT: You need to replace with actual URL
window.licence_url = "https://fairplay.gumlet.com/licence/:org_id";
// ADAPT: You need to replace with actual URL
window.playback_url = "https://video.gumlet.io/your_url.m3u8";

Here org_id wiil be replaced with your Gumlet organization id. First step is to write a function which can download and store certificate. The certificate will be used later.

async function loadCertificate()
    try {
        let response = await fetch(window.certificate_url);
        window.certificate = await response.arrayBuffer();
    } catch(e) {
        window.console.error(`Could not load certificate at ${serverCertificatePath}`);

Now we will write function to call this function to load certificate and start video playback.

async function startVideo()
  await loadCertificate();
  let video = document.querySelector('video');
  video.addEventListener('encrypted', onEncrypted);
  video.src = window.playback_url;

You can see there is a function onEncrypted that we will call when 'encrypted' event is fired. That function will fetch credntials and pass to secure session so Safari can decode the encrypted content.

async function onEncrypted(event) {
  try {
    let initDataType = event.initDataType;
    if (initDataType !== 'skd') {
      window.console.error(`Received unexpected initialization data type "${initDataType}"`);
    let video = event.target;
    if (!video.mediaKeys) {
      let access = await navigator.requestMediaKeySystemAccess("com.apple.fps", [{
        initDataTypes: [initDataType],
        videoCapabilities: [{ contentType: 'application/vnd.apple.mpegurl', robustness: '' }],
        distinctiveIdentifier: 'not-allowed',
        persistentState: 'not-allowed',
        sessionTypes: ['temporary'],

      let keys = await access.createMediaKeys();
      // Heads Up! The certificate we fetched earlier is used here.
      await keys.setServerCertificate(window.certificate);
      await video.setMediaKeys(keys);

    let initData = event.initData;
    let session = video.mediaKeys.createSession();
    session.generateRequest(initDataType, initData);
        let message = await new Promise(resolve => {
        session.addEventListener('message', resolve, { once: true });
    // licence_url we set earlier is used here.
    let response = await getResponse(message, window.licence_url);
    await session.update(response);
    return session;
  } catch(e) {
    window.console.error(`Could not start encrypted playback due to exception "${e}"`)

Here is our last function that we used. It's getResponse function which sends SPC message to Fairplay server and returned CKC is set in given encrypted session.

async function getResponse(event, licence_server_url) {
    // need to convert the message to Base64 string
    let spc_string = btoa(String.fromCharCode.apply(null, new Uint8Array(event.message)));
    let licenseResponse = await fetch(licence_server_url, {
        method: 'POST',
        headers: new Headers({'Content-type': 'application/json'}),
        body: JSON.stringify({
            "spc" : spc_string
    let responseObject = await licenseResponse.json();
    return Uint8Array.from(atob(responseObject.ckc), c => c.charCodeAt(0));

Now, everything is ready and we can just put these functions in <head> section or in separate JS file. All we need to do is to put <video> tag and call startVideo function.

  document.addEventListener('DOMContentLoaded', startVideo);
<video controls preload="metadata" width=960></video>



All steps are now complete and Fairplay video can be played on Safari browsers on iOS and MacOS. Full HTML code is available.

Did this page help you?