1
0
Fork 0
mirror of https://github.com/jellyfin/jellyfin-web synced 2025-03-30 19:56:21 +00:00

get api libs from bower

This commit is contained in:
Luke Pulverenti 2015-12-16 00:30:14 -05:00
parent def418714f
commit f36e664503
97 changed files with 16860 additions and 197 deletions

View file

@ -0,0 +1,47 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title></title>
</head>
<body>
<script src="../dist/hls.js"></script>
<video id="video" controls></video>
<script>
/* get stream from query string */
function getParameterByName(name) {
name = name.replace(/[\[]/, "\\[").replace(/[\]]/, "\\]");
var regex = new RegExp("[\\?&]" + name + "=([^&#]*)"),
results = regex.exec(location.search);
return results === null ? "" : decodeURIComponent(results[1].replace(/\+/g, " "));
}
var stream = getParameterByName('stream') || 'http://www.streambox.fr/playlists/x36xhzz/url_9/193039199_mp4_h264_aac_fhd_7.m3u8';
</script>
<script>
if(Hls.isSupported()) {
var video = document.getElementById('video');
var hls = new Hls();
hls.loadSource(stream);
hls.attachMedia(video);
hls.on(Hls.Events.MANIFEST_PARSED,function() {
video.play();
});
}
</script>
<script>
var video = document.getElementById('video');
window.onload = function(){
var i=0;
var el = document.getElementById('update');
function foo(){
i++;
el.innerHTML = 'animation:' + i+',decoded:' + video.webkitDecodedFrameCount + ',dropped:' + video.webkitDroppedFrameCount;
window.requestAnimationFrame(foo);
}
foo();
};
</script>
<div id="update"></div>
</body>
</html>

View file

