diff --git a/packages/react-client/src/hooks/internal/useScreenshareManager.ts b/packages/react-client/src/hooks/internal/useScreenshareManager.ts index 8c30f54b..b67b35ac 100644 --- a/packages/react-client/src/hooks/internal/useScreenshareManager.ts +++ b/packages/react-client/src/hooks/internal/useScreenshareManager.ts @@ -58,92 +58,107 @@ export const useScreenShareManager = ({ const cleanMiddlewareFnRef = useRef<(() => void) | null>(null); const stream = state.stream ?? null; + const tracksMiddleware = state.tracksMiddleware ?? null; const [mediaVideoTrack, mediaAudioTrack] = stream ? getTracksFromStream(stream) : [null, null]; - const getDisplayName = () => { + const getDisplayName = useCallback(() => { const name = fishjamClient.getLocalPeer()?.metadata?.peer?.displayName; if (typeof name === "string") return name; - }; - - const addTrackToFishjamClient = async (track: MediaStreamTrack, trackMetadata: TrackMetadata) => { - try { - return fishjamClient.addTrack(track, trackMetadata); - } catch (err) { - if (err instanceof TrackTypeError) { - logger.warn(err.message); - return undefined; + }, [fishjamClient]); + + const addTrackToFishjamClient = useCallback( + async (track: MediaStreamTrack, trackMetadata: TrackMetadata) => { + try { + return fishjamClient.addTrack(track, trackMetadata); + } catch (err) { + if (err instanceof TrackTypeError) { + logger.warn(err.message); + return undefined; + } + throw err; + } + }, + [fishjamClient, logger], + ); + + const startStreaming: UseScreenshareResult["startStreaming"] = useCallback( + async (props) => { + const displayStream = await navigator.mediaDevices.getDisplayMedia({ + video: props?.videoConstraints ?? true, + audio: props?.audioConstraints ?? true, + }); + + const displayName = getDisplayName(); + + let [video, audio] = getTracksFromStream(displayStream); + + if (tracksMiddleware) { + const { videoTrack, audioTrack, onClear } = await tracksMiddleware(video, audio); + video = videoTrack; + audio = audioTrack; + cleanMiddlewareFnRef.current = onClear; } - throw err; - } - }; - - const startStreaming: UseScreenshareResult["startStreaming"] = async (props) => { - const displayStream = await navigator.mediaDevices.getDisplayMedia({ - video: props?.videoConstraints ?? true, - audio: props?.audioConstraints ?? true, - }); - - const displayName = getDisplayName(); - - let [video, audio] = getTracksFromStream(displayStream); - - if (state.tracksMiddleware) { - const { videoTrack, audioTrack, onClear } = await state.tracksMiddleware(video, audio); - video = videoTrack; - audio = audioTrack; - cleanMiddlewareFnRef.current = onClear; - } - // TODO: FCE-2659 Refactor this hook so this check is not required. - // This check is needed to support screensharing in livestreams which don't use the FishjamClient. - // trackIds are simply ignored because they are not used in this use case. - if (fishjamClient.status === "initialized") { - const addTrackPromises = [ - addTrackToFishjamClient(video, { displayName, type: "screenShareVideo", paused: false }), - ]; - if (audio) - addTrackPromises.push(addTrackToFishjamClient(audio, { displayName, type: "screenShareAudio", paused: false })); - - const [videoId, audioId] = await Promise.all(addTrackPromises); - setState({ stream: displayStream, trackIds: { videoId, audioId } }); - } else { - setState({ stream: displayStream, trackIds: {} }); - } - }; + // TODO: FCE-2659 Refactor this hook so this check is not required. + // This check is needed to support screensharing in livestreams which don't use the FishjamClient. + // trackIds are simply ignored because they are not used in this use case. + if (fishjamClient.status === "initialized") { + const addTrackPromises = [ + addTrackToFishjamClient(video, { displayName, type: "screenShareVideo", paused: false }), + ]; + if (audio) + addTrackPromises.push( + addTrackToFishjamClient(audio, { displayName, type: "screenShareAudio", paused: false }), + ); + + const [videoId, audioId] = await Promise.all(addTrackPromises); + setState({ stream: displayStream, trackIds: { videoId, audioId } }); + } else { + setState({ stream: displayStream, trackIds: {} }); + } + }, + [tracksMiddleware, getDisplayName, addTrackToFishjamClient, fishjamClient], + ); - const replaceTracks = async (newVideoTrack: MediaStreamTrack, newAudioTrack: MediaStreamTrack | null) => { - if (!state?.stream) return; + const replaceTracks = useCallback( + async (newVideoTrack: MediaStreamTrack, newAudioTrack: MediaStreamTrack | null) => { + if (!state?.stream) return; - const addTrackPromises: Promise[] = []; + const addTrackPromises: Promise[] = []; - if (newVideoTrack && state.trackIds.videoId) - addTrackPromises.push(fishjamClient.replaceTrack(state.trackIds.videoId, newVideoTrack)); - if (newAudioTrack && state.trackIds.audioId) - addTrackPromises.push(fishjamClient.replaceTrack(state.trackIds.audioId, newAudioTrack)); + if (newVideoTrack && state.trackIds.videoId) + addTrackPromises.push(fishjamClient.replaceTrack(state.trackIds.videoId, newVideoTrack)); + if (newAudioTrack && state.trackIds.audioId) + addTrackPromises.push(fishjamClient.replaceTrack(state.trackIds.audioId, newAudioTrack)); - await Promise.all(addTrackPromises); - }; + await Promise.all(addTrackPromises); + }, + [state.stream, state.trackIds?.videoId, state.trackIds?.audioId, fishjamClient], + ); const cleanMiddleware = useCallback(() => { cleanMiddlewareFnRef.current?.(); cleanMiddlewareFnRef.current = null; }, []); - const setTracksMiddleware = async (middleware: TracksMiddleware | null): Promise => { - if (!state?.stream) return; + const setTracksMiddleware = useCallback( + async (middleware: TracksMiddleware | null): Promise => { + if (!state?.stream) return; - const [video, audio] = getTracksFromStream(state.stream); + const [video, audio] = getTracksFromStream(state.stream); - cleanMiddleware(); + cleanMiddleware(); - const { videoTrack, audioTrack, onClear } = (await middleware?.(video, audio)) ?? { - videoTrack: video, - audioTrack: audio, - onClear: null, - }; - cleanMiddlewareFnRef.current = onClear; - await replaceTracks(videoTrack, audioTrack); - }; + const { videoTrack, audioTrack, onClear } = (await middleware?.(video, audio)) ?? { + videoTrack: video, + audioTrack: audio, + onClear: null, + }; + cleanMiddlewareFnRef.current = onClear; + await replaceTracks(videoTrack, audioTrack); + }, + [state.stream, cleanMiddleware, replaceTracks], + ); const stopStreaming: UseScreenshareResult["stopStreaming"] = useCallback(async () => { if (!state.stream) { @@ -164,7 +179,7 @@ export const useScreenShareManager = ({ } cleanMiddleware(); - setState(({ tracksMiddleware }) => ({ stream: null, trackIds: null, tracksMiddleware })); + setState((prev) => ({ stream: null, trackIds: null, tracksMiddleware: prev.tracksMiddleware })); }, [ state.stream, state.trackIds?.videoId, @@ -212,7 +227,7 @@ export const useScreenShareManager = ({ videoTrack: mediaVideoTrack, audioTrack: mediaAudioTrack, setTracksMiddleware, - currentTracksMiddleware: state?.tracksMiddleware ?? null, + currentTracksMiddleware: tracksMiddleware, }; }; diff --git a/packages/react-client/src/hooks/useSandbox.ts b/packages/react-client/src/hooks/useSandbox.ts index dbf2b804..ce1b49a2 100644 --- a/packages/react-client/src/hooks/useSandbox.ts +++ b/packages/react-client/src/hooks/useSandbox.ts @@ -1,3 +1,5 @@ +import { useCallback } from "react"; + import { useFishjamId } from "../contexts/fishjamId"; import { resolveFishjamUrl } from "../utils/fishjamUrl"; @@ -23,50 +25,59 @@ export const useSandbox = (props?: UseSandboxProps) => { const sandboxApiUrl = props?.configOverride?.sandboxApiUrl ?? `${fishjamUrl}/room-manager`; - const getSandboxPeerToken = async (roomName: string, peerName: string, roomType: RoomType = "conference") => { - const url = new URL(sandboxApiUrl); - url.searchParams.set("roomName", roomName); - url.searchParams.set("peerName", peerName); - url.searchParams.set("roomType", roomType); - - const res = await fetch(url); - - if (!res.ok) { - const message = `Failed to retrieve peer token for peer '${peerName}' in ${roomType} room '${roomName}'.`; - throw new Error(message); - } - - const data: RoomManagerResponse = await res.json(); - return data.peerToken; - }; + const getSandboxPeerToken = useCallback( + async (roomName: string, peerName: string, roomType: RoomType = "conference") => { + const url = new URL(sandboxApiUrl); + url.searchParams.set("roomName", roomName); + url.searchParams.set("peerName", peerName); + url.searchParams.set("roomType", roomType); - const getSandboxViewerToken = async (roomName: string) => { - const url = new URL(`${sandboxApiUrl}/${roomName}/livestream-viewer-token`); + const res = await fetch(url); - const res = await fetch(url); - if (!res.ok) { - let message = `Failed to retrieve viewer token for '${roomName}' livestream room.`; - if (res.status === 404) { - message = `A livestream room of name '${roomName}' does not exist.`; + if (!res.ok) { + const message = `Failed to retrieve peer token for peer '${peerName}' in ${roomType} room '${roomName}'.`; + throw new Error(message); } - throw new Error(message); - } - const data: { token: string } = await res.json(); - return data.token; - }; - - const getSandboxLivestream = async (roomName: string, isPublic: boolean = false) => { - const url = new URL(`${sandboxApiUrl}/livestream`); - url.searchParams.set("roomName", roomName); - url.searchParams.set("public", isPublic.toString()); - - const res = await fetch(url); - if (!res.ok) throw new Error(`Failed to retrieve streamer token for '${roomName}' livestream room.`); - - const data: { streamerToken: string; room: { id: string; name: string } } = await res.json(); - return data; - }; + const data: RoomManagerResponse = await res.json(); + return data.peerToken; + }, + [sandboxApiUrl], + ); + + const getSandboxViewerToken = useCallback( + async (roomName: string) => { + const url = new URL(`${sandboxApiUrl}/${roomName}/livestream-viewer-token`); + + const res = await fetch(url); + if (!res.ok) { + let message = `Failed to retrieve viewer token for '${roomName}' livestream room.`; + if (res.status === 404) { + message = `A livestream room of name '${roomName}' does not exist.`; + } + throw new Error(message); + } + const data: { token: string } = await res.json(); + + return data.token; + }, + [sandboxApiUrl], + ); + + const getSandboxLivestream = useCallback( + async (roomName: string, isPublic: boolean = false) => { + const url = new URL(`${sandboxApiUrl}/livestream`); + url.searchParams.set("roomName", roomName); + url.searchParams.set("public", isPublic.toString()); + + const res = await fetch(url); + if (!res.ok) throw new Error(`Failed to retrieve streamer token for '${roomName}' livestream room.`); + + const data: { streamerToken: string; room: { id: string; name: string } } = await res.json(); + return data; + }, + [sandboxApiUrl], + ); return { getSandboxPeerToken, getSandboxViewerToken, getSandboxLivestream }; }; diff --git a/packages/react-client/src/hooks/useStatistics.ts b/packages/react-client/src/hooks/useStatistics.ts index 321e86ca..35e74469 100644 --- a/packages/react-client/src/hooks/useStatistics.ts +++ b/packages/react-client/src/hooks/useStatistics.ts @@ -1,4 +1,4 @@ -import { useContext } from "react"; +import { useCallback, useContext } from "react"; import { FishjamClientContext } from "../contexts/fishjamClient"; @@ -6,10 +6,12 @@ export const useStatistics = () => { const client = useContext(FishjamClientContext); if (!client) throw new Error("useStatistics must be used within a FishjamProvider"); + const getStatistics = useCallback(() => client.current.getStatistics(), [client]); + return { /* * Returns a low level RTCStatsReport statistics object about the connection. */ - getStatistics: () => client.current.getStatistics(), + getStatistics, }; };