text: // Last time updated: 2017-09-20 11:19:01 AM UTC
  
  // ________________________
  // MultiStreamsMixer v1.0.3
  
  // Open-Sourced: https://github.com/muaz-khan/MultiStreamsMixer
  
  // --------------------------------------------------
  // Muaz Khan     - www.MuazKhan.com
  // MIT License   - www.WebRTC-Experiment.com/licence
  // --------------------------------------------------
  
  function MultiStreamsMixer(arrayOfMediaStreams) {
  
      // requires: chrome://flags/#enable-experimental-web-platform-features
  
      var videos = [];
      var isStopDrawingFrames = false;
  
      var canvas = document.createElement('canvas');
      var context = canvas.getContext('2d');
      canvas.style = 'opacity:0;position:absolute;z-index:-1;top: -100000000;left:-1000000000; margin-top:-1000000000;margin-left:-1000000000;';
      (document.body || document.documentElement).appendChild(canvas);
  
      this.disableLogs = false;
      this.frameInterval = 10;
  
      this.width = 360;
      this.height = 240;
  
      // use gain node to prevent echo
      this.useGainNode = true;
  
      var self = this;
  
      // _____________________________
      // Cross-Browser-Declarations.js
  
      // WebAudio API representer
      var AudioContext = window.AudioContext;
  
      if (typeof AudioContext === 'undefined') {
          if (typeof webkitAudioContext !== 'undefined') {
              /*global AudioContext:true */
              AudioContext = webkitAudioContext;
          }
  
          if (typeof mozAudioContext !== 'undefined') {
              /*global AudioContext:true */
              AudioContext = mozAudioContext;
          }
      }
  
      /*jshint -W079 */
      var URL = window.URL;
  
      if (typeof URL === 'undefined' && typeof webkitURL !== 'undefined') {
          /*global URL:true */
          URL = webkitURL;
      }
  
      if (typeof navigator !== 'undefined' && typeof navigator.getUserMedia === 'undefined') { // maybe window.navigator?
          if (typeof navigator.webkitGetUserMedia !== 'undefined') {
              navigator.getUserMedia = navigator.webkitGetUserMedia;
          }
  
          if (typeof navigator.mozGetUserMedia !== 'undefined') {
              navigator.getUserMedia = navigator.mozGetUserMedia;
          }
      }
  
      var MediaStream = window.MediaStream;
  
      if (typeof MediaStream === 'undefined' && typeof webkitMediaStream !== 'undefined') {
          MediaStream = webkitMediaStream;
      }
  
      /*global MediaStream:true */
      if (typeof MediaStream !== 'undefined') {
          if (!('getVideoTracks' in MediaStream.prototype)) {
              MediaStream.prototype.getVideoTracks = function() {
                  if (!this.getTracks) {
                      return [];
                  }
  
                  var tracks = [];
                  this.getTracks.forEach(function(track) {
                      if (track.kind.toString().indexOf('video') !== -1) {
                          tracks.push(track);
                      }
                  });
                  return tracks;
              };
  
              MediaStream.prototype.getAudioTracks = function() {
                  if (!this.getTracks) {
                      return [];
                  }
  
                  var tracks = [];
                  this.getTracks.forEach(function(track) {
                      if (track.kind.toString().indexOf('audio') !== -1) {
                          tracks.push(track);
                      }
                  });
                  return tracks;
              };
          }
  
          // override "stop" method for all browsers
          if (typeof MediaStream.prototype.stop === 'undefined') {
              MediaStream.prototype.stop = function() {
                  this.getTracks().forEach(function(track) {
                      track.stop();
                  });
              };
          }
      }
  
      var Storage = {};
  
      if (typeof AudioContext !== 'undefined') {
          Storage.AudioContext = AudioContext;
      } else if (typeof webkitAudioContext !== 'undefined') {
          Storage.AudioContext = webkitAudioContext;
      }
  
      this.startDrawingFrames = function() {
          drawVideosToCanvas();
      };
  
      function drawVideosToCanvas() {
          if (isStopDrawingFrames) {
              return;
          }
  
          var videosLength = videos.length;
  
          var fullcanvas = false;
          var remaining = [];
          videos.forEach(function(video) {
              if (!video.stream) {
                  video.stream = {};
              }
  
              if (video.stream.fullcanvas) {
                  fullcanvas = video;
              } else {
                  remaining.push(video);
              }
          });
  
          if (fullcanvas) {
              canvas.width = fullcanvas.stream.width;
              canvas.height = fullcanvas.stream.height;
          } else if (remaining.length) {
              canvas.width = videosLength > 1 ? remaining[0].width * 2 : remaining[0].width;
  
              var height = 1;
              if (videosLength === 3 || videosLength === 4) {
                  height = 2;
              }
              if (videosLength === 5 || videosLength === 6) {
                  height = 3;
              }
              if (videosLength === 7 || videosLength === 8) {
                  height = 4;
              }
              if (videosLength === 9 || videosLength === 10) {
                  height = 5;
              }
              canvas.height = remaining[0].height * height;
          } else {
              canvas.width = self.width || 360;
              canvas.height = self.height || 240;
          }
  
          if (fullcanvas && fullcanvas instanceof HTMLVideoElement) {
              drawImage(fullcanvas);
          }
  
          remaining.forEach(function(video, idx) {
              drawImage(video, idx);
          });
  
          setTimeout(drawVideosToCanvas, self.frameInterval);
      }
  
      function drawImage(video, idx) {
          if (isStopDrawingFrames) {
              return;
          }
  
          var x = 0;
          var y = 0;
          var width = video.width;
          var height = video.height;
  
          if (idx === 1) {
              x = video.width;
          }
  
          if (idx === 2) {
              y = video.height;
          }
  
          if (idx === 3) {
              x = video.width;
              y = video.height;
          }
  
          if (idx === 4) {
              y = video.height * 2;
          }
  
          if (idx === 5) {
              x = video.width;
              y = video.height * 2;
          }
  
          if (idx === 6) {
              y = video.height * 3;
          }
  
          if (idx === 7) {
              x = video.width;
              y = video.height * 3;
          }
  
          if (typeof video.stream.left !== 'undefined') {
              x = video.stream.left;
          }
  
          if (typeof video.stream.top !== 'undefined') {
              y = video.stream.top;
          }
  
          if (typeof video.stream.width !== 'undefined') {
              width = video.stream.width;
          }
  
          if (typeof video.stream.height !== 'undefined') {
              height = video.stream.height;
          }
  
          context.drawImage(video, x, y, width, height);
  
          if (typeof video.stream.onRender === 'function') {
              video.stream.onRender(context, x, y, width, height, idx);
          }
      }
  
      function getMixedStream() {
          isStopDrawingFrames = false;
          var mixedVideoStream = getMixedVideoStream();
  
          var mixedAudioStream = getMixedAudioStream();
          if (mixedAudioStream) {
              mixedAudioStream.getAudioTracks().forEach(function(track) {
                  mixedVideoStream.addTrack(track);
              });
          }
  
          var fullcanvas;
          arrayOfMediaStreams.forEach(function(stream) {
              if (stream.fullcanvas) {
                  fullcanvas = true;
              }
          });
  
          return mixedVideoStream;
      }
  
      function getMixedVideoStream() {
          resetVideoStreams();
  
          var capturedStream;
  
          if ('captureStream' in canvas) {
              capturedStream = canvas.captureStream();
          } else if ('mozCaptureStream' in canvas) {
              capturedStream = canvas.mozCaptureStream();
          } else if (!self.disableLogs) {
              console.error('Upgrade to latest Chrome or otherwise enable this flag: chrome://flags/#enable-experimental-web-platform-features');
          }
  
          var videoStream = new MediaStream();
  
          capturedStream.getVideoTracks().forEach(function(track) {
              videoStream.addTrack(track);
          });
  
          canvas.stream = videoStream;
  
          return videoStream;
      }
  
      function getMixedAudioStream() {
          // via: @pehrsons
          if (!Storage.AudioContextConstructor) {
              Storage.AudioContextConstructor = new Storage.AudioContext();
          }
  
          self.audioContext = Storage.AudioContextConstructor;
  
          self.audioSources = [];
  
          if (self.useGainNode === true) {
              self.gainNode = self.audioContext.createGain();
              self.gainNode.connect(self.audioContext.destination);
              self.gainNode.gain.value = 0; // don't hear self
          }
  
          var audioTracksLength = 0;
          arrayOfMediaStreams.forEach(function(stream) {
              if (!stream.getAudioTracks().length) {
                  return;
              }
  
              audioTracksLength++;
  
              var audioSource = self.audioContext.createMediaStreamSource(stream);
  
              if (self.useGainNode === true) {
                  audioSource.connect(self.gainNode);
              }
  
              self.audioSources.push(audioSource);
          });
  
          if (!audioTracksLength) {
              return;
          }
  
          self.audioDestination = self.audioContext.createMediaStreamDestination();
          self.audioSources.forEach(function(audioSource) {
              audioSource.connect(self.audioDestination);
          });
          return self.audioDestination.stream;
      }
  
      function getVideo(stream) {
          var video = document.createElement('video');
  
          if ('srcObject' in video) {
              video.srcObject = stream;
          } else {
              video.src = URL.createObjectURL(stream);
          }
  
          video.muted = true;
          video.volume = 0;
  
          video.width = stream.width || self.width || 360;
          video.height = stream.height || self.height || 240;
  
          video.play();
  
          return video;
      }
  
      this.appendStreams = function(streams) {
          if (!streams) {
              throw 'First parameter is required.';
          }
  
          if (!(streams instanceof Array)) {
              streams = [streams];
          }
  
          arrayOfMediaStreams.concat(streams);
  
          streams.forEach(function(stream) {
              if (stream.getVideoTracks().length) {
                  var video = getVideo(stream);
                  video.stream = stream;
                  videos.push(video);
              }
  
              if (stream.getAudioTracks().length && self.audioContext) {
                  var audioSource = self.audioContext.createMediaStreamSource(stream);
                  audioSource.connect(self.audioDestination);
                  self.audioSources.push(audioSource);
              }
          });
      };
  
      this.releaseStreams = function() {
          videos = [];
          isStopDrawingFrames = true;
  
          if (self.gainNode) {
              self.gainNode.disconnect();
              self.gainNode = null;
          }
  
          if (self.audioSources.length) {
              self.audioSources.forEach(function(source) {
                  source.disconnect();
              });
              self.audioSources = [];
          }
  
          if (self.audioDestination) {
              self.audioDestination.disconnect();
              self.audioDestination = null;
          }
  
          self.audioContext = null;
  
          context.clearRect(0, 0, canvas.width, canvas.height);
  
          if (canvas.stream) {
              canvas.stream.stop();
              canvas.stream = null;
          }
      };
  
      this.resetVideoStreams = function(streams) {
          if (streams && !(streams instanceof Array)) {
              streams = [streams];
          }
  
          resetVideoStreams(streams);
      };
  
      function resetVideoStreams(streams) {
          videos = [];
          streams = streams || arrayOfMediaStreams;
  
          // via: @adrian-ber
          streams.forEach(function(stream) {
              if (!stream.getVideoTracks().length) {
                  return;
              }
  
              var video = getVideo(stream);
              video.stream = stream;
              videos.push(video);
          });
      }
  
      // for debugging
      this.name = 'MultiStreamsMixer';
      this.toString = function() {
          return this.name;
      };
  
      this.getMixedStream = getMixedStream;
  
  }
  