1 <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> 2 3 <!-- 4 Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. 5 6 Use of this source code is governed by a BSD-style license 7 that can be found in the LICENSE file in the root of the source 8 tree. An additional intellectual property rights grant can be found 9 in the file PATENTS. All contributing project authors may 10 be 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"> 19 body, input, button, select, table { 20 font-family:"Lucida Grande", "Lucida Sans", Verdana, Arial, sans-serif; 21 font-size: 13 px; 22 } 23 body, input:enable, button:enable, select:enable, table { 24 color: rgb(51, 51, 51); 25 } 26 h1 {font-size: 40 px;} 27 </style> 28 29 <script type="text/javascript"> 30 31 // TODO: Catch more exceptions 32 33 var server; 34 var myId = -1; 35 var myName; 36 var remoteId = -1; 37 var remoteName; 38 var request = null; 39 var hangingGet = null; 40 var pc = null; 41 var localStream = null; 42 var disconnecting = false; 43 var callState = 0; // 0 - Not started, 1 - Call ongoing 44 45 46 // General 47 48 function toggleExtraButtons() { 49 document.getElementById("createPcBtn").hidden = 50 !document.getElementById("createPcBtn").hidden; 51 document.getElementById("test1Btn").hidden = 52 !document.getElementById("test1Btn").hidden; 53 } 54 55 function trace(txt) { 56 var elem = document.getElementById("debug"); 57 elem.innerHTML += txt + "<br>"; 58 } 59 60 function trace_warning(txt) { 61 var wtxt = "<b>" + txt + "</b>"; 62 trace(wtxt); 63 } 64 65 function trace_exception(e, txt) { 66 var etxt = "<b>" + txt + "</b> (" + e.name + " / " + e.message + ")"; 67 trace(etxt); 68 } 69 70 function setCallState(state) { 71 trace("Changing call state: " + callState + " -> " + state); 72 callState = state; 73 } 74 75 function 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 86 function 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 93 function 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 100 function 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 112 function 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 125 function 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 138 function 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 152 function clearPeerList() { 153 var peerList = document.getElementById("peers"); 154 while (peerList.length > 0) 155 peerList.remove(0); 156 } 157 158 function 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 173 function 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 188 function 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 207 function 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 222 function 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 234 function hangUp() { 235 document.getElementById("hangup").disabled = true; 236 trace("Sending BYE to " + remoteName + " (ID " + remoteId + ")"); 237 sendToPeer(remoteId, "BYE"); 238 closeCall(); 239 } 240 241 function 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 259 function 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 266 function 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 272 function 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 282 function 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 304 function 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 350 function 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 356 function 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 385 function 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 393 function 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 405 function 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 421 function 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 456 function 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 468 function 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 481 function 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 518 window.onload = getUserMedia; 519 window.onbeforeunload = disconnect; 520 521 522 </script> 523 </head> 524 525 <body> 526 <h1>WebRTC</h1> 527 You must have a WebRTC capable browser in order to make calls using this test 528 page.<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