@ -0,0 +1,574 @@
var eventLeftMargin = 180;
var eventRightMargin = 0;
function canvasLoadEventUpdate(canvas, minTime, maxTime, events) {
var ctx = canvas.getContext('2d');
for (var i =0, y_offset = 20; i < events.length; i++) {
var event = events[i], start = event.time, end = event.time + event.duration + event.latency;
if((start >= minTime && start <= maxTime)) {
y_offset+=20;
}
}
canvas.height = y_offset;
ctx.fillStyle = "green";
ctx.globalAlpha = 0.5;
ctx.fillRect(0,0,eventLeftMargin, canvas.height);
ctx.fillRect(canvas.width-eventRightMargin,0,eventRightMargin, canvas.height);
ctx.globalAlpha = 1;
//draw legend
var x_offset = 5;
ctx.font = "12px Arial";
legend = "load event";
ctx.fillStyle = "black";
ctx.fillText(legend,x_offset,15);
x_offset = eventLeftMargin+5;
legend = 'start - end';
ctx.fillStyle = "black";
ctx.fillText(legend,x_offset,15);
x_offset += ctx.measureText(legend).width+5;
legend = '[latency';
ctx.fillStyle = "orange";
ctx.fillText(legend,x_offset,15);
x_offset += ctx.measureText(legend).width+5;
legend = 'loading';
ctx.fillStyle = "green";
ctx.fillText(legend,x_offset,15);
x_offset += ctx.measureText(legend).width+5;
legend = 'parsing';
ctx.fillStyle = "blue";
ctx.fillText(legend,x_offset,15);
x_offset += ctx.measureText(legend).width+5;
legend = 'appending]';
ctx.fillStyle = "red";
ctx.fillText(legend,x_offset,15);
x_offset += ctx.measureText(legend).width+5;
legend = 'size bitrate';
ctx.fillStyle = "black";
ctx.fillText(legend,x_offset,15);
x_offset += ctx.measureText(legend).width+5;
for (i =0, y_offset = 20; i < events.length; i++) {
var event = events[i], start = Math.round(event.time), end = Math.round(event.time + event.duration + event.latency);
if((start >= minTime && start <= maxTime)) {
canvasDrawLoadEvent(ctx,y_offset,event,minTime,maxTime);
y_offset+=20;
}
}
}
function canvasVideoEventUpdate(canvas, minTime, maxTime, events) {
var ctx = canvas.getContext('2d');
for (var i =0, y_offset = 20; i < events.length; i++) {
var event = events[i], start = event.time, end = event.time;
if((start >= minTime && start <= maxTime)) {
y_offset+=20;
}
}
canvas.height = y_offset;
ctx.fillStyle = "green";
ctx.globalAlpha = 0.5;
ctx.fillRect(0,0,eventLeftMargin, canvas.height);
ctx.fillRect(canvas.width-eventRightMargin,0,eventRightMargin, canvas.height);
ctx.globalAlpha = 1;
//draw legend
var x_offset = 5;
ctx.font = "12px Arial";
legend = 'video event';
ctx.fillStyle = "black";
ctx.fillText(legend,x_offset,15);
x_offset = eventLeftMargin+5;
legend = 'time';
ctx.fillStyle = "black";
ctx.fillText(legend,x_offset,15);
x_offset += ctx.measureText(legend).width+5;
legend = '[duration]';
ctx.fillStyle = "blue";
ctx.fillText(legend,x_offset,15);
for (i =0, y_offset = 20; i < events.length; i++) {
var event = events[i], start = Math.round(event.time), end = Math.round(event.time);
if((start >= minTime && start <= maxTime)) {
canvasDrawVideoEvent(ctx,y_offset,event,minTime,maxTime);
y_offset+=20;
}
}
}
function canvasBufferWindowUpdate(canvas, minTime, maxTime, focusTime, events) {
var ctx = canvas.getContext('2d'),
minTimeBuffer, minTimePos,focusTimeBuffer,focusTimePos,
bufferChartStart = eventLeftMargin,
bufferChartWidth = ctx.canvas.width-eventLeftMargin-eventRightMargin;
ctx.clearRect (0,0,canvas.width, canvas.height);
if(events.length === 0) {
return;
}
ctx.fillStyle = "green";
ctx.globalAlpha = 0.5;
ctx.fillRect(0,0,eventLeftMargin, canvas.height);
ctx.globalAlpha = 1;
//draw legend
var x_offset = 5;
var y_offset = 0;
ctx.font = "15px Arial";
var maxBuffer = 0, firstEventIdx = -1, focusEventIdx= -1, event;
for (var i =0 ; i < events.length; i++) {
event = events[i];
maxBuffer = Math.max(maxBuffer, event.buffer+event.pos);
if(firstEventIdx === -1 && event.time >= minTime) {
firstEventIdx = Math.max(0,i-1);
}
if(focusEventIdx === -1 && event.time >= focusTime) {
focusEventIdx = Math.max(0,i-1);
}
}
// compute position and buffer length at pos minTime using linear approximation
if((firstEventIdx+1) < events.length) {
minTimePos = events[firstEventIdx].pos + (minTime-events[firstEventIdx].time)*(events[firstEventIdx+1].pos-events[firstEventIdx].pos)/(events[firstEventIdx+1].time-events[firstEventIdx].time);
minTimeBuffer = minTimePos + events[firstEventIdx].buffer + (minTime-events[firstEventIdx].time)*(events[firstEventIdx+1].buffer-events[firstEventIdx].buffer)/(events[firstEventIdx+1].time-events[firstEventIdx].time);
} else {
minTimeBuffer = 0;
minTimePos = 0;
}
// compute position and buffer length at pos focusTime using linear approximation
if((focusEventIdx+1) < events.length) {
focusTimePos = events[focusEventIdx].pos + (focusTime-events[focusEventIdx].time)*(events[focusEventIdx+1].pos-events[focusEventIdx].pos)/(events[focusEventIdx+1].time-events[focusEventIdx].time);
focusTimeBuffer = events[focusEventIdx].buffer + (focusTime-events[focusEventIdx].time)*(events[focusEventIdx+1].buffer-events[focusEventIdx].buffer)/(events[focusEventIdx+1].time-events[focusEventIdx].time);
} else {
focusTimePos = 0;
focusTimeBuffer = 0;
}
maxBuffer*=1.1;
y_offset += 15;
legend = 'play pos/buffer zoomed';
ctx.fillStyle = "black";
ctx.fillText(legend,x_offset,y_offset);
y_offset += 15;
legend = '[' + minTime + ',' + maxTime + ']';
ctx.fillText(legend,x_offset,y_offset);
y_offset += 15;
legend = 'focus time:' + focusTime + ' ms';
ctx.fillText(legend,x_offset,y_offset);
y_offset += 15;
legend = 'focus position:' + Math.round(focusTimePos) + ' ms';
ctx.fillText(legend,x_offset,y_offset);
y_offset += 15;
legend = 'focus buffer:' + Math.round(focusTimeBuffer) + ' ms';
ctx.fillText(legend,x_offset,y_offset);
ctx.fillStyle = "blue";
ctx.beginPath();
ctx.moveTo(bufferChartStart, ctx.canvas.height);
ctx.lineTo(bufferChartStart, ctx.canvas.height*(1 - minTimeBuffer/maxBuffer));
for (var i =firstEventIdx+1 ; i < events.length; i++) {
event = events[i];
x_offset = bufferChartStart + (bufferChartWidth*(event.time-minTime))/(maxTime-minTime);
y_offset = ctx.canvas.height*(1 - (event.buffer+event.pos)/maxBuffer);
ctx.lineTo(x_offset,y_offset);
}
ctx.lineTo(x_offset, canvas.height);
ctx.fill();
ctx.fillStyle = "brown";
ctx.beginPath();
ctx.moveTo(bufferChartStart, ctx.canvas.height);
ctx.lineTo(bufferChartStart, ctx.canvas.height*(1 - minTimePos/maxBuffer));
for (var i =firstEventIdx+1 ; i < events.length; i++) {
event = events[i];
x_offset = bufferChartStart + (bufferChartWidth*(event.time-minTime))/(maxTime-minTime);
y_offset = ctx.canvas.height*(1 - (event.pos)/maxBuffer);
ctx.lineTo(x_offset,y_offset);
}
ctx.lineTo(x_offset, canvas.height);
ctx.fill();
ctx.fillStyle = "white";
ctx.fillRect(canvas.width-eventRightMargin,0,eventRightMargin, canvas.height);
ctx.fillStyle = "green";
ctx.globalAlpha = 0.5;
ctx.fillRect(canvas.width-eventRightMargin,0,eventRightMargin, canvas.height);
ctx.globalAlpha = 1;
ctx.fillStyle = "black";
x_offset = bufferChartStart + (bufferChartWidth*(focusTime-minTime))/(maxTime-minTime);
ctx.moveTo(x_offset, ctx.canvas.height);
y_offset = ctx.canvas.height*(1 - (focusTimePos+focusTimeBuffer)/maxBuffer);
ctx.lineTo(x_offset,y_offset);
ctx.stroke();
}
function canvasBufferTimeRangeUpdate(canvas, minTime, maxTime, windowMinTime, windowMaxTime, events) {
var ctx = canvas.getContext('2d'),
bufferChartStart = eventLeftMargin,
bufferChartWidth = ctx.canvas.width-eventLeftMargin-eventRightMargin,
x_offset = 0,y_offset = 0,
event;
ctx.clearRect (0,0,canvas.width, canvas.height);
ctx.fillStyle = "green";
ctx.globalAlpha = 0.5;
ctx.fillRect(0,0,eventLeftMargin, canvas.height);
ctx.fillRect(canvas.width-eventRightMargin,0,eventRightMargin, canvas.height);
ctx.globalAlpha = 1;
x_offset = 5;
y_offset = 15;
legend = 'play pos/buffer';
ctx.fillStyle = "black";
ctx.font = "15px Arial";
ctx.fillText(legend,x_offset,y_offset);
if(events.length === 0) {
return;
}
var maxBuffer = 0;
for (var i =0 ; i < events.length; i++) {
maxBuffer = Math.max(maxBuffer, events[i].buffer + events[i].pos);
}
y_offset+=15;
legend = 'last pos:' + events[events.length-1].pos + ' ms';
ctx.fillText(legend,x_offset,y_offset);
y_offset+=15;
legend = 'last buffer:' + events[events.length-1].buffer + ' ms';
ctx.fillText(legend,x_offset,y_offset);
y_offset+=15;
legend = 'max buffer:' + maxBuffer + ' ms';
ctx.fillText(legend,x_offset,y_offset);
y_offset += 15;
legend = 'nb samples:' + events.length;
ctx.fillText(legend,x_offset,y_offset);
maxBuffer*=1.1;
ctx.fillStyle = "blue";
ctx.beginPath();
ctx.moveTo(bufferChartStart, ctx.canvas.height);
for (var i =0 ; i < events.length; i++) {
event = events[i];
x_offset = bufferChartStart + (bufferChartWidth*(event.time-minTime))/(maxTime-minTime);
y_offset = ctx.canvas.height*(1 - (event.buffer+event.pos)/maxBuffer);
ctx.lineTo(x_offset,y_offset);
}
ctx.lineTo(x_offset, canvas.height);
ctx.fill();
ctx.fillStyle = "brown";
ctx.beginPath();
ctx.moveTo(bufferChartStart, ctx.canvas.height);
for (var i =0 ; i < events.length; i++) {
event = events[i];
x_offset = bufferChartStart + (bufferChartWidth*(event.time-minTime))/(maxTime-minTime);
y_offset = ctx.canvas.height*(1 - event.pos/maxBuffer);
ctx.lineTo(x_offset,y_offset);
}
ctx.lineTo(x_offset, canvas.height);
ctx.fill();
ctx.globalAlpha = 0.7;
ctx.fillStyle = "grey";
var x_start = bufferChartStart;
var x_w = bufferChartWidth*(windowMinTime-minTime)/(maxTime-minTime);
ctx.fillRect(x_start,0,x_w, canvas.height);
var x_start = bufferChartStart+bufferChartWidth*(windowMaxTime-minTime)/(maxTime-minTime);
var x_w = canvas.width-x_start-eventRightMargin;
ctx.fillRect(x_start,0,x_w, canvas.height);
ctx.globalAlpha = 1;
}
function canvasBitrateEventUpdate(canvas, minTime, maxTime, windowMinTime, windowMaxTime, levelEvents, bitrateEvents) {
var ctx = canvas.getContext('2d'),
bufferChartStart = eventLeftMargin,
bufferChartWidth = ctx.canvas.width-eventLeftMargin-eventRightMargin,
x_offset = 0,y_offset = 0,
event, maxLevel, minLevel, sumLevel, maxBitrate, minBitrate, sumDuration;
ctx.clearRect (0,0,canvas.width, canvas.height);
if(levelEvents.length === 0) {
return;
}
maxBitrate = minBitrate = bitrateEvents[0].bitrate;
sumLevel = sumDuration = 0;
for (var i =0 ; i < bitrateEvents.length; i++) {
sumLevel += bitrateEvents[i].duration*bitrateEvents[i].level;
sumDuration += bitrateEvents[i].duration;
maxBitrate = Math.max(maxBitrate, bitrateEvents[i].bitrate);
minBitrate = Math.min(minBitrate, bitrateEvents[i].bitrate);
}
maxLevel = minLevel = levelEvents[0].id;
for (var i =0 ; i < levelEvents.length; i++) {
maxLevel = Math.max(maxLevel, levelEvents[i].id);
minLevel = Math.min(minLevel, levelEvents[i].id);
}
ctx.fillStyle = "green";
ctx.globalAlpha = 0.5;
ctx.fillRect(0,0,eventLeftMargin, canvas.height);
ctx.fillRect(canvas.width-eventRightMargin,0,eventRightMargin, canvas.height);
ctx.globalAlpha = 1;
x_offset = 5;
y_offset = 0;
ctx.fillStyle = "black";
ctx.font = "15px Arial";
y_offset+=15;
legend = 'last bitrate:' + (bitrateEvents[bitrateEvents.length-1].bitrate/1000).toFixed(2) + "Mb/s";
ctx.fillText(legend,x_offset,y_offset);
y_offset+=15;
legend = 'min bitrate:' + (minBitrate/1000).toFixed(2) + "Mb/s";
ctx.fillText(legend,x_offset,y_offset);
y_offset+=15;
legend = 'max bitrate:' + (maxBitrate/1000).toFixed(2) + "Mb/s";
ctx.fillText(legend,x_offset,y_offset);
y_offset+=15;
legend = 'min/last/max level:' + minLevel + '/' + levelEvents[levelEvents.length-1].id + '/' + maxLevel;
ctx.fillText(legend,x_offset,y_offset);
y_offset += 15;
legend = 'nb level switch:' + (levelEvents.length-1);
ctx.fillText(legend,x_offset,y_offset);
y_offset += 15;
legend = 'average level:' + (sumLevel/sumDuration).toFixed(2);
ctx.fillText(legend,x_offset,y_offset);
maxBitrate*=1.1;
ctx.strokeStyle = "blue";
ctx.beginPath();
ctx.moveTo(bufferChartStart, ctx.canvas.height);
for (var i =0 ; i < bitrateEvents.length; i++) {
event = bitrateEvents[i];
x_offset = bufferChartStart + (bufferChartWidth*(event.time-minTime))/(maxTime-minTime);
y_offset = ctx.canvas.height*(1 - event.bitrate/maxBitrate);
ctx.lineTo(x_offset,y_offset);
}
ctx.lineTo(bufferChartStart+bufferChartWidth, y_offset);
ctx.stroke();
ctx.strokeStyle = "black";
ctx.beginPath();
x_offset = bufferChartStart;
y_offset = ctx.canvas.height;
ctx.moveTo(x_offset, y_offset);
for (var i =0 ; i < levelEvents.length; i++) {
event = levelEvents[i];
x_offset = bufferChartStart + (bufferChartWidth*(event.time-minTime))/(maxTime-minTime);
ctx.lineTo(x_offset,y_offset);
y_offset = ctx.canvas.height*(1 - event.bitrate/maxBitrate);
ctx.lineTo(x_offset,y_offset);
}
ctx.lineTo(bufferChartStart+bufferChartWidth, y_offset);
ctx.stroke();
ctx.globalAlpha = 0.7;
ctx.fillStyle = "grey";
var x_start = bufferChartStart;
var x_w = bufferChartWidth*(windowMinTime-minTime)/(maxTime-minTime);
ctx.fillRect(x_start,0,x_w, canvas.height);
var x_start = bufferChartStart+bufferChartWidth*(windowMaxTime-minTime)/(maxTime-minTime);
var x_w = canvas.width-x_start-eventRightMargin;
ctx.fillRect(x_start,0,x_w, canvas.height);
ctx.globalAlpha = 1;
}
function canvasDrawLoadEvent(ctx,yoffset,event,minTime,maxTime) {
var legend,offset,x_start,x_w,
networkChartStart = eventLeftMargin,
networkChartWidth = ctx.canvas.width-eventLeftMargin-eventRightMargin,
tend = Math.round(event.time + event.duration + event.latency);
//draw start
ctx.fillStyle = "black";
ctx.font = "12px Arial";
legend = Math.round(event.time);
offset = ctx.measureText(legend).width+5;
x_start = networkChartStart-offset+networkChartWidth*(event.time-minTime)/(maxTime-minTime);
ctx.fillText(legend,x_start,yoffset+12);
//draw latency rectangle
ctx.fillStyle = "orange";
x_start = networkChartStart + networkChartWidth*(event.time-minTime)/(maxTime-minTime);
x_w = networkChartWidth*event.latency/(maxTime-minTime);
ctx.fillRect(x_start,yoffset,x_w, 15);
//draw download rectangle
ctx.fillStyle = "green";
x_start = networkChartStart + networkChartWidth*(event.time+event.latency-minTime)/(maxTime-minTime);
x_w = networkChartWidth*event.load/(maxTime-minTime);
ctx.fillRect(x_start,yoffset,x_w, 15);
if(event.parsing) {
//draw parsing rectangle
ctx.fillStyle = "blue";
x_start = networkChartStart + networkChartWidth*(event.time+event.latency+event.load-minTime)/(maxTime-minTime);
x_w = networkChartWidth*event.parsing/(maxTime-minTime);
ctx.fillRect(x_start,yoffset,x_w, 15);
if(event.buffer) {
//draw buffering rectangle
ctx.fillStyle = "red";
x_start = networkChartStart + networkChartWidth*(event.time+event.latency+event.load+event.parsing-minTime)/(maxTime-minTime);
x_w = networkChartWidth*event.buffer/(maxTime-minTime);
ctx.fillRect(x_start,yoffset,x_w, 15);
}
}
//draw end time
ctx.fillStyle = "black";
ctx.font = "12px Arial";
legend = tend;
x_start += x_w + 5;
ctx.fillText(legend,x_start,yoffset+12);
x_start += ctx.measureText(legend).width+5;
legend = "[" + Math.round(event.latency);
ctx.fillStyle = "orange";
ctx.fillText(legend,x_start,yoffset+12);
x_start += ctx.measureText(legend).width+5;
legend = Math.round(event.load);
if(!event.parsing) legend += "]";
ctx.fillStyle = "green";
ctx.fillText(legend,x_start,yoffset+12);
x_start += ctx.measureText(legend).width+5;
if(event.parsing) {
legend = Math.round(event.parsing);
if(!event.buffer) legend +="]";
ctx.fillStyle = "blue";
ctx.fillText(legend,x_start,yoffset+12);
x_start += ctx.measureText(legend).width+5;
if(event.buffer) {
legend = Math.round(event.buffer) + "]";
ctx.fillStyle = "red";
ctx.fillText(legend,x_start,yoffset+12);
x_start += ctx.measureText(legend).width+5;
}
}
if(event.size) {
if(event.size > 1000*1000) {
legend = (event.size/1000000).toFixed(1) + 'MB';
} else {
legend = Math.round(event.size/1000) + 'kB';
}
ctx.fillStyle = "black";
ctx.fillText(legend,x_start,yoffset+12);
x_start += ctx.measureText(legend).width+5;
}
if(event.bw) {
if(event.bw > 1000) {
legend = (event.bw/1000).toFixed(1) + 'Mbps';
} else {
legend = event.bw + ' kbps';
}
ctx.fillStyle = "black";
ctx.fillText(legend,x_start,yoffset+12);
x_start += ctx.measureText(legend).width+5;
}
// draw event name
ctx.fillStyle = "black";
ctx.font = "15px Arial";
legend = event.type;
if(event.id2 !== undefined) {
legend += ' ' + event.id2;
}
if(event.id !== undefined) {
if(event.type === 'fragment') {
legend += ' @';
}
legend += ' ' + event.id;
}
if(event.start !== undefined) {
legend += ' [' + event.start + ',' + event.end + ']';
}
ctx.fillText(legend,5,yoffset+15);
}
function canvasDrawVideoEvent(ctx,yoffset,event,minTime,maxTime) {
var legend,offset,x_start,x_w,
networkChartStart = eventLeftMargin,
networkChartWidth = ctx.canvas.width-eventLeftMargin-eventRightMargin;
// draw event name
ctx.fillStyle = "black";
ctx.font = "15px Arial";
legend = event.type;
if (event.name) legend+= ':' + event.name;
ctx.fillText(legend,5,yoffset+15);
//draw start time
ctx.fillStyle = "black";
ctx.font = "12px Arial";
legend = Math.round(event.time);
offset = ctx.measureText(legend).width+5;
x_start = networkChartStart-offset+networkChartWidth*(event.time-minTime)/(maxTime-minTime);
ctx.fillText(legend,x_start,yoffset+12);
//draw event rectangle
x_start = networkChartStart + networkChartWidth*(event.time-minTime)/(maxTime-minTime);
if(event.duration) {
x_w = networkChartWidth*event.duration/(maxTime-minTime);
} else {
x_w = 1;
}
ctx.fillRect(x_start,yoffset,x_w, 15);
if(event.duration) {
//draw end time
ctx.fillStyle = "black";
ctx.font = "12px Arial";
legend = Math.round(event.time+event.duration);
x_start += x_w + 5;
ctx.fillText(legend,x_start,yoffset+12);
x_start += ctx.measureText(legend).width+5;
legend = "[" + Math.round(event.duration) + "]";
ctx.fillStyle = "blue";
ctx.fillText(legend,x_start,yoffset+12);
}
}

