LL-HLS
Vindral Live supports LL-HLS output. It is not activated by default, as most customers do not need it since Vindral's regular MoQ output provides configurable latency that can go as low as competitors' fixed solutions while maintaining superior stability, better features, and high availability on devices.
Contact us for information on how to enable LL-HLS output for your channels.
Player Implementation
HLS playback isn’t implemented in the Vindral Live player. Instead, open-source HLS players such as hls.js are recommended for web applications. This approach allows you to leverage well-maintained, feature-rich players that are actively developed by the community.
Implementation Examples
HLS.js with Safari Fallback
This example shows how to implement HLS playback using HLS.js for most browsers, with automatic fallback to Safari's native HLS support. HLS.js is a JavaScript library that implements an HTTP Live Streaming client using HTML5 video and MediaSource Extensions, while Safari can play HLS streams natively without additional libraries.
<script src="https://cdn.jsdelivr.net/npm/hls.js"></script>
<video id="video" controls autoplay muted><video>
<script>
function supportsNativeHLS() {
var video = document.createElement('video');
return Boolean(video.canPlayType('application/vnd.apple.mpegURL'))
}
const channelId = 'vindral_demo1_ci_099ee1fa-80f3-455e-aa23-3d184e93e04f';
const playlistUrl = `https://lb.cdn.vindral.com/api/hls?channelId=${channelId}`;
if (!supportsNativeHLS()) {
const video = document.getElementById('video');
const hls = new Hls({
lowLatencyMode: true,
});
hls.loadSource(playlistUrl);
hls.attachMedia(video);
} else {
video.src = playlistUrl;
}
</script>
DRM
<script src="https://cdn.jsdelivr.net/npm/hls.js"></script>
<video id="video" controls autoplay muted><video>
<script>
function supportsNativeHLS() {
var video = document.createElement('video');
return Boolean(video.canPlayType('application/vnd.apple.mpegURL'))
}
// The certificate can either be supplied as a blob here, or downloaded from a server
const fairplayCertificate = ""
const fairplayLicenseUrl = ""
const widewineLicenseUrl = ""
const playreadyLicenseUrl = ""
const channelId = 'vindral_demo1_ci_099ee1fa-80f3-455e-aa23-3d184e93e04f';
const playlistUrl = `https://lb.cdn.vindral.com/api/hls?channelId=${channelId}`;
if (!supportsNativeHLS()) {
const video = document.getElementById('video');
const hls = new Hls();
var hls = new Hls({
lowLatencyMode: true,
emeEnabled: true,
drmSystems: {
"com.widevine.alpha": {
"licenseUrl": widewineLicenseUrl
},
"com.microsoft.playready": {
"licenseUrl": playreadyLicenseUrl
},
"com.apple.fps": {
licenseUrl: fairplayLicenseUrl,
serverCertificateUrl: `data:text/plain;base64,${fairplayCertificate}`,
},
},
licenseXhrSetup: function (xhr, url, keyContext, licenseChallenge) {
// When using Irdeto a valid JWT needs to be supplied in the auth header
//
// xhr.open('POST', url, true);
// xhr.setRequestHeader('Authorization', `Bearer ${jwt}`);
// Return undefined to use the default license request payload with modified xhr
return undefined
},
});
hls.loadSource(url);
hls.attachMedia(video);
} else {
async function onEncrypted(event) {
const initDataType = event.initDataType;
const video = event.target;
const access = await navigator.requestMediaKeySystemAccess("com.apple.fps", [{
initDataTypes: [initDataType],
videoCapabilities: [{ contentType: 'video/mp4; codecs="avc1.42c01f"', robustness: '' }],
audioCapabilities: [{ contentType: 'audio/mp4; codecs="mp4a.40.2"', robustness: '' }],
distinctiveIdentifier: 'not-allowed',
persistentState: 'not-allowed',
sessionTypes: ['temporary'],
}]);
const keys = await access.createMediaKeys();
await keys.setServerCertificate(base64ToArrayBuffer(fairplayCertificate));
await video.setMediaKeys(keys);
const initData = event.initData;
const session = video.mediaKeys.createSession();
session.generateRequest(initDataType, initData);
const { message } = await new Promise(resolve => {
session.addEventListener('message', resolve, { once: true });
});
const licenseResponse = await fetch(fairplayLicenseUrl, {
method: 'POST',
// When using Irdeto a valid JWT needs to be supplied in the auth header
//
// headers: new Headers({'Content-type': 'application/octet-stream', 'Authorization': `Bearer ${jwt}`}),
body: message
});
const license = await licenseResponse.arrayBuffer()
await session.update(license);
return session;
}
video.addEventListener('encrypted', onEncrypted);
video.src = playlistUrl;
}
</script>