import {
    RTCPeerConnection,
    RTCMediaStream,
    RTCIceCandidate,
    RTCSessionDescription,
    RTCView,
    MediaStreamTrack,
    getUserMedia,
  } from 'react-native-webrtc';

  import {
    Alert
  } from 'react-native';

  /*
      The MIT License (MIT)
  
      Copyright (c) 2016 Meetecho
  
      Permission is hereby granted, free of charge, to any person obtaining
      a copy of this software and associated documentation files (the "Software"),
      to deal in the Software without restriction, including without limitation
      the rights to use, copy, modify, merge, publish, distribute, sublicense,
      and/or sell copies of the Software, and to permit persons to whom the
      Software is furnished to do so, subject to the following conditions:
  
      The above copyright notice and this permission notice shall be included
      in all copies or substantial portions of the Software.
  
      THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
      OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
      FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
      THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR
      OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
      ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
      OTHER DEALINGS IN THE SOFTWARE.
   */
  
  // List of sessions
  Janus.sessions = {};
  
  // Screensharing Chrome Extension ID
  Janus.extensionId = "hapfgfdkleiggjjpfpenajgdnfckjpaj";
  // Janus.isExtensionEnabled = function() {
  //     if(window.navigator.userAgent.match('Chrome')) {
  //         var chromever = parseInt(window.navigator.userAgent.match(/Chrome\/(.*) /)[1], 10);
  //         var maxver = 33;
  //         if(window.navigator.userAgent.match('Linux'))
  //             maxver = 35;    // "known" crash in chrome 34 and 35 on linux
  //         if(chromever >= 26 && chromever <= maxver) {
  //             // Older versions of Chrome don't support this extension-based approach, so lie
  //             return true;
  //         }
  //         return (document.getElementById('janus-extension-installed') !== null);
  //     } else {
  //         // Firefox of others, no need for the extension (but this doesn't mean it will work)
  //         return true;
  //     }
  // };
  
  Janus.noop = function() {};
  
  // Initialization
  Janus.init = function(options) {
      options = options || {};
      options.callback = (typeof options.callback == "function") ? options.callback : Janus.noop;
      if(Janus.initDone === true) {
          // Already initialized
          options.callback();
      } else {
          if(typeof console == "undefined" || typeof console.log == "undefined")
              console = { log: function() {} };
          // Console logging (all debugging disabled by default)
          Janus.trace = Janus.noop;
          Janus.debug = Janus.noop;
          Janus.log = Janus.noop;
          Janus.warn = Janus.noop;
          Janus.error = Janus.noop;
          if(options.debug === true || options.debug === "all") {
              // Enable all debugging levels
              Janus.trace = console.trace.bind(console);
              Janus.debug = console.debug.bind(console);
              Janus.log = console.log.bind(console);
              Janus.warn = console.warn.bind(console);
              Janus.error = console.error.bind(console);
          } else if(Array.isArray(options.debug)) {
              for(var i in options.debug) {
                  var d = options.debug[i];
                  switch(d) {
                      case "trace":
                          Janus.trace = console.trace.bind(console);
                          break;
                      case "debug":
                          Janus.debug = console.debug.bind(console);
                          break;
                      case "log":
                          Janus.log = console.log.bind(console);
                          break;
                      case "warn":
                          Janus.warn = console.warn.bind(console);
                          break;
                      case "error":
                          Janus.error = console.error.bind(console);
                          break;
                      default:
                          console.error("Unknown debugging option '" + d + "' (supported: 'trace', 'debug', 'log', warn', 'error')");
                          break;
                  }
              }
          }
          Janus.log("Initializing library");
          // Helper method to enumerate devices
          Janus.listDevices = function(callback) {
              callback = (typeof callback == "function") ? callback : Janus.noop;
              if(navigator.mediaDevices) {
                  getUserMedia({ audio: true, video: true }, function(stream) {
                      navigator.mediaDevices.enumerateDevices().then(function(devices) {
                          Janus.debug(devices);
                          callback(devices);
                          // Get rid of the now useless stream
                          try {
                              stream.stop();
                          } catch(e) {}
                          try {
                              var tracks = stream.getTracks();
                              for(var i in tracks) {
                                  var mst = tracks[i];
                                  if(mst !== null && mst !== undefined)
                                      mst.stop();
                              }
                          } catch(e) {}
                      });
                  }, function(err) {
                      Janus.error(err);
                      callback([]);
                  });
              } else {
                  Janus.warn("navigator.mediaDevices unavailable");
                  callback([]);
              }
          }
          // Prepare a helper method to send AJAX requests in a syntax similar to jQuery (at least for what we care)
          Janus.ajax = function(params) {
              // Check params
              if(params === null || params === undefined)
                  return;
              params.success = (typeof params.success == "function") ? params.success : Janus.noop;
              params.error = (typeof params.error == "function") ? params.error : Janus.noop;
              // Make sure there's an URL
              if(params.url === null || params.url === undefined) {
                  Janus.error('Missing url', params.url);
                  params.error(null, -1, 'Missing url');
                  return;
              }
              // Validate async
              params.async = (params.async === null || params.async === undefined) ? true : (params.async === true);
              Janus.log(params);
              // IE doesn't even know what WebRTC is, so no polyfill needed
              var XHR = new XMLHttpRequest();
              XHR.open(params.type, params.url, params.async);
              if(params.contentType !== null && params.contentType !== undefined)
                  XHR.setRequestHeader('Content-type', params.contentType);
              if(params.async) {
                  XHR.onreadystatechange = function () {
                      if(XHR.readyState != 4)
                          return;
                      if(XHR.status !== 200) {
                          // Got an error?
                          if(XHR.status === 0)
                              XHR.status = "error";
                          params.error(XHR, XHR.status, "");
                          return;
                      }
                      // Got payload
                      params.success(JSON.parse(XHR.responseText));
                  };
              }
              try {
                  XHR.send(params.data);
                  if(!params.async) {
                      if(XHR.status !== 200) {
                          // Got an error?
                          if(XHR.status === 0)
                              XHR.status = "error";
                          params.error(XHR, XHR.status, "");
                          return;
                      }
                      // Got payload
                      params.success(JSON.parse(XHR.responseText));
                  }
              } catch(e) {
                  // Something broke up
                  params.error(XHR, 'error', '');
              };
          };
          // Detect tab close
          window.onbeforeunload = function() {
              Janus.log("Closing window");
              for(var s in Janus.sessions) {
                  if(Janus.sessions[s] !== null && Janus.sessions[s] !== undefined &&
                          Janus.sessions[s].destroyOnUnload) {
                      Janus.log("Destroying session " + s);
                      Janus.sessions[s].destroy();
                  }
              }
          }
          // function addJsList(srcArray) {
          //     if (!srcArray || !Array.isArray(srcArray) || srcArray.length == 0) {
          //         options.callback();
          //     }
          //     var count = 0;
          //     addJs(srcArray[count],next);
  
          //     function next() {
          //         count++;
          //         if (count<srcArray.length) {
          //             addJs(srcArray[count],next);
          //         }
          //         else {
          //             options.callback();
          //         }
          //     }
          // }
          // function addJs(src,done) {
          //     if(src === 'adapter.js') {
          //         if(window.getUserMedia && window.RTCPeerConnection) {
          //             // Already loaded
          //             done();
          //             return;
          //         }
          //     }
          //     var oHead = document.getElementsByTagName('head').item(0);
          //     var oScript = document.createElement("script");
          //     oScript.type = "text/javascript";
          //     oScript.src = src;
          //     oScript.onload = function() {
          //         Janus.log("Library " + src + " loaded");
          //         done();
          //     }
          //     oHead.appendChild(oScript);
          // }
          Janus.initDone = true;
          // addJsList(["adapter.js"]);  // We may need others in the future
      }
  };
  
  // Helper method to check whether WebRTC is supported by this browser
  Janus.isWebrtcSupported = function() {
      return window.RTCPeerConnection && window.getUserMedia;
  };
  
  
  // Janus session object
  function Janus(gatewayCallbacks) {
  // function Janus(gatewayCallbacks) {
      if(Janus.initDone === undefined) {
          gatewayCallbacks.error("Library not initialized");
          return {};
      }
      // if(!Janus.isWebrtcSupported()) {
          // gatewayCallbacks.error("WebRTC not supported by this browser");
          // return {};
      // }
      Janus.log("Library initialized: " + Janus.initDone);
      gatewayCallbacks = gatewayCallbacks || {};
      gatewayCallbacks.success = (typeof gatewayCallbacks.success == "function") ? gatewayCallbacks.success : Janus.noop;
      gatewayCallbacks.error = (typeof gatewayCallbacks.error == "function") ? gatewayCallbacks.error : Janus.noop;
      gatewayCallbacks.destroyed = (typeof gatewayCallbacks.destroyed == "function") ? gatewayCallbacks.destroyed : Janus.noop;
      if(gatewayCallbacks.server === null || gatewayCallbacks.server === undefined) {
          gatewayCallbacks.error("Invalid gateway url");
          return {};
      }
      var localstream = null
      var websockets = false;
      var ws = null;
      var wsHandlers = {};
      var wsKeepaliveTimeoutId = null;
  
      var servers = null, serversIndex = 0;
      var server = gatewayCallbacks.server;
      var camera_front = gatewayCallbacks.camera_front;
      if(Array.isArray(server)) {
          Janus.log("Multiple servers provided (" + server.length + "), will use the first that works");
          server = null;
          servers = gatewayCallbacks.server;
          Janus.debug(servers);
      } else {
          if(server.indexOf("ws") === 0) {
              websockets = true;
              Janus.log("Using WebSockets to contact Janus: " + server);
          } else {
              websockets = false;
              Janus.log("Using REST API to contact Janus: " + server);
          }
      }
      var iceServers = gatewayCallbacks.iceServers;
      if(iceServers === undefined || iceServers === null)
          iceServers = [{"url": "stun:stun.l.google.com:19302"}];
      // Whether IPv6 candidates should be gathered
      var ipv6Support = gatewayCallbacks.ipv6;
      if(ipv6Support === undefined || ipv6Support === null)
          ipv6Support = false;
      // Optional max events
      var maxev = null;
      if(gatewayCallbacks.max_poll_events !== undefined && gatewayCallbacks.max_poll_events !== null)
          maxev = gatewayCallbacks.max_poll_events;
      if(maxev < 1)
          maxev = 1;
      // Token to use (only if the token based authentication mechanism is enabled)
      var token = null;
      if(gatewayCallbacks.token !== undefined && gatewayCallbacks.token !== null)
          token = gatewayCallbacks.token;
      // API secret to use (only if the shared API secret is enabled)
      var apisecret = null;
      if(gatewayCallbacks.apisecret !== undefined && gatewayCallbacks.apisecret !== null)
          apisecret = gatewayCallbacks.apisecret;
      // Whether we should destroy this session when onbeforeunload is called
      this.destroyOnUnload = true;
      if(gatewayCallbacks.destroyOnUnload !== undefined && gatewayCallbacks.destroyOnUnload !== null)
          this.destroyOnUnload = (gatewayCallbacks.destroyOnUnload === true);
  
      var connected = false;
      var sessionId = null;
      var pluginHandles = {};
      var that = this;
      var retries = 0;
      var transactions = {};
      console.log("sample")
      createSession(gatewayCallbacks);
  
      // Public methods
      this.getServer = function() { return server; };
      this.isConnected = function() { return connected; };
      this.getSessionId = function() { return sessionId; };
      this.destroy = function(callbacks) { destroySession(callbacks); };
      this.attach = function(callbacks) { createHandle(callbacks); };
  
      // Private method to create random identifiers (e.g., transaction)
      function randomString(len) {
          charSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789';
          var randomString = '';
          for (var i = 0; i < len; i++) {
              var randomPoz = Math.floor(Math.random() * charSet.length);
              randomString += charSet.substring(randomPoz,randomPoz+1);
          }
          return randomString;
      }
  
      function eventHandler() {
          if(sessionId == null)
              return;
          Janus.debug('Long poll...');
          if(!connected) {
              Janus.warn("Is the gateway down? (connected=false)");
              return;
          }
          var longpoll = server + "/" + sessionId + "?rid=" + new Date().getTime();
          if(maxev !== undefined && maxev !== null)
              longpoll = longpoll + "&maxev=" + maxev;
          if(token !== null && token !== undefined)
              longpoll = longpoll + "&token=" + token;
          if(apisecret !== null && apisecret !== undefined)
              longpoll = longpoll + "&apisecret=" + apisecret;
          Janus.ajax({
              type: 'GET',
              url: longpoll,
              cache: false,
              timeout: 1000, // FIXME
              success: handleEvent,
              sucess: function(json) {
                  console.log(json)
              },
              error: function(XMLHttpRequest, textStatus, errorThrown) {
                  console.log(XMLHttpRequest)
                  Janus.error(textStatus + ": " + errorThrown);
                  //~ clearTimeout(timeoutTimer);
                  retries++;
                  if(retries > 1) {
                      // Did we just lose the gateway? :-(
                      connected = false;
                      gatewayCallbacks.error("Lost connection to the gateway (is it down?)");
                      return;
                  }
                  eventHandler();
              },
              dataType: "json"
          });
      }
  
      // Private event handler: this will trigger plugin callbacks, if set
      function handleEvent(json) {
          retries = 0;
          if(!websockets && sessionId !== undefined && sessionId !== null)
              setTimeout(eventHandler, 200);
          Janus.debug("Got event on session " + sessionId);
          Janus.debug(json);
          if(!websockets && Array.isArray(json)) {
              // We got an array: it means we passed a maxev > 1, iterate on all objects
              for(var i=0; i<json.length; i++) {
                  handleEvent(json[i]);
              }
              return;
          }
          if(json["janus"] === "keepalive") {
              // Nothing happened
              return;
          } else if(json["janus"] === "ack") {
              // Just an ack, we can probably ignore
              var transaction = json["transaction"];
              if(transaction !== null && transaction !== undefined) {
                  var reportSuccess = transactions[transaction];
                  if(reportSuccess !== null && reportSuccess !== undefined) {
                      reportSuccess(json);
                  }
                  delete transactions[transaction];
              }
              return;
          } else if(json["janus"] === "success") {
              // Success!
              var transaction = json["transaction"];
              if(transaction !== null && transaction !== undefined) {
                  var reportSuccess = transactions[transaction];
                  if(reportSuccess !== null && reportSuccess !== undefined) {
                      reportSuccess(json);
                  }
                  delete transactions[transaction];
              }
              return;
          } else if(json["janus"] === "webrtcup") {
              // The PeerConnection with the gateway is up! Notify this
              var sender = json["sender"];
              if(sender === undefined || sender === null) {
                  Janus.warn("Missing sender...");
                  return;
              }
              var pluginHandle = pluginHandles[sender];
              if(pluginHandle === undefined || pluginHandle === null) {
                  Janus.warn("This handle is not attached to this session");
                  return;
              }
              pluginHandle.webrtcState(true);
              return;
          } else if(json["janus"] === "hangup") {
              // A plugin asked the core to hangup a PeerConnection on one of our handles
              var sender = json["sender"];
              if(sender === undefined || sender === null) {
                  Janus.warn("Missing sender...");
                  return;
              }
              var pluginHandle = pluginHandles[sender];
              if(pluginHandle === undefined || pluginHandle === null) {
                  Janus.warn("This handle is not attached to this session");
                  return;
              }
              pluginHandle.webrtcState(false);
              pluginHandle.hangup();
          } else if(json["janus"] === "detached") {
              // A plugin asked the core to detach one of our handles
              var sender = json["sender"];
              if(sender === undefined || sender === null) {
                  Janus.warn("Missing sender...");
                  return;
              }
              var pluginHandle = pluginHandles[sender];
              if(pluginHandle === undefined || pluginHandle === null) {
                  // Don't warn here because destroyHandle causes this situation.
                  return;
              }
              pluginHandle.ondetached();
              pluginHandle.detach();
          } else if(json["janus"] === "media") {
              // Media started/stopped flowing
              var sender = json["sender"];
              if(sender === undefined || sender === null) {
                  Janus.warn("Missing sender...");
                  return;
              }
              var pluginHandle = pluginHandles[sender];
              if(pluginHandle === undefined || pluginHandle === null) {
                  Janus.warn("This handle is not attached to this session");
                  return;
              }
              pluginHandle.mediaState(json["type"], json["receiving"]);
          } else if(json["janus"] === "error") {
              // Oops, something wrong happened
            //   Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason);   // FIXME
              var transaction = json["transaction"];
              if(transaction !== null && transaction !== undefined) {
                  var reportSuccess = transactions[transaction];
                  if(reportSuccess !== null && reportSuccess !== undefined) {
                      reportSuccess(json);
                  }
                  delete transactions[transaction];
              }
              return;
          } else if(json["janus"] === "event") {
              var sender = json["sender"];
              if(sender === undefined || sender === null) {
                  Janus.warn("Missing sender...");
                  return;
              }
              var plugindata = json["plugindata"];
              if(plugindata === undefined || plugindata === null) {
                  Janus.warn("Missing plugindata...");
                  return;
              }
              Janus.debug("  -- Event is coming from " + sender + " (" + plugindata["plugin"] + ")");
              var data = plugindata["data"];
              Janus.debug(data);
              var pluginHandle = pluginHandles[sender];
              if(pluginHandle === undefined || pluginHandle === null) {
                  Janus.warn("This handle is not attached to this session");
                  return;
              }
              var jsep = json["jsep"];
              if(jsep !== undefined && jsep !== null) {
                  Janus.debug("Handling SDP as well...");
                  Janus.debug(jsep);
              }
              var callback = pluginHandle.onmessage;
              if(callback !== null && callback !== undefined) {
                  Janus.debug("Notifying application...");
                  // Send to callback specified when attaching plugin handle
                  callback(data, jsep);
              } else {
                  // Send to generic callback (?)
                  Janus.debug("No provided notification callback");
              }
          } else {
              Janus.warn("Unknown message '" + json["janus"] + "'");
          }
      }
  
      // Private helper to send keep-alive messages on WebSockets
      function keepAlive() {
          if(server === null || !websockets || !connected)
              return;
          wsKeepaliveTimeoutId = setTimeout(keepAlive, 30000);
          var request = { "janus": "keepalive", "session_id": sessionId, "transaction": randomString(12) };
          if(token !== null && token !== undefined)
              request["token"] = token;
          if(apisecret !== null && apisecret !== undefined)
              request["apisecret"] = apisecret;
          ws.send(JSON.stringify(request));
      }
  
      // Private method to create a session
      function createSession(callbacks) {
          var transaction = randomString(12);
          var request = { "janus": "create", "transaction": transaction };
          if(token !== null && token !== undefined)
              request["token"] = token;
          if(apisecret !== null && apisecret !== undefined)
              request["apisecret"] = apisecret;
          if(server === null && Array.isArray(servers)) {
              // We still need to find a working server from the list we were given
              server = servers[serversIndex];
              if(server.indexOf("ws") === 0) {
                  websockets = true;
                  Alert.alert("Server #" + (serversIndex+1) + ": trying WebSockets to contact Janus (" + server + ")");
              } else {
                  websockets = false;
                  Alert.alert("Server #" + (serversIndex+1) + ": trying REST API to contact Janus (" + server + ")");
              }
          }
          if(websockets) {
              ws = new WebSocket(server, 'janus-protocol');
              wsHandlers = {
                  'error': function() {
                    Alert.alert("Error connecting to the Janus WebSockets server... " + server);
                      if (Array.isArray(servers)) {
                          serversIndex++;
                          if (serversIndex == servers.length) {
                              // We tried all the servers the user gave us and they all failed
                              callbacks.error("Error connecting to any of the provided Janus servers: Is the gateway down?");
                              return;
                          }
                          // Let's try the next server
                          server = null;
                          setTimeout(function() {
                              createSession(callbacks);
                          }, 200);
                          return;
                      }
                      callbacks.error("Error connecting to the Janus WebSockets server: Is the gateway down?");
                  },
  
                  'open': function() {
                      // We need to be notified about the success
                      transactions[transaction] = function(json) {
                          Janus.debug(json);
                          if (json["janus"] !== "success") {
                              Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason);   // FIXME
                              callbacks.error(json["error"].reason);
                              return;
                          }
                          wsKeepaliveTimeoutId = setTimeout(keepAlive, 30000);
                          connected = true;
                          sessionId = json.data["id"];
                          Janus.log("Created session: " + sessionId);
                          Janus.sessions[sessionId] = that;
                          callbacks.success();
                      };
                      ws.send(JSON.stringify(request));
                  },
  
                  'message': function(event) {
                      handleEvent(JSON.parse(event.data));
                  },
  
                  'close': function() {
                      if (server === null || !connected) {
                          return;
                      }
                      connected = false;
                      // FIXME What if this is called when the page is closed?
                      gatewayCallbacks.error("Lost connection to the gateway (is it down?)");
                  }
              };
  
              for(var eventName in wsHandlers) {
                  ws.addEventListener(eventName, wsHandlers[eventName]);
              }
  
              return;
          }
          console.log("Janus.ajax({})")
          console.log(server)
          console.log(JSON.stringify(request))
          Janus.ajax({
              type: 'POST',
              url: server,
              cache: false,
              contentType: "application/json",
              data: JSON.stringify(request),
              success: function(json) {
                  Janus.debug(json);
                  if(json["janus"] !== "success") {
                      Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason);   // FIXME
                      callbacks.error(json["error"].reason);
                      return;
                  }
                  connected = true;
                  sessionId = json.data["id"];
                  Janus.log("Created session: " + sessionId);
                  Janus.sessions[sessionId] = that;
                  eventHandler();
                  callbacks.success();
              },
              error: function(XMLHttpRequest, textStatus, errorThrown) {
                  console.log("*********error***********")
                  console.log(XMLHttpRequest)
                  console.log(textStatus)
                  console.log(errorThrown)
                  // Janus.error(textStatus + ": " + errorThrown);   // FIXME
                  if(Array.isArray(servers)) {
                      serversIndex++;
                      if(serversIndex == servers.length) {
                          // We tried all the servers the user gave us and they all failed
                          callbacks.error("Error connecting to any of the provided Janus servers: Is the gateway down?");
                          return;
                      }
                      // Let's try the next server
                      server = null;
                      setTimeout(function() { createSession(callbacks); }, 200);
                      return;
                  }
                  if(errorThrown === "")
                      callbacks.error(textStatus + ": Is the gateway down?");
                  else
                      callbacks.error(textStatus + ": " + errorThrown);
              },
              dataType: "json"
          });
      }
  
      // Private method to destroy a session
      function destroySession(callbacks, syncRequest) {
          syncRequest = (syncRequest === true);
          Janus.log("Destroying session " + sessionId + " (sync=" + syncRequest + ")");
          callbacks = callbacks || {};
          // FIXME This method triggers a success even when we fail
          callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
          if(!connected) {
              Janus.warn("Is the gateway down? (connected=false)");
              callbacks.success();
              return;
          }
          if(sessionId === undefined || sessionId === null) {
              Janus.warn("No session to destroy");
              callbacks.success();
              gatewayCallbacks.destroyed();
              return;
          }
          delete Janus.sessions[sessionId];
          // Destroy all handles first
          for(var ph in pluginHandles) {
              var phv = pluginHandles[ph];
              Janus.log("Destroying handle " + phv.id + " (" + phv.plugin + ")");
              destroyHandle(phv.id, null, syncRequest);
          }
          // Ok, go on
          var request = { "janus": "destroy", "transaction": randomString(12) };
          if(token !== null && token !== undefined)
              request["token"] = token;
          if(apisecret !== null && apisecret !== undefined)
              request["apisecret"] = apisecret;
          if(websockets) {
              request["session_id"] = sessionId;
  
              var unbindWebSocket = function() {
                  for(var eventName in wsHandlers) {
                      ws.removeEventListener(eventName, wsHandlers[eventName]);
                  }
                  ws.removeEventListener('message', onUnbindMessage);
                  ws.removeEventListener('error', onUnbindError);
                  if(wsKeepaliveTimeoutId) {
                      clearTimeout(wsKeepaliveTimeoutId);
                  }
              };
  
              var onUnbindMessage = function(event){
                  var data = JSON.parse(event.data);
                  if(data.session_id == request.session_id && data.transaction == request.transaction) {
                      unbindWebSocket();
                      callbacks.success();
                      gatewayCallbacks.destroyed();
                  }
              };
              var onUnbindError = function(event) {
                  unbindWebSocket();
                  callbacks.error("Failed to destroy the gateway: Is the gateway down?");
                  gatewayCallbacks.destroyed();
              };
  
              ws.addEventListener('message', onUnbindMessage);
              ws.addEventListener('error', onUnbindError);
  
              ws.send(JSON.stringify(request));
              return;
          }
          console.log("sessionId")
          console.log(sessionId)
          console.log("server")
          console.log(server)
          Janus.ajax({
              type: 'POST',
              url: server + "/" + sessionId,
              async: true, // Sometimes we need false here, or destroying in onbeforeunload won't work
              cache: false,
              contentType: "application/json",
              data: JSON.stringify(request),
              success: function(json) {
                  Janus.log("Destroyed session:");
                  Janus.debug(json);
                  sessionId = null;
                  connected = false;
                  if(json["janus"] !== "success") {
                      Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason);   // FIXME
                  }
                  callbacks.success();
                  gatewayCallbacks.destroyed();
              },
              error: function(XMLHttpRequest, textStatus, errorThrown) {
                  Janus.error(textStatus + ": " + errorThrown);   // FIXME
                  // Reset everything anyway
                  sessionId = null;
                  connected = false;
                  callbacks.success();
                  gatewayCallbacks.destroyed();
              },
              dataType: "json"
          });
      }
  
      // Private method to create a plugin handle
      function createHandle(callbacks) {
          callbacks = callbacks || {};
          callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
          callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop;
          callbacks.consentDialog = (typeof callbacks.consentDialog == "function") ? callbacks.consentDialog : Janus.noop;
          callbacks.mediaState = (typeof callbacks.mediaState == "function") ? callbacks.mediaState : Janus.noop;
          callbacks.webrtcState = (typeof callbacks.webrtcState == "function") ? callbacks.webrtcState : Janus.noop;
          callbacks.onmessage = (typeof callbacks.onmessage == "function") ? callbacks.onmessage : Janus.noop;
          callbacks.onlocalstream = (typeof callbacks.onlocalstream == "function") ? callbacks.onlocalstream : Janus.noop;
          callbacks.onremotestream = (typeof callbacks.onremotestream == "function") ? callbacks.onremotestream : Janus.noop;
          callbacks.ondata = (typeof callbacks.ondata == "function") ? callbacks.ondata : Janus.noop;
          callbacks.ondataopen = (typeof callbacks.ondataopen == "function") ? callbacks.ondataopen : Janus.noop;
          callbacks.oncleanup = (typeof callbacks.oncleanup == "function") ? callbacks.oncleanup : Janus.noop;
          callbacks.ondetached = (typeof callbacks.ondetached == "function") ? callbacks.ondetached : Janus.noop;
          if(!connected) {
              Janus.warn("Is the gateway down? (connected=false)");
              callbacks.error("Is the gateway down? (connected=false)");
              return;
          }
          var plugin = callbacks.plugin;
          if(plugin === undefined || plugin === null) {
              Janus.error("Invalid plugin");
              callbacks.error("Invalid plugin");
              return;
          }
          var transaction = randomString(12);
          var request = { "janus": "attach", "plugin": plugin, "transaction": transaction };
          if(token !== null && token !== undefined)
              request["token"] = token;
          if(apisecret !== null && apisecret !== undefined)
              request["apisecret"] = apisecret;
          if(websockets) {
              transactions[transaction] = function(json) {
                  Janus.debug(json);
                  if(json["janus"] !== "success") {
                      Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason);   // FIXME
                      callbacks.error("Ooops: " + json["error"].code + " " + json["error"].reason);
                      return;
                  }
                  var handleId = json.data["id"];
                  Janus.log("Created handle: " + handleId);
                  var pluginHandle =
                      {
                          session : that,
                          plugin : plugin,
                          id : handleId,
                          webrtcStuff : {
                              started : false,
                              myStream : null,
                              streamExternal : false,
                              remoteStream : null,
                              mySdp : null,
                              pc : null,
                              dataChannel : null,
                              dtmfSender : null,
                              trickle : true,
                              iceDone : false,
                              sdpSent : false,
                              volume : {
                                  value : null,
                                  timer : null
                              },
                              bitrate : {
                                  value : null,
                                  bsnow : null,
                                  bsbefore : null,
                                  tsnow : null,
                                  tsbefore : null,
                                  timer : null
                              }
                          },
                          getId : function() { return handleId; },
                          getPlugin : function() { return plugin; },
                          getVolume : function() { return getVolume(handleId); },
                          isAudioMuted : function() { return isMuted(handleId, false); },
                          muteAudio : function() { return mute(handleId, false, true); },
                          unmuteAudio : function() { return mute(handleId, false, false); },
                          isVideoMuted : function() { return isMuted(handleId, true); },
                          muteVideo : function() { return mute(handleId, true, true); },
                          unmuteVideo : function() { return mute(handleId, true, false); },
                          getBitrate : function() { return getBitrate(handleId); },
                          changeLocalCamera : function(isFront) { return changeLocalCamera(handleId, isFront); },
                          send : function(callbacks) { sendMessage(handleId, callbacks); },
                          data : function(callbacks) { sendData(handleId, callbacks); },
                          dtmf : function(callbacks) { sendDtmf(handleId, callbacks); },
                          consentDialog : callbacks.consentDialog,
                          mediaState : callbacks.mediaState,
                          webrtcState : callbacks.webrtcState,
                          onmessage : callbacks.onmessage,
                          createOffer : function(callbacks) { prepareWebrtc(handleId, callbacks); },
                          createAnswer : function(callbacks) { prepareWebrtc(handleId, callbacks); },
                          handleRemoteJsep : function(callbacks) { prepareWebrtcPeer(handleId, callbacks); },
                          onlocalstream : callbacks.onlocalstream,
                          onremotestream : callbacks.onremotestream,
                          ondata : callbacks.ondata,
                          ondataopen : callbacks.ondataopen,
                          oncleanup : callbacks.oncleanup,
                          ondetached : callbacks.ondetached,
                          hangup : function(sendRequest) { cleanupWebrtc(handleId, sendRequest === true); },
                          detach : function(callbacks) { destroyHandle(handleId, callbacks); }
                      }
                  pluginHandles[handleId] = pluginHandle;
                  callbacks.success(pluginHandle);
              };
              request["session_id"] = sessionId;
              ws.send(JSON.stringify(request));
              return;
          }
          Janus.ajax({
              type: 'POST',
              url: server + "/" + sessionId,
              cache: false,
              contentType: "application/json",
              data: JSON.stringify(request),
              success: function(json) {
                  Janus.debug(json);
                  if(json["janus"] !== "success") {
                      Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason);   // FIXME
                      callbacks.error("Ooops: " + json["error"].code + " " + json["error"].reason);
                      return;
                  }
                  var handleId = json.data["id"];
                  Janus.log("Created handle: " + handleId);
                  var pluginHandle =
                      {
                          session : that,
                          plugin : plugin,
                          id : handleId,
                          webrtcStuff : {
                              started : false,
                              myStream : null,
                              streamExternal : false,
                              remoteStream : null,
                              mySdp : null,
                              pc : null,
                              dataChannel : null,
                              dtmfSender : null,
                              trickle : true,
                              iceDone : false,
                              sdpSent : false,
                              volume : {
                                  value : null,
                                  timer : null
                              },
                              bitrate : {
                                  value : null,
                                  bsnow : null,
                                  bsbefore : null,
                                  tsnow : null,
                                  tsbefore : null,
                                  timer : null
                              }
                          },
                          getId : function() { return handleId; },
                          getPlugin : function() { return plugin; },
                          getVolume : function() { return getVolume(handleId); },
                          isAudioMuted : function() { return isMuted(handleId, false); },
                          muteAudio : function() { return mute(handleId, false, true); },
                          unmuteAudio : function() { return mute(handleId, false, false); },
                          isVideoMuted : function() { return isMuted(handleId, true); },
                          muteVideo : function() { return mute(handleId, true, true); },
                          unmuteVideo : function() { return mute(handleId, true, false); },
                          getBitrate : function() { return getBitrate(handleId); },
                          changeLocalCamera : function(isFront) { return changeLocalCamera(handleId, isFront); },
                          send : function(callbacks) { sendMessage(handleId, callbacks); },
                          data : function(callbacks) { sendData(handleId, callbacks); },
                          dtmf : function(callbacks) { sendDtmf(handleId, callbacks); },
                          consentDialog : callbacks.consentDialog,
                          mediaState : callbacks.mediaState,
                          webrtcState : callbacks.webrtcState,
                          onmessage : callbacks.onmessage,
                          createOffer : function(callbacks) { prepareWebrtc(handleId, callbacks); },
                          createAnswer : function(callbacks) { prepareWebrtc(handleId, callbacks); },
                          handleRemoteJsep : function(callbacks) { prepareWebrtcPeer(handleId, callbacks); },
                          onlocalstream : callbacks.onlocalstream,
                          onremotestream : callbacks.onremotestream,
                          ondata : callbacks.ondata,
                          ondataopen : callbacks.ondataopen,
                          oncleanup : callbacks.oncleanup,
                          ondetached : callbacks.ondetached,
                          hangup : function(sendRequest) { cleanupWebrtc(handleId, sendRequest === true); },
                          detach : function(callbacks) { destroyHandle(handleId, callbacks); }
                      }
                  pluginHandles[handleId] = pluginHandle;
                  callbacks.success(pluginHandle);
              },
              error: function(XMLHttpRequest, textStatus, errorThrown) {
                  Janus.error(textStatus + ": " + errorThrown);   // FIXME
              },
              dataType: "json"
          });
      }
  
      // Private method to send a message
      function sendMessage(handleId, callbacks) {
          callbacks = callbacks || {};
          callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
          callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop;
          if(!connected) {
              Janus.warn("Is the gateway down? (connected=false)");
              callbacks.error("Is the gateway down? (connected=false)");
              return;
          }
          var message = callbacks.message;
          var jsep = callbacks.jsep;
          var transaction = randomString(12);
          var request = { "janus": "message", "body": message, "transaction": transaction };
          if(token !== null && token !== undefined)
              request["token"] = token;
          if(apisecret !== null && apisecret !== undefined)
              request["apisecret"] = apisecret;
          if(jsep !== null && jsep !== undefined)
              request.jsep = jsep;
          Janus.debug("Sending message to plugin (handle=" + handleId + "):");
          Janus.debug(request);
          if(websockets) {
              request["session_id"] = sessionId;
              request["handle_id"] = handleId;
              transactions[transaction] = function(json) {
                  Janus.debug("Message sent!");
                  Janus.debug(json);
                  if(json["janus"] === "success") {
                      // We got a success, must have been a synchronous transaction
                      var plugindata = json["plugindata"];
                      if(plugindata === undefined || plugindata === null) {
                          Janus.warn("Request succeeded, but missing plugindata...");
                          callbacks.success();
                          return;
                      }
                      Janus.log("Synchronous transaction successful (" + plugindata["plugin"] + ")");
                      var data = plugindata["data"];
                      Janus.debug(data);
                      callbacks.success(data);
                      return;
                  } else if(json["janus"] !== "ack") {
                      // Not a success and not an ack, must be an error
                      if(json["error"] !== undefined && json["error"] !== null) {
                          Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason);   // FIXME
                          callbacks.error(json["error"].code + " " + json["error"].reason);
                      } else {
                          Janus.error("Unknown error");   // FIXME
                          callbacks.error("Unknown error");
                      }
                      return;
                  }
                  // If we got here, the plugin decided to handle the request asynchronously
                  callbacks.success();
              };
              ws.send(JSON.stringify(request));
              return;
          }
          Janus.ajax({
              type: 'POST',
              url: server + "/" + sessionId + "/" + handleId,
              cache: false,
              contentType: "application/json",
              data: JSON.stringify(request),
              success: function(json) {
                  Janus.debug("Message sent!");
                  Janus.debug(json);
                  if(json["janus"] === "success") {
                      // We got a success, must have been a synchronous transaction
                      var plugindata = json["plugindata"];
                      if(plugindata === undefined || plugindata === null) {
                          Janus.warn("Request succeeded, but missing plugindata...");
                          callbacks.success();
                          return;
                      }
                      Janus.log("Synchronous transaction successful (" + plugindata["plugin"] + ")");
                      var data = plugindata["data"];
                      Janus.debug(data);
                      callbacks.success(data);
                      return;
                  } else if(json["janus"] !== "ack") {
                      // Not a success and not an ack, must be an error
                      if(json["error"] !== undefined && json["error"] !== null) {
                          Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason);   // FIXME
                          callbacks.error(json["error"].code + " " + json["error"].reason);
                      } else {
                          Janus.error("Unknown error");   // FIXME
                          callbacks.error("Unknown error");
                      }
                      return;
                  }
                  // If we got here, the plugin decided to handle the request asynchronously
                  callbacks.success();
              },
              error: function(XMLHttpRequest, textStatus, errorThrown) {
                  Janus.error(textStatus + ": " + errorThrown);   // FIXME
                  callbacks.error(textStatus + ": " + errorThrown);
              },
              dataType: "json"
          });
      }
  
      // Private method to send a trickle candidate
      function sendTrickleCandidate(handleId, candidate) {
          if(!connected) {
              Janus.warn("Is the gateway down? (connected=false)");
              return;
          }
          var request = { "janus": "trickle", "candidate": candidate, "transaction": randomString(12) };
          if(token !== null && token !== undefined)
              request["token"] = token;
          if(apisecret !== null && apisecret !== undefined)
              request["apisecret"] = apisecret;
          Janus.debug("Sending trickle candidate (handle=" + handleId + "):");
          Janus.debug(request);
          if(websockets) {
              request["session_id"] = sessionId;
              request["handle_id"] = handleId;
              ws.send(JSON.stringify(request));
              return;
          }
          Janus.ajax({
              type: 'POST',
              url: server + "/" + sessionId + "/" + handleId,
              cache: false,
              contentType: "application/json",
              data: JSON.stringify(request),
              success: function(json) {
                  Janus.debug("Candidate sent!");
                  Janus.debug(json);
                  if(json["janus"] !== "ack") {
                      Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason);   // FIXME
                      return;
                  }
              },
              error: function(XMLHttpRequest, textStatus, errorThrown) {
                  Janus.error(textStatus + ": " + errorThrown);   // FIXME
              },
              dataType: "json"
          });
      }
  
      // Private method to send a data channel message
      function sendData(handleId, callbacks) {
          callbacks = callbacks || {};
          callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
          callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop;
          var pluginHandle = pluginHandles[handleId];
          if(pluginHandle === null || pluginHandle === undefined ||
                  pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
              Janus.warn("Invalid handle");
              callbacks.error("Invalid handle");
              return;
          }
          var config = pluginHandle.webrtcStuff;
          var text = callbacks.text;
          if(text === null || text === undefined) {
              Janus.warn("Invalid text");
              callbacks.error("Invalid text");
              return;
          }
          Janus.log("Sending string on data channel: " + text);
          config.dataChannel.send(text);
          callbacks.success();
      }
  
      // Private method to send a DTMF tone
      function sendDtmf(handleId, callbacks) {
          callbacks = callbacks || {};
          callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
          callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop;
          var pluginHandle = pluginHandles[handleId];
          if(pluginHandle === null || pluginHandle === undefined ||
                  pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
              Janus.warn("Invalid handle");
              callbacks.error("Invalid handle");
              return;
          }
          var config = pluginHandle.webrtcStuff;
          if(config.dtmfSender === null || config.dtmfSender === undefined) {
              // Create the DTMF sender, if possible
              if(config.myStream !== undefined && config.myStream !== null) {
                  var tracks = config.myStream.getAudioTracks();
                  if(tracks !== null && tracks !== undefined && tracks.length > 0) {
                      var local_audio_track = tracks[0];
                      config.dtmfSender = config.pc.createDTMFSender(local_audio_track);
                      Janus.log("Created DTMF Sender");
                      config.dtmfSender.ontonechange = function(tone) { Janus.debug("Sent DTMF tone: " + tone.tone); };
                  }
              }
              if(config.dtmfSender === null || config.dtmfSender === undefined) {
                  Janus.warn("Invalid DTMF configuration");
                  callbacks.error("Invalid DTMF configuration");
                  return;
              }
          }
          var dtmf = callbacks.dtmf;
          if(dtmf === null || dtmf === undefined) {
              Janus.warn("Invalid DTMF parameters");
              callbacks.error("Invalid DTMF parameters");
              return;
          }
          var tones = dtmf.tones;
          if(tones === null || tones === undefined) {
              Janus.warn("Invalid DTMF string");
              callbacks.error("Invalid DTMF string");
              return;
          }
          var duration = dtmf.duration;
          if(duration === null || duration === undefined)
              duration = 500; // We choose 500ms as the default duration for a tone
          var gap = dtmf.gap;
          if(gap === null || gap === undefined)
              gap = 50;   // We choose 50ms as the default gap between tones
          Janus.debug("Sending DTMF string " + tones + " (duration " + duration + "ms, gap " + gap + "ms");
          config.dtmfSender.insertDTMF(tones, duration, gap);
      }
  
      // Private method to destroy a plugin handle
      function destroyHandle(handleId, callbacks, syncRequest) {
          syncRequest = false ;
          Janus.log("Destroying handle " + handleId + " (sync=" + syncRequest + ")");
          callbacks = callbacks || {};
          callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
          callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop;
          cleanupWebrtc(handleId);
          if(!connected) {
              Janus.warn("Is the gateway down? (connected=false)");
              callbacks.error("Is the gateway down? (connected=false)");
              return;
          }
          var request = { "janus": "detach", "transaction": randomString(12) };
          if(token !== null && token !== undefined)
              request["token"] = token;
          if(apisecret !== null && apisecret !== undefined)
              request["apisecret"] = apisecret;
          if(websockets) {
              request["session_id"] = sessionId;
              request["handle_id"] = handleId;
              ws.send(JSON.stringify(request));
              delete pluginHandles[handleId];
              callbacks.success();
              return;
          }
          Janus.ajax({
              type: 'POST',
              url: server + "/" + sessionId + "/" + handleId,
              async: true, // Sometimes we need false here, or destroying in onbeforeunload won't work
              cache: false,
              contentType: "application/json",
              data: JSON.stringify(request),
              success: function(json) {
                  Janus.log("Destroyed handle:");
                  Janus.debug(json);
                  if(json["janus"] !== "success") {
                      Janus.log("Ooops: " + json["error"].code + " " + json["error"].reason);   // FIXME
                  }
                  delete pluginHandles[handleId];
                  callbacks.success();
              },
              error: function(XMLHttpRequest, textStatus, errorThrown) {
                  Janus.error(textStatus + ": " + errorThrown);   // FIXME
                  // We cleanup anyway
                  delete pluginHandles[handleId];
                  callbacks.success();
              },
              dataType: "json"
          });
      }
  
  
  
  
      // WebRTC stuff
      function changeLocalCamera(handleId) {
          localstream._tracks[1]._switchCamera()             
      }
  
      function streamsDone(handleId, jsep, media, callbacks, stream) {
          var pluginHandle = pluginHandles[handleId];
          if(pluginHandle === null || pluginHandle === undefined ||
                  pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
              Janus.warn("Invalid handle");
              callbacks.error("Invalid handle");
              return;
          }
          var config = pluginHandle.webrtcStuff;
          console.log("streamsDone:", stream)
          Janus.debug("streamsDone:", stream);
          config.myStream = stream;
          var pc_config = {"iceServers": iceServers};
          //~ var pc_constraints = {'mandatory': {'MozDontOfferDataChannel':true}};
          var pc_constraints = {
              "optional": [{"DtlsSrtpKeyAgreement": true}]
          };
          if(ipv6Support === true) {
              // FIXME This is only supported in Chrome right now
              // For support in Firefox track this: https://bugzilla.mozilla.org/show_bug.cgi?id=797262
              pc_constraints.optional.push({"googIPv6":true});
          }
          Janus.log("Creating PeerConnection");
          console.log("Creating PeerConnection");
          Janus.debug(pc_constraints);
          config.pc = new RTCPeerConnection(pc_config, pc_constraints);
          Janus.debug(config.pc);
          console.log(config.pc);
  
          if(config.pc.getStats) {    // FIXME
              config.volume.value = 0;
              config.bitrate.value = "0 kbits/sec";
          }
          Janus.log("Preparing local SDP and gathering candidates (trickle=" + config.trickle + ")");
          console.log("Preparing local SDP and gathering candidates (trickle=" + config.trickle + ")");
  
          config.pc.onicecandidate = function(event) {
              // console.log("config.pc.onicecandidate")
              // if (event.candidate == null ||
              //         (webrtcDetectedBrowser === 'edge' && event.candidate.candidate.indexOf('endOfCandidates') > 0)) {
              //     Janus.log("End of candidates.");
              //     config.iceDone = true;
              //     if(config.trickle === true) {
              //         // Notify end of candidates
              //         sendTrickleCandidate(handleId, {"completed": true});
              //     } else {
              //         // No trickle, time to send the complete SDP (including all candidates)
              //         sendSDP(handleId, callbacks);
              //     }
              // } else {
                  // JSON.stringify doesn't work on some WebRTC objects anymore
                  // See https://code.google.com/p/chromium/issues/detail?id=467366
                  if (event.candidate) {
                      var candidate = {
                          "candidate": event.candidate.candidate,
                          "sdpMid": event.candidate.sdpMid,
                          "sdpMLineIndex": event.candidate.sdpMLineIndex
                      };
                      if(config.trickle === true) {
                          // Send candidate
                          sendTrickleCandidate(handleId, candidate);
                      }
                  }
              // }
          };
  
  
          if(stream !== null && stream !== undefined) {
              Janus.log('Adding local stream');
              console.log('Adding local stream')
              config.pc.addStream(stream);
              console.log('Adding local end')
              pluginHandle.onlocalstream(stream);
  
              localstream = stream
          }
  
          config.pc.onaddstream = function(remoteStream) {
              Janus.log("Handling Remote Stream");
              console.log("Handling Remote Stream");
              Janus.debug(remoteStream);
              config.remoteStream = remoteStream;
              pluginHandle.onremotestream(remoteStream.stream);
          };
          // Any data channel to create?
          if(isDataEnabled(media)) {
              Janus.log("Creating data channel");
              console.log("Creating data channel");
              var onDataChannelMessage = function(event) {
                  Janus.log('Received message on data channel: ' + event.data);
                  pluginHandle.ondata(event.data);    // FIXME
              }
              var onDataChannelStateChange = function() {
                  var dcState = config.dataChannel !== null ? config.dataChannel.readyState : "null";
                  Janus.log('State change on data channel: ' + dcState);
                  if(dcState === 'open') {
                      pluginHandle.ondataopen();  // FIXME
                  }
              }
              var onDataChannelError = function(error) {
                  Janus.error('Got error on data channel:', error);
                  // TODO
              }
              // Until we implement the proxying of open requests within the Janus core, we open a channel ourselves whatever the case
              config.dataChannel = config.pc.createDataChannel("JanusDataChannel", {ordered:false});  // FIXME Add options (ordered, maxRetransmits, etc.)
              config.dataChannel.onmessage = onDataChannelMessage;
              config.dataChannel.onopen = onDataChannelStateChange;
              config.dataChannel.onclose = onDataChannelStateChange;
              config.dataChannel.onerror = onDataChannelError;
          }
          // Create offer/answer now
          if(jsep === null || jsep === undefined) {
              console.log("createOffer")
              createOffer(handleId, media, callbacks);
          } else {
              config.pc.setRemoteDescription(
                      new RTCSessionDescription(jsep),
                      function() {
                          Janus.log("Remote description accepted!");
                          createAnswer(handleId, media, callbacks);
                      }, callbacks.error);
          }
      }
  
      function prepareWebrtc(handleId, callbacks) {
          callbacks = callbacks || {};
          callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
          callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : webrtcError;
          var jsep = callbacks.jsep;
          var media = callbacks.media;
          var pluginHandle = pluginHandles[handleId];
          if(pluginHandle === null || pluginHandle === undefined ||
                  pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
              Janus.warn("Invalid handle");
              callbacks.error("Invalid handle");
              return;
          }
          var config = pluginHandle.webrtcStuff;
          // Are we updating a session?
          if(config.pc !== undefined && config.pc !== null) {
              Janus.log("Updating existing media session");
              // Create offer/answer now
              if(jsep === null || jsep === undefined) {
                  createOffer(handleId, media, callbacks);
              } else {
                  config.pc.setRemoteDescription(
                          new RTCSessionDescription(jsep),
                          function() {
                              Janus.log("Remote description accepted!");
                              createAnswer(handleId, media, callbacks);
                          }, callbacks.error);
              }
              return;
          }
          // Was a MediaStream object passed, or do we need to take care of that?
          if(callbacks.stream !== null && callbacks.stream !== undefined) {
              var stream = callbacks.stream;
              Janus.log("MediaStream provided by the application");
              Janus.debug(stream);
              // Skip the getUserMedia part
              config.streamExternal = true;
              streamsDone(handleId, jsep, media, callbacks, stream);
              return;
          }
          config.trickle = isTrickleEnabled(callbacks.trickle);
          if(isAudioSendEnabled(media) || isVideoSendEnabled(media)) {
              var constraints = { mandatory: {}, optional: []};
              pluginHandle.consentDialog(true);
              var audioSupport = isAudioSendEnabled(media);
              if(audioSupport === true && media != undefined && media != null) {
                  if(typeof media.audio === 'object') {
                      audioSupport = media.audio;
                  }
              }
              var videoSupport = isVideoSendEnabled(media);
              if(videoSupport === true && media != undefined && media != null) {
                  if(media.video && media.video != 'screen' && media.video != 'window') {
                      var width = 0;
                      var height = 0, maxHeight = 0;
                      if(media.video === 'lowres') {
                          // Small resolution, 4:3
                          height = 240;
                          maxHeight = 240;
                          width = 320;
                      } else if(media.video === 'lowres-16:9') {
                          // Small resolution, 16:9
                          height = 180;
                          maxHeight = 180;
                          width = 320;
                      } else if(media.video === 'hires' || media.video === 'hires-16:9' ) {
                          // High resolution is only 16:9
                          height = 720;
                          maxHeight = 720;
                          width = 1280;
                          if(navigator.mozGetUserMedia) {
                              var firefoxVer = parseInt(window.navigator.userAgent.match(/Firefox\/(.*)/)[1], 10);
                              if(firefoxVer < 38) {
                                  // Unless this is and old Firefox, which doesn't support it
                                  Janus.warn(media.video + " unsupported, falling back to stdres (old Firefox)");
                                  height = 480;
                                  maxHeight = 480;
                                  width  = 640;
                              }
                          }
                      } else if(media.video === 'stdres') {
                          // Normal resolution, 4:3
                          height = 480;
                          maxHeight = 480;
                          width  = 640;
                      } else if(media.video === 'stdres-16:9') {
                          // Normal resolution, 16:9
                          height = 360;
                          maxHeight = 360;
                          width = 640;
                      } else {
                          Janus.log("Default video setting (" + media.video + ") is stdres 4:3");
                          height = 480;
                          maxHeight = 480;
                          width = 640;
                      }
                      Janus.log("Adding media constraint " + media.video);
                      if(navigator.mozGetUserMedia) {
                          var firefoxVer = parseInt(window.navigator.userAgent.match(/Firefox\/(.*)/)[1], 10);
                          if(firefoxVer < 38) {
                              videoSupport = {
                                  'require': ['height', 'width'],
                                  'height': {'max': maxHeight, 'min': height},
                                  'width':  {'max': width,  'min': width}
                              };
                          } else {
                              // http://stackoverflow.com/questions/28282385/webrtc-firefox-constraints/28911694#28911694
                              // https://github.com/meetecho/janus-gateway/pull/246
                              videoSupport = {
                                  'height': {'ideal': height},
                                  'width':  {'ideal': width}
                              };
                          }
                      } else {
                          videoSupport = {
                              'mandatory': {
                                  'maxHeight': maxHeight,
                                  'minHeight': height,
                                  'maxWidth':  width,
                                  'minWidth':  width
                              },
                              'optional': []
                          };
                      }
                      if(typeof media.video === 'object') {
                          videoSupport = media.video;
                      }
                      Janus.debug(videoSupport);
                  } else if(media.video === 'screen' || media.video === 'window') {
                      // Not a webcam, but screen capture
                      if(window.location.protocol !== 'https:') {
                          // Screen sharing mandates HTTPS
                          Janus.warn("Screen sharing only works on HTTPS, try the https:// version of this page");
                          pluginHandle.consentDialog(false);
                          callbacks.error("Screen sharing only works on HTTPS, try the https:// version of this page");
                          return;
                      }
                      // We're going to try and use the extension for Chrome 34+, the old approach
                      // for older versions of Chrome, or the experimental support in Firefox 33+
                      var cache = {};
                      function callbackUserMedia (error, stream) {
                          pluginHandle.consentDialog(false);
                          if(error) {
                              callbacks.error({code: error.code, name: error.name, message: error.message});
                          } else {
                              streamsDone(handleId, jsep, media, callbacks, stream);
                          }
                      };
                      function getScreenMedia(constraints, gsmCallback) {
                          Janus.log("Adding media constraint (screen capture)");
                          Janus.debug(constraints);
                          navigator.mediaDevices.getUserMedia(constraints)
                              .then(function(stream) { gsmCallback(null, stream); })
                              .catch(function(error) { pluginHandle.consentDialog(false); gsmCallback(error); });
                      };
                      if(window.navigator.userAgent.match('Chrome')) {
                          var chromever = parseInt(window.navigator.userAgent.match(/Chrome\/(.*) /)[1], 10);
                          var maxver = 33;
                          if(window.navigator.userAgent.match('Linux'))
                              maxver = 35;    // "known" crash in chrome 34 and 35 on linux
                          if(chromever >= 26 && chromever <= maxver) {
                              // Chrome 26->33 requires some awkward chrome://flags manipulation
                              constraints = {
                                  video: {
                                      mandatory: {
                                          googLeakyBucket: true,
                                          maxWidth: window.screen.width,
                                          maxHeight: window.screen.height,
                                          maxFrameRate: 3,
                                          chromeMediaSource: 'screen'
                                      }
                                  },
                                  audio: isAudioSendEnabled(media)
                              };
                              getScreenMedia(constraints, callbackUserMedia);
                          } else {
                              // Chrome 34+ requires an extension
                              var pending = window.setTimeout(
                                  function () {
                                      error = new Error('NavigatorUserMediaError');
                                      error.name = 'The required Chrome extension is not installed: click <a href="#">here</a> to install it. (NOTE: this will need you to refresh the page)';
                                      pluginHandle.consentDialog(false);
                                      return callbacks.error(error);
                                  }, 1000);
                              cache[pending] = [callbackUserMedia, null];
                              window.postMessage({ type: 'janusGetScreen', id: pending }, '*');
                          }
                      } else if (window.navigator.userAgent.match('Firefox')) {
                          var ffver = parseInt(window.navigator.userAgent.match(/Firefox\/(.*)/)[1], 10);
                          if(ffver >= 33) {
                              // Firefox 33+ has experimental support for screen sharing
                              constraints = {
                                  video: {
                                      mozMediaSource: media.video,
                                      mediaSource: media.video
                                  },
                                  audio: isAudioSendEnabled(media)
                              };
                              getScreenMedia(constraints, function (err, stream) {
                                  callbackUserMedia(err, stream);
                                  // Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=1045810
                                  if (!err) {
                                      var lastTime = stream.currentTime;
                                      var polly = window.setInterval(function () {
                                          if(!stream)
                                              window.clearInterval(polly);
                                          if(stream.currentTime == lastTime) {
                                              window.clearInterval(polly);
                                              if(stream.onended) {
                                                  stream.onended();
                                              }
                                          }
                                          lastTime = stream.currentTime;
                                      }, 500);
                                  }
                              });
                          } else {
                              var error = new Error('NavigatorUserMediaError');
                              error.name = 'Your version of Firefox does not support screen sharing, please install Firefox 33 (or more recent versions)';
                              pluginHandle.consentDialog(false);
                              callbacks.error(error);
                              return;
                          }
                      }
  
                      // Wait for events from the Chrome Extension
                      window.addEventListener('message', function (event) {
                          if(event.origin != window.location.origin)
                              return;
                          if(event.data.type == 'janusGotScreen' && cache[event.data.id]) {
                              var data = cache[event.data.id];
                              var callback = data[0];
                              delete cache[event.data.id];
  
                              if (event.data.sourceId === '') {
                                  // user canceled
                                  var error = new Error('NavigatorUserMediaError');
                                  error.name = 'You cancelled the request for permission, giving up...';
                                  pluginHandle.consentDialog(false);
                                  callbacks.error(error);
                              } else {
                                  constraints = {
                                      audio: isAudioSendEnabled(media),
                                      video: {
                                          mandatory: {
                                              chromeMediaSource: 'desktop',
                                              maxWidth: window.screen.width,
                                              maxHeight: window.screen.height,
                                              maxFrameRate: 3
                                          },
                                          optional: [
                                              {googLeakyBucket: true},
                                              {googTemporalLayeredScreencast: true}
                                          ]
                                      }
                                  };
                                  constraints.video.mandatory.chromeMediaSourceId = event.data.sourceId;
                                  getScreenMedia(constraints, callback);
                              }
                          } else if (event.data.type == 'janusGetScreenPending') {
                              window.clearTimeout(event.data.id);
                          }
                      });
                      return;
                  }
              }
              // If we got here, we're not screensharing
              if(media === null || media === undefined || media.video !== 'screen') {
                  // Check whether all media sources are actually available or not

                  MediaStreamTrack.getSources(sourceInfos => {
                      console.log(sourceInfos);
                      getUserMedia({
                          audio: true,
                          video: {
                            facingMode: (true ? "user" : "environment"),
                          }
                      },  (stream) => {
                        localStream = stream
                        console.log("Succeeded to get the local camera!")
                        streamsDone(handleId, jsep, media, callbacks, stream)
                      }, (error) => {
                        console.log("Failed to get the local camera!")
                        console.log(error)
                      }
                      )
                  });
              }
          } else {
              // No need to do a getUserMedia, create offer/answer right away
              streamsDone(handleId, jsep, media, callbacks);
          }
      }
  
      function prepareWebrtcPeer(handleId, callbacks) {
          callbacks = callbacks || {};
          callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
          callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : webrtcError;
          var jsep = callbacks.jsep;
          var pluginHandle = pluginHandles[handleId];
          if(pluginHandle === null || pluginHandle === undefined ||
                  pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
              Janus.warn("Invalid handle");
              callbacks.error("Invalid handle");
              return;
          }
          var config = pluginHandle.webrtcStuff;
          if(jsep !== undefined && jsep !== null) {
              if(config.pc === null) {
                  Janus.warn("Wait, no PeerConnection?? if this is an answer, use createAnswer and not handleRemoteJsep");
                  callbacks.error("No PeerConnection: if this is an answer, use createAnswer and not handleRemoteJsep");
                  return;
              }
              config.pc.setRemoteDescription(
                      new RTCSessionDescription(jsep),
                      function() {
                          Janus.log("Remote description accepted!");
                          callbacks.success();
                      }, callbacks.error);
          } else {
              callbacks.error("Invalid JSEP");
          }
      }
  
      function createOffer(handleId, media, callbacks) {
          callbacks = callbacks || {};
          callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
          callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop;
          var pluginHandle = pluginHandles[handleId];
          if(pluginHandle === null || pluginHandle === undefined ||
                  pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
              Janus.warn("Invalid handle");
              callbacks.error("Invalid handle");
              return;
          }
          var config = pluginHandle.webrtcStuff;
          Janus.log("Creating offer (iceDone=" + config.iceDone + ")");
          // https://code.google.com/p/webrtc/issues/detail?id=3508
          var mediaConstraints = null;
          // if(webrtcDetectedBrowser == "firefox" || webrtcDetectedBrowser == "edge") {
          //     mediaConstraints = {
          //         'offerToReceiveAudio':isAudioRecvEnabled(media),
          //         'offerToReceiveVideo':isVideoRecvEnabled(media)
          //     };
          // } else {
              mediaConstraints = {
                  'mandatory': {
                      'OfferToReceiveAudio':isAudioRecvEnabled(media),
                      'OfferToReceiveVideo':isVideoRecvEnabled(media)
                  }
              };
          // }
          Janus.debug(mediaConstraints);
  
  
          config.pc.createOffer(
              function(offer) {
                  Janus.debug(offer);
                  if(config.mySdp === null || config.mySdp === undefined) {
                      console.log("Setting local description");
                      config.mySdp = offer.sdp;
                      // config.pc.setLocalDescription(offer);
  
                      config.pc.setLocalDescription(offer, () => {
                      console.log('setLocalDescription', config.pc.localDescription);
                      }, function(e) {});
                  }
                  if(!config.iceDone && !config.trickle) {
                      // Don't do anything until we have all candidates
                      Janus.log("Waiting for all candidates...");
                      return;
                  }
                  if(config.sdpSent) {
                      Janus.log("Offer already sent, not sending it again");
                      return;
                  }
                  Janus.log("Offer ready");
                  Janus.debug(callbacks);
                  config.sdpSent = true;
                  // JSON.stringify doesn't work on some WebRTC objects anymore
                  // See https://code.google.com/p/chromium/issues/detail?id=467366
                  var jsep = {
                      "type": offer.type,
                      "sdp": offer.sdp
                  };
                  callbacks.success(jsep);
              }, function(e) {});
      }
  
      function createAnswer(handleId, media, callbacks) {
          callbacks = callbacks || {};
          callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
          callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop;
          var pluginHandle = pluginHandles[handleId];
          if(pluginHandle === null || pluginHandle === undefined ||
                  pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
              Janus.warn("Invalid handle");
              callbacks.error("Invalid handle");
              return;
          }
          var config = pluginHandle.webrtcStuff;
          Janus.log("Creating answer (iceDone=" + config.iceDone + ")");
          var mediaConstraints = null;
          // if(webrtcDetectedBrowser == "firefox" || webrtcDetectedBrowser == "edge") {
          //     mediaConstraints = {
          //         'offerToReceiveAudio':isAudioRecvEnabled(media),
          //         'offerToReceiveVideo':isVideoRecvEnabled(media)
          //     };
          // } else {
              mediaConstraints = {
                  'mandatory': {
                      'OfferToReceiveAudio':isAudioRecvEnabled(media),
                      'OfferToReceiveVideo':isVideoRecvEnabled(media)
                  }
              };
          // }
          Janus.debug(mediaConstraints);
          config.pc.createAnswer(
              function(answer) {
                  Janus.debug(answer);
                  if(config.mySdp === null || config.mySdp === undefined) {
                      Janus.log("Setting local description");
                      config.mySdp = answer.sdp;
                      config.pc.setLocalDescription(answer, () => {
                          // console.log('setLocalDescription', config.pc.localDescription);
                      }, (error)=> {
                          console.log(error);
                      },
                      );
                  }
                  if(!config.iceDone && !config.trickle) {
                      // Don't do anything until we have all candidates
                      Janus.log("Waiting for all candidates...");
                      return;
                  }
                  if(config.sdpSent) {    // FIXME badly
                      Janus.log("Answer already sent, not sending it again");
                      return;
                  }
                  config.sdpSent = true;
                  // JSON.stringify doesn't work on some WebRTC objects anymore
                  // See https://code.google.com/p/chromium/issues/detail?id=467366
                  var jsep = {
                      "type": answer.type,
                      "sdp": answer.sdp
                  };
                  callbacks.success(jsep);
              }, callbacks.error, mediaConstraints);
      }
  
      function sendSDP(handleId, callbacks) {
          callbacks = callbacks || {};
          callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop;
          callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop;
          var pluginHandle = pluginHandles[handleId];
          if(pluginHandle === null || pluginHandle === undefined ||
                  pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
              Janus.warn("Invalid handle, not sending anything");
              return;
          }
          var config = pluginHandle.webrtcStuff;
          Janus.log("Sending offer/answer SDP...");
          if(config.mySdp === null || config.mySdp === undefined) {
              Janus.warn("Local SDP instance is invalid, not sending anything...");
              return;
          }
          config.mySdp = {
              "type": config.pc.localDescription.type,
              "sdp": config.pc.localDescription.sdp
          };
          if(config.sdpSent) {
              Janus.log("Offer/Answer SDP already sent, not sending it again");
              return;
          }
          if(config.trickle === false)
              config.mySdp["trickle"] = false;
          Janus.debug(callbacks);
          config.sdpSent = true;
          callbacks.success(config.mySdp);
      }
  
      function getVolume(handleId) {
          var pluginHandle = pluginHandles[handleId];
          if(pluginHandle === null || pluginHandle === undefined ||
                  pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
              Janus.warn("Invalid handle");
              return 0;
          }
          var config = pluginHandle.webrtcStuff;
          // Start getting the volume, if getStats is supported
          if(config.pc.getStats && webrtcDetectedBrowser == "chrome") {   // FIXME
              if(config.remoteStream === null || config.remoteStream === undefined) {
                  Janus.warn("Remote stream unavailable");
                  return 0;
              }
              // http://webrtc.googlecode.com/svn/trunk/samples/js/demos/html/constraints-and-stats.html
              if(config.volume.timer === null || config.volume.timer === undefined) {
                  Janus.log("Starting volume monitor");
                  config.volume.timer = setInterval(function() {
                      config.pc.getStats(function(stats) {
                          var results = stats.result();
                          for(var i=0; i<results.length; i++) {
                              var res = results[i];
                              if(res.type == 'ssrc' && res.stat('audioOutputLevel')) {
                                  config.volume.value = res.stat('audioOutputLevel');
                              }
                          }
                      });
                  }, 200);
                  return 0;   // We don't have a volume to return yet
              }
              return config.volume.value;
          } else {
              Janus.log("Getting the remote volume unsupported by browser");
              return 0;
          }
      }
  
      function isMuted(handleId, video) {
          var pluginHandle = pluginHandles[handleId];
          if(pluginHandle === null || pluginHandle === undefined ||
                  pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
              Janus.warn("Invalid handle");
              return true;
          }
          var config = pluginHandle.webrtcStuff;
          if(config.pc === null || config.pc === undefined) {
              Janus.warn("Invalid PeerConnection");
              return true;
          }
          if(config.myStream === undefined || config.myStream === null) {
              Janus.warn("Invalid local MediaStream");
              return true;
          }
          if(video) {
              // Check video track
              if(config.myStream.getVideoTracks() === null
                      || config.myStream.getVideoTracks() === undefined
                      || config.myStream.getVideoTracks().length === 0) {
                  Janus.warn("No video track");
                  return true;
              }
              return !config.myStream.getVideoTracks()[0].enabled;
          } else {
              // Check audio track
              if(config.myStream.getAudioTracks() === null
                      || config.myStream.getAudioTracks() === undefined
                      || config.myStream.getAudioTracks().length === 0) {
                  Janus.warn("No audio track");
                  return true;
              }
              return !config.myStream.getAudioTracks()[0].enabled;
          }
      }
  
      function mute(handleId, video, mute) {
          var pluginHandle = pluginHandles[handleId];
          if(pluginHandle === null || pluginHandle === undefined ||
                  pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
              Janus.warn("Invalid handle");
              return false;
          }
          var config = pluginHandle.webrtcStuff;
          if(config.pc === null || config.pc === undefined) {
              Janus.warn("Invalid PeerConnection");
              return false;
          }
          if(config.myStream === undefined || config.myStream === null) {
              Janus.warn("Invalid local MediaStream");
              return false;
          }
          if(video) {
              // Mute/unmute video track
              if(config.myStream.getVideoTracks() === null
                      || config.myStream.getVideoTracks() === undefined
                      || config.myStream.getVideoTracks().length === 0) {
                  Janus.warn("No video track");
                  return false;
              }
              config.myStream.getVideoTracks()[0].enabled = mute ? false : true;
              return true;
          } else {
              // Mute/unmute audio track
              if(config.myStream.getAudioTracks() === null
                      || config.myStream.getAudioTracks() === undefined
                      || config.myStream.getAudioTracks().length === 0) {
                  Janus.warn("No audio track");
                  return false;
              }
              config.myStream.getAudioTracks()[0].enabled = mute ? false : true;
              return true;
          }
      }
  
      function getBitrate(handleId) {
          var pluginHandle = pluginHandles[handleId];
          if(pluginHandle === null || pluginHandle === undefined ||
                  pluginHandle.webrtcStuff === null || pluginHandle.webrtcStuff === undefined) {
              Janus.warn("Invalid handle");
              return "Invalid handle";
          }
          var config = pluginHandle.webrtcStuff;
          if(config.pc === null || config.pc === undefined)
              return "Invalid PeerConnection";
          // Start getting the bitrate, if getStats is supported
          if(true) {
              // Do it the Chrome way
              if(config.remoteStream === null || config.remoteStream === undefined) {
                  Janus.warn("Remote stream unavailable");
                  return "Remote stream unavailable";
              }
              // http://webrtc.googlecode.com/svn/trunk/samples/js/demos/html/constraints-and-stats.html
              if(config.bitrate.timer === null || config.bitrate.timer === undefined) {
                  Janus.log("Starting bitrate timer (Chrome)");
                  config.bitrate.timer = setInterval(function() {
                      config.pc.getStats(function(stats) {
                          var results = stats.result();
                          for(var i=0; i<results.length; i++) {
                              var res = results[i];
                              if(res.type == 'ssrc' && res.stat('googFrameHeightReceived')) {
                                  config.bitrate.bsnow = res.stat('bytesReceived');
                                  config.bitrate.tsnow = res.timestamp;
                                  if(config.bitrate.bsbefore === null || config.bitrate.tsbefore === null) {
                                      // Skip this round
                                      config.bitrate.bsbefore = config.bitrate.bsnow;
                                      config.bitrate.tsbefore = config.bitrate.tsnow;
                                  } else {
                                      // Calculate bitrate
                                      var bitRate = Math.round((config.bitrate.bsnow - config.bitrate.bsbefore) * 8 / (config.bitrate.tsnow - config.bitrate.tsbefore));
                                      config.bitrate.value = bitRate + ' kbits/sec';
                                      //~ Janus.log("Estimated bitrate is " + config.bitrate.value);
                                      config.bitrate.bsbefore = config.bitrate.bsnow;
                                      config.bitrate.tsbefore = config.bitrate.tsnow;
                                  }
                              }
                          }
                      });
                  }, 1000);
                  return "0 kbits/sec";   // We don't have a bitrate value yet
              }
              return config.bitrate.value;
          } else if(config.pc.getStats && webrtcDetectedBrowser == "firefox") {
              // Do it the Firefox way
              if(config.remoteStream === null || config.remoteStream === undefined
                      || config.remoteStream.stream === null || config.remoteStream.stream === undefined) {
                  Janus.warn("Remote stream unavailable");
                  return "Remote stream unavailable";
              }
              var videoTracks = config.remoteStream.stream.getVideoTracks();
              if(videoTracks === null || videoTracks === undefined || videoTracks.length < 1) {
                  Janus.warn("No video track");
                  return "No video track";
              }
              // https://github.com/muaz-khan/getStats/blob/master/getStats.js
              if(config.bitrate.timer === null || config.bitrate.timer === undefined) {
                  Janus.log("Starting bitrate timer (Firefox)");
                  config.bitrate.timer = setInterval(function() {
                      // We need a helper callback
                      var cb = function(res) {
                          if(res === null || res === undefined ||
                                  res.inbound_rtp_video_1 == null || res.inbound_rtp_video_1 == null) {
                              config.bitrate.value = "Missing inbound_rtp_video_1";
                              return;
                          }
                          config.bitrate.bsnow = res.inbound_rtp_video_1.bytesReceived;
                          config.bitrate.tsnow = res.inbound_rtp_video_1.timestamp;
                          if(config.bitrate.bsbefore === null || config.bitrate.tsbefore === null) {
                              // Skip this round
                              config.bitrate.bsbefore = config.bitrate.bsnow;
                              config.bitrate.tsbefore = config.bitrate.tsnow;
                          } else {
                              // Calculate bitrate
                              var bitRate = Math.round((config.bitrate.bsnow - config.bitrate.bsbefore) * 8 / (config.bitrate.tsnow - config.bitrate.tsbefore));
                              config.bitrate.value = bitRate + ' kbits/sec';
                              config.bitrate.bsbefore = config.bitrate.bsnow;
                              config.bitrate.tsbefore = config.bitrate.tsnow;
                          }
                      };
                      // Actually get the stats
                      config.pc.getStats(videoTracks[0], function(stats) {
                          cb(stats);
                      }, cb);
                  }, 1000);
                  return "0 kbits/sec";   // We don't have a bitrate value yet
              }
              return config.bitrate.value;
          } else {
              Janus.warn("Getting the video bitrate unsupported by browser");
              return "Feature unsupported by browser";
          }
      }
  
      function webrtcError(error) {
          Janus.error("WebRTC error:", error);
      }
  
      function cleanupWebrtc(handleId, hangupRequest) {
  
  
          Janus.log("Cleaning WebRTC stuff");
          console.log(handleId)
          console.log(hangupRequest)
          var pluginHandle = pluginHandles[handleId];
          if(pluginHandle === null || pluginHandle === undefined) {
              // Nothing to clean
              return;
          }
          var config = pluginHandle.webrtcStuff;
          if(config !== null && config !== undefined) {
              if(hangupRequest === true) {
                  // Send a hangup request (we don't really care about the response)
                  var request = { "janus": "hangup", "transaction": randomString(12) };
                  if(token !== null && token !== undefined)
                      request["token"] = token;
                  if(apisecret !== null && apisecret !== undefined)
                      request["apisecret"] = apisecret;
                  Janus.debug("Sending hangup request (handle=" + handleId + "):");
                  Janus.debug(request);
                  if(websockets) {
                      request["session_id"] = sessionId;
                      request["handle_id"] = handleId;
                      ws.send(JSON.stringify(request));
                  } else {
                      $.ajax({
                          type: 'POST',
                          url: server + "/" + sessionId + "/" + handleId,
                          cache: false,
                          contentType: "application/json",
                          data: JSON.stringify(request),
                          dataType: "json"
                      });
                  }
              }
              // Cleanup stack
              config.remoteStream = null;
              if(config.volume.timer)
                  clearInterval(config.volume.timer);
              config.volume.value = null;
              if(config.bitrate.timer)
                  clearInterval(config.bitrate.timer);
              config.bitrate.timer = null;
              config.bitrate.bsnow = null;
              config.bitrate.bsbefore = null;
              config.bitrate.tsnow = null;
              config.bitrate.tsbefore = null;
              config.bitrate.value = null;
              try {
                  // Try a MediaStream.stop() first
                  if(!config.streamExternal && config.myStream !== null && config.myStream !== undefined) {
                      // console.log(config)
                      Janus.log("Stopping local stream");
                      // config.pc.removeStream(localstream)
                      // localstream.release()
                      // localstream.stop()
                      localStream.getTracks().forEach((t) => {
                          localStream.removeTrack(t);
                      });
                      localStream.release();
                      // config.myStream.stop();
                      config.myStream.getTracks().forEach(function (track) { track.stop(); });
                  }
              } catch(e) {
                  console.log(e)
                  // Do nothing if this fails
              }
              try {
                  // Try a MediaStreamTrack.stop() for each track as well
                  if(!config.streamExternal && config.myStream !== null && config.myStream !== undefined) {
                      Janus.log("Stopping local stream tracks");
                      var tracks = config.myStream.getTracks();
                      for(var i in tracks) {
                          var mst = tracks[i];
                          Janus.log(mst);
                          if(mst !== null && mst !== undefined)
                              mst.stop();
                      }
                  }
              } catch(e) {
                  console.log(e)
                  // Do nothing if this fails
              }
              config.streamExternal = false;
              config.myStream = null;
              // Close PeerConnection
              try {
                  config.pc.close();
              } catch(e) {
                  // Do nothing
              }
              config.pc = null;
              config.mySdp = null;
              config.iceDone = false;
              config.sdpSent = false;
              config.dataChannel = null;
              config.dtmfSender = null;
          }
          pluginHandle.oncleanup();
      }
  
      // Helper methods to parse a media object
      function isAudioSendEnabled(media) {
          Janus.debug("isAudioSendEnabled:", media);
          if(media === undefined || media === null)
              return true;    // Default
          if(media.audio === false)
              return false;   // Generic audio has precedence
          if(media.audioSend === undefined || media.audioSend === null)
              return true;    // Default
          return (media.audioSend === true);
      }
  
      function isAudioRecvEnabled(media) {
          Janus.debug("isAudioRecvEnabled:", media);
          if(media === undefined || media === null)
              return true;    // Default
          if(media.audio === false)
              return false;   // Generic audio has precedence
          if(media.audioRecv === undefined || media.audioRecv === null)
              return true;    // Default
          return (media.audioRecv === true);
      }
  
      function isVideoSendEnabled(media) {
          Janus.debug("isVideoSendEnabled:", media);
          // if(webrtcDetectedBrowser == "edge") {
          //     Janus.warn("Edge doesn't support compatible video yet");
          //     return false;
          // }
          if(media === undefined || media === null)
              return true;    // Default
          if(media.video === false)
              return false;   // Generic video has precedence
          if(media.videoSend === undefined || media.videoSend === null)
              return true;    // Default
          return (media.videoSend === true);
      }
  
      function isVideoRecvEnabled(media) {
          Janus.debug("isVideoRecvEnabled:", media);
          // if(webrtcDetectedBrowser == "edge") {
          //     Janus.warn("Edge doesn't support compatible video yet");
          //     return false;
          // }
          if(media === undefined || media === null)
              return true;    // Default
          if(media.video === false)
              return false;   // Generic video has precedence
          if(media.videoRecv === undefined || media.videoRecv === null)
              return true;    // Default
          return (media.videoRecv === true);
      }
  
      function isDataEnabled(media) {
          Janus.debug("isDataEnabled:", media);
          // if(webrtcDetectedBrowser == "edge") {
          //     Janus.warn("Edge doesn't support data channels yet");
          //     return false;
          // }
          if(media === undefined || media === null)
              return false;   // Default
          return (media.data === true);
      }
  
      function isTrickleEnabled(trickle) {
          Janus.debug("isTrickleEnabled:", trickle);
          if(trickle === undefined || trickle === null)
              return true;    // Default is true
          return (trickle === true);
      }
  };
  
  
  module.exports =  Janus