From 622ebe240e30d4757e42b55851123257c524f9c6 Mon Sep 17 00:00:00 2001 From: Victor Rios Date: Mon, 14 Apr 2025 15:36:21 -0700 Subject: [PATCH] #4094400: 5x5 changes --- Project/src/MakeCall/CallCard.js | 5 +- .../src/MakeCall/FunctionalStreamRenderer.js | 186 ----------- Project/src/MakeCall/StreamRenderer.js | 291 +++++++++--------- 3 files changed, 147 insertions(+), 335 deletions(-) delete mode 100644 Project/src/MakeCall/FunctionalStreamRenderer.js diff --git a/Project/src/MakeCall/CallCard.js b/Project/src/MakeCall/CallCard.js index df0e757..fb9dcdf 100644 --- a/Project/src/MakeCall/CallCard.js +++ b/Project/src/MakeCall/CallCard.js @@ -1,6 +1,6 @@ import React from "react"; import { MessageBar, MessageBarType } from '@fluentui/react' -import { FunctionalStreamRenderer as StreamRenderer } from "./FunctionalStreamRenderer"; +import { StreamRenderer } from "./StreamRenderer"; import AddParticipantPopover from "./AddParticipantPopover"; import RemoteParticipantCard from "./RemoteParticipantCard"; import { Panel, PanelType } from '@fluentui/react/lib/Panel'; @@ -517,7 +517,6 @@ export default class CallCard extends React.Component { } updateListOfParticipantsToRender(reason) { - const ovcFeature = this.call.feature(Features.OptimalVideoCount); const optimalVideoCount = ovcFeature.optimalVideoCount; console.log(`updateListOfParticipantsToRender because ${reason}, ovc is ${optimalVideoCount}`); @@ -559,7 +558,7 @@ export default class CallCard extends React.Component { }); streamsToAdd = streamsToAdd.slice(0, optimalVideoCount - streamsToKeep.length); console.log(`updateListOfParticipantsToRender identified ${streamsToAdd.length} streams to add`); - streamsToKeep = streamsToKeep.concat(streamsToAdd.filter(e => !!e)); + streamsToKeep = streamsToKeep.concat(streamsToAdd.filter(stream => !!stream.participant)); } console.log(`updateListOfParticipantsToRender final number of streams to render ${streamsToKeep.length}}`); this.setState(prevState => ({ diff --git a/Project/src/MakeCall/FunctionalStreamRenderer.js b/Project/src/MakeCall/FunctionalStreamRenderer.js deleted file mode 100644 index 40b1473..0000000 --- a/Project/src/MakeCall/FunctionalStreamRenderer.js +++ /dev/null @@ -1,186 +0,0 @@ -import React, { useEffect, useState, useRef, useImperativeHandle, forwardRef } from "react"; -import { utils } from '../Utils/Utils'; -import { VideoStreamRenderer } from "@azure/communication-calling"; -import CustomVideoEffects from "./RawVideoAccess/CustomVideoEffects"; -import VideoReceiveStats from './VideoReceiveStats'; - -export const FunctionalStreamRenderer = forwardRef(({ - remoteParticipant, - stream, - isPinningActive, - isPinned, - dominantRemoteParticipant, - dominantSpeakerMode, - call, - streamCount, - showMediaStats -}, ref) => { - const componentId = `${utils.getIdentifierText(remoteParticipant.identifier)}-${stream.mediaStreamType}-${stream.id}`; - const videoContainerId = componentId + '-videoContainer'; - const componentContainer = useRef(null); - const videoContainer = useRef(null); - const renderer = useRef(null); - const view = useRef(null); - const [isLoading, setIsLoading] = useState(false); - const [isSpeaking, setIsSpeaking] = useState(!!remoteParticipant?.isSpeaking); - const [isMuted, setIsMuted] = useState(!!remoteParticipant?.isMuted); - const [displayName, setDisplayName] = useState(remoteParticipant?.displayName?.trim() ?? ''); - const [videoStats, setVideoStats] = useState(); - const [transportStats, setTransportStats] = useState(); - - useEffect(() => { - initializeComponent(); - return () => { - stream.off('isReceivingChanged', isReceivingChanged); - remoteParticipant.off('isSpeakingChanged', isSpeakingChanged); - remoteParticipant.off('isMutedChanged', isMutedChanged); - remoteParticipant.off('displayNameChanged', isDisplayNameChanged); - disposeRenderer(); - } - }, []); - - const getRenderer = () => { - return view.current; - } - - const createRenderer = async () => { - if (!renderer.current) { - renderer.current = new VideoStreamRenderer(stream); - view.current = await renderer.current.createView(); - return view.current; - } else { - throw new Error(`[App][StreamMedia][id=${stream.id}][createRenderer] stream already has a renderer`); - } - } - - const attachRenderer = (v) => { - try { - if (v) { - view.current = v; - } - - if (!view.current.target) { - throw new Error(`[App][StreamMedia][id=${stream.id}][attachRenderer] target is undefined. Must create renderer first`); - } else { - componentContainer.current.style.display = 'block'; - videoContainer.current.appendChild(view.current?.target); - } - } catch (e) { - console.error(e); - } - } - - const disposeRenderer = () => { - if (videoContainer.current && componentContainer.current) { - videoContainer.current.innerHTML = ''; - componentContainer.current.style.display = 'none'; - } - if (renderer.current) { - renderer.current.dispose(); - } else { - console.warn(`[App][StreamMedia][id=${stream.id}][disposeRender] no renderer to dispose`); - } - } - const isReceivingChanged = () => { - try { - if (stream?.isAvailable) { - setIsLoading(!stream.isReceiving); - } else { - setIsLoading(false); - } - - } catch (e) { - console.error(e); - } - }; - - const isMutedChanged = () => { - setIsMuted(remoteParticipant && remoteParticipant?.isMuted); - }; - - const isSpeakingChanged = () => { - setIsSpeaking(remoteParticipant && remoteParticipant.isSpeaking); - } - - const isDisplayNameChanged = () => { - setDisplayName(remoteParticipant.displayName.trim()); - } - /** - * Start stream after DOM has rendered - */ - const initializeComponent = async () => { - stream.on('isReceivingChanged', isReceivingChanged); - remoteParticipant.on('isMutedChanged', isMutedChanged); - remoteParticipant.on('isSpeakingChanged', isSpeakingChanged); - if (dominantSpeakerMode && dominantRemoteParticipant !== remoteParticipant) { - return; - } - - try { - if (stream.isAvailable && !renderer.current) { - await createRenderer(); - attachRenderer(); - } - } catch (e) { - console.error(e); - } - } - - useImperativeHandle(ref, () => ({ - updateReceiveStats(videoStatsReceived, transportStatsReceived) { - if (videoStatsReceived) { - if (videoStatsReceived !== videoStats && stream.isAvailable) { - setVideoStats(videoStatsReceived); - setTransportStats(transportStatsReceived); - } - } - }, - getRenderer, - createRenderer, - attachRenderer, - disposeRenderer - })); - - if (stream.isAvailable) { - return ( -
-
-

