Skip to content

Commit 96ded97

Browse files
authored
Merge pull request #1727 from fippo/always-nego
peerconnection: add alwaysNegotiateDataChannels sample
2 parents 81bd60e + c62dc1c commit 96ded97

File tree

4 files changed

+255
-0
lines changed

4 files changed

+255
-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: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
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 localVideo = document.getElementById('localVideo');
12+
const remoteVideo = document.getElementById('remoteVideo');
13+
const startButton = document.getElementById('startButton');
14+
const callButton = document.getElementById('callButton');
15+
const renegotiateButton = document.getElementById('renegotiateButton');
16+
const hangupButton = document.getElementById('hangupButton');
17+
const negotiateCheckbox = document.getElementById('negotiateDataChannel');
18+
startButton.onclick = start;
19+
callButton.onclick = call;
20+
renegotiateButton.onclick = renegotiate;
21+
hangupButton.onclick = hangup;
22+
23+
let localStream;
24+
let pc1;
25+
let pc2;
26+
27+
function log(text) {
28+
document.getElementById('log').innerText += text + '\n';
29+
}
30+
function getName(pc) {
31+
return (pc === pc1) ? 'pc1' : 'pc2';
32+
}
33+
34+
function getOtherPc(pc) {
35+
return (pc === pc1) ? pc2 : pc1;
36+
}
37+
38+
async function start() {
39+
startButton.disabled = true;
40+
negotiateCheckbox.disabled = true;
41+
const stream = await navigator.mediaDevices.getUserMedia({
42+
audio: false,
43+
video: true,
44+
});
45+
localVideo.srcObject = stream;
46+
localStream = stream;
47+
callButton.disabled = false;
48+
negotiateCheckbox.disabled = false;
49+
}
50+
51+
async function call() {
52+
callButton.disabled = true;
53+
negotiateCheckbox.disabled = true;
54+
renegotiateButton.disabled = false;
55+
hangupButton.disabled = false;
56+
console.log('Starting call');
57+
const audioTracks = localStream.getAudioTracks();
58+
if (audioTracks.length > 0) {
59+
console.log(`Using audio device: ${audioTracks[0].label}`);
60+
}
61+
const config = {
62+
alwaysNegotiateDataChannels: negotiateCheckbox.checked,
63+
};
64+
pc1 = new RTCPeerConnection(config);
65+
// Use pc.getConfiguration to detect whether the feature is supported.
66+
if (pc1.getConfiguration().alwaysNegotiateDataChannels === undefined) {
67+
log('RTCConfiguration.alwaysNegotiateDataChannel is not supported in this browser (' + adapter.browserDetails.browser + '/' + adapter.browserDetails.version + ')');
68+
} else {
69+
log('RTCConfiguration.alwaysNegotiateDataChannel is supported in this browser (' + adapter.browserDetails.browser + '/' + adapter.browserDetails.version + ')');
70+
}
71+
pc1.onicecandidate = e => onIceCandidate(pc1, e);
72+
pc2 = new RTCPeerConnection();
73+
pc2.onicecandidate = e => onIceCandidate(pc2, e);
74+
pc2.ontrack = gotRemoteStream;
75+
76+
// Create an audio transceiver which serves as transport.
77+
pc1.addTransceiver('audio');
78+
localStream.getTracks().forEach(track => pc1.addTrack(track, localStream));
79+
80+
await pc1.setLocalDescription();
81+
await pc2.setRemoteDescription(pc1.localDescription);
82+
await pc2.setLocalDescription();
83+
await pc1.setRemoteDescription(pc2.localDescription);
84+
renegotiateButton.disabled = false;
85+
}
86+
87+
function gotRemoteStream(e) {
88+
remoteVideo.srcObject = e.streams[0];
89+
}
90+
91+
function onIceCandidate(pc, event) {
92+
getOtherPc(pc)
93+
.addIceCandidate(event.candidate)
94+
.catch(err => onAddIceCandidateError(pc, err));
95+
}
96+
97+
function onAddIceCandidateError(pc, error) {
98+
console.log(`${getName(pc)} failed to add ICE Candidate: ${error.toString()}`);
99+
}
100+
101+
async function renegotiate() {
102+
renegotiateButton.disabled = true;
103+
hangupButton.disabled = true;
104+
pc1.getTransceivers()[0].stop(); // stops the first transceiver.
105+
if (negotiateCheckbox.checked) {
106+
log('Stopped transceiver, the right video should not freeze (if always negotiating data channels is supported)');
107+
} else {
108+
log('Stopped transceiver, the right video will freeze for ~4 seconds');
109+
}
110+
await pc1.setLocalDescription();
111+
await pc2.setRemoteDescription(pc1.localDescription);
112+
await new Promise(r => setTimeout(r, 4000)); // wait 4 seconds.
113+
console.log('renegotiated');
114+
await pc2.setLocalDescription();
115+
await pc1.setRemoteDescription(pc2.localDescription);
116+
await new Promise(r => setTimeout(r, 1000)); // wait 1 seconds.
117+
const stats = await pc2.getStats();
118+
const freezes = [...stats.values()].filter(s => s.type === 'inbound-rtp' && s.kind === 'video').map(s => s.totalFreezesDuration);
119+
log('Renegotiation done, the getStats() API detected a freeze lasting ' + freezes + ' seconds');
120+
log('Flip the "always negotiate data channels" box and try again');
121+
hangupButton.disabled = false;
122+
}
123+
124+
function hangup() {
125+
console.log('Ending call');
126+
pc1.close();
127+
pc2.close();
128+
pc1 = null;
129+
pc2 = null;
130+
131+
hangupButton.disabled = true;
132+
callButton.disabled = false;
133+
negotiateCheckbox.disabled = false;
134+
}

0 commit comments

Comments
 (0)