Przeglądaj źródła

[RN] Add audio only mode for conferences

The behavior can be triggered with the toggleAudioOnly action, which is
currently fired with a button.

The following aspects of the conference will change when in audio only mode:

- local video is muted
- last N is set to 0 (effectively muting remote video)
- full-screen mode is exited
- audio mode is set to "audio chat" (default output is the earpiece)
- the wake lock is disengaged

One aspect not handled in this patch is disabling the video mute button while in
audio only mode. The user should not be able to turn back video on in that case.
j8
Saúl Ibarra Corretgé 8 lat temu
rodzic
commit
8fe3dce649

+ 25
- 1
react/features/base/conference/actionTypes.js Wyświetl plik

68
  */
68
  */
69
 export const LOCK_STATE_CHANGED = Symbol('LOCK_STATE_CHANGED');
69
 export const LOCK_STATE_CHANGED = Symbol('LOCK_STATE_CHANGED');
70
 
70
 
71
+/**
72
+ * The type of the Redux action which sets the audio-only flag for the current
73
+ * conference.
74
+ *
75
+ * {
76
+ *     type: SET_AUDIO_ONLY,
77
+ *     audioOnly: boolean
78
+ * }
79
+ */
80
+export const SET_AUDIO_ONLY = Symbol('SET_AUDIO_ONLY');
81
+
82
+/**
83
+ * The type of redux action which signals that video will be muted because the
84
+ * audio-only mode was enabled / disabled.
85
+ *
86
+ * {
87
+ *     type: _SET_AUDIO_ONLY_VIDEO_MUTED,
88
+ *     muted: boolean
89
+ * }
90
+ *
91
+ * @protected
92
+ */
93
+export const _SET_AUDIO_ONLY_VIDEO_MUTED
94
+    = Symbol('_SET_AUDIO_ONLY_VIDEO_MUTED');
95
+
71
 /**
96
 /**
72
  * The type of redux action which sets the video channel's lastN (value).
97
  * The type of redux action which sets the video channel's lastN (value).
73
  *
98
  *
75
  *     type: SET_LASTN,
100
  *     type: SET_LASTN,
76
  *     lastN: number
101
  *     lastN: number
77
  * }
102
  * }
78
- *
79
  */
103
  */
80
 export const SET_LASTN = Symbol('SET_LASTN');
104
 export const SET_LASTN = Symbol('SET_LASTN');
81
 
105
 

+ 73
- 0
react/features/base/conference/actions.js Wyświetl plik

1
 import { JitsiConferenceEvents } from '../lib-jitsi-meet';
1
 import { JitsiConferenceEvents } from '../lib-jitsi-meet';
2
+import { setVideoMuted } from '../media';
2
 import {
3
 import {
3
     dominantSpeakerChanged,
4
     dominantSpeakerChanged,
4
     getLocalParticipant,
5
     getLocalParticipant,
17
     CONFERENCE_WILL_JOIN,
18
     CONFERENCE_WILL_JOIN,
18
     CONFERENCE_WILL_LEAVE,
19
     CONFERENCE_WILL_LEAVE,
19
     LOCK_STATE_CHANGED,
20
     LOCK_STATE_CHANGED,
21
+    SET_AUDIO_ONLY,
22
+    _SET_AUDIO_ONLY_VIDEO_MUTED,
20
     SET_LASTN,
23
     SET_LASTN,
21
     SET_PASSWORD,
24
     SET_PASSWORD,
22
     SET_ROOM
25
     SET_ROOM
295
     };
298
     };
296
 }
299
 }
297
 
300
 
