Import new tests from the CSS Text 3 WPT test suite
[WebKit-https.git] / ManualTests / webrtc-one-tab-p2p.html
1 <!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
13 self.RTCPeerConnection = self.RTCPeerConnection || self.webkitRTCPeerConnection || self.mozRTCPeerConnection;
14 navigator.getUserMedia = navigator.getUserMedia || navigator.webkitGetUserMedia || navigator.mozGetUserMedia;
15
16 let legacyCheckBox;
17 let closeButton;
18 let pcA;
19 let pcB;
20 let localStream;
21
22 const 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)
28 const configuration = { "iceServers": [{ "urls": "stun:mmt-stun.verkstad.net" }] };
29
30 document.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
92 function getTestFunction(name) {
93     const functionName = legacyCheckBox.checked ? name : `${name}Promise`;
94     return self[functionName];
95 }
96
97 function 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
134 function 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
172 function 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
205 function 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
238 function 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
249 function 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
261 function 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
280 function renderStream(stream, video) {
281     if (typeof video.srcObject !== "undefined")
282         video.srcObject = stream;
283     else
284         video.src = URL.createObjectURL(stream);
285 }
286
287 function 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
296 function 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
311 bidirectional media setup shows the same output in all four video elements. Open console to view
312 signaling details. Some browsers only allow access to user media via a secure origin (e.g.
313 localhost).</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>
321 Setup bidirectional media: <input type="button" id="single_but" value="Single SDP dialog" disabled>
322 <br>
323 Setup 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>