WebRTC

Real-time Communication with WebRTC

Updated: 03 September 2023

From this CodeLab

Getting the Code

The code can be found here

Terminal window
1
git 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).
4
const mediaStreamConstraints = {
5
video: true,
6
}
7
8
// Video element where stream will be placed.
9
const localVideo = document.querySelector('video')
10
11
// Local stream that will be reproduced on the video.
12
let localStream
13
14
// Handles success by adding the MediaStream to the video element.
15
function gotLocalMediaStream(mediaStream) {
16
localStream = mediaStream
17
localVideo.srcObject = mediaStream
18
}
19
20
// Handles error by logging a message to the console with the error message.
21
function handleLocalMediaStreamError(error) {
22
console.log('navigator.getUserMedia error: ', error)
23
}
24
25
// Initializes media stream.
26
navigator.mediaDevices
27
.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:

1
const 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.
7
const mediaStreamConstraints = {
8
video: true,
9
}
10
11
// Set up to exchange only video.
12
const offerOptions = {
13
offerToReceiveVideo: 1,
14
}
15
16
// Define initial start time of the call (defined as connection between peers).
17
let startTime = null
18
19
// Define peer connections, streams and video elements.
20
const localVideo = document.getElementById('localVideo')
21
const remoteVideo = document.getElementById('remoteVideo')
22
23
let localStream
24
let remoteStream
25
26
let localPeerConnection
27
let remotePeerConnection
28
29
// Define MediaStreams callbacks.
30
31
// Sets the MediaStream as the video element src.
32
function gotLocalMediaStream(mediaStream) {
33
localVideo.srcObject = mediaStream
34
localStream = mediaStream
35
trace('Received local stream.')
36
callButton.disabled = false // Enable call button.
37
}
38
39
// Handles error by logging a message to the console.
40
function handleLocalMediaStreamError(error) {
41
trace(`navigator.getUserMedia error: ${error.toString()}.`)
42
}
43
44
// Handles remote MediaStream success by adding it as the remoteVideo src.
45
function gotRemoteMediaStream(event) {
46
const mediaStream = event.stream
47
remoteVideo.srcObject = mediaStream
48
remoteStream = mediaStream
49
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.
55
function logVideoLoaded(event) {
56
const video = event.target
57
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.
65
function logResizedVideo(event) {
66
logVideoLoaded(event)
67
68
if (startTime) {
69
const elapsedTime = window.performance.now() - startTime
70
startTime = null
71
trace(`Setup time: ${elapsedTime.toFixed(3)}ms.`)
72
}
73
}
74
75
localVideo.addEventListener('loadedmetadata', logVideoLoaded)
76
remoteVideo.addEventListener('loadedmetadata', logVideoLoaded)
77
remoteVideo.addEventListener('onresize', logResizedVideo)
78
79
// Define RTC peer connection behavior.
80
81
// Connects with new peer candidate.
82
function handleConnection(event) {
83
const peerConnection = event.target
84
const iceCandidate = event.candidate
85
86
if (iceCandidate) {
87
const newIceCandidate = new RTCIceCandidate(iceCandidate)
88
const otherPeer = getOtherPeer(peerConnection)
89
90
otherPeer
91
.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.
107
function handleConnectionSuccess(peerConnection) {
108
trace(`${getPeerName(peerConnection)} addIceCandidate success.`)
109
}
110
111
// Logs that the connection failed.
112
function 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.
120
function handleConnectionChange(event) {
121
const peerConnection = event.target
122
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.
130
function setSessionDescriptionError(error) {
131
trace(`Failed to create session description: ${error.toString()}.`)
132
}
133
134
// Logs success when setting session description.
135
function setDescriptionSuccess(peerConnection, functionName) {
136
const peerName = getPeerName(peerConnection)
137
trace(`${peerName} ${functionName} complete.`)
138
}
139
140
// Logs success when localDescription is set.
141
function setLocalDescriptionSuccess(peerConnection) {
142
setDescriptionSuccess(peerConnection, 'setLocalDescription')
143
}
144
145
// Logs success when remoteDescription is set.
146
function setRemoteDescriptionSuccess(peerConnection) {
147
setDescriptionSuccess(peerConnection, 'setRemoteDescription')
148
}
149
150
// Logs offer creation and sets peer connection session descriptions.
151
function createdOffer(description) {
152
trace(`Offer from localPeerConnection:\n${description.sdp}`)
153
154
trace('localPeerConnection setLocalDescription start.')
155
localPeerConnection
156
.setLocalDescription(description)
157
.then(() => {
158
setLocalDescriptionSuccess(localPeerConnection)
159
})
160
.catch(setSessionDescriptionError)
161
162
trace('remotePeerConnection setRemoteDescription start.')
163
remotePeerConnection
164
.setRemoteDescription(description)
165
.then(() => {
166
setRemoteDescriptionSuccess(remotePeerConnection)
167
})
168
.catch(setSessionDescriptionError)
169
170
trace('remotePeerConnection createAnswer start.')
171
remotePeerConnection
172
.createAnswer()
173
.then(createdAnswer)
174
.catch(setSessionDescriptionError)
175
}
176
177
// Logs answer to offer creation and sets peer connection session descriptions.
178
function createdAnswer(description) {
179
trace(`Answer from remotePeerConnection:\n${description.sdp}.`)
180
181
trace('remotePeerConnection setLocalDescription start.')
182
remotePeerConnection
183
.setLocalDescription(description)
184
.then(() => {
185
setLocalDescriptionSuccess(remotePeerConnection)
186
})
187
.catch(setSessionDescriptionError)
188
189
trace('localPeerConnection setRemoteDescription start.')
190
localPeerConnection
191
.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.
201
const startButton = document.getElementById('startButton')
202
const callButton = document.getElementById('callButton')
203
const hangupButton = document.getElementById('hangupButton')
204
205
// Set up initial action buttons status: disable call and hangup.
206
callButton.disabled = true
207
hangupButton.disabled = true
208
209
// Handles start button action: creates local MediaStream.
210
function startAction() {
211
startButton.disabled = true
212
navigator.mediaDevices
213
.getUserMedia(mediaStreamConstraints)
214
.then(gotLocalMediaStream)
215
.catch(handleLocalMediaStreamError)
216
trace('Requesting local stream.')
217
}
218
219
// Handles call button action: creates peer connection.
220
function callAction() {
221
callButton.disabled = true
222
hangupButton.disabled = false
223
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
handleConnectionChange
247
)
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
handleConnectionChange
256
)
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
localPeerConnection
265
.createOffer(offerOptions)
266
.then(createdOffer)
267
.catch(setSessionDescriptionError)
268
}
269
270
// Handles hangup action: ends up call, closes connections and resets peers.
271
function hangupAction() {
272
localPeerConnection.close()
273
remotePeerConnection.close()
274
localPeerConnection = null
275
remotePeerConnection = null
276
hangupButton.disabled = true
277
callButton.disabled = false
278
trace('Ending call.')
279
}
280
281
// Add click event handlers for buttons.
282
startButton.addEventListener('click', startAction)
283
callButton.addEventListener('click', callAction)
284
hangupButton.addEventListener('click', hangupAction)
285
286
// Define helper functions.
287
288
// Gets the "other" peer connection.
289
function getOtherPeer(peerConnection) {
290
return peerConnection === localPeerConnection
291
? remotePeerConnection
292
: localPeerConnection
293
}
294
295
// Gets the name of a certain peer connection.
296
function getPeerName(peerConnection) {
297
return peerConnection === localPeerConnection
298
? 'localPeerConnection'
299
: 'remotePeerConnection'
300
}
301
302
// Logs an action (text) and the time when it happened on the console.
303
function trace(text) {
304
text = text.trim()
305
const now = (window.performance.now() / 1000).toFixed(3)
306
307
console.log(now, text)
308
}