301
+/**
302
+ * Sets the audio-only flag for the current JitsiConference.
303
+ *
304
+ * @param {boolean} audioOnly - True if the conference should be audio only;
305
+ * false, otherwise.
306
+ * @private
307
+ * @returns {{
308
+ *     type: SET_AUDIO_ONLY,
309
+ *     audioOnly: boolean
310
+ * }}
311
+ */
312
+function _setAudioOnly(audioOnly) {
313
+    return {
314
+        type: SET_AUDIO_ONLY,
315
+        audioOnly
316
+    };
317
+}
318
+
319
+/**
320
+ * Signals that the app should mute video because it's now in audio-only mode,
321
+ * or unmute it because it no longer is. If video was already muted, nothing
322
+ * will happen; otherwise, it will be muted. When audio-only mode is disabled,
323
+ * the previous state will be restored.
324
+ *
325
+ * @param {boolean} muted - True if video should be muted; false, otherwise.
326
+ * @protected
327
+ * @returns {Function}
328
+ */
329
+export function _setAudioOnlyVideoMuted(muted: boolean) {
330
+    return (dispatch, getState) => {
331
+        if (muted) {
332
+            const { video } = getState()['features/base/media'];
333
+
334
+            if (video.muted) {
335
+                // Video is already muted, do nothing.
336
+                return;
337
+            }
338
+        } else {
339
+            const { audioOnlyVideoMuted }
340
+                = getState()['features/base/conference'];
341
+
342
+            if (!audioOnlyVideoMuted) {
343
+                // We didn't mute video, do nothing.
344
+                return;
345
+            }
346
+        }
347
+
348
+        // Remember that local video was muted due to the audio-only mode
349
+        // vs user's choice.
350
+        dispatch({
351
+            type: _SET_AUDIO_ONLY_VIDEO_MUTED,
352
+            muted
353
+        });
354
+        dispatch(setVideoMuted(muted));
355
+    };
356
+}
357
+
298
 /**
358
 /**
299
  * Sets the video channel's last N (value) of the current conference. A value of
359
  * Sets the video channel's last N (value) of the current conference. A value of
300
  * undefined shall be used to reset it to the default value.
360
  * undefined shall be used to reset it to the default value.
403
         room
463
         room
404
     };
464
     };
405
 }
465
 }
466
+
467
+/**
468
+ * Toggles the audio-only flag for the current JitsiConference.
469
+ *
470
+ * @returns {Function}
471
+ */
472
+export function toggleAudioOnly() {
473
+    return (dispatch: Dispatch<*>, getState: Function) => {
474
+        const { audioOnly } = getState()['features/base/conference'];
475
+
476
+        return dispatch(_setAudioOnly(!audioOnly));
477
+    };
478
+}

+ 38
- 2
react/features/base/conference/middleware.js Wyświetl plik

8
 import { MiddlewareRegistry } from '../redux';
8
 import { MiddlewareRegistry } from '../redux';
9
 import { TRACK_ADDED, TRACK_REMOVED } from '../tracks';
9
 import { TRACK_ADDED, TRACK_REMOVED } from '../tracks';
10
 
10
 
11
-import { createConference } from './actions';
12
-import { SET_LASTN } from './actionTypes';
11
+import {
12
+    createConference,
13
+    _setAudioOnlyVideoMuted,
14
+    setLastN
15
+} from './actions';
16
+import { SET_AUDIO_ONLY, SET_LASTN } from './actionTypes';
13
 import {
17
 import {
14
     _addLocalTracksToConference,
18
     _addLocalTracksToConference,
15
     _handleParticipantError,
19
     _handleParticipantError,
30
     case PIN_PARTICIPANT:
34
     case PIN_PARTICIPANT:
31
         return _pinParticipant(store, next, action);
35
         return _pinParticipant(store, next, action);
32
 
36
 
37
+    case SET_AUDIO_ONLY:
38
+        return _setAudioOnly(store, next, action);
39
+
33
     case SET_LASTN:
40
     case SET_LASTN:
34
         return _setLastN(store, next, action);
41
         return _setLastN(store, next, action);
35
 
42
 
116
     return next(action);
123
     return next(action);
117
 }
124
 }
118
 
125
 
