fix(webcam): tweak subscriptions
This commit is contained in:
parent
558fca5f19
commit
9b02a41e3d
@ -6,6 +6,16 @@ configuration:
|
||||
custom_column_names: {}
|
||||
custom_name: user_ref
|
||||
custom_root_fields: {}
|
||||
object_relationships:
|
||||
- name: reaction
|
||||
using:
|
||||
manual_configuration:
|
||||
column_mapping:
|
||||
userId: userId
|
||||
insertion_order: null
|
||||
remote_table:
|
||||
name: v_user_reaction_current
|
||||
schema: public
|
||||
select_permissions:
|
||||
- role: bbb_client
|
||||
permission:
|
||||
|
@ -2,7 +2,7 @@ import { Meteor } from 'meteor/meteor';
|
||||
import AudioService from '/imports/ui/components/audio/service';
|
||||
import AudioManager from '/imports/ui/services/audio-manager';
|
||||
import VideoService from '/imports/ui/components/video-provider/video-provider-graphql/service';
|
||||
import { Stream } from '/imports/ui/components/video-provider/video-provider-graphql/state';
|
||||
import type { Stream } from '/imports/ui/components/video-provider/video-provider-graphql/types';
|
||||
import { screenshareHasEnded } from '/imports/ui/components/screenshare/service';
|
||||
import { didUserSelectedListenOnly, didUserSelectedMicrophone } from '../../audio/audio-modal/service';
|
||||
import logger from '/imports/startup/client/logger';
|
||||
|
@ -1,4 +1,5 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { throttle } from 'radash';
|
||||
import logger from '/imports/startup/client/logger';
|
||||
import {
|
||||
VIDEO_STREAMS_SUBSCRIPTION,
|
||||
@ -8,6 +9,8 @@ import { setStreams } from './state';
|
||||
import { AdapterProps } from '../../components-data/graphqlToMiniMongoAdapterManager/component';
|
||||
import useDeduplicatedSubscription from '/imports/ui/core/hooks/useDeduplicatedSubscription';
|
||||
|
||||
const throttledSetStreams = throttle({ interval: 500 }, setStreams);
|
||||
|
||||
const VideoStreamAdapter: React.FC<AdapterProps> = ({
|
||||
onReady,
|
||||
children,
|
||||
@ -23,24 +26,23 @@ const VideoStreamAdapter: React.FC<AdapterProps> = ({
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
setStreams([]);
|
||||
throttledSetStreams([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const streams = data.user_camera.map(({ streamId, user, voice }) => ({
|
||||
stream: streamId,
|
||||
deviceId: streamId.split('_')[3],
|
||||
userId: user.userId,
|
||||
name: user.name,
|
||||
nameSortable: user.nameSortable,
|
||||
pinned: user.pinned,
|
||||
floor: voice?.floor || false,
|
||||
lastFloorTime: voice?.lastFloorTime || '0',
|
||||
isModerator: user.isModerator,
|
||||
userId: user.userId,
|
||||
user,
|
||||
floor: voice?.floor ?? false,
|
||||
lastFloorTime: voice?.lastFloorTime ?? '0',
|
||||
type: 'stream' as const,
|
||||
}));
|
||||
|
||||
setStreams(streams);
|
||||
throttledSetStreams(streams);
|
||||
}, [data]);
|
||||
|
||||
useEffect(() => {
|
||||
|
@ -22,7 +22,7 @@ import {
|
||||
import { notify } from '/imports/ui/services/notification';
|
||||
import { shouldForceRelay } from '/imports/ui/services/bbb-webrtc-sfu/utils';
|
||||
import WebRtcPeer from '/imports/ui/services/webrtc-base/peer';
|
||||
import { StreamItem, StreamUser, VideoItem } from './types';
|
||||
import { StreamItem, VideoItem } from './types';
|
||||
import { Output } from '../../layout/layoutTypes';
|
||||
|
||||
const intlClientErrors = defineMessages({
|
||||
@ -109,7 +109,6 @@ interface VideoProviderGraphqlProps {
|
||||
isUserLocked: boolean;
|
||||
currentVideoPageIndex: number;
|
||||
streams: VideoItem[];
|
||||
users: StreamUser[];
|
||||
info: {
|
||||
userId: string | null | undefined;
|
||||
userName: string | null | undefined;
|
||||
@ -1343,7 +1342,6 @@ class VideoProviderGraphql extends Component<VideoProviderGraphqlProps, VideoPro
|
||||
focusedId,
|
||||
handleVideoFocus,
|
||||
isGridEnabled,
|
||||
users,
|
||||
} = this.props;
|
||||
|
||||
return (
|
||||
@ -1356,7 +1354,6 @@ class VideoProviderGraphql extends Component<VideoProviderGraphqlProps, VideoPro
|
||||
focusedId,
|
||||
handleVideoFocus,
|
||||
isGridEnabled,
|
||||
users,
|
||||
}}
|
||||
onVideoItemMount={this.createVideoTag}
|
||||
onVideoItemUnmount={this.destroyVideoTag}
|
||||
|
@ -82,7 +82,6 @@ const VideoProviderContainerGraphql: React.FC<VideoProviderContainerGraphqlProps
|
||||
streams,
|
||||
gridUsers,
|
||||
totalNumberOfStreams,
|
||||
users,
|
||||
} = useVideoStreams(isGridEnabled, paginationEnabled, viewParticipantsWebcams);
|
||||
|
||||
let usersVideo: VideoItem[] = streams;
|
||||
@ -96,7 +95,13 @@ const VideoProviderContainerGraphql: React.FC<VideoProviderContainerGraphqlProps
|
||||
&& currentUser?.locked
|
||||
) {
|
||||
usersVideo = usersVideo.filter(
|
||||
(uv) => (uv.type !== 'connecting' && uv.isModerator) || uv.userId === currentUserId,
|
||||
(uv) => (
|
||||
(
|
||||
(uv.type === 'stream' && uv.user.isModerator)
|
||||
|| (uv.type === 'grid' && uv.isModerator)
|
||||
)
|
||||
|| uv.userId === currentUserId
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@ -124,7 +129,6 @@ const VideoProviderContainerGraphql: React.FC<VideoProviderContainerGraphqlProps
|
||||
isUserLocked={isUserLocked}
|
||||
currentVideoPageIndex={currentVideoPageIndex}
|
||||
streams={usersVideo}
|
||||
users={users}
|
||||
info={info}
|
||||
playStart={playStart}
|
||||
exitVideo={exitVideo}
|
||||
|
@ -29,15 +29,14 @@ import {
|
||||
} from '../state';
|
||||
import {
|
||||
OWN_VIDEO_STREAMS_QUERY,
|
||||
VIDEO_STREAMS_USERS_FILTERED_SUBSCRIPTION,
|
||||
GRID_USERS_SUBSCRIPTION,
|
||||
VIEWERS_IN_WEBCAM_COUNT_SUBSCRIPTION,
|
||||
VideoStreamsUsersResponse,
|
||||
GridUsersResponse,
|
||||
OwnVideoStreamsResponse,
|
||||
} from '../queries';
|
||||
import videoService from '../service';
|
||||
import { CAMERA_BROADCAST_STOP } from '../mutations';
|
||||
import { GridItem, StreamItem, StreamUser } from '../types';
|
||||
import { GridItem, StreamItem } from '../types';
|
||||
import { DesktopPageSizes, MobilePageSizes } from '/imports/ui/Types/meetingClientSettings';
|
||||
import logger from '/imports/startup/client/logger';
|
||||
import useDeduplicatedSubscription from '/imports/ui/core/hooks/useDeduplicatedSubscription';
|
||||
@ -239,19 +238,14 @@ export const useStreams = () => {
|
||||
export const useStreamUsers = (isGridEnabled: boolean) => {
|
||||
const { streams } = useStreams();
|
||||
const gridSize = useGridSize();
|
||||
const [users, setUsers] = useState<StreamUser[]>([]);
|
||||
const [gridUsers, setGridUsers] = useState<GridItem[]>([]);
|
||||
const userIds = useMemo(() => streams.map((s) => s.userId), [streams]);
|
||||
const userIds = useMemo(() => streams.map((s) => s.user.userId), [streams]);
|
||||
const streamCount = streams.length;
|
||||
const { data, loading, error } = useDeduplicatedSubscription<VideoStreamsUsersResponse>(
|
||||
VIDEO_STREAMS_USERS_FILTERED_SUBSCRIPTION,
|
||||
{ variables: { userIds } },
|
||||
);
|
||||
const {
|
||||
data: gridData,
|
||||
loading: gridLoading,
|
||||
error: gridError,
|
||||
} = useDeduplicatedSubscription<VideoStreamsUsersResponse>(
|
||||
} = useDeduplicatedSubscription<GridUsersResponse>(
|
||||
GRID_USERS_SUBSCRIPTION,
|
||||
{
|
||||
variables: { exceptUserIds: userIds, limit: Math.max(gridSize - streamCount, 0) },
|
||||
@ -259,16 +253,6 @@ export const useStreamUsers = (isGridEnabled: boolean) => {
|
||||
},
|
||||
);
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) return;
|
||||
|
||||
if (error) {
|
||||
logger.error(`Stream users subscription failed. name=${error.name}`, error);
|
||||
}
|
||||
|
||||
setUsers(data ? data.user : []);
|
||||
}, [data]);
|
||||
|
||||
useEffect(() => {
|
||||
if (gridLoading) return;
|
||||
|
||||
@ -289,10 +273,9 @@ export const useStreamUsers = (isGridEnabled: boolean) => {
|
||||
|
||||
return {
|
||||
streams,
|
||||
users,
|
||||
gridUsers,
|
||||
loading: loading || gridLoading,
|
||||
error: error || gridError,
|
||||
loading: gridLoading,
|
||||
error: gridError,
|
||||
};
|
||||
};
|
||||
|
||||
@ -348,7 +331,7 @@ export const useVideoStreams = (
|
||||
) => {
|
||||
const [state] = useVideoState();
|
||||
const { currentVideoPageIndex, numberOfPages } = state;
|
||||
const { users, gridUsers, streams: videoStreams } = useStreamUsers(isGridEnabled);
|
||||
const { gridUsers, streams: videoStreams } = useStreamUsers(isGridEnabled);
|
||||
const connectingStream = useConnectingStream(videoStreams);
|
||||
const myPageSize = useMyPageSize();
|
||||
const isPaginationEnabled = useIsPaginationEnabled(paginationEnabled);
|
||||
@ -368,11 +351,11 @@ export const useVideoStreams = (
|
||||
if (isPaginationEnabled) {
|
||||
const [filtered, others] = partition(
|
||||
streams,
|
||||
(vs: StreamItem) => videoService.isLocalStream(vs.stream) || (vs.type === 'stream' && vs.pinned),
|
||||
(vs: StreamItem) => videoService.isLocalStream(vs.stream) || (vs.type === 'stream' && vs.user.pinned),
|
||||
);
|
||||
const [pin, mine] = partition(
|
||||
filtered,
|
||||
(vs: StreamItem) => vs.type === 'stream' && vs.pinned,
|
||||
(vs: StreamItem) => vs.type === 'stream' && vs.user.pinned,
|
||||
);
|
||||
|
||||
if (myPageSize !== 0) {
|
||||
@ -415,7 +398,6 @@ export const useVideoStreams = (
|
||||
streams,
|
||||
gridUsers,
|
||||
totalNumberOfStreams: streams.length,
|
||||
users,
|
||||
};
|
||||
};
|
||||
|
||||
|
@ -1,42 +1,21 @@
|
||||
import { gql } from '@apollo/client';
|
||||
import type { User } from './types';
|
||||
|
||||
interface Voice {
|
||||
floor: boolean;
|
||||
lastFloorTime: string;
|
||||
}
|
||||
|
||||
export interface VideoStreamsResponse {
|
||||
user_camera: {
|
||||
streamId: string;
|
||||
user: {
|
||||
userId: string;
|
||||
pinned: boolean;
|
||||
nameSortable: string;
|
||||
name: string;
|
||||
isModerator: boolean;
|
||||
};
|
||||
voice?: {
|
||||
floor: boolean;
|
||||
lastFloorTime: string;
|
||||
};
|
||||
user: User;
|
||||
voice?: Voice;
|
||||
}[];
|
||||
}
|
||||
|
||||
export interface VideoStreamsUsersResponse {
|
||||
user: {
|
||||
userId: string;
|
||||
pinned: boolean;
|
||||
nameSortable: string;
|
||||
name: string;
|
||||
away: boolean;
|
||||
disconnected: boolean;
|
||||
emoji: string;
|
||||
role: string;
|
||||
avatar: string;
|
||||
color: string;
|
||||
presenter: boolean;
|
||||
clientType: string;
|
||||
raiseHand: boolean;
|
||||
isModerator: boolean;
|
||||
reaction: {
|
||||
reactionEmoji: string;
|
||||
};
|
||||
}[];
|
||||
export interface GridUsersResponse {
|
||||
user: User[];
|
||||
}
|
||||
|
||||
export interface OwnVideoStreamsResponse {
|
||||
@ -50,11 +29,23 @@ export const VIDEO_STREAMS_SUBSCRIPTION = gql`
|
||||
user_camera {
|
||||
streamId
|
||||
user {
|
||||
userId
|
||||
pinned
|
||||
nameSortable
|
||||
name
|
||||
userId
|
||||
nameSortable
|
||||
pinned
|
||||
away
|
||||
disconnected
|
||||
emoji
|
||||
role
|
||||
avatar
|
||||
color
|
||||
presenter
|
||||
clientType
|
||||
raiseHand
|
||||
isModerator
|
||||
reaction {
|
||||
reactionEmoji
|
||||
}
|
||||
}
|
||||
voice {
|
||||
floor
|
||||
@ -115,38 +106,6 @@ export const GRID_USERS_SUBSCRIPTION = gql`
|
||||
color
|
||||
presenter
|
||||
clientType
|
||||
userId
|
||||
raiseHand
|
||||
isModerator
|
||||
reaction {
|
||||
reactionEmoji
|
||||
}
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
export const VIDEO_STREAMS_USERS_FILTERED_SUBSCRIPTION = gql`
|
||||
subscription FilteredVideoStreamsUsers($userIds: [String]!) {
|
||||
user(
|
||||
where: {
|
||||
userId: {
|
||||
_in: $userIds
|
||||
}
|
||||
}
|
||||
) {
|
||||
name
|
||||
userId
|
||||
nameSortable
|
||||
pinned
|
||||
away
|
||||
disconnected
|
||||
emoji
|
||||
role
|
||||
avatar
|
||||
color
|
||||
presenter
|
||||
clientType
|
||||
userId
|
||||
raiseHand
|
||||
isModerator
|
||||
reaction {
|
||||
@ -161,5 +120,4 @@ export default {
|
||||
VIDEO_STREAMS_SUBSCRIPTION,
|
||||
VIEWERS_IN_WEBCAM_COUNT_SUBSCRIPTION,
|
||||
GRID_USERS_SUBSCRIPTION,
|
||||
VIDEO_STREAMS_USERS_FILTERED_SUBSCRIPTION,
|
||||
};
|
||||
|
@ -16,12 +16,12 @@ import {
|
||||
setVideoState,
|
||||
setConnectingStream,
|
||||
getVideoState,
|
||||
Stream,
|
||||
} from './state';
|
||||
import WebRtcPeer from '/imports/ui/services/webrtc-base/peer';
|
||||
import { Constraints2 } from '/imports/ui/Types/meetingClientSettings';
|
||||
import MediaStreamUtils from '/imports/utils/media-stream-utils';
|
||||
import Session from '/imports/ui/services/storage/in-memory';
|
||||
import type { Stream } from './types';
|
||||
|
||||
const TOKEN = '_';
|
||||
|
||||
@ -110,6 +110,7 @@ class VideoService {
|
||||
stream: streamName,
|
||||
userId: Auth.userID as string,
|
||||
name: Auth.fullname as string,
|
||||
nameSortable: Auth.fullname as string,
|
||||
type: 'connecting' as const,
|
||||
};
|
||||
setConnectingStream(stream);
|
||||
|
@ -1,5 +1,6 @@
|
||||
import { makeVar, useReactiveVar } from '@apollo/client';
|
||||
import createUseLocalState from '/imports/ui/core/local-states/createUseLocalState';
|
||||
import type { ConnectingStream, Stream } from './types';
|
||||
|
||||
const [useVideoState, setVideoState, videoState] = createUseLocalState({
|
||||
isConnecting: false,
|
||||
@ -12,13 +13,6 @@ const [useVideoState, setVideoState, videoState] = createUseLocalState({
|
||||
|
||||
const getVideoState = () => videoState();
|
||||
|
||||
export type ConnectingStream = {
|
||||
stream: string;
|
||||
name: string;
|
||||
userId: string;
|
||||
type: 'connecting';
|
||||
};
|
||||
|
||||
const connectingStream = makeVar<ConnectingStream | null>(null);
|
||||
|
||||
const useConnectingStream = (streams?: Stream[]) => {
|
||||
@ -41,19 +35,6 @@ const setConnectingStream = (stream: ConnectingStream | null) => {
|
||||
|
||||
const getConnectingStream = () => connectingStream();
|
||||
|
||||
export type Stream = {
|
||||
stream: string;
|
||||
deviceId: string;
|
||||
userId: string;
|
||||
name: string;
|
||||
nameSortable: string;
|
||||
pinned: boolean;
|
||||
floor: boolean;
|
||||
lastFloorTime: string;
|
||||
isModerator: boolean;
|
||||
type: 'stream';
|
||||
}
|
||||
|
||||
const streams = makeVar<Stream[]>([]);
|
||||
|
||||
const setStreams = (vs: Stream[]) => {
|
||||
|
@ -13,9 +13,9 @@ export const sortPin = (s1: StreamItem, s2: StreamItem) => {
|
||||
if (s2.type === 'connecting') {
|
||||
return -1;
|
||||
}
|
||||
if (s1.pinned) {
|
||||
if (s1.user.pinned) {
|
||||
return -1;
|
||||
} if (s2.pinned) {
|
||||
} if (s2.user.pinned) {
|
||||
return 1;
|
||||
}
|
||||
return 0;
|
||||
|
@ -1,7 +1,44 @@
|
||||
import { VideoStreamsUsersResponse } from './queries';
|
||||
import { ConnectingStream, Stream } from './state';
|
||||
export type User = {
|
||||
userId: string;
|
||||
pinned: boolean;
|
||||
nameSortable: string;
|
||||
name: string;
|
||||
away: boolean;
|
||||
disconnected: boolean;
|
||||
emoji: string;
|
||||
role: string;
|
||||
avatar: string;
|
||||
color: string;
|
||||
presenter: boolean;
|
||||
clientType: string;
|
||||
raiseHand: boolean;
|
||||
isModerator: boolean;
|
||||
reaction: {
|
||||
reactionEmoji: string;
|
||||
};
|
||||
}
|
||||
|
||||
export type StreamUser = VideoStreamsUsersResponse['user'][number];
|
||||
export type ConnectingStream = {
|
||||
userId: string;
|
||||
stream: string;
|
||||
name: string;
|
||||
nameSortable: string;
|
||||
type: 'connecting';
|
||||
};
|
||||
|
||||
export type Stream = {
|
||||
userId: string;
|
||||
stream: string;
|
||||
deviceId: string;
|
||||
name: string;
|
||||
nameSortable: string;
|
||||
user: User;
|
||||
floor: boolean;
|
||||
lastFloorTime: string;
|
||||
type: 'stream';
|
||||
}
|
||||
|
||||
export type GridUser = User;
|
||||
export type StreamItem = Stream | ConnectingStream;
|
||||
export type GridItem = StreamUser & { type: 'grid' };
|
||||
export type GridItem = GridUser & { type: 'grid' };
|
||||
export type VideoItem = StreamItem | GridItem;
|
||||
|
@ -10,7 +10,7 @@ import playAndRetry from '/imports/utils/mediaElementPlayRetry';
|
||||
import VideoService from '../service';
|
||||
import { ACTIONS } from '/imports/ui/components/layout/enums';
|
||||
import { Output } from '../../../layout/layoutTypes';
|
||||
import { StreamUser, VideoItem } from '../types';
|
||||
import { VideoItem } from '../types';
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
autoplayBlockedDesc: {
|
||||
@ -74,7 +74,6 @@ interface VideoListProps {
|
||||
focusedId: string;
|
||||
handleVideoFocus: (id: string) => void;
|
||||
isGridEnabled: boolean;
|
||||
users: StreamUser[];
|
||||
streams: VideoItem[];
|
||||
intl: IntlShape;
|
||||
onVideoItemMount: (stream: string, video: HTMLVideoElement) => void;
|
||||
@ -346,7 +345,6 @@ class VideoList extends Component<VideoListProps, VideoListState> {
|
||||
swapLayout,
|
||||
handleVideoFocus,
|
||||
focusedId,
|
||||
users,
|
||||
} = this.props;
|
||||
const numOfStreams = streams.length;
|
||||
|
||||
@ -356,7 +354,6 @@ class VideoList extends Component<VideoListProps, VideoListState> {
|
||||
const stream = isStream ? item.stream : null;
|
||||
const key = isStream ? stream : userId;
|
||||
const isFocused = isStream && focusedId === stream && numOfStreams > 2;
|
||||
const user = users.find((u) => u.userId === userId) || {};
|
||||
|
||||
return (
|
||||
<Styled.VideoListItem
|
||||
@ -365,7 +362,6 @@ class VideoList extends Component<VideoListProps, VideoListState> {
|
||||
data-test="webcamVideoItem"
|
||||
>
|
||||
<VideoListItemContainer
|
||||
user={user}
|
||||
numOfStreams={numOfStreams}
|
||||
cameraId={stream}
|
||||
userId={userId}
|
||||
|
@ -2,7 +2,7 @@ import React from 'react';
|
||||
import VideoList from '/imports/ui/components/video-provider/video-provider-graphql/video-list/component';
|
||||
import { layoutSelect, layoutDispatch } from '/imports/ui/components/layout/context';
|
||||
import { useNumberOfPages } from '../hooks';
|
||||
import { StreamUser, VideoItem } from '../types';
|
||||
import { VideoItem } from '../types';
|
||||
import { Layout, Output } from '../../../layout/layoutTypes';
|
||||
|
||||
interface VideoListContainerProps {
|
||||
@ -13,7 +13,6 @@ interface VideoListContainerProps {
|
||||
focusedId: string;
|
||||
handleVideoFocus: (id: string) => void;
|
||||
isGridEnabled: boolean;
|
||||
users: StreamUser[];
|
||||
onVideoItemMount: (stream: string, video: HTMLVideoElement) => void;
|
||||
onVideoItemUnmount: (stream: string) => void;
|
||||
onVirtualBgDrop: (stream: string, type: string, name: string, data: string) => Promise<unknown>;
|
||||
@ -30,7 +29,6 @@ const VideoListContainer: React.FC<VideoListContainerProps> = (props) => {
|
||||
handleVideoFocus,
|
||||
isGridEnabled,
|
||||
swapLayout,
|
||||
users,
|
||||
onVideoItemMount,
|
||||
onVideoItemUnmount,
|
||||
onVirtualBgDrop,
|
||||
@ -51,7 +49,6 @@ const VideoListContainer: React.FC<VideoListContainerProps> = (props) => {
|
||||
focusedId={focusedId}
|
||||
handleVideoFocus={handleVideoFocus}
|
||||
isGridEnabled={isGridEnabled}
|
||||
users={users}
|
||||
streams={streams}
|
||||
onVideoItemMount={onVideoItemMount}
|
||||
onVideoItemUnmount={onVideoItemUnmount}
|
||||
|
@ -15,7 +15,8 @@ import VideoService from '/imports/ui/components/video-provider/video-provider-g
|
||||
import Styled from './styles';
|
||||
import withDragAndDrop from './drag-and-drop/component';
|
||||
import Auth from '/imports/ui/services/auth';
|
||||
import { StreamUser, VideoItem } from '../../types';
|
||||
import { VideoItem } from '../../types';
|
||||
import useCurrentUser from '/imports/ui/core/hooks/useCurrentUser';
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
disableDesc: {
|
||||
@ -41,7 +42,6 @@ interface VideoListItemProps {
|
||||
onVideoItemUnmount: (stream: string) => void;
|
||||
settingsSelfViewDisable: boolean;
|
||||
stream: VideoItem;
|
||||
user: Partial<StreamUser>;
|
||||
makeDragOperations: (userId?: string) => {
|
||||
onDragOver: (e: DragEvent) => void,
|
||||
onDrop: (e: DragEvent) => void,
|
||||
@ -59,7 +59,7 @@ interface VideoListItemProps {
|
||||
|
||||
const VideoListItem: React.FC<VideoListItemProps> = (props) => {
|
||||
const {
|
||||
name, voiceUser, isFullscreenContext, layoutContextDispatch, user, onHandleVideoFocus,
|
||||
name, voiceUser, isFullscreenContext, layoutContextDispatch, onHandleVideoFocus,
|
||||
cameraId, numOfStreams, focused, onVideoItemMount, onVideoItemUnmount,
|
||||
makeDragOperations, dragging, draggingOver, isRTL, isStream, settingsSelfViewDisable,
|
||||
disabledCams, amIModerator, stream,
|
||||
@ -69,7 +69,7 @@ const VideoListItem: React.FC<VideoListItemProps> = (props) => {
|
||||
|
||||
const [videoDataLoaded, setVideoDataLoaded] = useState(false);
|
||||
const [isStreamHealthy, setIsStreamHealthy] = useState(false);
|
||||
const [isMirrored, setIsMirrored] = useState<boolean>(VideoService.mirrorOwnWebcam(user?.userId));
|
||||
const [isMirrored, setIsMirrored] = useState<boolean>(VideoService.mirrorOwnWebcam(stream.userId));
|
||||
const [isVideoSqueezed, setIsVideoSqueezed] = useState(false);
|
||||
const [isSelfViewDisabled, setIsSelfViewDisabled] = useState(false);
|
||||
|
||||
@ -87,6 +87,44 @@ const VideoListItem: React.FC<VideoListItemProps> = (props) => {
|
||||
const Settings = getSettingsSingletonInstance();
|
||||
const { animations, webcamBorderHighlightColor } = Settings.application;
|
||||
const talking = voiceUser?.talking;
|
||||
const raiseHand = (stream.type === 'grid' && stream.raiseHand)
|
||||
|| (stream.type === 'stream' && stream.user.raiseHand);
|
||||
const { data: currentUser } = useCurrentUser((u) => ({
|
||||
userId: u.userId,
|
||||
pinned: u.pinned,
|
||||
nameSortable: u.nameSortable,
|
||||
name: u.name,
|
||||
away: u.away,
|
||||
disconnected: u.disconnected,
|
||||
emoji: u.emoji,
|
||||
role: u.role,
|
||||
avatar: u.avatar,
|
||||
color: u.color,
|
||||
presenter: u.presenter,
|
||||
clientType: u.clientType,
|
||||
raiseHand: u.raiseHand,
|
||||
isModerator: u.isModerator,
|
||||
reaction: {
|
||||
reactionEmoji: u.reaction?.reactionEmoji ?? '',
|
||||
},
|
||||
}));
|
||||
|
||||
let user;
|
||||
switch (stream.type) {
|
||||
case 'stream': {
|
||||
user = stream.user;
|
||||
break;
|
||||
}
|
||||
case 'grid': {
|
||||
user = stream;
|
||||
break;
|
||||
}
|
||||
case 'connecting':
|
||||
default: {
|
||||
user = currentUser ?? {};
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
const onStreamStateChange = (e: CustomEvent) => {
|
||||
const { streamState } = e.detail;
|
||||
@ -135,7 +173,7 @@ const VideoListItem: React.FC<VideoListItemProps> = (props) => {
|
||||
if (!isSelfViewDisabled && videoDataLoaded) {
|
||||
playElement(videoTag.current!);
|
||||
}
|
||||
if ((isSelfViewDisabled && user.userId === Auth.userID) || disabledCams?.includes(cameraId)) {
|
||||
if ((isSelfViewDisabled && stream.userId === Auth.userID) || disabledCams?.includes(cameraId)) {
|
||||
videoTag.current?.pause?.();
|
||||
}
|
||||
}, [isSelfViewDisabled, videoDataLoaded]);
|
||||
@ -185,7 +223,7 @@ const VideoListItem: React.FC<VideoListItemProps> = (props) => {
|
||||
squeezed={false}
|
||||
/>
|
||||
<Styled.TopBar>
|
||||
{user?.raiseHand && <Styled.RaiseHand>✋</Styled.RaiseHand>}
|
||||
{raiseHand && <Styled.RaiseHand>✋</Styled.RaiseHand>}
|
||||
</Styled.TopBar>
|
||||
<Styled.BottomBar>
|
||||
<UserActions
|
||||
@ -233,7 +271,7 @@ const VideoListItem: React.FC<VideoListItemProps> = (props) => {
|
||||
const renderDefaultButtons = () => (
|
||||
<>
|
||||
<Styled.TopBar>
|
||||
{user.raiseHand && <Styled.RaiseHand>✋</Styled.RaiseHand>}
|
||||
{raiseHand && <Styled.RaiseHand>✋</Styled.RaiseHand>}
|
||||
<PinArea
|
||||
stream={stream}
|
||||
amIModerator={amIModerator}
|
||||
@ -271,7 +309,7 @@ const VideoListItem: React.FC<VideoListItemProps> = (props) => {
|
||||
onDragLeave,
|
||||
onDragOver,
|
||||
onDrop,
|
||||
} = makeDragOperations(user?.userId);
|
||||
} = makeDragOperations(stream.userId);
|
||||
|
||||
return (
|
||||
// @ts-expect-error -> Until everything in Typescript.
|
||||
@ -293,7 +331,7 @@ const VideoListItem: React.FC<VideoListItemProps> = (props) => {
|
||||
>
|
||||
|
||||
<Styled.VideoContainer
|
||||
$selfViewDisabled={(isSelfViewDisabled && user.userId === Auth.userID)
|
||||
$selfViewDisabled={(isSelfViewDisabled && stream.userId === Auth.userID)
|
||||
|| disabledCams.includes(cameraId)}
|
||||
>
|
||||
<Styled.Video
|
||||
@ -307,7 +345,7 @@ const VideoListItem: React.FC<VideoListItemProps> = (props) => {
|
||||
/>
|
||||
</Styled.VideoContainer>
|
||||
|
||||
{isStream && ((isSelfViewDisabled && user.userId === Auth.userID)
|
||||
{isStream && ((isSelfViewDisabled && stream.userId === Auth.userID)
|
||||
|| disabledCams.includes(cameraId)) && (
|
||||
<Styled.VideoDisabled>
|
||||
{intl.formatMessage(intlMessages.disableDesc)}
|
||||
@ -322,7 +360,7 @@ const VideoListItem: React.FC<VideoListItemProps> = (props) => {
|
||||
{!videoIsReady && (!isSelfViewDisabled || !isStream) && (
|
||||
isVideoSqueezed ? renderWebcamConnectingSqueezed() : renderWebcamConnecting()
|
||||
)}
|
||||
{((isSelfViewDisabled && user.userId === Auth.userID) || disabledCams.includes(cameraId))
|
||||
{((isSelfViewDisabled && stream.userId === Auth.userID) || disabledCams.includes(cameraId))
|
||||
&& renderWebcamConnecting()}
|
||||
</Styled.Content>
|
||||
);
|
||||
|
@ -5,7 +5,7 @@ import VoiceUsers from '/imports/api/voice-users/';
|
||||
import useCurrentUser from '/imports/ui/core/hooks/useCurrentUser';
|
||||
import { layoutSelect, layoutDispatch } from '/imports/ui/components/layout/context';
|
||||
import VideoListItem from './component';
|
||||
import { StreamUser, VideoItem } from '../../types';
|
||||
import { VideoItem } from '../../types';
|
||||
import { Layout } from '/imports/ui/components/layout/layoutTypes';
|
||||
import useSettings from '/imports/ui/services/settings/hooks/useSettings';
|
||||
import { SETTINGS } from '/imports/ui/services/settings/enums';
|
||||
@ -21,7 +21,6 @@ type TrackerData = {
|
||||
}
|
||||
|
||||
type TrackerProps = {
|
||||
user: Partial<StreamUser>;
|
||||
numOfStreams: number;
|
||||
cameraId: string | null;
|
||||
userId: string;
|
||||
@ -50,7 +49,6 @@ const VideoListItemContainer: React.FC<VideoListItemContainerProps> = (props) =>
|
||||
onVideoItemUnmount,
|
||||
onVirtualBgDrop,
|
||||
stream,
|
||||
user,
|
||||
voiceUser,
|
||||
} = props;
|
||||
|
||||
@ -90,7 +88,6 @@ const VideoListItemContainer: React.FC<VideoListItemContainerProps> = (props) =>
|
||||
onVirtualBgDrop={onVirtualBgDrop}
|
||||
settingsSelfViewDisable={settingsSelfViewDisable}
|
||||
stream={stream}
|
||||
user={user}
|
||||
voiceUser={voiceUser}
|
||||
/>
|
||||
);
|
||||
|
@ -4,7 +4,7 @@ import VideoService from '/imports/ui/components/video-provider/video-provider-g
|
||||
import { useMutation } from '@apollo/client';
|
||||
import Styled from './styles';
|
||||
import { SET_CAMERA_PINNED } from '/imports/ui/core/graphql/mutations/userMutations';
|
||||
import { VideoItem } from '../../../types';
|
||||
import { VideoItem } from '/imports/ui/components/video-provider/video-provider-graphql/types';
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
unpinLabel: {
|
||||
@ -25,7 +25,7 @@ const PinArea: React.FC<PinAreaProps> = (props) => {
|
||||
|
||||
const { stream, amIModerator } = props;
|
||||
const { userId, type } = stream;
|
||||
const pinned = type === 'stream' && stream.pinned;
|
||||
const pinned = type === 'stream' && stream.user.pinned;
|
||||
const videoPinActionAvailable = VideoService.isVideoPinEnabledForCurrentUser(amIModerator);
|
||||
|
||||
const [setCameraPinned] = useMutation(SET_CAMERA_PINNED);
|
||||
|
@ -13,7 +13,7 @@ import Auth from '/imports/ui/services/auth';
|
||||
import { PluginsContext } from '/imports/ui/components/components-data/plugin-context/context';
|
||||
import { notify } from '/imports/ui/services/notification';
|
||||
import { SET_CAMERA_PINNED } from '/imports/ui/core/graphql/mutations/userMutations';
|
||||
import { VideoItem } from '../../../types';
|
||||
import { VideoItem } from '/imports/ui/components/video-provider/video-provider-graphql/types';
|
||||
import { ACTIONS } from '/imports/ui/components/layout/enums';
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
@ -134,7 +134,7 @@ const UserActions: React.FC<UserActionProps> = (props) => {
|
||||
}, []);
|
||||
|
||||
const getAvailableActions = () => {
|
||||
const pinned = stream.type === 'stream' && stream.pinned;
|
||||
const pinned = stream.type === 'stream' && stream.user.pinned;
|
||||
const { userId } = stream;
|
||||
const isPinnedIntlKey = !pinned ? 'pin' : 'unpin';
|
||||
const isFocusedIntlKey = !focused ? 'focus' : 'unfocus';
|
||||
|
@ -2,10 +2,10 @@ import React from 'react';
|
||||
import Styled from './styles';
|
||||
import Icon from '/imports/ui/components/common/icon/component';
|
||||
import UserListService from '/imports/ui/components/user-list/service';
|
||||
import { StreamUser, VideoItem } from '../../../types';
|
||||
import { User, VideoItem } from '/imports/ui/components/video-provider/video-provider-graphql/types';
|
||||
|
||||
interface UserAvatarVideoProps {
|
||||
user: Partial<StreamUser>;
|
||||
user: Partial<User>;
|
||||
stream: VideoItem;
|
||||
// eslint-disable-next-line react/require-default-props
|
||||
voiceUser?: {
|
||||
|
@ -1,9 +1,9 @@
|
||||
import React from 'react';
|
||||
import Styled from './styles';
|
||||
import { StreamUser, VideoItem } from '../../../types';
|
||||
import { User, VideoItem } from '/imports/ui/components/video-provider/video-provider-graphql/types';
|
||||
|
||||
interface UserStatusProps {
|
||||
user: Partial<StreamUser>;
|
||||
user: Partial<User>;
|
||||
stream: VideoItem;
|
||||
voiceUser: {
|
||||
muted: boolean;
|
||||
|
@ -1,7 +1,9 @@
|
||||
import { useEffect, useMemo, useRef } from 'react';
|
||||
import GrahqlSubscriptionStore, { stringToHash } from '/imports/ui/core/singletons/subscriptionStore';
|
||||
import GrahqlSubscriptionStore, { stringToHash, SubscriptionStructure } from '/imports/ui/core/singletons/subscriptionStore';
|
||||
import { DocumentNode, TypedQueryDocumentNode } from 'graphql';
|
||||
import { OperationVariables, SubscriptionHookOptions, useReactiveVar } from '@apollo/client';
|
||||
import {
|
||||
OperationVariables, SubscriptionHookOptions, makeVar, useReactiveVar,
|
||||
} from '@apollo/client';
|
||||
// same as useSubscription type
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const useDeduplicatedSubscription = <T = any>(
|
||||
@ -11,7 +13,11 @@ const useDeduplicatedSubscription = <T = any>(
|
||||
const subscriptionHashRef = useRef<string>('');
|
||||
const subscriptionRef = useRef <DocumentNode | TypedQueryDocumentNode | null>(null);
|
||||
const optionsRef = useRef(options);
|
||||
const subscriptionHash = stringToHash(JSON.stringify({ subscription, variables: options?.variables }));
|
||||
const subscriptionHash = stringToHash(JSON.stringify({
|
||||
subscription,
|
||||
variables: options?.variables,
|
||||
skip: options?.skip,
|
||||
}));
|
||||
|
||||
useEffect(() => {
|
||||
return () => {
|
||||
@ -32,8 +38,17 @@ const useDeduplicatedSubscription = <T = any>(
|
||||
}, [subscriptionHash]);
|
||||
|
||||
const sub = useMemo(() => {
|
||||
if (options?.skip) {
|
||||
return makeVar<SubscriptionStructure<T>>({
|
||||
count: 0,
|
||||
data: null,
|
||||
error: null,
|
||||
loading: true,
|
||||
sub: null,
|
||||
});
|
||||
}
|
||||
return GrahqlSubscriptionStore.makeSubscription<T>(subscription, options?.variables);
|
||||
}, [subscriptionHash]);
|
||||
}, [subscriptionHash, options?.skip]);
|
||||
return useReactiveVar(sub);
|
||||
};
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user