Build Real-time transcription app using React Hooks
Tags: showdev, webdev, react, ai
Published: 2024-05-25 17:55:26

## Step-by-Step Tutorial for the Code

This tutorial will guide you through the setup and functioning of the React app using the VideoSDK.live SDK. The app allows users to join or create meetings and provides functionalities for recording and transcription.

### Prerequisites
1. **Node.js and npm**: Ensure you have Node.js and npm installed.
2. **VideoSDK Account**: Sign up at [VideoSDK.live](https://www.videosdk.live) and get your API key.

### Step 1: Setup the Project
1. **Create a React App**: If you don't already have a React app, create one using the following command:
   ```sh
   npx create-react-app my-video-app
   cd my-video-app
   ```

2. **Install Dependencies**: Install the necessary packages.
   ```sh
   npm install @videosdk.live/react-sdk react-player
   ```

### Step 2: Setup API Functions
Create an `API.js` file in the `src` directory to handle API interactions.
```js
// API.js
export const authToken = "YOUR_VIDEO_SDK_AUTH_TOKEN";

export const createMeeting = async ({ token }) => {
  const response = await fetch("https://api.videosdk.live/v1/meetings", {
    method: "POST",
    headers: {
      Authorization: token,
      "Content-Type": "application/json",
    },
  });
  const data = await response.json();
  return data.meetingId;
};
```

### Step 3: Build the Components
In your `App.js`, import necessary modules and create components as described below.

#### JoinScreen Component
Allows users to enter a meeting ID or create a new meeting.
```js
import React, { useState } from "react";

function JoinScreen({ getMeetingAndToken }) {
  const [meetingId, setMeetingId] = useState(null);
  const onClick = async () => {
    await getMeetingAndToken(meetingId);
  };
  return (
    <div>
      <input
        type="text"
        placeholder="Enter Meeting Id"
        onChange={(e) => {
          setMeetingId(e.target.value);
        }}
      />
      <button onClick={onClick}>Join</button>
      {" or "}
      <button onClick={onClick}>Create Meeting</button>
    </div>
  );
}

export default JoinScreen;
```

#### ParticipantView Component
Displays the video and audio of a participant.
```js
import React, { useEffect, useMemo, useRef } from "react";
import { useParticipant } from "@videosdk.live/react-sdk";
import ReactPlayer from "react-player";

function ParticipantView(props) {
  const micRef = useRef(null);
  const { webcamStream, micStream, webcamOn, micOn, isLocal, displayName } =
    useParticipant(props.participantId);

  const videoStream = useMemo(() => {
    if (webcamOn && webcamStream) {
      const mediaStream = new MediaStream();
      mediaStream.addTrack(webcamStream.track);
      return mediaStream;
    }
  }, [webcamStream, webcamOn]);

  useEffect(() => {
    if (micRef.current) {
      if (micOn && micStream) {
        const mediaStream = new MediaStream();
        mediaStream.addTrack(micStream.track);

        micRef.current.srcObject = mediaStream;
        micRef.current
          .play()
          .catch((error) =>
            console.error("videoElem.current.play() failed", error)
          );
      } else {
        micRef.current.srcObject = null;
      }
    }
  }, [micStream, micOn]);

  return (
    <div>
      <p>
        Participant: {displayName} | Webcam: {webcamOn ? "ON" : "OFF"} | Mic:{" "}
        {micOn ? "ON" : "OFF"}
      </p>
      <audio ref={micRef} autoPlay playsInline muted={isLocal} />
      {webcamOn && (
        <ReactPlayer
          playsinline
          pip={false}
          light={false}
          controls={false}
          muted={true}
          playing={true}
          url={videoStream}
          height={"300px"}
          width={"300px"}
          onError={(err) => {
            console.log(err, "participant video error");
          }}
        />
      )}
    </div>
  );
}

export default ParticipantView;
```

#### Controls Component
Provides buttons to leave the meeting and toggle mic/webcam.
```js
import React from "react";
import { useMeeting } from "@videosdk.live/react-sdk";

function Controls() {
  const { leave, toggleMic, toggleWebcam } = useMeeting();
  return (
    <div>
      <button onClick={() => leave()}>Leave</button>
      <button onClick={() => toggleMic()}>toggleMic</button>
      <button onClick={() => toggleWebcam()}>toggleWebcam</button>
    </div>
  );
}

export default Controls;
```

#### MeetingView Component
Main component to handle meeting functionalities like transcription, recording, and displaying participants.
```js
import React, { useState } from "react";
import { useMeeting, useTranscription, Constants } from "@videosdk.live/react-sdk";
import ParticipantView from "./ParticipantView";
import Controls from "./Controls";

function MeetingView(props) {
  const [transcript, setTranscript] = useState("Transcription");
  const [transcriptState, setTranscriptState] = useState("Not Started");
  const tConfig = { webhookUrl: "https://www.example.com" };
  const { startTranscription, stopTranscription } = useTranscription({
    onTranscriptionStateChanged: (data) => {
      const { status } = data;
      if (status === Constants.transcriptionEvents.TRANSCRIPTION_STARTING) {
        setTranscriptState("Transcription Starting");
      } else if (status === Constants.transcriptionEvents.TRANSCRIPTION_STARTED) {
        setTranscriptState("Transcription Started");
      } else if (status === Constants.transcriptionEvents.TRANSCRIPTION_STOPPING) {
        setTranscriptState("Transcription Stopping");
      } else if (status === Constants.transcriptionEvents.TRANSCRIPTION_STOPPED) {
        setTranscriptState("Transcription Stopped");
      }
    },
    onTranscriptionText: (data) => {
      let { participantName, text, timestamp } = data;
      console.log(`${participantName}: ${text} ${timestamp}`);
      setTranscript(transcript + `${participantName}: ${text} ${timestamp}`);
    },
  });

  const { startRecording, stopRecording } = useMeeting();

  const handleStartRecording = () => {
    startRecording("YOUR_WEB_HOOK_URL", "AWS_Directory_Path", {
      layout: { type: "GRID", priority: "SPEAKER", gridSize: 4 },
      theme: "DARK",
      mode: "video-and-audio",
      quality: "high",
      orientation: "landscape",
    });
  };

  const handleStopRecording = () => stopRecording();
  const handleStartTranscription = () => startTranscription(tConfig);
  const handleStopTranscription = () => stopTranscription();

  const [joined, setJoined] = useState(null);
  const { join, participants } = useMeeting({
    onMeetingJoined: () => setJoined("JOINED"),
    onMeetingLeft: () => props.onMeetingLeave(),
  });

  const joinMeeting = () => {
    setJoined("JOINING");
    join();
  };

  return (
    <div className="container">
      <h3>Meeting Id: {props.meetingId}</h3>
      {joined && joined === "JOINED" ? (
        <div>
          <Controls />
          <button onClick={handleStartRecording}>Start Recording</button>
          <button onClick={handleStopRecording}>Stop Recording</button>
          <button onClick={handleStartTranscription}>Start Transcription</button>
          <button onClick={handleStopTranscription}>Stop Transcription</button>
          {[...participants.keys()].map((participantId) => (
            <ParticipantView participantId={participantId} key={participantId} />
          ))}
          <p>State: {transcriptState}</p>
          <p>{transcript}</p>
        </div>
      ) : joined && joined === "JOINING" ? (
        <p>Joining the meeting...</p>
      ) : (
        <button onClick={joinMeeting}>Join</button>
      )}
    </div>
  );
}

export default MeetingView;
```

#### App Component
Main component that manages the meeting state and provides the necessary context.
```js
import React, { useState } from "react";
import { MeetingProvider } from "@videosdk.live/react-sdk";
import JoinScreen from "./JoinScreen";
import MeetingView from "./MeetingView";
import { authToken, createMeeting } from "./API";

function App() {
  const [meetingId, setMeetingId] = useState(null);

  const getMeetingAndToken = async (id) => {
    const meetingId =
      id == null ? await createMeeting({ token: authToken }) : id;
    setMeetingId(meetingId);
  };

  const onMeetingLeave = () => setMeetingId(null);

  return authToken && meetingId ? (
    <MeetingProvider
      config={{
        meetingId,
        micEnabled: true,
        webcamEnabled: true,
        name: "C.V. Raman",
      }}
      token={authToken}
    >
      <MeetingView meetingId={meetingId} onMeetingLeave={onMeetingLeave} />
    </MeetingProvider

>
  ) : (
    <JoinScreen getMeetingAndToken={getMeetingAndToken} />
  );
}

export default App;
```

### Step 4: Run the Application
1. **Start the React App**:
   ```sh
   npm start
   ```

2. **Navigate to Your Browser**: Open [http://localhost:3000](http://localhost:3000) to view the app.


![VideoSDK Real-time Transcription Output](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/s8poe6kf9gps0c3jqz3p.gif)

### Optional Step: Add CSS for Styling
To enhance the visual appeal of your app, you can add the following CSS.

Create an `App.css` file in your `src` directory with the following content:
```css
/* App.css */

body {
  background-color: #121212;
  color: #e0e0e0;
  font-family: 'Roboto', sans-serif;
  margin: 0;
  padding: 0;
}

input, button {
  background-color: #1e1e1e;
  border: 1px solid #333;
  color: #e0e0e0;
  padding: 10px;
  margin: 5px;
  border-radius: 5px;
}

button:hover {
  background-color: #333;
  cursor: pointer;
}

.container {
  max-width: 800px;
  margin: auto;
  padding: 20px;
  text-align: center;
}

h3 {
  color: #f5f5f5;
}

p {
  margin: 10px 0;
}

audio, .react-player__preview {
  background-color: #333;
  border: 1px solid #555;
  border-radius: 5px;
  margin: 10px 0;
}

.react-player__preview img {
  border-radius: 5px;
}

.react-player__shadow {
  border-radius: 5px;
}
```

### Summary
You have created a functional video meeting application using the VideoSDK.live SDK. This app allows users to join or create meetings, manage participants, and control functionalities like recording and transcription. The optional CSS step ensures a consistent and visually appealing user interface.