126
+/**
127
+ * Sets the audio-only flag for the current conference. When audio-only is set,
128
+ * local video is muted and last N is set to 0 to avoid receiving remote video.
129
+ *
130
+ * @param {Store} store - The Redux store in which the specified action is being
131
+ * dispatched.
132
+ * @param {Dispatch} next - The Redux dispatch function to dispatch the
133
+ * specified action to the specified store.
134
+ * @param {Action} action - The Redux action SET_AUDIO_ONLY which is being
135
+ * dispatched in the specified store.
136
+ * @private
137
+ * @returns {Object} The new state that is the result of the reduction of the
138
+ * specified action.
139
+ */
140
+function _setAudioOnly(store, next, action) {
141
+    const result = next(action);
142
+
143
+    const { audioOnly } = action;
144
+
145
+    // Set lastN to 0 in case audio-only is desired; leave it as undefined,
146
+    // otherwise, and the default lastN value will be chosen automatically.
147
+    store.dispatch(setLastN(audioOnly ? 0 : undefined));
148
+
149
+    // Mute local video
150
+    store.dispatch(_setAudioOnlyVideoMuted(audioOnly));
151
+
152
+    return result;
153
+}
154
+
119
 /**
155
 /**
120
  * Sets the last N (value) of the video channel in the conference.
156
  * Sets the last N (value) of the video channel in the conference.
121
  *
157
  *

+ 41
- 0
react/features/base/conference/reducer.js Wyświetl plik

11
     CONFERENCE_LEFT,
11
     CONFERENCE_LEFT,
12
     CONFERENCE_WILL_LEAVE,
12
     CONFERENCE_WILL_LEAVE,
13
     LOCK_STATE_CHANGED,
13
     LOCK_STATE_CHANGED,
14
+    SET_AUDIO_ONLY,
15
+    _SET_AUDIO_ONLY_VIDEO_MUTED,
14
     SET_PASSWORD,
16
     SET_PASSWORD,
15
     SET_ROOM
17
     SET_ROOM
16
 } from './actionTypes';
18
 } from './actionTypes';
37
     case LOCK_STATE_CHANGED:
39
     case LOCK_STATE_CHANGED:
38
         return _lockStateChanged(state, action);
40
         return _lockStateChanged(state, action);
39
 
41
 
42
+    case SET_AUDIO_ONLY:
43
+        return _setAudioOnly(state, action);
44
+
45
+    case _SET_AUDIO_ONLY_VIDEO_MUTED:
46
+        return _setAudioOnlyVideoMuted(state, action);
47
+
40
     case SET_PASSWORD:
48
     case SET_PASSWORD:
41
         return _setPassword(state, action);
49
         return _setPassword(state, action);
42
 
50
 
71
 
79
 
72
     return (
80
     return (
73
         setStateProperties(state, {
81
         setStateProperties(state, {
82
+            audioOnly: undefined,
83
+            audioOnlyVideoMuted: undefined,
74
             conference: undefined,
84
             conference: undefined,
75
             leaving: undefined,
85
             leaving: undefined,
76
             locked: undefined,
86
             locked: undefined,
144
 
154
 
145
     return (
155
     return (
146
         setStateProperties(state, {
156
         setStateProperties(state, {
157
+            audioOnly: undefined,
158
+            audioOnlyVideoMuted: undefined,
147
             conference: undefined,
159
             conference: undefined,
148
             leaving: undefined,
160
             leaving: undefined,
149
             locked: undefined,
161
             locked: undefined,
200
     return setStateProperty(state, 'locked', action.locked || undefined);
212
     return setStateProperty(state, 'locked', action.locked || undefined);
201
 }
213
 }
202
 
214
 
215
+/**
216
+ * Reduces a specific Redux action SET_AUDIO_ONLY of the feature
217
+ * base/conference.
218
+ *
219
+ * @param {Object} state - The Redux state of the feature base/conference.
220
+ * @param {Action} action - The Redux action SET_AUDIO_ONLY to reduce.
221
+ * @private
222
+ * @returns {Object} The new state of the feature base/conference after the
223
+ * reduction of the specified action.
224
+ */
225
+function _setAudioOnly(state, action) {
226
+    return setStateProperty(state, 'audioOnly', action.audioOnly);
227
+}
228
+
229
+/**
230
+ * Reduces a specific Redux action _SET_AUDIO_ONLY_VIDEO_MUTED of the feature
231
+ * base/conference.
232
+ *
233
+ * @param {Object} state - The Redux state of the feature base/conference.
234
+ * @param {Action} action - The Redux action SET_AUDIO_ONLY_VIDEO_MUTED to
235
+ * reduce.
236
+ * @private
237
+ * @returns {Object} The new state of the feature base/conference after the
238
+ * reduction of the specified action.
239
+ */
240
+function _setAudioOnlyVideoMuted(state, action) {
241
+    return setStateProperty(state, 'audioOnlyVideoMuted', action.muted);
242
+}
243
+
203
 /**
244
 /**
204
  * Reduces a specific Redux action SET_PASSWORD of the feature base/conference.
245
  * Reduces a specific Redux action SET_PASSWORD of the feature base/conference.
205
  *
246
  *

+ 9
- 6
react/features/mobile/audio-mode/middleware.js Wyświetl plik

6
 import {
6
 import {
7
     CONFERENCE_FAILED,
7
     CONFERENCE_FAILED,
8
     CONFERENCE_LEFT,
8
     CONFERENCE_LEFT,
9
-    CONFERENCE_WILL_JOIN
9
+    CONFERENCE_WILL_JOIN,
10
+    SET_AUDIO_ONLY
10
 } from '../../base/conference';
11
 } from '../../base/conference';
11
 import { MiddlewareRegistry } from '../../base/redux';
12
 import { MiddlewareRegistry } from '../../base/redux';
12
 
13
 
21
 MiddlewareRegistry.register(store => next => action => {
22
 MiddlewareRegistry.register(store => next => action => {
22
     const AudioMode = NativeModules.AudioMode;
23
     const AudioMode = NativeModules.AudioMode;
23
 
24
 
24
-    // The react-native module AudioMode is implemented on iOS at the time of
25
-    // this writing.
26
     if (AudioMode) {
25
     if (AudioMode) {
27
         let mode;
26
         let mode;
28
 
27
 
34
             break;
33
             break;
35
 
34
 
36
         case CONFERENCE_WILL_JOIN: {
35
         case CONFERENCE_WILL_JOIN: {
37
-            const conference = store.getState()['features/base/conference'];
36
+            const { audioOnly } = store.getState()['features/base/conference'];
38
 
37
 
38
+            mode = audioOnly ? AudioMode.AUDIO_CALL : AudioMode.VIDEO_CALL;
39
+            break;
40
+        }
41
+
42
+        case SET_AUDIO_ONLY:
39
             mode
43
             mode
40
-                = conference.audioOnly
44
+                = action.audioOnly
41
                     ? AudioMode.AUDIO_CALL
45
                     ? AudioMode.AUDIO_CALL
42
                     : AudioMode.VIDEO_CALL;
46
                     : AudioMode.VIDEO_CALL;
43
             break;
47
             break;
44
-        }
45
 
48
 
46
         default:
49
         default:
47
             mode = null;
50
             mode = null;

+ 4
- 2
react/features/mobile/background/actions.js Wyświetl plik

42
         // for last N will be chosen automatically.
42
         // for last N will be chosen automatically.
43
         const { audioOnly } = getState()['features/base/conference'];
43
         const { audioOnly } = getState()['features/base/conference'];
44
 
44
 
45
-        if (!audioOnly) {
46
-            dispatch(setLastN(muted ? 0 : undefined));
45
+        if (audioOnly) {
46
+            return;
47
         }
47
         }
48
 
48
 
49
+        dispatch(setLastN(muted ? 0 : undefined));
50
+
49
         if (muted) {
51
         if (muted) {
50
             const { video } = getState()['features/base/media'];
52
             const { video } = getState()['features/base/media'];
51
 
53
 

+ 6
- 1
react/features/mobile/full-screen/middleware.js Wyświetl plik

7
 import {
7
 import {
8
     CONFERENCE_FAILED,
8
     CONFERENCE_FAILED,
9
     CONFERENCE_LEFT,
9
     CONFERENCE_LEFT,
10
-    CONFERENCE_WILL_JOIN
10
+    CONFERENCE_WILL_JOIN,
11
+    SET_AUDIO_ONLY
11
 } from '../../base/conference';
12
 } from '../../base/conference';
12
 import { Platform } from '../../base/react';
13
 import { Platform } from '../../base/react';
13
 import { MiddlewareRegistry } from '../../base/redux';
14
 import { MiddlewareRegistry } from '../../base/redux';
50
     case CONFERENCE_LEFT:
51
     case CONFERENCE_LEFT:
51
         fullScreen = false;
52
         fullScreen = false;
52
         break;
53
         break;
54
+
55
+    case SET_AUDIO_ONLY:
56
+        fullScreen = !action.audioOnly;
57
+        break;
53
     }
58
     }
54
 
59
 
55
     if (fullScreen !== null) {
60
     if (fullScreen !== null) {

+ 8
- 6
react/features/mobile/wake-lock/middleware.js Wyświetl plik

3
 import {
3
 import {
4
     CONFERENCE_FAILED,
4
     CONFERENCE_FAILED,
5
     CONFERENCE_JOINED,
5
     CONFERENCE_JOINED,
6
-    CONFERENCE_LEFT
6
+    CONFERENCE_LEFT,
7
+    SET_AUDIO_ONLY
7
 } from '../../base/conference';
8
 } from '../../base/conference';
8
 import { MiddlewareRegistry } from '../../base/redux';
9
 import { MiddlewareRegistry } from '../../base/redux';
9
 
10
 
18
 MiddlewareRegistry.register(store => next => action => {
19
 MiddlewareRegistry.register(store => next => action => {
19
     switch (action.type) {
20
     switch (action.type) {
20
     case CONFERENCE_JOINED: {
21
     case CONFERENCE_JOINED: {
21
-        const state = store.getState()['features/base/conference'];
22
+        const { audioOnly } = store.getState()['features/base/conference'];
22
 
23
 
23
-        // TODO(saghul): Implement audio-only mode.
24
-        if (!state.audioOnly) {
25
-            _setWakeLock(true);
26
-        }
24
+        _setWakeLock(!audioOnly);
27
         break;
25
         break;
28
     }
26
     }
29
 
27
 
31
     case CONFERENCE_LEFT:
29
     case CONFERENCE_LEFT:
32
         _setWakeLock(false);
30
         _setWakeLock(false);
33
         break;
31
         break;
32
+
33
+    case SET_AUDIO_ONLY:
34
+        _setWakeLock(!action.audioOnly);
35
+        break;
34
     }
36
     }
35
 
37
 
36
     return next(action);
38
     return next(action);

+ 31
- 7
react/features/toolbox/components/Toolbox.native.js Wyświetl plik

2
 import { View } from 'react-native';
2
 import { View } from 'react-native';
3
 import { connect } from 'react-redux';
3
 import { connect } from 'react-redux';
4
 
4
 
5
+import { toggleAudioOnly } from '../../base/conference';
5
 import { MEDIA_TYPE, toggleCameraFacingMode } from '../../base/media';
6
 import { MEDIA_TYPE, toggleCameraFacingMode } from '../../base/media';
6
 import { Container } from '../../base/react';
7
 import { Container } from '../../base/react';
7
 import { ColorPalette } from '../../base/styles';
8
 import { ColorPalette } from '../../base/styles';
40
         _onHangup: React.PropTypes.func,
41
         _onHangup: React.PropTypes.func,
41
 
42
 
42
         /**
43
         /**
43
-         * Handler for room locking.
44
+         * Sets the lock i.e. password protection of the conference/room.
44
          */
