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:
parent
a53a86ee7a
commit
e7f9e8e7f7
|
@ -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
|
||||||
|
|
|
@ -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';
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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;
|
||||||
|
|
||||||
|
|
|
@ -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';
|
|
@ -0,0 +1,2 @@
|
||||||
|
import './middleware';
|
||||||
|
import './reducer';
|
|
@ -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
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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;
|
||||||
|
});
|
Loading…
Reference in New Issue