text: // _________________
  // WhammyRecorder.js
  
  /**
   * WhammyRecorder is a standalone class used by {@link RecordRTC} to bring video recording in Chrome. It runs top over {@link Whammy}.
   * @summary Video recording feature in Chrome.
   * @license {@link https://github.com/muaz-khan/RecordRTC#license|MIT}
   * @author {@link http://www.MuazKhan.com|Muaz Khan}
   * @typedef WhammyRecorder
   * @class
   * @example
   * var recorder = new WhammyRecorder(mediaStream);
   * recorder.record();
   * recorder.stop(function(blob) {
   *     video.src = URL.createObjectURL(blob);
   * });
   * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code}
   * @param {MediaStream} mediaStream - MediaStream object fetched using getUserMedia API or generated using captureStreamUntilEnded or WebAudio API.
   * @param {object} config - {disableLogs: true, initCallback: function, video: HTMLVideoElement, etc.}
   */
  
  function WhammyRecorder(mediaStream, config) {
  
      config = config || {};
  
      if (!config.frameInterval) {
          config.frameInterval = 10;
      }
  
      if (!config.disableLogs) {
          console.log('Using frames-interval:', config.frameInterval);
      }
  
      /**
       * This method records video.
       * @method
       * @memberof WhammyRecorder
       * @example
       * recorder.record();
       */
      this.record = function() {
          if (!config.width) {
              config.width = 320;
          }
  
          if (!config.height) {
              config.height = 240;
          }
  
          if (!config.video) {
              config.video = {
                  width: config.width,
                  height: config.height
              };
          }
  
          if (!config.canvas) {
              config.canvas = {
                  width: config.width,
                  height: config.height
              };
          }
  
          canvas.width = config.canvas.width;
          canvas.height = config.canvas.height;
  
          context = canvas.getContext('2d');
  
          // setting defaults
          if (config.video && config.video instanceof HTMLVideoElement) {
              video = config.video.cloneNode();
  
              if (config.initCallback) {
                  config.initCallback();
              }
          } else {
              video = document.createElement('video');
  
              if (typeof video.srcObject !== 'undefined') {
                  video.srcObject = mediaStream;
              } else {
                  video.src = URL.createObjectURL(mediaStream);
              }
  
              video.onloadedmetadata = function() { // "onloadedmetadata" may NOT work in FF?
                  if (config.initCallback) {
                      config.initCallback();
                  }
              };
  
              video.width = config.video.width;
              video.height = config.video.height;
          }
  
          video.muted = true;
          video.play();
  
          lastTime = new Date().getTime();
          whammy = new Whammy.Video();
  
          if (!config.disableLogs) {
              console.log('canvas resolutions', canvas.width, '*', canvas.height);
              console.log('video width/height', video.width || canvas.width, '*', video.height || canvas.height);
          }
  
          drawFrames(config.frameInterval);
      };
  
      /**
       * Draw and push frames to Whammy
       * @param {integer} frameInterval - set minimum interval (in milliseconds) between each time we push a frame to Whammy
       */
      function drawFrames(frameInterval) {
          frameInterval = typeof frameInterval !== 'undefined' ? frameInterval : 10;
  
          var duration = new Date().getTime() - lastTime;
          if (!duration) {
              return setTimeout(drawFrames, frameInterval, frameInterval);
          }
  
          if (isPausedRecording) {
              lastTime = new Date().getTime();
              return setTimeout(drawFrames, 100);
          }
  
          // via #206, by Jack i.e. @Seymourr
          lastTime = new Date().getTime();
  
          if (video.paused) {
              // via: https://github.com/muaz-khan/WebRTC-Experiment/pull/316
              // Tweak for Android Chrome
              video.play();
          }
  
          context.drawImage(video, 0, 0, canvas.width, canvas.height);
          whammy.frames.push({
              duration: duration,
              image: canvas.toDataURL('image/webp')
          });
  
          if (!isStopDrawing) {
              setTimeout(drawFrames, frameInterval, frameInterval);
          }
      }
  
      function asyncLoop(o) {
          var i = -1,
              length = o.length;
  
          var loop = function() {
              i++;
              if (i === length) {
                  o.callback();
                  return;
              }
              o.functionToLoop(loop, i);
          };
          loop(); //init
      }
  
  
      /**
       * remove black frames from the beginning to the specified frame
       * @param {Array} _frames - array of frames to be checked
       * @param {number} _framesToCheck - number of frame until check will be executed (-1 - will drop all frames until frame not matched will be found)
       * @param {number} _pixTolerance - 0 - very strict (only black pixel color) ; 1 - all
       * @param {number} _frameTolerance - 0 - very strict (only black frame color) ; 1 - all
       * @returns {Array} - array of frames
       */
      // pull#293 by @volodalexey
      function dropBlackFrames(_frames, _framesToCheck, _pixTolerance, _frameTolerance, callback) {
          var localCanvas = document.createElement('canvas');
          localCanvas.width = canvas.width;
          localCanvas.height = canvas.height;
          var context2d = localCanvas.getContext('2d');
          var resultFrames = [];
  
          var checkUntilNotBlack = _framesToCheck === -1;
          var endCheckFrame = (_framesToCheck && _framesToCheck > 0 && _framesToCheck <= _frames.length) ?
              _framesToCheck : _frames.length;
          var sampleColor = {
              r: 0,
              g: 0,
              b: 0
          };
          var maxColorDifference = Math.sqrt(
              Math.pow(255, 2) +
              Math.pow(255, 2) +
              Math.pow(255, 2)
          );
          var pixTolerance = _pixTolerance && _pixTolerance >= 0 && _pixTolerance <= 1 ? _pixTolerance : 0;
          var frameTolerance = _frameTolerance && _frameTolerance >= 0 && _frameTolerance <= 1 ? _frameTolerance : 0;
          var doNotCheckNext = false;
  
          asyncLoop({
              length: endCheckFrame,
              functionToLoop: function(loop, f) {
                  var matchPixCount, endPixCheck, maxPixCount;
  
                  var finishImage = function() {
                      if (!doNotCheckNext && maxPixCount - matchPixCount <= maxPixCount * frameTolerance) {
                          // console.log('removed black frame : ' + f + ' ; frame duration ' + _frames[f].duration);
                      } else {
                          // console.log('frame is passed : ' + f);
                          if (checkUntilNotBlack) {
                              doNotCheckNext = true;
                          }
                          resultFrames.push(_frames[f]);
                      }
                      loop();
                  };
  
                  if (!doNotCheckNext) {
                      var image = new Image();
                      image.onload = function() {
                          context2d.drawImage(image, 0, 0, canvas.width, canvas.height);
                          var imageData = context2d.getImageData(0, 0, canvas.width, canvas.height);
                          matchPixCount = 0;
                          endPixCheck = imageData.data.length;
                          maxPixCount = imageData.data.length / 4;
  
                          for (var pix = 0; pix < endPixCheck; pix += 4) {
                              var currentColor = {
                                  r: imageData.data[pix],
                                  g: imageData.data[pix + 1],
                                  b: imageData.data[pix + 2]
                              };
                              var colorDifference = Math.sqrt(
                                  Math.pow(currentColor.r - sampleColor.r, 2) +
                                  Math.pow(currentColor.g - sampleColor.g, 2) +
                                  Math.pow(currentColor.b - sampleColor.b, 2)
                              );
                              // difference in color it is difference in color vectors (r1,g1,b1) <=> (r2,g2,b2)
                              if (colorDifference <= maxColorDifference * pixTolerance) {
                                  matchPixCount++;
                              }
                          }
                          finishImage();
                      };
                      image.src = _frames[f].image;
                  } else {
                      finishImage();
                  }
              },
              callback: function() {
                  resultFrames = resultFrames.concat(_frames.slice(endCheckFrame));
  
                  if (resultFrames.length <= 0) {
                      // at least one last frame should be available for next manipulation
                      // if total duration of all frames will be < 1000 than ffmpeg doesn't work well...
                      resultFrames.push(_frames[_frames.length - 1]);
                  }
                  callback(resultFrames);
              }
          });
      }
  
      var isStopDrawing = false;
  
      /**
       * This method stops recording video.
       * @param {function} callback - Callback function, that is used to pass recorded blob back to the callee.
       * @method
       * @memberof WhammyRecorder
       * @example
       * recorder.stop(function(blob) {
       *     video.src = URL.createObjectURL(blob);
       * });
       */
      this.stop = function(callback) {
          isStopDrawing = true;
  
          var _this = this;
          // analyse of all frames takes some time!
          setTimeout(function() {
              // e.g. dropBlackFrames(frames, 10, 1, 1) - will cut all 10 frames
              // e.g. dropBlackFrames(frames, 10, 0.5, 0.5) - will analyse 10 frames
              // e.g. dropBlackFrames(frames, 10) === dropBlackFrames(frames, 10, 0, 0) - will analyse 10 frames with strict black color
              dropBlackFrames(whammy.frames, -1, null, null, function(frames) {
                  whammy.frames = frames;
  
                  // to display advertisement images!
                  if (config.advertisement && config.advertisement.length) {
                      whammy.frames = config.advertisement.concat(whammy.frames);
                  }
  
                  /**
                   * @property {Blob} blob - Recorded frames in video/webm blob.
                   * @memberof WhammyRecorder
                   * @example
                   * recorder.stop(function() {
                   *     var blob = recorder.blob;
                   * });
                   */
                  whammy.compile(function(blob) {
                      _this.blob = blob;
  
                      if (_this.blob.forEach) {
                          _this.blob = new Blob([], {
                              type: 'video/webm'
                          });
                      }
  
                      if (callback) {
                          callback(_this.blob);
                      }
                  });
              });
          }, 10);
      };
  
      var isPausedRecording = false;
  
      /**
       * This method pauses the recording process.
       * @method
       * @memberof WhammyRecorder
       * @example
       * recorder.pause();
       */
      this.pause = function() {
          isPausedRecording = true;
      };
  
      /**
       * This method resumes the recording process.
       * @method
       * @memberof WhammyRecorder
       * @example
       * recorder.resume();
       */
      this.resume = function() {
          isPausedRecording = false;
  
          if (isStopDrawing) {
              this.record();
          }
      };
  
      /**
       * This method resets currently recorded data.
       * @method
       * @memberof WhammyRecorder
       * @example
       * recorder.clearRecordedData();
       */
      this.clearRecordedData = function() {
          this.pause();
          whammy.frames = [];
      };
  
      var canvas = document.createElement('canvas');
      var context = canvas.getContext('2d');
  
      var video;
      var lastTime;
      var whammy;
  }
  
  if (typeof RecordRTC !== 'undefined') {
      RecordRTC.WhammyRecorder = WhammyRecorder;
  }
  