Parcourir la source

Switching microphone on the fly: flac and wav support

master
Radium Zheng il y a 7 ans
Parent
révision
b6e1a49d33

+ 69
- 15
react/features/local-recording/controller/RecordingController.js Voir le fichier

72
     /**
72
     /**
73
      * Stopping.
73
      * Stopping.
74
      */
74
      */
75
-    STOPPING: Symbol('STOPPING')
75
+    STOPPING: Symbol('STOPPING'),
76
+
77
+    /**
78
+     * Failed, due to error during starting / stopping process.
79
+     */
80
+    FAILED: Symbol('FAILED')
76
 });
81
 });
77
 
82
 
78
 /**
83
 /**
147
      */
152
      */
148
     _isMuted = false;
153
     _isMuted = false;
149
 
154
 
155
+    /**
156
+     * The ID of the active microphone.
157
+     *
158
+     * @private
159
+     */
160
+    _micDeviceId = 'default';
161
+
150
     /**
162
     /**
151
      * Current recording format. This will be in effect from the next
163
      * Current recording format. This will be in effect from the next
152
      * recording session, i.e., if this value is changed during an on-going
164
      * recording session, i.e., if this value is changed during an on-going
190
      * @returns {void}
202
      * @returns {void}
191
      */
203
      */
192
     constructor() {
204
     constructor() {
193
-        this._updateStats = this._updateStats.bind(this);
205
+        this.registerEvents = this.registerEvents.bind(this);
206
+        this.getParticipantsStats = this.getParticipantsStats.bind(this);
194
         this._onStartCommand = this._onStartCommand.bind(this);
207
         this._onStartCommand = this._onStartCommand.bind(this);
195
         this._onStopCommand = this._onStopCommand.bind(this);
208
         this._onStopCommand = this._onStopCommand.bind(this);
196
         this._onPingCommand = this._onPingCommand.bind(this);
209
         this._onPingCommand = this._onPingCommand.bind(this);
197
         this._doStartRecording = this._doStartRecording.bind(this);
210
         this._doStartRecording = this._doStartRecording.bind(this);
198
         this._doStopRecording = this._doStopRecording.bind(this);
211
         this._doStopRecording = this._doStopRecording.bind(this);
199
-        this.registerEvents = this.registerEvents.bind(this);
200
-        this.getParticipantsStats = this.getParticipantsStats.bind(this);
212
+        this._updateStats = this._updateStats.bind(this);
213
+        this._switchToNewSession = this._switchToNewSession.bind(this);
201
     }
214
     }
202
 
215
 
203
     registerEvents: () => void;
216
     registerEvents: () => void;
311
         }
324
         }
312
     }
325
     }
313
 
326
 
