Merge pull request #20806 from JoVictorNunes/webcam-patch-0724
fix(webcam): fixes related to graphql subscriptions, et al.
This commit is contained in:
commit
46de3f7efd
@ -12,8 +12,6 @@ import LocatedErrorBoundary from '/imports/ui/components/common/error-boundary/l
|
||||
import CustomUsersSettings from '/imports/ui/components/join-handler/custom-users-settings/component';
|
||||
import MeetingClient from '/client/meetingClient';
|
||||
|
||||
import GraphqlToMakeVarAdapterManager from '/imports/ui/components/components-data/graphqlToMakeVarAdapterManager/component';
|
||||
|
||||
const STARTUP_CRASH_METADATA = { logCode: 'app_startup_crash', logMessage: 'Possible startup crash' };
|
||||
const APP_CRASH_METADATA = { logCode: 'app_crash', logMessage: 'Possible app crash' };
|
||||
|
||||
@ -27,11 +25,9 @@ const Main: React.FC = () => {
|
||||
<LocatedErrorBoundary Fallback={ErrorScreen} logMetadata={APP_CRASH_METADATA}>
|
||||
<ConnectionManager>
|
||||
<PresenceManager>
|
||||
<GraphqlToMakeVarAdapterManager>
|
||||
<CustomUsersSettings>
|
||||
<MeetingClient />
|
||||
</CustomUsersSettings>
|
||||
</GraphqlToMakeVarAdapterManager>
|
||||
<CustomUsersSettings>
|
||||
<MeetingClient />
|
||||
</CustomUsersSettings>
|
||||
</PresenceManager>
|
||||
</ConnectionManager>
|
||||
</LocatedErrorBoundary>
|
||||
|
@ -22,11 +22,11 @@ import useUserChangedLocalSettings from '../../services/settings/hooks/useUserCh
|
||||
import { PINNED_PAD_SUBSCRIPTION } from '../notes/queries';
|
||||
import connectionStatus from '../../core/graphql/singletons/connectionStatus';
|
||||
import useDeduplicatedSubscription from '../../core/hooks/useDeduplicatedSubscription';
|
||||
import VideoStreamsState from '../video-provider/state';
|
||||
import useSettings from '../../services/settings/hooks/useSettings';
|
||||
import { SETTINGS } from '../../services/settings/enums';
|
||||
import { useStorageKey } from '../../services/storage/hooks';
|
||||
import useMuteMicrophone from '../audio/audio-graphql/hooks/useMuteMicrophone';
|
||||
import { useVideoStreamsCount } from '../video-provider/hooks';
|
||||
|
||||
const currentUserEmoji = (currentUser) => (currentUser
|
||||
? {
|
||||
@ -128,7 +128,7 @@ const AppContainer = (props) => {
|
||||
const isSharedNotesPinnedFromGraphql = !!pinnedPadData
|
||||
&& pinnedPadData.sharedNotes[0]?.sharedNotesExtId === NOTES_CONFIG.id;
|
||||
const isSharedNotesPinned = sharedNotesInput?.isPinned && isSharedNotesPinnedFromGraphql;
|
||||
const isThereWebcam = VideoStreamsState.getStreams().length > 0;
|
||||
const isThereWebcam = useVideoStreamsCount() > 0;
|
||||
const muteMicrophone = useMuteMicrophone();
|
||||
const isScreenSharingEnabled = useIsScreenSharingEnabled();
|
||||
const isExternalVideoEnabled = useIsExternalVideoEnabled();
|
||||
|
@ -219,7 +219,7 @@ const BreakoutJoinConfirmationContainer: React.FC = () => {
|
||||
data: breakoutData,
|
||||
} = useDeduplicatedSubscription<GetBreakoutDataResponse>(getBreakoutData);
|
||||
const exitVideo = useExitVideo(true);
|
||||
const { streams: videoStreams } = useStreams();
|
||||
const videoStreams = useStreams();
|
||||
const storeVideoDevices = () => {
|
||||
VideoService.storeDeviceIds(videoStreams);
|
||||
};
|
||||
|
@ -161,7 +161,7 @@ const BreakoutRoom: React.FC<BreakoutRoomProps> = ({
|
||||
}, [breakouts]);
|
||||
|
||||
const exitVideo = useExitVideo();
|
||||
const { streams } = useStreams();
|
||||
const streams = useStreams();
|
||||
|
||||
return (
|
||||
<Styled.Panel
|
||||
|
@ -1,49 +0,0 @@
|
||||
import React, {
|
||||
useCallback,
|
||||
useMemo,
|
||||
useRef,
|
||||
useState,
|
||||
} from 'react';
|
||||
|
||||
import VideoStreamAdapter from '/imports/ui/components/video-provider/adapter';
|
||||
|
||||
interface GraphqlToMakeVarAdapterManagerProps {
|
||||
children: React.ReactNode;
|
||||
}
|
||||
|
||||
export interface AdapterProps extends GraphqlToMakeVarAdapterManagerProps {
|
||||
onReady: (key:string) => void;
|
||||
}
|
||||
|
||||
const GraphqlToMakeVarAdapterManager: React.FC<GraphqlToMakeVarAdapterManagerProps> = ({ children }) => {
|
||||
const [adapterLoaded, setAdapterLoaded] = useState(false);
|
||||
const loadedComponents = useRef<{
|
||||
[key: string]: number;
|
||||
}>({});
|
||||
const adapterComponents = useRef([
|
||||
VideoStreamAdapter,
|
||||
]);
|
||||
|
||||
const onReady = useCallback((key: string) => {
|
||||
loadedComponents.current[key] = 1;
|
||||
if (Object.keys(loadedComponents.current).length >= adapterComponents.current.length) {
|
||||
setAdapterLoaded(true);
|
||||
}
|
||||
}, []);
|
||||
|
||||
const nestAdapters = useMemo(() => {
|
||||
return adapterComponents.current.reduce((acc, Component) => (
|
||||
<Component onReady={onReady}>
|
||||
{acc}
|
||||
</Component>
|
||||
), <span />);
|
||||
}, []);
|
||||
return (
|
||||
<>
|
||||
{nestAdapters}
|
||||
{adapterLoaded ? children : null}
|
||||
</>
|
||||
);
|
||||
};
|
||||
|
||||
export default GraphqlToMakeVarAdapterManager;
|
@ -3,7 +3,6 @@ import { useMutation } from '@apollo/client';
|
||||
import { UPDATE_CONNECTION_ALIVE_AT } from './mutations';
|
||||
import { getStatus, handleAudioStatsEvent, startMonitoringNetwork } from '/imports/ui/components/connection-status/service';
|
||||
import connectionStatus from '../../core/graphql/singletons/connectionStatus';
|
||||
import { useGetStats } from '../video-provider/hooks';
|
||||
|
||||
import getBaseUrl from '/imports/ui/core/utils/getBaseUrl';
|
||||
|
||||
@ -14,8 +13,6 @@ const ConnectionStatus = () => {
|
||||
|
||||
const [updateConnectionAliveAtM] = useMutation(UPDATE_CONNECTION_ALIVE_AT);
|
||||
|
||||
const getVideoStreamsStats = useGetStats();
|
||||
|
||||
const handleUpdateConnectionAliveAt = () => {
|
||||
const startTime = performance.now();
|
||||
fetch(
|
||||
@ -66,7 +63,7 @@ const ConnectionStatus = () => {
|
||||
|
||||
if (STATS_ENABLED) {
|
||||
window.addEventListener('audiostats', handleAudioStatsEvent);
|
||||
startMonitoringNetwork(getVideoStreamsStats);
|
||||
startMonitoringNetwork();
|
||||
}
|
||||
|
||||
return () => {
|
||||
|
@ -3,7 +3,6 @@ import { CONNECTION_STATUS_REPORT_SUBSCRIPTION } from '../queries';
|
||||
import Service from '../service';
|
||||
import Component from './component';
|
||||
import useCurrentUser from '/imports/ui/core/hooks/useCurrentUser';
|
||||
import { useGetStats } from '../../video-provider/hooks';
|
||||
import useDeduplicatedSubscription from '/imports/ui/core/hooks/useDeduplicatedSubscription';
|
||||
import { useReactiveVar } from '@apollo/client';
|
||||
import connectionStatus from '/imports/ui/core/graphql/singletons/connectionStatus';
|
||||
@ -16,14 +15,11 @@ const ConnectionStatusContainer = (props) => {
|
||||
|
||||
const newtworkData = useReactiveVar(connectionStatus.getNetworkDataVar());
|
||||
|
||||
const getVideoStreamsStats = useGetStats();
|
||||
|
||||
return (
|
||||
<Component
|
||||
{...props}
|
||||
connectionData={connectionData}
|
||||
amIModerator={amIModerator}
|
||||
getVideoStreamsStats={getVideoStreamsStats}
|
||||
networkData={newtworkData}
|
||||
/>
|
||||
);
|
||||
|
@ -5,6 +5,7 @@ import Session from '/imports/ui/services/storage/in-memory';
|
||||
import { notify } from '/imports/ui/services/notification';
|
||||
import AudioService from '/imports/ui/components/audio/service';
|
||||
import ScreenshareService from '/imports/ui/components/screenshare/service';
|
||||
import VideoService from '/imports/ui/components/video-provider/service';
|
||||
import connectionStatus from '../../core/graphql/singletons/connectionStatus';
|
||||
|
||||
const intlMessages = defineMessages({
|
||||
@ -210,8 +211,8 @@ const getAudioData = async () => {
|
||||
* @returns An Object containing video data for all video peers and screenshare
|
||||
* peer
|
||||
*/
|
||||
const getVideoData = async (getVideoStreamsStats) => {
|
||||
const camerasData = await getVideoStreamsStats() || {};
|
||||
const getVideoData = async () => {
|
||||
const camerasData = await VideoService.getStats() || {};
|
||||
|
||||
const screenshareData = await ScreenshareService.getStats() || {};
|
||||
|
||||
@ -226,10 +227,10 @@ const getVideoData = async (getVideoStreamsStats) => {
|
||||
* For audio, this will get information about the mic/listen-only stream.
|
||||
* @returns An Object containing all this data.
|
||||
*/
|
||||
const getNetworkData = async (getVideoStreamsStats) => {
|
||||
const getNetworkData = async () => {
|
||||
const audio = await getAudioData();
|
||||
|
||||
const video = await getVideoData(getVideoStreamsStats);
|
||||
const video = await getVideoData();
|
||||
|
||||
const user = {
|
||||
time: new Date(),
|
||||
@ -401,11 +402,11 @@ export function getStatus(levels, value) {
|
||||
* Start monitoring the network data.
|
||||
* @return {Promise} A Promise that resolves when process started.
|
||||
*/
|
||||
export async function startMonitoringNetwork(getVideoStreamsStats) {
|
||||
let previousData = await getNetworkData(getVideoStreamsStats);
|
||||
export async function startMonitoringNetwork() {
|
||||
let previousData = await getNetworkData();
|
||||
|
||||
setInterval(async () => {
|
||||
const data = await getNetworkData(getVideoStreamsStats);
|
||||
const data = await getNetworkData();
|
||||
|
||||
const {
|
||||
outbound: audioCurrentUploadRate,
|
||||
|
@ -1,74 +0,0 @@
|
||||
import { useEffect, useRef } from 'react';
|
||||
import { throttle } from 'radash';
|
||||
import logger from '/imports/startup/client/logger';
|
||||
import { VIDEO_STREAMS_SUBSCRIPTION } from './queries';
|
||||
import { VideoStreamsResponse } from './types';
|
||||
import { setStreams } from './state';
|
||||
import { AdapterProps } from '../components-data/graphqlToMakeVarAdapterManager/component';
|
||||
import createUseSubscription from '/imports/ui/core/hooks/createUseSubscription';
|
||||
import { VIDEO_TYPES } from './enums';
|
||||
|
||||
const throttledSetStreams = throttle({ interval: 500 }, setStreams);
|
||||
|
||||
type SubscriptionData = VideoStreamsResponse['user_camera'][number];
|
||||
|
||||
const useVideoStreamsSubscription = createUseSubscription(
|
||||
VIDEO_STREAMS_SUBSCRIPTION,
|
||||
{},
|
||||
true,
|
||||
);
|
||||
|
||||
const VideoStreamAdapter: React.FC<AdapterProps> = ({
|
||||
onReady,
|
||||
children,
|
||||
}) => {
|
||||
const ready = useRef(false);
|
||||
const { data, loading, errors } = useVideoStreamsSubscription();
|
||||
|
||||
useEffect(() => {
|
||||
if (loading) return;
|
||||
|
||||
if (errors) {
|
||||
errors.forEach((error) => {
|
||||
logger.error({
|
||||
logCode: 'video_stream_sub_error',
|
||||
extraInfo: {
|
||||
errorName: error.name,
|
||||
errorMessage: error.message,
|
||||
},
|
||||
}, 'Video streams subscription failed.');
|
||||
});
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
throttledSetStreams([]);
|
||||
return;
|
||||
}
|
||||
|
||||
const streams = (data as SubscriptionData[]).map(({ streamId, user, voice }) => ({
|
||||
stream: streamId,
|
||||
deviceId: streamId.split('_')[3],
|
||||
name: user.name,
|
||||
nameSortable: user.nameSortable,
|
||||
userId: user.userId,
|
||||
user,
|
||||
floor: voice?.floor ?? false,
|
||||
lastFloorTime: voice?.lastFloorTime ?? '0',
|
||||
voice,
|
||||
type: VIDEO_TYPES.STREAM,
|
||||
}));
|
||||
|
||||
throttledSetStreams(streams);
|
||||
}, [data]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!ready.current) {
|
||||
ready.current = true;
|
||||
onReady('VideoStreamAdapter');
|
||||
}
|
||||
}, [loading]);
|
||||
|
||||
return children;
|
||||
};
|
||||
|
||||
export default VideoStreamAdapter;
|
@ -20,7 +20,6 @@ import VideoService from './service';
|
||||
import { Output } from '/imports/ui/components/layout/layoutTypes';
|
||||
import { VideoItem } from './types';
|
||||
import { debounce } from '/imports/utils/debounce';
|
||||
import WebRtcPeer from '/imports/ui/services/webrtc-base/peer';
|
||||
import useSettings from '/imports/ui/services/settings/hooks/useSettings';
|
||||
import { SETTINGS } from '/imports/ui/services/settings/enums';
|
||||
import { useStorageKey } from '/imports/ui/services/storage/hooks';
|
||||
@ -64,7 +63,7 @@ const VideoProviderContainer: React.FC<VideoProviderContainerProps> = (props) =>
|
||||
VideoService.applyCameraProfile,
|
||||
CAMERA_QUALITY_THR_DEBOUNCE,
|
||||
{ leading: false, trailing: true },
|
||||
);
|
||||
) as typeof VideoService.applyCameraProfile;
|
||||
|
||||
const { data: currentMeeting } = useMeeting((m) => ({
|
||||
usersPolicies: m.usersPolicies,
|
||||
@ -89,6 +88,7 @@ const VideoProviderContainer: React.FC<VideoProviderContainerProps> = (props) =>
|
||||
totalNumberOfStreams,
|
||||
totalNumberOfOtherStreams,
|
||||
} = useVideoStreams();
|
||||
VideoService.updateActivePeers(streams);
|
||||
|
||||
let usersVideo: VideoItem[] = streams;
|
||||
|
||||
@ -167,7 +167,7 @@ const VideoProviderContainer: React.FC<VideoProviderContainerProps> = (props) =>
|
||||
exitVideo={exitVideo}
|
||||
lockUser={lockUser}
|
||||
stopVideo={stopVideo}
|
||||
applyCameraProfile={applyCameraProfile as (peer: WebRtcPeer, profileId: string) => void}
|
||||
applyCameraProfile={applyCameraProfile}
|
||||
myRole={myRole}
|
||||
/>
|
||||
);
|
||||
|
@ -1,6 +1,7 @@
|
||||
import {
|
||||
useCallback,
|
||||
useEffect,
|
||||
useRef,
|
||||
} from 'react';
|
||||
import {
|
||||
useReactiveVar,
|
||||
@ -22,22 +23,24 @@ import {
|
||||
getConnectingStream,
|
||||
setVideoState,
|
||||
useConnectingStream,
|
||||
streams,
|
||||
getVideoState,
|
||||
} from '../state';
|
||||
} from '/imports/ui/components/video-provider/state';
|
||||
import {
|
||||
OWN_VIDEO_STREAMS_QUERY,
|
||||
GRID_USERS_SUBSCRIPTION,
|
||||
VIEWERS_IN_WEBCAM_COUNT_SUBSCRIPTION,
|
||||
} from '../queries';
|
||||
import videoService from '../service';
|
||||
import { CAMERA_BROADCAST_STOP } from '../mutations';
|
||||
VIDEO_STREAMS_SUBSCRIPTION,
|
||||
} from '/imports/ui/components/video-provider/queries';
|
||||
import videoService from '/imports/ui/components/video-provider/service';
|
||||
import { CAMERA_BROADCAST_STOP } from '/imports/ui/components/video-provider/mutations';
|
||||
import {
|
||||
GridItem,
|
||||
StreamItem,
|
||||
GridUsersResponse,
|
||||
OwnVideoStreamsResponse,
|
||||
} from '../types';
|
||||
StreamSubscriptionData,
|
||||
Stream,
|
||||
} from '/imports/ui/components/video-provider/types';
|
||||
import { DesktopPageSizes, MobilePageSizes } from '/imports/ui/Types/meetingClientSettings';
|
||||
import logger from '/imports/startup/client/logger';
|
||||
import useDeduplicatedSubscription from '/imports/ui/core/hooks/useDeduplicatedSubscription';
|
||||
@ -47,11 +50,54 @@ import { SETTINGS } from '/imports/ui/services/settings/enums';
|
||||
import { useStorageKey } from '/imports/ui/services/storage/hooks';
|
||||
import ConnectionStatus from '/imports/ui/core/graphql/singletons/connectionStatus';
|
||||
import { VIDEO_TYPES } from '/imports/ui/components/video-provider/enums';
|
||||
import createUseSubscription from '/imports/ui/core/hooks/createUseSubscription';
|
||||
|
||||
const FILTER_VIDEO_STATS = [
|
||||
'outbound-rtp',
|
||||
'inbound-rtp',
|
||||
];
|
||||
const useVideoStreamsSubscription = createUseSubscription(
|
||||
VIDEO_STREAMS_SUBSCRIPTION,
|
||||
{},
|
||||
true,
|
||||
);
|
||||
|
||||
export const useStreams = () => {
|
||||
const { data, loading, errors } = useVideoStreamsSubscription();
|
||||
const streams = useRef<Stream[]>([]);
|
||||
|
||||
if (loading) return streams.current;
|
||||
|
||||
if (errors) {
|
||||
errors.forEach((error) => {
|
||||
logger.error({
|
||||
logCode: 'video_stream_sub_error',
|
||||
extraInfo: {
|
||||
errorName: error.name,
|
||||
errorMessage: error.message,
|
||||
},
|
||||
}, 'Video streams subscription failed.');
|
||||
});
|
||||
}
|
||||
|
||||
if (!data) {
|
||||
streams.current = [];
|
||||
return streams.current;
|
||||
}
|
||||
|
||||
const mappedStreams = (data as StreamSubscriptionData[]).map(({ streamId, user, voice }) => ({
|
||||
stream: streamId,
|
||||
deviceId: streamId.split('_')[3],
|
||||
name: user.name,
|
||||
nameSortable: user.nameSortable,
|
||||
userId: user.userId,
|
||||
user,
|
||||
floor: voice?.floor ?? false,
|
||||
lastFloorTime: voice?.lastFloorTime ?? '0',
|
||||
voice,
|
||||
type: VIDEO_TYPES.STREAM,
|
||||
}));
|
||||
|
||||
streams.current = mappedStreams;
|
||||
|
||||
return streams.current;
|
||||
};
|
||||
|
||||
export const useStatus = () => {
|
||||
const { isConnected, isConnecting } = useVideoState();
|
||||
@ -87,13 +133,13 @@ export const useIsUserLocked = () => {
|
||||
};
|
||||
|
||||
export const useVideoStreamsCount = () => {
|
||||
const { streams } = useStreams();
|
||||
const streams = useStreams();
|
||||
|
||||
return streams.length;
|
||||
};
|
||||
|
||||
export const useLocalVideoStreamsCount = () => {
|
||||
const { streams } = useStreams();
|
||||
const streams = useStreams();
|
||||
const localStreams = streams.filter((vs) => videoService.isLocalStream(vs.stream));
|
||||
|
||||
return localStreams.length;
|
||||
@ -248,26 +294,25 @@ export const useIsPaginationEnabled = () => {
|
||||
return myPageSize > 0 && paginationEnabled;
|
||||
};
|
||||
|
||||
export const useStreams = () => {
|
||||
const videoStreams = useReactiveVar(streams);
|
||||
return { streams: videoStreams };
|
||||
};
|
||||
|
||||
export const useGridUsers = (exceptUserIds: string[], visibleStreamCount: number) => {
|
||||
export const useGridUsers = (visibleStreamCount: number) => {
|
||||
const gridSize = useGridSize();
|
||||
const isGridEnabled = useStorageKey('isGridEnabled');
|
||||
const gridItems = useRef<GridItem[]>([]);
|
||||
|
||||
const {
|
||||
data: gridData,
|
||||
error: gridError,
|
||||
loading: gridLoading,
|
||||
} = useSubscription<GridUsersResponse>(
|
||||
GRID_USERS_SUBSCRIPTION,
|
||||
{
|
||||
variables: { exceptUserIds, limit: Math.max(gridSize - visibleStreamCount, 0) },
|
||||
variables: { limit: Math.max(gridSize - visibleStreamCount, 0) },
|
||||
skip: !isGridEnabled,
|
||||
},
|
||||
);
|
||||
|
||||
if (gridLoading) return gridItems.current;
|
||||
|
||||
if (gridError) {
|
||||
logger.error({
|
||||
logCode: 'grid_users_sub_error',
|
||||
@ -278,21 +323,21 @@ export const useGridUsers = (exceptUserIds: string[], visibleStreamCount: number
|
||||
}, 'Grid users subscription failed.');
|
||||
}
|
||||
|
||||
let gridUsers: GridItem[] = [];
|
||||
|
||||
if (gridData) {
|
||||
const newGridUsers = gridData.user.map((user) => ({
|
||||
...user,
|
||||
type: VIDEO_TYPES.GRID,
|
||||
}));
|
||||
gridUsers = newGridUsers;
|
||||
gridItems.current = newGridUsers;
|
||||
} else {
|
||||
gridItems.current = [];
|
||||
}
|
||||
|
||||
return gridUsers;
|
||||
return gridItems.current;
|
||||
};
|
||||
|
||||
export const useSharedDevices = () => {
|
||||
const { streams } = useStreams();
|
||||
const streams = useStreams();
|
||||
const devices = streams
|
||||
.filter((s) => videoService.isLocalStream(s.stream))
|
||||
.map((vs) => vs.deviceId);
|
||||
@ -339,7 +384,7 @@ export const useGridSize = () => {
|
||||
export const useVideoStreams = () => {
|
||||
const { viewParticipantsWebcams } = useSettings(SETTINGS.DATA_SAVING) as { viewParticipantsWebcams?: boolean };
|
||||
const { currentVideoPageIndex, numberOfPages } = useVideoState();
|
||||
const { streams: videoStreams } = useStreams();
|
||||
const videoStreams = useStreams();
|
||||
const connectingStream = useConnectingStream(videoStreams);
|
||||
const myPageSize = useMyPageSize();
|
||||
const isPaginationEnabled = useIsPaginationEnabled();
|
||||
@ -382,10 +427,7 @@ export const useVideoStreams = () => {
|
||||
streams = sortVideoStreams(streams, DEFAULT_SORTING);
|
||||
}
|
||||
|
||||
const gridUsers = useGridUsers(
|
||||
videoStreams.map((s) => s.userId),
|
||||
streams.length,
|
||||
);
|
||||
const gridUsers = useGridUsers(streams.length);
|
||||
|
||||
return {
|
||||
streams,
|
||||
@ -396,7 +438,7 @@ export const useVideoStreams = () => {
|
||||
};
|
||||
|
||||
export const useHasVideoStream = () => {
|
||||
const { streams } = useStreams();
|
||||
const streams = useStreams();
|
||||
const connectingStream = useConnectingStream();
|
||||
return !!connectingStream || streams.some((s) => videoService.isLocalStream(s.stream));
|
||||
};
|
||||
@ -485,53 +527,6 @@ export const useStopVideo = () => {
|
||||
}, [cameraBroadcastStop]);
|
||||
};
|
||||
|
||||
export const useActivePeers = () => {
|
||||
const videoData = useVideoStreams();
|
||||
|
||||
if (!videoData) return null;
|
||||
|
||||
const { streams: activeVideoStreams } = videoData;
|
||||
|
||||
if (!activeVideoStreams) return null;
|
||||
|
||||
const activePeers: Record<string, RTCPeerConnection> = {};
|
||||
|
||||
activeVideoStreams.forEach((stream) => {
|
||||
if (videoService.webRtcPeersRef()[stream.stream]) {
|
||||
activePeers[stream.stream] = videoService.webRtcPeersRef()[stream.stream].peerConnection;
|
||||
}
|
||||
});
|
||||
|
||||
return activePeers;
|
||||
};
|
||||
|
||||
export const useGetStats = () => {
|
||||
const peers = useActivePeers();
|
||||
|
||||
return useCallback(async () => {
|
||||
if (!peers) return null;
|
||||
|
||||
const stats: Record<string, unknown> = {};
|
||||
|
||||
await Promise.all(
|
||||
Object.keys(peers).map(async (peerId) => {
|
||||
const peerStats = await peers[peerId].getStats();
|
||||
|
||||
const videoStats: Record<string, unknown> = {};
|
||||
|
||||
peerStats.forEach((stat) => {
|
||||
if (FILTER_VIDEO_STATS.includes(stat.type)) {
|
||||
videoStats[stat.type] = stat;
|
||||
}
|
||||
});
|
||||
stats[peerId] = videoStats;
|
||||
}),
|
||||
);
|
||||
|
||||
return stats;
|
||||
}, [peers]);
|
||||
};
|
||||
|
||||
export const useShouldRenderPaginationToggle = () => {
|
||||
const myPageSize = useMyPageSize();
|
||||
const {
|
||||
|
@ -57,11 +57,13 @@ export const VIEWERS_IN_WEBCAM_COUNT_SUBSCRIPTION = gql`
|
||||
`;
|
||||
|
||||
export const GRID_USERS_SUBSCRIPTION = gql`
|
||||
subscription GridUsers($exceptUserIds: [String]!, $limit: Int!) {
|
||||
subscription GridUsers($limit: Int!) {
|
||||
user(
|
||||
where: {
|
||||
userId: {
|
||||
_nin: $exceptUserIds,
|
||||
cameras_aggregate: {
|
||||
count: {
|
||||
predicate: { _eq: 0 },
|
||||
},
|
||||
},
|
||||
},
|
||||
limit: $limit,
|
||||
|
@ -18,11 +18,16 @@ 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';
|
||||
import type { Stream, StreamItem } from './types';
|
||||
import { VIDEO_TYPES } from './enums';
|
||||
|
||||
const TOKEN = '_';
|
||||
|
||||
const FILTER_VIDEO_STATS = [
|
||||
'outbound-rtp',
|
||||
'inbound-rtp',
|
||||
];
|
||||
|
||||
class VideoService {
|
||||
public isMobile: boolean;
|
||||
|
||||
@ -40,6 +45,8 @@ class VideoService {
|
||||
|
||||
private deviceId: string | null = null;
|
||||
|
||||
private activePeers: Record<string, RTCPeerConnection>;
|
||||
|
||||
private readonly clientSessionUUID: string;
|
||||
|
||||
constructor() {
|
||||
@ -60,6 +67,7 @@ class VideoService {
|
||||
}
|
||||
|
||||
this.webRtcPeersRef = {};
|
||||
this.activePeers = {};
|
||||
}
|
||||
|
||||
static fetchNumberOfDevices(devices: MediaDeviceInfo[]) {
|
||||
@ -474,6 +482,39 @@ class VideoService {
|
||||
getPrefix() {
|
||||
return `${Auth.userID}${TOKEN}${this.clientSessionUUID}`;
|
||||
}
|
||||
|
||||
updateActivePeers(streams: StreamItem[]) {
|
||||
const activePeers: Record<string, RTCPeerConnection> = {};
|
||||
|
||||
streams.forEach((vs) => {
|
||||
if (this.webRtcPeersRef[vs.stream]) {
|
||||
activePeers[vs.stream] = this.webRtcPeersRef[vs.stream].peerConnection;
|
||||
}
|
||||
});
|
||||
|
||||
this.activePeers = activePeers;
|
||||
}
|
||||
|
||||
async getStats() {
|
||||
const stats: Record<string, unknown> = {};
|
||||
|
||||
await Promise.all(
|
||||
Object.keys(this.activePeers).map(async (peerId) => {
|
||||
const peerStats = await this.activePeers[peerId].getStats();
|
||||
|
||||
const videoStats: Record<string, unknown> = {};
|
||||
|
||||
peerStats.forEach((stat) => {
|
||||
if (FILTER_VIDEO_STATS.includes(stat.type)) {
|
||||
videoStats[stat.type] = stat;
|
||||
}
|
||||
});
|
||||
stats[peerId] = videoStats;
|
||||
}),
|
||||
);
|
||||
|
||||
return stats;
|
||||
}
|
||||
}
|
||||
|
||||
const videoService = new VideoService();
|
||||
@ -507,7 +548,7 @@ export default {
|
||||
updatePeerDictionaryReference: (
|
||||
newRef: Record<string, WebRtcPeer>,
|
||||
) => videoService.updatePeerDictionaryReference(newRef),
|
||||
webRtcPeersRef: () => videoService.webRtcPeersRef,
|
||||
getWebRtcPeersRef: () => videoService.webRtcPeersRef,
|
||||
isMobile: videoService.isMobile,
|
||||
notify: (message: string) => notify(message, 'error', 'video'),
|
||||
applyCameraProfile: VideoService.applyCameraProfile,
|
||||
@ -516,4 +557,6 @@ export default {
|
||||
getRoleViewer: VideoService.getRoleViewer,
|
||||
getPrefix: videoService.getPrefix.bind(videoService),
|
||||
isPinEnabled: VideoService.isPinEnabled,
|
||||
updateActivePeers: (streams: StreamItem[]) => videoService.updateActivePeers(streams),
|
||||
getStats: () => videoService.getStats(),
|
||||
};
|
||||
|
@ -52,14 +52,6 @@ const setConnectingStream = (stream: ConnectingStream | null) => {
|
||||
|
||||
const getConnectingStream = () => connectingStream();
|
||||
|
||||
const streams = makeVar<Stream[]>([]);
|
||||
|
||||
const setStreams = (vs: Stream[]) => {
|
||||
streams(vs);
|
||||
};
|
||||
|
||||
const getStreams = () => streams();
|
||||
|
||||
export {
|
||||
useVideoState,
|
||||
setVideoState,
|
||||
@ -67,9 +59,6 @@ export {
|
||||
useConnectingStream,
|
||||
getConnectingStream,
|
||||
setConnectingStream,
|
||||
setStreams,
|
||||
getStreams,
|
||||
streams,
|
||||
};
|
||||
|
||||
export default {
|
||||
@ -79,7 +68,4 @@ export default {
|
||||
useConnectingStream,
|
||||
getConnectingStream,
|
||||
setConnectingStream,
|
||||
setStreams,
|
||||
getStreams,
|
||||
streams,
|
||||
};
|
||||
|
@ -6,13 +6,13 @@ import { VIDEO_TYPES } from './enums';
|
||||
|
||||
const DEFAULT_SORTING_MODE = 'LOCAL_ALPHABETICAL';
|
||||
|
||||
// connecting last -> pin first
|
||||
// pin first, ignore connecting streams
|
||||
export const sortPin = (s1: StreamItem, s2: StreamItem) => {
|
||||
if (s1.type === VIDEO_TYPES.CONNECTING) {
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
if (s2.type === VIDEO_TYPES.CONNECTING) {
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
if (s1.user.pinned) {
|
||||
return -1;
|
||||
@ -24,13 +24,13 @@ export const sortPin = (s1: StreamItem, s2: StreamItem) => {
|
||||
|
||||
export const mandatorySorting = (s1: StreamItem, s2: StreamItem) => sortPin(s1, s2);
|
||||
|
||||
// connecting last -> lastFloorTime (descending)
|
||||
// lastFloorTime (descending), ignore connecting streams
|
||||
export const sortVoiceActivity = (s1: StreamItem, s2: StreamItem) => {
|
||||
if (s1.type === VIDEO_TYPES.CONNECTING) {
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
if (s2.type === VIDEO_TYPES.CONNECTING) {
|
||||
return -1;
|
||||
return 0;
|
||||
}
|
||||
if (s2.lastFloorTime < s1.lastFloorTime) {
|
||||
return -1;
|
||||
|
@ -74,3 +74,4 @@ export type Stream = {
|
||||
export type StreamItem = Stream | ConnectingStream;
|
||||
export type GridItem = GridUser & { type: typeof VIDEO_TYPES.GRID };
|
||||
export type VideoItem = StreamItem | GridItem;
|
||||
export type StreamSubscriptionData = VideoStreamsResponse['user_camera'][number];
|
||||
|
Loading…
Reference in New Issue
Block a user