Merge pull request #7908 from capilkey/improve-users-find

Improve user fetch and sort, and webcam fetch
This commit is contained in:
Anton Georgiev 2019-08-12 12:39:58 -04:00 committed by GitHub
commit 1ea030e31d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 123 additions and 108 deletions

View File

@ -7,5 +7,10 @@ export default function handleVoiceUpdate({ body }, meetingId) {
check(meetingId, String);
// If a person is muted we have to force them to not talking
if (voiceUser.muted) {
voiceUser.talking = false;
}
return updateVoiceUser(meetingId, voiceUser);
}

View File

@ -122,7 +122,7 @@ export default withModalMounter(withTracker(() => {
data.children = <ScreenshareContainer />;
}
const usersVideo = VideoService.getAllUsersVideo();
const usersVideo = VideoService.getAllWebcamUsers();
data.usersVideo = usersVideo;
if (MediaService.shouldShowOverlay() && usersVideo.length && viewParticipantsWebcams) {

View File

@ -59,7 +59,7 @@ const toggleSwapLayout = () => {
export const shouldEnableSwapLayout = () => {
const { viewParticipantsWebcams } = Settings.dataSaving;
const usersVideo = VideoService.getAllUsersVideo();
const usersVideo = VideoService.getAllWebcamUsers();
const poll = PollingService.mapPolls();
return usersVideo.length > 0 // prevent swap without any webcams

View File

@ -14,7 +14,6 @@ const propTypes = {
}).isRequired,
CustomLogoUrl: PropTypes.string.isRequired,
handleEmojiChange: PropTypes.func.isRequired,
getUsersId: PropTypes.func.isRequired,
isBreakoutRoom: PropTypes.bool,
getAvailableActions: PropTypes.func.isRequired,
normalizeEmojiName: PropTypes.func.isRequired,
@ -65,7 +64,6 @@ class UserList extends PureComponent {
getEmoji,
showBranding,
hasBreakoutRoom,
getUsersId,
hasPrivateChatBetweenUsers,
toggleUserLock,
requestUserInformation,
@ -102,7 +100,6 @@ class UserList extends PureComponent {
getEmojiList,
getEmoji,
hasBreakoutRoom,
getUsersId,
hasPrivateChatBetweenUsers,
toggleUserLock,
requestUserInformation,

View File

@ -8,7 +8,6 @@ import UserList from './component';
const propTypes = {
activeChats: PropTypes.arrayOf(String).isRequired,
getUsersId: PropTypes.func.isRequired,
isBreakoutRoom: PropTypes.bool.isRequired,
getAvailableActions: PropTypes.func.isRequired,
normalizeEmojiName: PropTypes.func.isRequired,
@ -33,7 +32,6 @@ UserListContainer.propTypes = propTypes;
export default withTracker(({ chatID, compact }) => ({
hasBreakoutRoom: Service.hasBreakoutRoom(),
getUsersId: Service.getUsersId,
activeChats: Service.getActiveChats(chatID),
isBreakoutRoom: meetingIsBreakout(),
getAvailableActions: Service.getAvailableActions,

View File

@ -18,6 +18,8 @@ const CHAT_CONFIG = Meteor.settings.public.chat;
const PUBLIC_GROUP_CHAT_ID = CHAT_CONFIG.public_group_id;
const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator;
const DIAL_IN_CLIENT_TYPE = 'dial-in-user';
// session for closed chat list
const CLOSED_CHAT_LIST_KEY = 'closedChatList';
@ -42,13 +44,16 @@ export const setCustomLogoUrl = path => Storage.setItem(CUSTOM_LOGO_URL_KEY, pat
const getCustomLogoUrl = () => Storage.getItem(CUSTOM_LOGO_URL_KEY);
const sortUsersByName = (a, b) => {
if (a.name.toLowerCase() < b.name.toLowerCase()) {
const aName = a.name.toLowerCase();
const bName = b.name.toLowerCase();
if (aName < bName) {
return -1;
} if (a.name.toLowerCase() > b.name.toLowerCase()) {
} if (aName > bName) {
return 1;
} if (a.id.toLowerCase() > b.id.toLowerCase()) {
} if (a.userId > b.userId) {
return -1;
} if (a.id.toLowerCase() < b.id.toLowerCase()) {
} if (a.userId < b.userId) {
return 1;
}
@ -56,32 +61,26 @@ const sortUsersByName = (a, b) => {
};
const sortUsersByEmoji = (a, b) => {
const { status: statusA } = a.emoji;
const { status: statusB } = b.emoji;
const emojiA = statusA in EMOJI_STATUSES ? EMOJI_STATUSES[statusA] : statusA;
const emojiB = statusB in EMOJI_STATUSES ? EMOJI_STATUSES[statusB] : statusB;
if (emojiA && emojiB && (emojiA !== 'none' && emojiB !== 'none')) {
if (a.emoji.changedAt < b.emoji.changedAt) {
if (a.emoji && b.emoji && (a.emoji !== 'none' && b.emoji !== 'none')) {
if (a.emojiTime < b.emojiTime) {
return -1;
} if (a.emoji.changedAt > b.emoji.changedAt) {
} if (a.emojiTime > b.emojiTime) {
return 1;
}
} if (emojiA && emojiA !== 'none') {
} if (a.emoji && a.emoji !== 'none') {
return -1;
} if (emojiB && emojiB !== 'none') {
} if (b.emoji && b.emoji !== 'none') {
return 1;
}
return 0;
};
const sortUsersByModerator = (a, b) => {
if (a.isModerator && b.isModerator) {
return sortUsersByEmoji(a, b);
} if (a.isModerator) {
if (a.role === ROLE_MODERATOR && b.role === ROLE_MODERATOR) {
return 0;
} if (a.role === ROLE_MODERATOR) {
return -1;
} if (b.isModerator) {
} if (b.role === ROLE_MODERATOR) {
return 1;
}
@ -89,11 +88,11 @@ const sortUsersByModerator = (a, b) => {
};
const sortUsersByPhoneUser = (a, b) => {
if (!a.isPhoneUser && !b.isPhoneUser) {
if (!a.clientType === DIAL_IN_CLIENT_TYPE && !b.clientType === DIAL_IN_CLIENT_TYPE) {
return 0;
} if (!a.isPhoneUser) {
} if (!a.clientType === DIAL_IN_CLIENT_TYPE) {
return -1;
} if (!b.isPhoneUser) {
} if (!b.clientType === DIAL_IN_CLIENT_TYPE) {
return 1;
}
@ -102,9 +101,9 @@ const sortUsersByPhoneUser = (a, b) => {
// current user's name is always on top
const sortUsersByCurrent = (a, b) => {
if (a.isCurrent) {
if (a.userId === Auth.userID) {
return -1;
} if (b.isCurrent) {
} if (b.userId === Auth.userID) {
return 1;
}
@ -189,13 +188,9 @@ const getUsers = () => {
}, userFindSorting)
.fetch();
return users
.map(mapUser)
.sort(sortUsers);
return users.sort(sortUsers);
};
const getUsersId = () => getUsers().map(u => u.id);
const hasBreakoutRoom = () => Breakouts.find({ parentMeetingId: Auth.meetingID }).count() > 0;
const getActiveChats = (chatID) => {
@ -481,6 +476,7 @@ const requestUserInformation = (userId) => {
};
export default {
sortUsers,
setEmojiStatus,
assignPresenter,
removeUser,
@ -489,7 +485,6 @@ export default {
muteAllExceptPresenter,
changeRole,
getUsers,
getUsersId,
getActiveChats,
getCurrentUser,
getAvailableActions,

View File

@ -31,7 +31,6 @@ const propTypes = {
roving: PropTypes.func.isRequired,
getGroupChatPrivate: PropTypes.func.isRequired,
handleEmojiChange: PropTypes.func.isRequired,
getUsersId: PropTypes.func.isRequired,
pollIsOpen: PropTypes.bool.isRequired,
forcePollOpen: PropTypes.bool.isRequired,
toggleUserLock: PropTypes.func.isRequired,
@ -72,7 +71,6 @@ class UserContent extends PureComponent {
pollIsOpen,
forcePollOpen,
hasBreakoutRoom,
getUsersId,
hasPrivateChatBetweenUsers,
toggleUserLock,
pendingUsers,
@ -150,7 +148,6 @@ class UserContent extends PureComponent {
getEmojiList,
getEmoji,
getGroupChatPrivate,
getUsersId,
hasPrivateChatBetweenUsers,
toggleUserLock,
requestUserInformation,

View File

@ -15,10 +15,9 @@ const propTypes = {
}).isRequired,
currentUser: PropTypes.shape({}).isRequired,
meeting: PropTypes.shape({}).isRequired,
users: PropTypes.arrayOf(PropTypes.string).isRequired,
users: PropTypes.arrayOf(PropTypes.shape({})).isRequired,
getGroupChatPrivate: PropTypes.func.isRequired,
handleEmojiChange: PropTypes.func.isRequired,
getUsersId: PropTypes.func.isRequired,
isBreakoutRoom: PropTypes.bool,
setEmojiStatus: PropTypes.func.isRequired,
assignPresenter: PropTypes.func.isRequired,
@ -145,7 +144,7 @@ class UserParticipants extends Component {
timeout={0}
component="div"
className={cx(styles.participantsList)}
key={u}
key={u.userId}
>
<div ref={(node) => { this.userRefs[index += 1] = node; }}>
<UserListItemContainer
@ -170,7 +169,7 @@ class UserParticipants extends Component {
requestUserInformation,
currentUser,
}}
userId={u}
user={u}
getScrollContainerRef={this.getScrollContainerRef}
/>
</div>

View File

@ -1,11 +1,12 @@
import React from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import Meetings from '/imports/api/meetings';
import UserListService from '/imports/ui/components/user-list/service';
import UserParticipants from './component';
const UserParticipantsContainer = props => <UserParticipants {...props} />;
export default withTracker(({ getUsersId }) => ({
export default withTracker(() => ({
meeting: Meetings.findOne({}),
users: getUsersId(),
users: UserListService.getUsers(),
}))(UserParticipantsContainer);

View File

@ -1,6 +1,5 @@
import React from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import Users from '/imports/api/users';
import Breakouts from '/imports/api/breakouts';
import Meetings from '/imports/api/meetings';
import Auth from '/imports/ui/services/auth';
@ -9,12 +8,12 @@ import UserListItem from './component';
const UserListItemContainer = props => <UserListItem {...props} />;
export default withTracker(({ userId }) => {
const findUserInBreakout = Breakouts.findOne({ 'joinedUsers.userId': new RegExp(`^${userId}`) });
export default withTracker(({ user }) => {
const findUserInBreakout = Breakouts.findOne({ 'joinedUsers.userId': new RegExp(`^${user.userId}`) });
const breakoutSequence = (findUserInBreakout || {}).sequence;
const Meeting = Meetings.findOne({ MeetingId: Auth.meetingID }, { fields: { meetingProp: 1 } });
return {
user: mapUser(Users.findOne({ userId })),
user: mapUser(user),
userInBreakout: !!findUserInBreakout,
breakoutSequence,
meetignIsBreakout: Meeting && Meeting.meetingProp.isBreakout,

View File

@ -244,7 +244,7 @@ class VideoProvider extends Component {
}
componentWillUpdate({ users, userId }) {
const usersSharingIds = users.map(u => u.id);
const usersSharingIds = users.map(u => u.userId);
const usersConnected = Object.keys(this.webRtcPeers);
const usersToConnect = usersSharingIds.filter(id => !usersConnected.includes(id));

View File

@ -1,6 +1,7 @@
import React from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import getFromUserSettings from '/imports/ui/services/users-settings';
import Auth from '/imports/ui/services/auth';
import VideoProvider from './component';
import VideoService from './service';
@ -9,16 +10,13 @@ const VideoProviderContainer = ({ children, ...props }) => {
return (!users.length ? null : <VideoProvider {...props}>{children}</VideoProvider>);
};
export default withTracker((props) => {
return {
cursor: props.cursor,
swapLayout: props.swapLayout,
meetingId: VideoService.meetingId(),
users: VideoService.getAllUsersVideo(),
userId: VideoService.userId(),
sessionToken: VideoService.sessionToken(),
userName: VideoService.userName(),
enableVideoStats: getFromUserSettings('enableVideoStats', Meteor.settings.public.kurento.enableVideoStats),
voiceBridge: VideoService.voiceBridge(),
};
})(VideoProviderContainer);
export default withTracker(props => ({
cursor: props.cursor,
swapLayout: props.swapLayout,
meetingId: VideoService.meetingId(),
users: VideoService.getAllWebcamUsers(),
userId: Auth.userID,
sessionToken: VideoService.sessionToken(),
enableVideoStats: getFromUserSettings('enableVideoStats', Meteor.settings.public.kurento.enableVideoStats),
voiceBridge: VideoService.voiceBridge(),
}))(VideoProviderContainer);

View File

@ -3,9 +3,11 @@ import { makeCall } from '/imports/ui/services/api';
import Auth from '/imports/ui/services/auth';
import Meetings from '/imports/api/meetings/';
import Users from '/imports/api/users/';
import mapUser from '/imports/ui/services/user/mapUser';
import UserListService from '/imports/ui/components/user-list/service';
const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator;
const ROLE_VIEWER = Meteor.settings.public.user.role_viewer;
class VideoService {
constructor() {
this.defineProperties({
@ -70,40 +72,47 @@ class VideoService {
makeCall('userUnshareWebcam', stream);
}
getAllUsers() {
// Use the same function as the user-list to share the sorting/mapping
return UserListService.getUsers();
}
getAllUsersVideo() {
const userId = this.userId();
const isLocked = this.isLocked();
const currentUser = Users.findOne({ userId });
const currentUserIsModerator = mapUser(currentUser).isModerator;
getAllWebcamUsers() {
const webcamsLocked = this.webcamsLocked();
const webcamsOnlyForModerator = this.webcamsOnlyForModerator();
const currentUser = Users.findOne({ userId: Auth.userID });
const currentUserIsViewer = currentUser.role === ROLE_VIEWER;
const sharedWebcam = this.isSharing;
const isSharingWebcam = user => user.isSharingWebcam || (sharedWebcam && user.isCurrent);
const isNotLocked = user => !(isLocked && user.isLocked);
let users = Users
.find({
meetingId: Auth.meetingID,
connectionStatus: 'online',
hasStream: true,
userId: { $ne: Auth.userID },
})
.fetch();
const isWebcamOnlyModerator = this.webcamOnlyModerator();
const allowedSeeViewersWebcams = !isWebcamOnlyModerator || currentUserIsModerator;
const webcamOnlyModerator = (user) => {
if (allowedSeeViewersWebcams) return true;
return user.isModerator || user.isCurrent;
};
const userIsNotLocked = user => user.role === ROLE_MODERATOR || !user.locked;
return this.getAllUsers()
.filter(isSharingWebcam)
.filter(isNotLocked)
.filter(webcamOnlyModerator);
if (webcamsLocked) {
users = users.filter(userIsNotLocked);
}
const userIsModerator = user => user.role === ROLE_MODERATOR;
if (webcamsOnlyForModerator && currentUserIsViewer) {
users = users.filter(userIsModerator);
}
if (sharedWebcam) {
users.unshift(currentUser);
}
return users.sort(UserListService.sortUsers);
}
webcamOnlyModerator() {
webcamsOnlyForModerator() {
const m = Meetings.findOne({ meetingId: Auth.meetingID }) || {};
return m.usersProp ? m.usersProp.webcamsOnlyForModerator : false;
}
isLocked() {
webcamsLocked() {
const m = Meetings.findOne({ meetingId: Auth.meetingID }) || {};
return m.lockSettingsProps ? m.lockSettingsProps.disableCam : false;
}
@ -145,9 +154,8 @@ export default {
exitVideo: () => videoService.exitVideo(),
exitingVideo: () => videoService.exitingVideo(),
exitedVideo: () => videoService.exitedVideo(),
getAllUsers: () => videoService.getAllUsers(),
webcamsLocked: () => videoService.webcamsLocked(),
webcamOnlyModerator: () => videoService.webcamOnlyModerator(),
isLocked: () => videoService.isLocked(),
isSharing: () => videoService.isSharing,
isConnected: () => videoService.isConnected,
isWaitingResponse: () => videoService.isWaitingResponse,
@ -156,10 +164,9 @@ export default {
joinedVideo: () => videoService.joinedVideo(),
sendUserShareWebcam: stream => videoService.sendUserShareWebcam(stream),
sendUserUnshareWebcam: stream => videoService.sendUserUnshareWebcam(stream),
userId: () => videoService.userId(),
userName: () => videoService.userName(),
meetingId: () => videoService.meetingId(),
getAllUsersVideo: () => videoService.getAllUsersVideo(),
getAllWebcamUsers: () => videoService.getAllWebcamUsers(),
sessionToken: () => videoService.sessionToken(),
voiceBridge: () => videoService.voiceBridge(),
};

View File

@ -18,7 +18,7 @@ const isDisabled = () => {
const isWaitingResponse = VideoService.isWaitingResponse();
const isConnected = VideoService.isConnected();
const lockCam = VideoService.isLocked();
const lockCam = VideoService.webcamsLocked();
const user = Users.findOne({ userId: Auth.userID });
const userLocked = mapUser(user).isLocked;

View File

@ -4,7 +4,7 @@ import { defineMessages, injectIntl } from 'react-intl';
import cx from 'classnames';
import _ from 'lodash';
import { styles } from './styles';
import VideoListItem from './video-list-item/component';
import VideoListItemContainer from './video-list-item/container';
import { withDraggableConsumer } from '../../media/webcam-draggable-overlay/context';
const propTypes = {
@ -158,7 +158,7 @@ class VideoList extends Component {
const { focusedId } = this.state;
return users.map((user) => {
const isFocused = focusedId === user.id;
const isFocused = focusedId === user.userId;
const isFocusedIntlKey = !isFocused ? 'focus' : 'unfocus';
let actions = [];
@ -166,28 +166,28 @@ class VideoList extends Component {
actions = [{
label: intl.formatMessage(intlMessages[`${isFocusedIntlKey}Label`]),
description: intl.formatMessage(intlMessages[`${isFocusedIntlKey}Desc`]),
onClick: () => this.handleVideoFocus(user.id),
onClick: () => this.handleVideoFocus(user.userId),
}];
}
return (
<div
key={user.id}
key={user.userId}
className={cx({
[styles.videoListItem]: true,
[styles.focused]: focusedId === user.id && users.length > 2,
[styles.focused]: focusedId === user.userId && users.length > 2,
})}
>
<VideoListItem
<VideoListItemContainer
numOfUsers={users.length}
user={user}
actions={actions}
onMount={(videoRef) => {
this.handleCanvasResize();
onMount(user.id, videoRef);
onMount(user.userId, videoRef);
}}
getStats={(videoRef, callback) => getStats(user.id, videoRef, callback)}
stopGettingStats={() => stopGettingStats(user.id)}
getStats={(videoRef, callback) => getStats(user.userId, videoRef, callback)}
stopGettingStats={() => stopGettingStats(user.userId)}
enableVideoStats={enableVideoStats}
/>
</div>

View File

@ -127,11 +127,11 @@ class VideoListItem extends Component {
return _.compact([
<DropdownListTitle className={styles.hiddenDesktop} key="name">{user.name}</DropdownListTitle>,
<DropdownListSeparator className={styles.hiddenDesktop} key="sep" />,
...actions.map(action => (<DropdownListItem key={user.id} {...action} />)),
...actions.map(action => (<DropdownListItem key={user.userId} {...action} />)),
(enableVideoStats
? (
<DropdownListItem
key={`list-item-stats-${user.id}`}
key={`list-item-stats-${user.userId}`}
onClick={() => { this.toggleStats(); }}
label={intl.formatMessage(intlMessages.connectionStatsLabel)}
/>
@ -176,6 +176,7 @@ class VideoListItem extends Component {
} = this.state;
const {
user,
voiceUser,
numOfUsers,
webcamDraggableState,
} = this.props;
@ -188,7 +189,7 @@ class VideoListItem extends Component {
return (
<div className={cx({
[styles.content]: true,
[styles.talking]: user.isTalking,
[styles.talking]: voiceUser.talking,
})}
>
{
@ -244,8 +245,8 @@ class VideoListItem extends Component {
</div>
)
}
{user.isMuted ? <Icon className={styles.muted} iconName="unmute_filled" /> : null}
{user.isListenOnly ? <Icon className={styles.voice} iconName="listen" /> : null}
{voiceUser.muted ? <Icon className={styles.muted} iconName="unmute_filled" /> : null}
{voiceUser.listenOnly ? <Icon className={styles.voice} iconName="listen" /> : null}
</div>
{
showStats

View File

@ -0,0 +1,18 @@
import React from 'react';
import { withTracker } from 'meteor/react-meteor-data';
import VoiceUsers from '/imports/api/voice-users/';
import VideoListItem from './component';
const VideoListItemContainer = props => (
<VideoListItem {...props} />
);
export default withTracker((props) => {
const {
user,
} = props;
return {
voiceUser: VoiceUsers.findOne({ intId: user.userId }),
};
})(VideoListItemContainer);