Embed: Raw/WS (HEVC only atm) player
This commit is contained in:
parent
ac13686048
commit
86379e44eb
20 changed files with 12742 additions and 53 deletions
|
@ -653,12 +653,14 @@ add_executable(MistOutHTTP
|
|||
generated/mews.js.h
|
||||
generated/flv.js.h
|
||||
generated/hlsjs.js.h
|
||||
generated/rawws.js.h
|
||||
generated/player_dash.js.h
|
||||
generated/player_dash_lic.js.h
|
||||
generated/player_video.js.h
|
||||
generated/player_webrtc.js.h
|
||||
generated/player_flv.js.h
|
||||
generated/player_hlsjs.js.h
|
||||
generated/player_libde265.js.h
|
||||
generated/skin_default.css.h
|
||||
generated/skin_dev.css.h
|
||||
generated/skin_videojs.css.h
|
||||
|
@ -770,7 +772,10 @@ add_custom_command(OUTPUT generated/hlsjs.js.h
|
|||
COMMAND ./sourcery ${SOURCE_DIR}/embed/min/wrappers/hlsjs.js hlsjs_js generated/hlsjs.js.h
|
||||
DEPENDS sourcery ${SOURCE_DIR}/embed/min/wrappers/hlsjs.js
|
||||
)
|
||||
# players
|
||||
add_custom_command(OUTPUT generated/rawws.js.h
|
||||
COMMAND ./sourcery ${SOURCE_DIR}/embed/min/wrappers/rawws.js rawws_js generated/rawws.js.h
|
||||
DEPENDS sourcery ${SOURCE_DIR}/embed/min/wrappers/rawws.js
|
||||
)# players
|
||||
add_custom_command(OUTPUT generated/player_dash_lic.js.h
|
||||
COMMAND ./sourcery ${SOURCE_DIR}/embed/players/dash.js.license.js player_dash_lic_js generated/player_dash_lic.js.h
|
||||
DEPENDS sourcery ${SOURCE_DIR}/embed/players/dash.js.license.js
|
||||
|
@ -795,6 +800,10 @@ add_custom_command(OUTPUT generated/player_hlsjs.js.h
|
|||
COMMAND ./sourcery ${SOURCE_DIR}/embed/players/hls.js player_hlsjs_js generated/player_hlsjs.js.h
|
||||
DEPENDS sourcery ${SOURCE_DIR}/embed/players/hls.js
|
||||
)
|
||||
add_custom_command(OUTPUT generated/player_libde265.js.h
|
||||
COMMAND ./sourcery ${SOURCE_DIR}/embed/players/libde265.min.js player_libde265_js generated/player_libde265.js.h
|
||||
DEPENDS sourcery ${SOURCE_DIR}/embed/players/libde265.min.js
|
||||
)
|
||||
# css
|
||||
add_custom_command(OUTPUT generated/skin_default.css.h
|
||||
COMMAND ./sourcery ${SOURCE_DIR}/embed/min/skins/default.css skin_default_css generated/skin_default.css.h
|
||||
|
|
File diff suppressed because one or more lines are too long
|
@ -1 +1 @@
|
|||
mistplayers.dashjs={name:"Dash.js player",mimes:["dash/video/mp4"],priority:MistUtil.object.keys(mistplayers).length+1,isMimeSupported:function(e){return MistUtil.array.indexOf(this.mimes,e)==-1?false:true},isBrowserSupported:function(e,t,i){if(location.protocol!=MistUtil.http.url.split(t.url).protocol){i.log("HTTP/HTTPS mismatch for this source");return false}if(location.protocol=="file:"){i.log("This source ("+e+") won't load if the page is run via file://");return false}return"MediaSource"in window},player:function(){this.onreadylist=[]},scriptsrc:function(e){return e+"/dashjs.js"}};var p=mistplayers.dashjs.player;p.prototype=new MistPlayer;p.prototype.build=function(e,t){var i=this;this.onDashLoad=function(){if(e.destroyed){return}e.log("Building DashJS player..");var r=document.createElement("video");if("Proxy"in window){var a={get:{},set:{}};e.player.api=new Proxy(r,{get:function(e,t,i){if(t in a.get){return a.get[t].apply(e,arguments)}var r=e[t];if(typeof r==="function"){return function(){return r.apply(e,arguments)}}return r},set:function(e,t,i){if(t in a.set){return a.set[t].call(e,i)}return e[t]=i}});if(e.info.type=="live"){a.get.duration=function(){var t=0;if(this.buffered.length){t=this.buffered.end(this.buffered.length-1)}var i=((new Date).getTime()-e.player.api.lastProgress.getTime())*.001;return t+i+-1*e.player.api.liveOffset+45};a.set.currentTime=function(t){var i=t-e.player.api.duration;e.log("Seeking to "+MistUtil.format.time(t)+" ("+Math.round(i*-10)/10+"s from live)");e.video.currentTime=t};MistUtil.event.addListener(r,"progress",function(){e.player.api.lastProgress=new Date});e.player.api.lastProgress=new Date;e.player.api.liveOffset=0}}else{i.api=r}if(e.options.autoplay){r.setAttribute("autoplay","")}if(e.options.loop&&e.info.type!="live"){r.setAttribute("loop","")}if(e.options.poster){r.setAttribute("poster",e.options.poster)}if(e.options.muted){r.muted=true}if(e.options.controls=="stock"){r.setAttribute("controls","")}var s=dashjs.MediaPlayer().create();s.initialize(r,e.source.url,e.options.autoplay);i.dash=s;var o=["METRIC_ADDED","METRIC_UPDATED","METRIC_CHANGED","METRICS_CHANGED","FRAGMENT_LOADING_STARTED","FRAGMENT_LOADING_COMPLETED","LOG","PLAYBACK_TIME_UPDATED","PLAYBACK_PROGRESS"];for(var n in dashjs.MediaPlayer.events){if(o.indexOf(n)<0){i.dash.on(dashjs.MediaPlayer.events[n],function(t){e.log("Player event fired: "+t.type)})}}e.player.setSize=function(e){this.api.style.width=e.width+"px";this.api.style.height=e.height+"px"};e.player.api.setSource=function(t){e.player.dash.attachSource(t)};var l=false;i.dash.on("allTextTracksAdded",function(){l=true});e.player.api.setSubtitle=function(t){if(!l){var r=function(){e.player.api.setSubtitle(t);i.dash.off("allTextTracksAdded",r)};i.dash.on("allTextTracksAdded",r);return}if(!t){i.dash.enableText(false);return}var a=i.dash.getTracksFor("text");for(var s in a){var o="idx"in t?t.idx:t.trackid;if(a[s].id==o){i.dash.setTextTrack(s);if(!i.dash.isTextEnabled()){i.dash.enableText()}return true}}return false};MistUtil.event.addListener(r,"progress",function(t){if(e.container.getAttribute("data-loading")=="stalled"){e.container.removeAttribute("data-loading")}});i.api.unload=function(){i.dash.reset()};e.log("Built html");t(r)};if("dashjs"in window){this.onDashLoad()}else{var r=MistUtil.scripts.insert(e.urlappend(mistplayers.dashjs.scriptsrc(e.options.host)),{onerror:function(t){var i="Failed to load dashjs.js";if(t.message){i+=": "+t.message}e.showError(i)},onload:i.onDashLoad},e)}};
|
||||
mistplayers.dashjs={name:"Dash.js player",mimes:["dash/video/mp4"],priority:MistUtil.object.keys(mistplayers).length+1,isMimeSupported:function(t){return MistUtil.array.indexOf(this.mimes,t)==-1?false:true},isBrowserSupported:function(t,e,i){if(location.protocol!=MistUtil.http.url.split(e.url).protocol){i.log("HTTP/HTTPS mismatch for this source");return false}if(location.protocol=="file:"){i.log("This source ("+t+") won't load if the page is run via file://");return false}var r={};for(var a in i.info.meta.tracks){if(i.info.meta.tracks[a].type!="meta"){r[i.info.meta.tracks[a].codec]=1}}r=MistUtil.object.keys(r);for(var a=r.length-1;a>=0;a--){if(r[a].substr(0,4)=="HEVC"){r.splice(a,1)}}if(r.length<e.simul_tracks){return false}return"MediaSource"in window},player:function(){this.onreadylist=[]},scriptsrc:function(t){return t+"/dashjs.js"}};var p=mistplayers.dashjs.player;p.prototype=new MistPlayer;p.prototype.build=function(t,e){var i=this;this.onDashLoad=function(){if(t.destroyed){return}t.log("Building DashJS player..");var r=document.createElement("video");if("Proxy"in window){var a={get:{},set:{}};t.player.api=new Proxy(r,{get:function(t,e,i){if(e in a.get){return a.get[e].apply(t,arguments)}var r=t[e];if(typeof r==="function"){return function(){return r.apply(t,arguments)}}return r},set:function(t,e,i){if(e in a.set){return a.set[e].call(t,i)}return t[e]=i}});if(t.info.type=="live"){a.get.duration=function(){var e=0;if(this.buffered.length){e=this.buffered.end(this.buffered.length-1)}var i=((new Date).getTime()-t.player.api.lastProgress.getTime())*.001;return e+i+-1*t.player.api.liveOffset+45};a.set.currentTime=function(e){var i=e-t.player.api.duration;t.log("Seeking to "+MistUtil.format.time(e)+" ("+Math.round(i*-10)/10+"s from live)");t.video.currentTime=e};MistUtil.event.addListener(r,"progress",function(){t.player.api.lastProgress=new Date});t.player.api.lastProgress=new Date;t.player.api.liveOffset=0}}else{i.api=r}if(t.options.autoplay){r.setAttribute("autoplay","")}if(t.options.loop&&t.info.type!="live"){r.setAttribute("loop","")}if(t.options.poster){r.setAttribute("poster",t.options.poster)}if(t.options.muted){r.muted=true}if(t.options.controls=="stock"){r.setAttribute("controls","")}var s=dashjs.MediaPlayer().create();s.initialize(r,t.source.url,t.options.autoplay);i.dash=s;var o=["METRIC_ADDED","METRIC_UPDATED","METRIC_CHANGED","METRICS_CHANGED","FRAGMENT_LOADING_STARTED","FRAGMENT_LOADING_COMPLETED","LOG","PLAYBACK_TIME_UPDATED","PLAYBACK_PROGRESS"];for(var n in dashjs.MediaPlayer.events){if(o.indexOf(n)<0){i.dash.on(dashjs.MediaPlayer.events[n],function(e){t.log("Player event fired: "+e.type)})}}t.player.setSize=function(t){this.api.style.width=t.width+"px";this.api.style.height=t.height+"px"};t.player.api.setSource=function(e){t.player.dash.attachSource(e)};var l=false;i.dash.on("allTextTracksAdded",function(){l=true});t.player.api.setSubtitle=function(e){if(!l){var r=function(){t.player.api.setSubtitle(e);i.dash.off("allTextTracksAdded",r)};i.dash.on("allTextTracksAdded",r);return}if(!e){i.dash.enableText(false);return}var a=i.dash.getTracksFor("text");for(var s in a){var o="idx"in e?e.idx:e.trackid;if(a[s].id==o){i.dash.setTextTrack(s);if(!i.dash.isTextEnabled()){i.dash.enableText()}return true}}return false};MistUtil.event.addListener(r,"progress",function(e){if(t.container.getAttribute("data-loading")=="stalled"){t.container.removeAttribute("data-loading")}});i.api.unload=function(){i.dash.reset()};t.log("Built html");e(r)};if("dashjs"in window){this.onDashLoad()}else{var r=MistUtil.scripts.insert(t.urlappend(mistplayers.dashjs.scriptsrc(t.options.host)),{onerror:function(e){var i="Failed to load dashjs.js";if(e.message){i+=": "+e.message}t.showError(i)},onload:i.onDashLoad},t)}};
|
|
@ -1 +1 @@
|
|||
mistplayers.hlsjs={name:"HLS.js player",mimes:["html5/application/vnd.apple.mpegurl","html5/application/vnd.apple.mpegurl;version=7"],priority:MistUtil.object.keys(mistplayers).length+1,isMimeSupported:function(e){return this.mimes.indexOf(e)==-1?false:true},isBrowserSupported:function(e,t,s){if(location.protocol!=MistUtil.http.url.split(t.url).protocol){s.log("HTTP/HTTPS mismatch for this source");return false}return true},player:function(){},scriptsrc:function(e){return e+"/hlsjs.js"}};var p=mistplayers.hlsjs.player;p.prototype=new MistPlayer;p.prototype.build=function(e,t){var s=this;var l=document.createElement("video");l.setAttribute("playsinline","");var r=["autoplay","loop","poster"];for(var i in r){var o=r[i];if(e.options[o]){l.setAttribute(o,e.options[o]===true?"":e.options[o])}}if(e.options.muted){l.muted=true}if(e.info.type=="live"){l.loop=false}if(e.options.controls=="stock"){l.setAttribute("controls","")}l.setAttribute("crossorigin","anonymous");this.setSize=function(e){l.style.width=e.width+"px";l.style.height=e.height+"px"};this.api=l;e.player.api.unload=function(){if(e.player.hls){e.player.hls.destroy();e.player.hls=false;e.log("hls.js instance disposed")}};function n(t){e.player.hls=new Hls({maxBufferLength:15,maxMaxBufferLength:60});e.player.hls.attachMedia(l);e.player.hls.on(Hls.Events.MEDIA_ATTACHED,function(){console.log("video and hls.js are now bound together !");e.player.hls.loadSource(t);e.player.hls.on(Hls.Events.MANIFEST_PARSED,function(e,t){console.log("manifest loaded, found "+t.levels.length+" quality level")})})}e.player.api.setSource=function(t){if(!e.player.hls){return}if(e.player.hls.url!=t){e.player.hls.destroy();n(t)}};e.player.api.setSubtitle=function(e){var t=l.getElementsByTagName("track");for(var s=t.length-1;s>=0;s--){l.removeChild(t[s])}if(e){var r=document.createElement("track");l.appendChild(r);r.kind="subtitles";r.label=e.label;r.srclang=e.lang;r.src=e.src;r.setAttribute("default","")}};function a(){n(e.source.url)}if("Hls"in window){a()}else{var p=e.urlappend(mistplayers.hlsjs.scriptsrc(e.options.host));MistUtil.scripts.insert(p,{onerror:function(t){var s="Failed to load hlsjs.js";if(t.message){s+=": "+t.message}e.showError(s)},onload:a},e)}t(l)};
|
||||
mistplayers.hlsjs={name:"HLS.js player",mimes:["html5/application/vnd.apple.mpegurl","html5/application/vnd.apple.mpegurl;version=7"],priority:MistUtil.object.keys(mistplayers).length+1,isMimeSupported:function(e){return this.mimes.indexOf(e)==-1?false:true},isBrowserSupported:function(e,t,s){if(location.protocol!=MistUtil.http.url.split(t.url).protocol){s.log("HTTP/HTTPS mismatch for this source");return false}var l={};for(var r in s.info.meta.tracks){if(s.info.meta.tracks[r].type!="meta"){l[s.info.meta.tracks[r].codec]=1}}l=MistUtil.object.keys(l);for(var r=l.length-1;r>=0;r--){if(l[r].substr(0,4)=="HEVC"){l.splice(r,1)}}if(l.length<t.simul_tracks){return false}return true},player:function(){},scriptsrc:function(e){return e+"/hlsjs.js"}};var p=mistplayers.hlsjs.player;p.prototype=new MistPlayer;p.prototype.build=function(e,t){var s=this;var l=document.createElement("video");l.setAttribute("playsinline","");var r=["autoplay","loop","poster"];for(var i in r){var o=r[i];if(e.options[o]){l.setAttribute(o,e.options[o]===true?"":e.options[o])}}if(e.options.muted){l.muted=true}if(e.info.type=="live"){l.loop=false}if(e.options.controls=="stock"){l.setAttribute("controls","")}l.setAttribute("crossorigin","anonymous");this.setSize=function(e){l.style.width=e.width+"px";l.style.height=e.height+"px"};this.api=l;e.player.api.unload=function(){if(e.player.hls){e.player.hls.destroy();e.player.hls=false;e.log("hls.js instance disposed")}};function n(t){e.player.hls=new Hls({maxBufferLength:15,maxMaxBufferLength:60});e.player.hls.attachMedia(l);e.player.hls.on(Hls.Events.MEDIA_ATTACHED,function(){console.log("video and hls.js are now bound together !");e.player.hls.loadSource(t);e.player.hls.on(Hls.Events.MANIFEST_PARSED,function(e,t){console.log("manifest loaded, found "+t.levels.length+" quality level")})})}e.player.api.setSource=function(t){if(!e.player.hls){return}if(e.player.hls.url!=t){e.player.hls.destroy();n(t)}};e.player.api.setSubtitle=function(e){var t=l.getElementsByTagName("track");for(var s=t.length-1;s>=0;s--){l.removeChild(t[s])}if(e){var r=document.createElement("track");l.appendChild(r);r.kind="subtitles";r.label=e.label;r.srclang=e.lang;r.src=e.src;r.setAttribute("default","")}};function a(){n(e.source.url)}if("Hls"in window){a()}else{var p=e.urlappend(mistplayers.hlsjs.scriptsrc(e.options.host));MistUtil.scripts.insert(p,{onerror:function(t){var s="Failed to load hlsjs.js";if(t.message){s+=": "+t.message}e.showError(s)},onload:a},e)}t(l)};
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
1
embed/min/wrappers/rawws.js
Normal file
1
embed/min/wrappers/rawws.js
Normal file
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
@ -48,7 +48,9 @@ echo " Minimizing wrappers.."
|
|||
terser -mn -o min/wrappers/flv.js -- wrappers/flv.js
|
||||
echo " Minimizing hls.js";
|
||||
terser -mn -o min/wrappers/hlsjs.js -- wrappers/hlsjs.js
|
||||
echo " Done.";
|
||||
echo " Minimizing rawws.js";
|
||||
terser -mn -o min/wrappers/rawws.js -- wrappers/rawws.js
|
||||
echo " Done.";
|
||||
|
||||
echo " Minimizing CSS..";
|
||||
|
||||
|
|
11425
embed/players/libde265.js
Normal file
11425
embed/players/libde265.js
Normal file
File diff suppressed because one or more lines are too long
462
embed/players/libde265.min.js
vendored
Normal file
462
embed/players/libde265.min.js
vendored
Normal file
File diff suppressed because one or more lines are too long
|
@ -1048,6 +1048,8 @@ MistSkins["default"] = {
|
|||
return button;
|
||||
},
|
||||
speaker: function(){
|
||||
|
||||
if (!this.player.api || !("muted" in this.player.api)) { return false; }
|
||||
|
||||
var hasaudio = false;
|
||||
var tracks = this.info.meta.tracks;
|
||||
|
@ -1061,6 +1063,10 @@ MistSkins["default"] = {
|
|||
|
||||
var video = this.video;
|
||||
//obey video states
|
||||
if ((!MistVideo.player.api.volume) || (MistVideo.player.api.muted)) {
|
||||
MistUtil.class.add(button,"off");
|
||||
}
|
||||
|
||||
MistUtil.event.addListener(video,"volumechange",function(){
|
||||
if ((MistVideo.player.api.volume) && (!MistVideo.player.api.muted)) {
|
||||
MistUtil.class.remove(button,"off");
|
||||
|
@ -1078,6 +1084,8 @@ MistSkins["default"] = {
|
|||
return button;
|
||||
},
|
||||
volume: function(options){
|
||||
|
||||
if (!this.player.api || !("volume" in this.player.api)) { return false; }
|
||||
|
||||
var hasaudio = false;
|
||||
var tracks = this.info.meta.tracks;
|
||||
|
@ -1121,6 +1129,8 @@ MistSkins["default"] = {
|
|||
},button);
|
||||
|
||||
//apply initial video state
|
||||
button.set(MistVideo.player.api.muted ? 0 : (MistVideo.player.api.volume*100));
|
||||
//apply stored volume
|
||||
var initevent = MistUtil.event.addListener(video,"loadedmetadata",function(){
|
||||
if (('localStorage' in window) && (localStorage != null) && ('mistVolume' in localStorage)) {
|
||||
MistVideo.player.api.volume = localStorage['mistVolume'];
|
||||
|
@ -1867,13 +1877,12 @@ MistSkins["default"] = {
|
|||
removeIcon();
|
||||
}
|
||||
},icon);
|
||||
MistUtil.event.addListener(MistVideo.video,"progress",function(e){
|
||||
if (("container" in MistVideo) && ("monitor" in MistVideo) && ("vars" in MistVideo.monitor) && ("score" in MistVideo.monitor.vars) && (MistVideo.monitor.vars.score > 0.99)) {
|
||||
removeIcon();
|
||||
}
|
||||
},icon);
|
||||
}
|
||||
|
||||
MistUtil.event.addListener(MistVideo.video,"progress",function(e){
|
||||
if (("container" in MistVideo) && ("monitor" in MistVideo) && ("vars" in MistVideo.monitor) && ("score" in MistVideo.monitor.vars) && (MistVideo.monitor.vars.score > 0.99)) {
|
||||
removeIcon();
|
||||
}
|
||||
},icon);
|
||||
}
|
||||
|
||||
return icon;
|
||||
|
@ -2508,12 +2517,13 @@ MistSkins.dev = {
|
|||
}
|
||||
},
|
||||
"Dropped frames": function(){
|
||||
if ((MistVideo.player.api) && ("getVideoPlaybackQuality" in MistVideo.player.api)) {
|
||||
var r = MistVideo.player.api.getVideoPlaybackQuality();
|
||||
if (r) {
|
||||
if (r.droppedVideoFrames) {
|
||||
return MistUtil.format.number(r.droppedVideoFrames);
|
||||
/* show a graph: return {
|
||||
if (MistVideo.player.api) {
|
||||
if ("getVideoPlaybackQuality" in MistVideo.player.api) {
|
||||
var r = MistVideo.player.api.getVideoPlaybackQuality();
|
||||
if (r) {
|
||||
if (r.droppedVideoFrames) {
|
||||
return MistUtil.format.number(r.droppedVideoFrames);
|
||||
/* show a graph: return {
|
||||
val: MistUtil.format.number(r.droppedVideoFrames),
|
||||
x: (new Date()).getTime()*1e-3,
|
||||
y: r.droppedVideoFrames,
|
||||
|
@ -2523,8 +2533,12 @@ MistSkins.dev = {
|
|||
reverseGradient: true
|
||||
}
|
||||
};*/
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if ("webkitDroppedFrameCount" in MistVideo.player.api) {
|
||||
return MistVideo.player.api.webkitDroppedFrameCount;
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -2588,6 +2602,21 @@ MistSkins.dev = {
|
|||
var out = MistUtil.format.bits(MistVideo.player.monitor.currentBps);
|
||||
return out ? out+"ps" : out;
|
||||
}
|
||||
if (MistVideo.player.api && "currentBps" in MistVideo.player.api) {
|
||||
var out = MistUtil.format.bits(MistVideo.player.api.currentBps());
|
||||
return out ? out+"ps" : out;
|
||||
}
|
||||
|
||||
},
|
||||
"Framerate in": function(){
|
||||
if (MistVideo.player.api && "framerate_in" in MistVideo.player.api) {
|
||||
return MistUtil.format.number(MistVideo.player.api.framerate_in());
|
||||
}
|
||||
},
|
||||
"Framerate out": function(){
|
||||
if (MistVideo.player.api && "framerate_out" in MistVideo.player.api) {
|
||||
return MistUtil.format.number(MistVideo.player.api.framerate_out());
|
||||
}
|
||||
}
|
||||
};
|
||||
var updates = [];
|
||||
|
|
|
@ -18,6 +18,21 @@ mistplayers.dashjs = {
|
|||
MistVideo.log("This source ("+mimetype+") won't load if the page is run via file://");
|
||||
return false;
|
||||
}
|
||||
|
||||
var codecs = {};
|
||||
for (var i in MistVideo.info.meta.tracks) {
|
||||
if (MistVideo.info.meta.tracks[i].type != "meta") {
|
||||
codecs[MistVideo.info.meta.tracks[i].codec] = 1;
|
||||
}
|
||||
}
|
||||
codecs = MistUtil.object.keys(codecs);
|
||||
//if there's a h265 track, remove it from the list of codecs
|
||||
for (var i = codecs.length-1; i >= 0; i--) {
|
||||
if (codecs[i].substr(0,4) == "HEVC") {
|
||||
codecs.splice(i,1);
|
||||
}
|
||||
}
|
||||
if (codecs.length < source.simul_tracks) { return false; } //if there's no longer enough playable tracks, skip this player
|
||||
|
||||
return ("MediaSource" in window);
|
||||
},
|
||||
|
|
|
@ -11,7 +11,22 @@ mistplayers.hlsjs = {
|
|||
MistVideo.log("HTTP/HTTPS mismatch for this source");
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
var codecs = {};
|
||||
for (var i in MistVideo.info.meta.tracks) {
|
||||
if (MistVideo.info.meta.tracks[i].type != "meta") {
|
||||
codecs[MistVideo.info.meta.tracks[i].codec] = 1;
|
||||
}
|
||||
}
|
||||
codecs = MistUtil.object.keys(codecs);
|
||||
//if there's a h265 track, remove it from the list of codecs
|
||||
for (var i = codecs.length-1; i >= 0; i--) {
|
||||
if (codecs[i].substr(0,4) == "HEVC") {
|
||||
codecs.splice(i,1);
|
||||
}
|
||||
}
|
||||
if (codecs.length < source.simul_tracks) { return false; } //if there's no longer enough playable tracks, skip this player
|
||||
|
||||
return true;
|
||||
},
|
||||
player: function(){},
|
||||
|
|
|
@ -42,37 +42,38 @@ mistplayers.html5 = {
|
|||
return support;
|
||||
}
|
||||
|
||||
function translateCodec(track) {
|
||||
|
||||
function bin2hex(index) {
|
||||
return ("0"+track.init.charCodeAt(index).toString(16)).slice(-2);
|
||||
}
|
||||
|
||||
switch (track.codec) {
|
||||
case "AAC":
|
||||
return "mp4a.40.2";
|
||||
case "MP3":
|
||||
return "mp3";
|
||||
//return "mp4a.40.34";
|
||||
case "AC3":
|
||||
return "ec-3";
|
||||
case "H264":
|
||||
return "avc1."+bin2hex(1)+bin2hex(2)+bin2hex(3);
|
||||
case "HEVC":
|
||||
return "hev1."+bin2hex(1)+bin2hex(6)+bin2hex(7)+bin2hex(8)+bin2hex(9)+bin2hex(10)+bin2hex(11)+bin2hex(12);
|
||||
default:
|
||||
return track.codec.toLowerCase();
|
||||
}
|
||||
|
||||
}
|
||||
var codecs = {};
|
||||
for (var i in MistVideo.info.meta.tracks) {
|
||||
if (MistVideo.info.meta.tracks[i].type != "meta") {
|
||||
codecs[translateCodec(MistVideo.info.meta.tracks[i])] = 1;
|
||||
}
|
||||
}
|
||||
codecs = MistUtil.object.keys(codecs);
|
||||
|
||||
if (shortmime == "video/mp4") {
|
||||
function translateCodec(track) {
|
||||
|
||||
function bin2hex(index) {
|
||||
return ("0"+track.init.charCodeAt(index).toString(16)).slice(-2);
|
||||
}
|
||||
|
||||
switch (track.codec) {
|
||||
case "AAC":
|
||||
return "mp4a.40.2";
|
||||
case "MP3":
|
||||
return "mp3";
|
||||
//return "mp4a.40.34";
|
||||
case "AC3":
|
||||
return "ec-3";
|
||||
case "H264":
|
||||
return "avc1."+bin2hex(1)+bin2hex(2)+bin2hex(3);
|
||||
case "HEVC":
|
||||
return "hev1."+bin2hex(1)+bin2hex(6)+bin2hex(7)+bin2hex(8)+bin2hex(9)+bin2hex(10)+bin2hex(11)+bin2hex(12);
|
||||
default:
|
||||
return track.codec.toLowerCase();
|
||||
}
|
||||
|
||||
}
|
||||
var codecs = {};
|
||||
for (var i in MistVideo.info.meta.tracks) {
|
||||
if (MistVideo.info.meta.tracks[i].type != "meta") {
|
||||
codecs[translateCodec(MistVideo.info.meta.tracks[i])] = 1;
|
||||
}
|
||||
}
|
||||
codecs = MistUtil.object.keys(codecs);
|
||||
if (codecs.length) {
|
||||
if (codecs.length > source.simul_tracks) {
|
||||
//not all of the tracks have to work
|
||||
|
@ -85,9 +86,18 @@ mistplayers.html5 = {
|
|||
}
|
||||
return (working >= source.simul_tracks);
|
||||
}
|
||||
shortmime += ";codecs=\""+codecs.join(",")+"\"";
|
||||
return test(shortmime+";codecs=\""+codecs.join(",")+"\"");
|
||||
}
|
||||
}
|
||||
else {
|
||||
//if there's a h265 track, remove it from the list of codecs
|
||||
for (var i = codecs.length-1; i >= 0; i--) {
|
||||
if (codecs[i].substr(0,4) == "hev1") {
|
||||
codecs.splice(i,1);
|
||||
}
|
||||
}
|
||||
if (codecs.length < source.simul_tracks) { return false; } //if there's no longer enough playable tracks, skip this player
|
||||
}
|
||||
|
||||
support = test(shortmime);
|
||||
} catch(e){}
|
||||
|
|
|
@ -44,6 +44,10 @@ mistplayers.mews = {
|
|||
var codecs = {};
|
||||
for (var i in MistVideo.info.meta.tracks) {
|
||||
if (MistVideo.info.meta.tracks[i].type != "meta") {
|
||||
if (MistVideo.info.meta.tracks[i].codec == "HEVC") {
|
||||
//the iPad claims to be able to play MP4/WS H265 tracks.. haha no.
|
||||
continue;
|
||||
}
|
||||
codecs[translateCodec(MistVideo.info.meta.tracks[i])] = MistVideo.info.meta.tracks[i].codec;
|
||||
}
|
||||
}
|
||||
|
|
675
embed/wrappers/rawws.js
Normal file
675
embed/wrappers/rawws.js
Normal file
|
@ -0,0 +1,675 @@
|
|||
mistplayers.rawws = {
|
||||
name: "RAW to Canvas",
|
||||
mimes: ["ws/video/raw"],
|
||||
priority: MistUtil.object.keys(mistplayers).length + 1,
|
||||
isMimeSupported: function (mimetype) {
|
||||
return (MistUtil.array.indexOf(this.mimes,mimetype) == -1 ? false : true);
|
||||
},
|
||||
isBrowserSupported: function (mimetype,source,MistVideo) {
|
||||
|
||||
//check for http/https mismatch
|
||||
if (location.protocol != MistUtil.http.url.split(source.url.replace(/^ws/,"http")).protocol) {
|
||||
if ((location.protocol == "file:") && (MistUtil.http.url.split(source.url.replace(/^ws/,"http")).protocol == "http:")) {
|
||||
MistVideo.log("This page was loaded over file://, the player might not behave as intended.");
|
||||
}
|
||||
else {
|
||||
MistVideo.log("HTTP/HTTPS mismatch for this source");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
for (var i in MistVideo.info.meta.tracks) {
|
||||
if (MistVideo.info.meta.tracks[i].codec == "HEVC") { return true; }
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
player: function(){
|
||||
this.onreadylist = [];
|
||||
},
|
||||
scriptsrc: function(host) { return host+"/libde265.js"; }
|
||||
};
|
||||
var p = mistplayers.rawws.player;
|
||||
p.prototype = new MistPlayer();
|
||||
p.prototype.build = function (MistVideo,callback) {
|
||||
|
||||
var player = this;
|
||||
|
||||
player.onDecoderLoad = function() {
|
||||
if (MistVideo.destroyed) { return; }
|
||||
|
||||
MistVideo.log("Building rawws player..");
|
||||
|
||||
var api = {};
|
||||
MistVideo.player.api = api;
|
||||
|
||||
var ele = document.createElement("canvas");
|
||||
var ctx = ele.getContext("2d");
|
||||
|
||||
ele.style.objectFit = "contain";
|
||||
|
||||
player.vars = {}; //will contain data like currentTime
|
||||
if (MistVideo.options.autoplay) {
|
||||
//if wantToPlay is false, playback will be paused after the first frame
|
||||
player.vars.wantToPlay = true;
|
||||
}
|
||||
player.dropping = false;
|
||||
player.frames = { //contains helper functions and statistics
|
||||
received: 0,
|
||||
bitsReceived: 0,
|
||||
decoded: 0,
|
||||
dropped: 0,
|
||||
behind: function(){
|
||||
return this.received - this.decoded - this.dropped;
|
||||
},
|
||||
timestamps: {},
|
||||
frame2time: function(frame,clean){
|
||||
if (frame in this.timestamps) {
|
||||
if (clean) {
|
||||
//clear any entries before the entry we're actually using
|
||||
for (var i in this.timestamps) {
|
||||
if (i == frame) { break; }
|
||||
delete this.timestamps[i];
|
||||
}
|
||||
}
|
||||
return this.timestamps[frame]*1e-3;
|
||||
}
|
||||
return 0;
|
||||
|
||||
|
||||
//get the closest known timestamp for the frame, then correct for the offset using the framerate
|
||||
var last = 0;
|
||||
var last_time = 0;
|
||||
for (var i in this.timestamps) {
|
||||
last = i;
|
||||
last_time = this.timestamps[i];
|
||||
if (i >= frame) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (clean) {
|
||||
//clear any entries before the entry we're actually using
|
||||
for (var i in this.timestamps) {
|
||||
if (i == last) { break; }
|
||||
delete this.timestamps[i];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
var framerate = this.framerate();
|
||||
if ((typeof framerate != "undefined") && (framerate > 0)) {
|
||||
return last_time + (frame - last) / framerate;
|
||||
}
|
||||
else {
|
||||
return last_time;
|
||||
}
|
||||
|
||||
},
|
||||
history: {
|
||||
log: [],
|
||||
add: function() {
|
||||
this.log.unshift({
|
||||
time: new Date().getTime(),
|
||||
received: player.frames.received,
|
||||
bitsReceived: player.frames.bitsReceived,
|
||||
decoded: player.frames.decoded
|
||||
});
|
||||
if (this.log.length > 3) { this.log.splice(3); }
|
||||
}
|
||||
},
|
||||
framerate_in: function(){
|
||||
var l = this.history.log.length -1;
|
||||
if (l < 1) { return 0; }
|
||||
var dframe = this.history.log[0].received - this.history.log[l].received;
|
||||
var dt = (this.history.log[0].time - this.history.log[l].time) * 1e-3;
|
||||
return dframe / dt;
|
||||
},
|
||||
bitrate_in: function(){
|
||||
var l = this.history.log.length -1;
|
||||
if (l < 1) { return 0; }
|
||||
var dbits = this.history.log[0].bitsReceived - this.history.log[l].bitsReceived;
|
||||
var dt = (this.history.log[0].time - this.history.log[l].time) * 1e-3;
|
||||
return dbits / dt;
|
||||
},
|
||||
framerate_out: function(){
|
||||
var l = this.history.log.length -1;
|
||||
if (l < 1) { return 0; }
|
||||
var dframe = this.history.log[0].decoded - this.history.log[l].decoded;
|
||||
var dt = (this.history.log[0].time - this.history.log[l].time) * 1e-3;
|
||||
return dframe / dt;
|
||||
},
|
||||
framerate: function(){
|
||||
if ("rate_theoretical" in this) { return this.rate_theoretical; }
|
||||
return this.framerate_in();
|
||||
//return undefined;
|
||||
},
|
||||
keepingUp: function(){
|
||||
var l = this.history.log.length -1;
|
||||
if (l < 1) { return 0; }
|
||||
var dBehind = this.history.log[l].received - this.history.log[l].decoded - (this.history.log[0].received - this.history.log[0].decoded);
|
||||
var dt = (this.history.log[0].time - this.history.log[l].time) * 1e-3;
|
||||
var keepingUp_frames = dBehind / dt; //amount of frames falling behind (negative) or catching up (positive) per second
|
||||
|
||||
return keepingUp_frames / this.framerate(); //in seconds per seconds
|
||||
}
|
||||
};
|
||||
api.framerate_in = function () { return player.frames.framerate_in(); }
|
||||
api.framerate_out = function() { return player.frames.framerate_out(); }
|
||||
api.currentBps = function () { return player.frames.bitrate_in(); }
|
||||
api.loop = MistVideo.options.loop;
|
||||
|
||||
//TODO define these if we're adding audio capabilities
|
||||
/*Object.defineProperty(MistVideo.player.api,"volume",{
|
||||
get: function(){ return 0; }
|
||||
});
|
||||
Object.defineProperty(MistVideo.player.api,"muted",{
|
||||
get: function(){ return true; }
|
||||
});*/
|
||||
|
||||
Object.defineProperty(MistVideo.player.api,"webkitDecodedFrameCount",{
|
||||
get: function(){ return player.frames.decoded; }
|
||||
});
|
||||
Object.defineProperty(MistVideo.player.api,"webkitDroppedFrameCount",{
|
||||
get: function(){ return player.frames.dropped; }
|
||||
});
|
||||
|
||||
|
||||
var decoder;
|
||||
this.decoder = null;
|
||||
|
||||
//shorter code to fake an event from the "video" (== canvas) element
|
||||
function emitEvent(type) {
|
||||
//console.log(type);
|
||||
MistUtil.event.send(type,undefined,ele);
|
||||
}
|
||||
|
||||
function init() {
|
||||
|
||||
function init_decoder() {
|
||||
decoder = new libde265.Decoder();
|
||||
MistVideo.player.decoder = decoder;
|
||||
|
||||
var onDecode = [];
|
||||
decoder.addListener = function(func){
|
||||
onDecode.push(func);
|
||||
};
|
||||
decoder.removeListener = function(func){
|
||||
var i = onDecode.indexOf(func);
|
||||
if (i < 0) { return; }
|
||||
onDecode.splice(i,1);
|
||||
return true;
|
||||
};
|
||||
|
||||
//pull requestAnimationFrame-if outside of display_image callback function so it only gets called once
|
||||
var displayImage;
|
||||
if (window.requestAnimationFrame) {
|
||||
displayImage = function(display_image_data){
|
||||
decoder.pending_image_data = display_image_data;
|
||||
window.requestAnimationFrame(function() {
|
||||
if (decoder.pending_image_data) {
|
||||
ctx.putImageData(decoder.pending_image_data, 0, 0);
|
||||
decoder.pending_image_data = null;
|
||||
}
|
||||
});
|
||||
};
|
||||
}
|
||||
else {
|
||||
displayImage = function(display_image_data){
|
||||
ctx.putImageData(display_image_data, 0, 0);
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
decoder.set_image_callback(function(image) {
|
||||
player.frames.decoded++;
|
||||
if (player.vars.wantToPlay && (player.state != "seeking")) {
|
||||
emitEvent("timeupdate");
|
||||
}
|
||||
|
||||
//image.display() wants a starting image, create it if it doesn't exist yet
|
||||
if (!decoder.image_data) {
|
||||
var w = image.get_width();
|
||||
var h = image.get_height();
|
||||
if (w != ele.width || h != ele.height || !this.image_data) {
|
||||
ele.width = w;
|
||||
ele.height = h;
|
||||
|
||||
var img = ctx.createImageData(w, h);
|
||||
decoder.image_data = img;
|
||||
}
|
||||
}
|
||||
|
||||
if (player.state != "seeking") {
|
||||
//when seeking, do not display the new frame if we're not yet at the appropriate timestamp
|
||||
|
||||
image.display(this.image_data, function(display_image_data) {
|
||||
decoder.decoding = false;
|
||||
displayImage(display_image_data);
|
||||
});
|
||||
}
|
||||
image.free();
|
||||
|
||||
//we've decoded and displayed a frame: change player state and emit events if required
|
||||
switch (player.state) {
|
||||
case "play":
|
||||
case "waiting": {
|
||||
if (!player.dropping) {
|
||||
emitEvent("canplay");
|
||||
emitEvent("playing");
|
||||
player.state = "playing";
|
||||
if (!player.vars.wantToPlay) {
|
||||
MistVideo.player.send({type:"hold"});
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "seeking": {
|
||||
var t = player.frames.frame2time(player.frames.decoded + player.frames.dropped);
|
||||
if (t >= player.vars.seekTo) {
|
||||
emitEvent("seeked");
|
||||
player.vars.seekTo = null;
|
||||
player.state = "playing";
|
||||
if (!player.vars.wantToPlay) {
|
||||
emitEvent("timeupdate");
|
||||
MistVideo.player.send({type:"hold"});
|
||||
}
|
||||
}
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
player.state = "playing";
|
||||
}
|
||||
}
|
||||
|
||||
//console.log("pending",player.frames.behind());
|
||||
|
||||
for (var i in onDecode) {
|
||||
onDecode[i]();
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
init_decoder();
|
||||
|
||||
/*
|
||||
* infoBytes:
|
||||
* start - length - meaning
|
||||
* 0 1 track index
|
||||
* 1 1 if == 1 ? keyframe : normal frame
|
||||
* 2 8 timestamp (when frame should be sent to decoder) [ms]
|
||||
* 9 2 offset (when frame should be outputted compared to timestamp) [ms]
|
||||
* */
|
||||
function isKeyFrame(infoBytes) {
|
||||
return !!infoBytes[1];
|
||||
}
|
||||
function toTimestamp(infoBytes) { //returns timestamp in ms
|
||||
var v = new DataView(new ArrayBuffer(8));
|
||||
for (var i = 0; i < 8; i++) {
|
||||
v.setUint8(i,infoBytes[i+2]);
|
||||
}
|
||||
//return v.getBigInt64();
|
||||
return v.getInt32(4); //64 bit is an issue in browsers apparently, but we can settle for a 32bit integer that rolls over
|
||||
}
|
||||
|
||||
function connect(){
|
||||
emitEvent("loadstart");
|
||||
|
||||
//?buffer=0 ensures real time sending
|
||||
//?video=hevc,|minbps selects the lowest bitrate hevc track
|
||||
var url = MistUtil.http.url.addParam(MistVideo.source.url,{buffer:0,video:"hevc,|minbps"});
|
||||
|
||||
var socket = new WebSocket(url);
|
||||
MistVideo.player.ws = socket;
|
||||
socket.binaryType = "arraybuffer";
|
||||
function send(obj) {
|
||||
if (!MistVideo.player.ws) { throw "No websocket to send to"; }
|
||||
if (socket.readyState == 1) {
|
||||
socket.send(JSON.stringify(obj));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
MistVideo.player.send = send;
|
||||
socket.wasConnected = false;
|
||||
socket.onopen = function(){
|
||||
if (!MistVideo.player.built) {
|
||||
MistVideo.player.built = true;
|
||||
callback(ele);
|
||||
}
|
||||
send({type:"request_codec_data",supported_codecs:["HEVC"]});
|
||||
socket.wasConnected = true;
|
||||
}
|
||||
socket.onclose = function(){
|
||||
if (this.wasConnected && (!MistVideo.destroyed) && (MistVideo.state == "Stream is online")) {
|
||||
MistVideo.log("Raw over WS: reopening websocket");
|
||||
connect(url);
|
||||
}
|
||||
else {
|
||||
MistVideo.showError("Raw over WS: websocket closed");
|
||||
}
|
||||
}
|
||||
socket.onerror = function(e){
|
||||
MistVideo.showError("Raw over WS: websocket error");
|
||||
};
|
||||
socket.onmessage = function(e){
|
||||
//console.log(new Uint8Array(e.data));
|
||||
if (typeof e.data == "string") {
|
||||
var d = JSON.parse(e.data);
|
||||
switch (d.type) {
|
||||
case "on_time": {
|
||||
//console.log("received",MistUtil.format.time(d.data.current*1e-3));
|
||||
|
||||
player.vars.paused = false;
|
||||
|
||||
player.frames.history.add();
|
||||
|
||||
if (player.vars.duration != d.data.end*1e-3) {
|
||||
player.vars.duration = d.data.end*1e-3;
|
||||
emitEvent("durationchange");
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
case "seek": {
|
||||
//MistVideo.player.decoder.reset(); //should be used when seeking, but makes things worse, honestly
|
||||
MistVideo.player.frames.timestamps = {};
|
||||
if (MistVideo.player.dropping) {
|
||||
MistVideo.log("Emptying drop queue for seek");
|
||||
MistVideo.player.frames.dropped += MistVideo.player.dropping.length;
|
||||
MistVideo.player.dropping = [];
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "codec_data": {
|
||||
emitEvent("loadedmetadata");
|
||||
send({type:"play"});
|
||||
player.state = "play";
|
||||
break;
|
||||
}
|
||||
case "info": {
|
||||
var tracks = MistVideo.info.meta.tracks;
|
||||
var track;
|
||||
for (var i in tracks) {
|
||||
if (tracks[i].idx == d.data.tracks[0]) {
|
||||
track = tracks[i];
|
||||
break;
|
||||
}
|
||||
}
|
||||
if ((typeof track != undefined) && (track.fpks > 0)) {
|
||||
player.frames.rate_theoretical = track.fpks*1e-3;
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "pause": {
|
||||
player.vars.paused = d.paused;
|
||||
if (d.paused) {
|
||||
player.decoder.flush(); //push last 6 frames through
|
||||
emitEvent("pause");
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "on_stop": {
|
||||
if (player.state == "ended") { return; }
|
||||
player.state = "ended";
|
||||
player.vars.paused = true;
|
||||
socket.onclose = function(){}; //don't reopen websocket, just close, it's okay, rly
|
||||
socket.close();
|
||||
player.decoder.flush(); //push last 6 frames through
|
||||
emitEvent("ended");
|
||||
|
||||
break;
|
||||
}
|
||||
default: {
|
||||
//console.log("ws message",d.type,d.data);
|
||||
}
|
||||
}
|
||||
}
|
||||
else {
|
||||
|
||||
player.frames.received++;
|
||||
player.frames.bitsReceived += e.data.byteLength*8;
|
||||
|
||||
var l = 12;
|
||||
var infoBytes = new Uint8Array(e.data.slice(0,l));
|
||||
//console.log(infoBytes);
|
||||
|
||||
var data = new Uint8Array(e.data.slice(l,e.data.byteLength)); //actual raw h265 frame
|
||||
|
||||
player.frames.timestamps[player.frames.received] = toTimestamp(infoBytes);
|
||||
|
||||
function prepare(data,infoBytes) {
|
||||
|
||||
//to avoid clogging the websocket onmessage, process the frame asynchronously
|
||||
setTimeout(function(){
|
||||
|
||||
if (player.dropping) {
|
||||
//console.log(player.frames.behind(),player.dropping.length);
|
||||
if (player.state != "waiting") {
|
||||
emitEvent("waiting");
|
||||
player.state = "waiting";
|
||||
}
|
||||
|
||||
if (isKeyFrame(infoBytes)) {
|
||||
if (player.dropping.length) {
|
||||
player.frames.dropped += player.dropping.length;
|
||||
MistVideo.log("Dropped "+player.dropping.length+" frames");
|
||||
player.dropping = [];
|
||||
}
|
||||
else {
|
||||
MistVideo.log("Caught up! no longer dropping");
|
||||
player.dropping = false;
|
||||
|
||||
}
|
||||
}
|
||||
else {
|
||||
player.dropping.push([infoBytes,data]);
|
||||
if (!decoder.decoding) {
|
||||
var d = player.dropping.shift();
|
||||
MistVideo.player.process(d[1],d[0]);
|
||||
}
|
||||
return;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (player.frames.behind() > 20) {
|
||||
//enable dropping
|
||||
player.dropping = [];
|
||||
MistVideo.log("Falling behind, dropping files..");
|
||||
}
|
||||
}
|
||||
|
||||
MistVideo.player.process(data,infoBytes);
|
||||
|
||||
},0);
|
||||
}
|
||||
|
||||
prepare(data,infoBytes);
|
||||
}
|
||||
}
|
||||
|
||||
socket.listeners = {}; //kind of event listener list for websocket messages
|
||||
socket.addListener = function(type,f){
|
||||
if (!(type in this.listeners)) { this.listeners[type] = []; }
|
||||
this.listeners[type].push(f);
|
||||
};
|
||||
socket.removeListener = function(type,f) {
|
||||
if (!(type in this.listeners)) { return; }
|
||||
var i = this.listeners[type].indexOf(f);
|
||||
if (i < 0) { return; }
|
||||
this.listeners[type].splice(i,1);
|
||||
return true;
|
||||
}
|
||||
|
||||
}
|
||||
MistVideo.player.connect = connect;
|
||||
MistVideo.player.process = function(data,infoBytes){
|
||||
|
||||
//add to the decoding queue
|
||||
decoder.decoding = true;
|
||||
var err = decoder.push_data(data);
|
||||
|
||||
if (player.state == "play") {
|
||||
emitEvent("loadeddata");
|
||||
player.state = "waiting";
|
||||
}
|
||||
|
||||
if (player.vars.wantToPlay && (player.state != "seeking")) {
|
||||
emitEvent("progress");
|
||||
}
|
||||
|
||||
function onerror(err) {
|
||||
if (err == 0) { return; }
|
||||
if (err == libde265.DE265_ERROR_WAITING_FOR_INPUT_DATA) {
|
||||
//emitEvent("waiting");
|
||||
player.state = "waiting";
|
||||
//do nothing, we'll decode again when we get the next data message
|
||||
return;
|
||||
}
|
||||
if (!libde265.de265_isOK(err)) {
|
||||
//console.warn("decode",libde265.de265_get_error_text(err));
|
||||
ele.error = "Decode error: "+libde265.de265_get_error_text(err);
|
||||
emitEvent("error");
|
||||
return true; //don't call decoder.decode();
|
||||
}
|
||||
}
|
||||
|
||||
if (!onerror(err)) {
|
||||
decoder.decode(onerror);
|
||||
}
|
||||
else {
|
||||
decoder.free();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
connect();
|
||||
}
|
||||
|
||||
init();
|
||||
|
||||
//redirect properties
|
||||
//using a function to make sure the "item" is in the correct scope
|
||||
function reroute(item) {
|
||||
Object.defineProperty(MistVideo.player.api,item,{
|
||||
get: function(){ return player.vars[item]; },
|
||||
set: function(value){
|
||||
return player.vars[item] = value;
|
||||
}
|
||||
});
|
||||
}
|
||||
var list = [
|
||||
"duration"
|
||||
,"paused"
|
||||
,"error"
|
||||
];
|
||||
for (var i in list) {
|
||||
reroute(list[i]);
|
||||
}
|
||||
|
||||
api.play = function(){
|
||||
return new Promise(function(resolve,reject){
|
||||
player.vars.wantToPlay = true;
|
||||
var f = function(){
|
||||
resolve();
|
||||
MistVideo.player.decoder.removeListener(f);
|
||||
};
|
||||
MistVideo.player.decoder.addListener(f);
|
||||
|
||||
if (MistVideo.player.ws.readyState > MistVideo.player.ws.OPEN) {
|
||||
//websocket has closed
|
||||
MistVideo.player.connect();
|
||||
MistVideo.log("Websocket was closed: reconnecting to resume playback");
|
||||
return;
|
||||
}
|
||||
if (api.paused) MistVideo.player.send({type:"play"});
|
||||
player.state = "play";
|
||||
});
|
||||
};
|
||||
api.pause = function(){
|
||||
player.vars.wantToPlay = false;
|
||||
MistVideo.player.send({type:"hold"});
|
||||
};
|
||||
|
||||
MistVideo.player.api.unload = function(){
|
||||
//close socket
|
||||
if (MistVideo.player.ws) {
|
||||
MistVideo.player.ws.onclose = function(){};
|
||||
MistVideo.player.ws.close();
|
||||
}
|
||||
|
||||
if (MistVideo.player.decoder) {
|
||||
//prevent adding of new data
|
||||
MistVideo.player.decoder.push_data = function(){};
|
||||
//free decoder
|
||||
MistVideo.player.decoder.flush();
|
||||
MistVideo.player.decoder.free();
|
||||
}
|
||||
}
|
||||
MistVideo.player.setSize = function(size){
|
||||
ele.style.width = size.width+"px";
|
||||
ele.style.height = size.height+"px";
|
||||
};
|
||||
|
||||
//override seeking
|
||||
Object.defineProperty(MistVideo.player.api,"currentTime",{
|
||||
get: function(){
|
||||
var n = player.frames.decoded + player.frames.dropped;
|
||||
if (player.state == "seeking") { return player.vars.seekTo; }
|
||||
if (n in player.frames.timestamps) { return player.frames.frame2time(n); }
|
||||
return 0;
|
||||
},
|
||||
set: function(value){
|
||||
emitEvent("seeking");
|
||||
player.state = "seeking";
|
||||
player.vars.seekTo = value;
|
||||
MistVideo.player.send({type:"seek", seek_time: value*1e3});
|
||||
//player.frames.timestamps[player.frames.received] = value; //set currentTime to value
|
||||
return value;
|
||||
}
|
||||
});
|
||||
|
||||
//show the difference between decoded frames and received frames as the buffer
|
||||
Object.defineProperty(MistVideo.player.api,"buffered",{
|
||||
get: function(){
|
||||
return {
|
||||
start: function(i){
|
||||
if (this.length && i == 0) {
|
||||
return player.frames.frame2time(player.frames.decoded + player.frames.dropped);
|
||||
}
|
||||
},
|
||||
end: function(i){
|
||||
if (this.length && i == 0) {
|
||||
return player.frames.frame2time(player.frames.received);
|
||||
}
|
||||
},
|
||||
length: player.frames.received - player.frames.decoded > 0 ? 1 : 0
|
||||
};
|
||||
}
|
||||
});
|
||||
|
||||
//loop
|
||||
if (MistVideo.info.type != "live") {
|
||||
MistUtil.event.addListener(ele,"ended",function(){
|
||||
if (player.api.loop) {
|
||||
player.api.play();
|
||||
player.api.currentTime = 0;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
if ("libde265" in window) {
|
||||
this.onDecoderLoad();
|
||||
}
|
||||
else {
|
||||
var scripttag = MistUtil.scripts.insert(MistVideo.urlappend(mistplayers.rawws.scriptsrc(MistVideo.options.host)),{
|
||||
onerror: function(e){
|
||||
var msg = "Failed to load H265 decoder";
|
||||
if (e.message) { msg += ": "+e.message; }
|
||||
MistVideo.showError(msg);
|
||||
},
|
||||
onload: MistVideo.player.onDecoderLoad
|
||||
},MistVideo);
|
||||
}
|
||||
}
|
|
@ -18,6 +18,21 @@ mistplayers.videojs = {
|
|||
MistVideo.log("This source ("+mimetype+") won't load if the page is run via file://");
|
||||
return false;
|
||||
}
|
||||
|
||||
var codecs = {};
|
||||
for (var i in MistVideo.info.meta.tracks) {
|
||||
if (MistVideo.info.meta.tracks[i].type != "meta") {
|
||||
codecs[MistVideo.info.meta.tracks[i].codec] = 1;
|
||||
}
|
||||
}
|
||||
codecs = MistUtil.object.keys(codecs);
|
||||
//if there's a h265 track, remove it from the list of codecs
|
||||
for (var i = codecs.length-1; i >= 0; i--) {
|
||||
if (codecs[i].substr(0,4) == "HEVC") {
|
||||
codecs.splice(i,1);
|
||||
}
|
||||
}
|
||||
if (codecs.length < source.simul_tracks) { return false; } //if there's no longer enough playable tracks, skip this player
|
||||
|
||||
return ("MediaSource" in window);
|
||||
},
|
||||
|
|
|
@ -131,6 +131,7 @@ namespace Mist{
|
|||
capa["url_match"].append("/webrtc.js");
|
||||
capa["url_match"].append("/flv.js");
|
||||
capa["url_match"].append("/hlsjs.js");
|
||||
capa["url_match"].append("/libde265.js");
|
||||
capa["url_match"].append("/skins/default.css");
|
||||
capa["url_match"].append("/skins/dev.css");
|
||||
capa["url_match"].append("/skins/videojs.css");
|
||||
|
@ -148,6 +149,7 @@ namespace Mist{
|
|||
capa["optional"]["wrappers"]["allowed"].append("dashjs");
|
||||
capa["optional"]["wrappers"]["allowed"].append("webrtc");
|
||||
capa["optional"]["wrappers"]["allowed"].append("mews");
|
||||
capa["optional"]["wrappers"]["allowed"].append("rawws");
|
||||
capa["optional"]["wrappers"]["allowed"].append("flv");
|
||||
capa["optional"]["wrappers"]["allowed"].append("flash_strobe");
|
||||
capa["optional"]["wrappers"]["option"] = "--wrappers";
|
||||
|
@ -894,6 +896,11 @@ namespace Mist{
|
|||
response.append((char *)mews_js, (size_t)mews_js_len);
|
||||
used = true;
|
||||
}
|
||||
if (it->asStringRef() == "rawws"){
|
||||
#include "rawws.js.h"
|
||||
response.append((char *)rawws_js, (size_t)rawws_js_len);
|
||||
used = true;
|
||||
}
|
||||
if (it->asStringRef() == "flv"){
|
||||
#include "flv.js.h"
|
||||
response.append((char *)flv_js, (size_t)flv_js_len);
|
||||
|
@ -1066,6 +1073,26 @@ namespace Mist{
|
|||
H.Clean();
|
||||
return;
|
||||
}
|
||||
if (H.url == "/libde265.js"){
|
||||
std::string response;
|
||||
H.Clean();
|
||||
H.SetHeader("Server", "MistServer/" PACKAGE_VERSION);
|
||||
H.setCORSHeaders();
|
||||
H.SetHeader("Content-Type", "application/javascript");
|
||||
if (method == "OPTIONS" || method == "HEAD"){
|
||||
H.SendResponse("200", "OK", myConn);
|
||||
H.Clean();
|
||||
return;
|
||||
}
|
||||
|
||||
#include "player_libde265.js.h"
|
||||
response.append((char*)player_libde265_js, (size_t)player_libde265_js_len);
|
||||
|
||||
H.SetBody(response);
|
||||
H.SendResponse("200", "OK", myConn);
|
||||
H.Clean();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void OutHTTP::sendIcon(){
|
||||
|
|
Loading…
Add table
Reference in a new issue