123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318 |
- /* global MediaRecorder, MediaStream */
-
- const RecordingResult = require('./recordingResult');
-
- /**
- * Possible audio formats MIME types
- */
- const AUDIO_WEBM = 'audio/webm'; // Supported in chrome
- const AUDIO_OGG = 'audio/ogg'; // Supported in firefox
-
- /**
- * A TrackRecorder object holds all the information needed for recording a
- * single JitsiTrack (either remote or local)
- * @param track The JitsiTrack the object is going to hold
- */
- const TrackRecorder = function(track) {
- // The JitsiTrack holding the stream
- this.track = track;
-
- // The MediaRecorder recording the stream
- this.recorder = null;
-
- // The array of data chunks recorded from the stream
- // acts as a buffer until the data is stored on disk
- this.data = null;
-
- // the name of the person of the JitsiTrack. This can be undefined and/or
- // not unique
- this.name = null;
-
- // the time of the start of the recording
- this.startTime = null;
- };
-
- /**
- * Starts the recording of a JitsiTrack in a TrackRecorder object.
- * This will also define the timestamp and try to update the name
- * @param trackRecorder the TrackRecorder to start
- */
- function startRecorder(trackRecorder) {
- if (trackRecorder.recorder === undefined) {
- throw new Error('Passed an object to startRecorder which is not a '
- + 'TrackRecorder object');
- }
- trackRecorder.recorder.start();
- trackRecorder.startTime = new Date();
- }
-
- /**
- * Stops the recording of a JitsiTrack in a TrackRecorder object.
- * This will also try to update the name
- * @param trackRecorder the TrackRecorder to stop
- */
- function stopRecorder(trackRecorder) {
- if (trackRecorder.recorder === undefined) {
- throw new Error('Passed an object to stopRecorder which is not a '
- + 'TrackRecorder object');
- }
- trackRecorder.recorder.stop();
- }
-
- /**
- * Determines which kind of audio recording the browser supports
- * chrome supports "audio/webm" and firefox supports "audio/ogg"
- */
- function determineCorrectFileType() {
- if (MediaRecorder.isTypeSupported(AUDIO_WEBM)) {
- return AUDIO_WEBM;
- } else if (MediaRecorder.isTypeSupported(AUDIO_OGG)) {
- return AUDIO_OGG;
- }
- throw new Error(
- 'unable to create a MediaRecorder with the right mimetype!');
- }
-
- /**
- * main exported object of the file, holding all
- * relevant functions and variables for the outside world
- * @param jitsiConference the jitsiConference which this object
- * is going to record
- */
- function AudioRecorder(jitsiConference) {
- // array of TrackRecorders, where each trackRecorder
- // holds the JitsiTrack, MediaRecorder and recorder data
- this.recorders = [];
-
- // get which file type is supported by the current browser
- this.fileType = determineCorrectFileType();
-
- // boolean flag for active recording
- this.isRecording = false;
-
- // the jitsiconference the object is recording
- this.jitsiConference = jitsiConference;
- }
-
- /**
- * Add the the exported module so that it can be accessed by other files
- */
- AudioRecorder.determineCorrectFileType = determineCorrectFileType;
-
- /**
- * Adds a new TrackRecorder object to the array.
- *
- * @param track the track potentially holding an audio stream
- */
- AudioRecorder.prototype.addTrack = function(track) {
- if (track.isAudioTrack()) {
- // create the track recorder
- const trackRecorder = this.instantiateTrackRecorder(track);
-
- // push it to the local array of all recorders
-
- this.recorders.push(trackRecorder);
-
- // update the name of the trackRecorders
- this.updateNames();
-
- // If we're already recording, immediately start recording this new
- // track.
- if (this.isRecording) {
- startRecorder(trackRecorder);
- }
- }
- };
-
- /**
- * Creates a TrackRecorder object. Also creates the MediaRecorder and
- * data array for the trackRecorder.
- * @param track the JitsiTrack holding the audio MediaStream(s)
- */
- AudioRecorder.prototype.instantiateTrackRecorder = function(track) {
- const trackRecorder = new TrackRecorder(track);
-
- // Create a new stream which only holds the audio track
- const originalStream = trackRecorder.track.getOriginalStream();
- const stream = createEmptyStream();
-
- originalStream.getAudioTracks().forEach(t => stream.addTrack(t));
-
- // Create the MediaRecorder
- trackRecorder.recorder = new MediaRecorder(stream,
- { mimeType: this.fileType });
-
- // array for holding the recorder data. Resets it when
- // audio already has been recorder once
- trackRecorder.data = [];
-
- // function handling a dataEvent, e.g the stream gets new data
- trackRecorder.recorder.ondataavailable = function(dataEvent) {
- if (dataEvent.data.size > 0) {
- trackRecorder.data.push(dataEvent.data);
- }
- };
-
- return trackRecorder;
- };
-
- /**
- * Notifies the module that a specific track has stopped, e.g participant left
- * the conference.
- * if the recording has not started yet, the TrackRecorder will be removed from
- * the array. If the recording has started, the recorder will stop recording
- * but not removed from the array so that the recorded stream can still be
- * accessed
- *
- * @param {JitsiTrack} track the JitsiTrack to remove from the recording session
- */
- AudioRecorder.prototype.removeTrack = function(track) {
- if (track.isVideoTrack()) {
- return;
- }
-
- const array = this.recorders;
- let i;
-
- for (i = 0; i < array.length; i++) {
- if (array[i].track.getParticipantId() === track.getParticipantId()) {
- const recorderToRemove = array[i];
-
- if (this.isRecording) {
- stopRecorder(recorderToRemove);
- } else {
- // remove the TrackRecorder from the array
- array.splice(i, 1);
- }
- }
- }
-
- // make sure the names are up to date
- this.updateNames();
- };
-
- /**
- * Tries to update the name value of all TrackRecorder in the array.
- * If it hasn't changed,it will keep the exiting name. If it changes to a
- * undefined value, the old value will also be kept.
- */
- AudioRecorder.prototype.updateNames = function() {
- const conference = this.jitsiConference;
-
- this.recorders.forEach(trackRecorder => {
- if (trackRecorder.track.isLocal()) {
- trackRecorder.name = 'the transcriber';
- } else {
- const id = trackRecorder.track.getParticipantId();
- const participant = conference.getParticipantById(id);
- const newName = participant.getDisplayName();
-
- if (newName !== 'undefined') {
- trackRecorder.name = newName;
- }
- }
- });
- };
-
- /**
- * Starts the audio recording of every local and remote track
- */
- AudioRecorder.prototype.start = function() {
- if (this.isRecording) {
- throw new Error('audiorecorder is already recording');
- }
-
- // set boolean isRecording flag to true so if new participants join the
- // conference, that track can instantly start recording as well
- this.isRecording = true;
-
- // start all the mediaRecorders
- this.recorders.forEach(trackRecorder => startRecorder(trackRecorder));
-
- // log that recording has started
- console.log(
- `Started the recording of the audio. There are currently ${
- this.recorders.length} recorders active.`);
- };
-
- /**
- * Stops the audio recording of every local and remote track
- */
- AudioRecorder.prototype.stop = function() {
- // set the boolean flag to false
- this.isRecording = false;
-
- // stop all recorders
- this.recorders.forEach(trackRecorder => stopRecorder(trackRecorder));
- console.log('stopped recording');
- };
-
- /**
- * link hacking to download all recorded audio streams
- */
- AudioRecorder.prototype.download = function() {
- this.recorders.forEach(trackRecorder => {
- const blob = new Blob(trackRecorder.data, { type: this.fileType });
- const url = URL.createObjectURL(blob);
- const a = document.createElement('a');
-
- document.body.appendChild(a);
- a.style = 'display: none';
- a.href = url;
- a.download = `test.${this.fileType.split('/')[1]}`;
- a.click();
- window.URL.revokeObjectURL(url);
- });
- };
-
- /**
- * returns the audio files of all recorders as an array of objects,
- * which include the name of the owner of the track and the starting time stamp
- * @returns {Array} an array of RecordingResult objects
- */
- AudioRecorder.prototype.getRecordingResults = function() {
- if (this.isRecording) {
- throw new Error(
- 'cannot get blobs because the AudioRecorder is still recording!');
- }
-
- // make sure the names are up to date before sending them off
- this.updateNames();
-
- const array = [];
-
- this.recorders.forEach(
- recorder =>
- array.push(
- new RecordingResult(
- new Blob(recorder.data, { type: this.fileType }),
- recorder.name,
- recorder.startTime)));
-
- return array;
- };
-
- /**
- * Gets the mime type of the recorder audio
- * @returns {String} the mime type of the recorder audio
- */
- AudioRecorder.prototype.getFileType = function() {
- return this.fileType;
- };
-
- /**
- * Creates a empty MediaStream object which can be used
- * to add MediaStreamTracks to
- * @returns MediaStream
- */
- function createEmptyStream() {
- if (typeof MediaStream !== 'undefined') {
- return new MediaStream();
- }
- throw new Error('cannot create a clean mediaStream');
- }
-
- /**
- * export the main object AudioRecorder
- */
- module.exports = AudioRecorder;
|