Skip to content

Latest commit

 

History

History
399 lines (319 loc) · 12 KB

File metadata and controls

399 lines (319 loc) · 12 KB

import JellyfishArchitecture from "./_jellyfish-architecture.mdx"; import StartingJellyfishBackend from "./_starting-jellyfish-backend.mdx"; import StartingJellyfishDashboard from "./_starting-dashboard.mdx";

React Quickstart Guide

What you'll learn

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.

Finished app

You can check out the finished project here

What do you need

  • a little bit of experience in creating React apps
  • IDE of your choice (for example Visual Studio Code)
  • Node.js installed on your machine

Jellyfish architecture

Setup

Create React + Vite project

Firstly create a brand new project.

npm create vite@latest my-react-app -- --template react-ts

Add dependencies

In order for this module to work you'll need to add our `react-client-sdk` package. This is necessary to create and connect Jellyfish Client.
npm install https://github.com/jellyfish-dev/react-client-sdk

Start the Jellyfish backend

Start the dashboard web front-end

(Optional) Add a bit of CSS styling

For this project, we prepared simple CSS classes, you are free to use it or create your own.

General project structure

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

First step - prepare all the hooks and the context

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 = () => {};

Now we need to wrap our app with the context provider

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>
);

UI component that will connect to the server and join the room

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>
  );
};

Once the UI is ready, we need to implement the logic

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.

Let's send our screen to the server

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!

What about the recieving part?

This is where the second component comes in handy

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;

Now the logic for the component

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;

Now we can use it in our main component

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:

Summary

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:

Videoroom Demo