Преглед изворни кода

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
     createLocalTracksF,
101
     createLocalTracksF,
102
     destroyLocalTracks,
102
     destroyLocalTracks,
103
     isLocalTrackMuted,
103
     isLocalTrackMuted,
104
+    isUserInteractionRequiredForUnmute,
104
     replaceLocalTrack,
105
     replaceLocalTrack,
105
     trackAdded,
106
     trackAdded,
106
     trackRemoved
107
     trackRemoved
663
                 options.roomName, {
664
                 options.roomName, {
664
                     startAudioOnly: config.startAudioOnly,
665
                     startAudioOnly: config.startAudioOnly,
665
                     startScreenSharing: config.startScreenSharing,
666
                     startScreenSharing: config.startScreenSharing,
666
-                    startWithAudioMuted: config.startWithAudioMuted || config.startSilent,
667
+                    startWithAudioMuted: config.startWithAudioMuted
668
+                        || config.startSilent
669
+                        || isUserInteractionRequiredForUnmute(APP.store.getState()),
667
                     startWithVideoMuted: config.startWithVideoMuted
670
                     startWithVideoMuted: config.startWithVideoMuted
671
+                        || isUserInteractionRequiredForUnmute(APP.store.getState())
668
                 }))
672
                 }))
669
             .then(([ tracks, con ]) => {
673
             .then(([ tracks, con ]) => {
670
                 tracks.forEach(track => {
674
                 tracks.forEach(track => {
765
      * dialogs in case of media permissions error.
769
      * dialogs in case of media permissions error.
766
      */
770
      */
767
     muteAudio(mute, showUI = true) {
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
         // Not ready to modify track's state yet
779
         // Not ready to modify track's state yet
769
         if (!this._localTracksInitialized) {
780
         if (!this._localTracksInitialized) {
770
             // This will only modify base/media.audio.muted which is then synced
781
             // This will only modify base/media.audio.muted which is then synced
828
      * dialogs in case of media permissions error.
839
      * dialogs in case of media permissions error.
829
      */
840
      */
830
     muteVideo(mute, showUI = true) {
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
         // If not ready to modify track's state yet adjust the base/media
849
         // If not ready to modify track's state yet adjust the base/media
832
         if (!this._localTracksInitialized) {
850
         if (!this._localTracksInitialized) {
833
             // This will only modify base/media.video.muted which is then synced
851
             // This will only modify base/media.video.muted which is then synced

+ 1
- 0
react/features/app/components/App.web.js Прегледај датотеку

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

+ 19
- 0
react/features/base/tracks/functions.js Прегледај датотеку

228
     return !track || track.muted;
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
  * Mutes or unmutes a specific {@code JitsiLocalTrack}. If the muted state of
251
  * Mutes or unmutes a specific {@code JitsiLocalTrack}. If the muted state of
233
  * the specified {@code track} is already in accord with the specified
252
  * the specified {@code track} is already in accord with the specified

+ 16
- 1
react/features/base/tracks/middleware.js Прегледај датотеку

24
     TRACK_REMOVED,
24
     TRACK_REMOVED,
25
     TRACK_UPDATED
25
     TRACK_UPDATED
26
 } from './actionTypes';
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
 declare var APP: Object;
34
 declare var APP: Object;
30
 
35
 
50
         break;
55
         break;
51
     }
56
     }
52
     case SET_AUDIO_MUTED:
57
     case SET_AUDIO_MUTED:
58
+        if (!action.muted
59
+                && isUserInteractionRequiredForUnmute(store.getState())) {
60
+            return;
61
+        }
62
+
53
         _setMuted(store, action, MEDIA_TYPE.AUDIO);
63
         _setMuted(store, action, MEDIA_TYPE.AUDIO);
54
         break;
64
         break;
55
 
65
 
74
     }
84
     }
75
 
85
 
76
     case SET_VIDEO_MUTED:
86
     case SET_VIDEO_MUTED:
87
+        if (!action.muted
88
+                && isUserInteractionRequiredForUnmute(store.getState())) {
89
+            return;
90
+        }
91
+
77
         _setMuted(store, action, MEDIA_TYPE.VIDEO);
92
         _setMuted(store, action, MEDIA_TYPE.VIDEO);
78
         break;
93
         break;
79
 
94
 

+ 20
- 0
react/features/base/user-interaction/actionTypes.js Прегледај датотеку

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 Прегледај датотеку

1
+import './middleware';
2
+import './reducer';

+ 79
- 0
react/features/base/user-interaction/middleware.js Прегледај датотеку

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 Прегледај датотеку

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…
Откажи
Сачувај