UNPKG

@fishjam-cloud/react-client

Version:
152 lines (151 loc) 6.22 kB
import { TrackTypeError, Variant } from "@fishjam-cloud/ts-client"; import { useEffect, useRef } from "react"; import { getConfigAndBandwidthFromProps, getRemoteOrLocalTrack } from "../../utils/track"; import { useCurrentCallback } from "./useCurrentCallback"; export const useTrackManager = ({ deviceManager, tsClient, peerStatus, bandwidthLimits, streamConfig, type, logger, }) => { const currentTrackIdRef = useRef(null); const connectionPromiseRef = useRef(null); const { startDevice, stopDevice, enableDevice, disableDevice, deviceTrack, applyMiddleware, currentMiddleware, selectDevice: _selectDevice, } = deviceManager; // Read live deviceTrack from the `joined` listener without re-subscribing // every time it changes. const getDeviceTrack = useCurrentCallback(() => deviceTrack); const getCurrentTrackId = async () => { if (connectionPromiseRef.current) { await connectionPromiseRef.current; } const refTrackId = currentTrackIdRef.current; if (!refTrackId) return null; const currentTrack = getRemoteOrLocalTrack(tsClient, refTrackId); return currentTrack?.trackId ?? null; }; const selectDevice = useCurrentCallback(async (deviceId) => { const result = await _selectDevice(deviceId); if (!result) return; const [newTrack, error] = result; if (error) return error; const currentTrackId = await getCurrentTrackId(); if (!currentTrackId) return; await tsClient.replaceTrack(currentTrackId, newTrack); }); const setTrackMiddleware = useCurrentCallback(async (middleware) => { const processedTrack = await applyMiddleware(middleware); const currentTrackId = await getCurrentTrackId(); if (!currentTrackId) return; await tsClient.replaceTrack(currentTrackId, processedTrack); }); const startStreaming = useCurrentCallback(async (track, props = { sentQualities: [Variant.VARIANT_LOW, Variant.VARIANT_MEDIUM, Variant.VARIANT_HIGH] }) => { // temporarily setting the local trackId until we have the remoteTrackId currentTrackIdRef.current = track.id; const trackMetadata = { type, paused: false }; const displayName = tsClient.getLocalPeer()?.metadata?.peer?.displayName; if (typeof displayName === "string") { trackMetadata.displayName = displayName; } const [maxBandwidth, simulcastConfig] = getConfigAndBandwidthFromProps(props.sentQualities, bandwidthLimits); try { const addTrackJob = tsClient.addTrack(track, trackMetadata, simulcastConfig, maxBandwidth); connectionPromiseRef.current = addTrackJob; const remoteTrackId = await addTrackJob; currentTrackIdRef.current = remoteTrackId; } catch (err) { if (err instanceof TrackTypeError) { logger.warn(err.message); currentTrackIdRef.current = null; } throw err; } }); const pauseStreaming = useCurrentCallback(async (trackId) => { if (peerStatus !== "connected") return; await tsClient.replaceTrack(trackId, null); return tsClient.updateTrackMetadata(trackId, { type, paused: true }); }); const resumeStreaming = useCurrentCallback(async (trackId, track) => { if (peerStatus !== "connected") return; await tsClient.replaceTrack(trackId, track); return tsClient.updateTrackMetadata(trackId, { type, paused: false }); }); /** * @see {@link TrackManager#toggleMute} for more details. */ const toggleMute = useCurrentCallback(async () => { const currentTrackId = await getCurrentTrackId(); const isTrackCurrentlyEnabled = Boolean(deviceTrack?.enabled); if (!currentTrackId) { logger.warn("Toggling mute is only possible while connected to a room."); return; } if (isTrackCurrentlyEnabled) { disableDevice(); await pauseStreaming(currentTrackId); } else if (deviceTrack) { enableDevice(); await resumeStreaming(currentTrackId, deviceTrack); } }); /** * @see {@link TrackManager#toggleDevice} for more details. */ const toggleDevice = useCurrentCallback(async () => { const currentTrackId = await getCurrentTrackId(); if (deviceTrack) { stopDevice(); if (currentTrackId) { await pauseStreaming(currentTrackId); } } else { const [newTrack, error] = await startDevice(); if (error) return error; if (currentTrackId) { await resumeStreaming(currentTrackId, newTrack); } else if (peerStatus === "connected") { await startStreaming(newTrack, streamConfig); } } }); useEffect(() => { const onJoinedRoom = () => { const currentDeviceTrack = getDeviceTrack(); if (!currentDeviceTrack) return; // The handler is sync; observe rejections so non-TrackTypeError failures // from addTrack don't surface as unhandledrejection. void startStreaming(currentDeviceTrack, streamConfig).catch((err) => { if (err instanceof TrackTypeError) return; logger.error(err); }); }; const onLeftRoom = () => { currentTrackIdRef.current = null; }; tsClient.on("joined", onJoinedRoom); tsClient.on("disconnected", onLeftRoom); return () => { tsClient.off("joined", onJoinedRoom); tsClient.off("disconnected", onLeftRoom); }; }, [startStreaming, tsClient, streamConfig, getDeviceTrack, logger]); return { deviceTrack, currentMiddleware, setTrackMiddleware, selectDevice, toggleMute, toggleDevice, stopDevice, startDevice, }; };