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:
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
|
@ -35,6 +35,7 @@ function MistVideo(streamName,options) {
|
|||
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_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
|
||||
},options);
|
||||
if (options.host) { options.host = MistUtil.http.url.sanitizeHost(options.host); }
|
||||
|
@ -144,7 +145,7 @@ function MistVideo(streamName,options) {
|
|||
for (var i in mistplayers) {
|
||||
mistplayers[i].shortname = i;
|
||||
}
|
||||
if (options.forcePlayer) {
|
||||
if (options.forcePlayer && mistplayers[options.forcePlayer]) {
|
||||
players = [mistplayers[options.forcePlayer]];
|
||||
MistVideo.log("Forcing player "+options.forcePlayer);
|
||||
}
|
||||
|
|
|
@ -770,6 +770,38 @@ MistSkins["default"] = {
|
|||
|
||||
var video = this.video;
|
||||
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
|
||||
container.updateBar = function(currentTime){
|
||||
|
@ -785,11 +817,11 @@ MistSkins["default"] = {
|
|||
if (!isFinite(MistVideo.player.api.duration)) { return 0; }
|
||||
var result = 0;
|
||||
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;
|
||||
}
|
||||
else {
|
||||
result = time / MistVideo.player.api.duration;
|
||||
result = (time - firsts()) / (MistVideo.player.api.duration - firsts());
|
||||
}
|
||||
return Math.min(1,Math.max(0,result));
|
||||
}
|
||||
|
@ -865,14 +897,14 @@ MistSkins["default"] = {
|
|||
var perc = MistUtil.getPos(this,e);
|
||||
if (MistVideo.info.type == "live") {
|
||||
//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
|
||||
return (perc-1) * bufferWindow + MistVideo.player.api.duration;
|
||||
}
|
||||
else {
|
||||
//VOD mode
|
||||
if (!isFinite(MistVideo.player.api.duration)) { return false; }
|
||||
return perc * MistVideo.player.api.duration;
|
||||
return perc * (MistVideo.player.api.duration - firsts()) + firsts();
|
||||
}
|
||||
};
|
||||
//seeking
|
||||
|
@ -887,6 +919,7 @@ MistSkins["default"] = {
|
|||
|
||||
//hovering
|
||||
var tooltip = MistVideo.UI.buildStructure({type:"tooltip"});
|
||||
|
||||
tooltip.style.opacity = 0;
|
||||
container.appendChild(tooltip);
|
||||
MistUtil.event.addListener(margincontainer,"mouseout",function(){
|
||||
|
@ -899,8 +932,15 @@ MistSkins["default"] = {
|
|||
tooltip.style.opacity = 0;
|
||||
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;
|
||||
|
||||
|
||||
var perc = MistUtil.getPos(this,e);// e.clientX - this.getBoundingClientRect().left;
|
||||
var pos = {bottom:20};
|
||||
|
@ -1193,8 +1233,18 @@ MistSkins["default"] = {
|
|||
var formatTime = MistUtil.format.time;
|
||||
container.set = function(){
|
||||
var v = MistVideo.player.api.currentTime;
|
||||
text.nodeValue = formatTime(v);
|
||||
container.setAttribute("title",text.nodeValue);
|
||||
var t;
|
||||
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();
|
||||
|
||||
|
@ -1226,7 +1276,16 @@ MistSkins["default"] = {
|
|||
return;
|
||||
}
|
||||
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(){
|
||||
|
|
|
@ -33,6 +33,73 @@ var MistUtil = {
|
|||
|
||||
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){
|
||||
return string.charAt(0).toUpperCase()+string.slice(1);
|
||||
},
|
||||
|
|
|
@ -195,6 +195,7 @@ p.prototype.build = function (MistVideo,callback) {
|
|||
|
||||
MistVideo.player.api.setSource(MistUtil.http.url.addParam(MistVideo.source.url,params));
|
||||
}
|
||||
|
||||
MistUtil.event.addListener(video,"progress",function(){
|
||||
MistVideo.player.api.lastProgress = new Date();
|
||||
});
|
||||
|
@ -217,9 +218,21 @@ p.prototype.build = function (MistVideo,callback) {
|
|||
};
|
||||
|
||||
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(){
|
||||
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 {
|
||||
|
|
|
@ -1067,7 +1067,7 @@ p.prototype.build = function (MistVideo,callback) {
|
|||
}
|
||||
});
|
||||
//override duration
|
||||
var lastduration = 1;
|
||||
var lastduration = Infinity;
|
||||
Object.defineProperty(this.api,"duration",{
|
||||
get: function(){
|
||||
return lastduration;
|
||||
|
@ -1079,7 +1079,7 @@ p.prototype.build = function (MistVideo,callback) {
|
|||
},
|
||||
set: function(value){
|
||||
var f = function(msg){
|
||||
video.playbackRate = msg.data.play_rate;
|
||||
video.playbackRate = msg.data.play_rate_curr;
|
||||
};
|
||||
player.ws.addListener("set_speed",f);
|
||||
send({type: "set_speed", play_rate: (value == 1 ? "auto" : value)});
|
||||
|
|
|
@ -100,6 +100,14 @@ p.prototype.build = function (MistVideo,callback) {
|
|||
MistVideo.log("Building videojs");
|
||||
me.videojs = videojs(ele,vjsopts,function(){
|
||||
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){
|
||||
|
@ -160,6 +168,7 @@ p.prototype.build = function (MistVideo,callback) {
|
|||
};
|
||||
|
||||
if (MistVideo.info.type == "live") {
|
||||
|
||||
function getLastBuffer(video) {
|
||||
var buffer_end = 0;
|
||||
if (video.buffered.length) {
|
||||
|
@ -171,8 +180,7 @@ p.prototype.build = function (MistVideo,callback) {
|
|||
|
||||
overrides.get.duration = function(){
|
||||
if (MistVideo.info) {
|
||||
var duration = (MistVideo.info.lastms + (new Date()).getTime() - MistVideo.info.updated.getTime())*1e-3;
|
||||
//if (isNaN(duration)) { return 1e9; }
|
||||
var duration = ele.duration;
|
||||
return duration;
|
||||
}
|
||||
return 0;
|
||||
|
@ -186,19 +194,25 @@ p.prototype.build = function (MistVideo,callback) {
|
|||
overrides.set.currentTime = function(value){
|
||||
var diff = MistVideo.player.api.currentTime - value;
|
||||
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.video.currentTime -= diff;
|
||||
MistVideo.player.videojs.currentTime(MistVideo.video.currentTime - diff); //seeking backwards does not work if we set it on the video directly
|
||||
MistVideo.player.videojs.currentTime(MistVideo.video.currentTime - diff);
|
||||
}
|
||||
var lastms = 0;
|
||||
overrides.get.currentTime = function(){
|
||||
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; }
|
||||
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 {
|
||||
|
|
|
@ -129,6 +129,8 @@ p.prototype.build = function (MistVideo,callback) {
|
|||
duration = d;
|
||||
MistUtil.event.send("durationchange",d,video);
|
||||
}
|
||||
|
||||
MistVideo.info.meta.buffer_window = ev.end - ev.begin;
|
||||
|
||||
if ((ev.tracks) && (currenttracks != ev.tracks)) {
|
||||
var tracks = MistVideo.info ? MistUtil.tracks.parse(MistVideo.info.meta.tracks) : [];
|
||||
|
|
Loading…
Add table
Reference in a new issue