1270 lines
		
	
	
	
		
			49 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
			
		
		
	
	
			1270 lines
		
	
	
	
		
			49 KiB
		
	
	
	
		
			JavaScript
		
	
	
	
	
	
mistplayers.mews = {
 | 
						|
  name: "MSE websocket player",
 | 
						|
  mimes: ["ws/video/mp4","ws/video/webm"],
 | 
						|
  priority: MistUtil.object.keys(mistplayers).length + 1,
 | 
						|
  isMimeSupported: function (mimetype) {
 | 
						|
    return (this.mimes.indexOf(mimetype) == -1 ? false : true);
 | 
						|
  },
 | 
						|
  isBrowserSupported: function (mimetype,source,MistVideo) {
 | 
						|
    
 | 
						|
    if ((!("WebSocket" in window)) || (!("MediaSource" in window))) { return false; }
 | 
						|
    
 | 
						|
    //check for http/https mismatch
 | 
						|
    if (location.protocol.replace(/^http/,"ws") != MistUtil.http.url.split(source.url.replace(/^http/,"ws")).protocol) {
 | 
						|
      MistVideo.log("HTTP/HTTPS mismatch for this source");
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
    
 | 
						|
    //it runs on MacOS, but breaks often on seek/track switch etc
 | 
						|
    if (navigator.platform.toUpperCase().indexOf('MAC') >= 0) {
 | 
						|
      return false;
 | 
						|
    }
 | 
						|
 | 
						|
    //check (and save) codec compatibility
 | 
						|
    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 "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])] = MistVideo.info.meta.tracks[i].codec;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    var container = mimetype.split("/")[2];
 | 
						|
    function test(codecs) {
 | 
						|
      //if (container == "webm") { return true; }
 | 
						|
      return MediaSource.isTypeSupported("video/"+container+";codecs=\""+codecs+"\"");
 | 
						|
    }
 | 
						|
    source.supportedCodecs = [];
 | 
						|
    for (var i in codecs) {
 | 
						|
      //i is the long name (like mp4a.40.2), codecs[i] is the short name (like AAC)
 | 
						|
      var s = test(i);
 | 
						|
      if (s) {
 | 
						|
        source.supportedCodecs.push(codecs[i]);
 | 
						|
      }
 | 
						|
    }
 | 
						|
    if ((!MistVideo.options.forceType) && (!MistVideo.options.forcePlayer)) { //unless we force mews, skip this players if not both video and audio are supported
 | 
						|
      if (source.supportedCodecs.length < source.simul_tracks) {
 | 
						|
        MistVideo.log("Not enough playable tracks for this source");
 | 
						|
        return false;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    return source.supportedCodecs.length > 0;
 | 
						|
  },
 | 
						|
  player: function(){}
 | 
						|
};
 | 
						|
var p = mistplayers.mews.player;
 | 
						|
p.prototype = new MistPlayer();
 | 
						|
p.prototype.build = function (MistVideo,callback) {
 | 
						|
  
 | 
						|
  var video = document.createElement("video");
 | 
						|
  video.setAttribute("playsinline",""); //iphones. effin' iphones.
 | 
						|
  
 | 
						|
  //apply options
 | 
						|
  var attrs = ["autoplay","loop","poster"];
 | 
						|
  for (var i in attrs) {
 | 
						|
    var attr = attrs[i];
 | 
						|
    if (MistVideo.options[attr]) {
 | 
						|
      video.setAttribute(attr,(MistVideo.options[attr] === true ? "" : MistVideo.options[attr]));
 | 
						|
    }
 | 
						|
  }
 | 
						|
  if (MistVideo.options.muted) {
 | 
						|
    video.muted = true; //don't use attribute because of Chrome bug
 | 
						|
  }
 | 
						|
  if (MistVideo.info.type == "live") {
 | 
						|
    video.loop = false;
 | 
						|
  }
 | 
						|
  if (MistVideo.options.controls == "stock") {
 | 
						|
    video.setAttribute("controls","");
 | 
						|
  }
 | 
						|
  video.setAttribute("crossorigin","anonymous");
 | 
						|
  this.setSize = function(size){
 | 
						|
    video.style.width = size.width+"px";
 | 
						|
    video.style.height = size.height+"px";
 | 
						|
  };
 | 
						|
  
 | 
						|
  var player = this;
 | 
						|
  //player.debugging = true;
 | 
						|
  //player.debugging = "dl"; //download appended data on ms close
 | 
						|
  player.built = false;
 | 
						|
  
 | 
						|
  //this function is called both when the websocket is ready and the media source is ready - both should be open to proceed
 | 
						|
  function checkReady() {
 | 
						|
    //console.log("checkready",player.ws.readyState,player.ms.readyState);
 | 
						|
    if ((player.ws.readyState == player.ws.OPEN) && (player.ms.readyState == "open") && (player.sb)) {
 | 
						|
      if (!player.built) {
 | 
						|
        callback(video);
 | 
						|
        player.built = true;
 | 
						|
      }
 | 
						|
      if (MistVideo.options.autoplay) {
 | 
						|
        player.api.play().catch(function(){});
 | 
						|
      }
 | 
						|
 | 
						|
      return true;
 | 
						|
    }
 | 
						|
  }
 | 
						|
  this.msoninit = []; //array of functions that will be executed once ms is open
 | 
						|
  this.msinit = function() {
 | 
						|
    return new Promise(function(resolve,reject){ 
 | 
						|
      //prepare mediasource
 | 
						|
      player.ms = new MediaSource();
 | 
						|
      video.src = URL.createObjectURL(player.ms);
 | 
						|
      player.ms.onsourceopen = function(){
 | 
						|
        for (var i in player.msoninit) {
 | 
						|
          player.msoninit[i]();
 | 
						|
        }
 | 
						|
        player.msoninit = [];
 | 
						|
        resolve();
 | 
						|
      };
 | 
						|
      player.ms.onsourceclose = function(e){
 | 
						|
        if (player.debugging) console.error("ms close",e);
 | 
						|
        send({type:"stop"}); //stop sending data please something went wrong
 | 
						|
      };
 | 
						|
      player.ms.onsourceended = function(e){
 | 
						|
        if (player.debugging) console.error("ms ended",e);
 | 
						|
        
 | 
						|
        if (player.debugging == "dl") {
 | 
						|
          function downloadBlob (data, fileName, mimeType) {
 | 
						|
            var blob, url;
 | 
						|
            blob = new Blob([data], {
 | 
						|
              type: mimeType
 | 
						|
            });
 | 
						|
            url = window.URL.createObjectURL(blob);
 | 
						|
            downloadURL(url, fileName);
 | 
						|
            setTimeout(function() {
 | 
						|
              return window.URL.revokeObjectURL(url);
 | 
						|
            }, 1000);
 | 
						|
          };
 | 
						|
 | 
						|
          function downloadURL (data, fileName) {
 | 
						|
            var a;
 | 
						|
            a = document.createElement('a');
 | 
						|
            a.href = data;
 | 
						|
            a.download = fileName;
 | 
						|
            document.body.appendChild(a);
 | 
						|
            a.style = 'display: none';
 | 
						|
            a.click();
 | 
						|
            a.remove();
 | 
						|
          };
 | 
						|
        
 | 
						|
          var l = 0;
 | 
						|
          for (var i = 0; i < player.sb.appended.length; i++) {
 | 
						|
            l += player.sb.appended[i].length;
 | 
						|
          }
 | 
						|
          var d = new Uint8Array(l);
 | 
						|
          var l = 0;
 | 
						|
          for (var i = 0; i < player.sb.appended.length; i++) {
 | 
						|
            d.set(player.sb.appended[i],l);
 | 
						|
            l += player.sb.appended[i].length;
 | 
						|
          }
 | 
						|
          
 | 
						|
          downloadBlob(d, 'appended.mp4.bin', 'application/octet-stream');
 | 
						|
        }
 | 
						|
        send({type:"stop"}); //stop sending data please something went wrong
 | 
						|
      };
 | 
						|
    });
 | 
						|
  }
 | 
						|
  this.msinit().then(function(){
 | 
						|
    if (player.sb) {
 | 
						|
      MistVideo.log("Not creating source buffer as one already exists.");
 | 
						|
      checkReady();
 | 
						|
      return;
 | 
						|
    }
 | 
						|
  });
 | 
						|
  this.onsbinit = [];
 | 
						|
  this.sbinit = function(codecs){
 | 
						|
    if (!codecs) { MistVideo.showError("Did not receive any codec: nothing to initialize."); return; }
 | 
						|
 | 
						|
    //console.log("sourcebuffers",player.ms.sourceBuffers.length);
 | 
						|
    //console.log("sb init","video/"+MistVideo.source.type.split("/")[2]+";codecs=\""+codecs.join(",")+"\"");
 | 
						|
    player.sb = player.ms.addSourceBuffer("video/"+MistVideo.source.type.split("/")[2]+";codecs=\""+codecs.join(",")+"\"");
 | 
						|
    player.sb.mode = "segments"; //the fragments will be put in the buffer at the correct time: much better behavior when seeking / not playing from 0s
 | 
						|
    
 | 
						|
    //save the current source buffer codecs
 | 
						|
    player.sb._codecs = codecs;
 | 
						|
    
 | 
						|
    player.sb._size = 0;
 | 
						|
    player.sb.queue = [];
 | 
						|
    var do_on_updateend = [];
 | 
						|
    player.sb.do_on_updateend = do_on_updateend; //so we can check it from the ws onmessage handler too
 | 
						|
    player.sb.appending = null;
 | 
						|
    player.sb.appended = [];
 | 
						|
    var n = 0;
 | 
						|
    player.sb.addEventListener("updateend",function(){
 | 
						|
      if (!player.sb) {
 | 
						|
        MistVideo.log("Reached updateend but the source buffer is "+JSON.stringify(player.sb)+". ");
 | 
						|
        return;
 | 
						|
      }
 | 
						|
      //player.sb._busy = true;
 | 
						|
      //console.log("start updateend");
 | 
						|
      
 | 
						|
      if (player.debugging) {
 | 
						|
        if (player.sb.appending) player.sb.appended.push(player.sb.appending);
 | 
						|
        player.sb.appending = null;
 | 
						|
      }
 | 
						|
      
 | 
						|
      //every 500 fragments, clean the buffer (about every 15 sec)
 | 
						|
      if (n >= 500) {
 | 
						|
        //console.log(n,video.currentTime - video.buffered.start(0));
 | 
						|
        n = 0;
 | 
						|
        player.sb._clean(10); //keep 10 sec
 | 
						|
      }
 | 
						|
      else {
 | 
						|
        n++;
 | 
						|
      }
 | 
						|
      
 | 
						|
      var do_funcs = do_on_updateend.slice(); //clone the array
 | 
						|
      do_on_updateend = [];
 | 
						|
      for (var i in do_funcs) {
 | 
						|
        //console.log("do_funcs",Number(i)+1,"/",do_funcs.length);
 | 
						|
        if (!player.sb) {
 | 
						|
          if (player.debugging) { console.warn("I was doing on_updateend but the sb was reset"); } 
 | 
						|
          break;
 | 
						|
        }
 | 
						|
        if (player.sb.updating) {
 | 
						|
          //it's updating again >_>
 | 
						|
          do_on_updateend.concat(do_funcs.slice(i)); //add the remaining functions to do_on_updateend
 | 
						|
          if (player.debugging) { console.warn("I was doing on_updateend but was interrupted"); }
 | 
						|
          break;
 | 
						|
        }
 | 
						|
        do_funcs[i](i < do_funcs.length-1 ? do_funcs.slice(i) : []); //pass remaining do_funcs as argument
 | 
						|
      }
 | 
						|
      
 | 
						|
      if (!player.sb) { return; }
 | 
						|
 | 
						|
      player.sb._busy = false;
 | 
						|
      //console.log("end udpateend");
 | 
						|
      //console.log("onupdateend",player.sb.queue.length,player.sb.updating);
 | 
						|
      if (player.sb && player.sb.queue.length > 0 && !player.sb.updating && !video.error) {
 | 
						|
        //console.log("appending from queue");
 | 
						|
        player.sb._append(this.queue.shift());
 | 
						|
      }
 | 
						|
    });
 | 
						|
    player.sb.error = function(e){
 | 
						|
      console.error("sb error",e);
 | 
						|
    };
 | 
						|
    player.sb.abort = function(e){
 | 
						|
      console.error("sb abort",e);
 | 
						|
    };
 | 
						|
    
 | 
						|
    player.sb._doNext = function(func) {
 | 
						|
      do_on_updateend.push(func);
 | 
						|
    };
 | 
						|
    player.sb._do = function(func) {
 | 
						|
      if (this.updating || this._busy) {
 | 
						|
        this._doNext(func);
 | 
						|
      }
 | 
						|
      else {
 | 
						|
        func();
 | 
						|
      }
 | 
						|
    }
 | 
						|
    player.sb._append = function(data) {
 | 
						|
      if (!data) { return; }
 | 
						|
      if (!data.buffer) { return; }
 | 
						|
      if (player.debugging) { player.sb.appending = new Uint8Array(data); }
 | 
						|
      if (player.sb._busy) {
 | 
						|
        if (player.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.");
 | 
						|
        player.sb.queue.unshift(data);
 | 
						|
        return;
 | 
						|
      }
 | 
						|
      player.sb._busy = true;
 | 
						|
      //console.log("appendBuffer");
 | 
						|
      try {
 | 
						|
        player.sb.appendBuffer(data);
 | 
						|
      }
 | 
						|
      catch(e){
 | 
						|
        switch (e.name) {
 | 
						|
          case "QuotaExceededError": {
 | 
						|
            if (video.buffered.length) {
 | 
						|
              if (video.currentTime - video.buffered.start(0) > 1) {
 | 
						|
                //clear as much from the buffer as we can
 | 
						|
                MistVideo.log("Triggered QuotaExceededError: cleaning up "+(Math.round((video.currentTime - video.buffered.start(0) - 1)*10)/10)+"s");
 | 
						|
                player.sb._clean(1);
 | 
						|
              }
 | 
						|
              else {
 | 
						|
                var bufferEnd = video.buffered.end(video.buffered.length-1);
 | 
						|
                MistVideo.log("Triggered QuotaExceededError but there is nothing to clean: skipping ahead "+(Math.round((bufferEnd - video.currentTime)*10)/10)+"s");
 | 
						|
                video.currentTime = bufferEnd;
 | 
						|
              }
 | 
						|
              player.sb._busy = false;
 | 
						|
              player.sb._append(data); //now try again
 | 
						|
              return;
 | 
						|
            }
 | 
						|
            break;
 | 
						|
          }
 | 
						|
          case "InvalidStateError": {
 | 
						|
            player.api.pause(); //playback is borked, so stop downloading more data
 | 
						|
            if (MistVideo.video.error) {
 | 
						|
              //Failed to execute 'appendBuffer' on 'SourceBuffer': The HTMLMediaElement.error attribute is not null
 | 
						|
 | 
						|
              //the video element error is already triggering the showError()
 | 
						|
              return;
 | 
						|
            }
 | 
						|
            break;
 | 
						|
          }
 | 
						|
        }
 | 
						|
        MistVideo.showError(e.message);
 | 
						|
      }
 | 
						|
    }
 | 
						|
    
 | 
						|
    //we're initing the source buffer and there is a msg queue of data built up before the buffer was ready. Start by adding these data fragments to the source buffer
 | 
						|
    if (player.msgqueue) {
 | 
						|
      //There may be more than one msg queue, i.e. when rapidly switching tracks. Add only one msg queue and always add the oldest msg queue first.
 | 
						|
      if (player.msgqueue[0]) {
 | 
						|
        var do_do = false; //if there are no messages in the queue, make sure to execute any do_on_updateend functions right away
 | 
						|
        if (player.msgqueue[0].length) {
 | 
						|
          for (var i in player.msgqueue[0]) {
 | 
						|
            if (player.sb.updating || player.sb.queue.length || player.sb._busy) {
 | 
						|
              player.sb.queue.push(player.msgqueue[0][i]);
 | 
						|
            }
 | 
						|
            else {
 | 
						|
              //console.log("appending new data");
 | 
						|
              player.sb._append(player.msgqueue[0][i]);
 | 
						|
            }
 | 
						|
          }
 | 
						|
        }
 | 
						|
        else {
 | 
						|
          do_do = true;
 | 
						|
        }
 | 
						|
        player.msgqueue.shift();
 | 
						|
        if (player.msgqueue.length == 0) { player.msgqueue = false; }
 | 
						|
        MistVideo.log("The newly initialized source buffer was filled with data from a seperate message queue."+(player.msgqueue ? " "+player.msgqueue.length+" more message queue(s) remain." : ""));
 | 
						|
        if (do_do) {
 | 
						|
          MistVideo.log("The seperate message queue was empty; manually triggering any onupdateend functions");
 | 
						|
          player.sb.dispatchEvent(new Event("updateend"));
 | 
						|
        }
 | 
						|
      }
 | 
						|
    }
 | 
						|
    
 | 
						|
    //remove everything keepaway secs before the current playback position to keep sourcebuffer from filling up
 | 
						|
    player.sb._clean = function(keepaway){
 | 
						|
      if (!keepaway) keepaway = 180;
 | 
						|
      if (video.currentTime > keepaway) {
 | 
						|
        player.sb._do(function(){
 | 
						|
          //make sure end time is never 0
 | 
						|
          player.sb.remove(0,Math.max(0.1,video.currentTime - keepaway));
 | 
						|
        });
 | 
						|
      }
 | 
						|
    }
 | 
						|
 | 
						|
    if (player.onsbinit.length) {
 | 
						|
      player.onsbinit.shift()();
 | 
						|
    }
 | 
						|
    checkReady();
 | 
						|
  };
 | 
						|
  
 | 
						|
  this.wsconnect = function(){
 | 
						|
    return new Promise(function(resolve,reject){
 | 
						|
      //prepare websocket (both data and messages)
 | 
						|
      this.ws = new WebSocket(MistVideo.source.url);
 | 
						|
      this.ws.binaryType = "arraybuffer";
 | 
						|
      
 | 
						|
      this.ws.s = this.ws.send;
 | 
						|
      this.ws.send = function(){
 | 
						|
        if (this.readyState == 1) {
 | 
						|
          return this.s.apply(this,arguments);
 | 
						|
        }
 | 
						|
        return false;
 | 
						|
      };
 | 
						|
      this.ws.onopen = function(){
 | 
						|
        this.wasConnected = true;
 | 
						|
        resolve();
 | 
						|
      };
 | 
						|
      this.ws.onerror = function(e){
 | 
						|
        MistVideo.showError("MP4 over WS: websocket error");
 | 
						|
      };
 | 
						|
      this.ws.onclose = function(e){
 | 
						|
        MistVideo.log("MP4 over WS: websocket closed");
 | 
						|
        if (this.wasConnected && (!MistVideo.destroyed) && (!player.sb || !player.sb.paused) && (MistVideo.state == "Stream is online") && (!(MistVideo.video && MistVideo.video.error))) {
 | 
						|
          MistVideo.log("MP4 over WS: reopening websocket");
 | 
						|
          player.wsconnect().then(function(){
 | 
						|
            if (!player.sb) {
 | 
						|
              //retrieve codec info
 | 
						|
              var f = function(msg){
 | 
						|
                //got codec data, set up source buffer
 | 
						|
                if (!player.sb) { player.sbinit(msg.data.codecs); }
 | 
						|
                else { player.api.play().catch(function(){}); }
 | 
						|
 | 
						|
                player.ws.removeListener("codec_data",f);
 | 
						|
              };
 | 
						|
              player.ws.addListener("codec_data",f);
 | 
						|
              send({type:"request_codec_data",supported_codecs:MistVideo.source.supportedCodecs});
 | 
						|
            }
 | 
						|
            else {
 | 
						|
              player.api.play();
 | 
						|
            }
 | 
						|
          },function(){
 | 
						|
            Mistvideo.error("Lost connection to the Media Server");
 | 
						|
          });
 | 
						|
        }
 | 
						|
      };
 | 
						|
      this.ws.timeOut = MistVideo.timers.start(function(){
 | 
						|
        if (player.ws.readyState == 0) {
 | 
						|
          MistVideo.log("MP4 over WS: socket timeout - try next combo");
 | 
						|
          MistVideo.nextCombo();
 | 
						|
        }
 | 
						|
      },5e3);
 | 
						|
 | 
						|
      this.ws.listeners = {}; //kind of event listener list for websocket messages
 | 
						|
      this.ws.addListener = function(type,f){
 | 
						|
        if (!(type in this.listeners)) { this.listeners[type] = []; }
 | 
						|
        this.listeners[type].push(f);
 | 
						|
      };
 | 
						|
      this.ws.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;
 | 
						|
      }
 | 
						|
      player.msgqueue = false;
 | 
						|
      var requested_rate = 1;
 | 
						|
      var serverdelay = [];
 | 
						|
      var currenttracks = [];
 | 
						|
      this.ws.onmessage = function(e){
 | 
						|
        if (!e.data) { throw "Received invalid data"; }
 | 
						|
        if (typeof e.data == "string") {
 | 
						|
          var msg = JSON.parse(e.data);
 | 
						|
          if (player.debugging && (msg.type != "on_time")) { console.log("ws message",msg); }
 | 
						|
          switch (msg.type) {
 | 
						|
            case "on_stop": {
 | 
						|
              //the last fragment has been added to the buffer
 | 
						|
              var eObj;
 | 
						|
              eObj = MistUtil.event.addListener(video,"waiting",function(e){
 | 
						|
                player.sb.paused = true;
 | 
						|
                MistUtil.event.send("ended",null,video);
 | 
						|
                MistUtil.event.removeListener(eObj);
 | 
						|
              });
 | 
						|
              player.ws.onclose = function(){}; //don't reopen websocket, just close, it's okay, rly
 | 
						|
              break;
 | 
						|
            }
 | 
						|
            case "on_time": {              
 | 
						|
              var buffer = msg.data.current - video.currentTime*1e3;
 | 
						|
              var serverDelay = player.ws.serverDelay.get();
 | 
						|
              var desiredBuffer = Math.max(100+serverDelay,serverDelay*2);
 | 
						|
              var desiredBufferwithJitter = desiredBuffer+(msg.data.jitter ? msg.data.jitter : 0);
 | 
						|
 | 
						|
 | 
						|
              if (MistVideo.info.type != "live") { desiredBuffer += 2000; } //if VoD, keep an extra 2 seconds of buffer
 | 
						|
 | 
						|
              if (player.debugging) {
 | 
						|
                console.log(
 | 
						|
                  "on_time received", msg.data.current/1e3,
 | 
						|
                  "currtime", video.currentTime,
 | 
						|
                  requested_rate+"x",
 | 
						|
                  "buffer",Math.round(buffer),"/",Math.round(desiredBuffer),
 | 
						|
                  (MistVideo.info.type == "live" ? "latency:"+Math.round(msg.data.end-video.currentTime*1e3)+"ms" : ""),
 | 
						|
                  (player.monitor ? "bitrate:"+MistUtil.format.bits(player.monitor.currentBps)+"/s" : ""),
 | 
						|
                  "listeners",player.ws.listeners && player.ws.listeners.on_time ? player.ws.listeners.on_time : 0,
 | 
						|
                  "msgqueue",player.msgqueue ? player.msgqueue.length : 0,
 | 
						|
                  "readyState",MistVideo.video.readyState,msg.data
 | 
						|
                );
 | 
						|
              }
 | 
						|
 | 
						|
              if (!player.sb) {
 | 
						|
                MistVideo.log("Received on_time, but the source buffer is being cleared right now. Ignoring.");
 | 
						|
                break;
 | 
						|
              }
 | 
						|
 | 
						|
              if (lastduration != msg.data.end*1e-3) {
 | 
						|
                lastduration = msg.data.end*1e-3;
 | 
						|
                MistUtil.event.send("durationchange",null,MistVideo.video);
 | 
						|
              }
 | 
						|
              MistVideo.info.meta.buffer_window = msg.data.end - msg.data.begin;
 | 
						|
              player.sb.paused = false;
 | 
						|
 | 
						|
              if (MistVideo.info.type == "live") {
 | 
						|
                if (requested_rate == 1) {
 | 
						|
                  if (msg.data.play_rate_curr == "auto") {
 | 
						|
                    if (video.currentTime > 0) { //give it some time to seek to live first when starting up
 | 
						|
                      //assume we want to be as live as possible
 | 
						|
                      if (buffer > desiredBufferwithJitter*2) {
 | 
						|
                        requested_rate = 1 + Math.min(1,((buffer-desiredBufferwithJitter)/desiredBufferwithJitter))*0.08;
 | 
						|
                        video.playbackRate *= requested_rate;
 | 
						|
                        MistVideo.log("Our buffer ("+Math.round(buffer)+"ms) is big (>"+Math.round(desiredBufferwithJitter*2)+"ms), so increase the playback speed to "+(Math.round(requested_rate*100)/100)+" to catch up.");
 | 
						|
                      }
 | 
						|
                      else if (buffer < 0) {
 | 
						|
                        requested_rate = 0.8;
 | 
						|
                        video.playbackRate *= requested_rate;
 | 
						|
                        MistVideo.log("Our buffer ("+Math.round(buffer)+"ms) is negative so decrease the playback speed to "+(Math.round(requested_rate*100)/100)+" to let it catch up.");
 | 
						|
                      }
 | 
						|
                      else if (buffer < desiredBuffer/2) {
 | 
						|
                        requested_rate = 1 + Math.min(1,((buffer-desiredBuffer)/desiredBuffer))*0.08;
 | 
						|
                        video.playbackRate *= requested_rate;
 | 
						|
                        MistVideo.log("Our buffer ("+Math.round(buffer)+"ms) is small (<"+Math.round(desiredBuffer/2)+"ms), so decrease the playback speed to "+(Math.round(requested_rate*100)/100)+" to catch up.");
 | 
						|
                      }
 | 
						|
                    }
 | 
						|
                  }
 | 
						|
                }
 | 
						|
                else if (requested_rate > 1) {
 | 
						|
                  if (buffer < desiredBufferwithJitter) {
 | 
						|
                    video.playbackRate /= requested_rate;
 | 
						|
                    requested_rate = 1;
 | 
						|
                    MistVideo.log("Our buffer ("+Math.round(buffer)+"ms) is small enough (<"+Math.round(desiredBufferwithJitter)+"ms), so return to real time playback.");
 | 
						|
                  }
 | 
						|
                }
 | 
						|
                else {
 | 
						|
                  //requested rate < 1
 | 
						|
                  if (buffer > desiredBufferwithJitter) {
 | 
						|
                    video.playbackRate /= requested_rate;
 | 
						|
                    requested_rate = 1;
 | 
						|
                    MistVideo.log("Our buffer ("+Math.round(buffer)+"ms) is big enough (>"+Math.round(desiredBufferwithJitter)+"ms), so return to real time playback.");
 | 
						|
                  }
 | 
						|
                }
 | 
						|
              }
 | 
						|
              else {
 | 
						|
                //it's VoD, change the rate at which the server sends data to try and keep the buffer small
 | 
						|
                if (requested_rate == 1) {
 | 
						|
                  if (msg.data.play_rate_curr == "auto") {
 | 
						|
                    if (buffer < desiredBuffer/2) {
 | 
						|
                      if (buffer < -10e3) {
 | 
						|
                        //seek to play point
 | 
						|
                        send({type: "seek", seek_time: Math.round(video.currentTime*1e3)});
 | 
						|
                      }
 | 
						|
                      else {
 | 
						|
                        //negative buffer? ask for faster delivery
 | 
						|
                        requested_rate = 2;
 | 
						|
                        MistVideo.log("Our buffer is negative, so request a faster download rate.");
 | 
						|
                        send({type: "set_speed", play_rate: requested_rate});
 | 
						|
                      }
 | 
						|
                    }
 | 
						|
                    else if (buffer - desiredBuffer > desiredBuffer) {
 | 
						|
                      MistVideo.log("Our buffer is big, so request a slower download rate.");
 | 
						|
                      requested_rate = 0.5;
 | 
						|
                      send({type: "set_speed", play_rate: requested_rate});
 | 
						|
                    }
 | 
						|
                  }
 | 
						|
                }
 | 
						|
                else if (requested_rate > 1) {
 | 
						|
                  if (buffer > desiredBuffer) {
 | 
						|
                    //we have enough buffer, ask for real time delivery
 | 
						|
                    send({type: "set_speed", play_rate: "auto"});
 | 
						|
                    requested_rate = 1;
 | 
						|
                    MistVideo.log("The buffer is big enough, so ask for realtime download rate.");
 | 
						|
                  }
 | 
						|
                }
 | 
						|
                else { //requested_rate < 1
 | 
						|
                  if (buffer < desiredBuffer) {
 | 
						|
                    //we have a small enough bugger, ask for real time delivery
 | 
						|
                    send({type: "set_speed", play_rate: "auto"});
 | 
						|
                    requested_rate = 1;
 | 
						|
                    MistVideo.log("The buffer is small enough, so ask for realtime download rate.");
 | 
						|
                  }
 | 
						|
                }
 | 
						|
              }
 | 
						|
 | 
						|
              if (MistVideo.reporting && msg.data.tracks) {
 | 
						|
                MistVideo.reporting.stats.d.tracks = msg.data.tracks.join(",");
 | 
						|
              }
 | 
						|
              
 | 
						|
              //check if the tracks are different than before, and if so, signal the skin to display the playing tracks
 | 
						|
              if ((msg.data.tracks) && (currenttracks != msg.data.tracks)) {
 | 
						|
                var tracks = MistVideo.info ? MistUtil.tracks.parse(MistVideo.info.meta.tracks) : [];
 | 
						|
                for (var i in msg.data.tracks) {
 | 
						|
                  if (currenttracks.indexOf(msg.data.tracks[i]) < 0) {
 | 
						|
                    //find track type
 | 
						|
                    var type;
 | 
						|
                    for (var j in tracks) {
 | 
						|
                      if (msg.data.tracks[i] in tracks[j]) {
 | 
						|
                        type = j;
 | 
						|
                        break;
 | 
						|
                      }
 | 
						|
                    }
 | 
						|
                    if (!type) {
 | 
						|
                      //track type not found, this should not happen
 | 
						|
                      continue;
 | 
						|
                    }
 | 
						|
 | 
						|
                    //create an event to pass this to the skin
 | 
						|
                    MistUtil.event.send("playerUpdate_trackChanged",{
 | 
						|
                      type: type,
 | 
						|
                      trackid: msg.data.tracks[i]
 | 
						|
                    },MistVideo.video);
 | 
						|
                  }
 | 
						|
                }
 | 
						|
 | 
						|
                currenttracks = msg.data.tracks;
 | 
						|
              }
 | 
						|
 | 
						|
              break;
 | 
						|
            }
 | 
						|
            case "tracks": {
 | 
						|
              //check if all codecs are equal to the ones we were using before
 | 
						|
              function checkEqual(arr1,arr2) {
 | 
						|
                if (!arr2) { return false; }
 | 
						|
                if (arr1.length != arr2.length) { return false; }
 | 
						|
                for (var i in arr1) {
 | 
						|
                  if (arr2.indexOf(arr1[i]) < 0) {
 | 
						|
                    return false;
 | 
						|
                  }
 | 
						|
                }
 | 
						|
                return true;
 | 
						|
              }
 | 
						|
              
 | 
						|
 | 
						|
              if (checkEqual(player.last_codecs ? player.last_codecs : player.sb._codecs,msg.data.codecs)) {
 | 
						|
                MistVideo.log("Player switched tracks, keeping source buffer as codecs are the same as before.");
 | 
						|
                if ((video.currentTime == 0) && (msg.data.current != 0)) {
 | 
						|
                  video.currentTime = msg.data.current;
 | 
						|
                }
 | 
						|
              }
 | 
						|
              else {
 | 
						|
                if (player.debugging) {
 | 
						|
                  console.warn("Different codecs!");
 | 
						|
                  console.warn("video time",video.currentTime,"switch startpoint",msg.data.current*1e-3);
 | 
						|
                }
 | 
						|
                player.last_codecs = msg.data.codecs;
 | 
						|
                //start gathering messages in a new msg queue. They won't be appended to the current source buffer
 | 
						|
                if (player.msgqueue) {
 | 
						|
                  player.msgqueue.push([]);
 | 
						|
                }
 | 
						|
                else {
 | 
						|
                  player.msgqueue = [[]];
 | 
						|
                }
 | 
						|
                //play out buffer, then when we reach the starting timestamp of the new data, reset the source buffers
 | 
						|
                var clear = function(){
 | 
						|
                  //once the source buffer is done updating the current segment, clear the specified interval from the buffer
 | 
						|
                  currPos = video.currentTime.toFixed(3);
 | 
						|
                  if (player && player.sb) {
 | 
						|
                    player.sb._do(function(remaining_do_on_updateend){
 | 
						|
                      if (!player.sb.updating) {
 | 
						|
                        if (!isNaN(player.ms.duration)) player.sb.remove(0,Infinity);
 | 
						|
                        player.sb.queue = [];
 | 
						|
                        player.ms.removeSourceBuffer(player.sb);
 | 
						|
                        player.sb = null;
 | 
						|
                        var t = (msg.data.current*1e-3).toFixed(3); //rounded because of floating point issues
 | 
						|
                        video.src = "";
 | 
						|
                        player.ms.onsourceclose = null;
 | 
						|
                        player.ms.onsourceended = null;
 | 
						|
                        //console.log("sb murdered");
 | 
						|
                        if (player.debugging && remaining_do_on_updateend && remaining_do_on_updateend.length) {
 | 
						|
                          console.warn("There are do_on_updateend functions queued, which I will re-apply after clearing the sb.");
 | 
						|
                        }
 | 
						|
 | 
						|
                        player.msinit().then(function(){
 | 
						|
                          player.sbinit(msg.data.codecs);
 | 
						|
                          player.sb.do_on_updateend = remaining_do_on_updateend;
 | 
						|
 | 
						|
                          var e = MistUtil.event.addListener(video,"loadedmetadata",function(){
 | 
						|
                            MistVideo.log("Buffer cleared");
 | 
						|
 | 
						|
                            var f = function() {
 | 
						|
                              if (currPos > t) {
 | 
						|
                                t = currPos;
 | 
						|
                              }
 | 
						|
                              if (!video.buffered.length || (video.buffered.end(video.buffered.length-1) < t)) {
 | 
						|
                                if (player.debugging) { console.log("Desired seeking position ("+MistUtil.format.time(t,{ms:true})+") not yet in buffer ("+(video.buffered.length ? MistUtil.format.time(video.buffered.end(video.buffered.length-1),{ms:true}) : "null")+")"); }
 | 
						|
                                player.sb._doNext(f);
 | 
						|
                                return;
 | 
						|
                              }
 | 
						|
                              video.currentTime = t;
 | 
						|
                              MistVideo.log("Setting playback position to "+MistUtil.format.time(t,{ms:true}));
 | 
						|
                              if (video.currentTime.toFixed(3) < t) {
 | 
						|
                                player.sb._doNext(f);
 | 
						|
                                if (player.debugging) { console.log("Could not set playback position"); }
 | 
						|
                              }
 | 
						|
                              else {
 | 
						|
                                if (player.debugging) { console.log("Set playback position to "+MistUtil.format.time(t,{ms:true})); }
 | 
						|
                                var p = function(){
 | 
						|
                                  player.sb._doNext(function(){
 | 
						|
                                    if (video.buffered.length) {
 | 
						|
                                      //if (player.debugging) { console.log(video.buffered.start(0),video.buffered.end(0),video.currentTime); }
 | 
						|
                                      if (video.buffered.start(0) > video.currentTime) { 
 | 
						|
                                        var b = video.buffered.start(0);
 | 
						|
                                        video.currentTime = b;
 | 
						|
                                        if (video.currentTime != b) {
 | 
						|
                                          p();
 | 
						|
                                        }
 | 
						|
                                      }
 | 
						|
                                    }
 | 
						|
                                    else {
 | 
						|
                                      p();
 | 
						|
                                    }
 | 
						|
                                  });
 | 
						|
                                };
 | 
						|
                                p();
 | 
						|
                              }
 | 
						|
                            }
 | 
						|
                            f();
 | 
						|
 | 
						|
                            MistUtil.event.removeListener(e);
 | 
						|
                          });
 | 
						|
                        });
 | 
						|
                      }
 | 
						|
                      else {
 | 
						|
                        clear();
 | 
						|
                      }
 | 
						|
                    });
 | 
						|
                  }
 | 
						|
                  else {
 | 
						|
                    if (player.debugging) { console.warn("sb not available to do clear"); }
 | 
						|
                    player.onsbinit.push(clear);
 | 
						|
                  }
 | 
						|
                };
 | 
						|
 | 
						|
                if (!msg.data.codecs || !msg.data.codecs.length) {
 | 
						|
                  MistVideo.showError("Track switch does not contain any codecs, aborting.");
 | 
						|
                  //reset setTracks to auto
 | 
						|
                  MistVideo.options.setTracks = false;
 | 
						|
                  clear();
 | 
						|
                  break;
 | 
						|
                }
 | 
						|
                function reachedSwitchingPoint(msg) {
 | 
						|
                  if (player.debugging) {
 | 
						|
                    console.warn("reached switching point",msg.data.current*1e-3,MistUtil.format.time(msg.data.current*1e-3));
 | 
						|
                  }
 | 
						|
                  MistVideo.log("Track switch: reached switching point");
 | 
						|
                  clear();
 | 
						|
                }
 | 
						|
                if (video.currentTime == 0) {
 | 
						|
                  reachedSwitchingPoint(msg);
 | 
						|
                }
 | 
						|
                else {
 | 
						|
                  if (msg.data.current >= video.currentTime*1e3) {
 | 
						|
                    MistVideo.log("Track switch: waiting for playback to reach the switching point ("+MistUtil.format.time(msg.data.current*1e-3,{ms:true})+")");
 | 
						|
 | 
						|
                    //wait untill the video has reached the time of the newly received track or the end of our buffer
 | 
						|
                    var ontime = MistUtil.event.addListener(video,"timeupdate",function(){
 | 
						|
                      if (msg.data.current < video.currentTime * 1e3) {
 | 
						|
                        if (player.debugging) { console.log("Track switch: video.currentTime has reached switching point."); }
 | 
						|
                        reachedSwitchingPoint(msg);
 | 
						|
                        MistUtil.event.removeListener(ontime);
 | 
						|
                        MistUtil.event.removeListener(onwait);
 | 
						|
                      }
 | 
						|
                    });
 | 
						|
                    var onwait = MistUtil.event.addListener(video,"waiting",function(){
 | 
						|
                      if (player.debugging) { console.log("Track switch: video has reached end of buffer.","Gap:",Math.round(msg.data.current - video.currentTime * 1e3),"ms"); }
 | 
						|
                      reachedSwitchingPoint(msg);
 | 
						|
                      MistUtil.event.removeListener(ontime);
 | 
						|
                      MistUtil.event.removeListener(onwait);
 | 
						|
                    });
 | 
						|
                  } 
 | 
						|
                  else {
 | 
						|
                    //subscribe to on_time, wait until we've received current playback point
 | 
						|
                    //if we don't wait, the screen will go black until the buffer is full enough
 | 
						|
                    MistVideo.log("Track switch: waiting for the received data to reach the current playback point");
 | 
						|
                    var ontime = function(newmsg){
 | 
						|
                      if (newmsg.data.current >= video.currentTime*1e3) {
 | 
						|
                        reachedSwitchingPoint(newmsg);
 | 
						|
                        player.ws.removeListener("on_time",ontime);
 | 
						|
                      }
 | 
						|
                    }
 | 
						|
                    player.ws.addListener("on_time",ontime);
 | 
						|
                  }
 | 
						|
                }
 | 
						|
              }
 | 
						|
              break;
 | 
						|
            }
 | 
						|
            case "pause": {
 | 
						|
              if (player.sb) { player.sb.paused = true; }
 | 
						|
              break;
 | 
						|
            }
 | 
						|
          }
 | 
						|
          if (msg.type in this.listeners) {
 | 
						|
            for (var i = this.listeners[msg.type].length-1; i >= 0; i--) { //start at last in case the listeners remove themselves
 | 
						|
              this.listeners[msg.type][i](msg);
 | 
						|
            }
 | 
						|
          }
 | 
						|
          return;
 | 
						|
        }
 | 
						|
        var data = new Uint8Array(e.data);
 | 
						|
        if (data) {
 | 
						|
          if (player.monitor && player.monitor.bitCounter) {
 | 
						|
            for (var i in player.monitor.bitCounter) {
 | 
						|
              player.monitor.bitCounter[i] += e.data.byteLength*8;
 | 
						|
            }
 | 
						|
          }
 | 
						|
          if ((player.sb) && (!player.msgqueue)) {
 | 
						|
            if (player.sb.updating || player.sb.queue.length || player.sb._busy) {
 | 
						|
              player.sb.queue.push(data);
 | 
						|
            }
 | 
						|
            else {
 | 
						|
              //console.log("appending new data");
 | 
						|
              player.sb._append(data);
 | 
						|
            }
 | 
						|
          }
 | 
						|
          else {
 | 
						|
            //There is no active source buffer or we're preparing for a track switch.
 | 
						|
            //Any data is kept in a seperate buffer and won't be appended to the source buffer until it is reinitialised.
 | 
						|
            if (!player.msgqueue) { player.msgqueue = [[]]; }
 | 
						|
            //There may be more than one seperate buffer (in case of rapid track switches), always append to the last of the buffers
 | 
						|
            player.msgqueue[player.msgqueue.length-1].push(data);
 | 
						|
          }
 | 
						|
        }
 | 
						|
        else {
 | 
						|
          //console.warn("no data, wut?",data,new Uint8Array(e.data));
 | 
						|
          MistVideo.log("Expecting data from websocket, but received none?!");
 | 
						|
        }
 | 
						|
      }
 | 
						|
      
 | 
						|
      
 | 
						|
      this.ws.serverDelay = {
 | 
						|
        delays: [],
 | 
						|
        log: function (type) {
 | 
						|
          var responseType = false;
 | 
						|
          switch (type) {
 | 
						|
            case "seek":
 | 
						|
            case "set_speed": {
 | 
						|
              //wait for cmd.type
 | 
						|
              responseType = type;
 | 
						|
              break;
 | 
						|
            }
 | 
						|
            case "request_codec_data": {
 | 
						|
              responseType = "codec_data";
 | 
						|
              break;
 | 
						|
            }
 | 
						|
            default: {
 | 
						|
              //do nothing
 | 
						|
              return;
 | 
						|
            }
 | 
						|
          }
 | 
						|
          if (responseType) {
 | 
						|
            var starttime = new Date().getTime();
 | 
						|
            function onResponse() {
 | 
						|
              player.ws.serverDelay.add(new Date().getTime() - starttime);
 | 
						|
              player.ws.removeListener(responseType,onResponse);
 | 
						|
            }
 | 
						|
            player.ws.addListener(responseType,onResponse);
 | 
						|
          }
 | 
						|
        },
 | 
						|
        add: function(delay){
 | 
						|
          this.delays.unshift(delay);
 | 
						|
          if (this.delays.length > 5) {
 | 
						|
            this.delays.splice(5);
 | 
						|
          }
 | 
						|
        },
 | 
						|
        get: function(){
 | 
						|
          if (this.delays.length) {
 | 
						|
            //return average of the last 3 recorded delays
 | 
						|
            let sum = 0;
 | 
						|
            let i = 0;
 | 
						|
            for (null; i < this.delays.length; i++){
 | 
						|
              if (i >= 3) { break; }
 | 
						|
              sum += this.delays[i];
 | 
						|
            }
 | 
						|
            return sum/i;
 | 
						|
          }
 | 
						|
          return 500;
 | 
						|
        }
 | 
						|
      };
 | 
						|
    }.bind(this));
 | 
						|
  };
 | 
						|
  this.wsconnect().then(function(){
 | 
						|
    //retrieve codec info
 | 
						|
    var f = function(msg){
 | 
						|
      //got codec data, set up source buffer
 | 
						|
      if (player.ms && player.ms.readyState == "open") {
 | 
						|
        player.sbinit(msg.data.codecs);
 | 
						|
      }
 | 
						|
      else {
 | 
						|
        player.msoninit.push(function(){
 | 
						|
          player.sbinit(msg.data.codecs);
 | 
						|
        });
 | 
						|
      }
 | 
						|
      
 | 
						|
      player.ws.removeListener("codec_data",f);
 | 
						|
    };
 | 
						|
    this.ws.addListener("codec_data",f);
 | 
						|
    send({type:"request_codec_data",supported_codecs:MistVideo.source.supportedCodecs});
 | 
						|
  }.bind(this));
 | 
						|
  
 | 
						|
  function send(cmd){
 | 
						|
    if (!player.ws) { throw "No websocket to send to"; }
 | 
						|
    if (player.ws.readyState >= player.ws.CLOSING) {
 | 
						|
      //throw "WebSocket has been closed already.";
 | 
						|
      MistVideo.log("MP4 over WS: reopening websocket");
 | 
						|
      player.wsconnect().then(function(){
 | 
						|
        if (!player.sb) {
 | 
						|
          //retrieve codec info
 | 
						|
          var f = function(msg){
 | 
						|
            //got codec data, set up source buffer
 | 
						|
            if (!player.sb) { player.sbinit(msg.data.codecs); }
 | 
						|
            else { player.api.play().catch(function(){}); }
 | 
						|
 | 
						|
            player.ws.removeListener("codec_data",f);
 | 
						|
          };
 | 
						|
          player.ws.addListener("codec_data",f);
 | 
						|
          send({type:"request_codec_data",supported_codecs:MistVideo.source.supportedCodecs});
 | 
						|
        }
 | 
						|
        else {
 | 
						|
          player.api.play();
 | 
						|
        }
 | 
						|
        send(cmd);
 | 
						|
      },function(){
 | 
						|
        Mistvideo.error("Lost connection to the Media Server");
 | 
						|
      });
 | 
						|
      return;
 | 
						|
    }
 | 
						|
    if (player.debugging) { console.log("ws send",cmd); }
 | 
						|
    
 | 
						|
    player.ws.serverDelay.log(cmd.type);
 | 
						|
    player.ws.send(JSON.stringify(cmd));
 | 
						|
  }
 | 
						|
 | 
						|
  player.findBuffer = function (position) {
 | 
						|
    var buffern = false;
 | 
						|
    for (var i = 0; i < video.buffered.length; i++) {
 | 
						|
      if ((video.buffered.start(i) <= position) && (video.buffered.end(i) >= position)) {
 | 
						|
        buffern = i;
 | 
						|
        break;
 | 
						|
      }
 | 
						|
    }
 | 
						|
    return buffern;
 | 
						|
  };
 | 
						|
  
 | 
						|
  this.api = {
 | 
						|
    play: function(skipToLive){
 | 
						|
      return new Promise(function(resolve,reject){
 | 
						|
        if (!video.paused) { 
 | 
						|
          //we're already playing, what are you doing?
 | 
						|
          resolve();
 | 
						|
          return;
 | 
						|
        }
 | 
						|
 | 
						|
        if (("paused" in player.sb) && !player.sb.paused) {
 | 
						|
          video.play().then(resolve).catch(reject);
 | 
						|
          return;
 | 
						|
        }
 | 
						|
 | 
						|
 | 
						|
        var f = function(e){
 | 
						|
          if (!player.sb) {
 | 
						|
            MistVideo.log("Attempting to play, but the source buffer is being cleared. Waiting for next on_time.");
 | 
						|
            return;
 | 
						|
          }
 | 
						|
          if (MistVideo.info.type == "live") {
 | 
						|
            if (skipToLive || (video.currentTime == 0)) {
 | 
						|
              var g = function(){
 | 
						|
                if (video.buffered.length) {
 | 
						|
                  //is data.current contained within a buffer? is video.currentTime also contained in that buffer? if not, seek the video
 | 
						|
                  var buffern = player.findBuffer(e.data.current*1e-3);
 | 
						|
                  if (buffern !== false) {
 | 
						|
                    if ((video.buffered.start(buffern) > video.currentTime) || (video.buffered.end(buffern) < video.currentTime)) {
 | 
						|
                      video.currentTime = e.data.current*1e-3;
 | 
						|
                      MistVideo.log("Setting live playback position to "+MistUtil.format.time(video.currentTime));
 | 
						|
                    }
 | 
						|
                    video.play().then(resolve).catch(function(){
 | 
						|
                      //could not play video, pause the download
 | 
						|
                      return reject.apply(this,arguments);
 | 
						|
                    });
 | 
						|
                    player.sb.paused = false;                   
 | 
						|
                    player.sb.removeEventListener("updateend",g);
 | 
						|
                  }
 | 
						|
                }
 | 
						|
              };
 | 
						|
              player.sb.addEventListener("updateend",g);
 | 
						|
            }
 | 
						|
            else {
 | 
						|
              player.sb.paused = false;
 | 
						|
              video.play().then(resolve).catch(function(){
 | 
						|
                //could not play video, pause the download
 | 
						|
                player.api.pause();
 | 
						|
                return reject.apply(this,arguments);
 | 
						|
              });
 | 
						|
            }
 | 
						|
            player.ws.removeListener("on_time",f);
 | 
						|
          }
 | 
						|
          else if (e.data.current > video.currentTime) {
 | 
						|
            player.sb.paused = false;
 | 
						|
            if (video.buffered.length && video.buffered.start(0) > video.currentTime) {
 | 
						|
              video.currentTime = video.buffered.start(0);
 | 
						|
            }
 | 
						|
            video.play().then(resolve).catch(reject);
 | 
						|
            player.ws.removeListener("on_time",f);
 | 
						|
          }
 | 
						|
        };
 | 
						|
        player.ws.addListener("on_time",f);
 | 
						|
        var cmd = {type:"play"};
 | 
						|
        if (skipToLive) { cmd.seek_time = "live"; }
 | 
						|
        send(cmd);
 | 
						|
      });
 | 
						|
    },
 | 
						|
    pause: function(){
 | 
						|
      video.pause();
 | 
						|
      send({type: "hold"});
 | 
						|
      if (player.sb) { player.sb.paused = true; }
 | 
						|
    },
 | 
						|
    setTracks: function(obj){
 | 
						|
      if (!MistUtil.object.keys(obj).length) { return; }
 | 
						|
      obj.type = "tracks";
 | 
						|
      obj = MistUtil.object.extend({
 | 
						|
        type: "tracks",
 | 
						|
        //seek_time:  Math.round(Math.max(0,video.currentTime*1e3-(500+player.ws.serverDelay.get())))
 | 
						|
      },obj);
 | 
						|
      send(obj);
 | 
						|
    },
 | 
						|
    unload: function(){
 | 
						|
      player.api.pause();
 | 
						|
      player.sb._do(function(){
 | 
						|
        player.sb.remove(0,Infinity);
 | 
						|
        try {
 | 
						|
          player.ms.endOfStream();
 | 
						|
          
 | 
						|
          //it's okay if it fails
 | 
						|
        } catch (e) {  }
 | 
						|
      });
 | 
						|
      player.ws.close();
 | 
						|
    }
 | 
						|
  };
 | 
						|
 
 | 
						|
  //override seeking
 | 
						|
  Object.defineProperty(this.api,"currentTime",{
 | 
						|
    get: function(){
 | 
						|
      return video.currentTime;
 | 
						|
    },
 | 
						|
    set: function(value){
 | 
						|
      //console.warn("seek to",value);
 | 
						|
      if (isNaN(value) || (value < 0)) {
 | 
						|
        MistVideo.log("Ignoring seek to "+value+" because ewww.");
 | 
						|
        return;
 | 
						|
      }
 | 
						|
      MistUtil.event.send("seeking",value,video);
 | 
						|
      send({type: "seek", seek_time: Math.round(Math.max(0,value*1e3-(250+player.ws.serverDelay.get())))}); //safety margin for server latency
 | 
						|
      //set listener "seek"
 | 
						|
      var onseek = function(e){
 | 
						|
        player.ws.removeListener("seek",onseek);
 | 
						|
        var ontime = function(e){
 | 
						|
          player.ws.removeListener("on_time",ontime);
 | 
						|
          //in the first on_time, assume that the data were getting is where we want to be
 | 
						|
          value = e.data.current * 1e-3;
 | 
						|
          value = value.toFixed(3);
 | 
						|
          //retry a max of 10 times
 | 
						|
          var retries = 10;
 | 
						|
          var f = function() {
 | 
						|
            video.currentTime = value;
 | 
						|
            if (video.currentTime.toFixed(3) < value) {
 | 
						|
              MistVideo.log("Failed to seek, wanted: "+value+" got: "+video.currentTime.toFixed(3));
 | 
						|
              if (retries >= 0) {
 | 
						|
                retries--;
 | 
						|
                player.sb._doNext(f);
 | 
						|
              }
 | 
						|
            }
 | 
						|
          }
 | 
						|
          f();
 | 
						|
        };
 | 
						|
        player.ws.addListener("on_time",ontime);
 | 
						|
      }
 | 
						|
      player.ws.addListener("seek",onseek);
 | 
						|
      video.currentTime = value;
 | 
						|
      MistVideo.log("Seeking to "+MistUtil.format.time(value,{ms:true})+" ("+value+")");
 | 
						|
    }
 | 
						|
  });
 | 
						|
  //override duration
 | 
						|
  var lastduration = 1;
 | 
						|
  Object.defineProperty(this.api,"duration",{
 | 
						|
    get: function(){
 | 
						|
      return lastduration;
 | 
						|
    }
 | 
						|
  });
 | 
						|
  Object.defineProperty(this.api,"playbackRate",{
 | 
						|
    get: function(){
 | 
						|
      return video.playbackRate;
 | 
						|
    },
 | 
						|
    set: function(value){
 | 
						|
      var f = function(msg){
 | 
						|
        video.playbackRate = msg.data.play_rate;
 | 
						|
      };
 | 
						|
      player.ws.addListener("set_speed",f);
 | 
						|
      send({type: "set_speed", play_rate: (value == 1 ? "auto" : value)});
 | 
						|
    }
 | 
						|
  });
 | 
						|
  
 | 
						|
  //redirect properties
 | 
						|
  //using a function to make sure the "item" is in the correct scope
 | 
						|
  function reroute(item) {
 | 
						|
    Object.defineProperty(player.api,item,{
 | 
						|
      get: function(){ return video[item]; },
 | 
						|
      set: function(value){
 | 
						|
        return video[item] = value;
 | 
						|
      }
 | 
						|
    });
 | 
						|
  }
 | 
						|
  var list = [
 | 
						|
    "volume"
 | 
						|
    ,"buffered"
 | 
						|
    ,"muted"
 | 
						|
    ,"loop"
 | 
						|
    ,"paused",
 | 
						|
    ,"error"
 | 
						|
    ,"textTracks"
 | 
						|
    ,"webkitDroppedFrameCount"
 | 
						|
    ,"webkitDecodedFrameCount"
 | 
						|
  ];
 | 
						|
  for (var i in list) {
 | 
						|
    reroute(list[i]);
 | 
						|
  }
 | 
						|
  
 | 
						|
  //loop
 | 
						|
  MistUtil.event.addListener(video,"ended",function(){
 | 
						|
    if (player.api.loop) {
 | 
						|
      player.api.currentTime = 0;
 | 
						|
      player.sb._do(function(){
 | 
						|
        player.sb.remove(0,Infinity);
 | 
						|
      });
 | 
						|
    }
 | 
						|
  });
 | 
						|
  
 | 
						|
  var seeking = false;
 | 
						|
  MistUtil.event.addListener(video,"seeking",function(){
 | 
						|
    seeking = true;
 | 
						|
    var seeked = MistUtil.event.addListener(video,"seeked",function(){
 | 
						|
      seeking = false;
 | 
						|
      MistUtil.event.removeListener(seeked);
 | 
						|
    });
 | 
						|
  });
 | 
						|
  MistUtil.event.addListener(video,"waiting",function(){
 | 
						|
    //check if there is a gap in the buffers, and if so, jump it
 | 
						|
    if (seeking) { return; }
 | 
						|
    var buffern = player.findBuffer(video.currentTime);
 | 
						|
    if (buffern !== false) {
 | 
						|
      if ((buffern+1 < video.buffered.length) && (video.buffered.start(buffern+1) - video.currentTime < 10e3)) {
 | 
						|
        MistVideo.log("Skipped over buffer gap (from "+MistUtil.format.time(video.currentTime)+" to "+MistUtil.format.time(video.buffered.start(buffern+1))+")");
 | 
						|
        video.currentTime = video.buffered.start(buffern+1);
 | 
						|
      }
 | 
						|
    } 
 | 
						|
  });
 | 
						|
  MistUtil.event.addListener(video,"pause",function(){
 | 
						|
    if (player.sb && !player.sb.paused) {
 | 
						|
      MistVideo.log("The browser paused the vid - probably because it has no audio and the tab is no longer visible. Pausing download.");
 | 
						|
      send({type:"hold"});
 | 
						|
      player.sb.paused = true;
 | 
						|
      var p = MistUtil.event.addListener(video,"play",function(){
 | 
						|
        if (player.sb && player.sb.paused) {
 | 
						|
          send({type:"play"});
 | 
						|
        }
 | 
						|
        MistUtil.event.removeListener(p);
 | 
						|
      });
 | 
						|
    }
 | 
						|
  });
 | 
						|
 | 
						|
  if (player.debugging) {
 | 
						|
    MistUtil.event.addListener(video,"waiting",function(){
 | 
						|
      //check the buffer available
 | 
						|
      var buffers = [];
 | 
						|
      var contained = false;
 | 
						|
      for (var i = 0; i < video.buffered.length; i++) {
 | 
						|
        if ((video.currentTime >= video.buffered.start(i)) && (video.currentTime <= video.buffered.end(i))) {
 | 
						|
          contained = true;
 | 
						|
        }
 | 
						|
        buffers.push([
 | 
						|
          video.buffered.start(i),
 | 
						|
          video.buffered.end(i),
 | 
						|
        ]);
 | 
						|
      }
 | 
						|
      console.log("waiting","currentTime",video.currentTime,"buffers",buffers,contained ? "contained" : "outside of buffer","readystate",video.readyState,"networkstate",video.networkState);
 | 
						|
      if ((video.readyState >= 2) && (video.networkState >= 2)) {
 | 
						|
        console.error("Why am I waiting?!",video.currentTime);
 | 
						|
      }
 | 
						|
      
 | 
						|
    });
 | 
						|
  }
 | 
						|
 | 
						|
  this.ABR = {
 | 
						|
    size: null,
 | 
						|
    bitrate: null,
 | 
						|
    generateString: function(type,raw){
 | 
						|
      switch (type) {
 | 
						|
        case "size": {
 | 
						|
          return "~"+[raw.width,raw.height].join("x");
 | 
						|
        }
 | 
						|
        case "bitrate": {
 | 
						|
          return "<"+Math.round(raw)+"bps,minbps";
 | 
						|
        }
 | 
						|
        default: {
 | 
						|
          throw "Unknown ABR type";
 | 
						|
        }
 | 
						|
      }
 | 
						|
    },
 | 
						|
    request: function(type,value){
 | 
						|
      this[type] = value;
 | 
						|
 | 
						|
      var request = [];
 | 
						|
      if (this.bitrate !== null) {
 | 
						|
        request.push(this.generateString("bitrate",this.bitrate));
 | 
						|
      }
 | 
						|
      if (this.size !== null) {
 | 
						|
        request.push(this.generateString("size",this.size));
 | 
						|
      }
 | 
						|
      else {
 | 
						|
        request.push("maxbps");
 | 
						|
      }
 | 
						|
 | 
						|
      return player.api.setTracks({
 | 
						|
        video: request.join(",|")
 | 
						|
      });
 | 
						|
    }
 | 
						|
  }
 | 
						|
 | 
						|
  this.api.ABR_resize = function(size){
 | 
						|
    MistVideo.log("Requesting the video track with the resolution that best matches the player size");
 | 
						|
    player.ABR.request("size",size);
 | 
						|
  };
 | 
						|
  //ABR: monitor playback issues and switch to lower bitrate track if available
 | 
						|
  //NB: this ABR requests a lower bitrate if needed, but it can never go back up
 | 
						|
  this.monitor = {
 | 
						|
    bitCounter: [],
 | 
						|
    bitsSince: [],
 | 
						|
    currentBps: null,
 | 
						|
    nWaiting: 0,
 | 
						|
    nWaitingThreshold: 3,
 | 
						|
    listener: MistVideo.options.ABR_bitrate ? MistUtil.event.addListener(video,"waiting",function(){
 | 
						|
      player.monitor.nWaiting++;
 | 
						|
 | 
						|
      if (player.monitor.nWaiting >= player.monitor.nWaitingThreshold) {
 | 
						|
        player.monitor.nWaiting = 0;
 | 
						|
        player.monitor.action();
 | 
						|
      }
 | 
						|
    }) : null,
 | 
						|
    getBitRate: function(){
 | 
						|
      if (player.sb && !player.sb.paused) {
 | 
						|
 | 
						|
        this.bitCounter.push(0);
 | 
						|
        this.bitsSince.push(new Date().getTime());
 | 
						|
 | 
						|
        //calculate current bitrate
 | 
						|
        var bits, since;
 | 
						|
        if (this.bitCounter.length > 5) {
 | 
						|
          bits = player.monitor.bitCounter.shift();
 | 
						|
          since = this.bitsSince.shift();
 | 
						|
        }
 | 
						|
        else {
 | 
						|
          bits = player.monitor.bitCounter[0];
 | 
						|
          since = this.bitsSince[0];
 | 
						|
        }
 | 
						|
        var dt = new Date().getTime() - since;
 | 
						|
        this.currentBps = bits / (dt*1e-3);
 | 
						|
 | 
						|
        //console.log(MistUtil.format.bits(this.currentBps)+"its/s");
 | 
						|
      }
 | 
						|
      MistVideo.timers.start(function(){
 | 
						|
        player.monitor.getBitRate();
 | 
						|
      },500);
 | 
						|
    },
 | 
						|
    action: function(){
 | 
						|
      if (MistVideo.options.setTracks && MistVideo.options.setTracks.video) {
 | 
						|
        //a video track was selected by the user, do not change it
 | 
						|
        return;
 | 
						|
      }
 | 
						|
      MistVideo.log("ABR threshold triggered, requesting lower quality");
 | 
						|
      player.ABR.request("bitrate",this.currentBps);
 | 
						|
    }
 | 
						|
  };
 | 
						|
  this.monitor.getBitRate();
 | 
						|
};
 |