feat(aot-prejoin) Add support for showing AOT on prejoin

This commit is contained in:
Horatiu Muresan 2022-08-30 11:42:29 +03:00 committed by GitHub
parent dbcbafe088
commit cc5a3e499f
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 160 additions and 59 deletions

View File

@ -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,

View File

@ -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.

View File

@ -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];

View File

@ -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);
}

View File

@ -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;

View File

@ -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 }} />
)
: (

View File

@ -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);
};
}

View File

@ -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
};