fix(webcam): tweak subscriptions

This commit is contained in:
João Victor 2024-06-07 17:41:36 -03:00
parent 558fca5f19
commit 9b02a41e3d
20 changed files with 187 additions and 172 deletions

View File

@ -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:

View File

@ -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';

View File

@ -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(() => {

View File

@ -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}

View File

@ -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}

View File

@ -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,
};
};

View File

@ -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,
};

View File

@ -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);

View File

@ -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[]) => {

View File

@ -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;

View File

@ -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;

View File

@ -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}

View File

@ -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}

View File

@ -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>
);

View File

@ -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}
/>
);

View File

@ -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);

View File

@ -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';

View File

@ -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?: {

View File

@ -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;

View File

@ -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);
};