View file

@ -0,0 +1,914 @@
<!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=""/>
<label class="innerControls"><input id="enableStream" type=checkbox checked/> Enable Streaming</label>
<label class="innerControls"><input id="autoRecoverError" type=checkbox checked/> Auto-Recover Media Error</label>
<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);
$('#enableStream').click(function() { enableStreaming = this.checked; loadStream($('#streamURL').val());});
$('#autoRecoverError').prop( "checked", true );
$('#autoRecoverError').click(function() { autoRecoverError = this.checked; });
});
'use strict';
var hls,events, stats, enableStreaming = true, autoRecoverError = true;
var video = $('#video')[0];
video.volume = 0.05;
var manifest = decodeURIComponent(location.search.split('src=')[1]);
if(manifest === 'undefined') {
manifest = 'http://www.streambox.fr/playlists/x36xhzz/x36xhzz.m3u8';
}
loadStream(manifest);
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);
var hlsLink = document.URL.split('?')[0] + '?src=' + encodeURIComponent(url);
var description = 'permalink: ' + "<a href=\"" + hlsLink + "\">" + hlsLink + "</a>";
$("#StreamPermalink").html(description);
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;
hls = new Hls({debug:true});
$("#HlsStatus").text('loading manifest and attaching video element...');
hls.loadSource(url);
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;
case Hls.ErrorDetails.LEVEL_LOAD_ERROR:
$("#HlsStatus").text("error while trying to load level playlist");
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:
$("#HlsStatus").text("error while trying to load fragment " + data.frag.url);
break;
case Hls.ErrorDetails.LEVEL_LOAD_TIMEOUT:
$("#HlsStatus").text("timeout while trying to load level playlist");
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;
case Hls.ErrorDetails.BUFFER_APPEND_ERROR:
$("#HlsStatus").text("Buffer Append Error");
break;
case Hls.ErrorDetails.BUFFER_APPENDING_ERROR:
$("#HlsStatus").text("Buffer Appending Error");
break;
case Hls.ErrorDetails.FRAG_LOOP_LOADING_ERROR:
$("#HlsStatus").text("Frag Loop Loading 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;
}
}
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 decodedFrames, droppedFrames;
if(navigator.userAgent.toLowerCase().indexOf('firefox') !== -1) {
decodedFrames = v.mozDecodedFrames;
droppedFrames = v.mozParsedFrames-v.mozPresentedFrames;
} else {
decodedFrames = v.webkitDecodedFrameCount;
droppedFrames = v.webkitDroppedFrameCount;
}
var log = "Duration:"
+ v.duration + "<br>"
+ "Buffered:"
+ timeRangesToString(v.buffered) + "<br>"
+ "Seekable:"
+ timeRangesToString(v.seekable) + "<br>"
+ "Played:"
+ timeRangesToString(v.played) + "<br>"
+ "Decoded Frames:"
+ decodedFrames + "<br>"
+ "Dropped Frames:"
+ droppedFrames + "<br>";
$("#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;
}
html3 += 'onclick="hls.autoLevelCapping=-1;updateLevelInfo()">auto</button>';
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;
}
html3 += 'onclick="hls.autoLevelCapping=' + i + ';updateLevelInfo()">' + levelName + '</button>';
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;
}
}
}
}
}
</script>
</body>
</html>

