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

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

* squash: explicitly whitelist react native
This commit is contained in:
virtuacoplenny 2019-07-10 04:02:27 -07:00 committed by Дамян Минков
parent a53a86ee7a
commit e7f9e8e7f7
8 changed files with 190 additions and 2 deletions

View File

@ -101,6 +101,7 @@ import {
createLocalTracksF, createLocalTracksF,
destroyLocalTracks, destroyLocalTracks,
isLocalTrackMuted, isLocalTrackMuted,
isUserInteractionRequiredForUnmute,
replaceLocalTrack, replaceLocalTrack,
trackAdded, trackAdded,
trackRemoved trackRemoved
@ -663,8 +664,11 @@ export default {
options.roomName, { options.roomName, {
startAudioOnly: config.startAudioOnly, startAudioOnly: config.startAudioOnly,
startScreenSharing: config.startScreenSharing, startScreenSharing: config.startScreenSharing,
startWithAudioMuted: config.startWithAudioMuted || config.startSilent, startWithAudioMuted: config.startWithAudioMuted
|| config.startSilent
|| isUserInteractionRequiredForUnmute(APP.store.getState()),
startWithVideoMuted: config.startWithVideoMuted startWithVideoMuted: config.startWithVideoMuted
|| isUserInteractionRequiredForUnmute(APP.store.getState())
})) }))
.then(([ tracks, con ]) => { .then(([ tracks, con ]) => {
tracks.forEach(track => { tracks.forEach(track => {
@ -765,6 +769,13 @@ export default {
* dialogs in case of media permissions error. * dialogs in case of media permissions error.
*/ */
muteAudio(mute, showUI = true) { muteAudio(mute, showUI = true) {
if (!mute
&& isUserInteractionRequiredForUnmute(APP.store.getState())) {
logger.error('Unmuting audio requires user interaction');
return;
}
// Not ready to modify track's state yet // Not ready to modify track's state yet
if (!this._localTracksInitialized) { if (!this._localTracksInitialized) {
// This will only modify base/media.audio.muted which is then synced // This will only modify base/media.audio.muted which is then synced
@ -828,6 +839,13 @@ export default {
* dialogs in case of media permissions error. * dialogs in case of media permissions error.
*/ */
muteVideo(mute, showUI = true) { muteVideo(mute, showUI = true) {
if (!mute
&& isUserInteractionRequiredForUnmute(APP.store.getState())) {
logger.error('Unmuting video requires user interaction');
return;
}
// If not ready to modify track's state yet adjust the base/media // If not ready to modify track's state yet adjust the base/media
if (!this._localTracksInitialized) { if (!this._localTracksInitialized) {
// This will only modify base/media.video.muted which is then synced // This will only modify base/media.video.muted which is then synced

View File

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

View File

@ -228,6 +228,25 @@ export function isRemoteTrackMuted(tracks, mediaType, participantId) {
return !track || track.muted; return !track || track.muted;
} }
/**
* Returns whether or not the current environment needs a user interaction with
* the page before any unmute can occur.
*
* @param {Object} state - The redux state.
* @returns {boolean}
*/
export function isUserInteractionRequiredForUnmute(state) {
const { browser } = JitsiMeetJS.util;
return !browser.isReactNative()
&& !browser.isChrome()
&& !browser.isChromiumBased()
&& !browser.isElectron()
&& window
&& window.self !== window.top
&& !state['features/base/user-interaction'].interacted;
}
/** /**
* Mutes or unmutes a specific {@code JitsiLocalTrack}. If the muted state of * Mutes or unmutes a specific {@code JitsiLocalTrack}. If the muted state of
* the specified {@code track} is already in accord with the specified * the specified {@code track} is already in accord with the specified

View File

@ -24,7 +24,12 @@ import {
TRACK_REMOVED, TRACK_REMOVED,
TRACK_UPDATED TRACK_UPDATED
} from './actionTypes'; } from './actionTypes';
import { getLocalTrack, getTrackByJitsiTrack, setTrackMuted } from './functions'; import {
getLocalTrack,
getTrackByJitsiTrack,
isUserInteractionRequiredForUnmute,
setTrackMuted
} from './functions';
declare var APP: Object; declare var APP: Object;
@ -50,6 +55,11 @@ MiddlewareRegistry.register(store => next => action => {
break; break;
} }
case SET_AUDIO_MUTED: case SET_AUDIO_MUTED:
if (!action.muted
&& isUserInteractionRequiredForUnmute(store.getState())) {
return;
}
_setMuted(store, action, MEDIA_TYPE.AUDIO); _setMuted(store, action, MEDIA_TYPE.AUDIO);
break; break;
@ -74,6 +84,11 @@ MiddlewareRegistry.register(store => next => action => {
} }
case SET_VIDEO_MUTED: case SET_VIDEO_MUTED:
if (!action.muted
&& isUserInteractionRequiredForUnmute(store.getState())) {
return;
}
_setMuted(store, action, MEDIA_TYPE.VIDEO); _setMuted(store, action, MEDIA_TYPE.VIDEO);
break; break;

View File

@ -0,0 +1,20 @@
/**
* The type of (redux) action which signals that an event listener has been
* added to listen for any user interaction with the page.
*
* {
* type: SET_USER_INTERACTION_LISTENER,
* userInteractionListener: Function
* }
*/
export const SET_USER_INTERACTION_LISTENER = 'SET_USER_INTERACTION_LISTENER';
/**
* The type of (redux) action which signals the user has interacted with the
* page.
*
* {
* type: USER_INTERACTION_RECEIVED,
* }
*/
export const USER_INTERACTION_RECEIVED = 'USER_INTERACTION_RECEIVED';

View File

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

View File

@ -0,0 +1,79 @@
// @flow
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../app';
import { MiddlewareRegistry } from '../redux';
import {
SET_USER_INTERACTION_LISTENER,
USER_INTERACTION_RECEIVED
} from './actionTypes';
/**
* Implements the entry point of the middleware of the feature base/user-interaction.
*
* @param {Store} store - The redux store.
* @returns {Function}
*/
MiddlewareRegistry.register(store => next => action => {
switch (action.type) {
case APP_WILL_MOUNT:
_startListeningForUserInteraction(store);
break;
case APP_WILL_UNMOUNT:
case USER_INTERACTION_RECEIVED:
_stopListeningForUserInteraction(store);
break;
}
return next(action);
});
/**
* Registers listeners to notify redux of any user interaction with the page.
*
* @param {Object} store - The redux store.
* @private
* @returns {void}
*/
function _startListeningForUserInteraction(store) {
const userInteractionListener = event => {
if (event.isTrusted) {
store.dispatch({
type: USER_INTERACTION_RECEIVED
});
_stopListeningForUserInteraction(store);
}
};
window.addEventListener('mousedown', userInteractionListener);
window.addEventListener('keydown', userInteractionListener);
store.dispatch({
type: SET_USER_INTERACTION_LISTENER,
userInteractionListener
});
}
/**
* Un-registers listeners intended to notify when the user has interacted with
* the page.
*
* @param {Object} store - The redux store.
* @private
* @returns {void}
*/
function _stopListeningForUserInteraction({ getState, dispatch }) {
const { userInteractionListener } = getState()['features/base/app'];
if (userInteractionListener) {
window.removeEventListener('mousedown', userInteractionListener);
window.removeEventListener('keydown', userInteractionListener);
dispatch({
type: SET_USER_INTERACTION_LISTENER,
userInteractionListener: undefined
});
}
}

View File

@ -0,0 +1,34 @@
// @flow
import { ReducerRegistry } from '../redux';
import { APP_WILL_MOUNT, APP_WILL_UNMOUNT } from '../app';
import {
SET_USER_INTERACTION_LISTENER,
USER_INTERACTION_RECEIVED
} from './actionTypes';
ReducerRegistry.register('features/base/user-interaction', (state = {}, action) => {
switch (action.type) {
case APP_WILL_MOUNT:
case APP_WILL_UNMOUNT: {
return {
...state,
interacted: false
};
}
case SET_USER_INTERACTION_LISTENER:
return {
...state,
userInteractionListener: action.userInteractionListener
};
case USER_INTERACTION_RECEIVED:
return {
...state,
interacted: true
};
}
return state;
});