Skip to content
Open
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
50 changes: 49 additions & 1 deletion examples/react-client/minimal-react/src/components/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,28 @@ import {
useMicrophone,
usePeers,
useScreenShare,
useSetTargetTrackEncoding,
Variant,
} from "@fishjam-cloud/react-client";
import { useStatistics } from "@fishjam-cloud/react-client/debug";
import { Fragment, useState } from "react";

import AudioPlayer from "./AudioPlayer";
import VideoPlayer from "./VideoPlayer";

const variantLabel = (variant: Variant | null | undefined): string => {
switch (variant) {
case Variant.VARIANT_LOW:
return "Low";
case Variant.VARIANT_MEDIUM:
return "Medium";
case Variant.VARIANT_HIGH:
return "High";
default:
return "N/A";
}
};

Comment on lines +16 to +28
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick:

Suggested change
const variantLabel = (variant: Variant | null | undefined): string => {
switch (variant) {
case Variant.VARIANT_LOW:
return "Low";
case Variant.VARIANT_MEDIUM:
return "Medium";
case Variant.VARIANT_HIGH:
return "High";
default:
return "N/A";
}
};
const variantLabelMap: Record<Variant, string> = {
[Variant.VARIANT_LOW]: "Low",
[Variant.VARIANT_MEDIUM]: "Medium",
[Variant.VARIANT_HIGH]: "High",
};