- {displayName ? displayName : remoteParticipant.displayName ? remoteParticipant.displayName : utils.getIdentifierText(remoteParticipant.identifier)} -

- - { - isLoading &&
- } -
- { - videoStats && showMediaStats && -

- -

- } -
- ); - } - return <>; -}); diff --git a/Project/src/MakeCall/StreamRenderer.js b/Project/src/MakeCall/StreamRenderer.js index 06b3a7c..1d87f36 100644 --- a/Project/src/MakeCall/StreamRenderer.js +++ b/Project/src/MakeCall/StreamRenderer.js @@ -1,187 +1,186 @@ -import React from "react"; +import React, { useEffect, useState, useRef, useImperativeHandle, forwardRef } from "react"; import { utils } from '../Utils/Utils'; import { VideoStreamRenderer } from "@azure/communication-calling"; import CustomVideoEffects from "./RawVideoAccess/CustomVideoEffects"; import VideoReceiveStats from './VideoReceiveStats'; -export default class StreamRenderer extends React.Component { - constructor(props) { - super(props); - this.stream = props.stream; - this.remoteParticipant = props.remoteParticipant; - this.componentId = `${utils.getIdentifierText(this.remoteParticipant.identifier)}-${this.stream.mediaStreamType}-${this.stream.id}`; - this.videoContainerId = this.componentId + '-videoContainer'; - this.videoContainer = undefined; - this.renderer = undefined; - this.view = undefined; - this.dominantSpeakerMode = props.dominantSpeakerMode; - this.dominantRemoteParticipant = props.dominantRemoteParticipant; - this.loadingSpinner = document.createElement('div'); - this.loadingSpinner.className = 'remote-video-loading-spinner'; - this.state = { - isSpeaking: false, - displayName: this.remoteParticipant.displayName?.trim(), - videoStats: undefined, - transportStats: undefined - }; - this.call = props.call; - } - - componentDidUpdate(prevProps, prevState, snapshot) { - if (this.dominantSpeakerMode !== prevProps.dominantSpeakerMode) { - this.dominantSpeakerMode = prevProps.dominantSpeakerMode +export const StreamRenderer = forwardRef(({ + remoteParticipant, + stream, + isPinningActive, + isPinned, + dominantRemoteParticipant, + dominantSpeakerMode, + call, + streamCount, + showMediaStats +}, ref) => { + const componentId = `${utils.getIdentifierText(remoteParticipant.identifier)}-${stream.mediaStreamType}-${stream.id}`; + const videoContainerId = componentId + '-videoContainer'; + const componentContainer = useRef(null); + const videoContainer = useRef(null); + const renderer = useRef(null); + const view = useRef(null); + const [isLoading, setIsLoading] = useState(false); + const [isSpeaking, setIsSpeaking] = useState(!!remoteParticipant?.isSpeaking); + const [isMuted, setIsMuted] = useState(!!remoteParticipant?.isMuted); + const [displayName, setDisplayName] = useState(remoteParticipant?.displayName?.trim() ?? ''); + const [videoStats, setVideoStats] = useState(); + const [transportStats, setTransportStats] = useState(); + + useEffect(() => { + initializeComponent(); + return () => { + stream.off('isReceivingChanged', isReceivingChanged); + remoteParticipant.off('isSpeakingChanged', isSpeakingChanged); + remoteParticipant.off('isMutedChanged', isMutedChanged); + remoteParticipant.off('displayNameChanged', isDisplayNameChanged); + disposeRenderer(); } + }, []); - if (this.dominantRemoteParticipant !== prevProps.dominantRemoteParticipant) { - this.dominantRemoteParticipant = prevProps.dominantRemoteParticipant - } + const getRenderer = () => { + return view.current; } - /** - * Start stream after DOM has rendered - */ - async componentDidMount() { - document.getElementById(this.componentId).hidden = true; - this.videoContainer = document.getElementById(this.videoContainerId); - - this.remoteParticipant.on('isSpeakingChanged', () => { - this.setState({ isSpeaking: this.remoteParticipant.isSpeaking }); - }); - - this.remoteParticipant.on('isMutedChanged', () => { - if (this.remoteParticipant.isMuted) { - this.setState({ isSpeaking: false }); - } - }); - this.remoteParticipant.on('displayNameChanged', () => { - this.setState({ displayName: this.remoteParticipant.displayName?.trim() }); - }) - - console.log(`[App][StreamMedia][id=${this.stream.id}] handle new stream`); - console.log(`[App][StreamMedia][id=${this.stream.id}] stream info - ` + - `streamId=${this.stream.id}, streamType=${this.stream.mediaStreamType}, ` + - `isAvailable=${this.stream.isAvailable}, isReceiving=${this.stream.isReceiving}`); + const createRenderer = async () => { + if (!renderer.current) { + renderer.current = new VideoStreamRenderer(stream); + view.current = await renderer.current.createView(); + return view.current; + } else { + throw new Error(`[App][StreamMedia][id=${stream.id}][createRenderer] stream already has a renderer`); + } + } - /** - * This feature is alpha - * @beta - */ - console.log(`[App][StreamMedia][id=${this.stream.id}] subscribing to isRenderingChanged`); - this.stream.on('isReceivingChanged', () => { - try { - if (this.stream.isAvailable) { - const isReceiving = this.stream.isReceiving; - const isLoadingSpinnerActive = this.videoContainer.contains(this.loadingSpinner); - if (!isReceiving && !isLoadingSpinnerActive) { - this.videoContainer.appendChild(this.loadingSpinner); - } else if (isReceiving && isLoadingSpinnerActive) { - this.videoContainer.removeChild(this.loadingSpinner); - } - } - } catch (e) { - console.error(e); + const attachRenderer = (v) => { + try { + if (v) { + view.current = v; } - }); - - console.log(`[App][StreamMedia][id=${this.stream.id}] subscribing to isAvailableChanged`); - this.stream.on('isAvailableChanged', async () => { - try { - if(this.dominantSpeakerMode && this.dominantRemoteParticipant !== this.remoteParticipant) { - return; - } - console.log(`[App][StreamMedia][id=${this.stream.id}][isAvailableChanged] triggered`); - if (this.stream.isAvailable && !this.renderer) { - console.log(`[App][StreamMedia][id=${this.stream.id}][isAvailableChanged] isAvailable=${this.stream.isAvailable}`); - await this.createRenderer(); - this.attachRenderer(); - } else { - console.log(`[App][StreamMedia][id=${this.stream.id}][isAvailableChanged] isAvailable=${this.stream.isAvailable}`); - this.disposeRenderer(); - } - } catch (e) { - console.error(e); + if (!view.current.target) { + throw new Error(`[App][StreamMedia][id=${stream.id}][attachRenderer] target is undefined. Must create renderer first`); + } else { + componentContainer.current.style.display = 'block'; + videoContainer.current.appendChild(view.current?.target); } - }); - - if(this.dominantSpeakerMode && this.dominantRemoteParticipant !== this.remoteParticipant) { - return; + } catch (e) { + console.error(e); } + } + const disposeRenderer = () => { + if (videoContainer.current && componentContainer.current) { + videoContainer.current.innerHTML = ''; + componentContainer.current.style.display = 'none'; + } + if (renderer.current) { + renderer.current.dispose(); + } else { + console.warn(`[App][StreamMedia][id=${stream.id}][disposeRender] no renderer to dispose`); + } + } + const isReceivingChanged = () => { try { - console.log(`[App][StreamMedia][id=${this.stream.id}] checking initial value - isAvailable=${this.stream.isAvailable}`); - if (this.stream.isAvailable && !this.renderer) { - await this.createRenderer(); - this.attachRenderer(); + if (stream?.isAvailable) { + setIsLoading(!stream.isReceiving); + } else { + setIsLoading(false); } + } catch (e) { console.error(e); } - } + }; - getRenderer() { - return this.renderer; - } + const isMutedChanged = () => { + setIsMuted(remoteParticipant && remoteParticipant?.isMuted); + }; - updateReceiveStats(videoStats, transportStats) { - if (this.state.videoStats !== videoStats) { - this.setState({ videoStats, transportStats }); - } + const isSpeakingChanged = () => { + setIsSpeaking(remoteParticipant && remoteParticipant.isSpeaking); } - async createRenderer() { - console.info(`[App][StreamMedia][id=${this.stream.id}][renderStream] attempt to render stream type=${this.stream.mediaStreamType}, id=${this.stream.id}`); - if (!this.renderer) { - this.renderer = new VideoStreamRenderer(this.stream); - this.view = await this.renderer.createView(); - console.info(`[App][StreamMedia][id=${this.stream.id}][renderStream] createView resolved, appending view`); - } else { - throw new Error(`[App][StreamMedia][id=${this.stream.id}][createRenderer] stream already has a renderer`); - } + const isDisplayNameChanged = () => { + setDisplayName(remoteParticipant.displayName.trim()); } + /** + * Start stream after DOM has rendered + */ + const initializeComponent = async () => { + stream.on('isReceivingChanged', isReceivingChanged); + remoteParticipant.on('isMutedChanged', isMutedChanged); + remoteParticipant.on('isSpeakingChanged', isSpeakingChanged); + if (dominantSpeakerMode && dominantRemoteParticipant !== remoteParticipant) { + return; + } - async attachRenderer() { - console.info(`[App][StreamMedia][id=${this.stream.id}][attachRenderer] attempt to attach view=${this.view.target}, id=${this.stream.id} to DOM, under container id=${this.videoContainerId}`); try { - if(!this.view.target) { - throw new Error(`[App][StreamMedia][id=${this.stream.id}][attachRenderer] target is undefined. Must create renderer first`); + if (stream.isAvailable && !renderer.current) { + await createRenderer(); + attachRenderer(); } - document.getElementById(this.componentId).hidden = false; - document.getElementById(this.videoContainerId).appendChild(this.view.target); } catch (e) { console.error(e); } } - disposeRenderer() { - if (this.renderer) { - this.renderer.dispose(); - this.renderer = undefined; - document.getElementById(this.componentId).hidden = true; - } else { - console.warn(`[App][StreamMedia][id=${this.stream.id}][disposeRender] no renderer to dispose`); - } - } - - render() { + useImperativeHandle(ref, () => ({ + updateReceiveStats(videoStatsReceived, transportStatsReceived) { + if (videoStatsReceived) { + if (videoStatsReceived !== videoStats && stream.isAvailable) { + setVideoStats(videoStatsReceived); + setTransportStats(transportStatsReceived); + } + } + }, + getRenderer, + createRenderer, + attachRenderer, + disposeRenderer + })); + + if (stream.isAvailable) { return ( -
-
-

- {this.state.displayName ? this.state.displayName : utils.getIdentifierText(this.remoteParticipant.identifier)} -

+
+
+

+ {displayName ? displayName : remoteParticipant.displayName ? remoteParticipant.displayName : utils.getIdentifierText(remoteParticipant.identifier)} +

+ + { + isLoading &&
+ } +
{ - this.state.videoStats && + videoStats && showMediaStats &&

- +

} -
-
); } -} - - - + return <>; +});