327
+    /**
328
+     * Changes the current microphone.
329
+     *
330
+     * @param {string} micDeviceId - The new microphone device ID.
331
+     * @returns {void}
332
+     */
333
+    setMicDevice(micDeviceId: string) {
334
+        if (micDeviceId !== this._micDeviceId) {
335
+            this._micDeviceId = String(micDeviceId);
336
+
337
+            if (this._state === ControllerState.RECORDING) {
338
+                // sessionManager.endSegment(this._currentSessionToken);
339
+                logger.log('Before switching microphone...');
340
+                this._adapters[this._currentSessionToken]
341
+                    .setMicDevice(this._micDeviceId)
342
+                    .then(() => {
343
+                        logger.log('Finished switching microphone.');
344
+
345
+                        // sessionManager.beginSegment(this._currentSesoken);
346
+                    })
347
+                    .catch(() => {
348
+                        logger.error('Failed to switch microphone');
349
+                    });
350
+            }
351
+            logger.log(`Switch microphone to ${this._micDeviceId}`);
352
+        }
353
+    }
354
+
314
     /**
355
     /**
315
      * Mute or unmute audio. When muted, the ongoing local recording should
356
      * Mute or unmute audio. When muted, the ongoing local recording should
316
      * produce silence.
357
      * produce silence.
322
         this._isMuted = Boolean(muted);
363
         this._isMuted = Boolean(muted);
323
 
364
 
324
         if (this._state === ControllerState.RECORDING) {
365
         if (this._state === ControllerState.RECORDING) {
325
-            this._adapters[this._currentSessionToken].setMuted(muted);
366
+            this._adapters[this._currentSessionToken].setMuted(this._isMuted);
326
         }
367
         }
327
     }
368
     }
328
 
369
 
442
 
483
 
443
         if (this._state === ControllerState.IDLE) {
484
         if (this._state === ControllerState.IDLE) {
444
             this._changeState(ControllerState.STARTING);
485
             this._changeState(ControllerState.STARTING);
445
-            this._format = format;
446
-            this._currentSessionToken = sessionToken;
447
-            logger.log(this._currentSessionToken);
448
-            this._adapters[sessionToken]
449
-                 = this._createRecordingAdapter();
486
+            this._switchToNewSession(sessionToken, format);
450
             this._doStartRecording();
487
             this._doStartRecording();
451
         } else if (this._state === ControllerState.RECORDING
488
         } else if (this._state === ControllerState.RECORDING
452
             && this._currentSessionToken !== sessionToken) {
489
             && this._currentSessionToken !== sessionToken) {
455
             // moderator's, so we need to restart the recording.
492
             // moderator's, so we need to restart the recording.
456
             this._changeState(ControllerState.STOPPING);
493
             this._changeState(ControllerState.STOPPING);
457
             this._doStopRecording().then(() => {
494
             this._doStopRecording().then(() => {
458
-                this._format = format;
459
-                this._currentSessionToken = sessionToken;
460
-                this._adapters[sessionToken]
461
-                    = this._createRecordingAdapter();
495
+                this._changeState(ControllerState.STARTING);
496
+                this._switchToNewSession(sessionToken, format);
462
                 this._doStartRecording();
497
                 this._doStartRecording();
463
             });
498
             });
464
         }
499
         }
518
         if (this._state === ControllerState.STARTING) {
553
         if (this._state === ControllerState.STARTING) {
519
             const delegate = this._adapters[this._currentSessionToken];
554
             const delegate = this._adapters[this._currentSessionToken];
520
 
555
 
521
-            delegate.start()
556
+            delegate.start(this._micDeviceId)
522
             .then(() => {
557
             .then(() => {
523
                 this._changeState(ControllerState.RECORDING);
558
                 this._changeState(ControllerState.RECORDING);
524
                 logger.log('Local recording engaged.');
559
                 logger.log('Local recording engaged.');
587
 
622
 
588
     }
623
     }
589
 
624
 
625
+    _switchToNewSession: (string, string) => void;
626
+
627
+    /**
628
+     * Switches to a new local recording session.
629
+     *
630
+     * @param {string} sessionToken - The session Token.
631
+     * @param {string} format - The recording format for the session.
632
+     * @returns {void}
633
+     */
634
+    _switchToNewSession(sessionToken, format) {
635
+        this._format = format;
636
+        this._currentSessionToken = sessionToken;
637
+        logger.log(`New session: ${this._currentSessionToken}, `
638
+            + `format: ${this._format}`);
639
+        this._adapters[sessionToken]
640
+             = this._createRecordingAdapter();
641
+        sessionManager.createSession(sessionToken, this._format);
642
+    }
643
+
590
     /**
644
     /**
591
      * Creates a recording adapter according to the current recording format.
645
      * Creates a recording adapter according to the current recording format.
592
      *
646
      *

+ 9
- 3
react/features/local-recording/middleware.js Voir le fichier

7
 import { i18next } from '../base/i18n';
7
 import { i18next } from '../base/i18n';
8
 import { SET_AUDIO_MUTED } from '../base/media';
8
 import { SET_AUDIO_MUTED } from '../base/media';
9
 import { MiddlewareRegistry } from '../base/redux';
9
 import { MiddlewareRegistry } from '../base/redux';
10
+import { SETTINGS_UPDATED } from '../base/settings/actionTypes';
10
 import { showNotification } from '../notifications';
11
 import { showNotification } from '../notifications';
11
 
12
 
12
 import { localRecordingEngaged, localRecordingUnengaged } from './actions';
13
 import { localRecordingEngaged, localRecordingUnengaged } from './actions';
76
     case SET_AUDIO_MUTED:
77
     case SET_AUDIO_MUTED:
77
         recordingController.setMuted(action.muted);
78
         recordingController.setMuted(action.muted);
78
         break;
79
         break;
79
-    }
80
+    case SETTINGS_UPDATED: {
81
+        const { micDeviceId } = getState()['features/base/settings'];
80
 
82
 
81
-    // @todo: detect change in features/base/settings micDeviceID
82
-    // @todo: SET_AUDIO_MUTED, when audio is muted
83
+        if (micDeviceId) {
84
+            recordingController.setMicDevice(micDeviceId);
85
+        }
86
+        break;
87
+    }
88
+    }
83
 
89
 
84
     return result;
90
     return result;
85
 });
91
 });

+ 5
- 4
react/features/local-recording/recording/OggAdapter.js Voir le fichier

24
      *
24
      *
25
      * @inheritdoc
25
      * @inheritdoc
26
      */
26
      */
