| OLD | NEW |
| (Empty) |
| 1 <!DOCTYPE HTML PUBLIC "-//IETF//DTD HTML//EN"> | |
| 2 <!-- | |
| 3 Copyright (c) 2012 The WebRTC project authors. All Rights Reserved. | |
| 4 | |
| 5 Use of this source code is governed by a BSD-style license | |
| 6 that can be found in the LICENSE file in the root of the source | |
| 7 tree. An additional intellectual property rights grant can be found | |
| 8 in the file PATENTS. All contributing project authors may | |
| 9 be found in the AUTHORS file in the root of the source tree. | |
| 10 --> | |
| 11 <html> | |
| 12 <head> | |
| 13 <title>WebRTC Multi-PeerConnection Test</title> | |
| 14 <script type="text/javascript"> | |
| 15 // This file can create an arbitrary number of peer connection calls, each | |
| 16 // with an arbitrary number of auto-echoing data channels. It can run with | |
| 17 // two separate cameras. | |
| 18 | |
| 19 // Our two local video / audio streams. | |
| 20 var gLocalStream1 = null; | |
| 21 var gLocalStream2 = null; | |
| 22 | |
| 23 // The number of remote view windows (2x number of calls). | |
| 24 var gNumRemoteViews = 0; | |
| 25 | |
| 26 // Maps connection id -> { connection1, connection2 }. | |
| 27 var gAllConnections = []; | |
| 28 var gNumConnections = 0; | |
| 29 | |
| 30 // Maps data channel id -> sending channel. | |
| 31 // Note: there can be many data channels per connection id. | |
| 32 var gSendingDataChannels = []; | |
| 33 var gTotalNumSendChannels = 0; | |
| 34 | |
| 35 function startTest() { | |
| 36 navigator.webkitGetUserMedia( | |
| 37 {video: true, audio: true}, | |
| 38 function(localStream) { | |
| 39 gLocalStream1 = localStream; | |
| 40 play(localStream, 'local-view-1'); | |
| 41 }, | |
| 42 getUserMediaFailedCallback); | |
| 43 navigator.webkitGetUserMedia( | |
| 44 {video: true, audio: true}, | |
| 45 function(localStream) { | |
| 46 gLocalStream2 = localStream; | |
| 47 play(localStream, 'local-view-2'); | |
| 48 }, | |
| 49 getUserMediaFailedCallback); | |
| 50 } | |
| 51 | |
| 52 function playStreamInNewRemoteView(stream, peerNumber) { | |
| 53 console.log('Remote stream to connection ' + peerNumber + | |
| 54 ': ' + stream.label); | |
| 55 gNumRemoteViews++; | |
| 56 var viewName = 'remote-view-' + gNumRemoteViews; | |
| 57 addRemoteView(viewName, peerNumber); | |
| 58 play(stream, viewName); | |
| 59 } | |
| 60 | |
| 61 function addRemoteView(elementName, peerNumber) { | |
| 62 var remoteViews = $('remote-views-' + peerNumber); | |
| 63 remoteViews.innerHTML += | |
| 64 '<tr><td><video width="320" height="240" id="' + elementName + '" ' + | |
| 65 'autoplay="autoplay"></video></td></tr>'; | |
| 66 } | |
| 67 | |
| 68 function play(stream, videoElement) { | |
| 69 var streamUrl = URL.createObjectURL(stream); | |
| 70 $(videoElement).src = streamUrl; | |
| 71 } | |
| 72 | |
| 73 function getUserMediaFailedCallback(error) { | |
| 74 console.log('getUserMedia request failed with code ' + error.code); | |
| 75 } | |
| 76 | |
| 77 function call() { | |
| 78 connection1 = new webkitRTCPeerConnection(null, | |
| 79 {optional:[{RtpDataChannels: true}]}); | |
| 80 connection1.addStream(gLocalStream1); | |
| 81 | |
| 82 connection2 = new webkitRTCPeerConnection( | |
| 83 null, {optional:[{RtpDataChannels: true}]}); | |
| 84 connection2.addStream(gLocalStream2); | |
| 85 connection2.onicecandidate = function(event) { | |
| 86 if (event.candidate) { | |
| 87 var candidate = new RTCIceCandidate(event.candidate); | |
| 88 connection1.addIceCandidate(candidate); | |
| 89 } | |
| 90 }; | |
| 91 connection1.onicecandidate = function(event) { | |
| 92 if (event.candidate) { | |
| 93 console.log('Ice candidate: ' + event.candidate); | |
| 94 var candidate = new RTCIceCandidate(event.candidate); | |
| 95 connection2.addIceCandidate(candidate); | |
| 96 } | |
| 97 }; | |
| 98 connection1.onaddstream = function(event) { | |
| 99 playStreamInNewRemoteView(event.stream, 1); | |
| 100 //addDataChannelAnchor(connection1, connection2); | |
| 101 }; | |
| 102 connection2.onaddstream = function(event) { | |
| 103 playStreamInNewRemoteView(event.stream, 2); | |
| 104 }; | |
| 105 // TODO(phoglund): hack to work around | |
| 106 // https://code.google.com/p/webrtc/issues/detail?id=1203. When it is fixed, | |
| 107 // uncomment the negotiate call, remove addDataChannel and uncomment in | |
| 108 // connection1.onaddstream. Also remove the notice at the top of the HTML! | |
| 109 // negotiate(connection1, connection2); | |
| 110 addDataChannelAnchor(connection1, connection2); | |
| 111 } | |
| 112 | |
| 113 function negotiate(connection1, connection2) { | |
| 114 connection1.createOffer(function(offer) { | |
| 115 connection1.setLocalDescription(offer); | |
| 116 connection2.setRemoteDescription(offer); | |
| 117 connection2.createAnswer(function(answer) { | |
| 118 console.log('Created answer ' + answer); | |
| 119 connection2.setLocalDescription(answer); | |
| 120 connection1.setRemoteDescription(answer); | |
| 121 }); | |
| 122 }); | |
| 123 } | |
| 124 | |
| 125 function addDataChannelAnchor(connection1, connection2) { | |
| 126 var connectionId = gNumConnections++; | |
| 127 gAllConnections[connectionId] = { connection1: connection1, | |
| 128 connection2: connection2 }; | |
| 129 addOneAnchor(1, connectionId); | |
| 130 addOneAnchor(2, connectionId); | |
| 131 } | |
| 132 | |
| 133 function makeDataChannelAnchorName(peerId, connectionId) { | |
| 134 return 'data-channels-peer' + peerId + '-' + connectionId; | |
| 135 } | |
| 136 | |
| 137 // This adds a target table we'll add our input fields to later. | |
| 138 function addOneAnchor(peerId, connectionId) { | |
| 139 var newButtonId = 'add-data-channel-' + connectionId; | |
| 140 var remoteViewContainer = 'remote-views-' + peerId; | |
| 141 $(remoteViewContainer).innerHTML += | |
| 142 '<tr><td><button id="' + newButtonId + '" ' + | |
| 143 'onclick="addDataChannel(' + connectionId + ')">' + | |
| 144 ' Add Echoing Data Channel</button></td></tr>'; | |
| 145 | |
| 146 var anchorName = makeDataChannelAnchorName(peerId, connectionId); | |
| 147 $(remoteViewContainer).innerHTML += | |
| 148 '<tr><td><table id="' + anchorName + '"></table></td></tr>'; | |
| 149 } | |
| 150 | |
| 151 // Called by clicking Add Echoing Data Channel. | |
| 152 function addDataChannel(connectionId) { | |
| 153 var dataChannelId = gTotalNumSendChannels++; | |
| 154 | |
| 155 var peer1SinkId = addDataChannelSink(1, connectionId, dataChannelId); | |
| 156 var peer2SinkId = addDataChannelSink(2, connectionId, dataChannelId); | |
| 157 var connections = gAllConnections[connectionId]; | |
| 158 | |
| 159 configureChannels(connections.connection1, connections.connection2, | |
| 160 peer1SinkId, peer2SinkId, dataChannelId); | |
| 161 | |
| 162 // Add the field the user types in, and a | |
| 163 // dummy field so everything lines up nicely. | |
| 164 addDataChannelSource(1, connectionId, dataChannelId); | |
| 165 addDisabledInputField(2, connectionId, '(the above is echoed)'); | |
| 166 | |
| 167 negotiate(connections.connection1, connections.connection2); | |
| 168 } | |
| 169 | |
| 170 function configureChannels(connection1, connection2, targetFor1, targetFor2, | |
| 171 dataChannelId) { | |
| 172 // Label the channel so we know where to send the data later in dispatch. | |
| 173 sendChannel = connection1.createDataChannel( | |
| 174 targetFor2, { reliable : false }); | |
| 175 sendChannel.onmessage = function(messageEvent) { | |
| 176 $(targetFor1).value = messageEvent.data; | |
| 177 } | |
| 178 | |
| 179 gSendingDataChannels[dataChannelId] = sendChannel; | |
| 180 | |
| 181 connection2.ondatachannel = function(event) { | |
| 182 // The channel got created by a message from a sending channel: hook this | |
| 183 // new receiver channel up to dispatch and then echo any messages. | |
| 184 event.channel.onmessage = dispatchAndEchoDataMessage; | |
| 185 } | |
| 186 } | |
| 187 | |
| 188 function addDataChannelSink(peerNumber, connectionId, dataChannelId) { | |
| 189 var sinkId = 'data-sink-peer' + peerNumber + '-' + dataChannelId; | |
| 190 var anchor = $(makeDataChannelAnchorName(peerNumber, connectionId)); | |
| 191 anchor.innerHTML += | |
| 192 '<tr><td><input type="text" id="' + sinkId + '" disabled/></td></tr>'; | |
| 193 return sinkId; | |
| 194 } | |
| 195 | |
| 196 function addDataChannelSource(peerNumber, connectionId, dataChannelId) { | |
| 197 var sourceId = 'data-source-peer' + peerNumber + '-' + dataChannelId; | |
| 198 var anchor = $(makeDataChannelAnchorName(peerNumber, connectionId)); | |
| 199 anchor.innerHTML += | |
| 200 '<tr><td><input type="text" id="' + sourceId + '"' + | |
| 201 ' onchange="userWroteSomethingIn(\'' + sourceId + '\', ' + | |
| 202 dataChannelId + ');"/></td></tr>'; | |
| 203 } | |
| 204 | |
| 205 function userWroteSomethingIn(sourceId, dataChannelId) { | |
| 206 var source = $(sourceId); | |
| 207 var dataChannel = gSendingDataChannels[dataChannelId]; | |
| 208 dataChannel.send(source.value); | |
| 209 } | |
| 210 | |
| 211 function addDisabledInputField(peerNumber, connectionId, text) { | |
| 212 var anchor = $(makeDataChannelAnchorName(peerNumber, connectionId)); | |
| 213 anchor.innerHTML += | |
| 214 '<tr><td><input type="text" value="' + text + '" disabled/></td></tr>'; | |
| 215 } | |
| 216 | |
| 217 function dispatchAndEchoDataMessage(messageEvent) { | |
| 218 // Since we labeled the channel earlier, we know to which input element | |
| 219 // we should send the data. | |
| 220 var dataChannel = messageEvent.currentTarget; | |
| 221 var targetInput = $(dataChannel.label); | |
| 222 targetInput.value = messageEvent.data; | |
| 223 dataChannel.send('echo: ' + messageEvent.data); | |
| 224 } | |
| 225 | |
| 226 window.onload = function() { | |
| 227 startTest(); | |
| 228 } | |
| 229 | |
| 230 $ = function(id) { | |
| 231 return document.getElementById(id); | |
| 232 }; | |
| 233 </script> | |
| 234 </head> | |
| 235 <body> | |
| 236 <table border="0"> | |
| 237 <tr> | |
| 238 <td colspan="2"> | |
| 239 Notes: | |
| 240 <ul> | |
| 241 <li>Due to https://code.google.com/p/webrtc/issues/detail?id=1203, | |
| 242 you must create a data channel to actually get a call negotiated. Add | |
| 243 one call at a time and click "add echoing data channel" for each and | |
| 244 you'll be fine.</li> | |
| 245 <li>For unknown reasons, adding a new data channel will clear the | |
| 246 input field contents for all other channels on the same call. This is | |
| 247 not the data channel's fault though.</li> | |
| 248 </ul> | |
| 249 </td> | |
| 250 </tr> | |
| 251 <tr> | |
| 252 <td>Local Preview for Peer 1</td> | |
| 253 <td>Local Preview for Peer 2</td> | |
| 254 </tr> | |
| 255 <tr> | |
| 256 <td><video width="320" height="240" id="local-view-1" | |
| 257 autoplay="autoplay"></video></td> | |
| 258 <td><video width="320" height="240" id="local-view-2" | |
| 259 autoplay="autoplay"></video></td> | |
| 260 </tr> | |
| 261 <tr> | |
| 262 <td><button id="add-call" onclick="call();">Add Call</button></td> | |
| 263 </tr> | |
| 264 <tr> | |
| 265 <td> | |
| 266 <table id="remote-views-1"> | |
| 267 <tr> | |
| 268 <td>Remote (Incoming to Peer 1)</td> | |
| 269 </tr> | |
| 270 </table> | |
| 271 </td> | |
| 272 <td> | |
| 273 <table id="remote-views-2"> | |
| 274 <tr> | |
| 275 <td>Remote (Incoming to Peer 2)</td> | |
| 276 </tr> | |
| 277 </table> | |
| 278 </td> | |
| 279 </tr> | |
| 280 </table> | |
| 281 </body> | |
| 282 </html> | |
| OLD | NEW |