Setting Up a Peer-to-Peer Connection: Client Video Streaming (Part 1 of 3)
Brian Baliach
Posted on May 7, 2023
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);
}
})
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.")
}
}
}
})
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!
Posted on May 7, 2023
Join Our Newsletter. No Spam, Only the good stuff.
Sign up to receive the latest update from our blog.