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:
parent
def418714f
commit
f36e664503
97 changed files with 16860 additions and 197 deletions
47
dashboard-ui/bower_components/hls.js/demo/benchmark.html
vendored
Normal file
47
dashboard-ui/bower_components/hls.js/demo/benchmark.html
vendored
Normal 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>
|
574
dashboard-ui/bower_components/hls.js/demo/canvas.js
vendored
Normal file
574
dashboard-ui/bower_components/hls.js/demo/canvas.js
vendored
Normal 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);
|
||||
}
|
||||
}
|
914
dashboard-ui/bower_components/hls.js/demo/index.html
vendored
Normal file
914
dashboard-ui/bower_components/hls.js/demo/index.html
vendored
Normal 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>
|
578
dashboard-ui/bower_components/hls.js/demo/jsonpack.js
vendored
Normal file
578
dashboard-ui/bower_components/hls.js/demo/jsonpack.js
vendored
Normal 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);
|
74
dashboard-ui/bower_components/hls.js/demo/metrics.html
vendored
Normal file
74
dashboard-ui/bower_components/hls.js/demo/metrics.html
vendored
Normal 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>
|
201
dashboard-ui/bower_components/hls.js/demo/metrics.js
vendored
Normal file
201
dashboard-ui/bower_components/hls.js/demo/metrics.js
vendored
Normal 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();
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue