WebRTC
Real-time Communication with WebRTC
Updated: 03 September 2023
Getting the Code
The code can be found here
1git clone https://github.com/googlecodelabs/webrtc-web
Running the Website
Run the application in the ‘work’ directory using an HTTP server
Stream video from webcam
In the index.html
file add a video
element as well as links to the relevant css
and js
files
1<!DOCTYPE html>2<html>3 <head>4 <title>Realtime communication with WebRTC</title>5
6 <link rel="stylesheet" href="css/main.css" />7 </head>8
9 <body>10 <h1>Realtime communication with WebRTC</h1>11
12 <video autoplay playsinline></video>13
14 <script src="js/main.js"></script>15 </body>16</html>
In the main.js
file add the following
1'use strict'2
3// On this codelab, you will be streaming only video (video: true).4const mediaStreamConstraints = {5 video: true,6}7
8// Video element where stream will be placed.9const localVideo = document.querySelector('video')10
11// Local stream that will be reproduced on the video.12let localStream13
14// Handles success by adding the MediaStream to the video element.15function gotLocalMediaStream(mediaStream) {16 localStream = mediaStream17 localVideo.srcObject = mediaStream18}19
20// Handles error by logging a message to the console with the error message.21function handleLocalMediaStreamError(error) {22 console.log('navigator.getUserMedia error: ', error)23}24
25// Initializes media stream.26navigator.mediaDevices27 .getUserMedia(mediaStreamConstraints)28 .then(gotLocalMediaStream)29 .catch(handleLocalMediaStreamError)
The getUserMedia
function requests access to the mediaStreamConstraints
object that it is given, and in turn (after requesting access from the user) will return a MediaStream object which can be used by a media object
Thereafter we use the gotLocalMediaStream
and handleLocalMediaStreamError
functions to set the video srcObject
or respond to errors respectively
The constraints
object can consist of different properties, such as:
1const hdConstraints = {2 video: {3 width: {4 min: 1280,5 },6 height: {7 min: 720,8 },9 },10}
More specific information on that can be found here
While we’re playing around with the video
element you can also simply flip the content on your webcam to mirror itself with the CSS property transform: rotateY(180deg)
and just generally play around with the filter
property
Also if you do not make use of autoplay
on the video you will only see a single frame
More examples of constraints here
Streaming Video with RTCPeerConnection
We will now update the application to consist of a local
and remote
video in which the page will connect to itself
Add a second video element as well as some buttons for controlling the content to the HTML. Also add the adapter.js
file which is a WebRTC shim for simpler compatability between browsers
index.html
1<!DOCTYPE html>2<html>3 <head>4 <title>Realtime communication with WebRTC</title>5 <link rel="stylesheet" href="css/main.css" />6 </head>7
8 <body>9 <h1>Realtime communication with WebRTC</h1>10
11 <video id="localVideo" autoplay playsinline></video>12 <video id="remoteVideo" autoplay playsinline></video>13
14 <div>15 <button id="startButton">Start</button>16 <button id="callButton">Call</button>17 <button id="hangupButton">Hang Up</button>18 </div>19
20 <script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>21 <script src="js/main.js"></script>22 </body>23</html>
main.js
1'use strict'2
3// Set up media stream constant and parameters.4
5// In this codelab, you will be streaming video only: "video: true".6// Audio will not be streamed because it is set to "audio: false" by default.7const mediaStreamConstraints = {8 video: true,9}10
11// Set up to exchange only video.12const offerOptions = {13 offerToReceiveVideo: 1,14}15
16// Define initial start time of the call (defined as connection between peers).17let startTime = null18
19// Define peer connections, streams and video elements.20const localVideo = document.getElementById('localVideo')21const remoteVideo = document.getElementById('remoteVideo')22
23let localStream24let remoteStream25
26let localPeerConnection27let remotePeerConnection28
29// Define MediaStreams callbacks.30
31// Sets the MediaStream as the video element src.32function gotLocalMediaStream(mediaStream) {33 localVideo.srcObject = mediaStream34 localStream = mediaStream35 trace('Received local stream.')36 callButton.disabled = false // Enable call button.37}38
39// Handles error by logging a message to the console.40function handleLocalMediaStreamError(error) {41 trace(`navigator.getUserMedia error: ${error.toString()}.`)42}43
44// Handles remote MediaStream success by adding it as the remoteVideo src.45function gotRemoteMediaStream(event) {46 const mediaStream = event.stream47 remoteVideo.srcObject = mediaStream48 remoteStream = mediaStream49 trace('Remote peer connection received remote stream.')50}51
52// Add behavior for video streams.53
54// Logs a message with the id and size of a video element.55function logVideoLoaded(event) {56 const video = event.target57 trace(58 `${video.id} videoWidth: ${video.videoWidth}px, ` +59 `videoHeight: ${video.videoHeight}px.`60 )61}62
63// Logs a message with the id and size of a video element.64// This event is fired when video begins streaming.65function logResizedVideo(event) {66 logVideoLoaded(event)67
68 if (startTime) {69 const elapsedTime = window.performance.now() - startTime70 startTime = null71 trace(`Setup time: ${elapsedTime.toFixed(3)}ms.`)72 }73}74
75localVideo.addEventListener('loadedmetadata', logVideoLoaded)76remoteVideo.addEventListener('loadedmetadata', logVideoLoaded)77remoteVideo.addEventListener('onresize', logResizedVideo)78
79// Define RTC peer connection behavior.80
81// Connects with new peer candidate.82function handleConnection(event) {83 const peerConnection = event.target84 const iceCandidate = event.candidate85
86 if (iceCandidate) {87 const newIceCandidate = new RTCIceCandidate(iceCandidate)88 const otherPeer = getOtherPeer(peerConnection)89
90 otherPeer91 .addIceCandidate(newIceCandidate)92 .then(() => {93 handleConnectionSuccess(peerConnection)94 })95 .catch((error) => {96 handleConnectionFailure(peerConnection, error)97 })98
99 trace(100 `${getPeerName(peerConnection)} ICE candidate:\n` +101 `${event.candidate.candidate}.`102 )103 }104}105
106// Logs that the connection succeeded.107function handleConnectionSuccess(peerConnection) {108 trace(`${getPeerName(peerConnection)} addIceCandidate success.`)109}110
111// Logs that the connection failed.112function handleConnectionFailure(peerConnection, error) {113 trace(114 `${getPeerName(peerConnection)} failed to add ICE Candidate:\n` +115 `${error.toString()}.`116 )117}118
119// Logs changes to the connection state.120function handleConnectionChange(event) {121 const peerConnection = event.target122 console.log('ICE state change event: ', event)123 trace(124 `${getPeerName(peerConnection)} ICE state: ` +125 `${peerConnection.iceConnectionState}.`126 )127}128
129// Logs error when setting session description fails.130function setSessionDescriptionError(error) {131 trace(`Failed to create session description: ${error.toString()}.`)132}133
134// Logs success when setting session description.135function setDescriptionSuccess(peerConnection, functionName) {136 const peerName = getPeerName(peerConnection)137 trace(`${peerName} ${functionName} complete.`)138}139
140// Logs success when localDescription is set.141function setLocalDescriptionSuccess(peerConnection) {142 setDescriptionSuccess(peerConnection, 'setLocalDescription')143}144
145// Logs success when remoteDescription is set.146function setRemoteDescriptionSuccess(peerConnection) {147 setDescriptionSuccess(peerConnection, 'setRemoteDescription')148}149
150// Logs offer creation and sets peer connection session descriptions.151function createdOffer(description) {152 trace(`Offer from localPeerConnection:\n${description.sdp}`)153
154 trace('localPeerConnection setLocalDescription start.')155 localPeerConnection156 .setLocalDescription(description)157 .then(() => {158 setLocalDescriptionSuccess(localPeerConnection)159 })160 .catch(setSessionDescriptionError)161
162 trace('remotePeerConnection setRemoteDescription start.')163 remotePeerConnection164 .setRemoteDescription(description)165 .then(() => {166 setRemoteDescriptionSuccess(remotePeerConnection)167 })168 .catch(setSessionDescriptionError)169
170 trace('remotePeerConnection createAnswer start.')171 remotePeerConnection172 .createAnswer()173 .then(createdAnswer)174 .catch(setSessionDescriptionError)175}176
177// Logs answer to offer creation and sets peer connection session descriptions.178function createdAnswer(description) {179 trace(`Answer from remotePeerConnection:\n${description.sdp}.`)180
181 trace('remotePeerConnection setLocalDescription start.')182 remotePeerConnection183 .setLocalDescription(description)184 .then(() => {185 setLocalDescriptionSuccess(remotePeerConnection)186 })187 .catch(setSessionDescriptionError)188
189 trace('localPeerConnection setRemoteDescription start.')190 localPeerConnection191 .setRemoteDescription(description)192 .then(() => {193 setRemoteDescriptionSuccess(localPeerConnection)194 })195 .catch(setSessionDescriptionError)196}197
198// Define and add behavior to buttons.199
200// Define action buttons.201const startButton = document.getElementById('startButton')202const callButton = document.getElementById('callButton')203const hangupButton = document.getElementById('hangupButton')204
205// Set up initial action buttons status: disable call and hangup.206callButton.disabled = true207hangupButton.disabled = true208
209// Handles start button action: creates local MediaStream.210function startAction() {211 startButton.disabled = true212 navigator.mediaDevices213 .getUserMedia(mediaStreamConstraints)214 .then(gotLocalMediaStream)215 .catch(handleLocalMediaStreamError)216 trace('Requesting local stream.')217}218
219// Handles call button action: creates peer connection.220function callAction() {221 callButton.disabled = true222 hangupButton.disabled = false223
224 trace('Starting call.')225 startTime = window.performance.now()226
227 // Get local media stream tracks.228 const videoTracks = localStream.getVideoTracks()229 const audioTracks = localStream.getAudioTracks()230 if (videoTracks.length > 0) {231 trace(`Using video device: ${videoTracks[0].label}.`)232 }233 if (audioTracks.length > 0) {234 trace(`Using audio device: ${audioTracks[0].label}.`)235 }236
237 const servers = null // Allows for RTC server configuration.238
239 // Create peer connections and add behavior.240 localPeerConnection = new RTCPeerConnection(servers)241 trace('Created local peer connection object localPeerConnection.')242
243 localPeerConnection.addEventListener('icecandidate', handleConnection)244 localPeerConnection.addEventListener(245 'iceconnectionstatechange',246 handleConnectionChange247 )248
249 remotePeerConnection = new RTCPeerConnection(servers)250 trace('Created remote peer connection object remotePeerConnection.')251
252 remotePeerConnection.addEventListener('icecandidate', handleConnection)253 remotePeerConnection.addEventListener(254 'iceconnectionstatechange',255 handleConnectionChange256 )257 remotePeerConnection.addEventListener('addstream', gotRemoteMediaStream)258
259 // Add local stream to connection and create offer to connect.260 localPeerConnection.addStream(localStream)261 trace('Added local stream to localPeerConnection.')262
263 trace('localPeerConnection createOffer start.')264 localPeerConnection265 .createOffer(offerOptions)266 .then(createdOffer)267 .catch(setSessionDescriptionError)268}269
270// Handles hangup action: ends up call, closes connections and resets peers.271function hangupAction() {272 localPeerConnection.close()273 remotePeerConnection.close()274 localPeerConnection = null275 remotePeerConnection = null276 hangupButton.disabled = true277 callButton.disabled = false278 trace('Ending call.')279}280
281// Add click event handlers for buttons.282startButton.addEventListener('click', startAction)283callButton.addEventListener('click', callAction)284hangupButton.addEventListener('click', hangupAction)285
286// Define helper functions.287
288// Gets the "other" peer connection.289function getOtherPeer(peerConnection) {290 return peerConnection === localPeerConnection291 ? remotePeerConnection292 : localPeerConnection293}294
295// Gets the name of a certain peer connection.296function getPeerName(peerConnection) {297 return peerConnection === localPeerConnection298 ? 'localPeerConnection'299 : 'remotePeerConnection'300}301
302// Logs an action (text) and the time when it happened on the console.303function trace(text) {304 text = text.trim()305 const now = (window.performance.now() / 1000).toFixed(3)306
307 console.log(now, text)308}