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); 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); return updateVoiceUser(meetingId, voiceUser);
} }

View File

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

View File

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

View File

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

View File

@ -8,7 +8,6 @@ import UserList from './component';
const propTypes = { const propTypes = {
activeChats: PropTypes.arrayOf(String).isRequired, activeChats: PropTypes.arrayOf(String).isRequired,
getUsersId: PropTypes.func.isRequired,
isBreakoutRoom: PropTypes.bool.isRequired, isBreakoutRoom: PropTypes.bool.isRequired,
getAvailableActions: PropTypes.func.isRequired, getAvailableActions: PropTypes.func.isRequired,
normalizeEmojiName: PropTypes.func.isRequired, normalizeEmojiName: PropTypes.func.isRequired,
@ -33,7 +32,6 @@ UserListContainer.propTypes = propTypes;
export default withTracker(({ chatID, compact }) => ({ export default withTracker(({ chatID, compact }) => ({
hasBreakoutRoom: Service.hasBreakoutRoom(), hasBreakoutRoom: Service.hasBreakoutRoom(),
getUsersId: Service.getUsersId,
activeChats: Service.getActiveChats(chatID), activeChats: Service.getActiveChats(chatID),
isBreakoutRoom: meetingIsBreakout(), isBreakoutRoom: meetingIsBreakout(),
getAvailableActions: Service.getAvailableActions, 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 PUBLIC_GROUP_CHAT_ID = CHAT_CONFIG.public_group_id;
const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator; const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator;
const DIAL_IN_CLIENT_TYPE = 'dial-in-user';
// session for closed chat list // session for closed chat list
const CLOSED_CHAT_LIST_KEY = 'closedChatList'; 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 getCustomLogoUrl = () => Storage.getItem(CUSTOM_LOGO_URL_KEY);
const sortUsersByName = (a, b) => { 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; return -1;
} if (a.name.toLowerCase() > b.name.toLowerCase()) { } if (aName > bName) {
return 1; return 1;
} if (a.id.toLowerCase() > b.id.toLowerCase()) { } if (a.userId > b.userId) {
return -1; return -1;
} if (a.id.toLowerCase() < b.id.toLowerCase()) { } if (a.userId < b.userId) {
return 1; return 1;
} }
@ -56,32 +61,26 @@ const sortUsersByName = (a, b) => {
}; };
const sortUsersByEmoji = (a, b) => { const sortUsersByEmoji = (a, b) => {
const { status: statusA } = a.emoji; if (a.emoji && b.emoji && (a.emoji !== 'none' && b.emoji !== 'none')) {
const { status: statusB } = b.emoji; if (a.emojiTime < b.emojiTime) {
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) {
return -1; return -1;
} if (a.emoji.changedAt > b.emoji.changedAt) { } if (a.emojiTime > b.emojiTime) {
return 1; return 1;
} }
} if (emojiA && emojiA !== 'none') { } if (a.emoji && a.emoji !== 'none') {
return -1; return -1;
} if (emojiB && emojiB !== 'none') { } if (b.emoji && b.emoji !== 'none') {
return 1; return 1;
} }
return 0; return 0;
}; };
const sortUsersByModerator = (a, b) => { const sortUsersByModerator = (a, b) => {
if (a.isModerator && b.isModerator) { if (a.role === ROLE_MODERATOR && b.role === ROLE_MODERATOR) {
return sortUsersByEmoji(a, b); return 0;
} if (a.isModerator) { } if (a.role === ROLE_MODERATOR) {
return -1; return -1;
} if (b.isModerator) { } if (b.role === ROLE_MODERATOR) {
return 1; return 1;
} }
@ -89,11 +88,11 @@ const sortUsersByModerator = (a, b) => {
}; };
const sortUsersByPhoneUser = (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; return 0;
} if (!a.isPhoneUser) { } if (!a.clientType === DIAL_IN_CLIENT_TYPE) {
return -1; return -1;
} if (!b.isPhoneUser) { } if (!b.clientType === DIAL_IN_CLIENT_TYPE) {
return 1; return 1;
} }
@ -102,9 +101,9 @@ const sortUsersByPhoneUser = (a, b) => {
// current user's name is always on top // current user's name is always on top
const sortUsersByCurrent = (a, b) => { const sortUsersByCurrent = (a, b) => {
if (a.isCurrent) { if (a.userId === Auth.userID) {
return -1; return -1;
} if (b.isCurrent) { } if (b.userId === Auth.userID) {
return 1; return 1;
} }
@ -189,13 +188,9 @@ const getUsers = () => {
}, userFindSorting) }, userFindSorting)
.fetch(); .fetch();
return users return users.sort(sortUsers);
.map(mapUser)
.sort(sortUsers);
}; };
const getUsersId = () => getUsers().map(u => u.id);
const hasBreakoutRoom = () => Breakouts.find({ parentMeetingId: Auth.meetingID }).count() > 0; const hasBreakoutRoom = () => Breakouts.find({ parentMeetingId: Auth.meetingID }).count() > 0;
const getActiveChats = (chatID) => { const getActiveChats = (chatID) => {
@ -481,6 +476,7 @@ const requestUserInformation = (userId) => {
}; };
export default { export default {
sortUsers,
setEmojiStatus, setEmojiStatus,
assignPresenter, assignPresenter,
removeUser, removeUser,
@ -489,7 +485,6 @@ export default {
muteAllExceptPresenter, muteAllExceptPresenter,
changeRole, changeRole,
getUsers, getUsers,
getUsersId,
getActiveChats, getActiveChats,
getCurrentUser, getCurrentUser,
getAvailableActions, getAvailableActions,

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,6 +1,7 @@
import React from 'react'; import React from 'react';
import { withTracker } from 'meteor/react-meteor-data'; import { withTracker } from 'meteor/react-meteor-data';
import getFromUserSettings from '/imports/ui/services/users-settings'; import getFromUserSettings from '/imports/ui/services/users-settings';
import Auth from '/imports/ui/services/auth';
import VideoProvider from './component'; import VideoProvider from './component';
import VideoService from './service'; import VideoService from './service';
@ -9,16 +10,13 @@ const VideoProviderContainer = ({ children, ...props }) => {
return (!users.length ? null : <VideoProvider {...props}>{children}</VideoProvider>); return (!users.length ? null : <VideoProvider {...props}>{children}</VideoProvider>);
}; };
export default withTracker((props) => { export default withTracker(props => ({
return { cursor: props.cursor,
cursor: props.cursor, swapLayout: props.swapLayout,
swapLayout: props.swapLayout, meetingId: VideoService.meetingId(),
meetingId: VideoService.meetingId(), users: VideoService.getAllWebcamUsers(),
users: VideoService.getAllUsersVideo(), userId: Auth.userID,
userId: VideoService.userId(), sessionToken: VideoService.sessionToken(),
sessionToken: VideoService.sessionToken(), enableVideoStats: getFromUserSettings('enableVideoStats', Meteor.settings.public.kurento.enableVideoStats),
userName: VideoService.userName(), voiceBridge: VideoService.voiceBridge(),
enableVideoStats: getFromUserSettings('enableVideoStats', Meteor.settings.public.kurento.enableVideoStats), }))(VideoProviderContainer);
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 Auth from '/imports/ui/services/auth';
import Meetings from '/imports/api/meetings/'; import Meetings from '/imports/api/meetings/';
import Users from '/imports/api/users/'; import Users from '/imports/api/users/';
import mapUser from '/imports/ui/services/user/mapUser';
import UserListService from '/imports/ui/components/user-list/service'; 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 { class VideoService {
constructor() { constructor() {
this.defineProperties({ this.defineProperties({
@ -70,40 +72,47 @@ class VideoService {
makeCall('userUnshareWebcam', stream); makeCall('userUnshareWebcam', stream);
} }
getAllUsers() { getAllWebcamUsers() {
// Use the same function as the user-list to share the sorting/mapping const webcamsLocked = this.webcamsLocked();
return UserListService.getUsers(); const webcamsOnlyForModerator = this.webcamsOnlyForModerator();
} const currentUser = Users.findOne({ userId: Auth.userID });
const currentUserIsViewer = currentUser.role === ROLE_VIEWER;
getAllUsersVideo() {
const userId = this.userId();
const isLocked = this.isLocked();
const currentUser = Users.findOne({ userId });
const currentUserIsModerator = mapUser(currentUser).isModerator;
const sharedWebcam = this.isSharing; const sharedWebcam = this.isSharing;
const isSharingWebcam = user => user.isSharingWebcam || (sharedWebcam && user.isCurrent); let users = Users
const isNotLocked = user => !(isLocked && user.isLocked); .find({
meetingId: Auth.meetingID,
connectionStatus: 'online',
hasStream: true,
userId: { $ne: Auth.userID },
})
.fetch();
const isWebcamOnlyModerator = this.webcamOnlyModerator(); const userIsNotLocked = user => user.role === ROLE_MODERATOR || !user.locked;
const allowedSeeViewersWebcams = !isWebcamOnlyModerator || currentUserIsModerator;
const webcamOnlyModerator = (user) => {
if (allowedSeeViewersWebcams) return true;
return user.isModerator || user.isCurrent;
};
return this.getAllUsers() if (webcamsLocked) {
.filter(isSharingWebcam) users = users.filter(userIsNotLocked);
.filter(isNotLocked) }
.filter(webcamOnlyModerator);
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 }) || {}; const m = Meetings.findOne({ meetingId: Auth.meetingID }) || {};
return m.usersProp ? m.usersProp.webcamsOnlyForModerator : false; return m.usersProp ? m.usersProp.webcamsOnlyForModerator : false;
} }
isLocked() { webcamsLocked() {
const m = Meetings.findOne({ meetingId: Auth.meetingID }) || {}; const m = Meetings.findOne({ meetingId: Auth.meetingID }) || {};
return m.lockSettingsProps ? m.lockSettingsProps.disableCam : false; return m.lockSettingsProps ? m.lockSettingsProps.disableCam : false;
} }
@ -145,9 +154,8 @@ export default {
exitVideo: () => videoService.exitVideo(), exitVideo: () => videoService.exitVideo(),
exitingVideo: () => videoService.exitingVideo(), exitingVideo: () => videoService.exitingVideo(),
exitedVideo: () => videoService.exitedVideo(), exitedVideo: () => videoService.exitedVideo(),
getAllUsers: () => videoService.getAllUsers(), webcamsLocked: () => videoService.webcamsLocked(),
webcamOnlyModerator: () => videoService.webcamOnlyModerator(), webcamOnlyModerator: () => videoService.webcamOnlyModerator(),
isLocked: () => videoService.isLocked(),
isSharing: () => videoService.isSharing, isSharing: () => videoService.isSharing,
isConnected: () => videoService.isConnected, isConnected: () => videoService.isConnected,
isWaitingResponse: () => videoService.isWaitingResponse, isWaitingResponse: () => videoService.isWaitingResponse,
@ -156,10 +164,9 @@ export default {
joinedVideo: () => videoService.joinedVideo(), joinedVideo: () => videoService.joinedVideo(),
sendUserShareWebcam: stream => videoService.sendUserShareWebcam(stream), sendUserShareWebcam: stream => videoService.sendUserShareWebcam(stream),
sendUserUnshareWebcam: stream => videoService.sendUserUnshareWebcam(stream), sendUserUnshareWebcam: stream => videoService.sendUserUnshareWebcam(stream),
userId: () => videoService.userId(),
userName: () => videoService.userName(), userName: () => videoService.userName(),
meetingId: () => videoService.meetingId(), meetingId: () => videoService.meetingId(),
getAllUsersVideo: () => videoService.getAllUsersVideo(), getAllWebcamUsers: () => videoService.getAllWebcamUsers(),
sessionToken: () => videoService.sessionToken(), sessionToken: () => videoService.sessionToken(),
voiceBridge: () => videoService.voiceBridge(), voiceBridge: () => videoService.voiceBridge(),
}; };

View File

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

View File

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

View File

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