import { DiagnosticError } from '../errors'; import { MediaStreamRecorder } from '../types'; import { Encoder } from './encoder'; /** * The [[AudioRecorder]] allows cross browser recording of audio from an input MediaStream. * It uses the native MediaStream Recording APIs if available, else, it process raw audio data * and converts it to a blob. * @internalapi */ export class AudioRecorder { /** * The raw audio data captured during the test */ private _audioData: MediaStreamRecorder.AudioData[] = []; /** * The MediaRecorder instance to be used for capturing audio */ private _mediaRecorder: MediaStreamRecorder.MediaRecorder; /** * Promise handle after calling .stop() */ private _stopPromise: Promise | null = null; /** * The source input stream */ private _stream: MediaStream; /** * The resuling object url that can be used for audio playback */ private _url: string = ''; /** * Construct an [[AudioRecorder]] instance and will start the recording immediately. * @constructor * @param options */ constructor(options: AudioRecorder.ExtendedOptions) { const factory = options.MediaRecorderFactory ?? (window as any).MediaRecorder ?? Encoder; this._stream = options.stream.clone(); this._mediaRecorder = new factory(this._stream, options.audioContext); this._mediaRecorder.ondataavailable = (e: MediaStreamRecorder.DataEvent) => this._audioData.push(e.data); this._mediaRecorder.start(); } /** * Stops the recording process. * If successful, the `.url` property will be populated. */ stop(): Promise { if (this._stopPromise) { return Promise.reject(new DiagnosticError(undefined, 'MediaRecorder has already stopped')); } this._stopPromise = new Promise((resolve, reject) => { this._mediaRecorder.onstop = () => { try { this._stream.getTracks().forEach((track: MediaStreamTrack) => track.stop()); this._generateObjectUrl(); } catch (ex) { reject(new DiagnosticError(ex, 'Unable to generate Object URL')); return; } resolve(); }; this._mediaRecorder.stop(); }); return this._stopPromise; } /** * Generates the object url that can be used for audio playback from raw audio data */ private _generateObjectUrl(): void { // Select default browser mime type if it exists. // Otherwise, use wav for faster and simple encoding. const type: string = this._mediaRecorder && this._mediaRecorder.mimeType ? this._mediaRecorder.mimeType : 'audio/wav'; const blob = new Blob(this._audioData, { type }); this._url = URL.createObjectURL(blob); this._audioData = []; } /** * The resuling object url that can be used for audio playback */ get url(): string { return this._url; } } /** * @internalapi */ export namespace AudioRecorder { /** * Options that may be passed to [[AudioRecorder]] constructor for internal testing. * @internalapi */ export interface ExtendedOptions extends Options { /** * The MediaRecorder class to use for testing */ MediaRecorderFactory?: any; } /** * Options passed to [[AudioRecorder]] constructor. * @internalapi */ export interface Options { /** * The AudioContext instance to use */ audioContext: AudioContext; /** * The source input stream */ stream: MediaStream; } }