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> 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> </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> </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