Merge branch 'development' into LTS_development

This commit is contained in:
Thulinma 2021-07-22 13:08:06 +02:00
commit e52bbb4af6
8 changed files with 373 additions and 77 deletions

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -28,7 +28,7 @@ function MistVideo(streamName,options) {
reloadDelay: false, //don't override default reload delay
urlappend: false, //don't add this to urls
setTracks: false, //don't set tracks
fillSpace: false, //don't fill parent container
fillSpace: false, //don't fill parent container
width: false, //no set width
height: false, //no set height
maxwidth: false, //no max width (apart from targets dimensions)
@ -62,6 +62,7 @@ function MistVideo(streamName,options) {
return event;
};
this.log("Initializing..");
this.bootMs = new Date().getTime();
this.timers = {
list: {}, //will contain the timeouts, format timeOutIndex: endTime
@ -110,14 +111,7 @@ function MistVideo(streamName,options) {
var source = false;
var mistPlayer = false;
if (options.startCombo) {
options.startCombo.started = {
player: false,
source: false
};
}
//retrieve the sources we can loop over
var sources;
if (options.forceSource) {
@ -177,7 +171,7 @@ function MistVideo(streamName,options) {
MistUtil.array.multiSort(players,sortoptions.player);
}
if ("first" in options.forcePriority) {
sortopions.first = options.forcePriority.first; //overwrite
sortoptions.first = options.forcePriority.first; //overwrite
}
@ -198,6 +192,20 @@ function MistVideo(streamName,options) {
current: false
}
};
if (options.startCombo) {
options.startCombo.started = {
player: false,
source: false
};
for (var i = 0; i < players.length; i++) {
if (players[i].shortname == options.startCombo.player) {
options.startCombo.player = i;
break;
}
}
}
function checkStartCombo(which) {
if ((options.startCombo) && (!options.startCombo.started[which])) {
@ -401,7 +409,16 @@ function MistVideo(streamName,options) {
if (MistVideo.choosePlayer()) {
if (MistVideo.reporting) {
MistVideo.reporting.report({
player: MistVideo.playerName,
sourceType: MistVideo.source.type,
sourceUrl: MistVideo.source.url,
pageUrl: location.href
});
}
//build player
MistVideo.player = new mistplayers[MistVideo.playerName].player();
@ -416,6 +433,10 @@ function MistVideo(streamName,options) {
MistVideo.container.removeAttribute("data-loading");
MistVideo.video = video;
if (MistVideo.reporting) {
MistVideo.reporting.init();
}
if ("api" in MistVideo.player) {
@ -495,6 +516,11 @@ function MistVideo(streamName,options) {
score = Math.max(score,list[list.length-1].score);
this.vars.score = score;
if (MistVideo.reporting) {
MistVideo.reporting.stats.set("playbackScore",Math.round(score*10)/10);
}
return score;
},
valueToScore: function(a,b){ //calculate the moving average
@ -567,8 +593,7 @@ function MistVideo(streamName,options) {
MistUtil.event.addListener(MistVideo.video,events[i],function(){
if (MistVideo.monitor) { MistVideo.monitor.reset(); }
});
}
}
}
//remove placeholder and add UI structure
@ -874,6 +899,7 @@ function MistVideo(streamName,options) {
MistUtil.event.send("initialized",null,options.target);
MistVideo.log("Initialized");
if (MistVideo.options.callback) { options.callback(MistVideo); }
});
@ -881,8 +907,8 @@ function MistVideo(streamName,options) {
else if (MistVideo.options.startCombo) {
//try again without a startCombo
delete MistVideo.options.startCombo;
MistVideo.unload();
MistVideo = mistPlay(MistVideo.stream,MistVideo.options);
MistVideo.unload("No compatible players found - retrying without startCombo.");
mistPlay(MistVideo.stream,MistVideo.options);
}
else {
MistVideo.showError("No compatible player/source combo found.",{reload:true});
@ -933,10 +959,188 @@ function MistVideo(streamName,options) {
socket.die = false;
socket.destroy = function(){
this.die = true;
this.onclose = function(){};
this.close();
};
socket.onopen = function(e){
this.wasConnected = true;
//report player status to MistServer
if (!MistVideo.reporting) {
MistVideo.reporting = {
stats: {
set: function(key,value){
this.d[key] = value;
},
add: function(key,add){
if (typeof add == "undefined") { add = 1; }
this.d[key] += add;
},
d: {
nWaiting: 0,
timeWaiting: 0,
nStalled: 0,
timeStalled: 0,
timeUnpaused: 0,
nError: 0,
nLog: 0,
videoHeight: null,
videoWidth: null,
playerHeight: null,
playerWidth: null
},
last: {
firstPlayback: null,
nWaiting: 0,
timeWaiting: 0,
nStalled: 0,
timeStalled: 0,
timeUnpaused: 0,
nError: 0,
lastError: null,
playbackScore: 1,
nLog: 0,
autoplay: null,
videoHeight: null,
videoWidth: null,
playerHeight: null,
playerWidth: null
}
},
report: function(d){
MistVideo.socket.send(JSON.stringify(d));
},
reportStats: function(){
var d = {};
var report = false;
var newlogs = MistVideo.logs.slice(this.stats.last.nLog);
for (var i in this.stats.d) {
if (this.stats.d[i] != this.stats.last[i]) {
d[i] = this.stats.d[i];
this.stats.last[i] = d[i];
report = true;
}
}
if (report) {
if (newlogs.length) {
d.logs = [];
for (var i in newlogs) {
d.logs.push(newlogs[i].message);
}
}
this.report(d);
}
MistVideo.timers.start(function(){
MistVideo.reporting.reportStats();
},5e3);
},
init: function(){
var video = MistVideo.video;
var firstPlay = MistUtil.event.addListener(video,"playing",function(){
MistVideo.reporting.stats.set("firstPlayback",new Date().getTime() - MistVideo.bootMs);
MistUtil.event.removeListener(firstPlay);
});
//set listeners for player reporting
MistUtil.event.addListener(video,"waiting",function(){
MistVideo.reporting.stats.add("nWaiting");
});
MistUtil.event.addListener(video,"stalled",function(){
MistVideo.reporting.stats.add("nStalled");
});
MistUtil.event.addListener(MistVideo.options.target,"error",function(e){
MistVideo.reporting.stats.add("nError");
MistVideo.reporting.stats.set("lastError",e.message);
});
if (Object && Object.defineProperty) {
var timeWaiting = 0;
var waitingSince = false;
var timeStalled = 0;
var stalledSince = false;
var timeUnpaused = 0;
var unpausedSince = false;
var d = MistVideo.reporting.stats.d;
Object.defineProperty(d,"timeWaiting",{
get: function(){
return timeWaiting + (waitingSince ? (new Date()).getTime() - waitingSince : 0);
}
});
Object.defineProperty(d,"timeStalled",{
get: function(){
return timeStalled + (stalledSince ? (new Date()).getTime() - stalledSince : 0);
}
});
Object.defineProperty(d,"timeUnpaused",{
get: function(){
return timeUnpaused + (unpausedSince ? (new Date()).getTime() - unpausedSince : 0);
}
});
Object.defineProperty(d,"nLog",{
get: function(){
return MistVideo.logs.length;
}
});
Object.defineProperty(d,"videoHeight",{
get: function(){
return MistVideo.video.videoHeight;
}
});
Object.defineProperty(d,"videoWidth",{
get: function(){
return MistVideo.video.videoWidth;
}
});
Object.defineProperty(d,"playerHeight",{
get: function(){
return MistVideo.video.clientHeight;
}
});
Object.defineProperty(d,"playerWidth",{
get: function(){
return MistVideo.video.clientWidth;
}
});
MistUtil.event.addListener(video,"waiting",function(){
timeWaiting = d.timeWaiting; //in case we get waiting several times in a row
waitingSince = (new Date()).getTime();
});
MistUtil.event.addListener(video,"stalled",function(){
timeStalled = d.timeStalled; //in case we get stalled several times in a row
stalledSince = (new Date()).getTime();
});
var events = ["playing","pause"];
for (var i in events) {
MistUtil.event.addListener(video,events[i],function(){
timeWaiting = d.timeWaiting;
timeStalled = d.timeStalled;
waitingSince = false;
stalledSince = false;
});
}
MistUtil.event.addListener(video,"playing",function(){
timeUnpaused = d.timeUnpaused; //in case we get playing several times in a row
unpausedSince = (new Date()).getTime();
});
MistUtil.event.addListener(video,"pause",function(){
timeUnpaused = d.timeUnpaused;
unpausedSince = false;
});
}
//periodically send the gathered stats
this.reportStats();
}
};
}
};
socket.onclose = function(e){
if (this.die) {
@ -1063,7 +1267,7 @@ function MistVideo(streamName,options) {
if ("source" in diff) {
if ("error" in MistVideo.info) {
MistVideo.reload();
MistVideo.reload("Reloading, stream info has error");
}
return;
}
@ -1090,11 +1294,11 @@ function MistVideo(streamName,options) {
}
break;
}
case "width":
case "height": {
resized = true;
break;
}
case "width":
case "height": {
resized = true;
break;
}
}
}
@ -1111,6 +1315,8 @@ function MistVideo(streamName,options) {
}
});
}
openSocket();
}
@ -1118,7 +1324,7 @@ function MistVideo(streamName,options) {
openWithGet();
}
this.unload = function(){
this.unload = function(reason){
if (this.destroyed) { return; }
this.log("Unloading..");
@ -1138,6 +1344,9 @@ function MistVideo(streamName,options) {
MistVideo.monitor.destroy();
}
if (this.socket) {
if (this.reporting) {
this.reporting.report({unload:reason ? reason : null});
}
this.socket.destroy();
}
if ((this.player) && (this.player.api)) {
@ -1173,17 +1382,17 @@ function MistVideo(streamName,options) {
delete this.video;
};
this.reload = function(){
this.reload = function(reason){
var time = ("player" in this && "api" in this.player ? this.player.api.currentTime : false);
this.unload();
MistVideo = mistPlay(this.stream,this.options);
this.unload(reason);
var NewMistVideo = mistPlay(this.stream,this.options);
if ((time) && (this.info.type != "live")) {
//after load, try to restore the video position
var f = function(){
if (MistVideo.player && MistVideo.player.api) {
MistVideo.player.api.currentTime = time;
if (NewMistVideo.player && NewMistVideo.player.api) {
NewMistVideo.player.api.currentTime = time;
}
this.removeEventListener("initialized",f);
};
@ -1212,7 +1421,7 @@ function MistVideo(streamName,options) {
}
}
this.unload();
this.unload("nextCombo");
var opts = this.options;
opts.startCombo = startCombo;
MistVideo = mistPlay(this.stream,opts);

View file

@ -357,9 +357,12 @@ MistSkins["default"] = {
var promise = MistVideo.player.api.play();
if (promise) {
promise.catch(function(){
promise.then(function(){
if (MistVideo.reporting) { MistVideo.reporting.stats.d.autoplay = "success"; }
}).catch(function(){
if (MistVideo.destroyed) { return; }
MistVideo.log("Autoplay failed even with muted video. Unmuting and showing play button.");
if (MistVideo.reporting) { MistVideo.reporting.stats.d.autoplay = "failed"; }
MistVideo.player.api.muted = false;
//play has failed
@ -388,6 +391,8 @@ MistSkins["default"] = {
MistVideo.log("Autoplay worked! Video will be unmuted on mouseover if the page has been interacted with.");
if (MistVideo.reporting) { MistVideo.reporting.stats.d.autoplay = "muted"; }
//show large "muted" icon
var largeMutedButton = MistVideo.skin.icons.build("muted",100);
MistUtil.class.add(largeMutedButton,"mistvideo-pointer");
@ -432,9 +437,11 @@ MistSkins["default"] = {
},function(){});
}
}
else if (MistVideo.reporting) { MistVideo.reporting.stats.d.autoplay = "failed"; }
});
}
}
else if (MistVideo.reporting) { MistVideo.reporting.stats.d.autoplay = "success"; }
});
}
@ -1046,7 +1053,7 @@ MistSkins["default"] = {
},button);
//apply initial video state
var initevent = MistUtil.event.addListener(video,"loadstart",function(){
var initevent = MistUtil.event.addListener(video,"loadedmetadata",function(){
if (('localStorage' in window) && (localStorage != null) && ('mistVolume' in localStorage)) {
MistVideo.player.api.volume = localStorage['mistVolume'];
}
@ -1953,7 +1960,7 @@ MistSkins["default"] = {
type: "button",
label: "Reload player",
onclick: function(){
MistVideo.reload();
MistVideo.reload("Reloading because reload button was clicked.");
}
};
if (!isNaN(options.reload+"")) { obj.delay = options.reload; }
@ -2470,7 +2477,7 @@ MistSkins.dev = {
MistUtil.event.addListener(select,"change",function(){
MistVideo.options.forcePlayer = (this.value == "" ? false : this.value);
if (MistVideo.options.forcePlayer != MistVideo.playerName) { //only reload if there is a change
MistVideo.reload();
MistVideo.reload("Reloading to force player.");
}
});
@ -2512,7 +2519,7 @@ MistSkins.dev = {
MistUtil.event.addListener(select,"change",function(){
MistVideo.options.forceType = (this.value == "" ? false : this.value);
if ((!MistVideo.source) || (MistVideo.options.forceType != MistVideo.source.type)) { //only reload if there is a change
MistVideo.reload();
MistVideo.reload("Reloading to force new type.");
}
});
@ -2546,7 +2553,7 @@ MistSkins.dev = {
MistUtil.event.addListener(select,"change",function(){
MistVideo.options.forceSource = (this.value == "" ? false : this.value);
if (MistVideo.options.forceSource != MistVideo.source.index) { //only reload if there is a change
MistVideo.reload();
MistVideo.reload("Reloading to force new source.");
}
});
@ -2601,7 +2608,7 @@ MistSkins.dev.structure.submenu.children.unshift({
title: "Build MistVideo again",
label: "MistVideo.reload();",
onclick: function(){
this.reload();
this.reload("Dev-reload button clicked.");
}
},{
type: "button",

View file

@ -37,7 +37,7 @@ var MistUtil = {
return string.charAt(0).toUpperCase()+string.slice(1);
},
number: function(num) {
if ((isNaN(Number(num))) || (num == 0)) { return num; }
if ((isNaN(Number(num))) || (Number(num) == 0)) { return num; }
//rounding
//use a significance of three, but don't round "visible" digits
@ -58,10 +58,10 @@ var MistUtil = {
return num;
},
bytes: function(val){
bytes: function(val,bits){
if (isNaN(Number(val))) { return val; }
var suffix = ["bytes","KB","MB","GB","TB","PB"];
var suffix = bits ? ["bits","Kb","Mb","Gb","Tb","Pb"] : ["bytes","KB","MB","GB","TB","PB"];
if (val == 0) {
unit = suffix[0];
}
@ -77,6 +77,7 @@ var MistUtil = {
}
return this.number(val)+unit;
},
bits: function(val) { return this.bytes(val,true); },
mime2human: function(mime){
switch (mime) {
case "html5/video/webm": {

View file

@ -333,14 +333,45 @@ p.prototype.build = function (MistVideo,callback) {
this.ws = new WebSocket(MistVideo.source.url);
this.ws.binaryType = "arraybuffer";
this.ws.s = this.ws.send;
this.ws.send = function(){
if (this.readyState == 1) {
return this.s.apply(this,arguments);
}
return false;
};
this.ws.onopen = function(){
this.wasConnected = true;
resolve();
};
this.ws.onerror = function(e){
MistVideo.showError("MP4 over WS: websocket error");
}
};
this.ws.onclose = function(e){
MistVideo.log("MP4 over WS: websocket closed");
if (this.wasConnected && (!MistVideo.destroyed)) {
MistVideo.log("MP4 over WS: reopening websocket");
player.wsconnect().then(function(){
if (!player.sb) {
//retrieve codec info
var f = function(msg){
//got codec data, set up source buffer
if (!player.sb) { player.sbinit(msg.data.codecs); }
else { player.api.play(); }
player.ws.removeListener("codec_data",f);
};
player.ws.addListener("codec_data",f);
send({type:"request_codec_data",supported_codecs:MistVideo.source.supportedCodecs});
}
else {
player.api.play();
}
},function(){
Mistvideo.error("Lost connection to the Media Server");
});
}
};
this.ws.listeners = {}; //kind of event listener list for websocket messages
this.ws.addListener = function(type,f){
@ -367,6 +398,7 @@ p.prototype.build = function (MistVideo,callback) {
//the last fragment has been added to the buffer
var eObj;
eObj = MistUtil.event.addListener(video,"waiting",function(e){
player.sb.paused = true;
MistUtil.event.send("ended",null,video);
MistUtil.event.removeListener(eObj);
});
@ -378,7 +410,7 @@ p.prototype.build = function (MistVideo,callback) {
var serverDelay = player.ws.serverDelay.get();
var desiredBuffer = Math.max(500+serverDelay,serverDelay*2);
if (MistVideo.info.type != "live") { desiredBuffer += 2000; } //if VoD, keep an extra 2 seconds of buffer
if (player.debugging) console.log("on_time received",msg.data.current/1e3,"currtime",video.currentTime,requested_rate+"x","buffer",Math.round(buffer),"/",Math.round(desiredBuffer),(MistVideo.info.type == "live" ? "latency:"+Math.round(msg.data.end-video.currentTime*1e3)+"ms" : ""),"listeners",player.ws.listeners && player.ws.listeners.on_time ? player.ws.listeners.on_time : 0,"msgqueue",player.msgqueue ? player.msgqueue.length : 0,msg.data);
if (player.debugging) console.log("on_time received",msg.data.current/1e3,"currtime",video.currentTime,requested_rate+"x","buffer",Math.round(buffer),"/",Math.round(desiredBuffer),(MistVideo.info.type == "live" ? "latency:"+Math.round(msg.data.end-video.currentTime*1e3)+"ms" : ""),"bitrate:"+MistUtil.format.bits(player.monitor.currentBps)+"/s","listeners",player.ws.listeners && player.ws.listeners.on_time ? player.ws.listeners.on_time : 0,"msgqueue",player.msgqueue ? player.msgqueue.length : 0,msg.data);
if (!player.sb) {
MistVideo.log("Received on_time, but the source buffer is being cleared right now. Ignoring.");
@ -466,6 +498,11 @@ p.prototype.build = function (MistVideo,callback) {
}
}
}
if (MistVideo.reporting && msg.data.tracks) {
MistVideo.reporting.stats.d.tracks = msg.data.tracks.join(",");
}
break;
}
case "tracks": {
@ -605,6 +642,9 @@ p.prototype.build = function (MistVideo,callback) {
}
var data = new Uint8Array(e.data);
if (data) {
for (var i in player.monitor.bitCounter) {
player.monitor.bitCounter[i] += e.data.byteLength*8;
}
if ((player.sb) && (!player.msgqueue)) {
if (player.sb.updating || player.sb.queue.length || player.sb._busy) {
player.sb.queue.push(data);
@ -755,6 +795,7 @@ p.prototype.build = function (MistVideo,callback) {
}
else if (e.data.current > video.currentTime) {
player.sb.paused = false;
video.currentTime = e.data.current*1e-3;
video.play().then(resolve).catch(reject);
player.ws.removeListener("on_time",f);
}
@ -768,15 +809,13 @@ p.prototype.build = function (MistVideo,callback) {
},
pause: function(){
video.pause();
send({type: "hold",});
send({type: "hold"});
if (player.sb) { player.sb.paused = true; }
},
setTracks: function(obj){
obj.type = "tracks";
obj = MistUtil.object.extend({
type: "tracks",
audio: null,
video: null,
seek_time: Math.max(0,video.currentTime*1e3-(500+player.ws.serverDelay.get()))
},obj);
send(obj);
@ -791,9 +830,7 @@ p.prototype.build = function (MistVideo,callback) {
//it's okay if it fails
} catch (e) { }
});
player.ws.close();
delete window.mistMewsOnVisibilityChange;
document.removeEventListener("visibilitychange",onVisibilityChange);
player.ws.close();
}
};
@ -881,32 +918,6 @@ p.prototype.build = function (MistVideo,callback) {
});
}
});
//pause if tab is hidden to prevent buildup of frames
var autopaused = false;
//only add this once!
function onVisibilityChange() {
if (document.hidden) {
//check if we are playing (not video.paused! that already returns true)
if (!player.sb.paused) {
player.api.pause();
autopaused = true;
if (MistVideo.info.type == "live") {
autopaused = "live"; //go to live point
//NB: even if the player wasn't near the live point when it was paused, we've likely exited the buffer while we were paused, so the current position probably won't exist anymore. Just skip to live.
}
MistVideo.log("Pausing the player as the tab is inactive.");
}
}
else if (autopaused) {
player.api.play(autopaused == "live");
autopaused = false;
MistVideo.log("Restarting the player as the tab is now active again.");
}
}
if (!window.mistMewsOnVisibilityChange) {
window.mistMewsOnVisibilityChange = true;
document.addEventListener("visibilitychange",onVisibilityChange);
}
var seeking = false;
MistUtil.event.addListener(video,"seeking",function(){
@ -927,6 +938,19 @@ p.prototype.build = function (MistVideo,callback) {
}
}
});
MistUtil.event.addListener(video,"pause",function(){
if (player.sb && !player.sb.paused) {
MistVideo.log("The browser paused the vid - probably because it has no audio and the tab is no longer visible. Pausing download.");
send({type:"hold"});
player.sb.paused = true;
var p = MistUtil.event.addListener(video,"play",function(){
if (player.sb && player.sb.paused) {
send({type:"play"});
}
MistUtil.event.removeListener(p);
});
}
});
if (player.debugging) {
MistUtil.event.addListener(video,"waiting",function(){
@ -949,4 +973,54 @@ p.prototype.build = function (MistVideo,callback) {
});
}
//ABR: monitor playback issues and switch to lower bitrate track if available
this.monitor = {
bitCounter: [],
bitsSince: [],
currentBps: null,
nWaiting: 0,
nWaitingThreshold: 3,
listener: MistUtil.event.addListener(video,"waiting",function(){
player.monitor.nWaiting++;
if (player.monitor.nWaiting >= player.monitor.nWaitingThreshold) {
player.monitor.nWaiting = 0;
MistVideo.log("ABR threshold triggered, requesting lower quality");
player.monitor.action();
}
}),
getBitRate: function(){
if (player.sb && !player.sb.paused) {
this.bitCounter.push(0);
this.bitsSince.push(new Date().getTime());
//calculate current bitrate
var bits, since;
if (this.bitCounter.length > 5) {
bits = player.monitor.bitCounter.shift();
since = this.bitsSince.shift();
}
else {
bits = player.monitor.bitCounter[0];
since = this.bitsSince[0];
}
var dt = new Date().getTime() - since;
this.currentBps = bits / (dt*1e-3);
//console.log(MistUtil.format.bytes(this.currentBps)+"its/s");
}
MistVideo.timers.start(function(){
player.monitor.getBitRate();
},500);
},
action: function(){
player.api.setTracks({video:"max<"+Math.round(this.currentBps)+"bps"});
}
};
this.monitor.getBitRate();
};

View file

@ -157,6 +157,10 @@ p.prototype.build = function (MistVideo,callback) {
currenttracks = ev.tracks;
}
if (MistVideo.reporting && ev.tracks) {
MistVideo.reporting.stats.d.tracks = ev.tracks.join(",");
}
},
on_seek: function(e){
var thisPlayer = this;
@ -276,7 +280,7 @@ p.prototype.build = function (MistVideo,callback) {
if (callback) { callback(); }
};
thisWebRTCPlayer.peerConn.onconnectionstatechange = function(e){
if (this.destroyed) { return; } //the player doesn't exist any more
if (MistVideo.destroyed) { return; } //the player doesn't exist any more
switch (this.connectionState) {
case "failed": {
//WebRTC will never work (firewall maybe?)
@ -296,7 +300,7 @@ p.prototype.build = function (MistVideo,callback) {
}
};
thisWebRTCPlayer.peerConn.oniceconnectionstatechange = function(e){
if (this.destroyed) { return; } //the player doesn't exist any more
if (MistVideo.destroyed) { return; } //the player doesn't exist any more
switch (this.iceConnectionState) {
case "failed": {
MistVideo.showError("ICE connection "+this.iceConnectionState);
@ -722,6 +726,7 @@ p.prototype.build = function (MistVideo,callback) {
try {
me.webrtc.stop();
me.webrtc.signaling.ws.close();
me.webrtc.peerConn.close();
} catch (e) {}
};