Setting Up a Peer-to-Peer Connection: Client Video Streaming (Part 1 of 3)
Published: 2023-05-07 16:02:42


Welcome to the first part of our three-part series on setting up a peer-to-peer (P2P) connection to send a video stream between two clients (a server serving as an intermediary only for initial connection set-up). 

We'll demonstrate this using a [React](https://react.dev/learn) up built on top of the [Next.js](https://nextjs.org/docs) framework. Strap in, it's going to be a fun ride!

Here's the link to the [repo](https://github.com/TylerMutai/peer-to-peer-video-conferencing).

Here's a [link](https://www.balysnotes.com/setting-up-a-peer-to-peer-connection-video-stream) to the original blog.
 
In this first installment, we'll be talking about the available Web APIs for obtaining a video stream from the client. To get started, let's take a look at a piece of TypeScript code that we'll be using as a reference:

```typescript
const getWebcamStream = useCallback(async () => {
    try {
      const videoStream = await navigator.mediaDevices.getUserMedia({
        video: {
          facingMode: facingMode.current
        }
      });
      if (videoStream && videoRef.current) {
        // set this video stream to our video stream object.
        videoRef.current.srcObject = videoStream
        setIsStreamStarted(true);
      }
    } catch (e: any) {
      setErrorMessage(e?.message || "Could not start video stream. Have you enabled permissions?")
      setIsStreamStarted(false);
    }
})
```

Now, let's dive into the nitty-gritty details of this code and see what's happening under the hood.

### Web APIs: Browser Support for Video Streaming

The code snippet above showcases a few Web APIs that work together to obtain a video stream from the client's device. Let's break them down one by one:

#### `navigator.mediaDevices.getUserMedia`

`getUserMedia` is a method available through the `navigator.mediaDevices` object, and it returns a Promise that resolves to the requested `MediaStream` object containing live audio and/or video streams. In our code snippet, we use the `await` keyword to fetch the video stream asynchronously.

The `getUserMedia` method accepts an object detailing the media constraints you want to apply to your stream. In our case, we're only interested in obtaining a video stream, so we pass `{ video: { facingMode: facingMode.current } }`. This tells the method to grab a video stream with the specified facing mode (either `user` for the front camera or `environment` for the rear camera on mobile devices).

#### `videoRef.current.srcObject`

Once we have the video stream, we need to display it on our web page. To do this, we set the `srcObject` property of our video element (`videoRef.current`) to the obtained video stream. Now the video element knows which stream to play, and the user can see what their camera is capturing.

### Stopping the Video Stream

Before we wrap up, let's take a look at another piece of TypeScript code that handles stopping the video stream:

```typescript
const handleStopStream = useCallback(async (shouldIgnoreState = false) => {
  const stream = videoRef.current?.srcObject;
  if (stream) {
    if ("getTracks" in stream) {
      const tracks = stream.getTracks();
      for (let i = 0; i < tracks.length; i++) {
        tracks[i].stop();
      }
      videoRef.current.srcObject = null;
      if (!shouldIgnoreState) {
        setIsStreamStarted(false);
        setErrorMessage("Video stream was stopped by the user.")
      }
    }
  }
})
```

This code snippet introduces a function called `handleStopStream` that stops the video stream when necessary. Let's break it down:

#### `videoRef.current?.srcObject`

First, we check if there's an active video stream by accessing the `srcObject` property of the video element (`videoRef.current`). If a stream exists, we proceed to stop it.

#### `stream.getTracks` and `tracks[i].stop()`

As we saw earlier, `getTracks` returns an array of `MediaStreamTrack` objects representing the audio and video tracks in the stream. To stop the stream, we loop through these tracks and call the `stop` method on each one. This effectively stops the video stream from capturing data and frees up resources.

#### `videoRef.current.srcObject = null`

Once the tracks are stopped, we set the `srcObject` property of the video element to `null`. This removes the video stream from the video element and stops displaying the video on the web page.

#### `setIsStreamStarted(false)` and `setErrorMessage("Video stream was stopped by the user.")`

Finally, we update the state of our application to reflect the fact that the video stream has been stopped. We set `isStreamStarted` to `false` and provide an appropriate error message. The `shouldIgnoreState` parameter is used to determine whether or not to update the state; it can be useful in scenarios where stopping the stream doesn't require updating the application state.

### Wrapping Up

So there you have it! With just a few lines of code and some powerful Web APIs, we can obtain a video stream from the client's device, display it on a web page, and set the stage for sharing it with other clients using P2P connections.

Stay tuned for the next part of this series, where we'll explore how to set up WebRTC using Socket.io.
Until then, happy coding!