src/media/StreamRecorder.js
import {EventEmitter} from '../eventemitter';
import {StreamRecorderStatus} from './StreamRecorderStatus';
/**
* @typedef {Object} StopEvent
* @property {Blob} data A Blob with the recorded data. For more information about
* how to process a Blob you can check
* [Sending and receiving Binary Data](https://developer.mozilla.org/en-US/docs/Web/API/XMLHttpRequest/Sending_and_Receiving_Binary_Data)
* or [FileSaver.js](https://github.com/eligrey/FileSaver.js/)
* @property {number} startTime Timestamp when the recording was started
* @property {number} stopTime Timestamp when the recording was stoped
* @property {StreamRecorder} target The StreamRecorder object
*/
/**
* This class allows to record a stream. Instances of this class are normally obtained using
* {@link Call#createLocalStreamRecorders} or {@link Call#createRemoteStreamRecorders}
*
* Note that this class uses internally a
* [MediaRecorder](https://developer.mozilla.org/en-US/docs/Web/API/MediaRecorder).
* According to its [specification](https://w3c.github.io/mediacapture-record/MediaRecorder.html),
* there is not a list of supported container formats, audio codecs or video codecs that must
* be supported for every browser and the default value for them is platform-specific.
*
* ## Events
*
* This class contains an instance of {EventEmitter} that emits the next events:
*
* - **`start`** is emitted after the recording is started.
* - **`pause`** is emitted every time the recording is paused.
* - **`resume`** is emitted every time the recording is resumed.
* - **`stop`** is emitted after the recording is stoped. It contains a {@link StopEvent}
*
* You can check EventEmitter2 documentation for additional information about
* adding and removing listeners for this events events.
*
* @see https://github.com/asyncly/EventEmitter2#api
*
* @example <caption>Basic usage</caption>
* let streamRecorder = call.remoteStreams[0].createStreamRecorder();
* streamRecorder.start();
* streamRecorder.emitter.on('stop', (data) => {
* console.log(data);
* // Here you can download the data, upload to a server..
* });
* streamRecorder.stop();
*/
export class StreamRecorder {
/**
* @param {MediaStream} mediaStream The media stream that is going to be recorded
*/
constructor(mediaStream) {
/**
* @private
* @type {MediaStream}
*/
this._mediaStream = mediaStream;
/**
* @private
* @type {MediaRecorder}
*/
this._mediaRecorder = null;
/**
* @private
* @type {Blob[]}
*/
this._chunks = [];
/**
* @private
* @type {Status}
*/
this._status = StreamRecorderStatus.INACTIVE;
/**
* @type {EventEmitter}
*/
this.emitter = new EventEmitter();
/**
* Indicates if the audio of the call must be recorded
* @type {boolean}
*/
this.isAudioRecordingEnabled = true;
/**
* Indicates if the video of the call must be recorded
* @type {boolean}
*/
this.isVideoRecordingEnabled = true;
}
/**
* @type {StreamRecorderStatus}
*/
get status() {
return this._status;
}
/**
* Starts the recording
* @returns {undefined}
*/
start() {
if (this._mediaRecorder !== null) {
throw Error('The StreamRecorder object must be stopped before starting it again');
}
let mediaStream;
switch (true) {
case this.isAudioRecordingEnabled && this.isVideoRecordingEnabled:
mediaStream = this._mediaStream;
break;
case this.isAudioRecordingEnabled && !this.isVideoRecordingEnabled:
mediaStream = window.MediaStream(this._mediaStream.getAudioTracks());
break;
case !this.isAudioRecordingEnabled && this.isVideoRecordingEnabled:
mediaStream = window.MediaStream(this._mediaStream.getVideoTracks());
break;
default:
throw Error('At least audio or video recording must be enabled');
}
this._mediaRecorder = new window.MediaRecorder(mediaStream);
this._mediaRecorder.onstart = () => {
this._status = StreamRecorderStatus.RECORDING;
this._recordingStartTime = Date.now();
this.emitter.emit('start');
};
this._mediaRecorder.onstop = () => {
this._status = StreamRecorderStatus.INACTIVE;
this.emitter.emit('stop', {
data: new Blob(this._chunks, {
type: this._chunks[0].type,
}),
startTime: this._recordingStartTime,
endTime: Date.now(),
target: this,
});
this._chunks = [];
};
this._mediaRecorder.ondataavailable = (event) => {
this._chunks.push(event.data);
};
this._mediaRecorder.onpause = () => {
this._status = StreamRecorderStatus.PAUSED;
this.emitter.emit('pause');
};
this._mediaRecorder.onresume = () => {
this._status = StreamRecorderStatus.RECORDING;
this.emitter.emit('resume');
};
this._mediaRecorder.onerror = (event) => {
this.emitter.emit('error', event);
};
this._mediaRecorder.start();
}
/**
* Stops the recording
*
* This method can only be called after the {@link MediaStream#start} method has between
* called. The stop event is received after calling this method with the recorded data.
* @returns {undefined}
*/
stop() {
if (this._mediaRecorder === null) {
throw Error('The StreamRecorder object must be started before');
}
this._mediaRecorder.stop();
this._mediaRecorder = null;
}
/**
* Pauses the recording
* @returns {undefined}
*/
pause() {
if (this._mediaRecorder === null) {
throw Error('The StreamRecorder object must be started before');
}
this._mediaRecorder.pause();
}
/**
* Resumes the recording
* @returns {undefined}
*/
resume() {
if (this._mediaRecorder === null) {
throw Error('The StreamRecorder object must be started before');
}
this._mediaRecorder.resume();
}
}