export const App = () => {
const [token, setToken] = useState("");

Expand All @@ -20,6 +35,7 @@ export const App = () => {
const screenShare = useScreenShare();
const { isCameraOn, toggleCamera } = useCamera();
const { isMicrophoneOn, toggleMicrophone } = useMicrophone();
const { setTargetTrackEncoding } = useSetTargetTrackEncoding();
const { getStatistics } = useStatistics();

{
Expand Down Expand Up @@ -95,7 +111,39 @@ export const App = () => {
return (
<Fragment key={id}>
{cameraStream && (
<VideoPlayer stream={cameraStream} peerId={id} />
<div>
<VideoPlayer stream={cameraStream} peerId={id} />
<div
style={{
display: "flex",
gap: "4px",
alignItems: "center",
marginTop: "4px",
}}
>
<span>Encoding: {variantLabel(cameraTrack?.encoding)}</span>
{[
Variant.VARIANT_LOW,
Variant.VARIANT_MEDIUM,
Variant.VARIANT_HIGH,
].map((variant) => (
<button
key={variant}
disabled={cameraTrack?.encoding === variant}
onClick={() => {
if (cameraTrack?.trackId) {
setTargetTrackEncoding(
cameraTrack.trackId,
variant,
);
}
}}
>
{variantLabel(variant)}
</button>
))}
</div>
</div>
)}
{microphoneStream && <AudioPlayer stream={microphoneStream} />}
{screenShareStream && (
Expand Down
14 changes: 12 additions & 2 deletions examples/react-client/minimal-react/src/main.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { FishjamProvider } from "@fishjam-cloud/react-client";
import { FishjamProvider, Variant } from "@fishjam-cloud/react-client";
import React from "react";
import ReactDOM from "react-dom/client";

Expand All @@ -8,7 +8,17 @@ const fishjamId = import.meta.env.VITE_FISHJAM_ID ?? "http://localhost:5555";

ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<FishjamProvider fishjamId={fishjamId} debug>
<FishjamProvider
fishjamId={fishjamId}
videoConfig={{
simulcast: [
Variant.VARIANT_LOW,
Variant.VARIANT_MEDIUM,
Variant.VARIANT_HIGH,
],
}}
debug
>
<App />
</FishjamProvider>
</React.StrictMode>,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ const eventNames = [
"trackAdded",
"trackRemoved",
"trackUpdated",
"encodingChanged",
"peerJoined",
"peerLeft",
"peerUpdated",
Expand Down
27 changes: 27 additions & 0 deletions packages/react-client/src/hooks/useSetTargetTrackEncoding.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { Variant } from "@fishjam-cloud/ts-client";
import { useCallback, useContext } from "react";

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

/**
* Hook provides a method to set the target encoding (simulcast variant) for a remote track.
*
* The encoding will be sent whenever it is available. If the chosen encoding is temporarily
* unavailable, some other encoding will be sent until the chosen encoding becomes active again.
*
* @category Connection
* @group Hooks
*/
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: i feel this hook is a bit too "low level" as for the react client. instead, i'd suggest adding a setTarget method to useCamera, useMicrophone, useScreenShare, useCustomSource` instead of introducing a new hook.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this will mitigate the need to pass over a track id. react client users don't even get to the "track" level, the hooks handle it for themselves. what do you think?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the thing is, we're going to be using this function wrt remote tracks, not local ones. so, as far as I understand, we'd need to extend usePeers, and you'd still need to somehow specify the video track from a given peer you want to act upon...

ofc if you see a more elegant way, I'm all ears -- my React proficiency is nonexistent

export function useSetTargetTrackEncoding() {
const fishjamClientRef = useContext(FishjamClientContext);
if (!fishjamClientRef) throw Error("useSetTargetTrackEncoding must be used within FishjamProvider");

const setTargetTrackEncoding = useCallback(
(trackId: string, encoding: Variant) => {
fishjamClientRef.current.setTargetTrackEncoding(trackId, encoding);
},
[fishjamClientRef],
);

return { setTargetTrackEncoding };
}
1 change: 1 addition & 0 deletions packages/react-client/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export {
export { type PeerWithTracks, usePeers } from "./hooks/usePeers";
export { type RoomType, useSandbox, type UseSandboxProps } from "./hooks/useSandbox";
export { useScreenShare } from "./hooks/useScreenShare";
export { useSetTargetTrackEncoding } from "./hooks/useSetTargetTrackEncoding";
export { useUpdatePeerMetadata } from "./hooks/useUpdatePeerMetadata";
export { useVAD } from "./hooks/useVAD";
export type {
Expand Down
4 changes: 4 additions & 0 deletions packages/ts-client/src/FishjamClient.ts
Original file line number Diff line number Diff line change
Expand Up @@ -374,6 +374,10 @@ export class FishjamClient<PeerMetadata = GenericMetadata, ServerMetadata = Gene
this.webrtc?.on('trackAdded', (ctx: TrackContext) => {
if (!isPeer(ctx.endpoint)) return;

ctx.on('encodingChanged', (trackCtx) => {
this.emit('encodingChanged', trackCtx as FishjamTrackContext);
});

this.emit('trackAdded', ctx as FishjamTrackContext);
});
this.webrtc?.on('trackRemoved', (ctx: TrackContext) => {
Expand Down
1 change: 1 addition & 0 deletions packages/ts-client/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,7 @@ export type MessageEvents<P, S> = {
*/
bandwidthEstimationChanged: (estimation: bigint) => void;

encodingChanged: TrackContextEvents['encodingChanged'];
targetTrackEncodingRequested: (event: Parameters<WebRTCEndpointEvents['targetTrackEncodingRequested']>[0]) => void;
localTrackAdded: (event: Parameters<WebRTCEndpointEvents['localTrackAdded']>[0]) => void;
localTrackRemoved: (event: Parameters<WebRTCEndpointEvents['localTrackRemoved']>[0]) => void;
Expand Down
34 changes: 16 additions & 18 deletions packages/webrtc-client/src/webRTCEndpoint.ts
Original file line number Diff line number Diff line change
Expand Up @@ -353,10 +353,8 @@ export class WebRTCEndpoint extends (EventEmitter as new () => TypedEmitter<Requ
* @param trackMetadata - Any information about this track that other endpoints will
* receive in {@link WebRTCEndpointEvents.endpointAdded}. E.g. this can source of the track - whether it's
* screensharing, webcam or some other media device.
* @param _simulcastConfig - Simulcast configuration parameter. **Currently ignored** - simulcast is disabled
* regardless of the value passed. This is a temporary change until bandwidth estimation is implemented or
* manual track selection support is added. For more information refer to {@link SimulcastConfig}.
* @param _maxBandwidth - maximal bandwidth this track can use. **Currently processed with a threshold check**:
* @param simulcastConfig - Simulcast configuration. For more information refer to {@link SimulcastConfig}.
* @param maxBandwidth - maximal bandwidth this track can use. **Currently processed with a threshold check**:
* if the value is a positive number, it will be used; otherwise, it defaults to 0 (unlimited).
* This option has no effect for simulcast and audio tracks.
* For simulcast tracks use `{@link WebRTCEndpoint.setTrackBandwidth}.
Expand Down Expand Up @@ -394,34 +392,34 @@ export class WebRTCEndpoint extends (EventEmitter as new () => TypedEmitter<Requ
public async addTrack(
track: MediaStreamTrack,
trackMetadata?: unknown,
_simulcastConfig: MediaEvent_Track_SimulcastConfig = {
simulcastConfig: MediaEvent_Track_SimulcastConfig = {
enabled: false,
enabledVariants: [],
disabledVariants: [],
},
_maxBandwidth: TrackBandwidthLimit = 0,
maxBandwidth: TrackBandwidthLimit = 0,
): Promise<string> {
const resolutionNotifier = new Deferred<void>();
const trackId = this.getTrackId(uuidv4());
const stream = new MediaStream();

// TODO: Simulcast is disabled manually, enable it once bandwidth estimation is implemented or we add manual track selection support.
const simulcastConfig: MediaEvent_Track_SimulcastConfig = {
enabled: false,
enabledVariants: [],
disabledVariants: [],
};

const maxBandwidth: TrackBandwidthLimit =
typeof _maxBandwidth === 'number' && _maxBandwidth > 0 ? _maxBandwidth : 0;
const resolvedMaxBandwidth: TrackBandwidthLimit =
typeof maxBandwidth === 'number' && maxBandwidth > 0 ? maxBandwidth : 0;

try {
stream.addTrack(track);

this.commandsQueue.pushCommand({
handler: async () =>
this.localTrackManager.addTrackHandler(trackId, track, stream, trackMetadata, simulcastConfig, maxBandwidth),
parse: () => this.localTrackManager.parseAddTrack(track, simulcastConfig, maxBandwidth),
this.localTrackManager.addTrackHandler(
trackId,
track,
stream,
trackMetadata,
simulcastConfig,
resolvedMaxBandwidth,
),
parse: () => this.localTrackManager.parseAddTrack(track, simulcastConfig, resolvedMaxBandwidth),
resolve: 'after-renegotiation',
resolutionNotifier,
});
Expand All @@ -436,7 +434,7 @@ export class WebRTCEndpoint extends (EventEmitter as new () => TypedEmitter<Requ
stream,
trackMetadata,
simulcastConfig,
maxBandwidth,
maxBandwidth: resolvedMaxBandwidth,
});
return trackId;
}
Expand Down
Loading