text: var AudioContext = window.AudioContext || window.webkitAudioContext
  
  function createWorker (fn) {
    var js = fn
      .toString()
      .replace(/^function\s*\(\)\s*{/, '')
      .replace(/}$/, '')
    var blob = new Blob([js])
    return new Worker(URL.createObjectURL(blob))
  }
  
  function error (method) {
    var event = new Event('error')
    event.data = new Error('Wrong state for ' + method)
    return event
  }
  
  var context, processor
  
  /**
   * Audio Recorder with MediaRecorder API.
   *
   * @param {MediaStream} stream The audio stream to record.
   *
   * @example
   * navigator.mediaDevices.getUserMedia({ audio: true }).then(function (stream) {
   *   var recorder = new MediaRecorder(stream)
   * })
   *
   * @class
   */
  function MediaRecorder (stream) {
    /**
     * The `MediaStream` passed into the constructor.
     * @type {MediaStream}
     */
    this.stream = stream
  
    /**
     * The current state of recording process.
     * @type {"inactive"|"recording"|"paused"}
     */
    this.state = 'inactive'
  
    this.em = document.createDocumentFragment()
    this.encoder = createWorker(MediaRecorder.encoder)
  
    var recorder = this
    this.encoder.addEventListener('message', function (e) {
      var event = new Event('dataavailable')
      event.data = new Blob([e.data], { type: recorder.mimeType })
      recorder.em.dispatchEvent(event)
      if (recorder.state === 'inactive') {
        recorder.em.dispatchEvent(new Event('stop'))
      }
    })
  }
  
  MediaRecorder.prototype = {
    /**
     * The MIME type that is being used for recording.
     * @type {string}
     */
    mimeType: 'audio/wav',
  
    /**
     * Begins recording media.
     *
     * @param {number} [timeslice] The milliseconds to record into each `Blob`.
     *                             If this parameter isn’t included, single `Blob`
     *                             will be recorded.
     *
     * @return {undefined}
     *
     * @example
     * recordButton.addEventListener('click', function () {
     *   recorder.start()
     * })
     */
    start: function start (timeslice) {
      if (this.state !== 'inactive') {
        return this.em.dispatchEvent(error('start'))
      }
  
      this.state = 'recording'
  
      if (!context) {
        context = new AudioContext()
      }
      this.clone = this.stream.clone()
      var input = context.createMediaStreamSource(this.clone)
  
      if (!processor) {
        processor = context.createScriptProcessor(2048, 1, 1)
      }
  
      var recorder = this
      processor.onaudioprocess = function (e) {
        if (recorder.state === 'recording') {
          recorder.encoder.postMessage([
            'encode', e.inputBuffer.getChannelData(0)
          ])
        }
      }
  
      input.connect(processor)
      processor.connect(context.destination)
  
      this.em.dispatchEvent(new Event('start'))
  
      if (timeslice) {
        this.slicing = setInterval(function () {
          if (recorder.state === 'recording') recorder.requestData()
        }, timeslice)
      }
  
      return undefined
    },
  
    /**
     * Stop media capture and raise `dataavailable` event with recorded data.
     *
     * @return {undefined}
     *
     * @example
     * finishButton.addEventListener('click', function () {
     *   recorder.stop()
     * })
     */
    stop: function stop () {
      if (this.state === 'inactive') {
        return this.em.dispatchEvent(error('stop'))
      }
  
      this.requestData()
      this.state = 'inactive'
      this.clone.getTracks().forEach(function (track) {
        track.stop()
      })
      return clearInterval(this.slicing)
    },
  
    /**
     * Pauses recording of media streams.
     *
     * @return {undefined}
     *
     * @example
     * pauseButton.addEventListener('click', function () {
     *   recorder.pause()
     * })
     */
    pause: function pause () {
      if (this.state !== 'recording') {
        return this.em.dispatchEvent(error('pause'))
      }
  
      this.state = 'paused'
      return this.em.dispatchEvent(new Event('pause'))
    },
  
    /**
     * Resumes media recording when it has been previously paused.
     *
     * @return {undefined}
     *
     * @example
     * resumeButton.addEventListener('click', function () {
     *   recorder.resume()
     * })
     */
    resume: function resume () {
      if (this.state !== 'paused') {
        return this.em.dispatchEvent(error('resume'))
      }
  
      this.state = 'recording'
      return this.em.dispatchEvent(new Event('resume'))
    },
  
    /**
     * Raise a `dataavailable` event containing the captured media.
     *
     * @return {undefined}
     *
     * @example
     * this.on('nextData', function () {
     *   recorder.requestData()
     * })
     */
    requestData: function requestData () {
      if (this.state === 'inactive') {
        return this.em.dispatchEvent(error('requestData'))
      }
  
      return this.encoder.postMessage(['dump', context.sampleRate])
    },
  
    /**
     * Add listener for specified event type.
     *
     * @param {"start"|"stop"|"pause"|"resume"|"dataavailable"|"error"}
     * type Event type.
     * @param {function} listener The listener function.
     *
     * @return {undefined}
     *
     * @example
     * recorder.addEventListener('dataavailable', function (e) {
     *   audio.src = URL.createObjectURL(e.data)
     * })
     */
    addEventListener: function addEventListener () {
      this.em.addEventListener.apply(this.em, arguments)
    },
  
    /**
     * Remove event listener.
     *
     * @param {"start"|"stop"|"pause"|"resume"|"dataavailable"|"error"}
     * type Event type.
     * @param {function} listener The same function used in `addEventListener`.
     *
     * @return {undefined}
     */
    removeEventListener: function removeEventListener () {
      this.em.removeEventListener.apply(this.em, arguments)
    },
  
    /**
     * Calls each of the listeners registered for a given event.
     *
     * @param {Event} event The event object.
     *
     * @return {boolean} Is event was no canceled by any listener.
     */
    dispatchEvent: function dispatchEvent () {
      this.em.dispatchEvent.apply(this.em, arguments)
    }
  }
  
  /**
   * Returns `true` if the MIME type specified is one the polyfill can record.
   *
   * This polyfill supports `audio/wav` and `audio/mpeg`.
   *
   * @param {string} mimeType The mimeType to check.
   *
   * @return {boolean} `true` on `audio/wav` and `audio/mpeg` MIME type.
   */
  MediaRecorder.isTypeSupported = function isTypeSupported (mimeType) {
    return MediaRecorder.prototype.mimeType === mimeType
  }
  
  /**
   * `true` if MediaRecorder can not be polyfilled in the current browser.
   * @type {boolean}
   *
   * @example
   * if (MediaRecorder.notSupported) {
   *   showWarning('Audio recording is not supported in this browser')
   * }
   */
  MediaRecorder.notSupported = !navigator.mediaDevices || !AudioContext
  
  /**
   * Converts RAW audio buffer to compressed audio files.
   * It will be loaded to Web Worker.
   * By default, WAVE encoder will be used.
   * @type {function}
   *
   * @example
   * MediaRecorder.prototype.mimeType = 'audio/ogg'
   * MediaRecorder.encoder = oggEncoder
   */
  MediaRecorder.encoder = require('./wave-encoder')
  
  module.exports = MediaRecorder
  