repo: janusweb
action: commit
revision: 
path_from: 
revision_from: 583a261a527041d6ce30b0a88ecfc778aa4107c7:
path_to: 
revision_to: 
git.thebackupbox.net
janusweb
git clone git://git.thebackupbox.net/janusweb
commit 583a261a527041d6ce30b0a88ecfc778aa4107c7
Author: James Baicoianu 
Date:   Tue Apr 26 14:07:05 2016 -0700

    Switched to using janusplayer

diff --git a/scripts/client.js b/scripts/client.js
index dce7ba997f3a25964ef303683c73bdd055f186b4..
index ..8e115dc31434e09608337f69be5c544e6ce3ae75 100644
--- a/scripts/client.js
+++ b/scripts/client.js
@@ -1,4 +1,4 @@
-elation.require(['engine.engine', 'engine.assets', 'engine.things.player', 'engine.things.light_ambient', 'engine.things.light_directional', 'engine.things.light_point', 'janusweb.janusweb', 'janusweb.chat'], function() {
+elation.require(['engine.engine', 'engine.assets', 'engine.things.light_ambient', 'engine.things.light_directional', 'engine.things.light_point', 'janusweb.janusweb', 'janusweb.chat', 'janusweb.janusplayer'], function() {
   elation.extend('janusweb.init', function(args) {
     if (!args) args = {};
     var proto = elation.utils.any(args.protocol, elation.config.get('dependencies.protocol'), document.location.protocol);
@@ -47,11 +47,11 @@ elation.require(['engine.engine', 'engine.assets', 'engine.things.player', 'engi
         things: {
           player: {
             name: 'player',
-            type: 'player',
+            type: 'janusplayer',
             properties: {
               position: [0,0,0],
               mass: 10,
-              movespeed: 100,
+              movespeed: 5000,
               collidable: false
             }
           },
diff --git a/scripts/external/JanusClientConnection.js b/scripts/external/JanusClientConnection.js
new file mode 100644
index 0000000000000000000000000000000000000000..8a04c427c31d4cae8802245ef806d8bdcb0788af
--- /dev/null
+++ b/scripts/external/JanusClientConnection.js
@@ -0,0 +1,365 @@
+// md5 hash functions
+
+/*
+ * http://www.myersdaily.org/joseph/javascript/md5-text.html
+ */
+(function (global) {
+
+  var md5cycle = function (x, k) {
+    var a = x[0],
+      b = x[1],
+      c = x[2],
+      d = x[3];
+
+    a = ff(a, b, c, d, k[0], 7, -680876936);
+    d = ff(d, a, b, c, k[1], 12, -389564586);
+    c = ff(c, d, a, b, k[2], 17, 606105819);
+    b = ff(b, c, d, a, k[3], 22, -1044525330);
+    a = ff(a, b, c, d, k[4], 7, -176418897);
+    d = ff(d, a, b, c, k[5], 12, 1200080426);
+    c = ff(c, d, a, b, k[6], 17, -1473231341);
+    b = ff(b, c, d, a, k[7], 22, -45705983);
+    a = ff(a, b, c, d, k[8], 7, 1770035416);
+    d = ff(d, a, b, c, k[9], 12, -1958414417);
+    c = ff(c, d, a, b, k[10], 17, -42063);
+    b = ff(b, c, d, a, k[11], 22, -1990404162);
+    a = ff(a, b, c, d, k[12], 7, 1804603682);
+    d = ff(d, a, b, c, k[13], 12, -40341101);
+    c = ff(c, d, a, b, k[14], 17, -1502002290);
+    b = ff(b, c, d, a, k[15], 22, 1236535329);
+
+    a = gg(a, b, c, d, k[1], 5, -165796510);
+    d = gg(d, a, b, c, k[6], 9, -1069501632);
+    c = gg(c, d, a, b, k[11], 14, 643717713);
+    b = gg(b, c, d, a, k[0], 20, -373897302);
+    a = gg(a, b, c, d, k[5], 5, -701558691);
+    d = gg(d, a, b, c, k[10], 9, 38016083);
+    c = gg(c, d, a, b, k[15], 14, -660478335);
+    b = gg(b, c, d, a, k[4], 20, -405537848);
+    a = gg(a, b, c, d, k[9], 5, 568446438);
+    d = gg(d, a, b, c, k[14], 9, -1019803690);
+    c = gg(c, d, a, b, k[3], 14, -187363961);
+    b = gg(b, c, d, a, k[8], 20, 1163531501);
+    a = gg(a, b, c, d, k[13], 5, -1444681467);
+    d = gg(d, a, b, c, k[2], 9, -51403784);
+    c = gg(c, d, a, b, k[7], 14, 1735328473);
+    b = gg(b, c, d, a, k[12], 20, -1926607734);
+
+    a = hh(a, b, c, d, k[5], 4, -378558);
+    d = hh(d, a, b, c, k[8], 11, -2022574463);
+    c = hh(c, d, a, b, k[11], 16, 1839030562);
+    b = hh(b, c, d, a, k[14], 23, -35309556);
+    a = hh(a, b, c, d, k[1], 4, -1530992060);
+    d = hh(d, a, b, c, k[4], 11, 1272893353);
+    c = hh(c, d, a, b, k[7], 16, -155497632);
+    b = hh(b, c, d, a, k[10], 23, -1094730640);
+    a = hh(a, b, c, d, k[13], 4, 681279174);
+    d = hh(d, a, b, c, k[0], 11, -358537222);
+    c = hh(c, d, a, b, k[3], 16, -722521979);
+    b = hh(b, c, d, a, k[6], 23, 76029189);
+    a = hh(a, b, c, d, k[9], 4, -640364487);
+    d = hh(d, a, b, c, k[12], 11, -421815835);
+    c = hh(c, d, a, b, k[15], 16, 530742520);
+    b = hh(b, c, d, a, k[2], 23, -995338651);
+
+    a = ii(a, b, c, d, k[0], 6, -198630844);
+    d = ii(d, a, b, c, k[7], 10, 1126891415);
+    c = ii(c, d, a, b, k[14], 15, -1416354905);
+    b = ii(b, c, d, a, k[5], 21, -57434055);
+    a = ii(a, b, c, d, k[12], 6, 1700485571);
+    d = ii(d, a, b, c, k[3], 10, -1894986606);
+    c = ii(c, d, a, b, k[10], 15, -1051523);
+    b = ii(b, c, d, a, k[1], 21, -2054922799);
+    a = ii(a, b, c, d, k[8], 6, 1873313359);
+    d = ii(d, a, b, c, k[15], 10, -30611744);
+    c = ii(c, d, a, b, k[6], 15, -1560198380);
+    b = ii(b, c, d, a, k[13], 21, 1309151649);
+    a = ii(a, b, c, d, k[4], 6, -145523070);
+    d = ii(d, a, b, c, k[11], 10, -1120210379);
+    c = ii(c, d, a, b, k[2], 15, 718787259);
+    b = ii(b, c, d, a, k[9], 21, -343485551);
+
+    x[0] = add32(a, x[0]);
+    x[1] = add32(b, x[1]);
+    x[2] = add32(c, x[2]);
+    x[3] = add32(d, x[3]);
+
+  }
+
+  var cmn = function (q, a, b, x, s, t) {
+    a = add32(add32(a, q), add32(x, t));
+    return add32((a << s) | (a >>> (32 - s)), b);
+  }
+
+  var ff = function (a, b, c, d, x, s, t) {
+    return cmn((b & c) | ((~b) & d), a, b, x, s, t);
+  }
+
+  var gg = function (a, b, c, d, x, s, t) {
+    return cmn((b & d) | (c & (~d)), a, b, x, s, t);
+  }
+
+  var hh = function (a, b, c, d, x, s, t) {
+    return cmn(b ^ c ^ d, a, b, x, s, t);
+  }
+
+  var ii = function (a, b, c, d, x, s, t) {
+    return cmn(c ^ (b | (~d)), a, b, x, s, t);
+  }
+
+  var md51 = function (s) {
+    var txt = '',
+      n = s.length,
+      state = [1732584193, -271733879, -1732584194, 271733878],
+      i;
+    for (i = 64; i <= s.length; i += 64) {
+      md5cycle(state, md5blk(s.substring(i - 64, i)));
+    }
+    s = s.substring(i - 64);
+    var tail = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
+    for (i = 0; i < s.length; i++)
+      tail[i >> 2] |= s.charCodeAt(i) << ((i % 4) << 3);
+    tail[i >> 2] |= 0x80 << ((i % 4) << 3);
+    if (i > 55) {
+      md5cycle(state, tail);
+      for (i = 0; i < 16; i++) tail[i] = 0;
+    }
+    tail[14] = n * 8;
+    md5cycle(state, tail);
+    return state;
+  }
+
+  /* there needs to be support for Unicode here,
+   * unless we pretend that we can redefine the MD-5
+   * algorithm for multi-byte characters (perhaps
+   * by adding every four 16-bit characters and
+   * shortening the sum to 32 bits). Otherwise
+   * I suggest performing MD-5 as if every character
+   * was two bytes--e.g., 0040 0025 = @%--but then
+   * how will an ordinary MD-5 sum be matched?
+   * There is no way to standardize text to something
+   * like UTF-8 before transformation; speed cost is
+   * utterly prohibitive. The JavaScript standard
+   * itself needs to look at this: it should start
+   * providing access to strings as preformed UTF-8
+   * 8-bit unsigned value arrays.
+   */
+  var md5blk = function (s) { /* I figured global was faster.   */
+    var md5blks = [],
+      i; /* Andy King said do it this way. */
+    for (i = 0; i < 64; i += 4) {
+      md5blks[i >> 2] = s.charCodeAt(i) + (s.charCodeAt(i + 1) << 8) + (s.charCodeAt(i + 2) << 16) + (s.charCodeAt(i + 3) << 24);
+    }
+    return md5blks;
+  }
+
+  var hex_chr = '0123456789abcdef'.split('');
+
+  var rhex = function (n) {
+    var s = '',
+      j = 0;
+    for (; j < 4; j++)
+      s += hex_chr[(n >> (j * 8 + 4)) & 0x0F] + hex_chr[(n >> (j * 8)) & 0x0F];
+    return s;
+  }
+
+  var hex = function (x) {
+    for (var i = 0; i < x.length; i++)
+      x[i] = rhex(x[i]);
+    return x.join('');
+  }
+
+  var md5 = global.md5 = function (s) {
+    return hex(md51(s));
+  }
+
+  /* this function is much faster,
+  so if possible we use it. Some IEs
+  are the only ones I know of that
+  need the idiotic second function,
+  generated by an if clause.  */
+
+  var add32 = function (a, b) {
+    return (a + b) & 0xFFFFFFFF;
+  }
+
+  if (md5('hello') != '5d41402abc4b2a76b9719d911017c592') {
+    var add32 = function (x, y) {
+      var lsw = (x & 0xFFFF) + (y & 0xFFFF),
+        msw = (x >> 16) + (y >> 16) + (lsw >> 16);
+      return (msw << 16) | (lsw & 0xFFFF);
+    }
+  }
+
+})(window);
+
+// event dispatcher by mrdoob https://github.com/mrdoob/eventdispatcher.js
+var EventDispatcher = function () {}
+
+EventDispatcher.prototype = {
+
+  constructor: EventDispatcher,
+
+  apply: function ( object ) {
+
+    object.addEventListener = EventDispatcher.prototype.addEventListener;
+    object.hasEventListener = EventDispatcher.prototype.hasEventListener;
+    object.removeEventListener = EventDispatcher.prototype.removeEventListener;
+    object.dispatchEvent = EventDispatcher.prototype.dispatchEvent;
+
+  },
+
+  addEventListener: function ( type, listener ) {
+
+    if ( this._listeners === undefined ) this._listeners = {};
+
+    var listeners = this._listeners;
+
+    if ( listeners[ type ] === undefined ) {
+
+      listeners[ type ] = [];
+
+    }
+
+    if ( listeners[ type ].indexOf( listener ) === - 1 ) {
+
+      listeners[ type ].push( listener );
+
+    }
+
+  },
+
+  hasEventListener: function ( type, listener ) {
+
+    if ( this._listeners === undefined ) return false;
+
+    var listeners = this._listeners;
+
+    if ( listeners[ type ] !== undefined && listeners[ type ].indexOf( listener ) !== - 1 ) {
+
+      return true;
+
+    }
+
+    return false;
+
+  },
+
+  removeEventListener: function ( type, listener ) {
+
+    if ( this._listeners === undefined ) return;
+
+    var listeners = this._listeners;
+    var listenerArray = listeners[ type ];
+
+    if ( listenerArray !== undefined ) {
+
+      var index = listenerArray.indexOf( listener );
+
+      if ( index !== - 1 ) {
+
+        listenerArray.splice( index, 1 );
+
+      }
+
+    }
+
+  },
+
+  dispatchEvent: function ( event ) {
+      
+    if ( this._listeners === undefined ) return;
+
+    var listeners = this._listeners;
+    var listenerArray = listeners[ event.type ];
+
+    if ( listenerArray !== undefined ) {
+
+      event.target = this;
+
+      var array = [];
+      var length = listenerArray.length;
+
+      for ( var i = 0; i < length; i ++ ) {
+
+        array[ i ] = listenerArray[ i ];
+
+      }
+
+      for ( var i = 0; i < length; i ++ ) {
+
+        array[ i ].call( this, event );
+
+      }
+
+    }
+
+  }
+
+};
+// jcc
+var JanusClientConnection = function(opts)
+{
+  this._userId = opts.userId;
+  this._roomUrl = opts.roomUrl;
+  this._version = opts.version;
+  this._websocket = new WebSocket(opts.host, 'binary');
+  this._websocket.onopen = function() {
+    this.sendLogon();
+    this.subscribe(this._roomUrl);
+    this.dispatchEvent({type: 'connect'});
+  }.bind(this)
+  this._websocket.onmessage = this.onMessage.bind(this)  
+};
+
+EventDispatcher.prototype.apply(JanusClientConnection.prototype);
+
+JanusClientConnection.prototype.sendLogon = function() {
+  var msgData = {
+    'method': 'logon',
+    'data': {
+      'userId': this._userId,
+      'version': this._version,
+      'roomId': md5(this._roomUrl)
+    }
+  }
+  this.send(msgData);
+};
+
+JanusClientConnection.prototype.send = function(msg) {
+  this._websocket.send(JSON.stringify(msg) + '\r\n');
+};
+
+JanusClientConnection.prototype.onMessage = function(msg) {
+  this.dispatchEvent({type: 'message', data: JSON.parse(msg.data)});
+};
+
+JanusClientConnection.prototype.subscribe = function(roomId) {
+  console.log('subscribing to ', roomId);
+  this.send({
+    'method': 'subscribe',
+    'data': {
+      'roomId': md5(roomId)
+    }
+  });
+};
+
+JanusClientConnection.prototype.unsubscribe = function(url) {
+    console.log('unsubscribing from', url);
+    this.send({
+      'method': 'unsubscribe',
+      'data': {
+        'roomId': md5(url)
+      }
+    });
+};
+
+JanusClientConnection.prototype.enter_room = function(url) {
+    this.send({
+      'method': 'enter_room',
+      'data': {
+        'roomId': md5(url)
+      }
+    });
+};
+
diff --git a/scripts/external/JanusVOIP.js b/scripts/external/JanusVOIP.js
new file mode 100644
index 0000000000000000000000000000000000000000..64911d526fae179f8ef409a3700c88d98c9ed796
--- /dev/null
+++ b/scripts/external/JanusVOIP.js
@@ -0,0 +1,430 @@
+// event dispatcher by mrdoob https://github.com/mrdoob/eventdispatcher.js
+var EventDispatcher = function () {}
+
+EventDispatcher.prototype = {
+
+  constructor: EventDispatcher,
+
+  apply: function ( object ) {
+
+    object.addEventListener = EventDispatcher.prototype.addEventListener;
+    object.hasEventListener = EventDispatcher.prototype.hasEventListener;
+    object.removeEventListener = EventDispatcher.prototype.removeEventListener;
+    object.dispatchEvent = EventDispatcher.prototype.dispatchEvent;
+
+  },
+
+  addEventListener: function ( type, listener ) {
+
+    if ( this._listeners === undefined ) this._listeners = {};
+
+    var listeners = this._listeners;
+
+    if ( listeners[ type ] === undefined ) {
+
+      listeners[ type ] = [];
+
+    }
+
+    if ( listeners[ type ].indexOf( listener ) === - 1 ) {
+
+      listeners[ type ].push( listener );
+
+    }
+
+  },
+
+  hasEventListener: function ( type, listener ) {
+
+    if ( this._listeners === undefined ) return false;
+
+    var listeners = this._listeners;
+
+    if ( listeners[ type ] !== undefined && listeners[ type ].indexOf( listener ) !== - 1 ) {
+
+      return true;
+
+    }
+
+    return false;
+
+  },
+
+  removeEventListener: function ( type, listener ) {
+
+    if ( this._listeners === undefined ) return;
+
+    var listeners = this._listeners;
+    var listenerArray = listeners[ type ];
+
+    if ( listenerArray !== undefined ) {
+
+      var index = listenerArray.indexOf( listener );
+
+      if ( index !== - 1 ) {
+
+        listenerArray.splice( index, 1 );
+
+      }
+
+    }
+
+  },
+
+  dispatchEvent: function ( event ) {
+      
+    if ( this._listeners === undefined ) return;
+
+    var listeners = this._listeners;
+    var listenerArray = listeners[ event.type ];
+
+    if ( listenerArray !== undefined ) {
+
+      event.target = this;
+
+      var array = [];
+      var length = listenerArray.length;
+
+      for ( var i = 0; i < length; i ++ ) {
+
+        array[ i ] = listenerArray[ i ];
+
+      }
+
+      for ( var i = 0; i < length; i ++ ) {
+
+        array[ i ].call( this, event );
+
+      }
+
+    }
+
+  }
+
+};
+JanusVOIPRecorder = function(args) {
+  if (!args) var args = {};
+  this.sampleRate = args.sampleRate || 11000;
+}
+EventDispatcher.prototype.apply(JanusVOIPRecorder.prototype);
+
+JanusVOIPRecorder.prototype.start = function() {
+  if (!this.context) {
+    this.createContext();
+  }
+  var getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
+  if (!navigator.getUserMedia) {
+    navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia || navigator.msGetUserMedia;
+  }
+
+  navigator.getUserMedia({audio: true}, this.attachContext.bind(this), this.handleFailure.bind(this));
+}
+JanusVOIPRecorder.prototype.stop = function() {
+  if (this.voipsource) {
+    var tracks = this.voipsource.getAudioTracks();
+    tracks.forEach(function(track) { 
+      track.stop(); 
+    });
+    this.voipsource = false;
+    this.dispatchEvent({type: 'voip_stop'});
+  }
+}
+JanusVOIPRecorder.prototype.createContext = function() {
+  var audioContext = window.AudioContext || window.webkitAudioContext;
+  if (!this.context) {
+    this.context = new audioContext();
+  }
+  var context = this.context;
+
+  // retrieve the current sample rate to be used for WAV packaging
+  var sampleRate = context.sampleRate;
+
+  // creates a gain node
+  this.volume = context.createGain();
+
+  /* From the spec: This value controls how frequently the audioprocess event is 
+  dispatched and how many sample-frames need to be processed each call. 
+  Lower values for buffer size will result in a lower (better) latency. 
+  Higher values will be necessary to avoid audio breakup and glitches */
+  var bufferSize = 4096;
+  this.recorder = context.createScriptProcessor(bufferSize, 1, 1);
+  var recorder = this.recorder;
+  // we connect the recorder
+  this.volume.connect (recorder);
+  recorder.connect (context.destination); 
+
+  this.recorder = recorder;
+  this.dispatchEvent({type: 'voip_init'});
+}
+JanusVOIPRecorder.prototype.attachContext = function(audiostream) {
+  this.voipsource = audiostream;
+
+  // creates an audio node from the microphone incoming stream
+  var audioInput = this.context.createMediaStreamSource(audiostream);
+
+  // connect the stream to the gain node
+  audioInput.connect(this.volume);
+
+  var recorder = this.recorder,
+      context = this.context;
+  recorder.onaudioprocess = this.processAudio.bind(this, context);
+  var tracks = this.voipsource.getTracks();
+  tracks.forEach(function(track) { 
+    track.addEventListener('ended', function() { recorder.onaudioprocess = null; });
+  }.bind(this))
+
+  this.dispatchEvent({type: 'voip_start', element: this});
+}
+JanusVOIPRecorder.prototype.processAudio = function(context, e){
+  var left = e.inputBuffer.getChannelData(0);
+  var resampler = new Resampler(context.sampleRate, this.sampleRate, 1, left);
+  var what = resampler.resampler(left.length);
+  var newbuf = new Uint16Array(resampler.outputBuffer.length);
+  for (var i = 0; i < newbuf.length; i++) {
+    newbuf[i] = Math.floor(resampler.outputBuffer[i] * 32767);
+  }
+  this.dispatchEvent({type: 'voip_data', element: this, data: newbuf});
+}
+JanusVOIPRecorder.prototype.handleFailure = function(err) {
+  this.dispatchEvent({type: 'voip_error', element: this, data: err});
+}
+
+
+JanusVOIPPlayer = function(args) {
+  if (!args) var args = {};
+  this.context = args.context;
+  this.sampleRate = args.sampleRate || 11000;
+  this.bufferTime = args.bufferTime || 1.0;
+  this.audioScale = args.audioScale || 32768;
+
+  this.bufferLength = this.sampleRate * this.bufferTime;
+}
+EventDispatcher.prototype.apply(JanusVOIPPlayer.prototype);
+
+JanusVOIPPlayer.prototype.start = function() {
+  this.rawbuffer = context.createBuffer(2, this.bufferLength, this.sampleRate);
+  this.readoffset = 0;
+  this.writeoffset = 0;
+
+  this.dispatchEvent({type: 'voip_player_init', element: this});
+}
+JanusVOIPPlayer.prototype.speak = function(noise) {
+  var binary_string = window.atob(noise);
+  var len = binary_string.length;
+
+  // Even though this is a mono source, we treat it as stereo so it can be used as poitional audio
+  var bufferLeft = this.rawbuffer.getChannelData(0);
+  var bufferRight = this.rawbuffer.getChannelData(1);
+
+  // Decode the binary string into an unsigned char array
+  var audiodata = new Uint8Array(len);
+  for (var i = 0; i < len; i++) {
+    audiodata[i] = binary_string.charCodeAt(i);
+  }
+
+  // Create a new view into the decoded data which gives us the data as int16_t instead of unsigned chars
+  var audiodata16 = new Int16Array(audiodata.buffer);
+
+  var startoffset = this.writeoffset;
+  // Write the scaled data into our buffer, treating it as a looping ring buffer
+  for (var i = 0; i < audiodata16.length; i++) {
+    var idx = (startoffset + i) % this.bufferLength;
+    bufferLeft[idx] = bufferRight[idx] = (audiodata16[i] / this.audioScale);
+  }
+  this.writeoffset += audiodata16.length;
+
+  this.dispatchEvent({type: 'voip_player_data', element: this, data: {buffer: bufferLeft, start: startoffset, end: this.writeoffset}});
+  if (this.talktimer) { 
+    clearTimeout(this.talktimer);
+  }
+  this.talktimer = setTimeout(elation.bind(this, this.stop), this.bufferLength * 500);
+}
+JanusVOIPPlayer.prototype.stop = function() {
+  this.dispatchEvent({type: 'voip_player_stop', element: this});
+}
+JanusVOIPPlayer.prototype.silence = function() {
+  var bufferLeft = this.rawbuffer.getChannelData(0);
+  var bufferRight = this.rawbuffer.getChannelData(1);
+  for (var i = 0; i < bufferLeft.length; i++) {
+    bufferLeft[i] = bufferRight[i] = 0;
+  }
+  this.writeoffset = 0;
+}
+
+
+
+
+
+
+//JavaScript Audio Resampler
+//Copyright (C) 2011-2015 Grant Galitz
+//Released to Public Domain
+function Resampler(fromSampleRate, toSampleRate, channels, inputBuffer) {
+    //Input Sample Rate:
+    this.fromSampleRate = +fromSampleRate;
+    //Output Sample Rate:
+    this.toSampleRate = +toSampleRate;
+    //Number of channels:
+    this.channels = channels | 0;
+    //Type checking the input buffer:
+    if (typeof inputBuffer != "object") {
+        throw(new Error("inputBuffer is not an object."));
+    }
+    if (!(inputBuffer instanceof Array) && !(inputBuffer instanceof Float32Array) && !(inputBuffer instanceof Float64Array)) {
+        throw(new Error("inputBuffer is not an array or a float32 or a float64 array."));
+    }
+    this.inputBuffer = inputBuffer;
+    //Initialize the resampler:
+    this.initialize();
+}
+Resampler.prototype.initialize = function () {
+	//Perform some checks:
+	if (this.fromSampleRate > 0 && this.toSampleRate > 0 && this.channels > 0) {
+		if (this.fromSampleRate == this.toSampleRate) {
+			//Setup a resampler bypass:
+			this.resampler = this.bypassResampler;		//Resampler just returns what was passed through.
+            this.ratioWeight = 1;
+            this.outputBuffer = this.inputBuffer;
+		}
+		else {
+            this.ratioWeight = this.fromSampleRate / this.toSampleRate;
+			if (this.fromSampleRate < this.toSampleRate) {
+				/*
+					Use generic linear interpolation if upsampling,
+					as linear interpolation produces a gradient that we want
+					and works fine with two input sample points per output in this case.
+				*/
+				this.compileLinearInterpolationFunction();
+				this.lastWeight = 1;
+			}
+			else {
+				/*
+					Custom resampler I wrote that doesn't skip samples
+					like standard linear interpolation in high downsampling.
+					This is more accurate than linear interpolation on downsampling.
+				*/
+				this.compileMultiTapFunction();
+				this.tailExists = false;
+				this.lastWeight = 0;
+			}
+			this.initializeBuffers();
+		}
+	}
+	else {
+		throw(new Error("Invalid settings specified for the resampler."));
+	}
+}
+Resampler.prototype.compileLinearInterpolationFunction = function () {
+	var toCompile = "var outputOffset = 0;\
+    if (bufferLength > 0) {\
+        var buffer = this.inputBuffer;\
+        var weight = this.lastWeight;\
+        var firstWeight = 0;\
+        var secondWeight = 0;\
+        var sourceOffset = 0;\
+        var outputOffset = 0;\
+        var outputBuffer = this.outputBuffer;\
+        for (; weight < 1; weight += " + this.ratioWeight + ") {\
+            secondWeight = weight % 1;\
+            firstWeight = 1 - secondWeight;";
+            for (var channel = 0; channel < this.channels; ++channel) {
+                toCompile += "outputBuffer[outputOffset++] = (this.lastOutput[" + channel + "] * firstWeight) + (buffer[" + channel + "] * secondWeight);";
+            }
+        toCompile += "}\
+        weight -= 1;\
+        for (bufferLength -= " + this.channels + ", sourceOffset = Math.floor(weight) * " + this.channels + "; sourceOffset < bufferLength;) {\
+            secondWeight = weight % 1;\
+            firstWeight = 1 - secondWeight;";
+            for (var channel = 0; channel < this.channels; ++channel) {
+                toCompile += "outputBuffer[outputOffset++] = (buffer[sourceOffset" + ((channel > 0) ? (" + " + channel) : "") + "] * firstWeight) + (buffer[sourceOffset + " + (this.channels + channel) + "] * secondWeight);";
+            }
+            toCompile += "weight += " + this.ratioWeight + ";\
+            sourceOffset = Math.floor(weight) * " + this.channels + ";\
+        }";
+        for (var channel = 0; channel < this.channels; ++channel) {
+            toCompile += "this.lastOutput[" + channel + "] = buffer[sourceOffset++];";
+        }
+        toCompile += "this.lastWeight = weight % 1;\
+    }\
+    return outputOffset;";
+	this.resampler = Function("bufferLength", toCompile);
+}
+Resampler.prototype.compileMultiTapFunction = function () {
+	var toCompile = "var outputOffset = 0;\
+    if (bufferLength > 0) {\
+        var buffer = this.inputBuffer;\
+        var weight = 0;";
+        for (var channel = 0; channel < this.channels; ++channel) {
+            toCompile += "var output" + channel + " = 0;"
+        }
+        toCompile += "var actualPosition = 0;\
+        var amountToNext = 0;\
+        var alreadyProcessedTail = !this.tailExists;\
+        this.tailExists = false;\
+        var outputBuffer = this.outputBuffer;\
+        var currentPosition = 0;\
+        do {\
+            if (alreadyProcessedTail) {\
+                weight = " + this.ratioWeight + ";";
+                for (channel = 0; channel < this.channels; ++channel) {
+                    toCompile += "output" + channel + " = 0;"
+                }
+            toCompile += "}\
+            else {\
+                weight = this.lastWeight;";
+                for (channel = 0; channel < this.channels; ++channel) {
+                    toCompile += "output" + channel + " = this.lastOutput[" + channel + "];"
+                }
+                toCompile += "alreadyProcessedTail = true;\
+            }\
+            while (weight > 0 && actualPosition < bufferLength) {\
+                amountToNext = 1 + actualPosition - currentPosition;\
+                if (weight >= amountToNext) {";
+                    for (channel = 0; channel < this.channels; ++channel) {
+                        toCompile += "output" + channel + " += buffer[actualPosition++] * amountToNext;"
+                    }
+                    toCompile += "currentPosition = actualPosition;\
+                    weight -= amountToNext;\
+                }\
+                else {";
+                    for (channel = 0; channel < this.channels; ++channel) {
+                        toCompile += "output" + channel + " += buffer[actualPosition" + ((channel > 0) ? (" + " + channel) : "") + "] * weight;"
+                    }
+                    toCompile += "currentPosition += weight;\
+                    weight = 0;\
+                    break;\
+                }\
+            }\
+            if (weight <= 0) {";
+                for (channel = 0; channel < this.channels; ++channel) {
+                    toCompile += "outputBuffer[outputOffset++] = output" + channel + " / " + this.ratioWeight + ";"
+                }
+            toCompile += "}\
+            else {\
+                this.lastWeight = weight;";
+                for (channel = 0; channel < this.channels; ++channel) {
+                    toCompile += "this.lastOutput[" + channel + "] = output" + channel + ";"
+                }
+                toCompile += "this.tailExists = true;\
+                break;\
+            }\
+        } while (actualPosition < bufferLength);\
+    }\
+    return outputOffset;";
+	this.resampler = Function("bufferLength", toCompile);
+}
+Resampler.prototype.bypassResampler = function (upTo) {
+    return upTo;
+}
+Resampler.prototype.initializeBuffers = function () {
+	//Initialize the internal buffer:
+    var outputBufferSize = (Math.ceil(this.inputBuffer.length * this.toSampleRate / this.fromSampleRate / this.channels * 1.000000476837158203125) * this.channels) + this.channels;
+	try {
+		this.outputBuffer = new Float32Array(outputBufferSize);
+		this.lastOutput = new Float32Array(this.channels);
+	}
+	catch (error) {
+		this.outputBuffer = [];
+		this.lastOutput = [];
+	}
+}
diff --git a/scripts/janusplayer.js b/scripts/janusplayer.js
new file mode 100644
index 0000000000000000000000000000000000000000..247d428448c8c3c747da96adfc9e6e186b83b840
--- /dev/null
+++ b/scripts/janusplayer.js
@@ -0,0 +1,65 @@
+elation.require(['engine.things.player', 'janusweb.JanusVOIP', 'ui.button'], function() {
+  elation.requireCSS('janusweb.janusplayer');
+
+  elation.component.add('engine.things.janusplayer', function() {
+    this.postinit = function() {
+      elation.engine.things.janusplayer.extendclass.postinit.call(this);
+      this.controlstate2 = this.engine.systems.controls.addContext('janusplayer', {
+        'voip_active': ['keyboard_v,keyboard_shift_v', elation.bind(this, this.activateVOIP)],
+        'browse_back': ['gamepad_0_button_4', elation.bind(this, this.browseBack)],
+        'browse_forward': ['gamepad_0_button_5', elation.bind(this, this.browseForward)],
+      });
+      this.voip = new JanusVOIPRecorder({audioScale: 1024});
+      this.voipqueue = [];
+      this.voipbutton = elation.ui.button({append: document.body, classname: 'janusweb_voip', label: 'VOIP'});
+      elation.events.add(this.voipbutton, 'mousedown,touchstart', elation.bind(this.voip, this.voip.start));
+      elation.events.add(this.voipbutton, 'mouseup,touchend', elation.bind(this.voip, this.voip.stop));
+      elation.events.add(this.voip, 'voip_start', elation.bind(this, this.handleVOIPStart));
+      elation.events.add(this.voip, 'voip_stop', elation.bind(this, this.handleVOIPStop));
+      elation.events.add(this.voip, 'voip_data', elation.bind(this, this.handleVOIPData));
+      elation.events.add(this.voip, 'voip_error', elation.bind(this, this.handleVOIPError));
+    }
+    this.enable = function() {
+      elation.engine.things.janusplayer.extendclass.enable.call(this);
+      this.engine.systems.controls.activateContext('janusplayer');
+    }
+    this.disable = function() {
+      this.engine.systems.controls.deactivateContext('janusplayer');
+      elation.engine.things.janusplayer.extendclass.disable.call(this);
+    }
+    this.activateVOIP = function(ev) {
+      var on = (ev.value == 1);
+      if (on) {
+        this.voip.start();
+      } else {
+        this.voip.stop();
+      }
+    }
+    this.handleVOIPStart = function() {
+      this.voipbutton.addclass('state_recording');
+      elation.events.fire('janusvr_voip_start');
+    }
+    this.handleVOIPStop = function() {
+      elation.events.fire('janusvr_voip_stop');
+      this.voipbutton.removeclass('state_recording');
+    }
+    this.handleVOIPData = function(ev) {
+      this.voipqueue.push(ev.data);
+    }
+    this.handleVOIPError = function(ev) {
+      this.voipbutton.addclass('state_error');
+      this.voipbutton.setTitle(ev.data.name + ': ' + ev.data.message);
+      elation.events.fire('janusvr_voip_error');
+    }
+    this.browseBack = function(ev) {
+      if (ev.value == 1) {
+        history.go(-1);
+      }
+    }
+    this.browseForward = function(ev) {
+      if (ev.value == 1) {
+        history.go(1);
+      }
+    }
+  }, elation.engine.things.player);
+});

-----END OF PAGE-----