text: var VisioUtils = {
      maxLevel: 0,
      remoteMaxLevel: 0,
      audioContext: null,
      remoteAudioContext: null,
  
      handleAudio: function() {
          VisioUtils.audioContext = new AudioContext();
  
          var microphone = VisioUtils.audioContext.createMediaStreamSource(
              Visio.withVideo
                  ? Visio.localVideo.srcObject
                  : Visio.localAudio.srcObject
          );
  
          var javascriptNode = VisioUtils.audioContext.createScriptProcessor(2048, 1, 1);
          var icon = document.querySelector('#toggle_audio i');
  
          microphone.connect(javascriptNode);
          javascriptNode.connect(VisioUtils.audioContext.destination);
          javascriptNode.onaudioprocess = function(event) {
              var inpt = event.inputBuffer.getChannelData(0);
              var instant = 0.0;
              var sum = 0.0;
  
              for(var i = 0; i < inpt.length; ++i) {
                  sum += inpt[i] * inpt[i];
              }
  
              instant = Math.sqrt(sum / inpt.length);
              VisioUtils.maxLevel = Math.max(VisioUtils.maxLevel, instant);
  
              var level = Math.log2((instant/VisioUtils.maxLevel)+1);
  
              if (level < 0.02) {
                  icon.style.color = 'rgb(255, 255, 255, 1)';
              } else {
                  var inverse = 255-(level.toPrecision(2)*255);
                  icon.style.color = 'rgb(' + inverse + ', 255, ' + inverse + ')';
              }
          }
      },
  
      handleRemoteAudio: function() {
          VisioUtils.remoteAudioContext = new AudioContext();
  
          var remoteMicrophone = VisioUtils.remoteAudioContext.createMediaStreamSource(
              Visio.withVideo
                  ? Visio.remoteVideo.srcObject
                  : Visio.remoteAudio.srcObject
          );
  
          var remoteJavascriptNode = VisioUtils.remoteAudioContext.createScriptProcessor(2048, 1, 1);
          var remoteMeter = document.querySelector('#visio #remote_level');
  
          remoteMicrophone.connect(remoteJavascriptNode);
          remoteJavascriptNode.connect(VisioUtils.remoteAudioContext.destination);
          remoteJavascriptNode.onaudioprocess = function(event) {
              var inpt = event.inputBuffer.getChannelData(0);
              var instant = 0.0;
              var sum = 0.0;
  
              for(var i = 0; i < inpt.length; ++i) {
                  sum += inpt[i] * inpt[i];
              }
  
              instant = Math.sqrt(sum / inpt.length);
              VisioUtils.remoteMaxLevel = Math.max(VisioUtils.remoteMaxLevel, instant);
  
              var level = Math.log2((instant/VisioUtils.remoteMaxLevel)+1);
              if (level < 0.02) {
                  level = 0;
                  VisioUtils.remoteMaxLevel = 0;
              }
  
              remoteMeter.style.borderColor = 'rgba(255, 255, 255, ' + level + ')';
          }
      },
  
      toggleFullScreen: function() {
          var button = document.querySelector('#toggle_fullscreen i');
  
          if (!document.fullscreenElement) {
              if (document.body.requestFullscreen) {
                  document.body.requestFullscreen();
              }
  
              button.innerText = 'fullscreen_exit';
          } else {
              if (document.exitFullscreen) {
                  document.exitFullscreen();
              }
  
              button.innerText = 'fullscreen';
          }
      },
  
      toggleAudio: function() {
          var button = document.querySelector('#toggle_audio i');
          var rtc = Visio.pc.getSenders().find(rtc => rtc.track && rtc.track.kind == 'audio');
  
          if (rtc && rtc.track.enabled == 1) {
              rtc.track.enabled = 0;
              button.innerText = 'mic_off';
          } else if (rtc) {
              rtc.track.enabled = 1;
              button.innerText = 'mic';
          }
      },
  
      toggleVideo: function() {
          var button = document.querySelector('#toggle_video i');
          var rtc = Visio.pc.getSenders().find(rtc => rtc.track && rtc.track.kind == 'video');
  
          if (rtc) {
              if (rtc.track.enabled == 1) {
                  rtc.track.enabled = 0;
                  button.innerText = 'videocam_off';
              } else {
                  rtc.track.enabled = 1;
                  button.innerText = 'videocam';
              }
          }
      },
  
      toggleMainButton: function() {
          button = document.getElementById('main');
          state = document.querySelector('p.state');
  
          i = button.querySelector('i');
  
          button.classList.remove('red', 'green', 'gray', 'orange', 'ring', 'blue');
          button.classList.add('disabled');
  
          if (Visio.pc) {
              let length = Visio.pc.getSenders().length;
  
              if (Visio.pc.iceConnectionState != 'closed'
              && length > 0) {
                  button.classList.remove('disabled');
              }
  
              button.onclick = function() {};
  
              if (length == 0) {
                  button.classList.add('gray');
                  i.innerText = 'more_horiz';
              } else if (Visio.pc.iceConnectionState == 'new') {
                  //if (Visio.pc.iceGatheringState == 'gathering'
                  //|| Visio.pc.iceGatheringState == 'complete') {
                  if (Visio.calling) {
                      button.classList.add('orange');
                      i.className = 'material-icons ring';
                      i.innerText = 'call';
                      state.innerText = Visio.states.ringing;
  
                      button.onclick = function() { Visio.goodbye('cancel'); };
                  } else {
                      button.classList.add('green');
                      button.classList.add('disabled');
                      i.innerText = 'call';
                  }
              } else if (Visio.pc.iceConnectionState == 'checking') {
                  button.classList.add('blue');
                  i.className = 'material-icons disabled';
                  i.innerText = 'more_horiz';
                  state.innerText = Visio.states.connecting;
              } else if (Visio.pc.iceConnectionState == 'closed') {
                  button.classList.add('gray');
                  button.classList.remove('disabled');
                  i.innerText = 'call_end';
  
                  button.onclick = function() { Visio.goodbye(); };
              } else if (Visio.pc.iceConnectionState == 'connected'
                     || Visio.pc.iceConnectionState == 'complete'
                     || Visio.pc.iceConnectionState == 'failed') {
                  button.classList.add('red');
                  i.className = 'material-icons';
                  i.innerText = 'call_end';
  
                  if (Visio.pc.iceConnectionState == 'failed') {
                      state.innerText = Visio.states.failed;
                  } else {
                      state.innerText = Visio.states.in_call;
                  }
  
                  button.onclick = () => Visio.goodbye();
              }
          }
      },
  
      enableScreenSharingButton: function() {
          document.querySelector('#screen_sharing').classList.add('enabled');
      },
  
      toggleScreenSharing: async function() {
          var button = document.querySelector('#screen_sharing i');
          if (Visio.screenSharing.srcObject == null) {
              try {
                  Visio.screenSharing.srcObject = await navigator.mediaDevices.getDisplayMedia({
                      video: {
                          cursor: "always"
                      },
                      audio: false
                  });
  
                  Visio.screenSharing.classList.add('sharing');
                  Visio.switchCamera.classList.add('disabled');
                  button.innerText = 'stop_screen_share';
  
                  Visio.gotScreen();
              } catch(err) {
                  console.error("Error: " + err);
              }
          } else {
              Visio.screenSharing.srcObject.getTracks().forEach(track => track.stop());
              Visio.screenSharing.srcObject = null;
              Visio.screenSharing.classList.remove('sharing');
              Visio.switchCamera.classList.remove('disabled');
  
              button.innerText = 'screen_share';
  
              Visio.gotQuickStream();
          }
      },
  
      switchCameraSetup: function() {
          Visio.videoSelect = document.querySelector('#visio select#visio_source');
          navigator.mediaDevices.enumerateDevices().then(devices => VisioUtils.gotDevices(devices));
  
          Visio.switchCamera = document.querySelector("#visio #switch_camera");
          Visio.switchCamera.onclick = () => {
              Visio.videoSelect.selectedIndex++;
  
              // No empty selection
              if (Visio.videoSelect.selectedIndex == -1) {
                  Visio.videoSelect.selectedIndex++;
              }
  
              Visio.gotStream();
          };
      },
  
      pcReplaceTrack: function(stream) {
          let videoTrack = stream.getVideoTracks()[0];
          var sender = Visio.pc.getSenders().find(s => s.track && s.track.kind == videoTrack.kind);
  
          if (sender) {
              sender.replaceTrack(videoTrack);
          }
      },
  
      gotDevices: function(deviceInfos) {
          Visio.videoSelect.innerText = '';
  
          const ids = [];
  
          for (let i = 0; i !== deviceInfos.length; ++i) {
              const deviceInfo = deviceInfos[i];
  
              if (deviceInfo.kind === 'videoinput' && !ids.includes(deviceInfo.deviceId)) {
                  const option = document.createElement('option');
                  option.value = deviceInfo.deviceId;
                  option.text = deviceInfo.label;
                  Visio.videoSelect.add(option);
                  ids.push(deviceInfo.deviceId);
              }
          }
  
          if (ids.length >= 2) {
              document.querySelector("#visio #switch_camera").classList.add('enabled');
          }
      }
  }
  