2015-12-15 22:30:14 -07:00
<!DOCTYPE html>
< head >
< style >
header {
text-align: center;
}
#controls
{
width: 70%;
min-width: 615px;
padding: 3px;
margin: 0px auto 20px auto;
border: 1px solid #606060;
overflow: hidden;
}
.innerControls
{
display:block;
float: left;
width: 99%;
margin: 3px;
padding-left: 3px;
font-size: 8pt
}
.videoCentered
{
width: 720px;
margin-left: auto;
margin-right: auto;
display: block
}
.center
{
width: 70%;
min-width: 615px;
overflow: hidden;
margin-left: auto;
margin-right: auto;
display: block
}
#customButtons input { width: 25%; display : inline-block; text-align: center; font-size: 8pt;}
#toggleButtons button { width: 24%; display : inline-block; text-align: center; font-size: 8pt; background-color: #A0A0A0 }
< / style >
< title > hls.js demo< / title >
< link rel = "icon" type = "image/png" href = "http://static1.dmcdn.net/images/favicon-32x32.png" sizes = "32x32" / >
< link rel = "icon" type = "image/png" href = "http://static1.dmcdn.net/images/favicon-16x16.png" sizes = "16x16" / >
< script src = "https://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js" > < / script >
< link rel = "stylesheet" href = "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap.min.css" >
< link rel = "stylesheet" href = "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/css/bootstrap-theme.min.css" >
< script src = "https://maxcdn.bootstrapcdn.com/bootstrap/3.3.4/js/bootstrap.min.js" > < / script >
< / head >
< body >
< div class = "header-container" >
< header class = "wrapper clearfix" >
< h1 class = "title" > < a href = "https://github.com/dailymotion/hls.js" > hls.js< / a > demo page< / h1 >
< / header >
< / div >
< div class = "main-container" >
< header >
< p >
test with your HLS streams below in Chrome, Firefox, IE11 or Safari !
< br > Advanced controls are also available at the bottom of this page.
< / p >
< / header >
< div id = "controls" >
< div id = "customButtons" > < / div >
< select id = "streamSelect" class = "innerControls" > < option value = "" selected > (Enter custom URL below)< / option > < / select >
< input id = "streamURL" class = "innerControls" type = text value = "" / >
2015-12-23 10:46:01 -07:00
< label class = "innerControls" > < input id = "enableStreaming" type = checkbox checked / > Enable Streaming< / label >
2015-12-15 22:30:14 -07:00
< label class = "innerControls" > < input id = "autoRecoverError" type = checkbox checked / > Auto-Recover Media Error< / label >
2015-12-23 10:46:01 -07:00
< label class = "innerControls" > < input id = "enableWorker" type = checkbox checked / > Enable Worker< / label >
2016-01-13 13:58:12 -07:00
< label class = "innerControls" > Level Capping < input id = "levelCapping" type = number/ > < / label >
2016-02-04 11:19:10 -07:00
< label class = "innerControls" > default Audio Codec < input id = "defaultAudioCodec" / > < / label >
2015-12-15 22:30:14 -07:00
< div id = "StreamPermalink" class = "innerControls" > < / div >
< div >
< select id = "videoSize" style = "float:left" >
< option value = "240" > player size: tiny (240p)< / option >
< option value = "384" > player size: small (384p)< / option >
< option value = "480" > player size: medium (480p)< / option >
< option value = "720" selected > player size: large (720p)< / option >
< option value = "1080" > player size: huge (1080p)< / option >
< / select >
< span id = "currentResolution" style = "float:right;font-size: 8pt;" > -< / span >
< / div >
< / div >
< video id = "video" controls autoplay class = "videoCentered" > < / video > < br >
< canvas id = "buffered_c" height = "15" class = "videoCentered" onclick = "buffered_seek(event);" > < / canvas > < br > < br >
< pre id = "HlsStatus" class = "center" > < / pre >
< div class = "center" id = "toggleButtons" >
< button type = "button" class = "btn btn-sm" onclick = "$('#PlaybackControl').toggle();" > toggle playback controls< / button >
< button type = "button" class = "btn btn-sm" onclick = "$('#QualityLevelControl').toggle();" > toggle Quality Level controls< / button >
< button type = "button" class = "btn btn-sm" onclick = "$('#MetricsDisplay').toggle();toggleMetricsDisplay();" > toggle Metrics Display< / button >
< button type = "button" class = "btn btn-sm" onclick = "$('#StatsDisplay').toggle();" > toggle Stats Display< / button >
< / div >
< div id = 'PlaybackControl' >
< h4 > Playback Control < / h4 >
< button type = "button" class = "btn btn-sm btn-info" onclick = "$('#video')[0].play()" > play< / button >
< button type = "button" class = "btn btn-sm btn-info" onclick = "$('#video')[0].pause()" > pause< / button >
< button type = "button" class = "btn btn-sm btn-info" onclick = "$('#video')[0].currentTime+=10" > currentTime+=10< / button >
< button type = "button" class = "btn btn-sm btn-info" onclick = "$('#video')[0].currentTime-=10" > currentTime-=10< / button >
< button type = "button" class = "btn btn-sm btn-info" onclick = "$('#video')[0].currentTime=$('#seek_pos').val()" > seek to < / button >
< input type = "text" id = 'seek_pos' size = "8" onkeydown = "if(window.event.keyCode=='13'){$('#video')[0].currentTime=$('#seek_pos').val();}" > < br > < br >
< button type = "button" class = "btn btn-xs btn-warning" onclick = "hls.attachMedia($('#video')[0])" > attach Video< / button >
< button type = "button" class = "btn btn-xs btn-warning" onclick = "hls.detachVideo()" > detach Video< / button > < br >
< button type = "button" class = "btn btn-xs btn-warning" onclick = "hls.startLoad()" > recover Network Error< / button >
< button type = "button" class = "btn btn-xs btn-warning" onclick = "hls.recoverMediaError()" > recover Media Error< / button > < br >
< / div >
< div id = 'QualityLevelControl' >
< h4 > Quality Control < / h4 >
< table >
< tr >
< td > current level< / td >
< td width = 10px > < / td >
< td > < div id = "currentLevelControl" style = "display: inline;" > < / div > < / td >
< / tr >
< tr >
< td > < p > next level< / p > < / td >
< td > < / td >
< td > < div id = "nextLevelControl" style = "display: inline;" > < / div > < / td >
< / tr >
< tr >
< td > < p > load level< / p > < / td >
< td > < / td >
< td > < div id = "loadLevelControl" style = "display: inline;" > < / div > < / td >
< / tr >
< tr >
< td > < p > cap level< / p > < / td >
< td > < / td >
< td > < div id = "levelCappingControl" style = "display: inline;" > < / div > < / td >
< / tr >
< / table >
< / div >
< div id = 'MetricsDisplay' >
< h4 > Real Time Metrics Display < / h4 >
< div id = "metricsButton" >
< button type = "button" class = "btn btn-xs btn-info" onclick = "$('#metricsButtonWindow').toggle();$('#metricsButtonFixed').toggle();windowSliding=!windowSliding; refreshCanvas()" > toggle sliding/fixed window< / button > < br >
< div id = "metricsButtonWindow" >
< button type = "button" class = "btn btn-xs btn-info" onclick = "timeRangeSetSliding(0)" > window ALL< / button >
< button type = "button" class = "btn btn-xs btn-info" onclick = "timeRangeSetSliding(2000)" > 2s< / button >
< button type = "button" class = "btn btn-xs btn-info" onclick = "timeRangeSetSliding(5000)" > 5s< / button >
< button type = "button" class = "btn btn-xs btn-info" onclick = "timeRangeSetSliding(10000)" > 10s< / button >
< button type = "button" class = "btn btn-xs btn-info" onclick = "timeRangeSetSliding(20000)" > 20s< / button >
< button type = "button" class = "btn btn-xs btn-info" onclick = "timeRangeSetSliding(30000)" > 30s< / button >
< button type = "button" class = "btn btn-xs btn-info" onclick = "timeRangeSetSliding(60000)" > 60s< / button >
< button type = "button" class = "btn btn-xs btn-info" onclick = "timeRangeSetSliding(120000)" > 120s< / button > < br >
< button type = "button" class = "btn btn-xs btn-info" onclick = "timeRangeZoomIn()" > Window Zoom In< / button >
< button type = "button" class = "btn btn-xs btn-info" onclick = "timeRangeZoomOut()" > Window Zoom Out< / button > < br >
< button type = "button" class = "btn btn-xs btn-info" onclick = "timeRangeSlideLeft()" > < < < Window Slide < / button >
< button type = "button" class = "btn btn-xs btn-info" onclick = "timeRangeSlideRight()" > Window Slide >>> < / button > < br >
< / div >
< div id = "metricsButtonFixed" >
< button type = "button" class = "btn btn-xs btn-info" onclick = "windowStart=$('#windowStart').val()" > fixed window start(ms)< / button >
< input type = "text" id = 'windowStart' defaultValue = "0" size = "8" onkeydown = "if(window.event.keyCode=='13'){windowStart=$('#windowStart').val();}" >
< button type = "button" class = "btn btn-xs btn-info" onclick = "windowEnd=$('#windowEnd').val()" > fixed window end(ms)< / button >
< input type = "text" id = 'windowEnd' defaultValue = "10000" size = "8" onkeydown = "if(window.event.keyCode=='13'){windowEnd=$('#windowEnd').val();}" > < br >
< / div >
< button type = "button" class = "btn btn-xs btn-success" onclick = "goToMetrics()" style = "font-size:18px" > metrics link< / button >
< button type = "button" class = "btn btn-xs btn-success" onclick = "goToMetricsPermaLink()" style = "font-size:18px" > metrics permalink< / button >
< button type = "button" class = "btn btn-xs btn-success" onclick = "copyMetricsToClipBoard()" style = "font-size:18px" > copy metrics to clipboard< / button >
< canvas id = "bufferTimerange_c" width = "640" height = "100" style = "border:1px solid #000000" onmousedown = "timeRangeCanvasonMouseDown(event)" onmousemove = "timeRangeCanvasonMouseMove(event)" onmouseup = "timeRangeCanvasonMouseUp(event)" onmouseout = "timeRangeCanvasonMouseOut(event);" > < / canvas >
< canvas id = "bitrateTimerange_c" width = "640" height = "100" style = "border:1px solid #000000;" > < / canvas >
< canvas id = "bufferWindow_c" width = "640" height = "100" style = "border:1px solid #000000" onmousemove = "windowCanvasonMouseMove(event);" > < / canvas >
< canvas id = "videoEvent_c" width = "640" height = "15" style = "border:1px solid #000000;" > < / canvas >
< canvas id = "loadEvent_c" width = "640" height = "15" style = "border:1px solid #000000;" > < / canvas > < br >
< / div >
< / div >
< div id = 'StatsDisplay' >
< h4 > Stats Display < / h4 >
< pre id = 'HlsStats' > < / pre >
< div id = "buffered_log" > < / div >
< / div >
< / div >
< br > < br >
< script src = "../../streams.js" > < / script >
<!-- live - reload script -->
< script src = "//localhost:8001" > < / script >
< script src = "../dist/hls.js" > < / script >
< script src = "canvas.js" > < / script >
< script src = "metrics.js" > < / script >
< script src = "jsonpack.js" > < / script >
< script >
$(document).ready(function() {
$('#streamSelect').change(function() { $('#streamURL').val($('#streamSelect').val());loadStream($('#streamURL').val());});
$('#streamURL').change(function() { loadStream($('#streamURL').val());});
$('#videoSize').change(function() { $('#video').width($('#videoSize').val()); $('#buffered_c').width($('#videoSize').val()); });
$("#PlaybackControl").hide();
$("#QualityLevelControl").hide();
$("#MetricsDisplay").hide();
$("#StatsDisplay").hide();
$('#metricsButtonWindow').toggle(windowSliding);
$('#metricsButtonFixed').toggle(!windowSliding);
2015-12-23 10:46:01 -07:00
$('#enableStreaming').click(function() { enableStreaming = this.checked; loadStream($('#streamURL').val()); });
$('#autoRecoverError').click(function() { autoRecoverError = this.checked; updatePermalink();});
$('#enableWorker').click(function() { enableWorker = this.checked; updatePermalink();});
2016-01-13 13:58:12 -07:00
$('#levelCapping').change(function() { levelCapping = this.value; updatePermalink();});
2016-02-04 11:19:10 -07:00
$('#defaultAudioCodec').change(function() { defaultAudioCodec = this.value; updatePermalink();});
2015-12-23 10:46:01 -07:00
$('#enableStreaming').prop( "checked", enableStreaming );
$('#autoRecoverError').prop( "checked", autoRecoverError );
$('#enableWorker').prop( "checked", enableWorker );
2016-01-13 13:58:12 -07:00
$('#levelCapping').val(levelCapping);
2016-02-04 11:19:10 -07:00
$('#defaultAudioCodec').val(defaultAudioCodec);
2015-12-15 22:30:14 -07:00
});
2015-12-23 10:46:01 -07:00
2015-12-15 22:30:14 -07:00
'use strict';
2015-12-23 10:46:01 -07:00
var hls,events, stats,
enableStreaming = JSON.parse(getURLParam('enableStreaming',true))
autoRecoverError = JSON.parse(getURLParam('autoRecoverError',true)),
2016-02-04 11:19:10 -07:00
enableWorker = JSON.parse(getURLParam('enableWorker',true)),
levelCapping = JSON.parse(getURLParam('levelCapping',-1)),
defaultAudioCodec = getURLParam('defaultAudioCodec',undefined);
2015-12-15 22:30:14 -07:00
var video = $('#video')[0];
video.volume = 0.05;
2015-12-23 10:46:01 -07:00
loadStream(decodeURIComponent(getURLParam('src','http://www.streambox.fr/playlists/x36xhzz/x36xhzz.m3u8')));
2015-12-15 22:30:14 -07:00
function loadStream(url) {
hideCanvas();
if(Hls.isSupported()) {
if(hls) {
hls.destroy();
if(hls.bufferTimer) {
clearInterval(hls.bufferTimer);
hls.bufferTimer = undefined;
}
hls = null;
}
$('#streamURL').val(url);
2015-12-23 10:46:01 -07:00
updatePermalink();
2015-12-15 22:30:14 -07:00
if(!enableStreaming) {
$("#HlsStatus").text("Streaming disabled");
return;
}
$("#HlsStatus").text('loading ' + url);
events = { url : url, t0 : performance.now(), load : [], buffer : [], video : [], level : [], bitrate : []};
recoverDecodingErrorDate = recoverSwapAudioCodecDate = null;
2016-02-04 11:19:10 -07:00
hls = new Hls({debug:true, enableWorker : enableWorker, defaultAudioCodec : defaultAudioCodec});
2015-12-15 22:30:14 -07:00
$("#HlsStatus").text('loading manifest and attaching video element...');
hls.loadSource(url);
2016-01-13 13:58:12 -07:00
hls.autoLevelCapping = levelCapping;
2015-12-15 22:30:14 -07:00
hls.attachMedia(video);
hls.on(Hls.Events.MEDIA_ATTACHED,function() {
$("#HlsStatus").text('MediaSource attached...');
bufferingIdx = -1;
events.video.push({time : performance.now() - events.t0, type : "Media attached"});
});
hls.on(Hls.Events.MEDIA_DETACHED,function() {
$("#HlsStatus").text('MediaSource detached...');
bufferingIdx = -1;
events.video.push({time : performance.now() - events.t0, type : "Media detached"});
});
hls.on(Hls.Events.FRAG_PARSING_INIT_SEGMENT,function(event,data) {
showCanvas();
var event = {time : performance.now() - events.t0, type : "init segment"};
events.video.push(event);
});
hls.on(Hls.Events.FRAG_PARSING_METADATA, function(event, data) {
console.log("Id3 samples ", data.samples);
});
hls.on(Hls.Events.LEVEL_SWITCH,function(event,data) {
events.level.push({time : performance.now() - events.t0, id : data.level, bitrate : Math.round(hls.levels[data.level].bitrate/1000)});
updateLevelInfo();
});
hls.on(Hls.Events.MANIFEST_PARSED,function(event,data) {
var event = {
type : "manifest",
name : "",
start : 0,
end : data.levels.length,
time : data.stats.trequest - events.t0,
latency : data.stats.tfirst - data.stats.trequest,
load : data.stats.tload - data.stats.tfirst,
duration : data.stats.tload - data.stats.tfirst,
};
events.load.push(event);
refreshCanvas();
});
hls.on(Hls.Events.MANIFEST_PARSED,function(event,data) {
$("#HlsStatus").text("manifest successfully loaded," + hls.levels.length + " levels found");
stats = {levelNb: data.levels.length};
updateLevelInfo();
});
hls.on(Hls.Events.LEVEL_LOADED,function(event,data) {
events.isLive = data.details.live;
var event = {
type : "level",
id : data.level,
start : data.details.startSN,
end : data.details.endSN,
time : data.stats.trequest - events.t0,
latency : data.stats.tfirst - data.stats.trequest,
load : data.stats.tload - data.stats.tfirst,
parsing : data.stats.tparsed - data.stats.tload,
duration : data.stats.tload - data.stats.tfirst
};
events.load.push(event);
refreshCanvas();
});
hls.on(Hls.Events.FRAG_BUFFERED,function(event,data) {
var event = {
type : "fragment",
id : data.frag.level,
id2 : data.frag.sn,
time : data.stats.trequest - events.t0,
latency : data.stats.tfirst - data.stats.trequest,
load : data.stats.tload - data.stats.tfirst,
parsing : data.stats.tparsed - data.stats.tload,
buffer : data.stats.tbuffered - data.stats.tparsed,
duration : data.stats.tbuffered - data.stats.tfirst,
bw : Math.round(8*data.stats.length/(data.stats.tbuffered - data.stats.tfirst)),
size : data.stats.length
};
events.load.push(event);
events.bitrate.push({time : performance.now() - events.t0, bitrate : event.bw , duration : data.frag.duration, level : event.id});
if(hls.bufferTimer === undefined) {
events.buffer.push({ time : 0, buffer : 0, pos: 0});
hls.bufferTimer = window.setInterval(checkBuffer, 100);
}
refreshCanvas();
updateLevelInfo();
var latency = data.stats.tfirst - data.stats.trequest, process = data.stats.tbuffered - data.stats.trequest, bitrate = Math.round(8 * data.stats.length / (data.stats.tbuffered - data.stats.tfirst));
if (stats.fragBuffered) {
stats.fragMinLatency = Math.min(stats.fragMinLatency, latency);
stats.fragMaxLatency = Math.max(stats.fragMaxLatency, latency);
stats.fragMinProcess = Math.min(stats.fragMinProcess, process);
stats.fragMaxProcess = Math.max(stats.fragMaxProcess, process);
stats.fragMinKbps = Math.min(stats.fragMinKbps, bitrate);
stats.fragMaxKbps = Math.max(stats.fragMaxKbps, bitrate);
stats.autoLevelCappingMin = Math.min(stats.autoLevelCappingMin, hls.autoLevelCapping);
stats.autoLevelCappingMax = Math.max(stats.autoLevelCappingMax, hls.autoLevelCapping);
stats.fragBuffered++;
} else {
stats.fragMinLatency = stats.fragMaxLatency = latency;
stats.fragMinProcess = stats.fragMaxProcess = process;
stats.fragMinKbps = stats.fragMaxKbps = bitrate;
stats.fragBuffered = 1;
stats.fragBufferedBytes = 0;
stats.autoLevelCappingMin = stats.autoLevelCappingMax = hls.autoLevelCapping;
this.sumLatency = 0;
this.sumKbps = 0;
this.sumProcess = 0;
}
stats.fraglastLatency = latency;
this.sumLatency += latency;
stats.fragAvgLatency = Math.round(this.sumLatency / stats.fragBuffered);
stats.fragLastProcess = process;
this.sumProcess += process;
stats.fragAvgProcess = Math.round(this.sumProcess / stats.fragBuffered);
stats.fragLastKbps = bitrate;
this.sumKbps += bitrate;
stats.fragAvgKbps = Math.round(this.sumKbps / stats.fragBuffered);
stats.fragBufferedBytes += data.stats.length;
stats.autoLevelCappingLast = hls.autoLevelCapping;
});
hls.on(Hls.Events.FRAG_CHANGED,function(event,data) {
var event = {time : performance.now() - events.t0, type : 'frag changed', name : data.frag.sn + ' @ ' + data.frag.level };
events.video.push(event);
refreshCanvas();
updateLevelInfo();
var level = data.frag.level, autoLevel = data.frag.autoLevel;
if (stats.levelStart === undefined) {
stats.levelStart = level;
}
if (autoLevel) {
if (stats.fragChangedAuto) {
stats.autoLevelMin = Math.min(stats.autoLevelMin, level);
stats.autoLevelMax = Math.max(stats.autoLevelMax, level);
stats.fragChangedAuto++;
if (this.levelLastAuto & & level !== stats.autoLevelLast) {
stats.autoLevelSwitch++;
}
} else {
stats.autoLevelMin = stats.autoLevelMax = level;
stats.autoLevelSwitch = 0;
stats.fragChangedAuto = 1;
this.sumAutoLevel = 0;
}
this.sumAutoLevel += level;
stats.autoLevelAvg = Math.round(1000 * this.sumAutoLevel / stats.fragChangedAuto) / 1000;
stats.autoLevelLast = level;
} else {
if (stats.fragChangedManual) {
stats.manualLevelMin = Math.min(stats.manualLevelMin, level);
stats.manualLevelMax = Math.max(stats.manualLevelMax, level);
stats.fragChangedManual++;
if (!this.levelLastAuto & & level !== stats.manualLevelLast) {
stats.manualLevelSwitch++;
}
} else {
stats.manualLevelMin = stats.manualLevelMax = level;
stats.manualLevelSwitch = 0;
stats.fragChangedManual = 1;
}
stats.manualLevelLast = level;
}
this.levelLastAuto = autoLevel;
});
hls.on(Hls.Events.FRAG_LOAD_EMERGENCY_ABORTED,function(event,data) {
if (stats) {
if (stats.fragLoadEmergencyAborted === undefined) {
stats.fragLoadEmergencyAborted = 1;
} else {
stats.fragLoadEmergencyAborted++;
}
}
});
hls.on(Hls.Events.ERROR, function(event,data) {
switch(data.details) {
case Hls.ErrorDetails.MANIFEST_LOAD_ERROR:
try {
$("#HlsStatus").html("cannot Load < a href = \"" + data . url + " \ " > " + url + "< / a > < br > HTTP response code:" + data.response.status + "< br > " + data.response.statusText);
if(data.response.status === 0) {
$("#HlsStatus").append("this might be a CORS issue, consider installing < a href = \"https://chrome.google.com/webstore/detail/allow-control-allow-origi/nlfbmbojpeacfghkpbjhddihlkkiljbi\" > Allow-Control-Allow-Origin< / a > Chrome Extension");
}
} catch(err) {
$("#HlsStatus").html("cannot Load < a href = \"" + data . url + " \ " > " + url + "< / a > < br > Reason:Load " + data.event.type);
}
break;
2016-01-13 13:58:12 -07:00
case Hls.ErrorDetails.MANIFEST_LOAD_TIMEOUT:
$("#HlsStatus").text("timeout while loading manifest");
break;
case Hls.ErrorDetails.MANIFEST_PARSING_ERROR:
$("#HlsStatus").text("error while parsing manifest:" + data.reason);
break;
2015-12-15 22:30:14 -07:00
case Hls.ErrorDetails.LEVEL_LOAD_ERROR:
2016-01-13 13:58:12 -07:00
$("#HlsStatus").text("error while loading level playlist");
break;
case Hls.ErrorDetails.LEVEL_LOAD_TIMEOUT:
$("#HlsStatus").text("timeout while loading level playlist");
2015-12-15 22:30:14 -07:00
break;
case Hls.ErrorDetails.LEVEL_SWITCH_ERROR:
$("#HlsStatus").text("error while trying to switch to level " + data.level);
break;
case Hls.ErrorDetails.FRAG_LOAD_ERROR:
2016-01-13 13:58:12 -07:00
$("#HlsStatus").text("error while loading fragment " + data.frag.url);
2015-12-15 22:30:14 -07:00
break;
2016-01-13 13:58:12 -07:00
case Hls.ErrorDetails.FRAG_LOAD_TIMEOUT:
$("#HlsStatus").text("timeout while loading fragment " + data.frag.url);
break;
case Hls.ErrorDetails.FRAG_LOOP_LOADING_ERROR:
$("#HlsStatus").text("Frag Loop Loading Error");
2015-12-15 22:30:14 -07:00
break;
case Hls.ErrorDetails.FRAG_DECRYPT_ERROR:
$("#HlsStatus").text("Decrypting Error:" + data.reason);
break;
case Hls.ErrorDetails.FRAG_PARSING_ERROR:
$("#HlsStatus").text("Parsing Error:" + data.reason);
break;
2016-01-13 13:58:12 -07:00
case Hls.ErrorDetails.KEY_LOAD_ERROR:
$("#HlsStatus").text("error while loading key " + data.frag.decryptdata.uri);
break;
case Hls.ErrorDetails.KEY_LOAD_TIMEOUT:
$("#HlsStatus").text("timeout while loading key " + data.frag.decryptdata.uri);
break;
2015-12-15 22:30:14 -07:00
case Hls.ErrorDetails.BUFFER_APPEND_ERROR:
$("#HlsStatus").text("Buffer Append Error");
break;
case Hls.ErrorDetails.BUFFER_APPENDING_ERROR:
$("#HlsStatus").text("Buffer Appending Error");
break;
default:
break;
}
if(data.fatal) {
switch(data.type) {
case Hls.ErrorTypes.MEDIA_ERROR:
handleMediaError();
break;
case Hls.ErrorTypes.NETWORK_ERROR:
$("#HlsStatus").append(",network error ...");
break;
default:
$("#HlsStatus").append(", unrecoverable error");
hls.destroy();
break;
}
2015-12-23 10:46:01 -07:00
console.log($("#HlsStatus").text());
2015-12-15 22:30:14 -07:00
}
if(!stats) stats = {};
// track all errors independently
if (stats[data.details] === undefined) {
stats[data.details] = 1;
} else {
stats[data.details] += 1;
}
// track fatal error
if (data.fatal) {
if (stats.fatalError === undefined) {
stats.fatalError = 1;
} else {
stats.fatalError += 1;
}
}
$("#HlsStats").text(JSON.stringify(sortObject(stats),null,"\t"));
});
hls.on(Hls.Events.FPS_DROP,function(event,data) {
var evt = {time : performance.now() - events.t0, type : "frame drop", name : data.currentDropped + "/" + data.currentDecoded};
events.video.push(evt);
if (stats) {
if (stats.fpsDropEvent === undefined) {
stats.fpsDropEvent = 1;
} else {
stats.fpsDropEvent++;
}
stats.fpsTotalDroppedFrames = data.totalDroppedFrames;
}
});
video.addEventListener('resize', handleVideoEvent);
video.addEventListener('seeking', handleVideoEvent);
video.addEventListener('seeked', handleVideoEvent);
video.addEventListener('pause', handleVideoEvent);
video.addEventListener('play', handleVideoEvent);
video.addEventListener('canplay', handleVideoEvent);
video.addEventListener('canplaythrough', handleVideoEvent);
video.addEventListener('ended', handleVideoEvent);
video.addEventListener('playing', handleVideoEvent);
video.addEventListener('error', handleVideoEvent);
video.addEventListener('loadedmetadata', handleVideoEvent);
video.addEventListener('loadeddata', handleVideoEvent);
video.addEventListener('durationchange', handleVideoEvent);
} else {
if(navigator.userAgent.toLowerCase().indexOf('firefox') !== -1) {
$("#HlsStatus").text("you are using Firefox, it looks like MediaSource is not enabled,< br > please ensure the following keys are set appropriately in < b > about:config< / b > < br > media.mediasource.enabled=true< br > media.mediasource.mp4.enabled=true< br > < b > media.mediasource.whitelist=false< / b > ");
} else {
$("#HlsStatus").text("your Browser does not support MediaSourceExtension / MP4 mediasource");
}
}
}
var lastSeekingIdx, lastStartPosition,lastDuration;
function handleVideoEvent(evt) {
var data = '';
switch(evt.type) {
case 'durationchange':
if(evt.target.duration - lastDuration < = 0.5) {
// some browsers reports several duration change events with almost the same value ... avoid spamming video events
return;
}
lastDuration = evt.target.duration;
data = Math.round(evt.target.duration*1000);
break;
case 'resize':
data = evt.target.videoWidth + '/' + evt.target.videoHeight;
break;
case 'loadedmetadata':
// data = 'duration:' + evt.target.duration + '/videoWidth:' + evt.target.videoWidth + '/videoHeight:' + evt.target.videoHeight;
// break;
case 'loadeddata':
case 'canplay':
case 'canplaythrough':
case 'ended':
case 'seeking':
case 'seeked':
case 'play':
case 'playing':
lastStartPosition = evt.target.currentTime;
case 'pause':
case 'waiting':
case 'stalled':
case 'error':
data = Math.round(evt.target.currentTime*1000);
if(evt.type === 'error') {
var errorTxt,mediaError=evt.currentTarget.error;
switch(mediaError.code) {
case mediaError.MEDIA_ERR_ABORTED:
errorTxt = "You aborted the video playback";
break;
case mediaError.MEDIA_ERR_DECODE:
errorTxt = "The video playback was aborted due to a corruption problem or because the video used features your browser did not support";
handleMediaError();
break;
case mediaError.MEDIA_ERR_NETWORK:
errorTxt = "A network error caused the video download to fail part-way";
break;
case mediaError.MEDIA_ERR_SRC_NOT_SUPPORTED:
errorTxt = "The video could not be loaded, either because the server or network failed or because the format is not supported";
break;
}
$("#HlsStatus").text(errorTxt);
console.error(errorTxt);
}
break;
// case 'progress':
// data = 'currentTime:' + evt.target.currentTime + ',bufferRange:[' + this.video.buffered.start(0) + ',' + this.video.buffered.end(0) + ']';
// break;
default:
break;
}
var event = {time : performance.now() - events.t0, type : evt.type, name : data};
events.video.push(event);
if(evt.type === 'seeking') {
lastSeekingIdx = events.video.length-1;
}
if(evt.type === 'seeked') {
events.video[lastSeekingIdx].duration = event.time - events.video[lastSeekingIdx].time;
}
}
var recoverDecodingErrorDate,recoverSwapAudioCodecDate;
function handleMediaError() {
if(autoRecoverError) {
var now = performance.now();
if(!recoverDecodingErrorDate || (now - recoverDecodingErrorDate) > 3000) {
recoverDecodingErrorDate = performance.now();
$("#HlsStatus").append(",try to recover media Error ...");
hls.recoverMediaError();
} else {
if(!recoverSwapAudioCodecDate || (now - recoverSwapAudioCodecDate) > 3000) {
recoverSwapAudioCodecDate = performance.now();
$("#HlsStatus").append(",try to swap Audio Codec and recover media Error ...");
hls.swapAudioCodec();
hls.recoverMediaError();
} else {
$("#HlsStatus").append(",cannot recover, last media error recovery failed ...");
}
}
}
}
function timeRangesToString(r) {
var log = "";
for (var i=0; i< r.length ; i + + ) {
log += "[" + r.start(i) + "," + r.end(i) + "]";
}
return log;
}
var bufferingIdx = -1;
function checkBuffer() {
var v = $('#video')[0];
var canvas = $('#buffered_c')[0];
var ctx = canvas.getContext('2d');
var r = v.buffered;
var bufferingDuration;
ctx.fillStyle = "black";
ctx.fillRect(0,0,canvas.width,canvas.height);
ctx.fillStyle = "gray";
if (r) {
if(!canvas.width || canvas.width !== v.clientWidth) {
canvas.width = v.clientWidth;
}
var pos = v.currentTime,bufferLen;
for (var i=0, bufferLen=0; i< r.length ; i + + ) {
var start = r.start(i)/v.duration * canvas.width;
var end = r.end(i)/v.duration * canvas.width;
ctx.fillRect(start, 3, Math.max(2, end-start), 10);
if(pos >= r.start(i) & & pos < r.end ( i ) ) {
// play position is inside this buffer TimeRange, retrieve end of buffer position and buffer length
bufferLen = r.end(i) - pos;
}
}
// check if we are in buffering / or playback ended state
if(bufferLen < = 0.1 & & v.paused === false & & (pos-lastStartPosition) > 0.5) {
// don't create buffering event if we are at the end of the playlist, don't report ended for live playlist
if(lastDuration -pos < = 0.5 & & events.isLive === false) {
} else {
// we are not at the end of the playlist ... real buffering
if(bufferingIdx !== -1) {
bufferingDuration = performance.now() - events.t0 - events.video[bufferingIdx].time;
events.video[bufferingIdx].duration = bufferingDuration;
events.video[bufferingIdx].name = bufferingDuration;
} else {
events.video.push({ type : 'buffering' , time : performance.now() - events.t0 });
// we are in buffering state
bufferingIdx = events.video.length-1;
}
}
}
if(bufferLen > 0.1 & & bufferingIdx !=-1) {
bufferingDuration = performance.now() - events.t0 - events.video[bufferingIdx].time;
events.video[bufferingIdx].duration = bufferingDuration;
events.video[bufferingIdx].name = bufferingDuration;
// we are out of buffering state
bufferingIdx = -1;
}
// update buffer/position for current Time
var event = { time : performance.now() - events.t0, buffer : Math.round(bufferLen*1000), pos: Math.round(pos*1000)};
var bufEvents = events.buffer, bufEventLen = bufEvents.length;
if(bufEventLen > 1) {
var event0 = bufEvents[bufEventLen-2],event1 = bufEvents[bufEventLen-1];
var slopeBuf0 = (event0.buffer - event1.buffer)/(event0.time-event1.time);
var slopeBuf1 = (event1.buffer - event.buffer)/(event1.time-event.time);
var slopePos0 = (event0.pos - event1.pos)/(event0.time-event1.time);
var slopePos1 = (event1.pos - event.pos)/(event1.time-event.time);
// compute slopes. if less than 30% difference, remove event1
if((slopeBuf0 === slopeBuf1 || Math.abs(slopeBuf0/slopeBuf1 -1) < = 0.3) & &
(slopePos0 === slopePos1 || Math.abs(slopePos0/slopePos1 -1) < = 0.3))
{
bufEvents.pop();
}
}
events.buffer.push(event);
refreshCanvas();
var log = "Duration:"
+ v.duration + "< br > "
+ "Buffered:"
+ timeRangesToString(v.buffered) + "< br > "
+ "Seekable:"
+ timeRangesToString(v.seekable) + "< br > "
+ "Played:"
2016-01-18 12:07:26 -07:00
+ timeRangesToString(v.played) + "< br > ";
var videoPlaybackQuality = v.getVideoPlaybackQuality;
if(videoPlaybackQuality & & typeof(videoPlaybackQuality) === typeof(Function)) {
log+="Dropped Frames:"+ v.getVideoPlaybackQuality().droppedVideoFrames + "< br > ";
log+="Corrupted Frames:"+ v.getVideoPlaybackQuality().corruptedVideoFrames + "< br > ";
} else if(v.webkitDroppedFrameCount) {
log+="Dropped Frames:"+ v.webkitDroppedFrameCount + "< br > ";
}
2015-12-15 22:30:14 -07:00
$("#buffered_log").html(log);
$("#HlsStats").text(JSON.stringify(sortObject(stats),null,"\t"));
ctx.fillStyle = "blue";
var x = v.currentTime / v.duration * canvas.width;
ctx.fillRect(x, 0, 2, 15);
}
}
function sortObject(obj) {
if(typeof obj !== 'object')
return obj
var temp = {};
var keys = [];
for(var key in obj)
keys.push(key);
keys.sort();
for(var index in keys)
temp[keys[index]] = sortObject(obj[keys[index]]);
return temp;
}
function showCanvas() {
showMetrics();
$("#buffered_log").show();
$("#buffered_c").show();
}
function hideCanvas() {
hideMetrics();
$("#buffered_log").hide();
$("#buffered_c").hide();
}
function getMetrics() {
var json = JSON.stringify(events);
var jsonpacked = jsonpack.pack(json);
console.log("packing JSON from " + json.length + " to " + jsonpacked.length + " bytes");
return btoa(jsonpacked);
}
function copyMetricsToClipBoard() {
copyTextToClipboard(getMetrics());
}
function copyTextToClipboard(text) {
var textArea = document.createElement("textarea");
textArea.value = text;
document.body.appendChild(textArea);
textArea.select();
try {
var successful = document.execCommand('copy');
var msg = successful ? 'successful' : 'unsuccessful';
console.log('Copying text command was ' + msg);
} catch (err) {
console.log('Oops, unable to copy');
}
document.body.removeChild(textArea);
}
function goToMetrics() {
var url = document.URL;
url = url.substr(0,url.lastIndexOf("/")+1) + 'metrics.html';
console.log(url);
window.open(url,'_blank');
}
function goToMetricsPermaLink() {
var url = document.URL;
var b64 = getMetrics();
url = url.substr(0,url.lastIndexOf("/")+1) + 'metrics.html?data=' + b64;
console.log(url);
window.open(url,'_blank');
}
function minsecs(ts) {
var m = Math.floor(Math.floor(ts % 3600) / 60);
var s = Math.floor(ts % 60);
return m + ":" + (s < 10 ? " 0 " : " " ) + s ;
}
function buffered_seek(event) {
var canvas = $("#buffered_c")[0];
var v = $('#video')[0];
var target = (event.clientX - canvas.offsetLeft) / canvas.width * v.duration;
v.currentTime = target;
}
function updateLevelInfo() {
var button_template = '< button type = "button" class = "btn btn-sm ' ;
var button_enabled = 'btn-primary" ';
var button_disabled = 'btn-success" ';
var html1 = button_template;
if(hls.autoLevelEnabled) {
html1 += button_enabled;
} else {
html1 += button_disabled;
}
html1 += 'onclick="hls.currentLevel=-1">auto< / button > ';
var html2 = button_template;
if(hls.autoLevelEnabled) {
html2 += button_enabled;
} else {
html2 += button_disabled;
}
html2 += 'onclick="hls.loadLevel=-1">auto< / button > ';
var html3 = button_template;
if(hls.autoLevelCapping === -1) {
html3 += button_enabled;
} else {
html3 += button_disabled;
}
2016-01-13 13:58:12 -07:00
html3 += 'onclick="levelCapping=hls.autoLevelCapping=-1;updateLevelInfo();updatePermalink();">auto< / button > ';
2015-12-15 22:30:14 -07:00
var html4 = button_template;
if(hls.autoLevelEnabled) {
html4 += button_enabled;
} else {
html4 += button_disabled;
}
html4 += 'onclick="hls.nextLevel=-1">auto< / button > ';
for (var i=0; i < hls.levels.length ; i + + ) {
html1 += button_template;
if(hls.currentLevel === i) {
html1 += button_enabled;
} else {
html1 += button_disabled;
}
var levelName = i, label = level2label(i);
if(label) {
levelName += '(' + level2label(i) + ')';
}
html1 += 'onclick="hls.currentLevel=' + i + '">' + levelName + '< / button > ';
html2 += button_template;
if(hls.loadLevel === i) {
html2 += button_enabled;
} else {
html2 += button_disabled;
}
html2 += 'onclick="hls.loadLevel=' + i + '">' + levelName + '< / button > ';
html3 += button_template;
if(hls.autoLevelCapping === i) {
html3 += button_enabled;
} else {
html3 += button_disabled;
}
2016-01-13 13:58:12 -07:00
html3 += 'onclick="levelCapping=hls.autoLevelCapping=' + i + ';updateLevelInfo();updatePermalink();">' + levelName + '< / button > ';
2015-12-15 22:30:14 -07:00
html4 += button_template;
if(hls.nextLevel === i) {
html4 += button_enabled;
} else {
html4 += button_disabled;
}
html4 += 'onclick="hls.nextLevel=' + i + '">' + levelName + '< / button > ';
}
var v = $('#video')[0];
if(v.videoWidth) {
$("#currentResolution").html("video resolution:" + v.videoWidth + 'x' + v.videoHeight);
}
$("#currentLevelControl").html(html1);
$("#loadLevelControl").html(html2);
$("#levelCappingControl").html(html3);
$("#nextLevelControl").html(html4);
}
function level2label(index) {
if(hls & & hls.levels.length-1 >= index) {
var level = hls.levels[index];
if (level.name) {
return level.name;
} else {
if (level.height) {
return(level.height + 'p / ' + Math.round(level.bitrate / 1024) + 'kb');
} else {
if(level.bitrate) {
return(Math.round(level.bitrate / 1024) + 'kb');
} else {
return null;
}
}
}
}
}
2015-12-23 10:46:01 -07:00
function getURLParam(sParam, defaultValue) {
var sPageURL = window.location.search.substring(1);
var sURLVariables = sPageURL.split('&');
for (var i = 0; i < sURLVariables.length ; i + + ) {
var sParameterName = sURLVariables[i].split('=');
if (sParameterName[0] == sParam) {
return sParameterName[1];
}
}
return defaultValue;
}
function updatePermalink() {
var url = $('#streamURL').val();
2016-01-13 13:58:12 -07:00
var hlsLink = document.URL.split('?')[0] + '?src=' + encodeURIComponent(url) +
'& enableStreaming=' + enableStreaming +
'& autoRecoverError=' + autoRecoverError +
'& enableWorker=' + enableWorker +
2016-02-04 11:19:10 -07:00
'& levelCapping=' + levelCapping +
'& defaultAudioCodec=' + defaultAudioCodec;
2015-12-23 10:46:01 -07:00
var description = 'permalink: ' + "< a href = \"" + hlsLink + " \ " > " + hlsLink + "< / a > ";
$("#StreamPermalink").html(description);
}
2015-12-15 22:30:14 -07:00
< / script >
< / body >
< / html >