1<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN">
2
3<!--
4Copyright (c) 2011 The WebRTC project authors. All Rights Reserved.
5
6Use of this source code is governed by a BSD-style license
7that can be found in the LICENSE file in the root of the source
8tree. An additional intellectual property rights grant can be found
9in the file PATENTS. All contributing project authors may
10be found in the AUTHORS file in the root of the source tree.
11-->
12
13<html>
14
15<head>
16<title>WebRTC Test</title>
17
18<style type="text/css">
19body, input, button, select, table {
20  font-family:"Lucida Grande", "Lucida Sans", Verdana, Arial, sans-serif;
21  font-size: 13 px;
22}
23body, input:enable, button:enable, select:enable, table {
24  color: rgb(51, 51, 51);
25}
26h1 {font-size: 40 px;}
27</style>
28
29<script type="text/javascript">
30
31// TODO: Catch more exceptions
32
33var server;
34var myId = -1;
35var myName;
36var remoteId = -1;
37var remoteName;
38var request = null;
39var hangingGet = null;
40var pc = null;
41var localStream = null;
42var disconnecting = false;
43var callState = 0; // 0 - Not started, 1 - Call ongoing
44
45
46// General
47
48function toggleExtraButtons() {
49  document.getElementById("createPcBtn").hidden =
50    !document.getElementById("createPcBtn").hidden;
51  document.getElementById("test1Btn").hidden =
52    !document.getElementById("test1Btn").hidden;
53}
54
55function trace(txt) {
56  var elem = document.getElementById("debug");
57  elem.innerHTML += txt + "<br>";
58}
59
60function trace_warning(txt) {
61  var wtxt = "<b>" + txt + "</b>";
62  trace(wtxt);
63}
64
65function trace_exception(e, txt) {
66  var etxt = "<b>" + txt + "</b> (" + e.name + " / " + e.message + ")";
67  trace(etxt);
68}
69
70function setCallState(state) {
71  trace("Changing call state: " + callState + " -> " + state);
72  callState = state;
73}
74
75function checkPeerConnection() {
76  if (!pc) {
77    trace_warning("No PeerConnection object exists");
78    return 0;
79  }
80  return 1;
81}
82
83
84// Local stream generation
85
86function gotStream(s) {
87  var url = webkitURL.createObjectURL(s);
88  document.getElementById("localView").src = url;
89  trace("User has granted access to local media. url = " + url);
90  localStream = s;
91}
92
93function gotStreamFailed(error) {
94  alert("Failed to get access to local media. Error code was " + error.code +
95    ".");
96  trace_warning("Failed to get access to local media. Error code was " +
97    error.code);
98}
99
100function getUserMedia() {
101  try {
102    navigator.webkitGetUserMedia("video,audio", gotStream, gotStreamFailed);
103    trace("Requested access to local media");
104  } catch (e) {
105    trace_exception(e, "getUserMedia error");
106  }
107}
108
109
110// Peer list and remote peer handling
111
112function peerExists(id) {
113  try {
114    var peerList = document.getElementById("peers");
115    for (var i = 0; i < peerList.length; i++) {
116      if (parseInt(peerList.options[i].value) == id)
117        return true;
118    }
119  } catch (e) {
120    trace_exception(e, "Error searching for peer");
121  }
122  return false;
123}
124
125function addPeer(id, pname) {
126  var peerList = document.getElementById("peers");
127  var option = document.createElement("option");
128  option.text = pname;
129  option.value = id;
130  try {
131    // For IE earlier than version 8
132    peerList.add(option, x.options[null]);
133  } catch (e) {
134    peerList.add(option, null);
135  }
136}
137
138function removePeer(id) {
139  try {
140    var peerList = document.getElementById("peers");
141    for (var i = 0; i < peerList.length; i++) {
142      if (parseInt(peerList.options[i].value) == id) {
143        peerList.remove(i);
144        break;
145      }
146    }
147  } catch (e) {
148    trace_exception(e, "Error removing peer");
149  }
150}
151
152function clearPeerList() {
153  var peerList = document.getElementById("peers");
154  while (peerList.length > 0)
155    peerList.remove(0);
156}
157
158function setSelectedPeer(id) {
159  try {
160    var peerList = document.getElementById("peers");
161    for (var i = 0; i < peerList.length; i++) {
162      if (parseInt(peerList.options[i].value) == id) {
163        peerList.options[i].selected = true;
164        return true;
165      }
166    }
167  } catch (e) {
168    trace_exception(e, "Error setting selected peer");
169  }
170  return false;
171}
172
173function getPeerName(id) {
174  try {
175    var peerList = document.getElementById("peers");
176    for (var i = 0; i < peerList.length; i++) {
177      if (parseInt(peerList.options[i].value) == id) {
178        return peerList.options[i].text;
179      }
180    }
181  } catch (e) {
182    trace_exception(e, "Error finding peer name");
183    return;
184  }
185  return;
186}
187
188function storeRemoteInfo() {
189  try {
190    var peerList = document.getElementById("peers");
191    if (peerList.selectedIndex < 0) {
192      alert("Please select a peer.");
193      return false;
194    } else
195      remoteId = parseInt(peerList.options[peerList.selectedIndex].value);
196      remoteName = peerList.options[peerList.selectedIndex].text;
197  } catch (e) {
198    trace_exception(e, "Error storing remote peer info");
199    return false;
200  }
201  return true;
202}
203
204
205// Call control
206
207function createPeerConnection() {
208  if (pc) {
209    trace_warning("PeerConnection object already exists");
210  }
211  trace("Creating PeerConnection object");
212  try {
213    pc = new webkitPeerConnection("STUN stun.l.google.com:19302",
214      onSignalingMessage);
215  pc.onaddstream = onAddStream;
216  pc.onremovestream = onRemoveStream;
217  } catch (e) {
218    trace_exception(e, "Create PeerConnection error");
219  }
220}
221
222function doCall() {
223  if (!storeRemoteInfo())
224    return;
225  document.getElementById("call").disabled = true;
226  document.getElementById("peers").disabled = true;
227  createPeerConnection();
228  trace("Adding stream");
229  pc.addStream(localStream);
230  document.getElementById("hangup").disabled = false;
231  setCallState(1);
232}
233
234function hangUp() {
235  document.getElementById("hangup").disabled = true;
236  trace("Sending BYE to " + remoteName + " (ID " + remoteId + ")");
237  sendToPeer(remoteId, "BYE");
238  closeCall();
239}
240
241function closeCall() {
242  trace("Stopping showing remote stream");
243  document.getElementById("remoteView").src = "dummy";
244  if (pc) {
245    trace("Stopping call [pc.close()]");
246    pc.close();
247    pc = null;
248  } else
249    trace("No pc object to close");
250  remoteId = -1;
251  document.getElementById("call").disabled = false;
252  document.getElementById("peers").disabled = false;
253  setCallState(0);
254}
255
256
257// PeerConnection callbacks
258
259function onAddStream(e) {
260  var stream = e.stream;
261  var url = webkitURL.createObjectURL(stream);
262  document.getElementById("remoteView").src = url;
263  trace("Started showing remote stream. url = " + url);
264}
265
266function onRemoveStream(e) {
267  // Currently if we get this callback, call has ended.
268  document.getElementById("remoteView").src = "";
269  trace("Stopped showing remote stream");
270}
271
272function onSignalingMessage(msg) {
273  trace("Sending message to " + remoteName + " (ID " + remoteId + "):\n" + msg);
274  sendToPeer(remoteId, msg);
275}
276
277// TODO: Add callbacks onconnecting, onopen and onstatechange.
278
279
280// Server interaction
281
282function handleServerNotification(data) {
283  trace("Server notification: " + data);
284  var parsed = data.split(",");
285  if (parseInt(parsed[2]) == 1) { // New peer
286    var peerId = parseInt(parsed[1]);
287    if (!peerExists(peerId)) {
288      var peerList = document.getElementById("peers");
289      if (peerList.length == 1 && peerList.options[0].value == -1)
290        clearPeerList();
291      addPeer(peerId, parsed[0]);
292      document.getElementById("peers").disabled = false;
293      document.getElementById("call").disabled = false;
294    }
295  } else if (parseInt(parsed[2]) == 0) { // Removed peer
296    removePeer(parseInt(parsed[1]));
297    if (document.getElementById("peers").length == 0) {
298      document.getElementById("peers").disabled = true;
299      addPeer(-1, "No other peer connected");
300    }
301  }
302}
303
304function handlePeerMessage(peer_id, msg) {
305  var peerName = getPeerName(peer_id);
306  if (peerName == undefined) {
307    trace_warning("Received message from unknown peer (ID " + peer_id +
308      "), ignoring message:");
309    trace(msg);
310    return;
311  }
312  trace("Received message from " + peerName + " (ID " + peer_id + "):\n" + msg);
313  // Assuming we receive the message from the peer we want to communicate with.
314  // TODO: Only accept messages from peer we communicate with with if call is
315  // ongoing.
316  if (msg.search("BYE") == 0) {
317    // Other side has hung up.
318    document.getElementById("hangup").disabled = true;
319    closeCall()
320  } else {
321    if (!pc) {
322      // Other side is calling us, startup
323      if (!setSelectedPeer(peer_id)) {
324        trace_warning("Recevied message from unknown peer, ignoring");
325        return;
326      }
327      if (!storeRemoteInfo())
328        return;
329      document.getElementById("call").disabled = true;
330      document.getElementById("peers").disabled = true;
331      createPeerConnection();
332      try {
333        pc.processSignalingMessage(msg);
334      } catch (e) {
335        trace_exception(e, "Process signaling message error");
336      }
337      trace("Adding stream");
338      pc.addStream(localStream);
339      document.getElementById("hangup").disabled = false;
340    } else {
341      try {
342        pc.processSignalingMessage(msg);
343      } catch (e) {
344        trace_exception(e, "Process signaling message error");
345      }
346    }
347  }
348}
349
350function getIntHeader(r, name) {
351  var val = r.getResponseHeader(name);
352  trace("header value: " + val);
353  return val != null && val.length ? parseInt(val) : -1;
354}
355
356function hangingGetCallback() {
357  try {
358    if (hangingGet.readyState != 4 || disconnecting)
359      return;
360    if (hangingGet.status != 200) {
361      trace_warning("server error, status: " + hangingGet.status + ", text: " +
362        hangingGet.statusText);
363      disconnect();
364    } else {
365      var peer_id = getIntHeader(hangingGet, "Pragma");
366      if (peer_id == myId) {
367        handleServerNotification(hangingGet.responseText);
368      } else {
369        handlePeerMessage(peer_id, hangingGet.responseText);
370      }
371    }
372
373    if (hangingGet) {
374      hangingGet.abort();
375      hangingGet = null;
376    }
377
378    if (myId != -1)
379      window.setTimeout(startHangingGet, 0);
380  } catch (e) {
381    trace_exception(e, "Hanging get error");
382  }
383}
384
385function onHangingGetTimeout() {
386  trace("hanging get timeout. issuing again");
387  hangingGet.abort();
388  hangingGet = null;
389  if (myId != -1)
390    window.setTimeout(startHangingGet, 0);
391}
392
393function startHangingGet() {
394  try {
395    hangingGet = new XMLHttpRequest();
396    hangingGet.onreadystatechange = hangingGetCallback;
397    hangingGet.ontimeout = onHangingGetTimeout;
398    hangingGet.open("GET", server + "/wait?peer_id=" + myId, true);
399    hangingGet.send();  
400  } catch (e) {
401    trace_exception(e, "Start hanging get error");
402  }
403}
404
405function sendToPeer(peer_id, data) {
406  if (myId == -1) {
407    alert("Not connected.");
408    return;
409  }
410  if (peer_id == myId) {
411    alert("Can't send a message to oneself.");
412    return;
413  }
414  var r = new XMLHttpRequest();
415  r.open("POST", server + "/message?peer_id=" + myId + "&to=" + peer_id, false);
416  r.setRequestHeader("Content-Type", "text/plain");
417  r.send(data);
418  r = null;
419}
420
421function signInCallback() {
422  try {
423    if (request.readyState == 4) {
424      if (request.status == 200) {
425        var peers = request.responseText.split("\n");
426        myId = parseInt(peers[0].split(",")[1]);
427        trace("My id: " + myId);
428        clearPeerList();
429        var added = 0;
430        for (var i = 1; i < peers.length; ++i) {
431          if (peers[i].length > 0) {
432            trace("Peer " + i + ": " + peers[i]);
433            var parsed = peers[i].split(",");
434            addPeer(parseInt(parsed[1]), parsed[0]);
435            ++added;
436          }
437        }
438        if (added == 0)
439          addPeer(-1, "No other peer connected");
440        else {
441          document.getElementById("peers").disabled = false;
442          document.getElementById("call").disabled = false;
443        }
444        startHangingGet();
445        request = null;
446        document.getElementById("connect").disabled = true;
447        document.getElementById("disconnect").disabled = false;
448      }
449    }
450  } catch (e) {
451    trace_exception(e, "Sign in error");
452    document.getElementById("connect").disabled = false;
453  }
454}
455
456function signIn() {
457  try {
458    request = new XMLHttpRequest();
459    request.onreadystatechange = signInCallback;
460    request.open("GET", server + "/sign_in?" + myName, true);
461    request.send();
462  } catch (e) {
463    trace_exception(e, "Start sign in error");
464    document.getElementById("connect").disabled = false;
465  }
466}
467
468function connect() {
469  myName = document.getElementById("local").value.toLowerCase();
470  server = document.getElementById("server").value.toLowerCase();
471  if (myName.length == 0) {
472    alert("I need a name please.");
473    document.getElementById("local").focus();
474  } else {
475    // TODO: Disable connect button here, but we need a timeout and check if we
476    // have connected, if so enable it again.
477    signIn();
478  }
479}
480
481function disconnect() {
482  if (callState == 1)
483    hangUp();
484
485  disconnecting = true;
486  
487  if (request) {
488    request.abort();
489    request = null;
490  }
491
492  if (hangingGet) {
493    hangingGet.abort();
494    hangingGet = null;
495  }
496
497  if (myId != -1) {
498    request = new XMLHttpRequest();
499    request.open("GET", server + "/sign_out?peer_id=" + myId, false);
500    request.send();
501    request = null;
502    myId = -1;
503  }
504
505  clearPeerList();
506  addPeer(-1, "Not connected");
507  document.getElementById("connect").disabled = false;
508  document.getElementById("disconnect").disabled = true;
509  document.getElementById("peers").disabled = true;
510  document.getElementById("call").disabled = true;
511
512  disconnecting = false;
513}
514
515
516// Window event handling
517
518window.onload = getUserMedia;
519window.onbeforeunload = disconnect;
520
521
522</script>
523</head>
524
525<body>
526<h1>WebRTC</h1>
527You must have a WebRTC capable browser in order to make calls using this test
528page.<br>&nbsp;
529
530<table border="0">
531<tr>
532 <td>Local Preview</td>
533 <td>Remote Video</td>
534</tr>
535<tr>
536 <td>
537  <video width="320" height="240" id="localView" autoplay="autoplay"></video>
538 </td>
539 <td>
540  <video width="640" height="480" id="remoteView" autoplay="autoplay"></video>
541 </td>
542</tr>
543</table>
544
545<table border="0">
546<tr>
547 <td valign="top">
548  <table border="0" cellpaddning="0" cellspacing="0">
549  <tr>
550   <td>Server:</td>
551   <td>
552    <input type="text" id="server" size="30" value="http://localhost:8888"/>
553   </td>
554  </tr>
555  <tr>
556   <td>Name:</td><td><input type="text" id="local" size="30" value="name"/></td>
557  </tr>
558  </table>
559 </td>
560 <td valign="top">
561  <button id="connect" onclick="connect();">Connect</button><br>
562  <button id="disconnect" onclick="disconnect();" disabled="true">Disconnect
563  </button>
564 </td>
565 <td>&nbsp;&nbsp;&nbsp;</td>
566 <td valign="top">
567  Connected peers:<br>
568  <select id="peers" size="5" disabled="true">
569   <option value="-1">Not connected</option>
570  </select>
571  </td>
572 <td valign="top">
573  <!--input type="text" id="peer_id" size="3" value="1"/><br-->
574  <button id="call" onclick="doCall();" disabled="true">Call</button><br>
575  <button id="hangup" onclick="hangUp();" disabled="true">Hang up</button><br>
576 </td>
577 <td>&nbsp;&nbsp;&nbsp;</td>
578 <td valign="top">
579  <button onclick="toggleExtraButtons();">Toggle extra buttons (debug)</button>
580  <br>
581  <button id="createPcBtn" onclick="createPeerConnection();" hidden="true">
582  Create peer connection</button>
583 </td>
584</tr>
585</table>
586
587<button onclick="document.getElementById('debug').innerHTML='';">Clear log
588</button>
589<pre id="debug"></pre>
590
591</body>
592
593</html>
594
595