Dolby Vision
Tip: open your browser's developer console and use the player and THEOplayer variables to interact with this demo.
This example plays a Dolby Vision profile 8.4 stream (an HLG-compatible base layer with a Dolby Vision
enhancement layer, signaled via supplemental codecs: dvh1.08.xx in DASH via scte214:supplementalCodecs,
and dvhe.08.xx in HLS via SUPPLEMENTAL-CODECS). It probes the browser with
MediaCapabilities.decodingInfo
for the Dolby Vision codec (dvh1.08.09 or dvhe.08.09) and the HEVC Main10 HLG base layer
(hvc1.2.4.L153.90 or hev1.2.4.L153.90), and checks for an HDR display with
matchMedia('(dynamic-range: high)'), re-checking when the display capability changes.
await navigator.mediaCapabilities.decodingInfo({
type: 'media-source',
video: {
contentType: 'video/mp4; codecs="dvh1.08.09"',
bitrate: 18000000,
width: 3840,
height: 2160,
framerate: 60,
transferFunction: 'hlg',
},
});
const hdrDisplay = matchMedia('(dynamic-range: high)').matches;
The status box is green when Dolby Vision decoding and an HDR display are both present, yellow when the browser can decode only the HEVC HLG base layer and not Dolby Vision, or when the display does not support HDR, and red when the browser cannot decode the HEVC base layer at all.
Code
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<title>Dolby Vision</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0, viewport-fit=cover" />
<link rel="preconnect" href="https://fonts.googleapis.com" />
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
<link href="https://fonts.googleapis.com/css2?family=Noto+Sans:ital,wght@0,400;0,700;1,400;1,700&display=swap" rel="stylesheet" />
<style>
html,
body {
margin: 0;
padding: 0;
}
theoplayer-default-ui:not(:defined) {
display: inline-block;
box-sizing: border-box;
}
theoplayer-default-ui {
width: 100%;
aspect-ratio: 16 / 9;
font-family: 'Noto Sans', sans-serif;
background: #000;
}
</style>
<script src="https://cdn.theoplayer.com/dash/theoplayer/THEOplayer.js"></script>
<script nomodule src="https://unpkg.com/@theoplayer/web-ui@2/polyfills"></script>
<script async src="https://unpkg.com/@theoplayer/web-ui@2"></script>
</head>
<body>
<!--
libraryLocation: For demonstration purposes, we use the THEOplayer CDN.
For production use, host the library yourself and update this URL and the script above.
licenseUrl: Point this to your license file, or replace it with a license property.
-->
<theoplayer-default-ui
configuration='{"libraryLocation":"https://cdn.theoplayer.com/dash/theoplayer/","licenseUrl":"../../../../theoplayer-license.txt","mutedAutoplay":"all"}'
source='{"sources":{"src":"https://ott.dolby.com/OTTEP/prod/v1_1/dovi/11/ta/clear/p8.4/60fps/hvc1/hvc1_supp.mpd","type":"application/dash+xml"},"metadata":{"title":"Dolby Vision (profile 8.4)"}}'
></theoplayer-default-ui>
<script src="../utils.js"></script>
<script>
// Detect Dolby Vision support: a Dolby Vision codec (dvh1/dvhe, profile 8) plus an HDR display.
// The DASH manifest signals dvh1.08.xx, the HLS manifest dvhe.08.xx; base layer is hvc1/hev1 HLG.
async function probeVideo(contentType, transferFunction) {
const video = { contentType, bitrate: 18000000, width: 3840, height: 2160, framerate: 60, transferFunction };
if (navigator.mediaCapabilities && navigator.mediaCapabilities.decodingInfo) {
try {
const result = await navigator.mediaCapabilities.decodingInfo({ type: 'media-source', video });
return { status: result.supported ? 'supported' : 'unsupported' };
} catch (e) {
return { status: 'unsupported' };
}
}
if (window.MediaSource) {
// MediaSource.isTypeSupported cannot probe the transfer function.
return { status: MediaSource.isTypeSupported(contentType) ? 'supported' : 'unsupported', fallback: true };
}
return { status: 'unavailable' };
}
function hdrDisplay() {
if (!window.matchMedia) return { status: 'unavailable' };
return { status: window.matchMedia('(dynamic-range: high)').matches ? 'supported' : 'unsupported' };
}
function reportActiveCodecs(player) {
const videoTrack = Array.from(player.videoTracks).find((track) => track.enabled);
const audioTrack = Array.from(player.audioTracks).find((track) => track.enabled);
const codecs = [
videoTrack && videoTrack.activeQuality && videoTrack.activeQuality.codecs,
audioTrack && audioTrack.activeQuality && audioTrack.activeQuality.codecs,
]
.filter(Boolean)
.join(', ');
if (codecs) sendToParent('playing-codecs', { codecs });
}
onPlayerReady(async (player) => {
async function report() {
const [dvh1, dvhe, hvc1, hev1] = await Promise.all([
probeVideo('video/mp4; codecs="dvh1.08.09"', 'hlg'),
probeVideo('video/mp4; codecs="dvhe.08.09"', 'hlg'),
probeVideo('video/mp4; codecs="hvc1.2.4.L153.90"', 'hlg'),
probeVideo('video/mp4; codecs="hev1.2.4.L153.90"', 'hlg'),
]);
// Either codec string is enough: dvh1 (DASH) or dvhe (HLS) for Dolby Vision,
// hvc1 (DASH) or hev1 (HLS) for the HEVC HLG base layer.
const dv =
dvh1.status === 'supported' || dvhe.status === 'supported'
? { status: 'supported', fallback: dvh1.fallback && dvhe.fallback }
: {
status: dvh1.status === 'unavailable' && dvhe.status === 'unavailable' ? 'unavailable' : 'unsupported',
fallback: dvh1.fallback || dvhe.fallback,
};
const hevc =
hvc1.status === 'supported' || hev1.status === 'supported'
? { status: 'supported', fallback: hvc1.fallback && hev1.fallback }
: {
status: hvc1.status === 'unavailable' && hev1.status === 'unavailable' ? 'unavailable' : 'unsupported',
fallback: hvc1.fallback || hev1.fallback,
};
const hdr = hdrDisplay();
const checks = [
{
label: 'Dolby Vision codec (dvh1.08.09 or dvhe.08.09, HLG)' + (dv.fallback ? ' — via MediaSource.isTypeSupported' : ''),
status: dv.status,
},
{
label:
'HEVC Main10 HLG base layer (hvc1.2.4.L153.90 or hev1.2.4.L153.90)' + (hevc.fallback ? ' — via MediaSource.isTypeSupported' : ''),
status: hevc.status,
},
{ label: 'HDR display (dynamic-range: high)', status: hdr.status },
];
const level = hevc.status !== 'supported' ? 'danger' : dv.status !== 'supported' || hdr.status !== 'supported' ? 'warning' : 'success';
const summary =
level === 'success'
? 'Dolby Vision is supported on this device and display.'
: level === 'danger'
? 'This browser cannot decode HEVC video.'
: dv.status !== 'supported'
? 'This browser cannot decode Dolby Vision, only the HEVC HLG base layer.'
: 'This browser can decode Dolby Vision, but this display does not support HDR.';
sendToParent('detection-status', { level, summary, checks });
}
await report();
if (window.matchMedia) {
window.matchMedia('(dynamic-range: high)').addEventListener('change', report);
}
player.videoTracks.addEventListener('addtrack', (event) =>
event.track.addEventListener('activequalitychanged', () => reportActiveCodecs(player))
);
player.audioTracks.addEventListener('addtrack', (event) =>
event.track.addEventListener('activequalitychanged', () => reportActiveCodecs(player))
);
onParentMessage('load-source', (message) => {
player.autoplay = true;
player.source = message.source;
});
});
</script>
</body>
</html>