View file

@ -0,0 +1,578 @@
/*
Copyright (c) 2013, Rodrigo González, Sapienlab All Rights Reserved.
Available via MIT LICENSE. See https://github.com/roro89/jsonpack/blob/master/LICENSE.md for details.
*/
(function(define) {
define([], function() {
var TOKEN_TRUE = -1;
var TOKEN_FALSE = -2;
var TOKEN_NULL = -3;
var TOKEN_EMPTY_STRING = -4;
var TOKEN_UNDEFINED = -5;
var pack = function(json, options) {
// Canonizes the options
options = options || {};
// A shorthand for debugging
var verbose = options.verbose || false;
verbose && console.log('Normalize the JSON Object');
// JSON as Javascript Object (Not string representation)
json = typeof json === 'string' ? this.JSON.parse(json) : json;
verbose && console.log('Creating a empty dictionary');
// The dictionary
var dictionary = {
strings : [],
integers : [],
floats : []
};
verbose && console.log('Creating the AST');
// The AST
var ast = (function recursiveAstBuilder(item) {
verbose && console.log('Calling recursiveAstBuilder with ' + this.JSON.stringify(item));
// The type of the item
var type = typeof item;
// Case 7: The item is null
if (item === null) {
return {
type : 'null',
index : TOKEN_NULL
};
}
//add undefined
if (typeof item === 'undefined') {
return {
type : 'undefined',
index : TOKEN_UNDEFINED
};
}
// Case 1: The item is Array Object
if ( item instanceof Array) {
// Create a new sub-AST of type Array (@)
var ast = ['@'];
// Add each items
for (var i in item) {
if (!item.hasOwnProperty(i)) continue;
ast.push(recursiveAstBuilder(item[i]));
}
// And return
return ast;
}
// Case 2: The item is Object
if (type === 'object') {
// Create a new sub-AST of type Object ($)
var ast = ['$'];
// Add each items
for (var key in item) {
if (!item.hasOwnProperty(key))
continue;
ast.push(recursiveAstBuilder(key));
ast.push(recursiveAstBuilder(item[key]));
}
// And return
return ast;
}
// Case 3: The item empty string
if (item === '') {
return {
type : 'empty',
index : TOKEN_EMPTY_STRING
};
}
// Case 4: The item is String
if (type === 'string') {
// The index of that word in the dictionary
var index = _indexOf.call(dictionary.strings, item);
// If not, add to the dictionary and actualize the index
if (index == -1) {
dictionary.strings.push(_encode(item));
index = dictionary.strings.length - 1;
}
// Return the token
return {
type : 'strings',
index : index
};
}
// Case 5: The item is integer
if (type === 'number' && item % 1 === 0) {
// The index of that number in the dictionary
var index = _indexOf.call(dictionary.integers, item);
// If not, add to the dictionary and actualize the index
if (index == -1) {
dictionary.integers.push(_base10To36(item));
index = dictionary.integers.length - 1;
}
// Return the token
return {
type : 'integers',
index : index
};
}
// Case 6: The item is float
if (type === 'number') {
// The index of that number in the dictionary
var index = _indexOf.call(dictionary.floats, item);
// If not, add to the dictionary and actualize the index
if (index == -1) {
// Float not use base 36
dictionary.floats.push(item);
index = dictionary.floats.length - 1;
}
// Return the token
return {
type : 'floats',
index : index
};
}
// Case 7: The item is boolean
if (type === 'boolean') {
return {
type : 'boolean',
index : item ? TOKEN_TRUE : TOKEN_FALSE
};
}
// Default
throw new Error('Unexpected argument of type ' + typeof (item));
})(json);
// A set of shorthands proxies for the length of the dictionaries
var stringLength = dictionary.strings.length;
var integerLength = dictionary.integers.length;
var floatLength = dictionary.floats.length;
verbose && console.log('Parsing the dictionary');
// Create a raw dictionary
var packed = dictionary.strings.join('|');
packed += '^' + dictionary.integers.join('|');
packed += '^' + dictionary.floats.join('|');
verbose && console.log('Parsing the structure');
// And add the structure
packed += '^' + (function recursiveParser(item) {
verbose && console.log('Calling a recursiveParser with ' + this.JSON.stringify(item));
// If the item is Array, then is a object of
// type [object Object] or [object Array]
if ( item instanceof Array) {
// The packed resulting
var packed = item.shift();
for (var i in item) {
if (!item.hasOwnProperty(i))
continue;
packed += recursiveParser(item[i]) + '|';
}
return (packed[packed.length - 1] === '|' ? packed.slice(0, -1) : packed) + ']';
}
// A shorthand proxies
var type = item.type, index = item.index;
if (type === 'strings') {
// Just return the base 36 of index
return _base10To36(index);
}
if (type === 'integers') {
// Return a base 36 of index plus stringLength offset
return _base10To36(stringLength + index);
}
if (type === 'floats') {
// Return a base 36 of index plus stringLength and integerLength offset
return _base10To36(stringLength + integerLength + index);
}
if (type === 'boolean') {
return item.index;
}
if (type === 'null') {
return TOKEN_NULL;
}
if (type === 'undefined') {
return TOKEN_UNDEFINED;
}
if (type === 'empty') {
return TOKEN_EMPTY_STRING;
}
throw new TypeError('The item is alien!');
})(ast);
verbose && console.log('Ending parser');
// If debug, return a internal representation of dictionary and stuff
if (options.debug)
return {
dictionary : dictionary,
ast : ast,
packed : packed
};
return packed;
};
var unpack = function(packed, options) {
// Canonizes the options
options = options || {};
// A raw buffer
var rawBuffers = packed.split('^');
// Create a dictionary
options.verbose && console.log('Building dictionary');
var dictionary = [];
// Add the strings values
var buffer = rawBuffers[0];
if (buffer !== '') {
buffer = buffer.split('|');
options.verbose && console.log('Parse the strings dictionary');
for (var i=0, n=buffer.length; i<n; i++){
dictionary.push(_decode(buffer[i]));
}
}
// Add the integers values
buffer = rawBuffers[1];
if (buffer !== '') {
buffer = buffer.split('|');
options.verbose && console.log('Parse the integers dictionary');
for (var i=0, n=buffer.length; i<n; i++){
dictionary.push(_base36To10(buffer[i]));
}
}
// Add the floats values
buffer = rawBuffers[2];
if (buffer !== '') {
buffer = buffer.split('|')
options.verbose && console.log('Parse the floats dictionary');
for (var i=0, n=buffer.length; i<n; i++){
dictionary.push(parseFloat(buffer[i]));
}
}
// Free memory
delete buffer;
options.verbose && console.log('Tokenizing the structure');
// Tokenizer the structure
var number36 = '';
var tokens = [];
var len=rawBuffers[3].length;
for (var i = 0; i < len; i++) {
var symbol = rawBuffers[3].charAt(i);
if (symbol === '|' || symbol === '$' || symbol === '@' || symbol === ']') {
if (number36) {
tokens.push(_base36To10(number36));
number36 = '';
}
symbol !== '|' && tokens.push(symbol);
} else {
number36 += symbol;
}
}
// A shorthand proxy for tokens.length
var tokensLength = tokens.length;
// The index of the next token to read
var tokensIndex = 0;
options.verbose && console.log('Starting recursive parser');
return (function recursiveUnpackerParser() {
// Maybe '$' (object) or '@' (array)
var type = tokens[tokensIndex++];
options.verbose && console.log('Reading collection type ' + (type === '$' ? 'object' : 'Array'));
// Parse an array
if (type === '@') {
var node = [];
for (; tokensIndex < tokensLength; tokensIndex++) {
var value = tokens[tokensIndex];
options.verbose && console.log('Read ' + value + ' symbol');
if (value === ']')
return node;
if (value === '@' || value === '$') {
node.push(recursiveUnpackerParser());
} else {
switch(value) {
case TOKEN_TRUE:
node.push(true);
break;
case TOKEN_FALSE:
node.push(false);
break;
case TOKEN_NULL:
node.push(null);
break;
case TOKEN_UNDEFINED:
node.push(undefined);
break;
case TOKEN_EMPTY_STRING:
node.push('');
break;
default:
node.push(dictionary[value]);
}
}
}
options.verbose && console.log('Parsed ' + this.JSON.stringify(node));
return node;
}
// Parse a object
if (type === '$') {
var node = {};
for (; tokensIndex < tokensLength; tokensIndex++) {
var key = tokens[tokensIndex];
if (key === ']')
return node;
if (key === TOKEN_EMPTY_STRING)
key = '';
else
key = dictionary[key];
var value = tokens[++tokensIndex];
if (value === '@' || value === '$') {
node[key] = recursiveUnpackerParser();
} else {
switch(value) {
case TOKEN_TRUE:
node[key] = true;
break;
case TOKEN_FALSE:
node[key] = false;
break;
case TOKEN_NULL:
node[key] = null;
break;
case TOKEN_UNDEFINED:
node[key] = undefined;
break;
case TOKEN_EMPTY_STRING:
node[key] = '';
break;
default:
node[key] = dictionary[value];
}
}
}
options.verbose && console.log('Parsed ' + this.JSON.stringify(node));
return node;
}
throw new TypeError('Bad token ' + type + ' isn\'t a type');
})();
}
/**
* Get the index value of the dictionary
* @param {Object} dictionary a object that have two array attributes: 'string' and 'number'
* @param {Object} data
*/
var _indexOfDictionary = function(dictionary, value) {
// The type of the value
var type = typeof value;
// If is boolean, return a boolean token
if (type === 'boolean')
return value ? TOKEN_TRUE : TOKEN_FALSE;
// If is null, return a... yes! the null token
if (value === null)
return TOKEN_NULL;
//add undefined
if (typeof value === 'undefined')
return TOKEN_UNDEFINED;
if (value === '') {
return TOKEN_EMPTY_STRING;
}
if (type === 'string') {
value = _encode(value);
var index = _indexOf.call(dictionary.strings, value);
if (index === -1) {
dictionary.strings.push(value);
index = dictionary.strings.length - 1;
}
}
// If has an invalid JSON type (example a function)
if (type !== 'string' && type !== 'number') {
throw new Error('The type is not a JSON type');
};
if (type === 'string') {// string
value = _encode(value);
} else if (value % 1 === 0) {// integer
value = _base10To36(value);
} else {// float
}
// If is number, "serialize" the value
value = type === 'number' ? _base10To36(value) : _encode(value);
// Retrieve the index of that value in the dictionary
var index = _indexOf.call(dictionary[type], value);
// If that value is not in the dictionary
if (index === -1) {
// Push the value
dictionary[type].push(value);
// And return their index
index = dictionary[type].length - 1;
}
// If the type is a number, then add the '+' prefix character
// to differentiate that they is a number index. If not, then
// just return a 36-based representation of the index
return type === 'number' ? '+' + index : index;
};
var _encode = function(str) {
if ( typeof str !== 'string')
return str;
return str.replace(/[\+ \|\^\%]/g, function(a) {
return ({
' ' : '+',
'+' : '%2B',
'|' : '%7C',
'^' : '%5E',
'%' : '%25'
})[a]
});
};
var _decode = function(str) {
if ( typeof str !== 'string')
return str;
return str.replace(/\+|%2B|%7C|%5E|%25/g, function(a) {
return ({
'+' : ' ',
'%2B' : '+',
'%7C' : '|',
'%5E' : '^',
'%25' : '%'
})[a]
})
};
var _base10To36 = function(number) {
return Number.prototype.toString.call(number, 36).toUpperCase();
};
var _base36To10 = function(number) {
return parseInt(number, 36);
};
var _indexOf = Array.prototype.indexOf ||
function(obj, start) {
for (var i = (start || 0), j = this.length; i < j; i++) {
if (this[i] === obj) {
return i;
}
}
return -1;
};
return {
JSON : JSON,
pack : pack,
unpack : unpack
};
});
})( typeof define == 'undefined' || !define.amd ? function(deps, factory) {
var jsonpack = factory();
if ( typeof exports != 'undefined')
for (var key in jsonpack)
exports[key] = jsonpack[key];
else
window.jsonpack = jsonpack;
} : define);

