import JellyfishArchitecture from "./_jellyfish-architecture.mdx"; import StartingJellyfishBackend from "./_starting-jellyfish-backend.mdx"; import StartingJellyfishDashboard from "./_starting-dashboard.mdx";
This tutorial will guide you through creating your first React project which uses Jellyfish client. By the end of the tutorial, you'll have a working web application that connects to the front-end dashboard using WebRTC technology, streams your screen and receives video tracks.
You can check out the finished project here
- a little bit of experience in creating React apps
- IDE of your choice (for example Visual Studio Code)
- Node.js installed on your machine
Firstly create a brand new project.
npm create vite@latest my-react-app -- --template react-tsnpm install https://github.com/jellyfish-dev/react-client-sdkFor this project, we prepared simple CSS classes, you are free to use it or create your own.
Our app will consist of two parts:
-
a component which will connect to the server and join the room
-
a component which will display the video tracks from other participants
In order to connect to the Jellyfish backend, we need to create a Membrane Client
instance. We can do it by using the create function from the
@jellyfish-dev/react-client-sdk package. It needs two generic parameters:
-
PeerMetadata- type of the metadata that will be sent to the server when connecting to the room (for example, user name) it has to be serializable -
TrackMetadata- type of the metadata that will be sent to the server when sending a track (for example, track name) it has to be serializable aswell
import React from "react";
import { create } from "@jellyfish-dev/react-client-sdk";
// Example metadata types for peer and track
// You can define your own metadata types just make sure they are serializable
type PeerMetadata = {
name: string;
};
type TrackMetadata = {
type: "camera" | "screen";
};
// Create a Jellyfish client instance
// Since we will use this context outside of the component we need to export it
export const {
JellyfishContextProvider, // Context provider
} = create<PeerMetadata, TrackMetadata>();
export const App = () => {};That's all we will need to do in this file. Simply import the JellyfishContextProvider along with the App
component and wrap the App component with the JellyfishContextProvider:
import React from "react";
import ReactDOM from "react-dom/client";
import { App, JellyfishContextProvider } from "./components/App";
ReactDOM.createRoot(document.getElementById("root") as HTMLElement).render(
<React.StrictMode>
<JellyfishContextProvider>
<App />
</JellyfishContextProvider>
</React.StrictMode>
);The UI of the component will be very simple. It will consist of a simple text imput field that will allow us to enter the peer token and a button which will connect to the server and join the room. We can also display the status of the connection.
import React, { useState } from "react";
//...
export const App = () => {
// Create a state to store the peer token
const [token, setToken] = useState("");
return (
<div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
<input value={token} onChange={(e) => setToken(() => e?.target?.value)} placeholder="token" />
<div style={{ display: "flex", flexDirection: "row", gap: "8px" }}>
<button
disabled={}
onClick={() => {}};
>
Connect
</button>
<button
disabled={}
onClick={() => {}};
>
Disconnect
</button>
<span>Status: {}</span>
</div>
</div>
);
};import { SignalingUrl } from "@jellyfish-dev/react-client-sdk/.";
//...
// This is the address of the Jellyfish backend. Adjust it to your server. We
// strongly recommend setting this as an environment variable, we hardcoded it here
// for simplicity.
const JELLYFISH_URL = {
protocol: "ws",
host: "localhost:4002",
path: "/socket/peer/websocket",
} as SignalingUrl;
export const {
// highlight-start
useStatus, // Hook to check the status of the connection
useConnect, // Hook to connect to the server
useDisconnect, // Hook to disconnect from the server
// highlight-end
JellyfishContextProvider, // Context provider
};
export const App = () => {
// Create a state to store the peer token
const [token, setToken] = useState("");
// Use build in hook to check the status of the connection
const status = useStatus();
const connect = useConnect();
const disconnect = useDisconnect();
return (
<div style={{ display: "flex", flexDirection: "column", gap: "8px" }}>
<input
className="input-field"
value={token}
onChange={(e) => setToken(() => e?.target?.value)}
placeholder="token"
/>
<div style={{ display: "flex", flexDirection: "row", gap: "8px" }}>
<button
className="button"
disabled={token === "" || status === "joined"} // simple check to avoid errors
onClick={() => {
// highlight-start
connect({
peerMetadata: { name: "John Doe" }, // example metadata
token: token,
signaling: JELLYFISH_URL,
});
// highlight-end
}}
>
Connect
</button>
<button
className="button"
disabled={status !== "joined"}
onClick={() => {
// highlight-next-line
disconnect();
}}
>
Disconnect
</button>
// highlight-next-line
<span className="span-status">Status: {status}</span>
</div>
</div>
);
};Great! Now we can connect to the server and join the room. But we still need to add some logic to send our tracks to the server and recieve tracks from others.
This hook uses Navigator.mediaDevices take a look how it works
import React, { useEffect, useState } from "react";
import { create, SCREEN_SHARING_MEDIA_CONSTRAINTS } from "@jellyfish-dev/react-client-sdk";
import { SignalingUrl, Peer } from "@jellyfish-dev/react-client-sdk/.";
//...
export const {
useStatus, // Hook to check the status of the connection
// highlight-next-line
useApi, // Hook to get the webrtcApi reference
useConnect, // Hook to connect to the server
useDisconnect, // Hook to disconnect from the server
JellyfishContextProvider, // Context provider
} = create<PeerMetadata, TrackMetadata>();
export const App = () => {
//...
// Get the webrtcApi reference
const webrtcApi = useApi();
function startScreenSharing() {
// Get screen sharing MediaStream
navigator.mediaDevices.getDisplayMedia(SCREEN_SHARING_MEDIA_CONSTRAINTS).then((screenStream) => {
// Add local MediaStream to webrtc
screenStream.getTracks().forEach((track) => webrtcApi.addTrack(track, screenStream, { type: "screen" }));
};
};
return (
//...
<button
className="button"
disabled={status !== "joined"}
onClick={() => {
startScreenSharing();
}}
>
Start screen share
</button>
<span>Status: {status}</span>
//...
)
};You should now see your screen recieved to each connected client on the dashboard. You can add another participant to check this out!
The streaming part of the app is ready!
For each track recieved we will create a new video element and display it on the screen. For clarity, we will separate this component to another file:
Create in your directory file VideoPlayer.tsx
type Props = {
stream: MediaStream | null | undefined;
};
const VideoPlayer = ({ stream }: Props) => {
return (
<div className="video-container">
<video autoPlay playsInline muted ref={/* place for track ref*/} />
</div>
);
};
export default VideoPlayer;type Props = {
stream: MediaStream | null | undefined;
};
const VideoPlayer = ({ stream }: Props) => {
const videoRef: RefObject<HTMLVideoElement> = useRef<HTMLVideoElement>(null);
useEffect(() => {
if (!videoRef.current) return;
videoRef.current.srcObject = stream || null;
}, [stream]);
return (
<div>
<video autoPlay playsInline muted ref={videoRef} />
</div>
);
};
export default VideoPlayer;import React, { useEffect, useState } from "react";
import { create, SCREEN_SHARING_MEDIA_CONSTRAINTS } from "@jellyfish-dev/react-client-sdk";
import { SignalingUrl, Peer } from "@jellyfish-dev/react-client-sdk/.";
import VideoPlayer from "./VideoPlayer";
//...
export const {
useStatus, // Hook to check the status of the connection
// highlight-next-line
useTracks, // Hook to get the tracks from the server
useApi, // Hook to get the webrtcApi reference
useConnect, // Hook to connect to the server
useDisconnect, // Hook to disconnect from the server
JellyfishContextProvider, // Context provider
} = create<PeerMetadata, TrackMetadata>();
export const App = () => {
const tracks = useTracks();
//...
<div
style={{
display: "flex",
flexWrap: "wrap",
justifyContent: "center", // To align items in the center
gap: "20px",
}}
>
{Object.values(tracks).map(({ stream, trackId }) => (
<VideoPlayer key={trackId} stream={stream} /> // pass the stream to the component
))}
</div>
//...
)You should see all the tracks sent from dashboard directly on your page, to test it, add new client and add them a track (for example a rotating frog). It will show up in your app automatically:
Congrats on finishing your first Jellyfish web application! In this tutorial, you've learned how to make a basic Jellyfish client application that streams your screen and receives video tracks with WebRTC technology.
But this was just the beginning. Jellyfish Client supports much more than just streaming camera: it can also stream audio or your device's camera, configure your camera and audio devices, detect voice activity, control simulcast, bandwidth and encoding settings, show camera preview, display WebRTC stats and more to come. Check out our other tutorials to learn about those features.
You can also take a look at our fully featured Videoroom Demo example:

