All files / src/tasks audioRecorder.js

58.18% Statements 64/110
100% Branches 1/1
10% Functions 1/10
58.18% Lines 64/110

Press n or j to go to the next uncovered block, b, p or k for the previous block.

1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 1111x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x 1x                 1x 1x 1x 1x 1x 1x 1x     1x 1x 1x 1x 1x 1x 1x             1x 1x             1x 1x 1x 1x 1x 1x               1x 1x 1x 1x 1x 1x                               1x 1x 1x 1x 1x 1x     1x 1x 1x  
/**
 * @class provides a simple interface for recording audio from a microphone
 * using the Media Recorder API.
 */
class AudioRecorder {
  /** @private */
  #mediaRecorder;
 
  /** @private */
  #recordedChunks = [];
 
  /** @private */
  #audioBlob;
 
  /** @private */
  #audioContext;
 
  /** @private */
  #arrayBuffer;
 
  /** @private */
  #recordedSignals = [];
 
  /** @private */
  sinkSamplingRate;
 
  #saveRecording = async () => {
    this.#arrayBuffer = await this.#audioBlob.arrayBuffer();

    // Convert array buffer into audio buffer
    await this.#audioContext.decodeAudioData(this.#arrayBuffer, audioBuffer => {
      const data = audioBuffer.getChannelData(0);
      this.#recordedSignals.push(data);
    });
  };
 
  /**
   * Event listener triggered when data is available in the media recorder.
   * @private
   * @param {*} e - The event object.
   */
  #onRecorderDataAvailable = e => {
    if (e.data && e.data.size > 0) this.#recordedChunks.push(e.data);
  };
 
  /**
   * Method to create a media recorder object and set up event listeners.
   * @private
   * @param {MediaStream} stream - The stream of audio from the Listener.
   */
  #setMediaRecorder = stream => {
    // Create a new MediaRecorder object
    this.#mediaRecorder = new MediaRecorder(stream);

    // Add event listeners
    this.#mediaRecorder.ondataavailable = e => this.#onRecorderDataAvailable(e);
  };
 
  #setAudioContext = () => {
    this.#audioContext = new (window.AudioContext ||
      window.webkitAudioContext ||
      window.audioContext)({
      sampleRate: this.sinkSamplingRate,
    });
  };
 
  /**
   * Public method to start the recording process.
   * @param {MediaStream} stream - The stream of audio from the Listener.
   */
  startRecording = async stream => {
    // Set up media recorder if needed
    // await this.#applyTrackContraints(stream);
    this.#setAudioContext();
    if (!this.#mediaRecorder) this.#setMediaRecorder(stream);
    this.#recordedChunks = [];
    this.#mediaRecorder.start();
  };
 
  /**
   * Method to stop the recording process.
   * @public
   */
  stopRecording = async () => {
    // Stop the media recorder, and wait for the data to be available
    await new Promise(resolve => {
      this.#mediaRecorder.onstop = () => {
        // when the stop event is triggered, resolve the promise
        this.#audioBlob = new Blob(this.#recordedChunks, {
          type: 'audio/wav; codecs=opus',
        });
        resolve(this.#audioBlob);
      };
      // call stop
      this.#mediaRecorder.stop();
    });
    // Now that we have data, save it
    await this.#saveRecording();
  };
 
  getLastRecordedSignal = () => this.#recordedSignals[this.#recordedSignals.length - 1];
 
  getAllRecordedSignals = () => this.#recordedSignals;
 
  setSinkSamplingRate = sinkSamplingRate => {
    this.sinkSamplingRate = sinkSamplingRate;
  };
}
 
export default AudioRecorder;