modified lib-jitsi-meet dev repo
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

audioRecorder.js 9.1KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304
  1. const RecordingResult = require('./recordingResult');
  2. /**
  3. * Possible audio formats MIME types
  4. */
  5. const AUDIO_WEBM = 'audio/webm'; // Supported in chrome
  6. const AUDIO_OGG = 'audio/ogg'; // Supported in firefox
  7. /**
  8. * A TrackRecorder object holds all the information needed for recording a
  9. * single JitsiTrack (either remote or local)
  10. * @param track The JitsiTrack the object is going to hold
  11. */
  12. const TrackRecorder = function(track) {
  13. // The JitsiTrack holding the stream
  14. this.track = track;
  15. // The MediaRecorder recording the stream
  16. this.recorder = null;
  17. // The array of data chunks recorded from the stream
  18. // acts as a buffer until the data is stored on disk
  19. this.data = null;
  20. // the name of the person of the JitsiTrack. This can be undefined and/or
  21. // not unique
  22. this.name = null;
  23. // the time of the start of the recording
  24. this.startTime = null;
  25. };
  26. /**
  27. * Starts the recording of a JitsiTrack in a TrackRecorder object.
  28. * This will also define the timestamp and try to update the name
  29. * @param trackRecorder the TrackRecorder to start
  30. */
  31. function startRecorder(trackRecorder) {
  32. if (trackRecorder.recorder === undefined) {
  33. throw new Error('Passed an object to startRecorder which is not a '
  34. + 'TrackRecorder object');
  35. }
  36. trackRecorder.recorder.start();
  37. trackRecorder.startTime = new Date();
  38. }
  39. /**
  40. * Stops the recording of a JitsiTrack in a TrackRecorder object.
  41. * This will also try to update the name
  42. * @param trackRecorder the TrackRecorder to stop
  43. */
  44. function stopRecorder(trackRecorder) {
  45. if (trackRecorder.recorder === undefined) {
  46. throw new Error('Passed an object to stopRecorder which is not a '
  47. + 'TrackRecorder object');
  48. }
  49. trackRecorder.recorder.stop();
  50. }
  51. /**
  52. * Determines which kind of audio recording the browser supports
  53. * chrome supports "audio/webm" and firefox supports "audio/ogg"
  54. */
  55. function determineCorrectFileType() {
  56. if (MediaRecorder.isTypeSupported(AUDIO_WEBM)) {
  57. return AUDIO_WEBM;
  58. } else if (MediaRecorder.isTypeSupported(AUDIO_OGG)) {
  59. return AUDIO_OGG;
  60. }
  61. throw new Error(
  62. 'unable to create a MediaRecorder with the right mimetype!');
  63. }
  64. /**
  65. * main exported object of the file, holding all
  66. * relevant functions and variables for the outside world
  67. * @param jitsiConference the jitsiConference which this object
  68. * is going to record
  69. */
  70. function AudioRecorder(jitsiConference) {
  71. // array of TrackRecorders, where each trackRecorder
  72. // holds the JitsiTrack, MediaRecorder and recorder data
  73. this.recorders = [];
  74. // get which file type is supported by the current browser
  75. this.fileType = determineCorrectFileType();
  76. // boolean flag for active recording
  77. this.isRecording = false;
  78. // the jitsiconference the object is recording
  79. this.jitsiConference = jitsiConference;
  80. }
  81. /**
  82. * Add the exported module so that it can be accessed by other files
  83. */
  84. AudioRecorder.determineCorrectFileType = determineCorrectFileType;
  85. /**
  86. * Adds a new TrackRecorder object to the array.
  87. *
  88. * @param track the track potentially holding an audio stream
  89. */
  90. AudioRecorder.prototype.addTrack = function(track) {
  91. if (track.isAudioTrack()) {
  92. // create the track recorder
  93. const trackRecorder = this.instantiateTrackRecorder(track);
  94. // push it to the local array of all recorders
  95. this.recorders.push(trackRecorder);
  96. // update the name of the trackRecorders
  97. this.updateNames();
  98. // If we're already recording, immediately start recording this new
  99. // track.
  100. if (this.isRecording) {
  101. startRecorder(trackRecorder);
  102. }
  103. }
  104. };
  105. /**
  106. * Creates a TrackRecorder object. Also creates the MediaRecorder and
  107. * data array for the trackRecorder.
  108. * @param track the JitsiTrack holding the audio MediaStream(s)
  109. */
  110. AudioRecorder.prototype.instantiateTrackRecorder = function(track) {
  111. const trackRecorder = new TrackRecorder(track);
  112. // Create a new stream which only holds the audio track
  113. const originalStream = trackRecorder.track.getOriginalStream();
  114. const stream = new MediaStream();
  115. originalStream.getAudioTracks().forEach(t => stream.addTrack(t));
  116. // Create the MediaRecorder
  117. trackRecorder.recorder = new MediaRecorder(stream,
  118. { mimeType: this.fileType });
  119. // array for holding the recorder data. Resets it when
  120. // audio already has been recorder once
  121. trackRecorder.data = [];
  122. // function handling a dataEvent, e.g the stream gets new data
  123. trackRecorder.recorder.ondataavailable = function(dataEvent) {
  124. if (dataEvent.data.size > 0) {
  125. trackRecorder.data.push(dataEvent.data);
  126. }
  127. };
  128. return trackRecorder;
  129. };
  130. /**
  131. * Notifies the module that a specific track has stopped, e.g participant left
  132. * the conference.
  133. * if the recording has not started yet, the TrackRecorder will be removed from
  134. * the array. If the recording has started, the recorder will stop recording
  135. * but not removed from the array so that the recorded stream can still be
  136. * accessed
  137. *
  138. * @param {JitsiTrack} track the JitsiTrack to remove from the recording session
  139. */
  140. AudioRecorder.prototype.removeTrack = function(track) {
  141. if (track.isVideoTrack()) {
  142. return;
  143. }
  144. const array = this.recorders;
  145. let i;
  146. for (i = 0; i < array.length; i++) {
  147. if (array[i].track.getParticipantId() === track.getParticipantId()) {
  148. const recorderToRemove = array[i];
  149. if (this.isRecording) {
  150. stopRecorder(recorderToRemove);
  151. } else {
  152. // remove the TrackRecorder from the array
  153. array.splice(i, 1);
  154. }
  155. }
  156. }
  157. // make sure the names are up to date
  158. this.updateNames();
  159. };
  160. /**
  161. * Tries to update the name value of all TrackRecorder in the array.
  162. * If it hasn't changed,it will keep the exiting name. If it changes to a
  163. * undefined value, the old value will also be kept.
  164. */
  165. AudioRecorder.prototype.updateNames = function() {
  166. const conference = this.jitsiConference;
  167. this.recorders.forEach(trackRecorder => {
  168. if (trackRecorder.track.isLocal()) {
  169. trackRecorder.name = 'the transcriber';
  170. } else {
  171. const id = trackRecorder.track.getParticipantId();
  172. const participant = conference.getParticipantById(id);
  173. const newName = participant.getDisplayName();
  174. if (newName !== 'undefined') {
  175. trackRecorder.name = newName;
  176. }
  177. }
  178. });
  179. };
  180. /**
  181. * Starts the audio recording of every local and remote track
  182. */
  183. AudioRecorder.prototype.start = function() {
  184. if (this.isRecording) {
  185. throw new Error('audiorecorder is already recording');
  186. }
  187. // set boolean isRecording flag to true so if new participants join the
  188. // conference, that track can instantly start recording as well
  189. this.isRecording = true;
  190. // start all the mediaRecorders
  191. this.recorders.forEach(trackRecorder => startRecorder(trackRecorder));
  192. // log that recording has started
  193. console.log(
  194. `Started the recording of the audio. There are currently ${
  195. this.recorders.length} recorders active.`);
  196. };
  197. /**
  198. * Stops the audio recording of every local and remote track
  199. */
  200. AudioRecorder.prototype.stop = function() {
  201. // set the boolean flag to false
  202. this.isRecording = false;
  203. // stop all recorders
  204. this.recorders.forEach(trackRecorder => stopRecorder(trackRecorder));
  205. console.log('stopped recording');
  206. };
  207. /**
  208. * link hacking to download all recorded audio streams
  209. */
  210. AudioRecorder.prototype.download = function() {
  211. this.recorders.forEach(trackRecorder => {
  212. const blob = new Blob(trackRecorder.data, { type: this.fileType });
  213. const url = URL.createObjectURL(blob);
  214. const a = document.createElement('a');
  215. document.body.appendChild(a);
  216. a.style = 'display: none';
  217. a.href = url;
  218. a.download = `test.${this.fileType.split('/')[1]}`;
  219. a.click();
  220. window.URL.revokeObjectURL(url);
  221. });
  222. };
  223. /**
  224. * returns the audio files of all recorders as an array of objects,
  225. * which include the name of the owner of the track and the starting time stamp
  226. * @returns {Array} an array of RecordingResult objects
  227. */
  228. AudioRecorder.prototype.getRecordingResults = function() {
  229. if (this.isRecording) {
  230. throw new Error(
  231. 'cannot get blobs because the AudioRecorder is still recording!');
  232. }
  233. // make sure the names are up to date before sending them off
  234. this.updateNames();
  235. const array = [];
  236. this.recorders.forEach(
  237. recorder =>
  238. array.push(
  239. new RecordingResult(
  240. new Blob(recorder.data, { type: this.fileType }),
  241. recorder.name,
  242. recorder.startTime)));
  243. return array;
  244. };
  245. /**
  246. * Gets the mime type of the recorder audio
  247. * @returns {String} the mime type of the recorder audio
  248. */
  249. AudioRecorder.prototype.getFileType = function() {
  250. return this.fileType;
  251. };
  252. /**
  253. * export the main object AudioRecorder
  254. */
  255. module.exports = AudioRecorder;