Fairplay HTML5 player integration

This documentation will guide you through the integration of the FairPlay DRM solution into your web application, ensuring secure and compliant playback of DRM-encrypted content.

What You'll Need

Playing DRM Content

Once video content is DRM encrypted, every play session needs a valid play license. Play license can be obtained from a License Server URL.

License server URL

Fairplay key server is accessible with the URL of the following pattern:

https://video.bunnycdn.com/FairPlayLicense/{library_id}/{video_id}
  • library_id: A unique identifier for the library or content collection.
  • video_id: The specific identifier for the FairPlay encrypted video.

Fairplay Integration with Safari

Safari browsers on macOS and iOS inherently support the playback of HLS streams encrypted with Fairplay. To facilitate Fairplay integration on Safari, use the provided sample code:

// MODIFY: You need to replace {library_id} and {video_id} with actual values  
window.fp_license_server_url = "https://video.bunnycdn.com/FairPlayLicense/{library_id}/{video_id}";  
// MODIFY: You need to replace {pull_zone_url} and {video_id} with actual URL  
window.playback_url = "https://{pull_zone_url}.b-cdn.net/{video_id}/playlist.m3u8";

Ensure to replace library_id, pull_zone_url, and video_id with valid values corresponding to the specific video player.
The next step is to create a function to download and store certificates, which will be used later:

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

Write a function to call the certificate-loading function and initiate video playback:

async function startVideo()  
{  
  await loadFpCertificate();
  // get html5 video element
  let video = document.querySelector('video');
  
  // hook up the encrypted event
  video.addEventListener('encrypted', onFpEncrypted); 
  
  // set the source property of the player.
  video.src = window.playback_url;  
}

Define the onFpEncrypted function to handle the 'encrypted' event. It fetches the license, passes it to secure session, and enables Safari to decode the encrypted content:

async function onFpEncrypted(event) {
  try {
    let initDataType = event.initDataType;
    if (initDataType !== 'skd') {
      window.console.error(`Received unexpected initialization data type "${initDataType}"`);
      return;
    }
    
    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();
      
      // The FairPlay certificate we fetched earlier is used here.
      await keys.setServerCertificate(window.fp_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 });
    });
	
    // license_server_url we set earlier is used here.
    let response = await getResponse(message, window.fp_license_server_url);
    await session.update(response);
    return session;
  } catch(e) {
    window.console.error(`Could not start encrypted playback due to exception "${e}"`)
  }
}

Implement the getResponse function to send the SPC message to the FairPlay server and obtain the CKC for the encrypted session.

async function getResponse(event, license_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(license_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));  
}

Place the following script block in the section or in a separate JavaScript file. This code ensures that video playback starts when the page is loaded.

<script>
  document.addEventListener('DOMContentLoaded', startVideo);
</script>

Place the HTML5 video element on desired place in the html document.

<video controls preload="metadata" width=960></video>