24
loading...
This website collects cookies to deliver better user experience
getUserMedia()
method of the navigator.mediaDevices
object. Note that this object will only be available when you're working in an HTTPS ****(secured) context.const getMedia = () => {
const constraints = {
// ...
};
try {
const stream = await navigator.mediaDevices.getUserMedia(constraints);
// use the stream
} catch (err) {
// handle the error - user's rejection or no media available
}
}
getMedia();
MediaStream
instance. Such an interface is a representation of currently streamed media. It consists of zero or more separate MediaStreamTrack
s, each representing video or audio track, from which audio tracks consist of right and left channels (for stereo and stuff). These tracks also provide some special methods and events if you need further control.getUserMedia()
request and the resulting stream. This object can have two properties of either boolean or object value - audio
and video
.// ...
const constraints = {
video: true,
audio: true,
};
// ...
getUserMedia()
to work, you have to set at least one of those two properties.getSupportedConstraints()
method.const getConstraints = async () => {
const supportedConstraints = navigator.mediaDevices.getSupportedConstraints();
const video = {};
if (supportedConstraints.width) {
video.width = 1920;
}
if (supportedConstraints.height) {
video.height = 1080;
}
if (supportedConstraints.deviceId) {
const devices = await navigator.mediaDevices.enumerateDevices();
const device = devices.find((device) => {
return device.kind == "videoinput";
});
video.deviceId = device.deviceId;
}
return { video };
};
// ...
enumerateDevices()
method to check for available input devices, together with setting the video's resolution. It returns a promise which resolves to an array of MediaDeviceInfo
objects, which we later use, to select the first one in line for video input.getMedia()
function, and we're good to go!// ...
const constraints = await getConstraints();
// ...
getDisplayMedia()
method.navigator.mediaDevices.getDisplayMedia();
getUserMedia()
and getDisplayMedia()
methods. Especially when combined with, e.g., Canvas and Web Audio APIs. But, as this is a topic for another day, we're only interested in previewing our streams with simple <video>
and <audio>
elements.<video autoplay></video>
<audio autoplay></audio>
// ...
// inside getMedia() after successfully retrieving MediaStream
const video = document.querySelector("video");
const audio = document.querySelector("audio");
video.srcObject = stream;
audio.srcObject = stream;
// ...
RTCPeerConnection
relies on events, we'll use these and something called RTCIceCandidate
to set up our connection. ICE stands for Internet Connectivity Establishment and indicates the established protocol and routing used throughout the connection. Here you can, e.g., decide between the UDP and TCP protocols, depending on whether you need your data to be delivered fast with some possible losses or otherwise.// One RTCPeerConnection instance for each side
const localConnection = new RTCPeerConnection();
// On the remote side:
const remoteConnection = new RTCPeerConnection();
icecandidate
event handlers to listen for possible offers.localConnection.addEventListener("icecandidate", async (event) => {
if (event.candidate) {
// Send event.candidate to the other side through e.g. WebSocket
try {
// On the remote side:
await remoteConnection.addIceCandidate(event.candidate);
} catch {
// handle error
}
}
});
// On the remote side:
remoteConnection.addEventListener("icecandidate", async (e) => {
if (e.candidate) {
// Send event.candidate to the other side through e.g. WebSocket
try {
// On the local side:
await localConnection.addIceCandidate(e.candidate);
} catch {
// handle error
}
}
});
const connect = async () => {
try {
const offer = await localConnection.createOffer();
await localConnection.setLocalDescription(offer);
/* Send offer to the other side through e.g. WebSocket
Then, on the remote side: */
await remoteConnection.setRemoteDescription(
localConnection.localDescription
);
const answer = await remoteConnection.createAnswer();
await remoteConnection.setLocalDescription(answer);
/* Send offer to the other side through e.g. WebSocket
Then, on the local side: */
await localConnection.setRemoteDescription(
remoteConnection.localDescription
);
} catch {
// handle errors
}
};
connect();
createOffer()
call, to then use it to configure the local connection. After that, we have to send this configuration to the remote connection, where it'll be either accepted or rejected. Here, as we don't do any decision-making process, we're accepting any configuration up-front. We do this using the setRemoteDescription()
, and then we send the return information to be accepted again on the local side.icecandidate
event will be fired, and the signaling process is complete.MediaStream
object from one of the user's input devices. We’ll use that for this demo.MediaStream
object ready to go. In this case, you can use the addTrack()
method to start transmitting individual tracks. It's worth noting that there used to be an addStream()
method to add whole MediaStream
s at once, but it's now obsolete and you shouldn’t use it (although some browsers might still support it).const startStreaming = async () => {
const stream = await navigator.mediaDevices.getUserMedia({
video: true,
audio: true,
});
for (const track of stream.getTracks()) {
localConnection.addTrack(track);
}
};
MediaStream
with its constructor, and listen for track
events. Add all incoming tracks to the stream, and attach the stream itself to a video element.const startReceiving = () => {
const video = document.getElementById("video");
const stream = new MediaStream();
video.srcObject = stream;
remoteConnection.addEventListener("track", (event) => {
stream.addTrack(event.track);
});
};
connect()
call.const run = async () => {
await startStreaming();
// On the remote side:
startReceiving();
// On both sides:
connect();
};
run();
negotiationneeded
event on the RTCPeerConnection
and renegotiate the offer there.RTCDataChannel
.createDataChannel()
method of RTCPeerConnection
, passing the channel’s name as a parameter.const startSendingData = async () => {
const dataChannel = localConnection.createDataChannel("my-data-channel");
};
datachannel
events, and extract channel
property from the event object.const startReceivingData = () => {
remoteConnection.addEventListener("datachannel", async (event) => {
const dataChannel = event.channel;
});
};
open
event listener. After that’s done, you can use the send()
method to send all kinds of data and receive it on the other side within the message
event handler.const useDataChannel = (dataChannel, onMessage) => {
return new Promise((resolve) => {
dataChannel.addEventListener("open", () => {
dataChannel.addEventListener("message", (event) => {
if (onMessage) {
onMessage(event);
}
});
resolve((message) => {
dataChannel.send(message);
});
});
});
};
RTCDataChannel
. Its Promise resolves on open
, returning a function for sending data and allowing for an optional callback to handle message
events coming from the other side.const startSendingData = () => {
const dataChannel = localConnection.createDataChannel("my-data-channel");
const sendMessage = useDataChannel(dataChannel, (event) => {
console.log("Local:", event.data);
}).then((sendMessage) => {
sendMessage("Message from local");
});
};
const startReceivingData = () => {
remoteConnection.addEventListener("datachannel", (event) => {
const dataChannel = event.channel;
const sendMessage = useDataChannel(dataChannel, (event) => {
console.log("Remote:", event.data);
}).then(() => {
sendMessage("Message from remote");
});
});
};
connect()
function called last.const run = async () => {
startSendingData();
// On the remote side:
startReceivingData();
// On both sides:
connect();
};
run();
await
for the channel to open - it’ll happen after the connection is made. To avoid confusion and illustrate this intention in both startSendingData()
and startReceivingData()
I’ve used the then()
instead of async
/await
.