View file

@ -0,0 +1,74 @@
<!DOCTYPE html>
<head>
<title>hls.js metrics page</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" />
<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://ajax.googleapis.com/ajax/libs/jquery/2.1.3/jquery.min.js"></script>
<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">hls.js metrics page</h1>
</header>
</div>
<pre id='HlsDate'></pre>
<pre id='StreamPermalink'></pre>
<input id="metricsData" class="innerControls" type=text value=""/>
window size
<div id="metricsButton">
<button type="button" class="btn btn-xs btn-primary" onclick="timeRangeSetSliding(0)">window ALL</button>
<button type="button" class="btn btn-xs btn-primary" onclick="timeRangeSetSliding(2000)">2s</button>
<button type="button" class="btn btn-xs btn-primary" onclick="timeRangeSetSliding(5000)">5s</button>
<button type="button" class="btn btn-xs btn-primary" onclick="timeRangeSetSliding(10000)">10s</button>
<button type="button" class="btn btn-xs btn-primary" onclick="timeRangeSetSliding(20000)">20s</button>
<button type="button" class="btn btn-xs btn-primary" onclick="timeRangeSetSliding(30000)">30s</button>
<button type="button" class="btn btn-xs btn-primary" onclick="timeRangeSetSliding(60000)">60s</button>
<button type="button" class="btn btn-xs btn-primary" onclick="timeRangeSetSliding(120000)">120s</button><br>
<button type="button" class="btn btn-xs btn-primary" onclick="timeRangeZoomIn()">Window Zoom In</button>
<button type="button" class="btn btn-xs btn-primary" onclick="timeRangeZoomOut()">Window Zoom Out</button><br>
<button type="button" class="btn btn-xs btn-primary" onclick="timeRangeSlideLeft()"> <<< Window Slide </button>
<button type="button" class="btn btn-xs btn-primary" onclick="timeRangeSlideRight()">Window Slide >>> </button><br>
<button type="button" class="btn btn-xs btn-primary" 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-primary" 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>
<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>
<script src="canvas.js"></script>
<script src="metrics.js"></script>
<script src="jsonpack.js"></script>
<script>
$(document).ready(function() {
$('#metricsData').change(function() { events = jsonpack.unpack(atob($('#metricsData').val())); updateMetrics(); });
});
var data = location.search.split('data=')[1],events;
if (data) {
events = jsonpack.unpack(atob(decodeURIComponent(data)));
updateMetrics();
}
function updateMetrics() {
var hlsLink = document.URL.substr(0,document.URL.lastIndexOf("/")+1) + 'index.html?src=' + encodeURIComponent(events.url);
var description = 'playlist: ' + "<a href=\"" + events.url + "\">" + events.url + "</a>" + '<br>replay: ' + "<a href=\"" + hlsLink + "\">" + hlsLink + "</a>";
$("#StreamPermalink").html(description);
$("#HlsDate").text("session Start Date:" + new Date(events.t0));
metricsDisplayed=true;
showMetrics();
refreshCanvas();
}
</script>
</body>

