| <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> |
| |
| <!-- |
| Copyright (c) 2012 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 setElementValuesFromURL() { |
| window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, |
| function(m, key, value) { |
| document.getElementById(key).value = unescape(value); |
| }); |
| } |
| |
| 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) { |
| try { |
| var peerList = document.getElementById("peers"); |
| var option = document.createElement("option"); |
| option.text = pname; |
| option.value = id; |
| peerList.add(option, null); |
| } catch (e) { |
| trace_exception(e, "Error adding peer"); |
| } |
| } |
| |
| 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("name").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 = function() { |
| if (navigator.webkitGetUserMedia) { |
| document.getElementById('testApp').hidden = false; |
| setElementValuesFromURL(); |
| getUserMedia(); |
| } else { |
| document.getElementById('errorText').hidden = false; |
| } |
| } |
| |
| window.onbeforeunload = disconnect; |
| |
| </script> |
| </head> |
| |
| <body> |
| <h1>WebRTC</h1> |
| <section id="errorText" hidden="true"> |
| Could not detect WebRTC support.<p> |
| You must have WebRTC enabled Chrome browser to use this test page. The browser |
| must be started with the --enable-media-stream command line flag. For more |
| information, please see |
| <a href="https://sites.google.com/site/webrtc/blog/webrtcnowavailableinthechromedevchannel"> |
| this blog post</a>. |
| </section> |
| |
| <section id="testApp" hidden="true"> |
| <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="name" 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> |
| </tr> |
| </table> |
| |
| <button onclick="document.getElementById('debug').innerHTML='';">Clear log |
| </button> |
| <pre id="debug"></pre> |
| </section> |
| |
| </body> |
| |
| </html> |
| |