瀏覽代碼

Muting support

fix Promise in setMuted
master
Radium Zheng 7 年之前
父節點
當前提交
65c76dcde5

+ 37
- 3
react/features/local-recording/controller/RecordingController.js 查看文件

40
  */
40
  */
41
 const PROPERTY_STATS = 'localRecStats';
41
 const PROPERTY_STATS = 'localRecStats';
42
 
42
 
43
+/**
44
+ * Supported recording formats.
45
+ */
46
+const RECORDING_FORMATS = new Set([ 'flac', 'wav', 'ogg' ]);
47
+
43
 /**
48
 /**
44
  * Default recording format.
49
  * Default recording format.
45
  */
50
  */
135
      */
140
      */
136
     _state = ControllerState.IDLE;
141
     _state = ControllerState.IDLE;
137
 
142
 
143
+    /**
144
+     * Whether or not the audio is muted in the UI. This is stored as internal
145
+     * state of {@code RecordingController} because we might have recording
146
+     * sessions that start muted.
147
+     */
148
+    _isMuted = false;
149
+
138
     /**
150
     /**
139
      * Current recording format. This will be in effect from the next
151
      * Current recording format. This will be in effect from the next
140
      * recording session, i.e., if this value is changed during an on-going
152
      * recording session, i.e., if this value is changed during an on-going
299
         }
311
         }
300
     }
312
     }
301
 
313
 
314
+    /**
315
+     * Mute or unmute audio. When muted, the ongoing local recording should
316
+     * produce silence.
317
+     *
318
+     * @param {boolean} muted - If the audio should be muted.
319
+     * @returns {void}
320
+     */
321
+    setMuted(muted: boolean) {
322
+        this._isMuted = Boolean(muted);
323
+
324
+        if (this._state === ControllerState.RECORDING) {
325
+            this._adapters[this._currentSessionToken].setMuted(muted);
326
+        }
327
+    }
328
+
302
     /**
329
     /**
303
      * Switches the recording format.
330
      * Switches the recording format.
304
      *
331
      *
306
      * @returns {void}
333
      * @returns {void}
307
      */
334
      */
308
     switchFormat(newFormat: string) {
335
     switchFormat(newFormat: string) {
336
+        if (!RECORDING_FORMATS.has(newFormat)) {
337
+            logger.log(`Unknown format ${newFormat}. Ignoring...`);
338
+
339
+            return;
340
+        }
309
         this._format = newFormat;
341
         this._format = newFormat;
310
         logger.log(`Recording format switched to ${newFormat}`);
342
         logger.log(`Recording format switched to ${newFormat}`);
311
 
343
 
465
     }
497
     }
466
 
498
 
467
     /**
499
     /**
468
-     * Generates a token that can be used to distinguish each
469
-     * recording session.
500
+     * Generates a token that can be used to distinguish each local recording
501
+     * session.
470
      *
502
      *
471
      * @returns {number}
503
      * @returns {number}
472
      */
504
      */
473
     _getRandomToken() {
505
     _getRandomToken() {
474
-        return Math.floor(Math.random() * 10000) + 1;
506
+        return Math.floor(Math.random() * 100000000) + 1;
475
     }
507
     }
476
 
508
 
477
     _doStartRecording: () => void;
509
     _doStartRecording: () => void;
497
                 if (this._onStateChanged) {
529
                 if (this._onStateChanged) {
498
                     this._onStateChanged(true);
530
                     this._onStateChanged(true);
499
                 }
531
                 }
532
+
533
+                delegate.setMuted(this._isMuted);
500
                 this._updateStats();
534
                 this._updateStats();
501
             })
535
             })
