ソースを参照

Muting support

fix Promise in setMuted
master
Radium Zheng 7年前
コミット
65c76dcde5

+ 37
- 3
react/features/local-recording/controller/RecordingController.js ファイルの表示

@@ -40,6 +40,11 @@ const COMMAND_PONG = 'localRecPong';
40 40
  */
41 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 49
  * Default recording format.
45 50
  */
@@ -135,6 +140,13 @@ class RecordingController {
135 140
      */
136 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 151
      * Current recording format. This will be in effect from the next
140 152
      * recording session, i.e., if this value is changed during an on-going
@@ -299,6 +311,21 @@ class RecordingController {
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 330
      * Switches the recording format.
304 331
      *
@@ -306,6 +333,11 @@ class RecordingController {
306 333
      * @returns {void}
307 334
      */
308 335
     switchFormat(newFormat: string) {
336
+        if (!RECORDING_FORMATS.has(newFormat)) {
337
+            logger.log(`Unknown format ${newFormat}. Ignoring...`);
338
+
339
+            return;
340
+        }
309 341
         this._format = newFormat;
310 342
         logger.log(`Recording format switched to ${newFormat}`);
311 343
 
@@ -465,13 +497,13 @@ class RecordingController {
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 503
      * @returns {number}
472 504
      */
473 505
     _getRandomToken() {
474
-        return Math.floor(Math.random() * 10000) + 1;
506
+        return Math.floor(Math.random() * 100000000) + 1;
475 507
     }
476 508
 
477 509
     _doStartRecording: () => void;
@@ -497,6 +529,8 @@ class RecordingController {
497 529
                 if (this._onStateChanged) {
498 530
                     this._onStateChanged(true);
499 531
                 }
532
+
533
+                delegate.setMuted(this._isMuted);
500 534
                 this._updateStats();
501 535
             })
502 536
             .catch(err => {

+ 10
- 0
react/features/local-recording/middleware.js ファイルの表示

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

+ 29
- 0
react/features/local-recording/recording/OggAdapter.js ファイルの表示

@@ -64,6 +64,34 @@ export class OggAdapter extends RecordingAdapter {
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 96
      * Initialize the adapter.
69 97
      *
@@ -78,6 +106,7 @@ export class OggAdapter extends RecordingAdapter {
78 106
         return new Promise((resolve, error) => {
79 107
             this._getAudioStream(0)
80 108
             .then(stream => {
109
+                this._stream = stream;
81 110
                 this._mediaRecorder = new MediaRecorder(stream);
82 111
                 this._mediaRecorder.ondataavailable
83 112
                     = e => this._saveMediaData(e.data);

+ 14
- 3
react/features/local-recording/recording/RecordingAdapter.js ファイルの表示

@@ -33,8 +33,19 @@ export class RecordingAdapter {
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 50
      * @protected
40 51
      * @param {number} micDeviceId - The ID of the current audio device.
@@ -52,7 +63,7 @@ export class RecordingAdapter {
52 63
             const mediaStream = result[0].stream;
53 64
 
54 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 69
             return mediaStream;

+ 1
- 2
react/features/local-recording/recording/Utils.js ファイルの表示

@@ -17,8 +17,7 @@ export function downloadBlob(blob, fileName = 'recording.ogg') {
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 22
  * @returns {string}
24 23
  */

+ 46
- 16
react/features/local-recording/recording/WavAdapter.js ファイルの表示

@@ -11,6 +11,11 @@ const WAV_SAMPLE_RATE = 44100;
11 11
  */
12 12
 export class WavAdapter extends RecordingAdapter {
13 13
 
14
+    /**
15
+     * The current {@code MediaStream} instance.
16
+     */
17
+    _stream = null;
18
+
14 19
     /**
15 20
      * {@code AudioContext} instance.
16 21
      */
@@ -65,17 +70,15 @@ export class WavAdapter extends RecordingAdapter {
65 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,6 +111,34 @@ export class WavAdapter extends RecordingAdapter {
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 143
      * Creates a WAVE file header.
113 144
      *
@@ -176,6 +207,7 @@ export class WavAdapter extends RecordingAdapter {
176 207
         const p = new Promise((resolve, reject) => {
177 208
             this._getAudioStream(0)
178 209
             .then(stream => {
210
+                this._stream = stream;
179 211
                 this._audioContext = new AudioContext();
180 212
                 this._audioSource
181 213
                     = this._audioContext.createMediaStreamSource(stream);
@@ -209,12 +241,10 @@ export class WavAdapter extends RecordingAdapter {
209 241
      * @returns {void}
210 242
      */
211 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 248
         this._wavBuffers.push(new Float32Array(data));
219 249
         this._wavLength += data.length;
220 250
     }

+ 31
- 1
react/features/local-recording/recording/flac/FlacAdapter.js ファイルの表示

@@ -20,6 +20,7 @@ export class FlacAdapter extends RecordingAdapter {
20 20
     _audioContext = null;
21 21
     _audioProcessingNode = null;
22 22
     _audioSource = null;
23
+    _stream = null;
23 24
 
24 25
     /**
25 26
      * Resolve function of the promise returned by {@code stop()}.
@@ -85,6 +86,34 @@ export class FlacAdapter extends RecordingAdapter {
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 118
      * Initialize the adapter.
90 119
      *
@@ -138,6 +167,7 @@ export class FlacAdapter extends RecordingAdapter {
138 167
         const callbackInitAudioContext = (resolve, reject) => {
139 168
             this._getAudioStream(0)
140 169
             .then(stream => {
170
+                this._stream = stream;
141 171
                 this._audioContext = new AudioContext();
142 172
                 this._audioSource
143 173
                     = this._audioContext.createMediaStreamSource(stream);
@@ -161,7 +191,7 @@ export class FlacAdapter extends RecordingAdapter {
161 191
             });
162 192
         };
163 193
 
164
-        // FIXME: because Promise constructor immediately executes the executor
194
+        // Because Promise constructor immediately executes the executor
165 195
         // function. This is undesirable, we want callbackInitAudioContext to be
166 196
         // executed only **after** promiseInitWorker is resolved.
167 197
         return promiseInitWorker

読み込み中…
キャンセル
保存