feat(aot-prejoin) Add support for showing AOT on prejoin
This commit is contained in:
parent
dbcbafe088
commit
cc5a3e499f
|
@ -160,6 +160,7 @@ import { AudioMixerEffect } from './react/features/stream-effects/audio-mixer/Au
|
|||
import { createPresenterEffect } from './react/features/stream-effects/presenter';
|
||||
import { createRnnoiseProcessor } from './react/features/stream-effects/rnnoise';
|
||||
import { endpointMessageReceived } from './react/features/subtitles';
|
||||
import { handleToggleVideoMuted } from './react/features/toolbox/actions.any';
|
||||
import { muteLocal } from './react/features/video-menu/actions.any';
|
||||
import UIEvents from './service/UI/UIEvents';
|
||||
|
||||
|
@ -1152,9 +1153,13 @@ export default {
|
|||
* Simulates toolbar button click for video mute. Used by shortcuts and API.
|
||||
* @param {boolean} [showUI] when set to false will not display any error
|
||||
* dialogs in case of media permissions error.
|
||||
* @param {boolean} ensureTrack - True if we want to ensure that a new track is
|
||||
* created if missing.
|
||||
*/
|
||||
toggleVideoMuted(showUI = true) {
|
||||
this.muteVideo(!this.isLocalVideoMuted(), showUI);
|
||||
toggleVideoMuted(showUI = true, ensureTrack = false) {
|
||||
const mute = !this.isLocalVideoMuted();
|
||||
|
||||
APP.store.dispatch(handleToggleVideoMuted(mute, showUI, ensureTrack));
|
||||
},
|
||||
|
||||
/**
|
||||
|
@ -2421,8 +2426,8 @@ export default {
|
|||
APP.UI.addListener(UIEvents.AUDIO_MUTED, muted => {
|
||||
this.muteAudio(muted);
|
||||
});
|
||||
APP.UI.addListener(UIEvents.VIDEO_MUTED, muted => {
|
||||
this.muteVideo(muted);
|
||||
APP.UI.addListener(UIEvents.VIDEO_MUTED, (muted, showUI = false) => {
|
||||
this.muteVideo(muted, showUI);
|
||||
});
|
||||
|
||||
room.addCommandListener(this.commands.defaults.ETHERPAD,
|
||||
|
|
|
@ -41,9 +41,11 @@ import {
|
|||
isLocalParticipantModerator,
|
||||
hasRaisedHand,
|
||||
grantModerator,
|
||||
overwriteParticipantsNames
|
||||
overwriteParticipantsNames,
|
||||
LOCAL_PARTICIPANT_DEFAULT_ID
|
||||
} from '../../react/features/base/participants';
|
||||
import { updateSettings } from '../../react/features/base/settings';
|
||||
import { getDisplayName } from '../../react/features/base/settings/functions.web';
|
||||
import { isToggleCameraEnabled, toggleCamera } from '../../react/features/base/tracks';
|
||||
import {
|
||||
autoAssignToBreakoutRooms,
|
||||
|
@ -63,6 +65,7 @@ import { openChat } from '../../react/features/chat/actions.web';
|
|||
import {
|
||||
processExternalDeviceRequest
|
||||
} from '../../react/features/device-selection/functions';
|
||||
import { appendSuffix } from '../../react/features/display-name';
|
||||
import { isEnabled as isDropboxEnabled } from '../../react/features/dropbox';
|
||||
import { setMediaEncryptionKey, toggleE2EE } from '../../react/features/e2ee/actions';
|
||||
import { setVolume } from '../../react/features/filmstrip';
|
||||
|
@ -299,7 +302,7 @@ function initCommands() {
|
|||
'toggle-video': () => {
|
||||
sendAnalytics(createApiEvent('toggle-video'));
|
||||
logger.log('Video toggle: API command received');
|
||||
APP.conference.toggleVideoMuted(false /* no UI */);
|
||||
APP.conference.toggleVideoMuted(false /* no UI */, true /* ensure track */);
|
||||
},
|
||||
'toggle-film-strip': () => {
|
||||
sendAnalytics(createApiEvent('film.strip.toggled'));
|
||||
|
@ -1451,6 +1454,39 @@ class API {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify external application (if API is enabled) that the prejoin video
|
||||
* visibility had changed.
|
||||
*
|
||||
* @param {boolean} isVisible - Whether the prejoin video is visible.
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyPrejoinVideoVisibilityChanged(isVisible: boolean) {
|
||||
this._sendEvent({
|
||||
name: 'on-prejoin-video-changed',
|
||||
isVisible
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify external application (if API is enabled) that the prejoin
|
||||
* screen was loaded.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
notifyPrejoinLoaded() {
|
||||
const state = APP.store.getState();
|
||||
const { defaultLocalDisplayName } = state['features/base/config'];
|
||||
const displayName = getDisplayName(state);
|
||||
|
||||
this._sendEvent({
|
||||
name: 'prejoin-screen-loaded',
|
||||
id: LOCAL_PARTICIPANT_DEFAULT_ID,
|
||||
displayName,
|
||||
formattedDisplayName: appendSuffix(displayName, defaultLocalDisplayName)
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Notify external application of an unexpected camera-related error having
|
||||
* occurred.
|
||||
|
|
|
@ -132,6 +132,7 @@ const events = {
|
|||
'participant-role-changed': 'participantRoleChanged',
|
||||
'participants-pane-toggled': 'participantsPaneToggled',
|
||||
'password-required': 'passwordRequired',
|
||||
'prejoin-screen-loaded': 'prejoinScreenLoaded',
|
||||
'proxy-connection-event': 'proxyConnectionEvent',
|
||||
'raise-hand-updated': 'raiseHandUpdated',
|
||||
'recording-link-available': 'recordingLinkAvailable',
|
||||
|
@ -356,7 +357,8 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
|||
this.invite(invitees);
|
||||
}
|
||||
this._tmpE2EEKey = e2eeKey;
|
||||
this._isLargeVideoVisible = true;
|
||||
this._isLargeVideoVisible = false;
|
||||
this._isPrejoinVideoVisible = false;
|
||||
this._numberOfParticipants = 0;
|
||||
this._participants = {};
|
||||
this._myUserID = undefined;
|
||||
|
@ -464,6 +466,24 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
|||
return iframe.contentWindow.document.getElementById('largeVideo');
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for the prejoin video element in Jitsi Meet.
|
||||
*
|
||||
* @returns {HTMLElement|undefined} - The prejoin video.
|
||||
*/
|
||||
_getPrejoinVideo() {
|
||||
const iframe = this.getIFrame();
|
||||
|
||||
if (!this._isPrejoinVideoVisible
|
||||
|| !iframe
|
||||
|| !iframe.contentWindow
|
||||
|| !iframe.contentWindow.document) {
|
||||
return;
|
||||
}
|
||||
|
||||
return iframe.contentWindow.document.getElementById('prejoinVideo');
|
||||
}
|
||||
|
||||
/**
|
||||
* Getter for participant specific video element in Jitsi Meet.
|
||||
*
|
||||
|
@ -582,6 +602,16 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
|
|||
this._isLargeVideoVisible = data.isVisible;
|
||||
this.emit('largeVideoChanged');
|
||||
break;
|
||||
case 'prejoin-screen-loaded':
|
||||
this._participants[userID] = {
|
||||
displayName: data.displayName,
|
||||
formattedDisplayName: data.formattedDisplayName
|
||||
};
|
||||
break;
|
||||
case 'on-prejoin-video-changed':
|
||||
this._isPrejoinVideoVisible = data.isVisible;
|
||||
this.emit('prejoinVideoChanged');
|
||||
break;
|
||||
case 'video-conference-left':
|
||||
changeParticipantNumber(this, -1);
|
||||
delete this._participants[this._myUserID];
|
||||
|
|
|
@ -61,8 +61,8 @@ export default class AlwaysOnTop extends Component<*, State> {
|
|||
this._avatarChangedListener = this._avatarChangedListener.bind(this);
|
||||
this._displayNameChangedListener
|
||||
= this._displayNameChangedListener.bind(this);
|
||||
this._largeVideoChangedListener
|
||||
= this._largeVideoChangedListener.bind(this);
|
||||
this._videoChangedListener
|
||||
= this._videoChangedListener.bind(this);
|
||||
this._mouseMove = this._mouseMove.bind(this);
|
||||
this._onMouseOut = this._onMouseOut.bind(this);
|
||||
this._onMouseOver = this._onMouseOver.bind(this);
|
||||
|
@ -118,19 +118,19 @@ export default class AlwaysOnTop extends Component<*, State> {
|
|||
TOOLBAR_TIMEOUT);
|
||||
}
|
||||
|
||||
_largeVideoChangedListener: () => void;
|
||||
_videoChangedListener: () => void;
|
||||
|
||||
/**
|
||||
* Handles large video changed api events.
|
||||
*
|
||||
* @returns {void}
|
||||
*/
|
||||
_largeVideoChangedListener() {
|
||||
_videoChangedListener() {
|
||||
const userID = api._getOnStageParticipant();
|
||||
const avatarURL = api.getAvatarURL(userID);
|
||||
const displayName = api.getDisplayName(userID);
|
||||
const formattedDisplayName = api._getFormattedDisplayName(userID);
|
||||
const isVideoDisplayed = Boolean(api._getLargeVideo());
|
||||
const isVideoDisplayed = Boolean(api._getPrejoinVideo?.() || api._getLargeVideo());
|
||||
|
||||
this.setState({
|
||||
avatarURL,
|
||||
|
@ -185,8 +185,7 @@ export default class AlwaysOnTop extends Component<*, State> {
|
|||
customAvatarBackgrounds,
|
||||
displayName,
|
||||
formattedDisplayName,
|
||||
isVideoDisplayed,
|
||||
userID
|
||||
isVideoDisplayed
|
||||
} = this.state;
|
||||
|
||||
if (isVideoDisplayed) {
|
||||
|
@ -197,10 +196,10 @@ export default class AlwaysOnTop extends Component<*, State> {
|
|||
<div id = 'videoNotAvailableScreen'>
|
||||
<div id = 'avatarContainer'>
|
||||
<StatelessAvatar
|
||||
color = { getAvatarColor(userID, customAvatarBackgrounds) }
|
||||
color = { getAvatarColor(displayName, customAvatarBackgrounds) }
|
||||
id = 'avatar'
|
||||
initials = { getInitials(displayName) }
|
||||
url = { avatarURL } />)
|
||||
url = { displayName ? null : avatarURL } />)
|
||||
</div>
|
||||
<div
|
||||
className = 'displayname'
|
||||
|
@ -220,9 +219,11 @@ export default class AlwaysOnTop extends Component<*, State> {
|
|||
componentDidMount() {
|
||||
api.on('avatarChanged', this._avatarChangedListener);
|
||||
api.on('displayNameChange', this._displayNameChangedListener);
|
||||
api.on('largeVideoChanged', this._largeVideoChangedListener);
|
||||
api.on('largeVideoChanged', this._videoChangedListener);
|
||||
api.on('prejoinVideoChanged', this._videoChangedListener);
|
||||
api.on('videoConferenceJoined', this._videoChangedListener);
|
||||
|
||||
this._largeVideoChangedListener();
|
||||
this._videoChangedListener();
|
||||
|
||||
window.addEventListener('mousemove', this._mouseMove);
|
||||
|
||||
|
@ -260,7 +261,13 @@ export default class AlwaysOnTop extends Component<*, State> {
|
|||
this._displayNameChangedListener);
|
||||
api.removeListener(
|
||||
'largeVideoChanged',
|
||||
this._largeVideoChangedListener);
|
||||
this._videoChangedListener);
|
||||
api.removeListener(
|
||||
'prejoinVideoChanged',
|
||||
this._videoChangedListener);
|
||||
api.removeListener(
|
||||
'videoConferenceJoined',
|
||||
this._videoChangedListener);
|
||||
|
||||
window.removeEventListener('mousemove', this._mouseMove);
|
||||
}
|
||||
|
|
|
@ -124,7 +124,7 @@ export default class VideoMuteButton extends Component<Props, State> {
|
|||
* @returns {void}
|
||||
*/
|
||||
_setVideoMuted(videoMuted: boolean) { // eslint-disable-line no-unused-vars
|
||||
this.state.videoAvailable && api.executeCommand('toggleVideo');
|
||||
this.state.videoAvailable && api.executeCommand('toggleVideo', false, true);
|
||||
}
|
||||
|
||||
_videoAvailabilityListener: ({ available: boolean }) => void;
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
// @flow
|
||||
|
||||
import React from 'react';
|
||||
import React, { useEffect } from 'react';
|
||||
|
||||
import { getDisplayName } from '../../../../base/settings';
|
||||
import { Avatar } from '../../../avatar';
|
||||
|
@ -9,6 +9,8 @@ import { getLocalParticipant } from '../../../participants';
|
|||
import { connect } from '../../../redux';
|
||||
import { getLocalVideoTrack } from '../../../tracks';
|
||||
|
||||
declare var APP: Object;
|
||||
|
||||
export type Props = {
|
||||
|
||||
/**
|
||||
|
@ -47,12 +49,23 @@ function Preview(props: Props) {
|
|||
const { _participantId, flipVideo, name, videoMuted, videoTrack } = props;
|
||||
const className = flipVideo ? 'flipVideoX' : '';
|
||||
|
||||
useEffect(() => {
|
||||
APP.API.notifyPrejoinVideoVisibilityChanged(Boolean(!videoMuted && videoTrack));
|
||||
}, [ videoMuted, videoTrack ]);
|
||||
|
||||
useEffect(() => {
|
||||
APP.API.notifyPrejoinLoaded();
|
||||
|
||||
return () => APP.API.notifyPrejoinVideoVisibilityChanged(false);
|
||||
}, []);
|
||||
|
||||
return (
|
||||
<div id = 'preview'>
|
||||
{!videoMuted && videoTrack
|
||||
? (
|
||||
<Video
|
||||
className = { className }
|
||||
id = 'prejoinVideo'
|
||||
videoTrack = {{ jitsiTrack: videoTrack }} />
|
||||
)
|
||||
: (
|
||||
|
|
|
@ -2,12 +2,20 @@
|
|||
|
||||
import type { Dispatch } from 'redux';
|
||||
|
||||
import UIEvents from '../../../service/UI/UIEvents';
|
||||
import { createToolbarEvent, sendAnalytics, VIDEO_MUTE } from '../analytics';
|
||||
import { setAudioOnly } from '../base/audio-only';
|
||||
import { setVideoMuted, VIDEO_MUTISM_AUTHORITY } from '../base/media';
|
||||
import { getLocalVideoType } from '../base/tracks';
|
||||
|
||||
import {
|
||||
SET_TOOLBOX_ENABLED,
|
||||
SET_TOOLBOX_VISIBLE,
|
||||
TOGGLE_TOOLBOX_VISIBLE
|
||||
} from './actionTypes';
|
||||
|
||||
declare var APP: Object;
|
||||
|
||||
/**
|
||||
* Enables/disables the toolbox.
|
||||
*
|
||||
|
@ -65,3 +73,40 @@ export function toggleToolboxVisible() {
|
|||
});
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Action to handle toggle video from toolbox's video buttons.
|
||||
*
|
||||
* @param {boolean} muted - Whether to mute or unmute.
|
||||
* @param {boolean} showUI - When set to false will not display any error.
|
||||
* @param {boolean} ensureTrack - True if we want to ensure that a new track is
|
||||
* created if missing.
|
||||
* @returns {Function}
|
||||
*/
|
||||
export function handleToggleVideoMuted(muted, showUI, ensureTrack) {
|
||||
return (dispatch: Dispatch<any>, getState: Function) => {
|
||||
const state = getState();
|
||||
const { enabled: audioOnly } = state['features/base/audio-only'];
|
||||
const tracks = state['features/base/tracks'];
|
||||
|
||||
sendAnalytics(createToolbarEvent(VIDEO_MUTE, { enable: muted }));
|
||||
if (audioOnly) {
|
||||
dispatch(setAudioOnly(false));
|
||||
}
|
||||
const mediaType = getLocalVideoType(tracks);
|
||||
|
||||
dispatch(
|
||||
setVideoMuted(
|
||||
muted,
|
||||
mediaType,
|
||||
VIDEO_MUTISM_AUTHORITY.USER,
|
||||
ensureTrack));
|
||||
|
||||
// FIXME: The old conference logic still relies on this event being
|
||||
// emitted.
|
||||
typeof APP === 'undefined'
|
||||
|| APP.UI.emitEvent(UIEvents.VIDEO_MUTED, muted, showUI);
|
||||
|
||||
};
|
||||
}
|
||||
|
|
|
@ -1,24 +1,18 @@
|
|||
// @flow
|
||||
|
||||
import UIEvents from '../../../../service/UI/UIEvents';
|
||||
import {
|
||||
ACTION_SHORTCUT_TRIGGERED,
|
||||
VIDEO_MUTE,
|
||||
createShortcutEvent,
|
||||
createToolbarEvent,
|
||||
sendAnalytics
|
||||
} from '../../analytics';
|
||||
import { setAudioOnly } from '../../base/audio-only';
|
||||
import { getFeatureFlag, VIDEO_MUTE_BUTTON_ENABLED } from '../../base/flags';
|
||||
import { translate } from '../../base/i18n';
|
||||
import {
|
||||
VIDEO_MUTISM_AUTHORITY,
|
||||
setVideoMuted
|
||||
} from '../../base/media';
|
||||
import { connect } from '../../base/redux';
|
||||
import { AbstractButton, AbstractVideoMuteButton } from '../../base/toolbox/components';
|
||||
import type { AbstractButtonProps } from '../../base/toolbox/components';
|
||||
import { getLocalVideoType, isLocalCameraTrackMuted } from '../../base/tracks';
|
||||
import { isLocalCameraTrackMuted } from '../../base/tracks';
|
||||
import { handleToggleVideoMuted } from '../actions.any';
|
||||
import { isVideoMuteButtonDisabled } from '../functions';
|
||||
|
||||
declare var APP: Object;
|
||||
|
@ -28,15 +22,6 @@ declare var APP: Object;
|
|||
*/
|
||||
type Props = AbstractButtonProps & {
|
||||
|
||||
/**
|
||||
* Whether the current conference is in audio only mode or not.
|
||||
*/
|
||||
_audioOnly: boolean,
|
||||
|
||||
/**
|
||||
* MEDIA_TYPE of the local video.
|
||||
*/
|
||||
_videoMediaType: string,
|
||||
|
||||
/**
|
||||
* Whether video is currently muted or not.
|
||||
|
@ -158,23 +143,7 @@ class VideoMuteButton extends AbstractVideoMuteButton<Props, *> {
|
|||
* @returns {void}
|
||||
*/
|
||||
_setVideoMuted(videoMuted: boolean) {
|
||||
sendAnalytics(createToolbarEvent(VIDEO_MUTE, { enable: videoMuted }));
|
||||
if (this.props._audioOnly) {
|
||||
this.props.dispatch(setAudioOnly(false));
|
||||
}
|
||||
const mediaType = this.props._videoMediaType;
|
||||
|
||||
this.props.dispatch(
|
||||
setVideoMuted(
|
||||
videoMuted,
|
||||
mediaType,
|
||||
VIDEO_MUTISM_AUTHORITY.USER,
|
||||
/* ensureTrack */ true));
|
||||
|
||||
// FIXME: The old conference logic still relies on this event being
|
||||
// emitted.
|
||||
typeof APP === 'undefined'
|
||||
|| APP.UI.emitEvent(UIEvents.VIDEO_MUTED, videoMuted, true);
|
||||
this.props.dispatch(handleToggleVideoMuted(videoMuted, true, true));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -185,19 +154,15 @@ class VideoMuteButton extends AbstractVideoMuteButton<Props, *> {
|
|||
* @param {Object} state - The Redux state.
|
||||
* @private
|
||||
* @returns {{
|
||||
* _audioOnly: boolean,
|
||||
* _videoMuted: boolean
|
||||
* }}
|
||||
*/
|
||||
function _mapStateToProps(state): Object {
|
||||
const { enabled: audioOnly } = state['features/base/audio-only'];
|
||||
const tracks = state['features/base/tracks'];
|
||||
const enabledFlag = getFeatureFlag(state, VIDEO_MUTE_BUTTON_ENABLED, true);
|
||||
|
||||
return {
|
||||
_audioOnly: Boolean(audioOnly),
|
||||
_videoDisabled: isVideoMuteButtonDisabled(state),
|
||||
_videoMediaType: getLocalVideoType(tracks),
|
||||
_videoMuted: isLocalCameraTrackMuted(tracks),
|
||||
visible: enabledFlag
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue