瀏覽代碼

fix(conference): require user interaction to unmute in iframe (#4429)

* fix(conference): require user interaction to unmute in iframe

* squash: explicitly whitelist react native
master
virtuacoplenny 6 年之前
父節點
當前提交
e7f9e8e7f7

+ 19
- 1
conference.js 查看文件

@@ -101,6 +101,7 @@ import {
101 101
     createLocalTracksF,
102 102
     destroyLocalTracks,
103 103
     isLocalTrackMuted,
104
+    isUserInteractionRequiredForUnmute,
104 105
     replaceLocalTrack,
105 106
     trackAdded,
106 107
     trackRemoved
@@ -663,8 +664,11 @@ export default {
663 664
                 options.roomName, {
664 665
                     startAudioOnly: config.startAudioOnly,
665 666
                     startScreenSharing: config.startScreenSharing,
666
-                    startWithAudioMuted: config.startWithAudioMuted || config.startSilent,
667
+                    startWithAudioMuted: config.startWithAudioMuted
668
+                        || config.startSilent
669
+                        || isUserInteractionRequiredForUnmute(APP.store.getState()),
667 670
                     startWithVideoMuted: config.startWithVideoMuted
671
+                        || isUserInteractionRequiredForUnmute(APP.store.getState())
668 672
                 }))
669 673
             .then(([ tracks, con ]) => {
670 674
                 tracks.forEach(track => {
@@ -765,6 +769,13 @@ export default {
765 769
      * dialogs in case of media permissions error.
766 770
      */
767 771
     muteAudio(mute, showUI = true) {
772
+        if (!mute
773
+                && isUserInteractionRequiredForUnmute(APP.store.getState())) {
774
+            logger.error('Unmuting audio requires user interaction');
775
+
776
+            return;
777
+        }
778
+
768 779
         // Not ready to modify track's state yet
769 780
         if (!this._localTracksInitialized) {
770 781
             // This will only modify base/media.audio.muted which is then synced
@@ -828,6 +839,13 @@ export default {
828 839
      * dialogs in case of media permissions error.
829 840
      */
830 841
     muteVideo(mute, showUI = true) {
842
+        if (!mute
843
+                && isUserInteractionRequiredForUnmute(APP.store.getState())) {
844
+            logger.error('Unmuting video requires user interaction');
845
+
846
+            return;
847
+        }
848
+
831 849
         // If not ready to modify track's state yet adjust the base/media
832 850
         if (!this._localTracksInitialized) {
833 851
             // This will only modify base/media.video.muted which is then synced

+ 1
- 0
react/features/app/components/App.web.js 查看文件

@@ -4,6 +4,7 @@ import { AtlasKitThemeProvider } from '@atlaskit/theme';
4 4
 import React from 'react';
5 5
 
6 6
 import { DialogContainer } from '../../base/dialog';
7
+import '../../base/user-interaction';
7 8
 import '../../base/responsive-ui';
8 9
 import '../../chat';
9 10
 import '../../external-api';

+ 19
- 0
react/features/base/tracks/functions.js 查看文件

@@ -228,6 +228,25 @@ export function isRemoteTrackMuted(tracks, mediaType, participantId) {
228 228
     return !track || track.muted;
229 229
 }
230 230
 
231
+/**
232
+ * Returns whether or not the current environment needs a user interaction with
233
+ * the page before any unmute can occur.
234
+ *
235
+ * @param {Object} state - The redux state.
236
+ * @returns {boolean}
237
+ */
238
+export function isUserInteractionRequiredForUnmute(state) {
239
+    const { browser } = JitsiMeetJS.util;
240
+
241
+    return !browser.isReactNative()
242
+        && !browser.isChrome()
243
+        && !browser.isChromiumBased()
244
+        && !browser.isElectron()
245
+        && window
246
+        && window.self !== window.top
247
+        && !state['features/base/user-interaction'].interacted;
248
+}
249
+
231 250
 /**
232 251
  * Mutes or unmutes a specific {@code JitsiLocalTrack}. If the muted state of
233 252
  * the specified {@code track} is already in accord with the specified

+ 16
- 1
react/features/base/tracks/middleware.js 查看文件

@@ -24,7 +24,12 @@ import {
24 24
     TRACK_REMOVED,
25 25
     TRACK_UPDATED
26 26
 } from './actionTypes';
27
-import { getLocalTrack, getTrackByJitsiTrack, setTrackMuted } from './functions';
27
+import {
28
+    getLocalTrack,
29
+    getTrackByJitsiTrack,
30
+    isUserInteractionRequiredForUnmute,
31
+    setTrackMuted
32
+} from './functions';
28 33
 
29 34
 declare var APP: Object;
30 35
 
@@ -50,6 +55,11 @@ MiddlewareRegistry.register(store => next => action => {
50 55
         break;
51 56
     }
52 57
     case SET_AUDIO_MUTED:
58
+        if (!action.muted
59
+                && isUserInteractionRequiredForUnmute(store.getState())) {
60
+            return;
61
+        }
62
+
53 63
         _setMuted(store, action, MEDIA_TYPE.AUDIO);
54 64
         break;
55 65
 
@@ -74,6 +84,11 @@ MiddlewareRegistry.register(store => next => action => {
74 84
     }
75 85
 
76 86
     case SET_VIDEO_MUTED:
87
+        if (!action.muted
88
+                && isUserInteractionRequiredForUnmute(store.getState())) {
89
+            return;
90
+        }
91
+
77 92
         _setMuted(store, action, MEDIA_TYPE.VIDEO);
78 93
         break;
79 94
 

+ 20
- 0
react/features/base/user-interaction/actionTypes.js 查看文件

@@ -0,0 +1,20 @@
1
+/**
2
+ * The type of (redux) action which signals that an event listener has been
3
+ * added to listen for any user interaction with the page.
4
+ *
5
+ * {
6
+ *     type: SET_USER_INTERACTION_LISTENER,
7
+ *     userInteractionListener: Function
8
+ * }
9
+ */
10
+export const SET_USER_INTERACTION_LISTENER = 'SET_USER_INTERACTION_LISTENER';
11
+
12
+/**
13
+ * The type of (redux) action which signals the user has interacted with the
14
+ * page.
15
+ *
16
+ * {
17
+ *     type: USER_INTERACTION_RECEIVED,
18
+ * }
19
+ */
20
+export const USER_INTERACTION_RECEIVED = 'USER_INTERACTION_RECEIVED';

+ 2
- 0
react/features/base/user-interaction/index.js 查看文件

@@ -0,0 +1,2 @@
1
+import './middleware';
2
+import './reducer';

+ 79
- 0
react/features/base/user-interaction/middleware.js 查看文件

@@ -0,0 +1,79 @@
1
+// @flow
2
+
3
+import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../app';
4
+import { MiddlewareRegistry } from '../redux';
5
+
6
+import {
7
+    SET_USER_INTERACTION_LISTENER,
8
+    USER_INTERACTION_RECEIVED
9
+} from './actionTypes';
10
+
11
+/**
12
+ * Implements the entry point of the middleware of the feature base/user-interaction.
13
+ *
14
+ * @param {Store} store - The redux store.
15
+ * @returns {Function}
16
+ */
17
+MiddlewareRegistry.register(store => next => action => {
18
+    switch (action.type) {
19
+    case APP_WILL_MOUNT:
20
+        _startListeningForUserInteraction(store);
21
+        break;
22
+
23
+    case APP_WILL_UNMOUNT:
24
+    case USER_INTERACTION_RECEIVED:
25
+        _stopListeningForUserInteraction(store);
26
+        break;
27
+    }
28
+
29
+    return next(action);
30
+});
31
+
32
+/**
33
+ * Registers listeners to notify redux of any user interaction with the page.
34
+ *
35
+ * @param {Object} store - The redux store.
36
+ * @private
37
+ * @returns {void}
38
+ */
39
+function _startListeningForUserInteraction(store) {
40
+    const userInteractionListener = event => {
41
+        if (event.isTrusted) {
42
+            store.dispatch({
43
+                type: USER_INTERACTION_RECEIVED
44
+            });
45
+
46
+            _stopListeningForUserInteraction(store);
47
+        }
48
+    };
49
+
50
+    window.addEventListener('mousedown', userInteractionListener);
51
+    window.addEventListener('keydown', userInteractionListener);
52
+
53
+    store.dispatch({
54
+        type: SET_USER_INTERACTION_LISTENER,
55
+        userInteractionListener
56
+    });
57
+}
58
+
59
+/**
60
+ * Un-registers listeners intended to notify when the user has interacted with
61
+ * the page.
62
+ *
63
+ * @param {Object} store - The redux store.
64
+ * @private
65
+ * @returns {void}
66
+ */
67
+function _stopListeningForUserInteraction({ getState, dispatch }) {
68
+    const { userInteractionListener } = getState()['features/base/app'];
69
+
70
+    if (userInteractionListener) {
71
+        window.removeEventListener('mousedown', userInteractionListener);
72
+        window.removeEventListener('keydown', userInteractionListener);
73
+
74
+        dispatch({
75
+            type: SET_USER_INTERACTION_LISTENER,
76
+            userInteractionListener: undefined
77
+        });
78
+    }
79
+}

+ 34
- 0
react/features/base/user-interaction/reducer.js 查看文件

@@ -0,0 +1,34 @@
1
+// @flow
2
+
3
+import { ReducerRegistry } from '../redux';
4
+
5
+import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../app';
6
+import {
7
+    SET_USER_INTERACTION_LISTENER,
8
+    USER_INTERACTION_RECEIVED
9
+} from './actionTypes';
10
+
11
+ReducerRegistry.register('features/base/user-interaction', (state = {}, action) => {
12
+    switch (action.type) {
13
+    case APP_WILL_MOUNT:
14
+    case APP_WILL_UNMOUNT: {
15
+        return {
16
+            ...state,
17
+            interacted: false
18
+        };
19
+    }
20
+    case SET_USER_INTERACTION_LISTENER:
21
+        return {
22
+            ...state,
23
+            userInteractionListener: action.userInteractionListener
24
+        };
25
+
26
+    case USER_INTERACTION_RECEIVED:
27
+        return {
28
+            ...state,
29
+            interacted: true
30
+        };
31
+    }
32
+
33
+    return state;
34
+});

Loading…
取消
儲存