1 line
No EOL
16 KiB
JavaScript
1 line
No EOL
16 KiB
JavaScript
mistplayers.mews={name:"MSE websocket player",mimes:["ws/video/mp4","ws/video/webm"],priority:MistUtil.object.keys(mistplayers).length+1,isMimeSupported:function(e){return this.mimes.indexOf(e)==-1?false:true},isBrowserSupported:function(e,t,n){if(!("WebSocket"in window)||!("MediaSource"in window)){return false}if(location.protocol.replace(/^http/,"ws")!=MistUtil.http.url.split(t.url.replace(/^http/,"ws")).protocol){n.log("HTTP/HTTPS mismatch for this source");return false}if(navigator.platform.toUpperCase().indexOf("MAC")>=0){return false}function i(e){function t(t){return("0"+e.init.charCodeAt(t).toString(16)).slice(-2)}switch(e.codec){case"AAC":return"mp4a.40.2";case"MP3":return"mp4a.40.34";case"AC3":return"ec-3";case"H264":return"avc1."+t(1)+t(2)+t(3);case"HEVC":return"hev1."+t(1)+t(6)+t(7)+t(8)+t(9)+t(10)+t(11)+t(12);default:return e.codec.toLowerCase()}}var s={};for(var r in n.info.meta.tracks){if(n.info.meta.tracks[r].type!="meta"){s[i(n.info.meta.tracks[r])]=n.info.meta.tracks[r].codec}}var a=e.split("/")[2];function o(e){return MediaSource.isTypeSupported("video/"+a+';codecs="'+e+'"')}t.supportedCodecs=[];for(var r in s){var u=o(r);if(u){t.supportedCodecs.push(s[r])}}if(!n.options.forceType&&!n.options.forcePlayer){if(t.supportedCodecs.length<t.simul_tracks){n.log("Not enough playable tracks for this source");return false}}return t.supportedCodecs.length>0},player:function(){}};var p=mistplayers.mews.player;p.prototype=new MistPlayer;p.prototype.build=function(e,t){var n=document.createElement("video");n.setAttribute("playsinline","");var i=["autoplay","loop","poster"];for(var s in i){var r=i[s];if(e.options[r]){n.setAttribute(r,e.options[r]===true?"":e.options[r])}}if(e.options.muted){n.muted=true}if(e.info.type=="live"){n.loop=false}if(e.options.controls=="stock"){n.setAttribute("controls","")}n.setAttribute("crossorigin","anonymous");this.setSize=function(e){n.style.width=e.width+"px";n.style.height=e.height+"px"};var a=this;function o(){if(a.ws.readyState==a.ws.OPEN&&a.ms.readyState=="open"&&a.sb){t(n);if(e.options.autoplay){a.api.play()}return true}}this.msinit=function(){return new Promise(function(e,t){a.ms=new MediaSource;n.src=URL.createObjectURL(a.ms);a.ms.onsourceopen=function(){e()};a.ms.onsourceclose=function(e){console.error("ms close",e);u({type:"stop"})};a.ms.onsourceended=function(e){console.error("ms ended",e);function t(e,t,i){var s,r;s=new Blob([e],{type:i});r=window.URL.createObjectURL(s);n(r,t);setTimeout(function(){return window.URL.revokeObjectURL(r)},1e3)}function n(e,t){var n;n=document.createElement("a");n.href=e;n.download=t;document.body.appendChild(n);n.style="display: none";n.click();n.remove()}if(a.debugging){var i=0;for(var s=0;s<a.sb.appended.length;s++){i+=a.sb.appended[s].length}var r=new Uint8Array(i);var i=0;for(var s=0;s<a.sb.appended.length;s++){r.set(a.sb.appended[s],i);i+=a.sb.appended[s].length}t(r,"appended.mp4.bin","application/octet-stream")}u({type:"stop"})}})};this.msinit().then(function(){if(a.sb){e.log("Not creating source buffer as one already exists.");return}o()});this.onsbinit=[];this.sbinit=function(t){if(!t){e.showError("Did not receive any codec: nothing to initialize.");return}a.sb=a.ms.addSourceBuffer("video/"+e.source.type.split("/")[2]+';codecs="'+t.join(",")+'"');a.sb.mode="segments";a.sb._codecs=t;a.sb._duration=1;a.sb._size=0;a.sb.queue=[];var i=[];a.sb.do_on_updateend=i;a.sb.appending=null;a.sb.appended=[];var s=0;a.sb.addEventListener("updateend",function(){if(!a.sb){e.log("Reached updateend but the source buffer is "+JSON.stringify(a.sb)+". ");return}if(a.debugging){if(a.sb.appending)a.sb.appended.push(a.sb.appending);a.sb.appending=null}if(s>=500){s=0;a.sb._clean(10)}else{s++}var t=i.slice();i=[];for(var r in t){if(!a.sb){if(a.debugging){console.warn("I was doing on_updateend but the sb was reset")}break}if(a.sb.updating){i.concat(t.slice(r));if(a.debugging){console.warn("I was doing on_updateend but was interrupted")}break}t[r](r<t.length-1?t.slice(r):[])}if(!a.sb){return}a.sb._busy=false;if(a.sb&&a.sb.queue.length>0&&!a.sb.updating&&!n.error){a.sb._append(this.queue.shift())}});a.sb.error=function(e){console.error("sb error",e)};a.sb.abort=function(e){console.error("sb abort",e)};a.sb._doNext=function(e){i.push(e)};a.sb._do=function(e){if(this.updating||this._busy){this._doNext(e)}else{e()}};a.sb._append=function(e){if(!e){return}if(!e.buffer){return}if(a.debugging){a.sb.appending=new Uint8Array(e)}if(a.sb._busy){if(a.debugging)console.warn("I wanted to append data, but now I won't because the thingy was still busy. Putting it back in the queue.");a.sb.queue.unshift(e);return}a.sb._busy=true;a.sb.appendBuffer(e)};if(a.msgqueue){if(a.msgqueue[0]){var r=false;if(a.msgqueue[0].length){for(var o in a.msgqueue[0]){if(a.sb.updating||a.sb.queue.length||a.sb._busy){a.sb.queue.push(a.msgqueue[0][o])}else{a.sb._append(a.msgqueue[0][o])}}}else{r=true}a.msgqueue.shift();if(a.msgqueue.length==0){a.msgqueue=false}e.log("The newly initialized source buffer was filled with data from a seperate message queue."+(a.msgqueue?" "+a.msgqueue.length+" more message queue(s) remain.":""));if(r){e.log("The seperate message queue was empty; manually triggering any onupdateend functions");a.sb.dispatchEvent(new Event("updateend"))}}}a.sb._clean=function(e){if(!e)e=180;if(n.currentTime>e){a.sb._do(function(){a.sb.remove(0,Math.max(.1,n.currentTime-e))})}};if(a.onsbinit.length){a.onsbinit.shift()()}};this.wsconnect=function(){return new Promise(function(t,i){this.ws=new WebSocket(e.source.url);this.ws.binaryType="arraybuffer";this.ws.onopen=function(){t()};this.ws.onerror=function(t){e.showError("MP4 over WS: websocket error")};this.ws.onclose=function(t){e.log("MP4 over WS: websocket closed")};this.ws.listeners={};this.ws.addListener=function(e,t){if(!(e in this.listeners)){this.listeners[e]=[]}this.listeners[e].push(t)};this.ws.removeListener=function(e,t){if(!(e in this.listeners)){return}var n=this.listeners[e].indexOf(t);if(n<0){return}this.listeners[e].splice(n,1);return true};a.msgqueue=false;var s=1;var r=[];this.ws.onmessage=function(t){if(!t.data){throw"Received invalid data"}if(typeof t.data=="string"){var i=JSON.parse(t.data);if(a.debugging&&i.type!="on_time"){console.log("ws message",i)}switch(i.type){case"on_stop":{var r;r=MistUtil.event.addListener(n,"waiting",function(e){MistUtil.event.send("ended",null,n);MistUtil.event.removeListener(r)});break}case"on_time":{var o=i.data.current-n.currentTime*1e3;var d=a.ws.serverDelay.get();var f=Math.max(500+d,d*2);if(e.info.type!="live"){f+=2e3}if(a.debugging)console.log("on_time received",i.data.current/1e3,"currtime",n.currentTime,s+"x","buffer",Math.round(o),"/",Math.round(f),e.info.type=="live"?"latency:"+Math.round(i.data.end-n.currentTime*1e3)+"ms":"","listeners",a.ws.listeners&&a.ws.listeners.on_time?a.ws.listeners.on_time:0,"msgqueue",a.msgqueue?a.msgqueue.length:0,i.data);if(!a.sb){e.log("Received on_time, but the source buffer is being cleared right now. Ignoring.");break}if(a.sb._duration!=i.data.end*.001){a.sb._duration=i.data.end*.001;MistUtil.event.send("durationchange",null,e.video)}e.info.meta.buffer_window=i.data.end-i.data.begin;a.sb.paused=false;if(e.info.type=="live"){if(s==1){if(i.data.play_rate_curr=="auto"){if(n.currentTime>0){if(o-f>f){s=1.1+Math.min(1,(o-f)/f)*.15;n.playbackRate*=s;e.log("Our buffer is big, so increase the playback speed to catch up.")}else if(o<f/2){s=.9;n.playbackRate*=s;e.log("Our buffer is small, so decrease the playback speed to catch up.")}}}}else if(s>1){if(o<f){n.playbackRate/=s;s=1;e.log("Our buffer is small enough, so return to real time playback.")}}else{if(o>f){n.playbackRate/=s;s=1;e.log("Our buffer is big enough, so return to real time playback.")}}}else{if(s==1){if(i.data.play_rate_curr=="auto"){if(o<f/2){if(o<-1e4){u({type:"seek",seek_time:n.currentTime*1e3})}else{s=2;e.log("Our buffer is negative, so request a faster download rate.");u({type:"set_speed",play_rate:s})}}else if(o-f>f){e.log("Our buffer is big, so request a slower download rate.");s=.5;u({type:"set_speed",play_rate:s})}}}else if(s>1){if(o>f){u({type:"set_speed",play_rate:"auto"});s=1;e.log("The buffer is big enough, so ask for realtime download rate.")}}else{if(o<f){u({type:"set_speed",play_rate:"auto"});s=1;e.log("The buffer is small enough, so ask for realtime download rate.")}}}break}case"tracks":{function c(e,t){if(!t){return false}if(e.length!=t.length){return false}for(var n in e){if(t.indexOf(e[n])<0){return false}}return true}if(c(a.last_codecs?a.last_codecs:a.sb._codecs,i.data.codecs)){if(a.debugging)console.log("reached switching point");if(i.data.current>0){if(a.sb){a.sb._do(function(){a.sb.remove(0,i.data.current*.001)})}}e.log("Player switched tracks, keeping source buffer as codecs are the same as before.")}else{if(a.debugging){console.warn("Different codecs!");console.warn("video time",n.currentTime,"waiting until",i.data.current*.001)}a.last_codecs=i.data.codecs;if(a.msgqueue){a.msgqueue.push([])}else{a.msgqueue=[[]]}var l=function(){if(a&&a.sb){a.sb._do(function(t){if(!a.sb.updating){if(!isNaN(a.ms.duration))a.sb.remove(0,Infinity);a.sb.queue=[];a.ms.removeSourceBuffer(a.sb);a.sb=null;var s=(i.data.current*.001).toFixed(3);n.src="";a.ms.onsourceclose=null;a.ms.onsourceended=null;if(a.debugging&&t&&t.length){console.warn("There are do_on_updateend functions queued, which I *should* re-apply after clearing the sb.")}a.msinit().then(function(){a.sbinit(i.data.codecs);a.sb.do_on_updateend=t;var r=MistUtil.event.addListener(n,"loadedmetadata",function(){e.log("Buffer cleared, setting playback position to "+MistUtil.format.time(s,{ms:true}));var t=function(){n.currentTime=s;if(n.currentTime<s){a.sb._doNext(t);if(a.debugging){console.log("Could not set playback position")}}else{if(a.debugging){console.log("Set playback position to "+MistUtil.format.time(s,{ms:true}))}var e=function(){a.sb._doNext(function(){if(n.buffered.length){if(a.debugging){console.log(n.buffered.start(0),n.buffered.end(0),n.currentTime)}if(n.buffered.start(0)>n.currentTime){var t=n.buffered.start(0);n.currentTime=t;if(n.currentTime!=t){e()}}}else{e()}})};e()}};t();MistUtil.event.removeListener(r)})})}else{l()}})}else{if(a.debugging){console.warn("sb not available to do clear")}a.onsbinit.push(l)}};if(!i.data.codecs||!i.data.codecs.length){e.showError("Track switch does not contain any codecs, aborting.");e.options.setTracks=false;l();break}if(a.debugging){console.warn("reached switching point",i.data.current*.001,MistUtil.format.time(i.data.current*.001))}l()}}}if(i.type in this.listeners){for(var p=this.listeners[i.type].length-1;p>=0;p--){this.listeners[i.type][p](i)}}return}var b=new Uint8Array(t.data);if(b){if(a.sb&&!a.msgqueue){if(a.sb.updating||a.sb.queue.length||a.sb._busy){a.sb.queue.push(b)}else{a.sb._append(b)}}else{if(!a.msgqueue){a.msgqueue=[[]]}a.msgqueue[a.msgqueue.length-1].push(b)}}else{e.log("Expecting data from websocket, but received none?!")}};this.ws.serverDelay={delays:[],log:function(e){var t=false;switch(e){case"seek":case"set_speed":{t=e;break}case"request_codec_data":{t="codec_data";break}default:{return}}if(t){var n=(new Date).getTime();function i(){a.ws.serverDelay.add((new Date).getTime()-n);a.ws.removeListener(t,i)}a.ws.addListener(t,i)}},add:function(e){this.delays.unshift(e);if(this.delays.length>5){this.delays.splice(5)}},get:function(){if(this.delays.length){let e=0;let t=0;for(null;t<this.delays.length;t++){if(t>=3){break}e+=this.delays[t]}return e/t}return 500}}}.bind(this))};this.wsconnect().then(function(){var t=function(e){a.sbinit(e.data.codecs);o();a.ws.removeListener("codec_data",t)};this.ws.addListener("codec_data",t);u({type:"request_codec_data",supported_codecs:e.source.supportedCodecs})}.bind(this));function u(e){if(!a.ws){throw"No websocket to send to"}if(a.ws.readyState>=a.ws.CLOSING){a.wsconnect().then(function(){u(e)});return}if(a.debugging){console.log("ws send",e)}a.ws.serverDelay.log(e.type);a.ws.send(JSON.stringify(e))}a.findBuffer=function(e){var t=false;for(var i=0;i<n.buffered.length;i++){if(n.buffered.start(i)<=e&&n.buffered.end(i)>=e){t=i;break}}return t};this.api={play:function(t){return new Promise(function(i,s){var r=function(o){if(!a.sb){e.log("Attempting to play, but the source buffer is being cleared. Waiting for next on_time.");return}if(e.info.type=="live"){if(t||n.currentTime==0){var u=function(){if(n.buffered.length){var t=a.findBuffer(o.data.current*.001);if(t!==false){if(n.buffered.start(t)>n.currentTime||n.buffered.end(t)<n.currentTime){n.currentTime=o.data.current*.001;e.log("Setting live playback position to "+MistUtil.format.time(n.currentTime))}n.play().then(i).catch(s);a.sb.paused=false;a.sb.removeEventListener("updateend",u)}}};a.sb.addEventListener("updateend",u)}else{a.sb.paused=false;n.play().then(i).catch(s)}a.ws.removeListener("on_time",r)}else if(o.data.current>n.currentTime){a.sb.paused=false;n.play().then(i).catch(s);a.ws.removeListener("on_time",r)}};a.ws.addListener("on_time",r);var o={type:"play"};if(t){o.seek_time="live"}u(o)})},pause:function(){n.pause();u({type:"hold"});if(a.sb){a.sb.paused=true}},setTracks:function(e){e.type="tracks";e=MistUtil.object.extend({type:"tracks",audio:null,video:null,seek_time:Math.max(0,n.currentTime*1e3-(500+a.ws.serverDelay.get()))},e);u(e)},unload:function(){a.api.pause();a.sb._do(function(){a.sb.remove(0,Infinity);try{a.ms.endOfStream()}catch(e){}});a.ws.close()}};Object.defineProperty(this.api,"currentTime",{get:function(){return n.currentTime},set:function(t){MistUtil.event.send("seeking",t,n);u({type:"seek",seek_time:Math.max(0,t*1e3-(250+a.ws.serverDelay.get()))});var i=function(e){a.ws.removeListener("seek",i);var s=function(e){a.ws.removeListener("on_time",s);t=(e.data.current*.001).toFixed(3);var i=function(){n.currentTime=t;if(n.currentTime!=t){if(a.debugging)console.log("Failed to set video.currentTime, wanted:",t,"got:",n.currentTime);a.sb._doNext(i)}};i()};a.ws.addListener("on_time",s)};a.ws.addListener("seek",i);n.currentTime=t;e.log("Seeking to "+MistUtil.format.time(t,{ms:true})+" ("+t+")")}});Object.defineProperty(this.api,"duration",{get:function(){return a.sb?a.sb._duration:1}});Object.defineProperty(this.api,"playbackRate",{get:function(){return n.playbackRate},set:function(e){var t=function(e){n.playbackRate=e.data.play_rate};a.ws.addListener("set_speed",t);u({type:"set_speed",play_rate:e==1?"auto":e})}});function d(e){Object.defineProperty(a.api,e,{get:function(){return n[e]},set:function(t){return n[e]=t}})}var f=["volume","buffered","muted","loop","paused",,"error","textTracks","webkitDroppedFrameCount","webkitDecodedFrameCount"];for(var s in f){d(f[s])}MistUtil.event.addListener(n,"ended",function(){if(a.api.loop){a.api.currentTime=0;a.sb._do(function(){a.sb.remove(0,Infinity)})}});var c=false;MistUtil.event.addListener(n,"seeking",function(){c=true;var e=MistUtil.event.addListener(n,"seeked",function(){c=false;MistUtil.event.removeListener(e)})});MistUtil.event.addListener(n,"waiting",function(){if(c){return}var t=a.findBuffer(n.currentTime);if(t!==false){if(t+1<n.buffered.length&&n.buffered.start(t+1)-n.currentTime<1e4){e.log("Skipped over buffer gap (from "+MistUtil.format.time(n.currentTime)+" to "+MistUtil.format.time(n.buffered.start(t+1))+")");n.currentTime=n.buffered.start(t+1)}}});MistUtil.event.addListener(n,"pause",function(){if(a.sb&&!a.sb.paused){e.log("The browser paused the vid - probably because it has no audio and the tab is no longer visible. Pausing download.");u({type:"hold"});a.sb.paused=true;var t=MistUtil.event.addListener(n,"play",function(){if(a.sb&&a.sb.paused){u({type:"play"})}MistUtil.event.removeListener(t)})}});if(a.debugging){MistUtil.event.addListener(n,"waiting",function(){var e=[];var t=false;for(var i=0;i<n.buffered.length;i++){if(n.currentTime>=n.buffered.start(i)&&n.currentTime<=n.buffered.end(i)){t=true}e.push([n.buffered.start(i),n.buffered.end(i)])}console.log("waiting","currentTime",n.currentTime,"buffers",e,t?"contained":"outside of buffer","readystate",n.readyState,"networkstate",n.networkState);if(n.readyState>=2&&n.networkState>=2){console.error("Why am I waiting?!")}})}}; |