502
             .catch(err => {
536
             .catch(err => {

+ 10
- 0
react/features/local-recording/middleware.js 查看文件

5
 import { CONFERENCE_JOINED } from '../base/conference';
5
 import { CONFERENCE_JOINED } from '../base/conference';
6
 import { toggleDialog } from '../base/dialog';
6
 import { toggleDialog } from '../base/dialog';
7
 import { i18next } from '../base/i18n';
7
 import { i18next } from '../base/i18n';
8
+import { SET_AUDIO_MUTED } from '../base/media';
8
 import { MiddlewareRegistry } from '../base/redux';
9
 import { MiddlewareRegistry } from '../base/redux';
9
 import { showNotification } from '../notifications';
10
 import { showNotification } from '../notifications';
10
 
11
 
25
     switch (action.type) {
26
     switch (action.type) {
26
     case CONFERENCE_JOINED: {
27
     case CONFERENCE_JOINED: {
27
         const { conference } = getState()['features/base/conference'];
28
         const { conference } = getState()['features/base/conference'];
29
+        const { localRecording } = getState()['features/base/config'];
30
+
31
+        if (localRecording && localRecording.format) {
32
+            recordingController.switchFormat(localRecording.format);
33
+        }
28
 
34
 
29
         recordingController.registerEvents(conference);
35
         recordingController.registerEvents(conference);
30
         break;
36
         break;
31
     }
37
     }
32
     case APP_WILL_MOUNT:
38
     case APP_WILL_MOUNT:
39
+
33
         // realize the delegates on recordingController, allowing the UI to
40
         // realize the delegates on recordingController, allowing the UI to
34
         // react to state changes in recordingController.
41
         // react to state changes in recordingController.
35
         recordingController.onStateChanged = isEngaged => {
42
         recordingController.onStateChanged = isEngaged => {
66
         recordingController.onNotify = null;
73
         recordingController.onNotify = null;
67
         recordingController.onWarning = null;
74
         recordingController.onWarning = null;
68
         break;
75
         break;
76
+    case SET_AUDIO_MUTED:
77
+        recordingController.setMuted(action.muted);
78
+        break;
69
     }
79
     }
70
 
80
 
71
     // @todo: detect change in features/base/settings micDeviceID
81
     // @todo: detect change in features/base/settings micDeviceID

+ 29
- 0
react/features/local-recording/recording/OggAdapter.js 查看文件

64
         }
64
         }
65
     }
65
     }
66
 
66
 
67
+    /**
68
+     * Implements {@link RecordingAdapter#setMuted()}.
69
+     *
70
+     * @inheritdoc
71
+     */
72
+    setMuted(muted) {
73
+        const shouldEnable = !muted;
74
+
75
+        if (!this._stream) {
76
+            return Promise.resolve();
77
+        }
78
+
79
+        const track = this._stream.getAudioTracks()[0];
80
+
81
+        if (!track) {
82
+            logger.error('Cannot mute/unmute. Track not found!');
83
+
84
+            return Promise.resolve();
85
+        }
86
+
87
+        if (track.enabled !== shouldEnable) {
88
+            track.enabled = shouldEnable;
89
+            logger.log(muted ? 'Mute' : 'Unmute');
90
+        }
91
+
92
+        return Promise.resolve();
93
+    }
94
+
67
     /**
95
     /**
68
      * Initialize the adapter.
96
      * Initialize the adapter.
69
      *
97
      *
78
         return new Promise((resolve, error) => {
106
         return new Promise((resolve, error) => {
79
             this._getAudioStream(0)
107
             this._getAudioStream(0)
80
             .then(stream => {
108
             .then(stream => {
109
+                this._stream = stream;
81
                 this._mediaRecorder = new MediaRecorder(stream);
110
                 this._mediaRecorder = new MediaRecorder(stream);
82
                 this._mediaRecorder.ondataavailable
111
                 this._mediaRecorder.ondataavailable
83
                     = e => this._saveMediaData(e.data);
112
                     = e => this._saveMediaData(e.data);

+ 14
- 3
react/features/local-recording/recording/RecordingAdapter.js 查看文件

33
     }
33
     }
34
 
34
 
35
     /**
35
     /**
36
-     * Helper method for getting an audio MediaStream. Use this instead of
37
-     * calling browser APIs directly.
36
+     * Mutes or unmutes the current recording.
37
+     *
38
+     * @param {boolean} muted - Whether to mute or to unmute.
39
+     * @returns {Promise}
40
+     */
41
+    setMuted(/* eslint-disable no-unused-vars */
42
+            muted/* eslint-enable no-unused-vars */) {
43
+        throw new Error('Not implemented');
44
+    }
45
+
46
+    /**
47
+     * Helper method for getting an audio {@code MediaStream}. Use this instead
48
+     * of calling browser APIs directly.
38
      *
49
      *
39
      * @protected
50
      * @protected
40
      * @param {number} micDeviceId - The ID of the current audio device.
51
      * @param {number} micDeviceId - The ID of the current audio device.
52
             const mediaStream = result[0].stream;
63
             const mediaStream = result[0].stream;
53
 
64
 
54
             if (mediaStream === undefined) {
65
             if (mediaStream === undefined) {
55
-                throw new Error('Failed to get MediaStream.');
66
+                throw new Error('Failed to create local track.');
56
             }
67
             }
57
 
68
 
58
             return mediaStream;
69
             return mediaStream;

+ 1
- 2
react/features/local-recording/recording/Utils.js 查看文件

17
 }
17
 }
18
 
18
 
19
 /**
19
 /**
20
- * Obtains a timestamp of now.
21
- * Used in filenames.
20
+ * Obtains a timestamp of now. Used in filenames.
22
  *
21
  *
23
  * @returns {string}
22
  * @returns {string}
24
  */
23
  */

+ 46
- 16
react/features/local-recording/recording/WavAdapter.js 查看文件

11
  */
11
  */
12
 export class WavAdapter extends RecordingAdapter {
12
 export class WavAdapter extends RecordingAdapter {
13
 
13
 
14
+    /**
15
+     * The current {@code MediaStream} instance.
16
+     */
17
+    _stream = null;
18
+
14
     /**
19
     /**
15
      * {@code AudioContext} instance.
20
      * {@code AudioContext} instance.
16
      */
21
      */
65
             this._initPromise = this._initialize();
70
             this._initPromise = this._initialize();
66
         }
71
         }
67
 
72
 
68
-        return new Promise(
69
-            (resolve, /* eslint-disable */_reject/* eslint-enable */) => {
70
-                this._wavBuffers = [];
71
-                this._wavLength = 0;
72
-                this._wavBuffers.push(this._createWavHeader());
73
+        return this._initPromise.then(() => {
74
+            this._wavBuffers = [];
75
+            this._wavLength = 0;
76
+            this._wavBuffers.push(this._createWavHeader());
73
 
77
 
74
-                this._audioSource.connect(this._audioProcessingNode);
75
-                this._audioProcessingNode
76
-                    .connect(this._audioContext.destination);
77
-                resolve();
78
-            });
78
+            this._audioSource.connect(this._audioProcessingNode);
79
+            this._audioProcessingNode
80
+                .connect(this._audioContext.destination);
81
+        });
79
     }
82
     }