View file

@ -0,0 +1,201 @@
function showMetrics() {
if(metricsDisplayed) {
var width = window.innerWidth-30;
$("#bufferWindow_c")[0].width =
$("#bitrateTimerange_c")[0].width =
$("#bufferTimerange_c")[0].width =
$("#videoEvent_c")[0].width =
$("#metricsButton")[0].width =
$("#loadEvent_c")[0].width = width;
$("#bufferWindow_c").show();
$("#bitrateTimerange_c").show();
$("#bufferTimerange_c").show();
$("#videoEvent_c").show();
$("#metricsButton").show();
$("#loadEvent_c").show();
}
}
function toggleMetricsDisplay() {
metricsDisplayed = !metricsDisplayed;
if(metricsDisplayed) {
showMetrics();
} else {
hideMetrics();
}
}
function hideMetrics() {
if(!metricsDisplayed) {
$("#bufferWindow_c").hide();
$("#bitrateTimerange_c").hide();
$("#bufferTimerange_c").hide();
$("#videoEvent_c").hide();
$("#metricsButton").hide();
$("#loadEvent_c").hide();
}
}
function timeRangeSetSliding(duration) {
windowDuration = duration;
windowSliding = true;
refreshCanvas();
}
var timeRangeMouseDown=false;
function timeRangeCanvasonMouseDown(evt) {
var canvas = evt.currentTarget,
bRect = canvas.getBoundingClientRect(),
mouseX = Math.round((evt.clientX - bRect.left)*(canvas.width/bRect.width));
windowStart = Math.max(0,Math.round((mouseX-eventLeftMargin) * getWindowTimeRange().now / (canvas.width-eventLeftMargin)));
windowEnd = windowStart+500;
timeRangeMouseDown = true;
windowSliding = false;
//console.log('windowStart/windowEnd:' + '/' + windowStart + '/' + windowEnd);
$("#windowStart").val(windowStart);
$("#windowEnd").val(windowEnd);
refreshCanvas();
}
function timeRangeCanvasonMouseMove(evt) {
if(timeRangeMouseDown) {
var canvas = evt.currentTarget,
bRect = canvas.getBoundingClientRect(),
mouseX = Math.round((evt.clientX - bRect.left)*(canvas.width/bRect.width)),
pos = Math.max(0,Math.round((mouseX-eventLeftMargin) * getWindowTimeRange().now / (canvas.width-eventLeftMargin)));
if(pos < windowStart) {
windowStart = pos;
} else {
windowEnd = pos;
}
if(windowStart === windowEnd) {
// to avoid division by zero ...
windowEnd +=50;
}
//console.log('windowStart/windowEnd:' + '/' + windowStart + '/' + windowEnd);
$("#windowStart").val(windowStart);
$("#windowEnd").val(windowEnd);
refreshCanvas();
}
}
function timeRangeCanvasonMouseUp(evt) {
timeRangeMouseDown = false;
}
function timeRangeCanvasonMouseOut(evt) {
timeRangeMouseDown = false;
}
function windowCanvasonMouseMove(evt) {
var canvas = evt.currentTarget,
bRect = canvas.getBoundingClientRect(),
mouseX = Math.round((evt.clientX - bRect.left)*(canvas.width/bRect.width)),
timeRange = getWindowTimeRange();
windowFocus = timeRange.min + Math.max(0,Math.round((mouseX-eventLeftMargin) * (timeRange.max - timeRange.min) / (canvas.width-eventLeftMargin)));
//console.log(windowFocus);
refreshCanvas();
}
var windowDuration=20000,windowSliding=true,windowStart=0,windowEnd=10000,windowFocus,metricsDisplayed=false;
$("#windowStart").val(windowStart);
$("#windowEnd").val(windowEnd);
function refreshCanvas() {
if(metricsDisplayed) {
try {
var windowTime = getWindowTimeRange();
canvasBufferTimeRangeUpdate($("#bufferTimerange_c")[0], 0, windowTime.now, windowTime.min,windowTime.max, events.buffer);
if(windowTime.min !== 0 || windowTime.max !== windowTime.now) {
$("#bufferWindow_c").show();
canvasBufferWindowUpdate($("#bufferWindow_c")[0], windowTime.min,windowTime.max, windowTime.focus, events.buffer);
} else {
$("#bufferWindow_c").hide();
}
canvasBitrateEventUpdate($("#bitrateTimerange_c")[0], 0, windowTime.now, windowTime.min,windowTime.max, events.level, events.bitrate);
canvasVideoEventUpdate($("#videoEvent_c")[0], windowTime.min,windowTime.max, events.video);
canvasLoadEventUpdate($("#loadEvent_c")[0], windowTime.min,windowTime.max, events.load);
} catch(err) {
console.log("refreshCanvas error:" +err.message);
}
}
}
function getWindowTimeRange() {
var tnow,minTime,maxTime;
if(events.buffer.length) {
tnow = events.buffer[events.buffer.length-1].time;
} else {
tnow = 0;
}
if(windowSliding) {
// let's show the requested window
if(windowDuration) {
minTime = Math.max(0, tnow-windowDuration),
maxTime = Math.min(minTime + windowDuration, tnow);
} else {
minTime = 0;
maxTime = tnow;
}
} else {
minTime = windowStart;
maxTime = windowEnd;
}
if(windowFocus === undefined || windowFocus < minTime || windowFocus > maxTime) {
windowFocus = minTime;
}
return { min : minTime, max: maxTime, now : tnow, focus : windowFocus}
}
function timeRangeZoomIn() {
if(windowSliding) {
windowDuration/=2;
} else {
var duration = windowEnd-windowStart;
windowStart+=duration/4;
windowEnd-=duration/4;
if(windowStart === windowEnd) {
windowEnd+=50;
}
}
$("#windowStart").val(windowStart);
$("#windowEnd").val(windowEnd);
refreshCanvas();
}
function timeRangeZoomOut() {
if(windowSliding) {
windowDuration*=2;
} else {
var duration = windowEnd-windowStart;
windowStart-=duration/2;
windowEnd+=duration/2;
windowStart=Math.max(0,windowStart);
windowEnd=Math.min(events.buffer[events.buffer.length-1].time,windowEnd);
}
$("#windowStart").val(windowStart);
$("#windowEnd").val(windowEnd);
refreshCanvas();
}
function timeRangeSlideLeft() {
var duration = windowEnd-windowStart;
windowStart-=duration/4;
windowEnd-=duration/4;
windowStart=Math.max(0,windowStart);
windowEnd=Math.min(events.buffer[events.buffer.length-1].time,windowEnd);
$("#windowStart").val(windowStart);
$("#windowEnd").val(windowEnd);
refreshCanvas();
}
function timeRangeSlideRight() {
var duration = windowEnd-windowStart;
windowStart+=duration/4;
windowEnd+=duration/4;
windowStart=Math.max(0,windowStart);
windowEnd=Math.min(events.buffer[events.buffer.length-1].time,windowEnd);
$("#windowStart").val(windowStart);
$("#windowEnd").val(windowEnd);
refreshCanvas();
}