Compare commits

...

8 Commits

Author SHA1 Message Date
Horatiu Muresan 4adebf9f61 fix(lint) Fix eslint 2022-09-15 17:17:38 +03:00
Horatiu Muresan db59e517c7 fix(external-api) Add email to local participant info 2022-09-15 14:13:22 +03:00
Horatiu Muresan 1b5d193149 fix(external-api) Fix notify audio muted/audio available 2022-09-15 14:13:03 +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
22 changed files with 293 additions and 56 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#9236a65d36dec8a4bfcb09fb26d81d010f47ea65",
"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#9236a65d36dec8a4bfcb09fb26d81d010f47ea65",
"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#9236a65d36dec8a4bfcb09fb26d81d010f47ea65",
"integrity": "sha512-YeJvRWtMrTgekfkr/+o1pE3rtBa9ZsPnS7KFQ146UHfXOK5c3MnVJ/4XzMDn0/H/3pkssYkai5XnUhVH8MrHWQ==",
"from": "lib-jitsi-meet@https://github.com/jitsi/lib-jitsi-meet#9236a65d36dec8a4bfcb09fb26d81d010f47ea65",
"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#9236a65d36dec8a4bfcb09fb26d81d010f47ea65",
"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>
<span className = { classes.subtitle }>
{_roomName}
</span>
{ _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

@ -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.
*/
@ -80,10 +89,10 @@ 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,
_isScreenSharing: boolean,
/**
* Whether or not the screen sharing is visible.
@ -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,11 +205,11 @@ 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 }
playsInline = { true } /* for Safari on iOS to work */ />}
playsInline = { true } /* for Safari on iOS to work */ /> }
</div>
</div>
{ interfaceConfig.DISABLE_TRANSCRIPTION_SUBTITLES
@ -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);
}
}
);