80
 
83
 
81
     /**
84
     /**
108
         }
111
         }
109
     }
112
     }
110
 
113
 
114
+    /**
115
+     * Implements {@link RecordingAdapter#setMuted()}.
116
+     *
117
+     * @inheritdoc
118
+     */
119
+    setMuted(muted) {
120
+        const shouldEnable = !muted;
121
+
122
+        if (!this._stream) {
123
+            return Promise.resolve();
124
+        }
125
+
126
+        const track = this._stream.getAudioTracks()[0];
127
+
128
+        if (!track) {
129
+            logger.error('Cannot mute/unmute. Track not found!');
130
+
131
+            return Promise.resolve();
132
+        }
133
+
134
+        if (track.enabled !== shouldEnable) {
135
+            track.enabled = shouldEnable;
136
+            logger.log(muted ? 'Mute' : 'Unmute');
137
+        }
138
+
139
+        return Promise.resolve();
140
+    }
141
+
111
     /**
142
     /**
112
      * Creates a WAVE file header.
143
      * Creates a WAVE file header.
113
      *
144
      *
176
         const p = new Promise((resolve, reject) => {
207
         const p = new Promise((resolve, reject) => {
177
             this._getAudioStream(0)
208
             this._getAudioStream(0)
178
             .then(stream => {
209
             .then(stream => {
210
+                this._stream = stream;
179
                 this._audioContext = new AudioContext();
211
                 this._audioContext = new AudioContext();
180
                 this._audioSource
212
                 this._audioSource
181
                     = this._audioContext.createMediaStreamSource(stream);
213
                     = this._audioContext.createMediaStreamSource(stream);
209
      * @returns {void}
241
      * @returns {void}
210
      */