45
          */
45
         _onRoomLock: React.PropTypes.func,
46
         _onRoomLock: React.PropTypes.func,
46
 
47
 
50
         _onToggleAudio: React.PropTypes.func,
51
         _onToggleAudio: React.PropTypes.func,
51
 
52
 
52
         /**
53
         /**
53
-         * Handler for toggling camera facing mode.
54
+         * Toggles the audio-only flag of the conference.
55
+         */
56
+        _onToggleAudioOnly: React.PropTypes.func,
57
+
58
+        /**
59
+         * Switches between the front/user-facing and back/environment-facing
60
+         * cameras.
54
          */
61
          */
55
         _onToggleCameraFacingMode: React.PropTypes.func,
62
         _onToggleCameraFacingMode: React.PropTypes.func,
56
 
63
 
198
                     onClick = { this.props._onRoomLock }
205
                     onClick = { this.props._onRoomLock }
199
                     style = { style }
206
                     style = { style }
200
                     underlayColor = { underlayColor } />
207
                     underlayColor = { underlayColor } />
208
+                <ToolbarButton
209
+                    iconName = 'star'
210
+                    iconStyle = { iconStyle }
211
+                    onClick = { this.props._onToggleAudioOnly }
212
+                    style = { style }
213
+                    underlayColor = { underlayColor } />
201
             </View>