27
-    start() {
27
+    start(micDeviceId) {
28
         if (!this._initPromise) {
28
         if (!this._initPromise) {
29
-            this._initPromise = this._initialize();
29
+            this._initPromise = this._initialize(micDeviceId);
30
         }
30
         }
31
 
31
 
32
         return this._initPromise.then(() =>
32
         return this._initPromise.then(() =>
96
      * Initialize the adapter.
96
      * Initialize the adapter.
97
      *
97
      *
98
      * @private
98
      * @private
99
+     * @param {string} micDeviceId - The current microphone device ID.
99
      * @returns {Promise}
100
      * @returns {Promise}
100
      */
101
      */
101
-    _initialize() {
102
+    _initialize(micDeviceId) {
102
         if (this._mediaRecorder) {
103
         if (this._mediaRecorder) {
103
             return Promise.resolve();
104
             return Promise.resolve();
104
         }
105
         }
105
 
106
 
106
         return new Promise((resolve, error) => {
107
         return new Promise((resolve, error) => {
107
-            this._getAudioStream(0)
108
+            this._getAudioStream(micDeviceId)
108
             .then(stream => {
109
             .then(stream => {
109
                 this._stream = stream;
110
                 this._stream = stream;
110
                 this._mediaRecorder = new MediaRecorder(stream);
111
                 this._mediaRecorder = new MediaRecorder(stream);

+ 14
- 1
react/features/local-recording/recording/RecordingAdapter.js Voir le fichier

8
     /**
8
     /**
9
      * Starts recording.
9
      * Starts recording.
10
      *
10
      *
11
+     * @param {string} micDeviceId - The microphone to record on.
11
      * @returns {Promise}
12
      * @returns {Promise}
12
      */
13
      */
13
-    start() {
14
+    start(/* eslint-disable no-unused-vars */
15
+            micDeviceId/* eslint-enable no-unused-vars */) {
14
         throw new Error('Not implemented');
16
         throw new Error('Not implemented');
15
     }
17
     }
16
 
18
 
43
         throw new Error('Not implemented');
45
         throw new Error('Not implemented');
44
     }
46
     }
45
 
47
 
48
+    /**
49
+     * Changes the current microphone.
50
+     *
51
+     * @param {string} micDeviceId - The new microphone device ID.
52
+     * @returns {Promise}
53
+     */
54
+    setMicDevice(/* eslint-disable no-unused-vars */
55
+            micDeviceId/* eslint-enable no-unused-vars */) {
56
+        throw new Error('Not implemented');
57
+    }
58
+
46
     /**
59
     /**
47
      * Helper method for getting an audio {@code MediaStream}. Use this instead
60
      * Helper method for getting an audio {@code MediaStream}. Use this instead
48
      * of calling browser APIs directly.
61
      * of calling browser APIs directly.

+ 8
- 7
react/features/local-recording/recording/WavAdapter.js Voir le fichier

65
      *
65
      *
66
      * @inheritdoc
66
      * @inheritdoc
67
      */
67
      */
68
-    start() {
68
+    start(micDeviceId) {
69
         if (!this._initPromise) {
69
         if (!this._initPromise) {
70
-            this._initPromise = this._initialize();
70
+            this._initPromise = this._initialize(micDeviceId);
71
         }
71
         }
72
 
72
 
73
         return this._initPromise.then(() => {
73
         return this._initPromise.then(() => {
197
      * Initialize the adapter.
197
      * Initialize the adapter.
198
      *
198
      *
199
      * @private
199
      * @private
200
+     * @param {string} micDeviceId - The current microphone device ID.
200
      * @returns {Promise}
201
      * @returns {Promise}
201
      */
202
      */
202
-    _initialize() {
203
+    _initialize(micDeviceId) {
203
         if (this._isInitialized) {
204
         if (this._isInitialized) {
204
             return Promise.resolve();
205
             return Promise.resolve();
205
         }
206
         }
206
 
207
 
207
         const p = new Promise((resolve, reject) => {
208
         const p = new Promise((resolve, reject) => {
208
-            this._getAudioStream(0)
209
+            this._getAudioStream(micDeviceId)
209
             .then(stream => {
210
             .then(stream => {
210
                 this._stream = stream;
211
                 this._stream = stream;
211
                 this._audioContext = new AudioContext();
212
                 this._audioContext = new AudioContext();
216
                 this._audioProcessingNode.onaudioprocess = e => {
217
                 this._audioProcessingNode.onaudioprocess = e => {
217
                     const channelLeft = e.inputBuffer.getChannelData(0);
218
                     const channelLeft = e.inputBuffer.getChannelData(0);
218
 
219
 
219
-                    // https://developer.mozilla.org/en-US/docs/
220
-                    // Web/API/AudioBuffer/getChannelData
221
-                    // the returned value is an Float32Array
220
+                    // See: https://developer.mozilla.org/en-US/docs/Web/API/
221
+                    //      AudioBuffer/getChannelData
222
+                    // The returned value is an Float32Array.
222
                     this._saveWavPCM(channelLeft);
223
                     this._saveWavPCM(channelLeft);
223
                 };
224
                 };
224
                 this._isInitialized = true;
225
                 this._isInitialized = true;

+ 44
- 6
react/features/local-recording/recording/flac/FlacAdapter.js Voir le fichier

38
      *
38
      *
39
      * @inheritdoc
39
      * @inheritdoc
40
      */
40
      */
41
-    start() {
41
+    start(micDeviceId) {
42
         if (!this._initPromise) {
42
         if (!this._initPromise) {
43
-            this._initPromise = this._initialize();
43
+            this._initPromise = this._initialize(micDeviceId);
44
         }
44
         }
45
 
45
 
46
         return this._initPromise.then(() => {
46
         return this._initPromise.then(() => {
114
         return Promise.resolve();
114
         return Promise.resolve();
115
     }
115
     }
116
 
116
 
117
+    /**
118
+     * Implements {@link RecordingAdapter#setMicDevice()}.
119
+     *
120
+     * @inheritdoc
121
+     */
122
+    setMicDevice(micDeviceId) {
123
+        return this._replaceMic(micDeviceId);
124
+    }
125
+
126
+    /**
127
+     * Replaces the current microphone MediaStream.
128
+     *
129
+     * @param {*} micDeviceId - New microphone ID.
130
+     * @returns {Promise}
131
+     */
132
+    _replaceMic(micDeviceId) {
133
+        if (this._audioContext && this._audioProcessingNode) {
134
+            return new Promise((resolve, reject) => {
135
+                this._getAudioStream(micDeviceId).then(newStream => {
136
+                    const newSource = this._audioContext
137
+                        .createMediaStreamSource(newStream);
138
+
139
+                    this._audioSource.disconnect();
140
+                    newSource.connect(this._audioProcessingNode);
141
+                    this._stream = newStream;
142
+                    this._audioSource = newSource;
143
+                    resolve();
144
+                })
145
+                .catch(() => {
146
+                    reject();
147
+                });
148
+            });
149
+        }
150
+
151
+        return Promise.resolve();
152
+    }
153
+
117
     /**
154
     /**
118
      * Initialize the adapter.
155
      * Initialize the adapter.
119
      *
156
      *
120
      * @private
157
      * @private
158
+     * @param {string} micDeviceId - The current microphone device ID.
121
      * @returns {Promise}
159
      * @returns {Promise}
122
      */
160
      */
123
-    _initialize() {
161
+    _initialize(micDeviceId) {
124
         if (this._encoder !== null) {
162
         if (this._encoder !== null) {
125
             return Promise.resolve();
163
             return Promise.resolve();
126
         }
164
         }
146
                 } else if (e.data.command === DEBUG) {
184
                 } else if (e.data.command === DEBUG) {
147
                     logger.log(e.data);
185
                     logger.log(e.data);
148
                 } else if (e.data.command === WORKER_LIBFLAC_READY) {
186
                 } else if (e.data.command === WORKER_LIBFLAC_READY) {
149
-                    logger.debug('libflac is ready.');
187
+                    logger.log('libflac is ready.');
150
                     resolve();
188
                     resolve();
151
                 } else {
189
                 } else {
152
                     logger.error(
190
                     logger.error(
165
         });
203
         });
166
 
204
 
167
         const callbackInitAudioContext = (resolve, reject) => {
205
         const callbackInitAudioContext = (resolve, reject) => {
168
-            this._getAudioStream(0)
206
+            this._getAudioStream(micDeviceId)
169
             .then(stream => {
207
             .then(stream => {
170
                 this._stream = stream;
208
                 this._stream = stream;
171
                 this._audioContext = new AudioContext();
209
                 this._audioContext = new AudioContext();
205
      * @returns {void}
243
      * @returns {void}
206
      */
244
      */
207
     _loadWebWorker() {
245
     _loadWebWorker() {
208
-        // FIXME: workaround for different file names in development/
246
+        // FIXME: Workaround for different file names in development/
209
         // production environments.
247
         // production environments.
210
         // We cannot import flacEncodeWorker as a webpack module,
248
         // We cannot import flacEncodeWorker as a webpack module,
211
         // because it is in a different bundle and should be lazy-loaded
249
         // because it is in a different bundle and should be lazy-loaded

+ 1
- 1
react/features/local-recording/recording/flac/flacEncodeWorker.js Voir le fichier

81
 });
81
 });
82
 
82
 
83
 /**
83
 /**
84
- * Default compression level.
84
+ * Default FLAC compression level.
85
  */
85
  */
86
 const FLAC_COMPRESSION_LEVEL = 5;
86
 const FLAC_COMPRESSION_LEVEL = 5;
87
 
87
 

Chargement…
Annuler
Enregistrer