<!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> <!-- Copyright (c) 2011 The WebRTC project authors. All Rights Reserved. Use of this source code is governed by a BSD-style license that can be found in the LICENSE file in the root of the source tree. An additional intellectual property rights grant can be found in the file PATENTS. All contributing project authors may be found in the AUTHORS file in the root of the source tree. --> <html> <head> <title>WebRTC Test</title> <style type="text/css"> body, input, button, select, table { font-family:"Lucida Grande", "Lucida Sans", Verdana, Arial, sans-serif; font-size: 13 px; } body, input:enable, button:enable, select:enable, table { color: rgb(51, 51, 51); } h1 {font-size: 40 px;} </style> <script type="text/javascript"> // TODO: Catch more exceptions var server; var myId = -1; var myName; var remoteId = -1; var remoteName; var request = null; var hangingGet = null; var pc = null; var localStream = null; var disconnecting = false; var callState = 0; // 0 - Not started, 1 - Call ongoing // General function toggleExtraButtons() { document.getElementById("createPcBtn").hidden = !document.getElementById("createPcBtn").hidden; document.getElementById("test1Btn").hidden = !document.getElementById("test1Btn").hidden; } function trace(txt) { var elem = document.getElementById("debug"); elem.innerHTML += txt + "<br>"; } function trace_warning(txt) { var wtxt = "<b>" + txt + "</b>"; trace(wtxt); } function trace_exception(e, txt) { var etxt = "<b>" + txt + "</b> (" + e.name + " / " + e.message + ")"; trace(etxt); } function setCallState(state) { trace("Changing call state: " + callState + " -> " + state); callState = state; } function checkPeerConnection() { if (!pc) { trace_warning("No PeerConnection object exists"); return 0; } return 1; } // Local stream generation function gotStream(s) { var url = webkitURL.createObjectURL(s); document.getElementById("localView").src = url; trace("User has granted access to local media. url = " + url); localStream = s; } function gotStreamFailed(error) { alert("Failed to get access to local media. Error code was " + error.code + "."); trace_warning("Failed to get access to local media. Error code was " + error.code); } function getUserMedia() { try { navigator.webkitGetUserMedia("video,audio", gotStream, gotStreamFailed); trace("Requested access to local media"); } catch (e) { trace_exception(e, "getUserMedia error"); } } // Peer list and remote peer handling function peerExists(id) { try { var peerList = document.getElementById("peers"); for (var i = 0; i < peerList.length; i++) { if (parseInt(peerList.options[i].value) == id) return true; } } catch (e) { trace_exception(e, "Error searching for peer"); } return false; } function addPeer(id, pname) { var peerList = document.getElementById("peers"); var option = document.createElement("option"); option.text = pname; option.value = id; try { // For IE earlier than version 8 peerList.add(option, x.options[null]); } catch (e) { peerList.add(option, null); } } function removePeer(id) { try { var peerList = document.getElementById("peers"); for (var i = 0; i < peerList.length; i++) { if (parseInt(peerList.options[i].value) == id) { peerList.remove(i); break; } } } catch (e) { trace_exception(e, "Error removing peer"); } } function clearPeerList() { var peerList = document.getElementById("peers"); while (peerList.length > 0) peerList.remove(0); } function setSelectedPeer(id) { try { var peerList = document.getElementById("peers"); for (var i = 0; i < peerList.length; i++) { if (parseInt(peerList.options[i].value) == id) { peerList.options[i].selected = true; return true; } } } catch (e) { trace_exception(e, "Error setting selected peer"); } return false; } function getPeerName(id) { try { var peerList = document.getElementById("peers"); for (var i = 0; i < peerList.length; i++) { if (parseInt(peerList.options[i].value) == id) { return peerList.options[i].text; } } } catch (e) { trace_exception(e, "Error finding peer name"); return; } return; } function storeRemoteInfo() { try { var peerList = document.getElementById("peers"); if (peerList.selectedIndex < 0) { alert("Please select a peer."); return false; } else remoteId = parseInt(peerList.options[peerList.selectedIndex].value); remoteName = peerList.options[peerList.selectedIndex].text; } catch (e) { trace_exception(e, "Error storing remote peer info"); return false; } return true; } // Call control function createPeerConnection() { if (pc) { trace_warning("PeerConnection object already exists"); } trace("Creating PeerConnection object"); try { pc = new webkitPeerConnection("STUN stun.l.google.com:19302", onSignalingMessage); pc.onaddstream = onAddStream; pc.onremovestream = onRemoveStream; } catch (e) { trace_exception(e, "Create PeerConnection error"); } } function doCall() { if (!storeRemoteInfo()) return; document.getElementById("call").disabled = true; document.getElementById("peers").disabled = true; createPeerConnection(); trace("Adding stream"); pc.addStream(localStream); document.getElementById("hangup").disabled = false; setCallState(1); } function hangUp() { document.getElementById("hangup").disabled = true; trace("Sending BYE to " + remoteName + " (ID " + remoteId + ")"); sendToPeer(remoteId, "BYE"); closeCall(); } function closeCall() { trace("Stopping showing remote stream"); document.getElementById("remoteView").src = "dummy"; if (pc) { trace("Stopping call [pc.close()]"); pc.close(); pc = null; } else trace("No pc object to close"); remoteId = -1; document.getElementById("call").disabled = false; document.getElementById("peers").disabled = false; setCallState(0); } // PeerConnection callbacks function onAddStream(e) { var stream = e.stream; var url = webkitURL.createObjectURL(stream); document.getElementById("remoteView").src = url; trace("Started showing remote stream. url = " + url); } function onRemoveStream(e) { // Currently if we get this callback, call has ended. document.getElementById("remoteView").src = ""; trace("Stopped showing remote stream"); } function onSignalingMessage(msg) { trace("Sending message to " + remoteName + " (ID " + remoteId + "):\n" + msg); sendToPeer(remoteId, msg); } // TODO: Add callbacks onconnecting, onopen and onstatechange. // Server interaction function handleServerNotification(data) { trace("Server notification: " + data); var parsed = data.split(","); if (parseInt(parsed[2]) == 1) { // New peer var peerId = parseInt(parsed[1]); if (!peerExists(peerId)) { var peerList = document.getElementById("peers"); if (peerList.length == 1 && peerList.options[0].value == -1) clearPeerList(); addPeer(peerId, parsed[0]); document.getElementById("peers").disabled = false; document.getElementById("call").disabled = false; } } else if (parseInt(parsed[2]) == 0) { // Removed peer removePeer(parseInt(parsed[1])); if (document.getElementById("peers").length == 0) { document.getElementById("peers").disabled = true; addPeer(-1, "No other peer connected"); } } } function handlePeerMessage(peer_id, msg) { var peerName = getPeerName(peer_id); if (peerName == undefined) { trace_warning("Received message from unknown peer (ID " + peer_id + "), ignoring message:"); trace(msg); return; } trace("Received message from " + peerName + " (ID " + peer_id + "):\n" + msg); // Assuming we receive the message from the peer we want to communicate with. // TODO: Only accept messages from peer we communicate with with if call is // ongoing. if (msg.search("BYE") == 0) { // Other side has hung up. document.getElementById("hangup").disabled = true; closeCall() } else { if (!pc) { // Other side is calling us, startup if (!setSelectedPeer(peer_id)) { trace_warning("Recevied message from unknown peer, ignoring"); return; } if (!storeRemoteInfo()) return; document.getElementById("call").disabled = true; document.getElementById("peers").disabled = true; createPeerConnection(); try { pc.processSignalingMessage(msg); } catch (e) { trace_exception(e, "Process signaling message error"); } trace("Adding stream"); pc.addStream(localStream); document.getElementById("hangup").disabled = false; } else { try { pc.processSignalingMessage(msg); } catch (e) { trace_exception(e, "Process signaling message error"); } } } } function getIntHeader(r, name) { var val = r.getResponseHeader(name); trace("header value: " + val); return val != null && val.length ? parseInt(val) : -1; } function hangingGetCallback() { try { if (hangingGet.readyState != 4 || disconnecting) return; if (hangingGet.status != 200) { trace_warning("server error, status: " + hangingGet.status + ", text: " + hangingGet.statusText); disconnect(); } else { var peer_id = getIntHeader(hangingGet, "Pragma"); if (peer_id == myId) { handleServerNotification(hangingGet.responseText); } else { handlePeerMessage(peer_id, hangingGet.responseText); } } if (hangingGet) { hangingGet.abort(); hangingGet = null; } if (myId != -1) window.setTimeout(startHangingGet, 0); } catch (e) { trace_exception(e, "Hanging get error"); } } function onHangingGetTimeout() { trace("hanging get timeout. issuing again"); hangingGet.abort(); hangingGet = null; if (myId != -1) window.setTimeout(startHangingGet, 0); } function startHangingGet() { try { hangingGet = new XMLHttpRequest(); hangingGet.onreadystatechange = hangingGetCallback; hangingGet.ontimeout = onHangingGetTimeout; hangingGet.open("GET", server + "/wait?peer_id=" + myId, true); hangingGet.send(); } catch (e) { trace_exception(e, "Start hanging get error"); } } function sendToPeer(peer_id, data) { if (myId == -1) { alert("Not connected."); return; } if (peer_id == myId) { alert("Can't send a message to oneself."); return; } var r = new XMLHttpRequest(); r.open("POST", server + "/message?peer_id=" + myId + "&to=" + peer_id, false); r.setRequestHeader("Content-Type", "text/plain"); r.send(data); r = null; } function signInCallback() { try { if (request.readyState == 4) { if (request.status == 200) { var peers = request.responseText.split("\n"); myId = parseInt(peers[0].split(",")[1]); trace("My id: " + myId); clearPeerList(); var added = 0; for (var i = 1; i < peers.length; ++i) { if (peers[i].length > 0) { trace("Peer " + i + ": " + peers[i]); var parsed = peers[i].split(","); addPeer(parseInt(parsed[1]), parsed[0]); ++added; } } if (added == 0) addPeer(-1, "No other peer connected"); else { document.getElementById("peers").disabled = false; document.getElementById("call").disabled = false; } startHangingGet(); request = null; document.getElementById("connect").disabled = true; document.getElementById("disconnect").disabled = false; } } } catch (e) { trace_exception(e, "Sign in error"); document.getElementById("connect").disabled = false; } } function signIn() { try { request = new XMLHttpRequest(); request.onreadystatechange = signInCallback; request.open("GET", server + "/sign_in?" + myName, true); request.send(); } catch (e) { trace_exception(e, "Start sign in error"); document.getElementById("connect").disabled = false; } } function connect() { myName = document.getElementById("local").value.toLowerCase(); server = document.getElementById("server").value.toLowerCase(); if (myName.length == 0) { alert("I need a name please."); document.getElementById("local").focus(); } else { // TODO: Disable connect button here, but we need a timeout and check if we // have connected, if so enable it again. signIn(); } } function disconnect() { if (callState == 1) hangUp(); disconnecting = true; if (request) { request.abort(); request = null; } if (hangingGet) { hangingGet.abort(); hangingGet = null; } if (myId != -1) { request = new XMLHttpRequest(); request.open("GET", server + "/sign_out?peer_id=" + myId, false); request.send(); request = null; myId = -1; } clearPeerList(); addPeer(-1, "Not connected"); document.getElementById("connect").disabled = false; document.getElementById("disconnect").disabled = true; document.getElementById("peers").disabled = true; document.getElementById("call").disabled = true; disconnecting = false; } // Window event handling window.onload = getUserMedia; window.onbeforeunload = disconnect; </script> </head> <body> <h1>WebRTC</h1> You must have a WebRTC capable browser in order to make calls using this test page.<br> <table border="0"> <tr> <td>Local Preview</td> <td>Remote Video</td> </tr> <tr> <td> <video width="320" height="240" id="localView" autoplay="autoplay"></video> </td> <td> <video width="640" height="480" id="remoteView" autoplay="autoplay"></video> </td> </tr> </table> <table border="0"> <tr> <td valign="top"> <table border="0" cellpaddning="0" cellspacing="0"> <tr> <td>Server:</td> <td> <input type="text" id="server" size="30" value="http://localhost:8888"/> </td> </tr> <tr> <td>Name:</td><td><input type="text" id="local" size="30" value="name"/></td> </tr> </table> </td> <td valign="top"> <button id="connect" onclick="connect();">Connect</button><br> <button id="disconnect" onclick="disconnect();" disabled="true">Disconnect </button> </td> <td> </td> <td valign="top"> Connected peers:<br> <select id="peers" size="5" disabled="true"> <option value="-1">Not connected</option> </select> </td> <td valign="top"> <!--input type="text" id="peer_id" size="3" value="1"/><br--> <button id="call" onclick="doCall();" disabled="true">Call</button><br> <button id="hangup" onclick="hangUp();" disabled="true">Hang up</button><br> </td> <td> </td> <td valign="top"> <button onclick="toggleExtraButtons();">Toggle extra buttons (debug)</button> <br> <button id="createPcBtn" onclick="createPeerConnection();" hidden="true"> Create peer connection</button> </td> </tr> </table> <button onclick="document.getElementById('debug').innerHTML='';">Clear log </button> <pre id="debug"></pre> </body> </html>