Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
153 changes: 84 additions & 69 deletions packages/react-client/src/hooks/internal/useScreenshareManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void>[] = [];
const addTrackPromises: Promise<void>[] = [];

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<void> => {
if (!state?.stream) return;
const setTracksMiddleware = useCallback(
async (middleware: TracksMiddleware | null): Promise<void> => {
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) {
Expand All @@ -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,
Expand Down Expand Up @@ -212,7 +227,7 @@ export const useScreenShareManager = ({
videoTrack: mediaVideoTrack,
audioTrack: mediaAudioTrack,
setTracksMiddleware,
currentTracksMiddleware: state?.tracksMiddleware ?? null,
currentTracksMiddleware: tracksMiddleware,
};
};

Expand Down
91 changes: 51 additions & 40 deletions packages/react-client/src/hooks/useSandbox.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { useCallback } from "react";

import { useFishjamId } from "../contexts/fishjamId";
import { resolveFishjamUrl } from "../utils/fishjamUrl";

Expand All @@ -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 };
};
6 changes: 4 additions & 2 deletions packages/react-client/src/hooks/useStatistics.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,17 @@
import { useContext } from "react";
import { useCallback, useContext } from "react";

import { FishjamClientContext } from "../contexts/fishjamClient";

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,
};
};
Loading