Embed: more fixes to do with showing the correct seek window in the progress bar

Embed: display more consistent timestamps across protocols and correctly show seekable range
Embed: fix: don't force nonexistent forcePlayer value
Embed: when info.unixoffset is known, display clock time based timestamps
This commit is contained in:
Cat 2021-10-20 13:46:10 +02:00 committed by Thulinma
parent c542155e10
commit 67d992e352
12 changed files with 178 additions and 22 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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -35,6 +35,7 @@ function MistVideo(streamName,options) {
maxheight: false, //no max height (apart from targets dimensions) maxheight: false, //no max height (apart from targets dimensions)
ABR_resize: true, //for supporting wrappers: when the player resizes, request a video track that matches the resolution best ABR_resize: true, //for supporting wrappers: when the player resizes, request a video track that matches the resolution best
ABR_bitrate: true, //for supporting wrappers: when there are playback issues, request a lower bitrate video track ABR_bitrate: true, //for supporting wrappers: when there are playback issues, request a lower bitrate video track
useDateTime: true, //when the unix timestamp of the stream is known, display the date/time
MistVideoObject: false//no reference object is passed MistVideoObject: false//no reference object is passed
},options); },options);
if (options.host) { options.host = MistUtil.http.url.sanitizeHost(options.host); } if (options.host) { options.host = MistUtil.http.url.sanitizeHost(options.host); }
@ -144,7 +145,7 @@ function MistVideo(streamName,options) {
for (var i in mistplayers) { for (var i in mistplayers) {
mistplayers[i].shortname = i; mistplayers[i].shortname = i;
} }
if (options.forcePlayer) { if (options.forcePlayer && mistplayers[options.forcePlayer]) {
players = [mistplayers[options.forcePlayer]]; players = [mistplayers[options.forcePlayer]];
MistVideo.log("Forcing player "+options.forcePlayer); MistVideo.log("Forcing player "+options.forcePlayer);
} }

View file

@ -771,6 +771,38 @@ MistSkins["default"] = {
var video = this.video; var video = this.video;
var MistVideo = this; var MistVideo = this;
var first = Infinity;
if (MistVideo.info && MistVideo.info.meta && MistVideo.info.meta.tracks) {
for (var i in MistVideo.info.meta.tracks) {
if (MistVideo.info.meta.tracks[i].firstms*1e-3 < first) {
first = MistVideo.info.meta.tracks[i].firstms*1e-3;
}
}
}
if (first == Infinity) {
first = 0;
}
function firsts() {
if (MistVideo.player.api.duration < first) { return 0; }
return first;
}
function getBufferWindow() {
var buffer_window = MistVideo.info.meta.buffer_window;
if (typeof buffer_window == "undefined") {
//for some reason, buffer_window is not defined (liveDVR?)
//just assume we can seek in our own buffer
if (MistVideo.player && MistVideo.player.api && MistVideo.player.api.buffered && MistVideo.player.api.buffered.length) {
buffer_window = (MistVideo.player.api.duration - MistVideo.player.api.buffered.start(0))*1e3;
}
else {
//no buffer of our own either? we'll just assume we have a minute
buffer_window = 60e3;
}
}
return buffer_window *= 1e-3;
}
//these functions update the states //these functions update the states
container.updateBar = function(currentTime){ container.updateBar = function(currentTime){
if (this.kids.bar) { if (this.kids.bar) {
@ -785,11 +817,11 @@ MistSkins["default"] = {
if (!isFinite(MistVideo.player.api.duration)) { return 0; } if (!isFinite(MistVideo.player.api.duration)) { return 0; }
var result = 0; var result = 0;
if (MistVideo.info.type == "live") { if (MistVideo.info.type == "live") {
var buffer_window = MistVideo.info.meta.buffer_window * 1e-3; var buffer_window = getBufferWindow();
result = (time - MistVideo.player.api.duration + buffer_window) / buffer_window; result = (time - MistVideo.player.api.duration + buffer_window) / buffer_window;
} }
else { else {
result = time / MistVideo.player.api.duration; result = (time - firsts()) / (MistVideo.player.api.duration - firsts());
} }
return Math.min(1,Math.max(0,result)); return Math.min(1,Math.max(0,result));
} }
@ -865,14 +897,14 @@ MistSkins["default"] = {
var perc = MistUtil.getPos(this,e); var perc = MistUtil.getPos(this,e);
if (MistVideo.info.type == "live") { if (MistVideo.info.type == "live") {
//live mode: seek in DVR window //live mode: seek in DVR window
var bufferWindow = MistVideo.info.meta.buffer_window * 1e-3; //buffer window in seconds var bufferWindow = getBufferWindow();
//assuming the "right" part or the progressbar is at true live //assuming the "right" part or the progressbar is at true live
return (perc-1) * bufferWindow + MistVideo.player.api.duration; return (perc-1) * bufferWindow + MistVideo.player.api.duration;
} }
else { else {
//VOD mode //VOD mode
if (!isFinite(MistVideo.player.api.duration)) { return false; } if (!isFinite(MistVideo.player.api.duration)) { return false; }
return perc * MistVideo.player.api.duration; return perc * (MistVideo.player.api.duration - firsts()) + firsts();
} }
}; };
//seeking //seeking
@ -887,6 +919,7 @@ MistSkins["default"] = {
//hovering //hovering
var tooltip = MistVideo.UI.buildStructure({type:"tooltip"}); var tooltip = MistVideo.UI.buildStructure({type:"tooltip"});
tooltip.style.opacity = 0; tooltip.style.opacity = 0;
container.appendChild(tooltip); container.appendChild(tooltip);
MistUtil.event.addListener(margincontainer,"mouseout",function(){ MistUtil.event.addListener(margincontainer,"mouseout",function(){
@ -899,9 +932,16 @@ MistSkins["default"] = {
tooltip.style.opacity = 0; tooltip.style.opacity = 0;
return; return;
} }
tooltip.setText(MistUtil.format.time(secs));
if (MistVideo.options.useDateTime && MistVideo.info && MistVideo.info.unixoffset) {
tooltip.setText(MistUtil.format.ago(new Date(MistVideo.info.unixoffset + secs*1e3)));
}
else {
tooltip.setText(MistUtil.format.time(secs));
}
tooltip.style.opacity = 1; tooltip.style.opacity = 1;
var perc = MistUtil.getPos(this,e);// e.clientX - this.getBoundingClientRect().left; var perc = MistUtil.getPos(this,e);// e.clientX - this.getBoundingClientRect().left;
var pos = {bottom:20}; var pos = {bottom:20};
//if the cursor is on the left side of the progress bar, show tooltip on the right of the cursor, otherwise, show it on the left side //if the cursor is on the left side of the progress bar, show tooltip on the right of the cursor, otherwise, show it on the left side
@ -1193,8 +1233,18 @@ MistSkins["default"] = {
var formatTime = MistUtil.format.time; var formatTime = MistUtil.format.time;
container.set = function(){ container.set = function(){
var v = MistVideo.player.api.currentTime; var v = MistVideo.player.api.currentTime;
text.nodeValue = formatTime(v); var t;
container.setAttribute("title",text.nodeValue); if (MistVideo.options.useDateTime && MistVideo.info && MistVideo.info.unixoffset) {
var d = new Date(MistVideo.info.unixoffset + v*1e3);
t = MistUtil.format.ago(d);
container.setAttribute("title",MistUtil.format.ago(d,34560e6));
}
else {
t = formatTime(v);
container.setAttribute("title",t);
}
text.nodeValue = t;
}; };
container.set(); container.set();
@ -1226,7 +1276,16 @@ MistSkins["default"] = {
return; return;
} }
this.style.display = ""; this.style.display = "";
text.nodeValue = MistUtil.format.time(duration);
if (MistVideo.options.useDateTime && MistVideo.info && MistVideo.info.unixoffset) {
var t = new Date(duration*1e3 + MistVideo.info.unixoffset)
text.nodeValue = MistUtil.format.ago(t);
container.setAttribute("title",MistUtil.format.ago(t,34560e6)); //format as if more than a year ago
}
else {
text.nodeValue = MistUtil.format.time(duration);
container.setAttribute("title",text.nodeValue);
}
}; };
MistUtil.event.addListener(MistVideo.video,"durationchange",function(){ MistUtil.event.addListener(MistVideo.video,"durationchange",function(){

View file

@ -33,6 +33,73 @@ var MistUtil = {
return (days ? days : "")+str.join(":")+ago; return (days ? days : "")+str.join(":")+ago;
}, },
ago: function(date,range){
//format a date nicely depending on how long ago it was
//if the range param [ms] is specified, use that to choose how to format the date string
var ago = range ? range : new Date().getTime() - date.getTime();
var out = "";
var negative = (ago < 0);
if (negative) { ago *= -1; }
if (ago < 1000) {
//less than a second ago
out = "live";
}
else if (ago < 60e3) {
//less than a minute ago
out = Math.round(ago/1e3)+" sec";
if (negative) {
out = "in "+out;
}
else {
out += " ago";
}
}
else if ((!range && (new Date().toLocaleDateString() == date.toLocaleDateString())) || (range < 86400e3)) {
//today
out = date.toLocaleTimeString(undefined,{
hour: "numeric",
minute: "2-digit",
second: "2-digit"
});
}
else if (ago < 518400e3) {
//less than 6 days ago
out = date.toLocaleString(undefined,{
weekday: "short",
hour: "numeric",
minute: "2-digit",
second: "2-digit"
});
}
else if ((!range && (new Date().getFullYear() == date.getFullYear())) || (range < 31622400e3)) {
//this year
out = date.toLocaleString(undefined,{
month: "short",
day: "numeric",
weekday: "short",
hour: "numeric",
minute: "2-digit",
second: "2-digit"
});
}
else {
//before this year
out = date.toLocaleString(undefined,{
year: "numeric",
month: "short",
day: "numeric",
hour: "numeric",
minute: "2-digit",
second: "2-digit"
});
}
return out;
},
ucFirst: function(string){ ucFirst: function(string){
return string.charAt(0).toUpperCase()+string.slice(1); return string.charAt(0).toUpperCase()+string.slice(1);
}, },

View file

@ -195,6 +195,7 @@ p.prototype.build = function (MistVideo,callback) {
MistVideo.player.api.setSource(MistUtil.http.url.addParam(MistVideo.source.url,params)); MistVideo.player.api.setSource(MistUtil.http.url.addParam(MistVideo.source.url,params));
} }
MistUtil.event.addListener(video,"progress",function(){ MistUtil.event.addListener(video,"progress",function(){
MistVideo.player.api.lastProgress = new Date(); MistVideo.player.api.lastProgress = new Date();
}); });
@ -217,9 +218,21 @@ p.prototype.build = function (MistVideo,callback) {
}; };
if (MistVideo.source.type == "html5/video/mp4") { if (MistVideo.source.type == "html5/video/mp4") {
var otherdurationoverride = overrides.get.duration;
overrides.get.duration = function(){
return otherdurationoverride.apply(this,arguments) - MistVideo.player.api.liveOffset + MistVideo.info.lastms * 1e-3;
}
overrides.get.currentTime = function(){ overrides.get.currentTime = function(){
return this.currentTime - MistVideo.player.api.liveOffset + MistVideo.info.lastms * 1e-3; return this.currentTime - MistVideo.player.api.liveOffset + MistVideo.info.lastms * 1e-3;
} }
overrides.get.buffered = function(){
var video = this;
return {
length: video.buffered.length,
start: function(i) { return video.buffered.start(i) - MistVideo.player.api.liveOffset + MistVideo.info.lastms * 1e-3; },
end: function(i) { return video.buffered.end(i) - MistVideo.player.api.liveOffset + MistVideo.info.lastms * 1e-3; }
}
};
} }
} }
else { else {

View file

@ -1067,7 +1067,7 @@ p.prototype.build = function (MistVideo,callback) {
} }
}); });
//override duration //override duration
var lastduration = 1; var lastduration = Infinity;
Object.defineProperty(this.api,"duration",{ Object.defineProperty(this.api,"duration",{
get: function(){ get: function(){
return lastduration; return lastduration;
@ -1079,7 +1079,7 @@ p.prototype.build = function (MistVideo,callback) {
}, },
set: function(value){ set: function(value){
var f = function(msg){ var f = function(msg){
video.playbackRate = msg.data.play_rate; video.playbackRate = msg.data.play_rate_curr;
}; };
player.ws.addListener("set_speed",f); player.ws.addListener("set_speed",f);
send({type: "set_speed", play_rate: (value == 1 ? "auto" : value)}); send({type: "set_speed", play_rate: (value == 1 ? "auto" : value)});

View file

@ -100,6 +100,14 @@ p.prototype.build = function (MistVideo,callback) {
MistVideo.log("Building videojs"); MistVideo.log("Building videojs");
me.videojs = videojs(ele,vjsopts,function(){ me.videojs = videojs(ele,vjsopts,function(){
MistVideo.log("Videojs initialized"); MistVideo.log("Videojs initialized");
if (MistVideo.info.type == "live") {
//overwrite the stream info's buffer window to the seekable range as indicated by the m3u8
MistUtil.event.addListener(ele,"progress",function(e){
var i = MistVideo.player.videojs.seekable().length-1;
MistVideo.info.meta.buffer_window = (Math.max(MistVideo.player.videojs.seekable().end(i),ele.duration) - MistVideo.player.videojs.seekable().start(i))*1e3;
});
}
}); });
MistUtil.event.addListener(ele,"error",function(e){ MistUtil.event.addListener(ele,"error",function(e){
@ -160,6 +168,7 @@ p.prototype.build = function (MistVideo,callback) {
}; };
if (MistVideo.info.type == "live") { if (MistVideo.info.type == "live") {
function getLastBuffer(video) { function getLastBuffer(video) {
var buffer_end = 0; var buffer_end = 0;
if (video.buffered.length) { if (video.buffered.length) {
@ -171,8 +180,7 @@ p.prototype.build = function (MistVideo,callback) {
overrides.get.duration = function(){ overrides.get.duration = function(){
if (MistVideo.info) { if (MistVideo.info) {
var duration = (MistVideo.info.lastms + (new Date()).getTime() - MistVideo.info.updated.getTime())*1e-3; var duration = ele.duration;
//if (isNaN(duration)) { return 1e9; }
return duration; return duration;
} }
return 0; return 0;
@ -186,19 +194,25 @@ p.prototype.build = function (MistVideo,callback) {
overrides.set.currentTime = function(value){ overrides.set.currentTime = function(value){
var diff = MistVideo.player.api.currentTime - value; var diff = MistVideo.player.api.currentTime - value;
var offset = value - MistVideo.player.api.duration; var offset = value - MistVideo.player.api.duration;
//MistVideo.player.api.liveOffset = offset;
MistVideo.log("Seeking to "+MistUtil.format.time(value)+" ("+Math.round(offset*-10)/10+"s from live)"); MistVideo.log("Seeking to "+MistUtil.format.time(value)+" ("+Math.round(offset*-10)/10+"s from live)");
//MistVideo.video.currentTime -= diff; MistVideo.player.videojs.currentTime(MistVideo.video.currentTime - diff);
MistVideo.player.videojs.currentTime(MistVideo.video.currentTime - diff); //seeking backwards does not work if we set it on the video directly
} }
var lastms = 0; var lastms = 0;
overrides.get.currentTime = function(){ overrides.get.currentTime = function(){
if (MistVideo.info) { lastms = MistVideo.info.lastms*1e-3; } if (MistVideo.info) { lastms = MistVideo.info.lastms*1e-3; }
var time = this.currentTime + lastms - MistVideo.player.api.liveOffset - HLSlatency; var time = MistVideo.player.videojs ? MistVideo.player.videojs.currentTime() : ele.currentTime;
if (isNaN(time)) { return 0; } if (isNaN(time)) { return 0; }
return time; return time;
} }
overrides.get.buffered = function(){
var buffered = MistVideo.player.videojs ? MistVideo.player.videojs.buffered() : ele.buffered;
return {
length: buffered.length,
start: function(i) { return buffered.start(i); },
end: function(i) { return buffered.end(i); i}
}
};
} }
} }
else { else {

View file

@ -130,6 +130,8 @@ p.prototype.build = function (MistVideo,callback) {
MistUtil.event.send("durationchange",d,video); MistUtil.event.send("durationchange",d,video);
} }
MistVideo.info.meta.buffer_window = ev.end - ev.begin;
if ((ev.tracks) && (currenttracks != ev.tracks)) { if ((ev.tracks) && (currenttracks != ev.tracks)) {
var tracks = MistVideo.info ? MistUtil.tracks.parse(MistVideo.info.meta.tracks) : []; var tracks = MistVideo.info ? MistUtil.tracks.parse(MistVideo.info.meta.tracks) : [];
for (var i in ev.tracks) { for (var i in ev.tracks) {