Skip to content

Commit 27dd99e

Browse files
committed
1 parent 81bd60e commit 27dd99e

File tree

4 files changed

+253
-0
lines changed

4 files changed

+253
-0
lines changed

index.html

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,8 @@ <h2 id="peerconnection"><a href="https://developer.mozilla.org/en-US/docs/Web/AP
136136

137137
<li><a href="src/content/peerconnection/channel/">Basic peer connection demo between two tabs</a></li>
138138

139+
<li><a href="src/content/peerconnection/always-negotiate-datachannels/">Always negotiate datachannels</a></li>
140+
139141
<li><a href="src/content/peerconnection/perfect-negotiation/">Peer connection using Perfect Negotiation</a></li>
140142

141143
<li><a href="src/content/peerconnection/audio/">Audio-only peer connection demo</a></li>
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
/*
2+
* Copyright (c) 2026 The WebRTC project authors. All Rights Reserved.
3+
*
4+
* Use of this source code is governed by a BSD-style license
5+
* that can be found in the LICENSE file in the root of the source
6+
* tree.
7+
*/
8+
button {
9+
margin: 0 20px 0 0;
10+
width: 90px;
11+
}
12+
13+
button#hangupButton {
14+
margin: 0;
15+
}
16+
17+
video {
18+
--width: 45%;
19+
width: var(--width);
20+
height: calc(var(--width) * 0.75);
21+
margin: 0 0 20px 0;
22+
vertical-align: top;
23+
}
24+
25+
video#localVideo {
26+
margin: 0 20px 20px 0;
27+
}
28+
29+
@media screen and (max-width: 400px) {
30+
button {
31+
width: 83px;
32+
margin: 0 11px 10px 0;
33+
}
34+
35+
video {
36+
height: 90px;
37+
margin: 0 0 10px 0;
38+
width: calc(50% - 7px);
39+
}
40+
video#localVideo {
41+
margin: 0 10px 20px 0;
42+
}
43+
44+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
<!DOCTYPE html>
2+
<!--
3+
* Copyright (c) 2026 The WebRTC project authors. All Rights Reserved.
4+
*
5+
* Use of this source code is governed by a BSD-style license
6+
* that can be found in the LICENSE file in the root of the source
7+
* tree.
8+
-->
9+
<html>
10+
<head>
11+
12+
<meta charset="utf-8">
13+
<meta name="description" content="WebRTC code samples">
14+
<meta name="viewport" content="width=device-width, user-scalable=yes, initial-scale=1, maximum-scale=1">
15+
<meta itemprop="description" content="Client-side WebRTC code samples">
16+
<meta itemprop="image" content="../../../images/webrtc-icon-192x192.png">
17+
<meta itemprop="name" content="WebRTC code samples">
18+
<meta name="mobile-web-app-capable" content="yes">
19+
<meta id="theme-color" name="theme-color" content="#ffffff">
20+
21+
<base target="_blank">
22+
23+
<title>Peer connection - Always negotiate datachannels</title>
24+
25+
<link rel="icon" sizes="192x192" href="../../../images/webrtc-icon-192x192.png">
26+
<link href="https://fonts.googleapis.com/css?family=Roboto:300,400,500,700" rel="stylesheet" type="text/css">
27+
<link rel="stylesheet" href="../../../css/main.css"/>
28+
<link rel="stylesheet" href="css/main.css"/>
29+
30+
</head>
31+
32+
<body>
33+
34+
<div id="container">
35+
<h1><a href="//webrtc.github.io/samples/" title="WebRTC samples homepage">WebRTC samples</a>
36+
<span>Peer connection</span></h1>
37+
38+
<video id="localVideo" playsinline autoplay muted></video>
39+
<video id="remoteVideo" playsinline autoplay></video>
40+
41+
<div>
42+
<button id="startButton">Start</button>
43+
<button id="callButton" disabled>Call</button>
44+
<button id="renegotiateButton" disabled>Renegotiate</button>
45+
<button id="hangupButton" disabled>Hang Up</button>
46+
<input type="checkbox" id="negotiateDataChannel"disabled><label for="negotiateDataChannel">Always negotiate datachannel</label>
47+
</div>
48+
49+
<p id="log">
50+
</p>
51+
<p>
52+
Data channels are not used in this example but negotiating them in the SDP (using the checkbox in supported browsers)
53+
avoids video freezes (on the right video) if the <a href="https://w3c.github.io/webrtc-extensions/#always-negotiating-datachannels" target=_blank>alwaysNegotiateDataChannels configuration option</a> is used.
54+
</p>
55+
56+
<p>View the console to see logging. The <code>MediaStream</code> object <code>localStream</code>, and the <code>RTCPeerConnection</code>
57+
objects <code>pc1</code> and <code>pc2</code> are in global scope, so you can inspect them in the console as
58+
well.</p>
59+
60+
<p>For more information about RTCPeerConnection, see <a href="http://www.html5rocks.com/en/tutorials/webrtc/basics/"
61+
title="HTML5 Rocks article about WebRTC by Sam Dutton">Getting
62+
Started With WebRTC</a>.</p>
63+
64+
65+
<a href="https://github.com/webrtc/samples/tree/gh-pages/src/content/peerconnection/always-negotiate-datachannels"
66+
title="View source for this page on GitHub" id="viewSource">View source on GitHub</a>
67+
68+
</div>
69+
70+
<script src="https://webrtc.github.io/adapter/adapter-latest.js"></script>
71+
<script src="js/main.js" async></script>
72+
73+
<script src="../../../js/lib/ga.js"></script>
74+
</body>
75+
</html>
Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
/*
2+
* Copyright (c) 2026 The WebRTC project authors. All Rights Reserved.
3+
*
4+
* Use of this source code is governed by a BSD-style license
5+
* that can be found in the LICENSE file in the root of the source
6+
* tree.
7+
*/
8+
9+
'use strict';
10+
11+
const startButton = document.getElementById('startButton');
12+
const callButton = document.getElementById('callButton');
13+
const renegotiateButton = document.getElementById('renegotiateButton');
14+
const hangupButton = document.getElementById('hangupButton');
15+
const negotiateCheckbox = document.getElementById('negotiateDataChannel');
16+
startButton.onclick = start;
17+
callButton.onclick = call;
18+
renegotiateButton.onclick = renegotiate;
19+
hangupButton.onclick = hangup;
20+
21+
let localStream;
22+
let pc1;
23+
let pc2;
24+
25+
function log(text) {
26+
document.getElementById('log').innerText += text + '\n';
27+
}
28+
function getName(pc) {
29+
return (pc === pc1) ? 'pc1' : 'pc2';
30+
}
31+
32+
function getOtherPc(pc) {
33+
return (pc === pc1) ? pc2 : pc1;
34+
}
35+
36+
async function start() {
37+
startButton.disabled = true;
38+
negotiateCheckbox.disabled = true;
39+
const stream = await navigator.mediaDevices.getUserMedia({
40+
audio: false,
41+
video: true,
42+
})
43+
localVideo.srcObject = stream;
44+
localStream = stream;
45+
callButton.disabled = false;
46+
negotiateCheckbox.disabled = false;
47+
}
48+
49+
async function call() {
50+
callButton.disabled = true;
51+
negotiateCheckbox.disabled = true;
52+
renegotiateButton.disabled = false;
53+
hangupButton.disabled = false;
54+
console.log('Starting call');
55+
const audioTracks = localStream.getAudioTracks();
56+
if (audioTracks.length > 0) {
57+
console.log(`Using audio device: ${audioTracks[0].label}`);
58+
}
59+
const config = {
60+
alwaysNegotiateDataChannels: negotiateCheckbox.checked,
61+
};
62+
pc1 = new RTCPeerConnection(config);
63+
// Use pc.getConfiguration to detect whether the feature is supported.
64+
if (pc1.getConfiguration().alwaysNegotiateDataChannels === undefined) {
65+
log('RTCConfiguration.alwaysNegotiateDataChannel is not supported in this browser (' + adapter.browserDetails.browser + '/' + adapter.browserDetails.version + ')');
66+
} else {
67+
log('RTCConfiguration.alwaysNegotiateDataChannel is supported in this browser (' + adapter.browserDetails.browser + '/' + adapter.browserDetails.version + ')');
68+
}
69+
pc1.onicecandidate = e => onIceCandidate(pc1, e);
70+
pc2 = new RTCPeerConnection();
71+
pc2.onicecandidate = e => onIceCandidate(pc2, e);
72+
pc2.ontrack = gotRemoteStream;
73+
74+
// Create an audio transceiver which serves as transport.
75+
pc1.addTransceiver('audio');
76+
localStream.getTracks().forEach(track => pc1.addTrack(track, localStream));
77+
78+
await pc1.setLocalDescription();
79+
await pc2.setRemoteDescription(pc1.localDescription);
80+
await pc2.setLocalDescription();
81+
await pc1.setRemoteDescription(pc2.localDescription);
82+
renegotiateButton.disabled = false;
83+
}
84+
85+
function gotRemoteStream(e) {
86+
remoteVideo.srcObject = e.streams[0];
87+
}
88+
89+
function onIceCandidate(pc, event) {
90+
getOtherPc(pc)
91+
.addIceCandidate(event.candidate)
92+
.catch(err => onAddIceCandidateError(pc, err));
93+
}
94+
95+
function onAddIceCandidateError(pc, error) {
96+
console.log(`${getName(pc)} failed to add ICE Candidate: ${error.toString()}`);
97+
}
98+
99+
async function renegotiate() {
100+
renegotiateButton.disabled = true;
101+
hangupButton.disabled = true;
102+
pc1.getTransceivers()[0].stop(); // stops the first transceiver.
103+
if (negotiateCheckbox.checked) {
104+
log('Stopped transceiver, the right video should not freeze (if always negotiating data channels is supported)');
105+
} else {
106+
log('Stopped transceiver, the right video will freeze for ~4 seconds');
107+
}
108+
await pc1.setLocalDescription();
109+
await pc2.setRemoteDescription(pc1.localDescription);
110+
await new Promise(r => setTimeout(r, 4000)); // wait 4 seconds.
111+
console.log('renegotiated');
112+
await pc2.setLocalDescription();
113+
await pc1.setRemoteDescription(pc2.localDescription);
114+
await new Promise(r => setTimeout(r, 1000)); // wait 1 seconds.
115+
const stats = await pc2.getStats();
116+
const freezes = [...stats.values()].filter(s => s.type === 'inbound-rtp' && s.kind === 'video').map(s => s.totalFreezesDuration);
117+
log('Renegotiation done, the getStats() API detected a freeze lasting ' + freezes + ' seconds');
118+
log('Flip the "always negotiate data channels" box and try again');
119+
hangupButton.disabled = false;
120+
}
121+
122+
function hangup() {
123+
console.log('Ending call');
124+
pc1.close();
125+
pc2.close();
126+
pc1 = null;
127+
pc2 = null;
128+
129+
hangupButton.disabled = true;
130+
callButton.disabled = false;
131+
negotiateCheckbox.disabled = false;
132+
}

0 commit comments

Comments
 (0)