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

View File

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

View File

@ -552,6 +552,7 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
this._myUserID = userID; this._myUserID = userID;
this._participants[userID] = { this._participants[userID] = {
email: data.email,
avatarURL: data.avatarURL avatarURL: data.avatarURL
}; };
} }
@ -619,6 +620,9 @@ export default class JitsiMeetExternalAPI extends EventEmitter {
case 'video-quality-changed': case 'video-quality-changed':
this._videoQuality = data.videoQuality; this._videoQuality = data.videoQuality;
break; break;
case 'breakout-rooms-updated':
this.updateNumberOfParticipants(data.rooms);
break;
case 'local-storage-changed': case 'local-storage-changed':
jitsiLocalStorage.setItem('jitsiLocalStorage', data.localStorageContent); 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. * 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. * participant is included.
* *
* @returns {int} The number of participants in the conference. * @returns {int} The number of participants in the conference.

View File

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

11
package-lock.json generated
View File

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

View File

@ -75,7 +75,7 @@
"js-md5": "0.6.1", "js-md5": "0.6.1",
"js-sha512": "0.8.0", "js-sha512": "0.8.0",
"jwt-decode": "2.2.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", "lodash": "4.17.21",
"moment": "2.29.4", "moment": "2.29.4",
"moment-duration-format": "2.2.2", "moment-duration-format": "2.2.2",

View File

@ -132,7 +132,7 @@ export function commonUserLeftHandling(
} else { } else {
const isReplaced = user.isReplaced && user.isReplaced(); 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 || subject
|| callDisplayName || callDisplayName
|| (callee && callee.name) || (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 { SET_VIDEO_MUTED } from './actionTypes';
import './subscriber';
/** /**
* Implements the entry point of the middleware of the feature base/media. * 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 * with the participant identified by the specified {@code id}. Only the local
* participant is allowed to not specify an associated {@code JitsiConference} * participant is allowed to not specify an associated {@code JitsiConference}
* instance. * instance.
* @param {boolean} isReplaced - Whether the participant is to be replaced in the meeting. * @param {Object} participantLeftProps - Other participant properties.
* @param {boolean} isVirtualScreenshareParticipant - Whether the participant is a virtual screen share participant. * @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 {{ * @returns {{
* type: PARTICIPANT_LEFT, * type: PARTICIPANT_LEFT,
* participant: { * participant: {
@ -391,15 +396,15 @@ export function hiddenParticipantLeft(id: string) {
* } * }
* }} * }}
*/ */
export function participantLeft(id: string, conference: any, export function participantLeft(id: string, conference: any, participantLeftProps: any = {}) {
isReplaced?: boolean, isVirtualScreenshareParticipant?: boolean) {
return { return {
type: PARTICIPANT_LEFT, type: PARTICIPANT_LEFT,
participant: { participant: {
conference, conference,
id, id,
isReplaced, isReplaced: participantLeftProps.isReplaced,
isVirtualScreenshareParticipant isVirtualScreenshareParticipant: participantLeftProps.isVirtualScreenshareParticipant,
isFakeParticipant: participantLeftProps.isFakeParticipant
} }
}; };
} }

View File

@ -369,7 +369,9 @@ StateListenerRegistry.register(
batch(() => { batch(() => {
for (const [ id, p ] of getRemoteParticipants(getState())) { for (const [ id, p ] of getRemoteParticipants(getState())) {
(!conference || p.conference !== conference) (!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) { 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); const addedScreenshareSourceNames = _.difference(currentScreenshareSourceNames, previousScreenshareSourceNames);
if (removedScreenshareSourceNames.length) { 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) { if (addedScreenshareSourceNames.length) {

View File

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

View File

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

View File

@ -3,7 +3,7 @@
import _ from 'lodash'; import _ from 'lodash';
import { getCurrentConference } from '../base/conference'; import { getCurrentConference } from '../base/conference';
import { getParticipantCount, isLocalParticipantModerator } from '../base/participants'; import { getParticipantById, getParticipantCount, isLocalParticipantModerator } from '../base/participants';
import { toState } from '../base/redux'; import { toState } from '../base/redux';
import { FEATURE_KEY } from './constants'; import { FEATURE_KEY } from './constants';
@ -30,6 +30,69 @@ export const getMainRoom = (stateful: Function | Object) => {
return _.find(rooms, (room: Object) => room.isMainRoom); 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. * Returns the room by Jid.
* *

View File

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

View File

@ -60,9 +60,9 @@ MiddlewareRegistry.register(({ dispatch, getState }) => next => action => {
return next(action); return next(action);
} }
case TRACK_ADDED: { 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 // need to pass this since the track is not yet added in the store
dispatch(startFaceLandmarksDetection(action.track)); dispatch(startFaceLandmarksDetection(action.track));
} }

View File

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

View File

@ -1,7 +1,12 @@
// @flow // @flow
import VideoLayout from '../../../modules/UI/videolayout/VideoLayout'; import VideoLayout from '../../../modules/UI/videolayout/VideoLayout';
import { getMultipleVideoSupportFeatureFlag } from '../base/config';
import { MEDIA_TYPE } from '../base/media';
import { StateListenerRegistry } from '../base/redux'; import { StateListenerRegistry } from '../base/redux';
import { getTrackByMediaTypeAndParticipant, getVirtualScreenshareParticipantTrack } from '../base/tracks';
import { getLargeVideoParticipant } from './functions';
/** /**
* Updates the on stage participant video. * Updates the on stage participant video.
@ -12,3 +17,32 @@ StateListenerRegistry.register(
VideoLayout.updateLargeVideo(participantId, true); 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, getLocalParticipant,
participantJoined, participantJoined,
participantLeft, participantLeft,
pinParticipant pinParticipant,
getParticipantById
} from '../base/participants'; } from '../base/participants';
import { MiddlewareRegistry } from '../base/redux'; import { MiddlewareRegistry } from '../base/redux';
@ -51,7 +52,12 @@ MiddlewareRegistry.register(store => next => action => {
if (isSharingStatus(sharedVideoStatus)) { if (isSharingStatus(sharedVideoStatus)) {
handleSharingVideoStatus(store, value, attributes, conference); handleSharingVideoStatus(store, value, attributes, conference);
} else if (sharedVideoStatus === 'stop') { } else if (sharedVideoStatus === 'stop') {
dispatch(participantLeft(value, conference)); const videoParticipant = getParticipantById(state, value);
dispatch(participantLeft(value, conference, {
isFakeParticipant: videoParticipant?.isFakeParticipant
}));
if (localParticipantId !== from) { if (localParticipantId !== from) {
dispatch(resetSharedVideoStatus()); dispatch(resetSharedVideoStatus());
} }

View File

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

View File

@ -8,6 +8,7 @@ import {
SET_FULL_SCREEN SET_FULL_SCREEN
} from './actionTypes'; } from './actionTypes';
import './subscriber';
declare var APP: Object; 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);
}
}
);