242
      */
211
     _saveWavPCM(data) {
243
     _saveWavPCM(data) {
212
-        // need to copy the Float32Array,
213
-        // unlike passing to WebWorker,
214
-        // this data is passed by reference,
215
-        // so we need to copy it, otherwise the
216
-        // audio file will be just repeating the last
217
-        // segment.
244
+        // Need to copy the Float32Array:
245
+        // unlike passing to WebWorker, this data is passed by reference,
246
+        // so we need to copy it, otherwise the resulting audio file will be
247
+        // just repeating the last segment.
218
         this._wavBuffers.push(new Float32Array(data));
248
         this._wavBuffers.push(new Float32Array(data));
219
         this._wavLength += data.length;
249
         this._wavLength += data.length;
220
     }
250
     }

+ 31
- 1
react/features/local-recording/recording/flac/FlacAdapter.js 查看文件

20
     _audioContext = null;
20
     _audioContext = null;
21
     _audioProcessingNode = null;
21
     _audioProcessingNode = null;
22
     _audioSource = null;
22
     _audioSource = null;
23
+    _stream = null;
23
 
24
 
24
     /**
25
     /**
25
      * Resolve function of the promise returned by {@code stop()}.
26
      * Resolve function of the promise returned by {@code stop()}.
85
         }
86
         }
86
     }
87
     }
87
 
88
 
89
+    /**
90
+     * Implements {@link RecordingAdapter#setMuted()}.
91
+     *
92
+     * @inheritdoc
93
+     */
94
+    setMuted(muted) {
95
+        const shouldEnable = !muted;
96
+
97
+        if (!this._stream) {
98
+            return Promise.resolve();
99
+        }
100
+
101
+        const track = this._stream.getAudioTracks()[0];
102
+
103
+        if (!track) {
104
+            logger.error('Cannot mute/unmute. Track not found!');
105
+
106
+            return Promise.resolve();
107
+        }
108
+
109
+        if (track.enabled !== shouldEnable) {
110
+            track.enabled = shouldEnable;
111
+            logger.log(muted ? 'Mute' : 'Unmute');
112
+        }
113
+
114
+        return Promise.resolve();
115
+    }
116
+
88
     /**
117
     /**
89
      * Initialize the adapter.
118
      * Initialize the adapter.
90
      *
119
      *
138
         const callbackInitAudioContext = (resolve, reject) => {
167
         const callbackInitAudioContext = (resolve, reject) => {
139
             this._getAudioStream(0)
168
             this._getAudioStream(0)
140
             .then(stream => {
169
             .then(stream => {
170
+                this._stream = stream;
141
                 this._audioContext = new AudioContext();
171
                 this._audioContext = new AudioContext();
142
                 this._audioSource
172
                 this._audioSource
143
                     = this._audioContext.createMediaStreamSource(stream);
173
                     = this._audioContext.createMediaStreamSource(stream);
161
             });
191
             });
162
         };
192
         };
163
 
193
 
164
-        // FIXME: because Promise constructor immediately executes the executor
194
+        // Because Promise constructor immediately executes the executor
165
         // function. This is undesirable, we want callbackInitAudioContext to be
195
         // function. This is undesirable, we want callbackInitAudioContext to be
166
         // executed only **after** promiseInitWorker is resolved.
196
         // executed only **after** promiseInitWorker is resolved.
167
         return promiseInitWorker
197
         return promiseInitWorker

Loading…
取消
儲存