@fishjam-cloud/react-client
Version:
React client library for Fishjam
86 lines (85 loc) • 3.83 kB
JavaScript
import { TrackTypeError } from "@fishjam-cloud/ts-client";
import { useCallback, useEffect, useMemo, useState } from "react";
export function useCustomSourceManager({ fishjamClient, peerStatus }) {
const [sources, setSources] = useState({});
const pendingSources = useMemo(() => Object.entries(sources).filter(([_, source]) => source.trackIds === undefined), [sources]);
const getDisplayName = useCallback(() => {
const name = fishjamClient.getLocalPeer()?.metadata?.peer?.displayName;
if (typeof name == "string")
return name;
}, [fishjamClient]);
const addTrackToFishjamClient = useCallback(async (track, trackMetadata) => {
try {
return fishjamClient.addTrack(track, trackMetadata);
}
catch (err) {
if (err instanceof TrackTypeError) {
console.warn(err.message);
return undefined;
}
throw err;
}
}, [fishjamClient]);
const startStreaming = useCallback(async (source) => {
const stream = source?.stream;
const video = stream.getVideoTracks().at(0);
const audio = stream.getAudioTracks().at(0);
const promises = [];
const displayName = getDisplayName();
if (video) {
const videoMetadata = { type: "customVideo", displayName, paused: false };
promises.push(addTrackToFishjamClient(video, videoMetadata));
}
if (audio) {
const audioMetadata = { type: "customAudio", displayName, paused: false };
promises.push(addTrackToFishjamClient(audio, audioMetadata));
}
if (promises.length === 0) {
console.warn("Attempted to add empty MediaStream as custom source.");
return source;
}
const [videoId, audioId] = await Promise.all(promises);
return { ...source, trackIds: { videoId, audioId } };
}, [addTrackToFishjamClient, getDisplayName]);
const removeTracks = useCallback(async ({ videoId, audioId }) => {
const promises = [];
if (videoId)
promises.push(fishjamClient.removeTrack(videoId));
if (audioId)
promises.push(fishjamClient.removeTrack(audioId));
await Promise.all(promises);
}, [fishjamClient]);
const getSource = useCallback((sourceId) => sources[sourceId], [sources]);
const setStream = useCallback(async (sourceId, stream) => {
const oldSource = sources[sourceId];
if (stream === oldSource?.stream)
return;
if (oldSource?.trackIds)
await removeTracks(oldSource.trackIds);
if (stream !== null) {
setSources((old) => ({ ...old, [sourceId]: { stream } }));
return;
}
if (!oldSource)
return;
setSources((old) => Object.fromEntries(Object.entries(old).filter(([id, _]) => id !== sourceId)));
}, [sources, removeTracks]);
useEffect(() => {
const onConnected = async () => {
if (pendingSources.length === 0)
return;
const patch = Object.fromEntries(await Promise.all(pendingSources.map(async ([id, source]) => [id, await startStreaming(source)])));
setSources((old) => ({ ...old, ...patch }));
};
const onDisconnected = () => {
setSources((old) => Object.fromEntries(Object.entries(old).map(([id, source]) => [id, { ...source, trackIds: undefined }])));
};
if (peerStatus === "connected")
onConnected();
fishjamClient.on("disconnected", onDisconnected);
return () => {
fishjamClient.off("disconnected", onDisconnected);
};
}, [pendingSources, fishjamClient, peerStatus, startStreaming]);
return { setStream, getSource };
}