Compare commits

...

12 Commits

Author SHA1 Message Date
Jaya Allamsetty 7ba41c7d2d fix(face-landmarks): Check mute state from redux before starting detection. 2022-09-28 11:49:42 -04:00
Jaya Allamsetty 58700abaeb fix(face-landmarks) Do not start detection on muted tracks 2022-09-26 11:54:22 -04:00
Jaya Allamsetty 47618f170e chore(deps) update lib-jitsi-meet.
This fixes an issue where mute camera operation doesn't stop sending camera stream even though locally it appears to the user that they are muted. This happens when multiple camera streams are added to peerconnection because of how toggle of the video button is implemented. This limitation will be removed when the application is fixed.
2022-09-21 13:31:23 -04:00
Jaya Allamsetty f4ff401365 chore(deps): Update lib-jitsi-meet.
Apply fixes for screensharing issues when call switches back and forth between p2p and jvb.
2022-09-19 13:29:40 -04:00
Horatiu Muresan 4864cb2d6a fix(lint) Fix eslint 2022-09-15 17:49:23 +03:00
Horatiu Muresan 5628177da0 fix(external-api) Add email to local participant info 2022-09-15 17:49:23 +03:00
Horatiu Muresan 5881a14afe fix(external-api) Fix notify audio muted/audio available 2022-09-15 17:49:23 +03:00
Saúl Ibarra Corretgé 658ec987d3 chore(deps) update ljm 2022-09-12 21:07:37 +02:00
Alexandru Petrus 322ea51831 fix(large-video) disable screen-sharing placeholder on Spot
Temporary hack, this should likely be configurable.
2022-09-12 20:59:30 +02:00
Horatiu Muresan 8cfc5e1435 fix(prejoin) Fix roomname showing for 3rd party prejoin app (#12155)
* fix(prejoin) Fix roomname showing for 3rd party prejoin app

* fix: Do not convert roomName to string when undefined.

Co-authored-by: damencho <damencho@jitsi.org>
2022-09-09 12:12:32 -05:00
Duduman Bogdan Vlad b709d079c9 fix(external_api): Fix number of participants in meeting (#12052) 2022-09-09 07:20:57 -05:00
Jaya Allamsetty fb402c7131 fix(large-video) Update large-video when the streamingStatus of the attached track changes.
This fixes an issue where screenshare appears on the thumbnail but not on the large-video.
2022-09-07 21:48:19 -05:00
23 changed files with 295 additions and 58 deletions

View File

@ -2983,7 +2983,6 @@ export default {
const available = audioDeviceCount > 0 || Boolean(localAudio);
APP.store.dispatch(setAudioAvailable(available));
APP.API.notifyAudioAvailabilityChanged(available);
},
/**
@ -3247,7 +3246,6 @@ export default {
*/
setAudioMuteStatus(muted) {
APP.UI.setAudioMuted(this.getMyUserId(), muted);
APP.API.notifyAudioMutedStatusChanged(muted);
},
/**

View File

@ -55,7 +55,7 @@ import {
removeBreakoutRoom,
sendParticipantToRoom
} from '../../react/features/breakout-rooms/actions';
import { getBreakoutRooms } from '../../react/features/breakout-rooms/functions';
import { getBreakoutRooms, getRoomsInfo } from '../../react/features/breakout-rooms/functions';
import {
sendMessage,
setPrivateMessageRecipient,
@ -846,6 +846,10 @@ function initCommands() {
callback(getBreakoutRooms(APP.store.getState()));
break;
}
case 'rooms-info': {
callback(getRoomsInfo(APP.store.getState()));
break;
}
default:
return false;
}

View File

@ -552,6 +552,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
this._myUserID = userID;
this._participants[userID] = {
email: data.email,
avatarURL: data.avatarURL
};
}
@ -619,6 +620,9 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
case 'video-quality-changed':
this._videoQuality = data.videoQuality;
break;
case 'breakout-rooms-updated':
this.updateNumberOfParticipants(data.rooms);
break;
case 'local-storage-changed':
jitsiLocalStorage.setItem('jitsiLocalStorage', data.localStorageContent);
@ -638,6 +642,40 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
});
}
/**
* Update number of participants based on all rooms.
*
* @param {Object} rooms - Rooms available rooms in the conference.
* @returns {void}
*/
updateNumberOfParticipants(rooms) {
if (!rooms || !Object.keys(rooms).length) {
return;
}
const allParticipants = Object.keys(rooms).reduce((prev, roomItemKey) => {
if (rooms[roomItemKey]?.participants) {
return Object.keys(rooms[roomItemKey].participants).length + prev;
}
return prev;
}, 0);
this._numberOfParticipants = allParticipants;
}
/**
* Returns the rooms info in the conference.
*
* @returns {Object} Rooms info.
*/
async getRoomsInfo() {
return this._transport.sendRequest({
name: 'rooms-info'
});
}
/**
* Adds event listener to Meet Jitsi.
*
@ -1101,7 +1139,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
}
/**
* Returns the number of participants in the conference. The local
* Returns the number of participants in the conference from all rooms. The local
* participant is included.
*
* @returns {int} The number of participants in the conference.

View File

@ -2,7 +2,7 @@
import Logger from '@jitsi/logger';
import { getSourceNameSignalingFeatureFlag } from '../../../react/features/base/config';
import { getMultipleVideoSupportFeatureFlag } from '../../../react/features/base/config';
import { MEDIA_TYPE, VIDEO_TYPE } from '../../../react/features/base/media';
import {
getPinnedParticipant,
@ -95,7 +95,7 @@ const VideoLayout = {
return VIDEO_TYPE.CAMERA;
}
if (getSourceNameSignalingFeatureFlag(state) && participant?.isVirtualScreenshareParticipant) {
if (getMultipleVideoSupportFeatureFlag(state) && participant?.isVirtualScreenshareParticipant) {
return VIDEO_TYPE.DESKTOP;
}
@ -190,7 +190,7 @@ const VideoLayout = {
let videoTrack;
if (getSourceNameSignalingFeatureFlag(state) && participant?.isVirtualScreenshareParticipant) {
if (getMultipleVideoSupportFeatureFlag(state) && participant?.isVirtualScreenshareParticipant) {
videoTrack = getVirtualScreenshareParticipantTrack(tracks, id);
} else {
videoTrack = getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, id);
@ -218,7 +218,6 @@ const VideoLayout = {
if (!isOnLarge || forceUpdate) {
const videoType = this.getRemoteVideoType(id);
largeVideo.updateLargeVideo(
id,
videoStream,

11
package-lock.json generated
View File

@ -70,7 +70,7 @@
"js-md5": "0.6.1",
"js-sha512": "0.8.0",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1494.0.0+c9be46e2/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet#96d3e93883898d384cfa7a22fed24bf7ca71ff20",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",
@ -12496,8 +12496,8 @@
},
"node_modules/lib-jitsi-meet": {
"version": "0.0.0",
"resolved": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1494.0.0+c9be46e2/lib-jitsi-meet.tgz",
"integrity": "sha512-7xH5ZRBab9/QYDbnQm7gidgehlKTgNlPxbDcylMqErFKbbAffZr5RKZLYgsPIg9RW1d+JXleGsKKcw4XOLXH4g==",
"resolved": "git+ssh://git@github.com/jitsi/lib-jitsi-meet.git#96d3e93883898d384cfa7a22fed24bf7ca71ff20",
"integrity": "sha512-YeJvRWtMrTgekfkr/+o1pE3rtBa9ZsPnS7KFQ146UHfXOK5c3MnVJ/4XzMDn0/H/3pkssYkai5XnUhVH8MrHWQ==",
"license": "Apache-2.0",
"dependencies": {
"@jitsi/js-utils": "2.0.0",
@ -29309,8 +29309,9 @@
}
},
"lib-jitsi-meet": {
"version": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1494.0.0+c9be46e2/lib-jitsi-meet.tgz",
"integrity": "sha512-7xH5ZRBab9/QYDbnQm7gidgehlKTgNlPxbDcylMqErFKbbAffZr5RKZLYgsPIg9RW1d+JXleGsKKcw4XOLXH4g==",
"version": "git+ssh://git@github.com/jitsi/lib-jitsi-meet.git#96d3e93883898d384cfa7a22fed24bf7ca71ff20",
"integrity": "sha512-YeJvRWtMrTgekfkr/+o1pE3rtBa9ZsPnS7KFQ146UHfXOK5c3MnVJ/4XzMDn0/H/3pkssYkai5XnUhVH8MrHWQ==",
"from": "lib-jitsi-meet@https://github.com/jitsi/lib-jitsi-meet#96d3e93883898d384cfa7a22fed24bf7ca71ff20",
"requires": {
"@jitsi/js-utils": "2.0.0",
"@jitsi/logger": "2.0.0",

View File

@ -75,7 +75,7 @@
"js-md5": "0.6.1",
"js-sha512": "0.8.0",
"jwt-decode": "2.2.0",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet/releases/download/v1494.0.0+c9be46e2/lib-jitsi-meet.tgz",
"lib-jitsi-meet": "https://github.com/jitsi/lib-jitsi-meet#96d3e93883898d384cfa7a22fed24bf7ca71ff20",
"lodash": "4.17.21",
"moment": "2.29.4",
"moment-duration-format": "2.2.2",

View File

@ -132,7 +132,7 @@ export function commonUserLeftHandling(
} else {
const isReplaced = user.isReplaced && user.isReplaced();
dispatch(participantLeft(id, conference, isReplaced));
dispatch(participantLeft(id, conference, { isReplaced }));
}
}
@ -192,7 +192,7 @@ export function getConferenceName(stateful: Function | Object): string {
|| subject
|| callDisplayName
|| (callee && callee.name)
|| safeStartCase(safeDecodeURIComponent(room));
|| (room && safeStartCase(safeDecodeURIComponent(room)));
}
/**

View File

@ -17,6 +17,8 @@ import MiddlewareRegistry from '../redux/MiddlewareRegistry';
import { SET_VIDEO_MUTED } from './actionTypes';
import './subscriber';
/**
* Implements the entry point of the middleware of the feature base/media.
*

View File

@ -0,0 +1,21 @@
import { IState, IStore } from '../../app/types';
import StateListenerRegistry from '../redux/StateListenerRegistry';
declare let APP: any;
/**
* Notifies when the local audio mute state changes.
*/
StateListenerRegistry.register(
/* selector */ (state: IState) => state['features/base/media'].audio.muted,
/* listener */ (muted: boolean, store: IStore, previousMuted: boolean) => {
if (typeof APP !== 'object') {
return;
}
if (muted !== previousMuted) {
APP.API.notifyAudioMutedStatusChanged(muted);
}
}
);

View File

@ -381,8 +381,13 @@ export function hiddenParticipantLeft(id: string) {
* with the participant identified by the specified {@code id}. Only the local
* participant is allowed to not specify an associated {@code JitsiConference}
* instance.
* @param {boolean} isReplaced - Whether the participant is to be replaced in the meeting.
* @param {boolean} isVirtualScreenshareParticipant - Whether the participant is a virtual screen share participant.
* @param {Object} participantLeftProps - Other participant properties.
* @typedef {Object} participantLeftProps
* @param {boolean} participantLeftProps.isReplaced - Whether the participant is to be replaced in the meeting.
* @param {boolean} participantLeftProps.isVirtualScreenshareParticipant - Whether the participant is a
* virtual screen share participant.
* @param {boolean} participantLeftProps.isFakeParticipant - Whether the participant is a fake participant.
*
* @returns {{
* type: PARTICIPANT_LEFT,
* participant: {
@ -391,15 +396,15 @@ export function hiddenParticipantLeft(id: string) {
* }
* }}
*/
export function participantLeft(id: string, conference: any,
isReplaced?: boolean, isVirtualScreenshareParticipant?: boolean) {
export function participantLeft(id: string, conference: any, participantLeftProps: any = {}) {
return {
type: PARTICIPANT_LEFT,
participant: {
conference,
id,
isReplaced,
isVirtualScreenshareParticipant
isReplaced: participantLeftProps.isReplaced,
isVirtualScreenshareParticipant: participantLeftProps.isVirtualScreenshareParticipant,
isFakeParticipant: participantLeftProps.isFakeParticipant
}
};
}

View File

@ -369,7 +369,9 @@ StateListenerRegistry.register(
batch(() => {
for (const [ id, p ] of getRemoteParticipants(getState())) {
(!conference || p.conference !== conference)
&& dispatch(participantLeft(id, p.conference, p.isReplaced));
&& dispatch(participantLeft(id, p.conference, {
isReplaced: p.isReplaced
}));
}
});
});

View File

@ -56,7 +56,10 @@ function _updateScreenshareParticipants({ getState, dispatch }) {
}
if (localScreenShare && !newLocalSceenshareSourceName) {
dispatch(participantLeft(localScreenShare.id, conference, undefined, true));
dispatch(participantLeft(localScreenShare.id, conference, {
isReplaced: undefined,
isVirtualScreenshareParticipant: true
}));
}
}
@ -64,7 +67,10 @@ function _updateScreenshareParticipants({ getState, dispatch }) {
const addedScreenshareSourceNames = _.difference(currentScreenshareSourceNames, previousScreenshareSourceNames);
if (removedScreenshareSourceNames.length) {
removedScreenshareSourceNames.forEach(id => dispatch(participantLeft(id, conference, undefined, true)));
removedScreenshareSourceNames.forEach(id => dispatch(participantLeft(id, conference, {
isReplaced: undefined,
isVirtualScreenshareParticipant: true
})));
}
if (addedScreenshareSourceNames.length) {

View File

@ -161,9 +161,11 @@ class PreMeetingScreen extends PureComponent<Props> {
<h1 className = 'title'>
{ title }
</h1>
{ _roomName && (
<span className = { classes.subtitle }>
{_roomName}
</span>
)}
{ children }
{ _buttons.length && <Toolbox toolbarButtons = { _buttons } /> }
{ skipPrejoinButton }

View File

@ -1,5 +1,6 @@
/* eslint-disable import/order */
import { Store } from 'redux';
import { IState } from '../../app/types';
import { equals } from './functions';
@ -27,7 +28,7 @@ type Listener
* The type selector supported for registration with
* {@link StateListenerRegistry} in association with a {@link Listener}.
*
* @param {Object} state - The redux state from which the {@code Selector} is to
* @param {IState} state - The redux state from which the {@code Selector} is to
* derive data.
* @param {any} prevSelection - The value previously derived from the redux
* store/state by the {@code Selector}. Provided in case the {@code Selector}
@ -37,7 +38,7 @@ type Listener
* {@code prevSelection}. The associated {@code Listener} will only be invoked
* if the returned value is other than {@code prevSelection}.
*/
type Selector = (state: Object, prevSelection: any) => any;
type Selector = (state: IState, prevSelection: any) => any;
/**
* Options that can be passed to the register method.

View File

@ -3,7 +3,7 @@
import _ from 'lodash';
import { getCurrentConference } from '../base/conference';
import { getParticipantCount, isLocalParticipantModerator } from '../base/participants';
import { getParticipantById, getParticipantCount, isLocalParticipantModerator } from '../base/participants';
import { toState } from '../base/redux';
import { FEATURE_KEY } from './constants';
@ -30,6 +30,69 @@ export const getMainRoom = (stateful: Function | Object) => {
return _.find(rooms, (room: Object) => room.isMainRoom);
};
export const getRoomsInfo = (stateful: Function | Object) => {
const breakoutRooms = getBreakoutRooms(stateful);
const conference = getCurrentConference(stateful);
const initialRoomsInfo = {
rooms: []
};
// only main roomn
if (!breakoutRooms || Object.keys(breakoutRooms).length === 0) {
return {
...initialRoomsInfo,
rooms: [ {
isMainRoom: true,
id: conference?.room?.roomjid,
jid: conference?.room?.myroomjid,
participants: conference && conference.participants && Object.keys(conference.participants).length
? Object.keys(conference.participants).map(participantId => {
const participantItem = conference?.participants[participantId];
const storeParticipant = getParticipantById(stateful, participantItem._id);
return {
jid: participantItem._jid,
role: participantItem._role,
displayName: participantItem._displayName,
avatarUrl: storeParticipant?.loadableAvatarUrl,
id: participantItem._id
};
}) : []
} ]
};
}
return {
...initialRoomsInfo,
rooms: Object.keys(breakoutRooms).map(breakoutRoomKey => {
const breakoutRoomItem = breakoutRooms[breakoutRoomKey];
return {
isMainRoom: Boolean(breakoutRoomItem.isMainRoom),
id: breakoutRoomItem.id,
jid: breakoutRoomItem.jid,
participants: breakoutRoomItem.participants && Object.keys(breakoutRoomItem.participants).length
? Object.keys(breakoutRoomItem.participants).map(participantLongId => {
const participantItem = breakoutRoomItem.participants[participantLongId];
const ids = participantLongId.split('/');
const storeParticipant = getParticipantById(stateful,
ids.length > 1 ? ids[1] : participantItem.jid);
return {
jid: participantItem?.jid,
role: participantItem?.role,
displayName: participantItem?.displayName,
avatarUrl: storeParticipant?.loadableAvatarUrl,
id: storeParticipant ? storeParticipant.id
: participantLongId
};
}) : []
};
})
};
};
/**
* Returns the room by Jid.
*

View File

@ -89,7 +89,7 @@ MiddlewareRegistry.register(store => next => action => {
const state = store.getState();
const { defaultLocalDisplayName } = state['features/base/config'];
const { room } = state['features/base/conference'];
const { loadableAvatarUrl, name, id } = getLocalParticipant(state);
const { loadableAvatarUrl, name, id, email } = getLocalParticipant(state);
const breakoutRoom = APP.conference.roomName.toString() !== room.toLowerCase();
// we use APP.conference.roomName as we do not update state['features/base/conference'].room when
@ -104,7 +104,8 @@ MiddlewareRegistry.register(store => next => action => {
defaultLocalDisplayName
),
avatarURL: loadableAvatarUrl,
breakoutRoom
breakoutRoom,
email
}
);
break;
@ -150,19 +151,32 @@ MiddlewareRegistry.register(store => next => action => {
{ id: action.kicker });
break;
case PARTICIPANT_LEFT:
case PARTICIPANT_LEFT: {
const { participant } = action;
const { isFakeParticipant, isVirtualScreenshareParticipant } = participant;
// Skip sending participant left event for fake or virtual screenshare participants.
if (isFakeParticipant || isVirtualScreenshareParticipant) {
break;
}
APP.API.notifyUserLeft(action.participant.id);
break;
}
case PARTICIPANT_JOINED: {
const state = store.getState();
const { defaultRemoteDisplayName } = state['features/base/config'];
const { participant } = action;
const { id, local, name } = participant;
const { id, isFakeParticipant, isVirtualScreenshareParticipant, local, name } = participant;
// The version of external api outside of middleware did not emit
// the local participant being created.
if (!local) {
// Skip sending participant joined event for fake or virtual screenshare participants.
if (isFakeParticipant || isVirtualScreenshareParticipant) {
break;
}
APP.API.notifyUserJoined(id, {
displayName: name,
formattedDisplayName: appendSuffix(

View File

@ -60,9 +60,9 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
return next(action);
}
case TRACK_ADDED: {
const { jitsiTrack: { isLocal, videoType } } = action.track;
const { jitsiTrack: { isLocal, videoType }, muted } = action.track;
if (videoType === 'camera' && isLocal()) {
if (videoType === 'camera' && isLocal() && !muted) {
// need to pass this since the track is not yet added in the store
dispatch(startFaceLandmarksDetection(action.track));
}

View File

@ -3,10 +3,12 @@
import React, { Component } from 'react';
import VideoLayout from '../../../../modules/UI/videolayout/VideoLayout';
import { getMultipleVideoSupportFeatureFlag } from '../../base/config';
import { MEDIA_TYPE, VIDEO_TYPE } from '../../base/media';
import { getLocalParticipant } from '../../base/participants';
import { Watermarks } from '../../base/react';
import { connect } from '../../base/redux';
import { getVideoTrackByParticipant } from '../../base/tracks';
import { getTrackByMediaTypeAndParticipant, getVirtualScreenshareParticipantTrack } from '../../base/tracks';
import { setColorAlpha } from '../../base/util';
import { StageParticipantNameLabel } from '../../display-name';
import { FILMSTRIP_BREAKPOINT, isFilmstripResizable } from '../../filmstrip';
@ -19,6 +21,8 @@ import { setSeeWhatIsBeingShared } from '../actions.web';
import ScreenSharePlaceholder from './ScreenSharePlaceholder.web';
// Hack to detect Spot.
const SPOT_DISPLAY_NAME = 'Meeting Room';
declare var interfaceConfig: Object;
@ -39,6 +43,11 @@ type Props = {
*/
_customBackgroundImageUrl: string,
/**
* Whether the screen-sharing placeholder should be displayed or not.
*/
_displayScreenSharingPlaceholder: boolean,
/**
* Prop that indicates whether the chat is open.
*/
@ -81,7 +90,7 @@ type Props = {
_largeVideoParticipantId: string,
/**
* Whether or not the screen sharing is on.
* Whether or not the local screen share is on large-video.
*/
_isScreenSharing: boolean,
@ -154,11 +163,10 @@ class LargeVideo extends Component<Props> {
*/
render() {
const {
_displayScreenSharingPlaceholder,
_isChatOpen,
_noAutoPlayVideo,
_showDominantSpeakerBadge,
_isScreenSharing,
_seeWhatIsBeingShared
_showDominantSpeakerBadge
} = this.props;
const style = this._getCustomStyles();
const className = `videocontainer${_isChatOpen ? ' shift-right' : ''}`;
@ -197,7 +205,7 @@ class LargeVideo extends Component<Props> {
onTouchEnd = { this._onDoubleTap }
ref = { this._wrapperRef }
role = 'figure' >
{_isScreenSharing && !_seeWhatIsBeingShared ? <ScreenSharePlaceholder /> : <video
{ _displayScreenSharingPlaceholder ? <ScreenSharePlaceholder /> : <video
autoPlay = { !_noAutoPlayVideo }
id = 'largeVideo'
muted = { true }
@ -321,25 +329,35 @@ function _mapStateToProps(state) {
const { backgroundColor, backgroundImageUrl } = state['features/dynamic-branding'];
const { isOpen: isChatOpen } = state['features/chat'];
const { width: verticalFilmstripWidth, visible } = state['features/filmstrip'];
const { hideDominantSpeakerBadge } = state['features/base/config'];
const { defaultLocalDisplayName, hideDominantSpeakerBadge } = state['features/base/config'];
const { seeWhatIsBeingShared } = state['features/large-video'];
const tracks = state['features/base/tracks'];
const localParticipantId = getLocalParticipant(state)?.id;
const largeVideoParticipant = getLargeVideoParticipant(state);
const videoTrack = getVideoTrackByParticipant(tracks, largeVideoParticipant);
const localParticipantisSharingTheScreen = largeVideoParticipant?.id?.includes(localParticipantId);
const isScreenSharing = localParticipantisSharingTheScreen && videoTrack?.videoType === 'desktop';
let videoTrack;
if (getMultipleVideoSupportFeatureFlag(state) && largeVideoParticipant?.isVirtualScreenshareParticipant) {
videoTrack = getVirtualScreenshareParticipantTrack(tracks, largeVideoParticipant?.id);
} else {
videoTrack = getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, largeVideoParticipant?.id);
}
const isLocalScreenshareOnLargeVideo = largeVideoParticipant?.id?.includes(localParticipantId)
&& videoTrack?.videoType === VIDEO_TYPE.DESKTOP;
const isOnSpot = defaultLocalDisplayName === SPOT_DISPLAY_NAME;
return {
_backgroundAlpha: state['features/base/config'].backgroundAlpha,
_customBackgroundColor: backgroundColor,
_customBackgroundImageUrl: backgroundImageUrl,
_displayScreenSharingPlaceholder: isLocalScreenshareOnLargeVideo && !seeWhatIsBeingShared && !isOnSpot,
_isChatOpen: isChatOpen,
_isScreenSharing: isScreenSharing,
_isScreenSharing: isLocalScreenshareOnLargeVideo,
_largeVideoParticipantId: largeVideoParticipant?.id,
_noAutoPlayVideo: testingConfig?.noAutoPlayVideo,
_resizableFilmstrip: isFilmstripResizable(state),
_seeWhatIsBeingShared: state['features/large-video'].seeWhatIsBeingShared,
_seeWhatIsBeingShared: seeWhatIsBeingShared,
_showDominantSpeakerBadge: !hideDominantSpeakerBadge,
_verticalFilmstripWidth: verticalFilmstripWidth.current,
_verticalViewMaxWidth: getVerticalViewMaxWidth(state),

View File

@ -1,7 +1,12 @@
// @flow
import VideoLayout from '../../../modules/UI/videolayout/VideoLayout';
import { getMultipleVideoSupportFeatureFlag } from '../base/config';
import { MEDIA_TYPE } from '../base/media';
import { StateListenerRegistry } from '../base/redux';
import { getTrackByMediaTypeAndParticipant, getVirtualScreenshareParticipantTrack } from '../base/tracks';
import { getLargeVideoParticipant } from './functions';
/**
* Updates the on stage participant video.
@ -12,3 +17,32 @@ StateListenerRegistry.register(
VideoLayout.updateLargeVideo(participantId, true);
}
);
/**
* Schedules a large video update when the streaming status of the track associated with the large video changes.
*/
StateListenerRegistry.register(
/* selector */ state => {
const largeVideoParticipant = getLargeVideoParticipant(state);
const tracks = state['features/base/tracks'];
let videoTrack;
if (getMultipleVideoSupportFeatureFlag(state) && largeVideoParticipant?.isVirtualScreenshareParticipant) {
videoTrack = getVirtualScreenshareParticipantTrack(tracks, largeVideoParticipant?.id);
} else {
videoTrack = getTrackByMediaTypeAndParticipant(tracks, MEDIA_TYPE.VIDEO, largeVideoParticipant?.id);
}
return {
participantId: largeVideoParticipant?.id,
streamingStatus: videoTrack?.streamingStatus
};
},
/* listener */ ({ participantId, streamingStatus }, previousState = {}) => {
if (streamingStatus !== previousState.streamingStatus) {
VideoLayout.updateLargeVideo(participantId, true);
}
}, {
deepEquals: true
}
);

View File

@ -10,7 +10,8 @@ import {
getLocalParticipant,
participantJoined,
participantLeft,
pinParticipant
pinParticipant,
getParticipantById
} from '../base/participants';
import { MiddlewareRegistry } from '../base/redux';
@ -51,7 +52,12 @@ MiddlewareRegistry.register(store => next => action => {
if (isSharingStatus(sharedVideoStatus)) {
handleSharingVideoStatus(store, value, attributes, conference);
} else if (sharedVideoStatus === 'stop') {
dispatch(participantLeft(value, conference));
const videoParticipant = getParticipantById(state, value);
dispatch(participantLeft(value, conference, {
isFakeParticipant: videoParticipant?.isFakeParticipant
}));
if (localParticipantId !== from) {
dispatch(resetSharedVideoStatus());
}

View File

@ -1,12 +1,12 @@
// @flow
import { IState } from '../app/types';
/**
* Indicates if the audio mute button is disabled or not.
*
* @param {Object} state - The state from the Redux store.
* @param {IState} state - The state from the Redux store.
* @returns {boolean}
*/
export function isAudioMuteButtonDisabled(state: Object) {
export function isAudioMuteButtonDisabled(state: IState) {
const { available, muted, unmuteBlocked } = state['features/base/media'].audio;
const { startSilent } = state['features/base/config'];

View File

@ -8,6 +8,7 @@ import {
SET_FULL_SCREEN
} from './actionTypes';
import './subscriber';
declare var APP: Object;

View File

@ -0,0 +1,22 @@
import { IState, IStore } from '../app/types';
import StateListenerRegistry from '../base/redux/StateListenerRegistry';
import { isAudioMuteButtonDisabled } from './functions.any';
declare let APP: any;
/**
* Notifies when audio availability changes.
*/
StateListenerRegistry.register(
/* selector */ (state: IState) => isAudioMuteButtonDisabled(state),
/* listener */ (disabled: boolean, store: IStore, previousDisabled: boolean) => {
if (typeof APP !== 'object') {
return;
}
if (disabled !== previousDisabled) {
APP.API.notifyAudioAvailabilityChanged(!disabled);
}
}
);