'Detect H.265 HEVC incompatibility
I am trying to detect when a browser will not play a specific video. A good example is trying to detect that Chrome cannot play H.265/HEVC encoded video.
I tried using the video fallback:
<video>
<source [src]="attachmentVideo" type='video/mp4; codecs="avc1.4D401E, mp4a.40.2"'>
Browser does not support the HTML5 Video element.
</video>
However I learned that the fallback only works if the browser does not support video. So in the case of Chrome which does support video, the fallback is not executed, but the video will fail to play.
Next up I tried to listen for the error event
<video (error)="onError($event)">
<source [src]="attachmentVideo" type='video/mp4; codecs="avc1.4D401E, mp4a.40.2"'>
Browser does not support the HTML5 Video element.
</video>
However I am also not seeing the error event.
For fun I attached the onplay event as well to make sure that events are firing. This event does fire.
<video (canplay)="onCanPlay($event)">
<source [src]="attachmentVideo" type='video/mp4; codecs="avc1.4D401E, mp4a.40.2"'>
Your browser does not support the HTML5 Video element.
</video>
Is it possible without an external library to detect if the browser supports a video and specific codec?
Solution 1:[1]
For checking if a codec is supported via JavaScript, see if the options below are useful to you:
(ie: convert your chosen one into Angular syntax/code)
(1) If codec is known... Use canPlayType to check codec compatibility.
var vid = document.getElementById("myVideoElementID");
if( vid.canPlayType('video/mp4; codecs="hev1"') )
{ alert("can play HVC1 / H.265 "); }
else if( vid.canPlayType('video/mp4; codecs="avc1"') )
{ alert("can play AVC1 / H.264 "); }
(2) If codec is unknown... Check file bytes for codec type (then do step (1) to confirm playability)
This means getting a chunk of the file's bytes (for example getting the first 64kb or last 64kb, depends if MP4 header is at front or back of file).
Read the data into an Array and then search that Array for the stsd section, which is where MP4 keeps the Sequence Parameter Set (SPS) of the H264 data, from that section you can get extract the codec info.
a) Find stsd position.
b) From that position, skip +16 bytes to find either avc1 or hev1.
c) From the +16 pos, skip forward from here +91 to get the SPS (3 bytes).
Here is an example using a selected (local) file which is read into an Array via FileReader API.
<!DOCTYPE html>
<html>
<body>
<!-- button Choose MP4 Video -->
<div style="z-index: 1; overflow:hidden; position: absolute; top: 18px; left: 10px; " >
<text> <b> Choose a Video (.MP4) file...</b> <br>
<input type="file" id="choose_video" accept=".mp4" />
</div>
<video id="myVideo" width="640" height="480" controls style="position: absolute; top: 80px; left: 10px; " >
<source src="" type="video/mp4">
</video>
</body>
<script>
var temp_int = 0; var temp_str = ""; var temp_arr = [];
var reader; var path;
var fileBytes; //# is updated to: uint8Array []
var file; //# a File object (using Reader)
//# codec name (is String object)
var str_codec = "-1";
var myvid = document.getElementById( 'myVideo' );
addEventListener("load", on_page_Ready );
function on_page_Ready()
{
//# listener for selecting MP4 file
document.getElementById('choose_video').addEventListener('change', onSelectFile, false);
}
function onSelectFile(evt)
{
file = evt.target.files[0]; //# FileList object
path = (window.URL || window.webkitURL).createObjectURL(file);
reader = new FileReader();
reader.readAsArrayBuffer(file);
reader.onload = function(evt)
{
if (evt.target.readyState == FileReader.DONE)
{
fileBytes = new Uint8Array( evt.target.result );
//# extract codec info
get_MP4_Codec( fileBytes );
//# load video data and play ...
myvid.setAttribute("src", path);
myvid.load();
myvid.play();
}
}
}
function get_MP4_Codec (inBA)
{
let idx = 0; //# index (position) in bytes
while(true)
{
//# auto stop if "stsd" not found after 32 kilobytes
if ( (idx > 32000) || (idx >= inBA.length-1) ) { break; }
//# look for starting "s" (is byte value 0x73)
if( inBA[ idx ] == 0x73 ) //# find "s"
{
//# check if next following 3 bytes are the required values
if( (inBA[ idx+1 ] == 0x74) && //# find "t"
(inBA[ idx+2 ] == 0x73) && //# find "s"
(inBA[ idx+3 ] == 0x64) //# find "d"
)
{
//# when found...
//# note the "stsd" position
temp_int = idx;
//# skip forward by 16 bytes to get codec type
//# codec type can be "avc1" or "hev1" or "hvc1"
idx += 16;
str_codec = String.fromCharCode( inBA[ idx+0 ] );
str_codec += String.fromCharCode( inBA[ idx+1 ] );
str_codec += String.fromCharCode( inBA[ idx+2 ] );
str_codec += String.fromCharCode( inBA[ idx+3 ] );
//# need that dot "." also
str_codec += ".";
//# skip forward by 91 bytes to get codec SPS details
//# example"avc1.64001f" the SPS is "64001F"
idx += 91;
temp_str = (inBA[idx].toString(16)).toUpperCase();
str_codec += temp_str.length === 2 ? temp_str : '0' + temp_str;
idx += 1;
temp_str = (inBA[idx].toString(16)).toUpperCase();
str_codec += temp_str.length === 2 ? temp_str : '0' + temp_str;
idx += 1;
temp_str = (inBA[idx].toString(16)).toUpperCase();
str_codec += temp_str.length === 2 ? temp_str : '0' + temp_str;
break;
}
}
idx++;
}
alert("found STSD @ byte pos : " + temp_int +"\n"+ "codec type : " + str_codec );
}
</script>
</html>
Sources
This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.
Source: Stack Overflow
| Solution | Source |
|---|---|
| Solution 1 | VC.One |
