fix(webcam): turn useGetStats hook into a service method to avoid function re-instantiation

This commit is contained in:
João Victor 2024-07-30 10:20:18 -03:00
parent 62662ee415
commit 8b4a2f39e0
6 changed files with 56 additions and 71 deletions

View File

@ -3,7 +3,6 @@ import { useMutation } from '@apollo/client';
import { UPDATE_CONNECTION_ALIVE_AT } from './mutations'; import { UPDATE_CONNECTION_ALIVE_AT } from './mutations';
import { getStatus, handleAudioStatsEvent, startMonitoringNetwork } from '/imports/ui/components/connection-status/service'; import { getStatus, handleAudioStatsEvent, startMonitoringNetwork } from '/imports/ui/components/connection-status/service';
import connectionStatus from '../../core/graphql/singletons/connectionStatus'; import connectionStatus from '../../core/graphql/singletons/connectionStatus';
import { useGetStats } from '../video-provider/hooks';
import getBaseUrl from '/imports/ui/core/utils/getBaseUrl'; import getBaseUrl from '/imports/ui/core/utils/getBaseUrl';
@ -14,8 +13,6 @@ const ConnectionStatus = () => {
const [updateConnectionAliveAtM] = useMutation(UPDATE_CONNECTION_ALIVE_AT); const [updateConnectionAliveAtM] = useMutation(UPDATE_CONNECTION_ALIVE_AT);
const getVideoStreamsStats = useGetStats();
const handleUpdateConnectionAliveAt = () => { const handleUpdateConnectionAliveAt = () => {
const startTime = performance.now(); const startTime = performance.now();
fetch( fetch(
@ -66,7 +63,7 @@ const ConnectionStatus = () => {
if (STATS_ENABLED) { if (STATS_ENABLED) {
window.addEventListener('audiostats', handleAudioStatsEvent); window.addEventListener('audiostats', handleAudioStatsEvent);
startMonitoringNetwork(getVideoStreamsStats); startMonitoringNetwork();
} }
return () => { return () => {

View File

@ -3,7 +3,6 @@ import { CONNECTION_STATUS_REPORT_SUBSCRIPTION } from '../queries';
import Service from '../service'; import Service from '../service';
import Component from './component'; import Component from './component';
import useCurrentUser from '/imports/ui/core/hooks/useCurrentUser'; import useCurrentUser from '/imports/ui/core/hooks/useCurrentUser';
import { useGetStats } from '../../video-provider/hooks';
import useDeduplicatedSubscription from '/imports/ui/core/hooks/useDeduplicatedSubscription'; import useDeduplicatedSubscription from '/imports/ui/core/hooks/useDeduplicatedSubscription';
import { useReactiveVar } from '@apollo/client'; import { useReactiveVar } from '@apollo/client';
import connectionStatus from '/imports/ui/core/graphql/singletons/connectionStatus'; import connectionStatus from '/imports/ui/core/graphql/singletons/connectionStatus';
@ -16,14 +15,11 @@ const ConnectionStatusContainer = (props) => {
const newtworkData = useReactiveVar(connectionStatus.getNetworkDataVar()); const newtworkData = useReactiveVar(connectionStatus.getNetworkDataVar());
const getVideoStreamsStats = useGetStats();
return ( return (
<Component <Component
{...props} {...props}
connectionData={connectionData} connectionData={connectionData}
amIModerator={amIModerator} amIModerator={amIModerator}
getVideoStreamsStats={getVideoStreamsStats}
networkData={newtworkData} networkData={newtworkData}
/> />
); );

View File

@ -5,6 +5,7 @@ import Session from '/imports/ui/services/storage/in-memory';
import { notify } from '/imports/ui/services/notification'; import { notify } from '/imports/ui/services/notification';
import AudioService from '/imports/ui/components/audio/service'; import AudioService from '/imports/ui/components/audio/service';
import ScreenshareService from '/imports/ui/components/screenshare/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'; import connectionStatus from '../../core/graphql/singletons/connectionStatus';
const intlMessages = defineMessages({ const intlMessages = defineMessages({
@ -210,8 +211,8 @@ const getAudioData = async () => {
* @returns An Object containing video data for all video peers and screenshare * @returns An Object containing video data for all video peers and screenshare
* peer * peer
*/ */
const getVideoData = async (getVideoStreamsStats) => { const getVideoData = async () => {
const camerasData = await getVideoStreamsStats() || {}; const camerasData = await VideoService.getStats() || {};
const screenshareData = await ScreenshareService.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. * For audio, this will get information about the mic/listen-only stream.
* @returns An Object containing all this data. * @returns An Object containing all this data.
*/ */
const getNetworkData = async (getVideoStreamsStats) => { const getNetworkData = async () => {
const audio = await getAudioData(); const audio = await getAudioData();
const video = await getVideoData(getVideoStreamsStats); const video = await getVideoData();
const user = { const user = {
time: new Date(), time: new Date(),
@ -401,11 +402,11 @@ export function getStatus(levels, value) {
* Start monitoring the network data. * Start monitoring the network data.
* @return {Promise} A Promise that resolves when process started. * @return {Promise} A Promise that resolves when process started.
*/ */
export async function startMonitoringNetwork(getVideoStreamsStats) { export async function startMonitoringNetwork() {
let previousData = await getNetworkData(getVideoStreamsStats); let previousData = await getNetworkData();
setInterval(async () => { setInterval(async () => {
const data = await getNetworkData(getVideoStreamsStats); const data = await getNetworkData();
const { const {
outbound: audioCurrentUploadRate, outbound: audioCurrentUploadRate,

View File

@ -20,7 +20,6 @@ import VideoService from './service';
import { Output } from '/imports/ui/components/layout/layoutTypes'; import { Output } from '/imports/ui/components/layout/layoutTypes';
import { VideoItem } from './types'; import { VideoItem } from './types';
import { debounce } from '/imports/utils/debounce'; 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 useSettings from '/imports/ui/services/settings/hooks/useSettings';
import { SETTINGS } from '/imports/ui/services/settings/enums'; import { SETTINGS } from '/imports/ui/services/settings/enums';
import { useStorageKey } from '/imports/ui/services/storage/hooks'; import { useStorageKey } from '/imports/ui/services/storage/hooks';
@ -64,7 +63,7 @@ const VideoProviderContainer: React.FC<VideoProviderContainerProps> = (props) =>
VideoService.applyCameraProfile, VideoService.applyCameraProfile,
CAMERA_QUALITY_THR_DEBOUNCE, CAMERA_QUALITY_THR_DEBOUNCE,
{ leading: false, trailing: true }, { leading: false, trailing: true },
); ) as typeof VideoService.applyCameraProfile;
const { data: currentMeeting } = useMeeting((m) => ({ const { data: currentMeeting } = useMeeting((m) => ({
usersPolicies: m.usersPolicies, usersPolicies: m.usersPolicies,
@ -89,6 +88,7 @@ const VideoProviderContainer: React.FC<VideoProviderContainerProps> = (props) =>
totalNumberOfStreams, totalNumberOfStreams,
totalNumberOfOtherStreams, totalNumberOfOtherStreams,
} = useVideoStreams(); } = useVideoStreams();
VideoService.updateActivePeers(streams);
let usersVideo: VideoItem[] = streams; let usersVideo: VideoItem[] = streams;
@ -167,7 +167,7 @@ const VideoProviderContainer: React.FC<VideoProviderContainerProps> = (props) =>
exitVideo={exitVideo} exitVideo={exitVideo}
lockUser={lockUser} lockUser={lockUser}
stopVideo={stopVideo} stopVideo={stopVideo}
applyCameraProfile={applyCameraProfile as (peer: WebRtcPeer, profileId: string) => void} applyCameraProfile={applyCameraProfile}
myRole={myRole} myRole={myRole}
/> />
); );

View File

@ -52,11 +52,6 @@ import ConnectionStatus from '/imports/ui/core/graphql/singletons/connectionStat
import { VIDEO_TYPES } from '/imports/ui/components/video-provider/enums'; import { VIDEO_TYPES } from '/imports/ui/components/video-provider/enums';
import createUseSubscription from '/imports/ui/core/hooks/createUseSubscription'; import createUseSubscription from '/imports/ui/core/hooks/createUseSubscription';
const FILTER_VIDEO_STATS = [
'outbound-rtp',
'inbound-rtp',
];
const useVideoStreamsSubscription = createUseSubscription( const useVideoStreamsSubscription = createUseSubscription(
VIDEO_STREAMS_SUBSCRIPTION, VIDEO_STREAMS_SUBSCRIPTION,
{}, {},
@ -532,53 +527,6 @@ export const useStopVideo = () => {
}, [cameraBroadcastStop]); }, [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.getWebRtcPeersRef()[stream.stream]) {
activePeers[stream.stream] = videoService.getWebRtcPeersRef()[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 = () => { export const useShouldRenderPaginationToggle = () => {
const myPageSize = useMyPageSize(); const myPageSize = useMyPageSize();
const { const {

View File

@ -18,11 +18,16 @@ import WebRtcPeer from '/imports/ui/services/webrtc-base/peer';
import { Constraints2 } from '/imports/ui/Types/meetingClientSettings'; import { Constraints2 } from '/imports/ui/Types/meetingClientSettings';
import MediaStreamUtils from '/imports/utils/media-stream-utils'; import MediaStreamUtils from '/imports/utils/media-stream-utils';
import Session from '/imports/ui/services/storage/in-memory'; 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'; import { VIDEO_TYPES } from './enums';
const TOKEN = '_'; const TOKEN = '_';
const FILTER_VIDEO_STATS = [
'outbound-rtp',
'inbound-rtp',
];
class VideoService { class VideoService {
public isMobile: boolean; public isMobile: boolean;
@ -40,6 +45,8 @@ class VideoService {
private deviceId: string | null = null; private deviceId: string | null = null;
private activePeers: Record<string, RTCPeerConnection>;
private readonly clientSessionUUID: string; private readonly clientSessionUUID: string;
constructor() { constructor() {
@ -60,6 +67,7 @@ class VideoService {
} }
this.webRtcPeersRef = {}; this.webRtcPeersRef = {};
this.activePeers = {};
} }
static fetchNumberOfDevices(devices: MediaDeviceInfo[]) { static fetchNumberOfDevices(devices: MediaDeviceInfo[]) {
@ -474,6 +482,39 @@ class VideoService {
getPrefix() { getPrefix() {
return `${Auth.userID}${TOKEN}${this.clientSessionUUID}`; 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(); const videoService = new VideoService();
@ -516,4 +557,6 @@ export default {
getRoleViewer: VideoService.getRoleViewer, getRoleViewer: VideoService.getRoleViewer,
getPrefix: videoService.getPrefix.bind(videoService), getPrefix: videoService.getPrefix.bind(videoService),
isPinEnabled: VideoService.isPinEnabled, isPinEnabled: VideoService.isPinEnabled,
updateActivePeers: (streams: StreamItem[]) => videoService.updateActivePeers(streams),
getStats: () => videoService.getStats(),
}; };