214
             </View>
202
         );
215
         );
203
 
216
 
224
  * @param {Function} dispatch - Redux action dispatcher.
237
  * @param {Function} dispatch - Redux action dispatcher.
225
  * @returns {{
238
  * @returns {{
226
  *     _onRoomLock: Function,
239
  *     _onRoomLock: Function,
240
+ *     _onToggleAudioOnly: Function,
227
  *     _onToggleCameraFacingMode: Function,
241
  *     _onToggleCameraFacingMode: Function,
228
  * }}
242
  * }}
229
  * @private
243
  * @private
233
         ...abstractMapDispatchToProps(dispatch),
247
         ...abstractMapDispatchToProps(dispatch),
234
 
248
 
235
         /**
249
         /**
236
-         * Dispatches an action to set the lock i.e. password protection of the
237
-         * conference/room.
250
+         * Sets the lock i.e. password protection of the conference/room.
238
          *
251
          *
239
          * @private
252
          * @private
240
-         * @returns {Object} - Dispatched action.
253
+         * @returns {Object} Dispatched action.
241
          * @type {Function}
254
          * @type {Function}
242
          */
255
          */
243
         _onRoomLock() {
256
         _onRoomLock() {
245
         },
258
         },
246
 
259
 
247
         /**
260
         /**
248
-         * Switches between the front/user-facing and rear/environment-facing
261
+         * Toggles the audio-only flag of the conference.
262
+         *
263
+         * @private
264
+         * @returns {Object} Dispatched action.
265
+         * @type {Function}
266
+         */
267
+        _onToggleAudioOnly() {
268
+            return dispatch(toggleAudioOnly());
269
+        },
270
+
271
+        /**
272
+         * Switches between the front/user-facing and back/environment-facing
249
          * cameras.
273
          * cameras.
250
          *
274
          *
251
          * @private
275
          * @private
252
-         * @returns {Object} - Dispatched action.
276
+         * @returns {Object} Dispatched action.
253
          * @type {Function}
277
          * @type {Function}
254
          */
278
          */
255
         _onToggleCameraFacingMode() {
279
         _onToggleCameraFacingMode() {

Ładowanie…
Anuluj
Zapisz