WebRTC: Difference between revisions
Line 146: | Line 146: | ||
==Resources== | ==Resources== | ||
* [https://www.html5rocks.com/en/tutorials/webrtc/basics/ HTML5Rocks: Getting Started with WebRTC (JS)] | * [https://www.html5rocks.com/en/tutorials/webrtc/basics/ HTML5Rocks: Getting Started with WebRTC (JS)] | ||
* [https://developer.mozilla.org/en-US/docs/Web/API/WebRTC_API/Simple_RTCDataChannel_sample MDN Simple RTCDataChannel sample] | |||
==References== | ==References== |
Revision as of 01:10, 19 August 2020
WebRTC (Web Real-Time Communication) is a standard for peer-to-peer real-time communication.
It supports audio streams, video streams, and data streams but has a complicated API and handshake which needs to be performed over an existing connection such as WebSockets to a server accessible from both peers.
Background (JavaScript)
To get started, first read this background section, then look into WebRTC examples in JavaScript.
WebRTC is a real-time P2P communications protocol which supports audio, video, and data.
For P2P applications to work, they need to first negotiation a connection using an intermediary server.
This signalling is typically done through a WebSocket but can also be done using AJAX via Socket.IO.
MediaStream
The purpose of MediaStream to to access the user's camera, microphone, or screen. This is done using MediaDevices.getUserMedia
RTCPeerConnection
The purpose of RTCPeerConnection is to negotiate a connection using the signalling channel (the Websocket forwarded through your nat).
When you make an RTCPeerConnection, you should pass in a list of iceServers:
const configuration = {iceServers: [{urls: 'stun.l.google.com:19302'}]};
const pc = new RTCPeerConnection(configuration);
There are two types of iceServers:
- STUN servers are used to find the public IP and port of the client.
- You can find a list of STUN servers here.
- TURN servers are a fallback used to proxy data through the NAT.
For each STUN server, you need to send it to the other peer via your signalling channel:
const configuration = {iceServers: [{urls: 'stuns:stun.example.org'}]};
The other client will add these as follows:
remoteConnection.addIceCandidate(e.candidate)
Below is from HTML5Rocks.
// handles JSON.stringify/parse
const signaling = new SignalingChannel();
const constraints = {audio: true, video: true};
const configuration = {iceServers: [{urls: 'stuns:stun.example.org'}]};
const pc = new RTCPeerConnection(configuration);
// send any ice candidates to the other peer
pc.onicecandidate = ({candidate}) => signaling.send({candidate});
// let the "negotiationneeded" event trigger offer generation
pc.onnegotiationneeded = async () => {
try {
await pc.setLocalDescription(await pc.createOffer());
// send the offer to the other peer
signaling.send({desc: pc.localDescription});
} catch (err) {
console.error(err);
}
};
// once remote track media arrives, show it in remote video element
pc.ontrack = (event) => {
// don't set srcObject again if it is already set.
if (remoteView.srcObject) return;
remoteView.srcObject = event.streams[0];
};
// call start() to initiate
async function start() {
try {
// get local stream, show it in self-view and add it to be sent
const stream =
await navigator.mediaDevices.getUserMedia(constraints);
stream.getTracks().forEach((track) =>
pc.addTrack(track, stream));
selfView.srcObject = stream;
} catch (err) {
console.error(err);
}
}
signaling.onmessage = async ({desc, candidate}) => {
try {
if (desc) {
// if we get an offer, we need to reply with an answer
if (desc.type === 'offer') {
await pc.setRemoteDescription(desc);
const stream =
await navigator.mediaDevices.getUserMedia(constraints);
stream.getTracks().forEach((track) =>
pc.addTrack(track, stream));
await pc.setLocalDescription(await pc.createAnswer());
signaling.send({desc: pc.localDescription});
} else if (desc.type === 'answer') {
await pc.setRemoteDescription(desc);
} else {
console.log('Unsupported SDP type.');
}
} else if (candidate) {
await pc.addIceCandidate(candidate);
}
} catch (err) {
console.error(err);
}
};
RTCDataChannel
const localConnection = new RTCPeerConnection(servers);
const remoteConnection = new RTCPeerConnection(servers);
const sendChannel =
localConnection.createDataChannel('sendDataChannel');
// ...
remoteConnection.ondatachannel = (event) => {
receiveChannel = event.channel;
receiveChannel.onmessage = onReceiveMessage;
receiveChannel.onopen = onReceiveChannelStateChange;
receiveChannel.onclose = onReceiveChannelStateChange;
};
function onReceiveMessage(event) {
document.querySelector("textarea#send").value = event.data;
}
document.querySelector("button#send").onclick = () => {
var data = document.querySelector("textarea#send").value;
sendChannel.send(data);
};