WebRTC: Difference between revisions
Created page with "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..." |
|||
(9 intermediate revisions by the same user not shown) | |||
Line 1: | Line 1: | ||
WebRTC (Web Real-Time Communication) is a standard for peer-to-peer real-time communication. | [https://webrtc.org/ 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. | 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. | |||
==Native APIs (C++)=== | ===MediaStream=== | ||
The purpose of MediaStream to to access the user's camera, microphone, or screen. | |||
This is done using [https://developer.mozilla.org/en-US/docs/Web/API/MediaDevices/getUserMedia 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: | |||
<syntaxhighlight lang="javascript"> | |||
const configuration = {iceServers: [{urls: 'stun.l.google.com:19302'}]}; | |||
const pc = new RTCPeerConnection(configuration); | |||
</syntaxhighlight> | |||
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 [https://gist.github.com/mondain/b0ec1cf5f60ae726202e 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: | |||
<syntaxhighlight lang="javascript"> | |||
const configuration = {iceServers: [{urls: 'stuns:stun.example.org'}]}; | |||
</syntaxhighlight> | |||
The other client will add these as follows: | |||
<syntaxhighlight lang="javascript"> | |||
remoteConnection.addIceCandidate(e.candidate) | |||
</syntaxhighlight> | |||
Next the local and remote clients exchange descriptions: | |||
<syntaxhighlight lang="javascript"> | |||
const localOffer = await localConnection.createOffer(); | |||
localConnection.setLocalDescription(localOffer); | |||
signallingChannel.send(localConnection.localDescription) | |||
# On the remote | |||
const localOffer = signalingChannel.receiveAnswer(); // Actually a callback irl | |||
remoteConnection.setRemoteDescription(localOffer); | |||
const answer = await remoteConnection.createAnswer(); | |||
remoteConnection.setLocalDescription(answer); | |||
signallingChannel.send(remoteConnection.localDescription) | |||
# On the local | |||
const remoteDescription = signalingChannel.receiveAnswer(); | |||
localConnection.setRemoteDescription(remoteDescription); | |||
</syntaxhighlight> | |||
* Note that both clients can continue to update descriptions as the network updates. | |||
*: As long as the signalling is setup correctly, you don't need to worry about what RTCPeerConnection is doing under the hood. | |||
{{ hidden | RTCPeerConnection Example | | |||
Below is from [https://www.html5rocks.com/en/tutorials/webrtc/basics/ HTML5Rocks]. | |||
<syntaxhighlight lang="javascript"> | |||
// 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); | |||
} | |||
}; | |||
</syntaxhighlight> | |||
}} | |||
===RTCDataChannel=== | |||
<syntaxhighlight lang="javascript"> | |||
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); | |||
}; | |||
</syntaxhighlight> | |||
==JavaScript API== | |||
===Multiple Clients=== | |||
To handle multiple clients, you simply have one RTCPeerConnection per client. | |||
==Native APIs (C++)== | |||
==Resources== | |||
* [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] | |||
* [https://news.ycombinator.com/item?id=25933016 HackerNews: WebRTC is now a W3C and IETF standard] | |||
==References== |