Setting Up a Peer-to-Peer Connection: Client Video Streaming (Part 1 of 3)

baliachbryan

Brian Baliach

Posted on May 7, 2023

Setting Up a Peer-to-Peer Connection: Client Video Streaming (Part 1 of 3)

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 up built on top of the Next.js framework. Strap in, it's going to be a fun ride!

Here's the link to the repo.

Here's a link 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:

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);
    }
})
Enter fullscreen mode Exit fullscreen mode

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:

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.")
      }
    }
  }
})
Enter fullscreen mode Exit fullscreen mode

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!

💖 💪 🙅 🚩
baliachbryan
Brian Baliach

Posted on May 7, 2023

Join Our Newsletter. No Spam, Only the good stuff.

Sign up to receive the latest update from our blog.

Related