Embed: Raw/WS (HEVC only atm) player

This commit is contained in:
Cat 2022-04-05 18:07:45 +02:00 committed by Thulinma
parent ac13686048
commit 86379e44eb
20 changed files with 12742 additions and 53 deletions

View file

@ -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

View file

@ -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)}};

View file

@ -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

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View file

@ -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

File diff suppressed because one or more lines are too long

462
embed/players/libde265.min.js vendored Normal file

File diff suppressed because one or more lines are too long

View file

@ -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 = [];

View file

@ -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);
},

View file

@ -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(){},

View file

@ -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){}

View file

@ -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
View 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);
}
}

View file

@ -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);
},

View file

@ -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(){