blob: c6f79fdc11b1b5789455ef4df296413024aeb875 [file] [log] [blame]
adam.bergkvist@ericsson.com2c199ed2016-10-21 10:20:23 +00001<!doctype html>
2<html>
3<head>
4<title>One tab p2p</title>
5
6<style type="text/css">
7 video { width: 240px; height: 160px; border: black 1px dashed; }
8 input { margin: 2px }
9</style>
10
11<script>
12// Make use of prefixed APIs to run this test in Chrome and Firefox
13self.RTCPeerConnection = self.RTCPeerConnection || self.webkitRTCPeerConnection || self.mozRTCPeerConnection;
14navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
15
16let legacyCheckBox;
17let closeButton;
18let pcA;
19let pcB;
20let localStream;
21
22const pcNames = {
23 first: "A",
24 second: "B"
25};
26
27// FIXME: We should be able to use an empty configuration (bug: http://webkit.org/b/158936)
28const configuration = { "iceServers": [{ "urls": "stun:mmt-stun.verkstad.net" }] };
29
30document.addEventListener("DOMContentLoaded", function () {
31 legacyCheckBox = document.querySelector("#legacy_check");
32 const audioCheckBox = document.querySelector("#audio_check");
33 const videoCheckBox = document.querySelector("#video_check");
34
35 const startButton = document.querySelector("#start_but");
36 closeButton = document.querySelector("#close_but");
37
38 const testButtons = {
39 "single": document.querySelector("#single_but"),
40 "mediaAtoB": document.querySelector("#media_A_to_B_but"),
41 "mediaBtoA": document.querySelector("#media_B_to_A_but")
42 };
43
44 function setTestButtonsDisabled(isDisabled) {
45 for (let p in testButtons)
46 testButtons[p].disabled = isDisabled;
47 }
48
49 startButton.onclick = function () {
50 navigator.getUserMedia({
51 "audio": audioCheckBox.checked,
52 "video": videoCheckBox.checked
53 }, function (stream) {
54 audioCheckBox.disabled = videoCheckBox.disabled = true;
55 localStream = stream;
56 startButton.disabled = true;
57 setTestButtonsDisabled(false);
58 }, logError);
59 };
60
61 closeButton.onclick = function (evt) {
62 evt.target.disabled = true;
63 console.log("Closing");
64 pcA.close();
65 pcB.close();
66 pcA = null;
67 pcB = null;
68
69 setTestButtonsDisabled(false);
70 }
71
72 testButtons.single.onclick = function (evt) {
73 setTestButtonsDisabled(true);
74 getTestFunction("singleDialog")();
75 }
76
77 testButtons.mediaAtoB.onclick = function (evt) {
78 setTestButtonsDisabled(true);
79 if (!pcA)
80 commonSetup();
81 getTestFunction("addOneWayMedia")(pcA, pcB, testButtons.mediaBtoA);
82 }
83
84 testButtons.mediaBtoA.onclick = function (evt) {
85 setTestButtonsDisabled(true);
86 if (!pcA)
87 commonSetup();
88 getTestFunction("addOneWayMedia")(pcB, pcA, testButtons.mediaAtoB);
89 }
90});
91
92function getTestFunction(name) {
93 const functionName = legacyCheckBox.checked ? name : `${name}Promise`;
94 return self[functionName];
95}
96
97function singleDialog() {
98 commonSetup();
99
100 renderStream(localStream, document.querySelector("#self_viewA"));
101 pcA.addStream(localStream);
102
103 pcA.createOffer(function (offer) {
104 pcA.setLocalDescription(offer, function () {
105 offerToB(pcA.localDescription);
106 }, logError);
107 }, logError);
108
109 function offerToB(offer) {
110 logSignalling(offer, pcA, pcB);
111 pcB.setRemoteDescription(offer, function () {
112 addStoredCandidates(pcB);
113 renderStream(localStream, document.querySelector("#self_viewB"));
114 pcB.addStream(localStream);
115
116 pcB.createAnswer(function (answer) {
117 pcB.setLocalDescription(answer, function () {
118 answerToA(pcB.localDescription);
119 }, logError);
120 }, logError);
121 }, logError);
122 }
123
124 function answerToA(answer) {
125 logSignalling(answer, pcB, pcA);
126 pcA.setRemoteDescription(answer, function () {
127 console.log("Initiator got answer, O/A dialog completed");
128 addStoredCandidates(pcA);
129 closeButton.disabled = false;
130 }, logError);
131 }
132}
133
134function singleDialogPromise() {
135 commonSetup();
136
137 renderStream(localStream, document.querySelector("#self_viewA"));
138 localStream.getTracks().forEach(track => {
139 pcA.addTrack(track, localStream);
140 });
141
142 pcA.createOffer().then(function (offer) {
143 return pcA.setLocalDescription(offer);
144 })
145 .then(function () {
146 logSignalling(pcA.localDescription, pcA, pcB);
147 return pcB.setRemoteDescription(pcA.localDescription);
148 })
149 .then(function () {
150 addStoredCandidates(pcB);
151 renderStream(localStream, document.querySelector("#self_viewB"));
152 localStream.getTracks().forEach(track => {
153 pcB.addTrack(track, localStream);
154 });
155 return pcB.createAnswer();
156 })
157 .then(function (answer) {
158 return pcB.setLocalDescription(answer);
159 })
160 .then(function () {
161 logSignalling(pcB.localDescription, pcB, pcA);
162 return pcA.setRemoteDescription(pcB.localDescription);
163 })
164 .then(function () {
165 addStoredCandidates(pcA);
166 console.log("Initiator got answer, O/A dialog completed");
167 closeButton.disabled = false;
168 })
169 .catch(logError);
170}
171
172function addOneWayMedia(offeringPc, answeringPc, continueButton) {
173 renderStream(localStream, document.querySelector(`#self_view${offeringPc.name}`));
174 offeringPc.addStream(localStream);
175
176 offeringPc.createOffer(function (offer) {
177 offeringPc.setLocalDescription(offer, function () {
178 offerToAnsweringPc(offeringPc.localDescription);
179 }, logError);
180 }, logError);
181
182 function offerToAnsweringPc(offer) {
183 logSignalling(offer, offeringPc, answeringPc);
184 answeringPc.setRemoteDescription(offer, function () {
185 addStoredCandidates(answeringPc);
186 answeringPc.createAnswer(function (answer) {
187 answeringPc.setLocalDescription(answer, function () {
188 answerToOfferingPc(answeringPc.localDescription);
189 }, logError);
190 }, logError);
191 }, logError);
192 }
193
194 function answerToOfferingPc(answer) {
195 logSignalling(answer, answeringPc, offeringPc);
196 offeringPc.setRemoteDescription(answer, function () {
197 console.log("Initiator side got answer, single way O/A dialog completed");
198 addStoredCandidates(offeringPc);
199 continueButton.disabled = false;
200 closeButton.disabled = false;
201 }, logError);
202 }
203}
204
205function addOneWayMediaPromise(offeringPc, answeringPc, continueButton) {
206 renderStream(localStream, document.querySelector(`#self_view${offeringPc.name}`));
207 localStream.getTracks().forEach(track => {
208 offeringPc.addTrack(track, localStream);
209 });
210
211 offeringPc.createOffer().then(function (offer) {
212 return offeringPc.setLocalDescription(offer);
213 })
214 .then(function () {
215 logSignalling(offeringPc.localDescription, offeringPc, answeringPc);
216 return answeringPc.setRemoteDescription(offeringPc.localDescription);
217 })
218 .then(function () {
219 addStoredCandidates(answeringPc);
220 return answeringPc.createAnswer();
221 })
222 .then(function (answer) {
223 return answeringPc.setLocalDescription(answer)
224 })
225 .then(function () {
226 logSignalling(answeringPc.localDescription, answeringPc, offeringPc);
227 return offeringPc.setRemoteDescription(answeringPc.localDescription)
228 })
229 .then(function () {
230 console.log("Initiator side got answer, single way O/A dialog completed");
231 addStoredCandidates(offeringPc);
232 continueButton.disabled = false;
233 closeButton.disabled = false;
234 })
235 .catch(logError);
236}
237
238function commonSetup() {
239 pcA = new RTCPeerConnection(configuration);
240 pcB = new RTCPeerConnection(configuration);
241
242 pcA.name = pcNames.first;
243 pcB.name = pcNames.second;
244
245 symetricSetup(pcA, pcB);
246 symetricSetup(pcB, pcA);
247}
248
249function addStoredCandidates(pc) {
250 if (!pc.storedCandidates)
251 return;
252
253 pc.storedCandidates.forEach(candidate => {
254 pc.addIceCandidate(candidate).catch(logError);
255 });
256
257 console.log(`Added ${pc.storedCandidates.length} stored candidates (arrived before remote description was set)`);
258 pc.storedCandidates = null;
259}
260
261function symetricSetup(pc, otherPc) {
262 pc.onicecandidate = function (evt) {
263 if (evt.candidate) {
264 logSignalling(evt.candidate, pc, otherPc);
265 // If the remote description isn't set yet, store the candidate
266 if (!otherPc.remoteDescription) {
267 if (!otherPc.storedCandidates)
268 otherPc.storedCandidates = [];
269 otherPc.storedCandidates.push(evt.candidate);
270 } else
271 otherPc.addIceCandidate(evt.candidate).catch(logError);
272 }
273 };
274
275 pc.onaddstream = function (evt) {
276 renderStream(evt.stream, document.querySelector(`#remote_view${pc.name}`));
277 };
278}
279
280function renderStream(stream, video) {
281 if (typeof video.srcObject !== "undefined")
282 video.srcObject = stream;
283 else
284 video.src = URL.createObjectURL(stream);
285}
286
287function logSignalling(msg, fromPc, toPc) {
288 const type = msg.candidate ? "Candidate" : msg.type.replace(/^[a-z]/, s => s.toUpperCase());
289 let header = `${type} `;
290 header += fromPc.name == pcNames.first ? `${fromPc.name} -> ${toPc.name}` : `${toPc.name} <- ${fromPc.name}`;
291 console.groupCollapsed(header);
292 console.log(msg.candidate || msg.sdp);
293 console.groupEnd();
294}
295
296function logError(error) {
297 if (error) {
298 if (error.name || error.message)
299 console.error(`logError: ${error.name || "-"}: ${error.message || "-"}`);
300 else
301 console.error(`logError: ${error}`);
302 } else
303 console.error("logError: (no error message)");
304}
305</script>
306
307</head>
308<body>
309<h3>One Tab P2P - Test Different Signaling Schemas</h3>
310<p>Click start to request user media. The same stream is sent in both directions so a successful
311bidirectional media setup shows the same output in all four video elements. Open console to view
312signaling details. Some browsers only allow access to user media via a secure origin (e.g.
313localhost).</p>
314<input type="checkbox" id="legacy_check">Use Legacy APIs (Chrome compatible)<br>
315<input type="checkbox" id="audio_check">Audio<br>
316<input type="checkbox" id="video_check" checked>Video<br>
317
318<input type="button" id="start_but" value="Start">
319<input type="button" id="close_but" value="Close Connections" disabled>
320<br>
321Setup bidirectional media: <input type="button" id="single_but" value="Single SDP dialog" disabled>
322<br>
323Setup media in one direction at a time: <input type="button" id="media_A_to_B_but" value="Media A to B" disabled>
324<input type="button" id="media_B_to_A_but" value="Media B to A" disabled>
325<br>
326
327<table>
328 <tr>
329 <td>Local (A)</td><td>Remote (A)</td>
330 </tr>
331 <tr>
332 <td><video id="self_viewA" autoplay muted></video></td>
333 <td><video id="remote_viewA" autoplay></video></td>
334 </tr>
335 <tr>
336 <td>Local (B)</td><td>Remote (B)</td>
337 </tr>
338 <tr>
339 <td><video id="self_viewB" autoplay muted></video></td>
340 <td><video id="remote_viewB" autoplay></video></td>
341 </tr>
342</table>
343</body>
344</html>