Chromium Code Reviews| OLD | NEW |
|---|---|
| 1 /** | 1 /** |
| 2 * Copyright (c) 2012 The Chromium Authors. All rights reserved. | 2 * Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| 3 * Use of this source code is governed by a BSD-style license that can be | 3 * Use of this source code is governed by a BSD-style license that can be |
| 4 * found in the LICENSE file. | 4 * found in the LICENSE file. |
| 5 */ | 5 */ |
| 6 | 6 |
| 7 /** | |
| 8 * See http://dev.w3.org/2011/webrtc/editor/getusermedia.html for more | |
|
phoglund_chromium
2014/01/03 16:07:55
Also link to the peerconnection spec.
jansson
2014/01/07 08:47:52
This http://dev.w3.org/2011/webrtc/editor/webrtc.h
| |
| 9 * information on getUserMedia. | |
| 10 */ | |
| 11 | |
| 12 /** TODO give it a better name | |
| 13 * Global namespace object. | |
| 14 */ | |
| 15 var global = {}; | |
| 16 | |
| 7 /** | 17 /** |
| 8 * Used as a shortcut. Moved to the top of the page due to race conditions. | 18 * We need a STUN server for some API calls. |
| 19 * @private | |
| 20 */ | |
| 21 var STUN_SERVER = 'stun.l.google.com:19302'; | |
| 22 | |
| 23 /** @private */ | |
| 24 global.transformOutgoingSdp = function(sdp) { return sdp; }; | |
| 25 | |
| 26 /** @private */ | |
| 27 global.dataStatusCallback = function(status) {}; | |
| 28 | |
| 29 /** @private */ | |
| 30 global.dataCallback = function(data) {}; | |
| 31 | |
| 32 /** @private */ | |
| 33 global.dtmfOnToneChange = function(tone) {}; | |
| 34 | |
| 35 /** | |
| 36 * Used as a shortcut for finding DOM elements by ID. | |
| 9 * @param {string} id is a case-sensitive string representing the unique ID of | 37 * @param {string} id is a case-sensitive string representing the unique ID of |
| 10 * the element being sought. | 38 * the element being sought. |
| 11 * @return {object} id returns the element object specified as a parameter | 39 * @return {object} id returns the element object specified as a parameter |
| 12 */ | 40 */ |
| 13 $ = function(id) { | 41 $ = function(id) { |
| 14 return document.getElementById(id); | 42 return document.getElementById(id); |
| 15 }; | 43 }; |
| 16 | 44 |
| 17 /** | 45 /** |
| 18 * Sets the global variable gUseRtpDataChannel in message_handling.js | 46 * Prepopulate constraints from JS to the UI and setup callbacks in the scripts |
| 19 * based on the check box element 'data-channel-type-rtp' checked status on | 47 * shared with browser tests or automated tests. Enumerates devices available |
|
phoglund_chromium
2014/01/03 16:07:55
Except they're not shared anymore, right?
jansson
2014/01/07 08:47:52
Done.
| |
| 20 * peerconnection.html. Also prints a help text to inform the user. | 48 * via getUserMedia. |
| 21 */ | 49 */ |
| 22 function setPcDataChannelType() { | 50 window.onload = function() { |
| 23 var useRtpDataChannels = $('data-channel-type-rtp').checked; | 51 hookupDataChannelCallbacks_(); |
| 24 useRtpDataChannelsForNewPeerConnections(useRtpDataChannels); | 52 hookupDtmfSenderCallback_(); |
| 25 if (useRtpDataChannels) { | 53 updateGetUserMediaConstraints(); |
| 26 debug('Make sure to tick the RTP check box on both the calling and ' + | 54 setupLocalStorageFieldValues(); |
| 27 'answering side to ensure a data channel can be setup properly as ' + | 55 acceptIncomingCalls(); |
| 28 'it has to use the same transport protocol (RTP to RTP and SCTP to ' + | 56 if ($('get-devices-onload').checked == true) { |
| 29 'SCTP).'); | 57 getDevices(); |
| 30 } | 58 } |
| 59 registerElementForEvents_(); | |
| 60 }; | |
| 61 | |
| 62 /** | |
| 63 * Disconnect before the tab is closed. | |
| 64 */ | |
| 65 window.onbeforeunload = function() { | |
| 66 if (!isDisconnected_()) | |
|
phoglund_chromium
2014/01/03 16:07:55
Move this check into disconnect_() (just return if
jansson
2014/01/07 08:47:52
Done.
| |
| 67 disconnect_(); | |
| 68 }; | |
| 69 | |
| 70 /** TODO Add element.id as a parameter and call this function instead? | |
| 71 * A list of element id's to be registered for local storage. | |
| 72 */ | |
| 73 function setupLocalStorageFieldValues() { | |
| 74 registerLocalStorage_('pc-server'); | |
| 75 registerLocalStorage_('pc-createanswer-constraints'); | |
| 76 registerLocalStorage_('pc-createoffer-constraints'); | |
| 77 registerLocalStorage_('get-devices-onload'); | |
| 78 registerLocalStorage_('auto-add-stream-when-called'); | |
| 79 registerLocalStorage_('data-channel-type-rtp'); | |
| 31 } | 80 } |
| 32 | 81 |
| 82 // Public HTML functions | |
| 83 | |
| 33 // The *Here functions are called from peerconnection.html and will make calls | 84 // The *Here functions are called from peerconnection.html and will make calls |
| 34 // into our underlying JavaScript library with the values from the page | 85 // into our underlying JavaScript library with the values from the page |
| 35 // (have to be named differently to avoid name clashes with existing functions). | 86 // (have to be named differently to avoid name clashes with existing functions). |
| 36 | 87 |
| 37 function getUserMediaFromHere() { | 88 function getUserMediaFromHere() { |
|
phoglund_chromium
2014/01/03 16:07:55
You should be able to get rid of all of these (but
jansson
2014/01/07 08:47:52
I will create a separate patch where I assign appr
| |
| 38 var constraints = $('getusermedia-constraints').value; | 89 var constraints = $('getusermedia-constraints').value; |
| 39 try { | 90 try { |
| 40 doGetUserMedia(constraints); | 91 doGetUserMedia_(constraints); |
| 41 } catch (exception) { | 92 } catch (exception) { |
| 42 print_('getUserMedia says: ' + exception); | 93 print_('getUserMedia says: ' + exception); |
| 43 } | 94 } |
| 44 } | 95 } |
| 45 | 96 |
| 46 function connectFromHere() { | 97 function connectFromHere() { |
| 47 var server = $('pc-server').value; | 98 var server = $('pc-server').value; |
| 48 if ($('peer-id').value == '') { | 99 if ($('peer-id').value == '') { |
| 49 // Generate a random name to distinguish us from other tabs: | 100 // Generate a random name to distinguish us from other tabs: |
| 50 $('peer-id').value = 'peer_' + Math.floor(Math.random() * 10000); | 101 $('peer-id').value = 'peer_' + Math.floor(Math.random() * 10000); |
| 51 debug('Our name from now on will be ' + $('peer-id').value); | 102 print_('Our name from now on will be ' + $('peer-id').value); |
| 52 } | 103 } |
| 53 connect(server, $('peer-id').value); | 104 connect(server, $('peer-id').value); |
| 54 } | 105 } |
| 55 | 106 |
| 56 function negotiateCallFromHere() { | 107 function negotiateCallFromHere() { |
| 57 // Set the global variables used in jsep01_call.js with values from our UI. | 108 // Set the global variables with values from our UI. |
| 58 setCreateOfferConstraints(getEvaluatedJavaScript_( | 109 setCreateOfferConstraints(getEvaluatedJavaScript_( |
| 59 $('pc-createoffer-constraints').value)); | 110 $('pc-createoffer-constraints').value)); |
| 60 setCreateAnswerConstraints(getEvaluatedJavaScript_( | 111 setCreateAnswerConstraints(getEvaluatedJavaScript_( |
| 61 $('pc-createanswer-constraints').value)); | 112 $('pc-createanswer-constraints').value)); |
| 62 | 113 |
| 63 ensureHasPeerConnection_(); | 114 ensureHasPeerConnection_(); |
| 64 try { | 115 negotiateCall_(); |
| 65 negotiateCall(); | |
| 66 } catch (exception) { | |
| 67 error(exception); | |
| 68 } | |
| 69 } | 116 } |
| 70 | 117 |
| 71 function addLocalStreamFromHere() { | 118 function addLocalStreamFromHere() { |
| 72 ensureHasPeerConnection_(); | 119 ensureHasPeerConnection_(); |
| 73 addLocalStream(); | 120 addLocalStream(); |
| 74 } | 121 } |
| 75 | 122 |
| 76 function removeLocalStreamFromHere() { | 123 function removeLocalStreamFromHere() { |
| 77 removeLocalStream(); | 124 removeLocalStream(); |
| 78 } | 125 } |
| 79 | 126 |
| 80 function hangUpFromHere() { | 127 function hangUpFromHere() { |
| 81 hangUp(); | 128 hangUp(); |
| 82 acceptIncomingCallsAgain(); | 129 acceptIncomingCalls(); |
| 83 } | 130 } |
| 84 | 131 |
| 85 function toggleRemoteVideoFromHere() { | 132 function toggleRemoteVideoFromHere() { |
| 86 toggleRemoteStream(function(remoteStream) { | 133 toggleRemoteStream(function(remoteStream) { |
| 87 return remoteStream.getVideoTracks()[0]; | 134 return remoteStream.getVideoTracks()[0]; |
| 88 }, 'video'); | 135 }, 'video'); |
| 89 } | 136 } |
| 90 | 137 |
| 91 function toggleRemoteAudioFromHere() { | 138 function toggleRemoteAudioFromHere() { |
| 92 toggleRemoteStream(function(remoteStream) { | 139 toggleRemoteStream(function(remoteStream) { |
| (...skipping 62 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
| 155 * resolution else it defaults to 640x480 in the constraints for screen | 202 * resolution else it defaults to 640x480 in the constraints for screen |
| 156 * capturing. | 203 * capturing. |
| 157 */ | 204 */ |
| 158 function updateGetUserMediaConstraints() { | 205 function updateGetUserMediaConstraints() { |
| 159 var audio_selected = $('audiosrc'); | 206 var audio_selected = $('audiosrc'); |
| 160 var video_selected = $('videosrc'); | 207 var video_selected = $('videosrc'); |
| 161 var constraints = { | 208 var constraints = { |
| 162 audio: $('audio').checked, | 209 audio: $('audio').checked, |
| 163 video: $('video').checked | 210 video: $('video').checked |
| 164 }; | 211 }; |
| 212 | |
| 165 if (audio_selected.disabled == false && video_selected.disabled == false) { | 213 if (audio_selected.disabled == false && video_selected.disabled == false) { |
| 166 var devices = getSourcesFromField(audio_selected, video_selected); | 214 var devices = getSourcesFromField_(audio_selected, video_selected); |
| 167 if ($('audio').checked == true) { | 215 if ($('audio').checked == true) { |
| 168 if (devices.audio_id != null) { | 216 if (devices.audio_id != null) { |
| 169 constraints.audio = {optional: [{sourceId: devices.audio_id}]}; | 217 constraints.audio = {optional: [{sourceId: devices.audio_id}]}; |
| 170 } | 218 } else { |
| 171 else { | |
| 172 constraints.audio = true; | 219 constraints.audio = true; |
| 173 } | 220 } |
| 174 } | 221 } |
| 175 if ($('video').checked == true) { | 222 if ($('video').checked == true) { |
| 176 // Default optional constraints placed here. | 223 // Default optional constraints placed here. |
| 177 constraints.video = {optional: [{minWidth: $('video-width').value}, | 224 constraints.video = {optional: [{minWidth: $('video-width').value}, |
| 178 {minHeight: $('video-height').value}, | 225 {minHeight: $('video-height').value}, |
| 179 {googCpuOveruseDetection: true}, | 226 {googCpuOveruseDetection: true}, |
| 180 {googLeakyBucket: true}] | 227 {googLeakyBucket: true}] |
| 181 }; | 228 }; |
| 182 if (devices.video_id != null) { | 229 if (devices.video_id != null) { |
| 183 constraints.video.optional.push({sourceId: devices.video_id}); | 230 constraints.video.optional.push({sourceId: devices.video_id}); |
| 184 } | 231 } |
| 185 } | 232 } |
| 186 } | 233 } |
| 234 | |
| 187 if ($('screencapture').checked) { | 235 if ($('screencapture').checked) { |
| 188 var constraints = { | 236 var constraints = { |
| 189 audio: $('audio').checked, | 237 audio: $('audio').checked, |
| 190 video: {mandatory: {chromeMediaSource: 'screen', | 238 video: {mandatory: {chromeMediaSource: 'screen', |
| 191 maxWidth: screen.width, | 239 maxWidth: screen.width, |
| 192 maxHeight: screen.height}} | 240 maxHeight: screen.height}} |
| 193 }; | 241 }; |
| 194 if ($('audio').checked == true) | 242 if ($('audio').checked == true) |
| 195 debug('Audio for screencapture is not implemented yet, please ' + | 243 print_('Audio for screencapture is not implemented yet, please ' + |
| 196 'try to set audio = false prior requesting screencapture'); | 244 'try to set audio = false prior requesting screencapture'); |
| 197 } | 245 } |
| 198 $('getusermedia-constraints').value = | 246 $('getusermedia-constraints').value = JSON.stringify(constraints, null, ' '); |
| 199 JSON.stringify(constraints, null, ' '); | |
| 200 } | 247 } |
| 201 | 248 |
| 202 function showServerHelp() { | 249 function showServerHelp() { |
| 203 alert('You need to build and run a peerconnection_server on some ' + | 250 alert('You need to build and run a peerconnection_server on some ' + |
| 204 'suitable machine. To build it in chrome, just run make/ninja ' + | 251 'suitable machine. To build it in chrome, just run make/ninja ' + |
| 205 'peerconnection_server. Otherwise, read in https://code.google' + | 252 'peerconnection_server. Otherwise, read in https://code.google' + |
| 206 '.com/searchframe#xSWYf0NTG_Q/trunk/peerconnection/README&q=REA' + | 253 '.com/searchframe#xSWYf0NTG_Q/trunk/peerconnection/README&q=REA' + |
| 207 'DME%20package:webrtc%5C.googlecode%5C.com.'); | 254 'DME%20package:webrtc%5C.googlecode%5C.com.'); |
| 208 } | 255 } |
| 209 | 256 |
| 210 function toggleHelp() { | |
| 211 var help = $('help'); | |
| 212 if (help.style.display == 'none') | |
| 213 help.style.display = 'inline'; | |
| 214 else | |
| 215 help.style.display = 'none'; | |
| 216 } | |
| 217 | |
| 218 function clearLog() { | 257 function clearLog() { |
| 219 $('messages').innerHTML = ''; | 258 $('messages').innerHTML = ''; |
| 220 $('debug').innerHTML = ''; | 259 $('debug').innerHTML = ''; |
| 221 } | 260 } |
| 222 | 261 |
| 223 /** | 262 /** |
| 224 * Prepopulate constraints from JS to the UI and setup callbacks in the scripts | 263 * Stops the local stream. |
| 225 * shared with browser tests or automated tests. Enumerates devices available | 264 */ |
| 226 * via getUserMedia. | 265 function stopLocalStream() { |
| 227 */ | 266 if (global.localStream == null) |
|
phoglund_chromium
2014/01/03 16:07:55
In the next patch, make these accessors on the glo
jansson
2014/01/07 08:47:52
Adding it to my notes.
| |
| 228 window.onload = function() { | 267 error_('Tried to stop local stream, ' + |
| 229 $('pc-createoffer-constraints').value = JSON.stringify( | 268 'but media access is not granted.'); |
| 230 gCreateOfferConstraints, null, ' '); | 269 |
| 231 $('pc-createanswer-constraints').value = JSON.stringify( | 270 global.localStream.stop(); |
| 232 gCreateAnswerConstraints, null, ' '); | 271 } |
| 233 replaceReturnCallback(print_); | 272 |
| 234 replaceDebugCallback(debug_); | 273 // Functions callable from other JavaScript modules. |
|
phoglund_chromium
2014/01/03 16:07:55
Remove this comment.
jansson
2014/01/07 08:47:52
Done.
| |
| 235 doNotAutoAddLocalStreamWhenCalled(); | 274 |
| 236 hookupDataChannelCallbacks_(); | 275 /** |
| 237 hookupDtmfSenderCallback_(); | 276 * Adds the current local media stream to a peer connection. |
| 238 displayVideoSize_($('local-view')); | 277 * @param {RTCPeerConnection} peerConnection |
| 239 displayVideoSize_($('remote-view')); | 278 */ |
| 240 updateGetUserMediaConstraints(); | 279 function addLocalStreamToPeerConnection(peerConnection) { |
| 241 setPcDataChannelType(); | 280 if (global.localStream == null) |
| 242 setupLocalStorageFieldValues(); | 281 error_('Tried to add local stream to peer connection, but there is no ' + |
| 243 if ($('get-devices-onload').checked == true) { | 282 'stream yet.'); |
| 244 getDevices(); | 283 try { |
| 245 } | 284 peerConnection.addStream(global.localStream, global.addStreamConstraints); |
| 246 }; | 285 } catch (exception) { |
| 286 error_('Failed to add stream with constraints ' + | |
| 287 global.addStreamConstraints + ': ' + exception); | |
| 288 } | |
| 289 print_('Added local stream.'); | |
| 290 } | |
| 291 | |
| 292 /** | |
| 293 * Removes the local stream from the peer connection. | |
| 294 * @param {rtcpeerconnection} peerConnection | |
| 295 */ | |
| 296 function removeLocalStreamFromPeerConnection(peerConnection) { | |
| 297 if (global.localStream == null) | |
| 298 error_('Tried to remove local stream from peer connection, but there is ' + | |
| 299 'no stream yet.'); | |
| 300 try { | |
| 301 peerConnection.removeStream(global.localStream); | |
| 302 } catch (exception) { | |
| 303 error_('Could not remove stream: ' + exception); | |
| 304 } | |
| 305 print_('Removed local stream.'); | |
| 306 } | |
| 307 | |
| 308 /** | |
| 309 * Enumerates the audio and video devices available in Chrome and adds the | |
| 310 * devices to the HTML elements with Id 'audiosrc' and 'videosrc'. | |
| 311 * Checks if device enumeration is supported and if the 'audiosrc' + 'videosrc' | |
| 312 * elements exists, if not a debug printout will be displayed. | |
| 313 * If the device label is empty, audio/video + sequence number will be used to | |
| 314 * populate the name. Also makes sure the children has been loaded in order | |
| 315 * to update the constraints. | |
| 316 */ | |
| 317 function getDevices() { | |
| 318 var audio_select = $('audiosrc'); | |
| 319 var video_select = $('videosrc'); | |
| 320 var get_devices = $('get-devices'); | |
| 321 audio_select.innerHTML = ''; | |
| 322 video_select.innerHTML = ''; | |
| 323 try { | |
| 324 eval(MediaStreamTrack.getSources(function() {})); | |
| 325 } catch (exception) { | |
| 326 audio_select.disabled = true; | |
| 327 video_select.disabled = true; | |
| 328 refresh_devices.disabled = true; | |
| 329 updateGetUserMediaConstraints(); | |
| 330 error_('Device enumeration not supported. ' + exception); | |
| 331 } | |
| 332 MediaStreamTrack.getSources(function(devices) { | |
| 333 for (var i = 0; i < devices.length; i++) { | |
| 334 var option = document.createElement('option'); | |
| 335 option.value = devices[i].id; | |
| 336 option.text = devices[i].label; | |
| 337 if (devices[i].kind == 'audio') { | |
| 338 if (option.text == '') { | |
| 339 option.text = devices[i].id; | |
| 340 } | |
| 341 audio_select.appendChild(option); | |
| 342 } else if (devices[i].kind == 'video') { | |
| 343 if (option.text == '') { | |
| 344 option.text = devices[i].id; | |
| 345 } | |
| 346 video_select.appendChild(option); | |
| 347 } else { | |
| 348 error_('Device type ' + devices[i].kind + ' not recognized, ' + | |
| 349 'cannot enumerate device. Currently only device types' + | |
| 350 '\'audio\' and \'video\' are supported'); | |
| 351 updateGetUserMediaConstraints(); | |
| 352 return; | |
| 353 } | |
| 354 } | |
| 355 }); | |
| 356 checkIfDeviceDropdownsArePopulated_(); | |
| 357 } | |
| 358 | |
|
phoglund_chromium
2014/01/03 16:07:55
These setters should be on the global object (perh
jansson
2014/01/07 08:47:52
Adding to my todo list.
| |
| 359 /** | |
| 360 * Sets the transform to apply just before setting the local description and | |
| 361 * sending to the peer. | |
| 362 * @param {function} transformFunction A function which takes one SDP string as | |
| 363 * argument and returns the modified SDP string. | |
| 364 */ | |
| 365 function setOutgoingSdpTransform(transformFunction) { | |
| 366 global.transformOutgoingSdp = function(sdp) { return sdp; }; | |
|
phoglund_chromium
2014/01/03 16:07:55
This is wrong: should assign to transformFunction.
jansson
2014/01/07 08:47:52
Done.
| |
| 367 } | |
| 368 | |
| 369 /** | |
| 370 * Sets the MediaConstraints to be used for PeerConnection createAnswer() calls. | |
| 371 * @param {string} mediaConstraints The constraints, as defined in the | |
| 372 * PeerConnection JS API spec. | |
| 373 */ | |
| 374 function setCreateAnswerConstraints(mediaConstraints) { | |
| 375 global.createAnswerConstraints = mediaConstraints; | |
| 376 } | |
| 377 | |
| 378 /** | |
| 379 * Sets the MediaConstraints to be used for PeerConnection createOffer() calls. | |
| 380 * @param {string} mediaConstraints The constraints, as defined in the | |
| 381 * PeerConnection JS API spec. | |
| 382 */ | |
| 383 function setCreateOfferConstraints(mediaConstraints) { | |
| 384 global.createOfferConstraints = mediaConstraints; | |
| 385 } | |
| 386 | |
| 387 /** | |
| 388 * Sets the callback functions that will receive DataChannel readyState updates | |
| 389 * and received data. | |
| 390 * @param {function} status_callback The function that will receive a string | |
| 391 * with | |
| 392 * the current DataChannel readyState. | |
| 393 * @param {function} data_callback The function that will a string with data | |
| 394 * received from the remote peer. | |
| 395 */ | |
| 396 function setDataCallbacks(status_callback, data_callback) { | |
| 397 global.dataStatusCallback = status_callback; | |
| 398 global.dataCallback = data_callback; | |
| 399 } | |
| 400 | |
| 401 /** | |
| 402 * Sends data on an active DataChannel. | |
| 403 * @param {string} data The string that will be sent to the remote peer. | |
| 404 */ | |
| 405 function sendDataOnChannel(data) { | |
| 406 if (global.dataChannel == null) | |
| 407 error_('Trying to send data, but there is no DataChannel.'); | |
| 408 global.dataChannel.send(data); | |
| 409 } | |
| 410 | |
| 411 /** | |
| 412 * Sets the callback function that will receive DTMF sender ontonechange events. | |
| 413 * @param {function} ontonechange The function that will receive a string with | |
| 414 * the tone that has just begun playout. | |
| 415 */ | |
| 416 function setOnToneChange(ontonechange) { | |
| 417 global.dtmfOnToneChange = ontonechange; | |
| 418 } | |
| 419 | |
| 420 /** TODO Fix param definitions | |
|
phoglund_chromium
2014/01/03 16:07:55
Remove or fix this comment.
jansson
2014/01/07 08:47:52
Done.
phoglund_chromium
2014/01/07 09:52:25
I rather meant to fix the TODO. In this case, remo
jansson
2014/01/07 10:22:35
Done.
| |
| 421 * Inserts DTMF tones on an active DTMF sender. | |
| 422 * @param {string} tones The string that will be sent to the remote peer. | |
| 423 * @param {string} duration The string that will be sent to the remote peer. | |
| 424 * @param {string} interToneGap The string that will be sent to the remote peer. | |
| 425 */ | |
| 426 function insertDtmf(tones, duration, interToneGap) { | |
| 427 if (global.dtmfSender == null) | |
| 428 error_('Trying to send DTMF, but there is no DTMF sender.'); | |
| 429 global.dtmfSender.insertDTMF(tones, duration, interToneGap); | |
| 430 } | |
| 431 | |
| 432 // Public interface towards the other javascript files, such as | |
|
phoglund_chromium
2014/01/03 16:07:55
Remove this comment, it no longer applies.
jansson
2014/01/07 08:47:52
Done.
| |
| 433 // message_handling.js. The contract for these functions is described in | |
| 434 // message_handling.js. | |
| 435 | |
| 436 function handleMessage(peerConnection, message) { | |
| 437 var parsed_msg = JSON.parse(message); | |
| 438 if (parsed_msg.type) { | |
| 439 var session_description = new RTCSessionDescription(parsed_msg); | |
| 440 peerConnection.setRemoteDescription( | |
| 441 session_description, | |
| 442 function() { success_('setRemoteDescription'); }, | |
| 443 function() { failure_('setRemoteDescription'); }); | |
| 444 if (session_description.type == 'offer') { | |
| 445 print_('createAnswer with constraints: ' + | |
| 446 JSON.stringify(global.createAnswerConstraints, null, ' ')); | |
| 447 peerConnection.createAnswer( | |
| 448 setLocalAndSendMessage_, | |
| 449 function() { failure_('createAnswer'); }, | |
| 450 global.createAnswerConstraints); | |
| 451 } | |
| 452 return; | |
| 453 } else if (parsed_msg.candidate) { | |
| 454 var candidate = new RTCIceCandidate(parsed_msg); | |
| 455 peerConnection.addIceCandidate(candidate); | |
| 456 return; | |
| 457 } | |
| 458 error_('unknown message received'); | |
| 459 } | |
| 460 | |
| 461 function createPeerConnection(stun_server, useRtpDataChannels) { | |
| 462 servers = {iceServers: [{url: 'stun:' + stun_server}]}; | |
| 463 try { | |
| 464 var constraints = { optional: [{ RtpDataChannels: useRtpDataChannels }]}; | |
| 465 peerConnection = new webkitRTCPeerConnection(servers, constraints); | |
| 466 } catch (exception) { | |
| 467 error_('Failed to create peer connection: ' + exception); | |
| 468 } | |
| 469 peerConnection.onaddstream = addStreamCallback_; | |
| 470 peerConnection.onremovestream = removeStreamCallback_; | |
| 471 peerConnection.onicecandidate = iceCallback_; | |
| 472 peerConnection.ondatachannel = onCreateDataChannelCallback_; | |
| 473 return peerConnection; | |
| 474 } | |
| 475 | |
| 476 function setupCall(peerConnection) { | |
| 477 print_('createOffer with constraints: ' + | |
| 478 JSON.stringify(global.createOfferConstraints, null, ' ')); | |
| 479 peerConnection.createOffer( | |
| 480 setLocalAndSendMessage_, | |
| 481 function() { failure_('createOffer'); }, | |
| 482 global.createOfferConstraints); | |
| 483 } | |
| 484 | |
| 485 function answerCall(peerConnection, message) { | |
| 486 handleMessage(peerConnection, message); | |
| 487 } | |
| 488 | |
| 489 function createDataChannel(peerConnection, label) { | |
| 490 if (global.dataChannel != null && global.dataChannel.readyState != 'closed') { | |
| 491 error_('Creating DataChannel, but we already have one.'); | |
|
phoglund_chromium
2014/01/03 16:07:55
Nit: remove {}.
jansson
2014/01/07 08:47:52
Done.
| |
| 492 } | |
| 493 | |
| 494 global.dataChannel = peerConnection.createDataChannel(label, | |
| 495 { reliable: false }); | |
| 496 print_('DataChannel with label ' + global.dataChannel.label + ' initiated ' + | |
| 497 'locally.'); | |
| 498 hookupDataChannelEvents(); | |
| 499 } | |
| 500 | |
| 501 function closeDataChannel(peerConnection) { | |
| 502 if (global.dataChannel == null) | |
| 503 error_('Closing DataChannel, but none exists.'); | |
| 504 print_('DataChannel with label ' + global.dataChannel.label + | |
| 505 ' is beeing closed.'); | |
| 506 global.dataChannel.close(); | |
| 507 } | |
| 508 | |
| 509 function createDtmfSender(peerConnection) { | |
| 510 if (global.dtmfSender != null) | |
| 511 error_('Creating DTMF sender, but we already have one.'); | |
| 512 | |
| 513 var localStream = global.localStream(); | |
| 514 if (localStream == null) | |
| 515 error_('Creating DTMF sender but local stream is null.'); | |
| 516 local_audio_track = localStream.getAudioTracks()[0]; | |
| 517 global.dtmfSender = peerConnection.createDTMFSender(local_audio_track); | |
| 518 global.dtmfSender.ontonechange = global.dtmfOnToneChange; | |
| 519 } | |
| 520 | |
| 521 /** | |
| 522 * Connects to the provided peerconnection_server. | |
| 523 * | |
| 524 * @param {string} serverUrl The server URL in string form without an ending | |
| 525 * slash, something like http://localhost:8888. | |
| 526 * @param {string} clientName The name to use when connecting to the server. | |
| 527 */ | |
| 528 function connect(serverUrl, clientName) { | |
| 529 if (global.ourPeerId != null) | |
| 530 error_('connecting, but is already connected.'); | |
| 531 | |
| 532 print_('Connecting to ' + serverUrl + ' as ' + clientName); | |
| 533 global.serverUrl = serverUrl; | |
| 534 global.ourClientName = clientName; | |
| 535 | |
| 536 request = new XMLHttpRequest(); | |
| 537 request.open('GET', serverUrl + '/sign_in?' + clientName, true); | |
| 538 print_(serverUrl + '/sign_in?' + clientName); | |
| 539 request.onreadystatechange = function() { | |
| 540 connectCallback_(request); | |
| 541 }; | |
| 542 request.send(); | |
| 543 } | |
| 544 | |
| 545 /** | |
| 546 * Checks if the remote peer has connected. Returns peer-connected if that is | |
| 547 * the case, otherwise no-peer-connected. | |
| 548 */ | |
| 549 function remotePeerIsConnected() { | |
| 550 if (global.remotePeerId == null) | |
| 551 print_('no-peer-connected'); | |
| 552 else | |
| 553 print_('peer-connected'); | |
| 554 } | |
| 555 | |
| 556 /** | |
| 557 * Creates a peer connection. Must be called before most other public functions | |
| 558 * in this file. | |
| 559 */ | |
| 560 function preparePeerConnection() { | |
| 561 if (global.peerConnection != null) | |
| 562 error_('creating peer connection, but we already have one.'); | |
| 563 | |
| 564 global.useRtpDataChannels = $('data-channel-type-rtp').checked; | |
| 565 global.peerConnection = createPeerConnection(STUN_SERVER, | |
| 566 global.useRtpDataChannels); | |
| 567 print_('ok-peerconnection-created'); | |
| 568 } | |
| 569 | |
| 570 /** | |
| 571 * Adds the local stream to the peer connection. You will have to re-negotiate | |
| 572 * the call for this to take effect in the call. | |
| 573 */ | |
| 574 function addLocalStream() { | |
| 575 if (global.peerConnection == null) | |
| 576 error_('adding local stream, but we have no peer connection.'); | |
| 577 | |
| 578 addLocalStreamToPeerConnection(global.peerConnection); | |
| 579 print_('ok-added'); | |
| 580 } | |
| 581 | |
| 582 /** | |
| 583 * Removes the local stream from the peer connection. You will have to | |
| 584 * re-negotiate the call for this to take effect in the call. | |
| 585 */ | |
| 586 function removeLocalStream() { | |
| 587 if (global.peerConnection == null) | |
| 588 error_('attempting to remove local stream, but no call is up'); | |
| 589 | |
| 590 removeLocalStreamFromPeerConnection(global.peerConnection); | |
| 591 print_('ok-local-stream-removed'); | |
| 592 } | |
| 593 | |
| 594 /** | |
| 595 * (see getReadyState_) | |
| 596 */ | |
| 597 function getPeerConnectionReadyState() { | |
| 598 print_(getReadyState_()); | |
| 599 } | |
| 600 | |
| 601 /** | |
| 602 * Toggles the remote audio stream's enabled state on the peer connection, given | |
| 603 * that a call is active. Returns ok-[typeToToggle]-toggled-to-[true/false] | |
| 604 * on success. | |
| 605 * | |
| 606 * @param {function} selectAudioOrVideoTrack A function that takes a remote | |
| 607 * stream as argument and returns a track (e.g. either the video or audio | |
| 608 * track). | |
| 609 * @param {function} typeToToggle Either "audio" or "video" depending on what | |
| 610 * the selector function selects. | |
| 611 */ | |
| 612 function toggleRemoteStream(selectAudioOrVideoTrack, typeToToggle) { | |
|
phoglund_chromium
2014/01/03 16:07:55
I think these functions are probably over-generali
jansson
2014/01/07 08:47:52
Adding to my TODO list.
| |
| 613 if (global.peerConnection == null) | |
| 614 error_('Tried to toggle remote stream, but have no peer connection.'); | |
| 615 if (global.peerConnection.getRemoteStreams().length == 0) | |
| 616 error_('Tried to toggle remote stream, but not receiving any stream.'); | |
| 617 | |
| 618 var track = selectAudioOrVideoTrack( | |
| 619 global.peerConnection.getRemoteStreams()[0]); | |
| 620 toggle_(track, 'remote', typeToToggle); | |
| 621 } | |
| 622 | |
| 623 /** | |
| 624 * See documentation on toggleRemoteStream (this function is the same except | |
| 625 * we are looking at local streams). | |
| 626 */ | |
| 627 function toggleLocalStream(selectAudioOrVideoTrack, typeToToggle) { | |
| 628 if (global.peerConnection == null) | |
| 629 error_('Tried to toggle local stream, but have no peer connection.'); | |
| 630 if (global.peerConnection.getLocalStreams().length == 0) | |
| 631 error_('Tried to toggle local stream, but there is no local stream in ' + | |
| 632 'the call.'); | |
| 633 | |
| 634 var track = selectAudioOrVideoTrack( | |
| 635 global.peerConnection.getLocalStreams()[0]); | |
| 636 toggle_(track, 'local', typeToToggle); | |
| 637 } | |
| 638 | |
| 639 /** | |
| 640 * Hangs up a started call. Returns ok-call-hung-up on success. This tab will | |
| 641 * not accept any incoming calls after this call. | |
| 642 */ | |
| 643 function hangUp() { | |
| 644 if (global.peerConnection == null) | |
| 645 error_('hanging up, but has no peer connection'); | |
| 646 if (getReadyState_() != 'active') | |
| 647 error_('hanging up, but ready state is not active (no call up).'); | |
| 648 sendToPeer(global.remotePeerId, 'BYE'); | |
| 649 closeCall_(); | |
| 650 global.acceptsIncomingCalls = false; | |
| 651 print_('ok-call-hung-up'); | |
| 652 } | |
| 653 | |
| 654 /** | |
| 655 * Start accepting incoming calls. | |
| 656 */ | |
| 657 function acceptIncomingCalls() { | |
| 658 global.acceptsIncomingCalls = true; | |
| 659 } | |
| 660 | |
| 661 /** | |
| 662 * Do not auto-add the local stream when called. | |
| 663 */ | |
| 664 function doNotAutoAddLocalStreamWhenCalled() { | |
| 665 gAutoAddLocalToPeerConnectionStreamWhenCalled = false; | |
|
phoglund_chromium
2014/01/03 16:07:55
You missed this global variable?
jansson
2014/01/07 08:47:52
Good catch, also It's not used anymore (used by au
| |
| 666 } | |
| 667 | |
| 668 /** | |
| 669 * Creates a DataChannel on the current PeerConnection. Only one DataChannel can | |
| 670 * be created on each PeerConnection. | |
| 671 * Returns ok-datachannel-created on success. | |
| 672 */ | |
| 673 function createDataChannelOnPeerConnection() { | |
| 674 if (global.peerConnection == null) | |
| 675 error_('Tried to create data channel, but have no peer connection.'); | |
| 676 | |
| 677 createDataChannel(global.peerConnection, global.ourClientName); | |
| 678 print_('ok-datachannel-created'); | |
| 679 } | |
| 680 | |
| 681 /** | |
| 682 * Close the DataChannel on the current PeerConnection. | |
| 683 * Returns ok-datachannel-close on success. | |
| 684 */ | |
| 685 function closeDataChannelOnPeerConnection() { | |
| 686 if (global.peerConnection == null) | |
| 687 error_('Tried to close data channel, but have no peer connection.'); | |
| 688 | |
| 689 closeDataChannel(global.peerConnection); | |
| 690 print_('ok-datachannel-close'); | |
| 691 } | |
| 692 | |
| 693 /** | |
| 694 * Creates a DTMF sender on the current PeerConnection. | |
| 695 * Returns ok-dtmfsender-created on success. | |
| 696 */ | |
| 697 function createDtmfSenderOnPeerConnection() { | |
| 698 if (global.peerConnection == null) | |
| 699 error_('Tried to create DTMF sender, but have no peer connection.'); | |
| 700 | |
| 701 createDtmfSender(global.peerConnection); | |
| 702 print_('ok-dtmfsender-created'); | |
| 703 } | |
| 704 | |
| 705 /** | |
| 706 * Send DTMF tones on the global.dtmfSender. | |
| 707 * Returns ok-dtmf-sent on success. | |
| 708 */ | |
| 709 function insertDtmfOnSender(tones, duration, interToneGap) { | |
| 710 if (global.dtmfSender == null) | |
| 711 error_('Tried to insert DTMF tones, but have no DTMF sender.'); | |
| 712 | |
| 713 insertDtmf(tones, duration, interToneGap); | |
| 714 print_('ok-dtmf-sent'); | |
| 715 } | |
| 716 | |
| 717 /** | |
| 718 * An array of elements to be registered for for DOM events. | |
| 719 * @private | |
| 720 */ | |
| 721 function registerElementForEvents_() { | |
| 722 // Add element id's to be registered for events and functions. | |
| 723 // Remember to add the element id to registerFunctionForElements_() as well. | |
| 724 var element_id_list = ['audio', 'video', 'videosrc', 'audiosrc', 're-request', | |
| 725 'screencapture', 'video-width', 'video-height', | |
| 726 'get-devices', ]; | |
| 727 | |
| 728 for (var id in element_id_list) { | |
| 729 var element = $(element_id_list[id]); | |
| 730 if (element != null) | |
| 731 registerEventBasedOnElementType_(element); | |
| 732 else | |
| 733 error_('Element does not exist ' + '\"' + element + '\"'); | |
| 734 } | |
| 735 } | |
| 736 | |
| 737 /** | |
| 738 * Assigns the appropriate event type based on element type. | |
| 739 * @private | |
| 740 * @param {!object} element assigned to the appropriate event listener based | |
| 741 * on element.type. | |
| 742 */ | |
| 743 function registerEventBasedOnElementType_(element) { | |
|
phoglund_chromium
2014/01/03 16:07:55
I don't understand these. Where did they come from
jansson
2014/01/07 08:47:52
Sorry, my eagerness have added extra functionality
| |
| 744 switch (element.type) { | |
| 745 case 'checkbox': | |
| 746 case 'submit': | |
| 747 element.onclick = function() { registerFunctionForElements_(this); }; | |
| 748 break; | |
| 749 case 'text': | |
| 750 element.onblur = function() { registerFunctionForElements_(this); }; | |
| 751 break; | |
| 752 case 'select-one': | |
| 753 element.onchange = function() { registerFunctionForElements_(this); }; | |
| 754 break; | |
| 755 default: | |
| 756 error_('Unsupportered element type: ' + '\"' + element.type + '\"'); | |
| 757 } | |
| 758 } | |
| 759 | |
| 760 /** | |
| 761 * Registers a function to be called based on element.id. | |
| 762 * @private | |
| 763 * @param {!object} element assigned to the appropriate function based on | |
| 764 * element.id. | |
| 765 */ | |
| 766 function registerFunctionForElements_(element) { | |
| 767 switch (element.id) { | |
| 768 case 'audio': | |
| 769 case 'video': | |
| 770 case 'videosrc': | |
| 771 case 'audiosrc': | |
| 772 case 'screencapture': | |
| 773 case 'video-width': | |
| 774 case 'video-height': | |
| 775 updateGetUserMediaConstraints(); | |
| 776 break; | |
| 777 case 're-request': | |
| 778 getUserMediaFromHere(); | |
| 779 break; | |
| 780 case 'get-devices': | |
| 781 getDevices(); | |
| 782 default: | |
| 783 error_('Element \'' + element.id + '\' is not defined for a function'); | |
| 784 } | |
| 785 } | |
| 786 | |
| 787 /** | |
| 788 * Sends a message to a peer through the peerconnection_server. | |
| 789 */ | |
| 790 function sendToPeer(peer, message) { | |
| 791 var messageToLog = message.sdp ? message.sdp : message; | |
| 792 print_('Sending message ' + messageToLog + ' to peer ' + peer + '.'); | |
| 793 | |
| 794 var request = new XMLHttpRequest(); | |
| 795 var url = global.serverUrl + '/message?peer_id=' + global.ourPeerId + '&to=' + | |
| 796 peer; | |
| 797 request.open('POST', url, false); | |
| 798 request.setRequestHeader('Content-Type', 'text/plain'); | |
| 799 request.send(message); | |
| 800 } | |
| 801 | |
| 802 /** | |
| 803 * @param {!string} videoTagId The ID of the video tag to update. | |
| 804 * @param {!string} width The width of the video to update the video tag, if | |
| 805 * width or height is 0, size will be taken from videoTag.videoWidth. | |
| 806 * @param {!string} height The height of the video to update the video tag, if | |
| 807 * width or height is 0 size will be taken from the videoTag.videoHeight. | |
| 808 */ | |
| 809 function updateVideoTagSize(videoTagId, width, height) { | |
| 810 var videoTag = $(videoTagId); | |
| 811 if (width > 0 || height > 0) { | |
| 812 videoTag.width = width; | |
| 813 videoTag.height = height; | |
| 814 } | |
| 815 else { | |
| 816 if (videoTag.videoWidth > 0 || videoTag.videoHeight > 0) { | |
| 817 videoTag.width = videoTag.videoWidth; | |
| 818 videoTag.height = videoTag.videoHeight; | |
| 819 } | |
| 820 else { | |
| 821 print_('"' + videoTagId + '" video stream size is 0, skipping resize'); | |
| 822 } | |
| 823 } | |
| 824 print_('Set video tag "' + videoTagId + '" size to ' + videoTag.width + 'x' + | |
| 825 videoTag.height); | |
| 826 displayVideoSize_(videoTag); | |
| 827 } | |
| 828 | |
| 829 // Internals. | |
| 830 | |
| 831 /** | |
| 832 * Disconnects from the peerconnection server. Returns ok-disconnected on | |
| 833 * success. | |
| 834 */ | |
| 835 function disconnect_() { | |
|
phoglund_chromium
2014/01/07 09:52:25
You forgot to move the check if we're already disc
jansson
2014/01/07 10:22:35
Done.
| |
| 836 if (global.ourPeerId == null) | |
| 837 error_('Disconnecting, but we are not connected.'); | |
| 838 | |
| 839 request = new XMLHttpRequest(); | |
| 840 request.open('GET', global.serverUrl + '/sign_out?peer_id=' + | |
| 841 global.ourPeerId, false); | |
| 842 request.send(); | |
| 843 global.ourPeerId = null; | |
| 844 print_('ok-disconnected'); | |
| 845 } | |
| 846 | |
| 847 /** | |
| 848 * Returns true if we are disconnected from peerconnection_server. | |
| 849 */ | |
| 850 function isDisconnected_() { | |
| 851 return global.ourPeerId == null; | |
| 852 } | |
| 853 | |
| 854 /** | |
| 855 * @private | |
| 856 * @return {!string} The current peer connection's ready state, or | |
| 857 * 'no-peer-connection' if there is no peer connection up. | |
| 858 * | |
| 859 * NOTE: The PeerConnection states are changing and until chromium has | |
| 860 * implemented the new states we have to use this interim solution of | |
| 861 * always assuming that the PeerConnection is 'active'. | |
| 862 */ | |
| 863 function getReadyState_() { | |
| 864 if (global.peerConnection == null) | |
| 865 return 'no-peer-connection'; | |
| 866 | |
| 867 return 'active'; | |
| 868 } | |
| 869 | |
| 870 /** | |
| 871 * This function asks permission to use the webcam and mic from the browser. It | |
| 872 * will return ok-requested to the test. This does not mean the request was | |
| 873 * approved though. The test will then have to click past the dialog that | |
| 874 * appears in Chrome, which will run either the OK or failed callback as a | |
| 875 * a result. To see which callback was called, use obtainGetUserMediaResult_(). | |
| 876 * @private | |
| 877 * @param {string} constraints Defines what to be requested, with mandatory | |
| 878 * and optional constraints defined. The contents of this parameter depends | |
| 879 * on the WebRTC version. This should be JavaScript code that we eval(). | |
| 880 */ | |
| 881 function doGetUserMedia_(constraints) { | |
| 882 if (!getUserMedia) { | |
| 883 print_('Browser does not support WebRTC.'); | |
| 884 return; | |
| 885 } | |
| 886 try { | |
| 887 var evaluatedConstraints; | |
| 888 eval('evaluatedConstraints = ' + constraints); | |
| 889 } catch (exception) { | |
| 890 error_('Not valid JavaScript expression: ' + constraints); | |
| 891 } | |
| 892 print_('Requesting doGetUserMedia: constraints: ' + constraints); | |
| 893 getUserMedia(evaluatedConstraints, getUserMediaOkCallback_, | |
| 894 getUserMediaFailedCallback_); | |
| 895 } | |
| 896 | |
| 897 /** | |
| 898 * Must be called after calling doGetUserMedia. | |
| 899 * @private | |
| 900 * @return {string} Returns not-called-yet if we have not yet been called back | |
| 901 * by WebRTC. Otherwise it returns either ok-got-stream or | |
| 902 * failed-with-error-x (where x is the error code from the error | |
| 903 * callback) depending on which callback got called by WebRTC. | |
| 904 */ | |
| 905 function obtainGetUserMediaResult_() { | |
| 906 if (global.requestWebcamAndMicrophoneResult == null) | |
| 907 global.requestWebcamAndMicrophoneResult = ' not called yet'; | |
| 908 | |
| 909 return global.requestWebcamAndMicrophoneResult; | |
| 910 | |
| 911 } | |
| 912 | |
| 913 /** | |
| 914 * Negotiates a call with the other side. This will create a peer connection on | |
| 915 * the other side if there isn't one. The other side will automatically add any | |
| 916 * stream it has unless doNotAutoAddLocalStreamWhenCalled() has been called. | |
| 917 * | |
| 918 * To call this method we need to be aware of the other side, e.g. we must be | |
| 919 * connected to peerconnection_server and we must have exactly one peer on that | |
| 920 * server. | |
| 921 * | |
| 922 * This method may be called any number of times. If you haven't added any | |
| 923 * streams to the call, an "empty" call will result. The method will return | |
| 924 * ok-negotiating immediately to the test if the negotiation was successfully | |
| 925 * sent. | |
| 926 * @private | |
| 927 */ | |
| 928 function negotiateCall_() { | |
| 929 if (global.peerConnection == null) | |
| 930 error_('Negotiating call, but we have no peer connection.'); | |
| 931 if (global.ourPeerId == null) | |
| 932 error_('Negotiating call, but not connected.'); | |
| 933 if (global.remotePeerId == null) | |
| 934 error_('Negotiating call, but missing remote peer.'); | |
| 935 | |
| 936 setupCall(global.peerConnection); | |
| 937 print_('ok-negotiating'); | |
| 938 } | |
| 939 | |
| 940 /** | |
| 941 * This provides the selected source id from the objects in the parameters | |
| 942 * provided to this function. If the audio_select or video_select objects does | |
| 943 * not have any HTMLOptions children it will return null in the source object. | |
| 944 * @param {object} audio_select HTML drop down element with audio devices added | |
| 945 * as HTMLOptionsCollection children. | |
| 946 * @param {object} video_select HTML drop down element with audio devices added | |
| 947 * as HTMLPptionsCollection children. | |
| 948 * @return {object} audio_id video_id Containing audio and video source ID from | |
| 949 * the selected devices in the drop down menus provided as parameters to | |
| 950 * this function. | |
| 951 * @private | |
| 952 */ | |
| 953 function getSourcesFromField_(audio_select, video_select) { | |
| 954 var source = { | |
| 955 audio_id: null, | |
| 956 video_id: null | |
| 957 }; | |
| 958 if (audio_select.options.length > 0) { | |
| 959 source.audio_id = audio_select.options[audio_select.selectedIndex].value; | |
| 960 } | |
| 961 if (video_select.options.length > 0) { | |
| 962 source.video_id = video_select.options[video_select.selectedIndex].value; | |
| 963 } | |
| 964 return source; | |
| 965 } | |
| 966 | |
| 967 /** | |
| 968 * @private | |
| 969 * @param {NavigatorUserMediaError} error Error containing details. | |
| 970 */ | |
| 971 function getUserMediaFailedCallback_(error) { | |
| 972 print_('GetUserMedia FAILED: Maybe the camera is in use by another process?'); | |
| 973 gRequestWebcamAndMicrophoneResult = 'failed-with-error-' + error.name; | |
| 974 print_(gRequestWebcamAndMicrophoneResult); | |
| 975 } | |
| 976 | |
| 977 /** @private */ | |
| 978 function success_(method) { | |
| 979 print_(method + '(): success.'); | |
| 980 } | |
| 981 | |
| 982 /** @private */ | |
| 983 function failure_(method, error) { | |
| 984 error_(method + '() failed: ' + error); | |
| 985 } | |
| 986 | |
| 987 /** @private */ | |
| 988 function iceCallback_(event) { | |
| 989 if (event.candidate) | |
| 990 sendToPeer(global.remotePeerId, JSON.stringify(event.candidate)); | |
| 991 } | |
| 992 | |
| 993 /** @private */ | |
| 994 function setLocalAndSendMessage_(session_description) { | |
| 995 session_description.sdp = | |
| 996 global.transformOutgoingSdp(session_description.sdp); | |
| 997 global.peerConnection.setLocalDescription( | |
| 998 session_description, | |
| 999 function() { success_('setLocalDescription'); }, | |
| 1000 function() { failure_('setLocalDescription'); }); | |
| 1001 print_('Sending SDP message:\n' + session_description.sdp); | |
| 1002 sendToPeer(global.remotePeerId, JSON.stringify(session_description)); | |
| 1003 } | |
| 1004 | |
| 1005 /** @private */ | |
| 1006 function addStreamCallback_(event) { | |
| 1007 print_('Receiving remote stream...'); | |
| 1008 var videoTag = document.getElementById('remote-view'); | |
| 1009 attachMediaStream(videoTag, event.stream); | |
| 1010 | |
| 1011 window.addEventListener('loadedmetadata', function() { | |
| 1012 displayVideoSize_(videoTag);}, true); | |
| 1013 } | |
| 1014 | |
| 1015 /** @private */ | |
| 1016 function removeStreamCallback_(event) { | |
| 1017 print_('Call ended.'); | |
| 1018 document.getElementById('remote-view').src = ''; | |
| 1019 } | |
| 1020 | |
| 1021 /** @private */ | |
| 1022 function onCreateDataChannelCallback_(event) { | |
| 1023 if (global.dataChannel != null && global.dataChannel.readyState != 'closed') { | |
| 1024 error_('Received DataChannel, but we already have one.'); | |
| 1025 } | |
| 1026 | |
| 1027 global.dataChannel = event.channel; | |
| 1028 print_('DataChannel with label ' + global.dataChannel.label + | |
| 1029 ' initiated by remote peer.'); | |
| 1030 hookupDataChannelEvents(); | |
| 1031 } | |
| 1032 | |
| 1033 /** @private */ | |
| 1034 function hookupDataChannelEvents() { | |
| 1035 global.dataChannel.onmessage = global.dataCallback; | |
| 1036 global.dataChannel.onopen = onDataChannelReadyStateChange_; | |
| 1037 global.dataChannel.onclose = onDataChannelReadyStateChange_; | |
| 1038 // Trigger global.dataStatusCallback so an application is notified | |
| 1039 // about the created data channel. | |
| 1040 onDataChannelReadyStateChange_(); | |
| 1041 } | |
| 1042 | |
| 1043 /** @private */ | |
| 1044 function onDataChannelReadyStateChange_() { | |
| 1045 var readyState = global.dataChannel.readyState; | |
| 1046 print_('DataChannel state:' + readyState); | |
| 1047 global.dataStatusCallback(readyState); | |
| 1048 } | |
| 1049 | |
| 1050 /** | |
| 1051 * @private | |
| 1052 * @param {MediaStream} stream Media stream. | |
| 1053 */ | |
| 1054 function getUserMediaOkCallback_(stream) { | |
| 1055 global.localStream = stream; | |
| 1056 global.requestWebcamAndMicrophoneResult = 'ok-got-stream'; | |
| 1057 | |
| 1058 if (stream.getVideoTracks().length > 0) { | |
| 1059 // Show the video tag if we did request video in the getUserMedia call. | |
| 1060 var videoTag = $('local-view'); | |
| 1061 attachMediaStream(videoTag, stream); | |
| 1062 | |
| 1063 window.addEventListener('loadedmetadata', function() { | |
| 1064 displayVideoSize_(videoTag);}, true); | |
| 1065 } | |
| 1066 } | |
| 1067 | |
| 1068 /** | |
| 1069 * @private | |
| 1070 * @param {string} videoTag The ID of the video tag + stream used to | |
| 1071 * write the size to a HTML tag based on id if the div's exists. | |
| 1072 */ | |
| 1073 function displayVideoSize_(videoTag) { | |
| 1074 if ($(videoTag.id + '-stream-size') && $(videoTag.id + '-size')) { | |
| 1075 if (videoTag.videoWidth > 0 || videoTag.videoHeight > 0) { | |
| 1076 $(videoTag.id + '-stream-size').innerHTML = '(stream size: ' + | |
| 1077 videoTag.videoWidth + 'x' + | |
| 1078 videoTag.videoHeight + ')'; | |
| 1079 $(videoTag.id + '-size').innerHTML = videoTag.width + 'x' + | |
| 1080 videoTag.height; | |
| 1081 } | |
| 1082 } else { | |
| 1083 print_('Skipping updating -stream-size and -size tags due to div\'s ' + | |
| 1084 'are missing'); | |
| 1085 } | |
| 1086 } | |
| 247 | 1087 |
| 248 /** | 1088 /** |
| 249 * Checks if the 'audiosrc' and 'videosrc' drop down menu elements has had all | 1089 * Checks if the 'audiosrc' and 'videosrc' drop down menu elements has had all |
| 250 * of its children appended in order to provide device ID's to the function | 1090 * of its children appended in order to provide device ID's to the function |
| 251 * 'updateGetUserMediaConstraints()', used in turn to populate the getUserMedia | 1091 * 'updateGetUserMediaConstraints()', used in turn to populate the getUserMedia |
| 252 * constraints text box when the page has loaded. If not the user is informed | 1092 * constraints text box when the page has loaded. |
| 253 * and instructions on how to populate the field is provided. | 1093 * @private |
| 254 */ | 1094 */ |
| 255 function checkIfDeviceDropdownsArePopulated() { | 1095 function checkIfDeviceDropdownsArePopulated_() { |
| 256 if (document.addEventListener) { | 1096 if (document.addEventListener) { |
| 257 $('audiosrc').addEventListener('DOMNodeInserted', | 1097 $('audiosrc').addEventListener('DOMNodeInserted', |
| 258 updateGetUserMediaConstraints, false); | 1098 updateGetUserMediaConstraints, false); |
| 259 $('videosrc').addEventListener('DOMNodeInserted', | 1099 $('videosrc').addEventListener('DOMNodeInserted', |
| 260 updateGetUserMediaConstraints, false); | 1100 updateGetUserMediaConstraints, false); |
| 261 } | 1101 } else { |
| 262 else { | 1102 print_('addEventListener is not supported by your browser, cannot update ' + |
| 263 debug('addEventListener is not supported by your browser, cannot update ' + | 1103 'device source ID\'s automatically. Select a device from the audio' + |
| 264 'device source ID\'s automatically. Select a device from the audio ' + | 1104 ' or video source drop down menu to update device source id\'s'); |
| 265 'or video source drop down menu to update device source id\'s'); | 1105 } |
| 266 } | 1106 } |
| 267 } | 1107 |
| 268 | 1108 /** |
| 269 /** | 1109 * Register an input element to use local storage to remember its state between |
| 270 * Disconnect before the tab is closed. | 1110 * sessions (using local storage). Only input elements are supported. |
| 271 */ | 1111 * @private |
| 272 window.onbeforeunload = function() { | 1112 * @param {!string} element_id to be used as a key for local storage and the id |
| 273 if (!isDisconnected()) | 1113 * of the element to store the state for. |
| 274 disconnect(); | 1114 */ |
| 275 }; | 1115 function registerLocalStorage_(element_id) { |
| 276 | 1116 var element = $(element_id); |
| 277 // Internals. | 1117 if (element.tagName != 'INPUT') { |
| 1118 error_('You can only use registerLocalStorage_ for input elements. ' + | |
| 1119 'Element \"' + element.tagName + '\" is not an input element. '); | |
| 1120 } | |
| 1121 | |
| 1122 if (localStorage.getItem(element.id) == 'undefined') { | |
| 1123 storeLocalStorageField_(element); | |
| 1124 } else { | |
| 1125 getLocalStorageField_(element); | |
| 1126 } | |
| 1127 // Registers the appropriate events for input elements. | |
| 1128 if (element.type == 'checkbox') { | |
| 1129 element.onclick = function() { storeLocalStorageField_(this); }; | |
| 1130 } else if (element.type == 'text') { | |
| 1131 element.onblur = function() { storeLocalStorageField_(this); }; | |
| 1132 } else { | |
| 1133 error_('Unsupportered input type: ' + '\"' + element.type + '\"'); | |
| 1134 } | |
| 1135 } | |
| 1136 | |
| 1137 /** | |
| 1138 * Fetches the stored values from local storage and updates checkbox status. | |
| 1139 * @private | |
| 1140 * @param {!Object} element of which id is representing the key parameter for | |
| 1141 * local storage. | |
| 1142 */ | |
| 1143 function getLocalStorageField_(element) { | |
| 1144 // Makes sure the checkbox status is matching the local storage value. | |
| 1145 if (element.type == 'checkbox') { | |
| 1146 element.checked = (localStorage.getItem(element.id) == 'true'); | |
| 1147 } else if (element.type == 'text') { | |
| 1148 element.value = localStorage.getItem(element.id); | |
| 1149 } else { | |
| 1150 error_('Unsupportered input type: ' + '\"' + element.type + '\"'); | |
| 1151 } | |
| 1152 } | |
| 1153 | |
| 1154 /** | |
| 1155 * Stores the string value of the element object using local storage. | |
| 1156 * @private | |
| 1157 * @param {!Object} element of which id is representing the key parameter for | |
| 1158 * local storage. | |
| 1159 */ | |
| 1160 function storeLocalStorageField_(element) { | |
| 1161 if (element.type == 'checkbox') { | |
| 1162 localStorage.setItem(element.id, element.checked); | |
| 1163 } else if (element.type == 'text') { | |
| 1164 localStorage.setItem(element.id, element.value); | |
| 1165 } | |
| 1166 } | |
| 278 | 1167 |
| 279 /** | 1168 /** |
| 280 * Create the peer connection if none is up (this is just convenience to | 1169 * Create the peer connection if none is up (this is just convenience to |
| 281 * avoid having a separate button for that). | 1170 * avoid having a separate button for that). |
| 282 * @private | 1171 * @private |
| 283 */ | 1172 */ |
| 284 function ensureHasPeerConnection_() { | 1173 function ensureHasPeerConnection_() { |
| 285 if (getReadyState() == 'no-peer-connection') { | 1174 if (getReadyState_() == 'no-peer-connection') { |
| 286 preparePeerConnection(); | 1175 preparePeerConnection(); |
| 287 } | 1176 } |
| 288 } | 1177 } |
| 289 | 1178 |
| 290 /** | 1179 /** |
| 291 * @private | 1180 * @private |
| 292 * @param {string} message Text to print. | 1181 * @param {string} message Text to print. |
| 293 */ | 1182 */ |
| 294 function print_(message) { | 1183 function print_(message) { |
| 295 // Filter out uninteresting noise. | |
| 296 if (message == 'ok-no-errors') | |
| 297 return; | |
| 298 | |
| 299 console.log(message); | 1184 console.log(message); |
| 300 $('messages').innerHTML += message + '<br>'; | 1185 $('messages').innerHTML += message + '<br>'; |
| 301 } | 1186 } |
| 302 | 1187 |
| 303 /** | 1188 /** |
| 304 * @private | 1189 * @private |
| 305 * @param {string} message Text to print. | 1190 * @param {string} message Text to print. |
| 306 */ | 1191 */ |
| 307 function debug_(message) { | 1192 function debug_(message) { |
| 308 console.log(message); | 1193 console.log(message); |
| 309 $('debug').innerHTML += message + '<br>'; | 1194 $('debug').innerHTML += message + '<br>'; |
| 310 } | 1195 } |
| 311 | 1196 |
| 312 /** | 1197 /** |
| 313 * Print error message in the debug log + JS console and throw an Error. | 1198 * Print error message in the debug log + JS console and throw an Error. |
| 314 * @private | 1199 * @private |
| 315 * @param {string} message Text to print. | 1200 * @param {string} message Text to print. |
| 316 */ | 1201 */ |
| 317 function error(message) { | 1202 function error_(message) { |
| 318 $('debug').innerHTML += '<span style="color:red;">' + message + '</span><br>'; | 1203 $('debug').innerHTML += '<span style="color:red;">' + message + '</span><br>'; |
| 319 throw new Error(message); | 1204 throw new Error(message); |
| 320 } | 1205 } |
| 321 | 1206 |
| 322 /** | 1207 /** |
| 323 * @private | 1208 * @private |
| 324 * @param {string} stringRepresentation JavaScript as a string. | 1209 * @param {string} stringRepresentation JavaScript as a string. |
| 325 * @return {Object} The PeerConnection constraints as a JavaScript dictionary. | 1210 * @return {Object} The PeerConnection constraints as a JavaScript dictionary. |
| 326 */ | 1211 */ |
| 327 function getEvaluatedJavaScript_(stringRepresentation) { | 1212 function getEvaluatedJavaScript_(stringRepresentation) { |
| 328 try { | 1213 try { |
| 329 var evaluatedJavaScript; | 1214 var evaluatedJavaScript; |
| 330 eval('evaluatedJavaScript = ' + stringRepresentation); | 1215 eval('evaluatedJavaScript = ' + stringRepresentation); |
| 331 return evaluatedJavaScript; | 1216 return evaluatedJavaScript; |
| 332 } catch (exception) { | 1217 } catch (exception) { |
| 333 error('Not valid JavaScript expression: ' + stringRepresentation); | 1218 error_('Not valid JavaScript expression: ' + stringRepresentation); |
| 334 } | 1219 } |
| 335 } | 1220 } |
| 336 | 1221 |
| 337 /** | 1222 /** |
| 338 * Swaps lines within a SDP message. | 1223 * Swaps lines within a SDP message. |
| 339 * @private | 1224 * @private |
| 340 * @param {string} sdp The full SDP message. | 1225 * @param {string} sdp The full SDP message. |
| 341 * @param {string} line The line to swap with swapWith. | 1226 * @param {string} line The line to swap with swapWith. |
| 342 * @param {string} swapWith The other line. | 1227 * @param {string} swapWith The other line. |
| 343 * @return {string} The altered SDP message. | 1228 * @return {string} The altered SDP message. |
| 344 */ | 1229 */ |
| 345 function swapSdpLines_(sdp, line, swapWith) { | 1230 function swapSdpLines_(sdp, line, swapWith) { |
| 346 var lines = sdp.split('\r\n'); | 1231 var lines = sdp.split('\r\n'); |
| 347 var lineStart = lines.indexOf(line); | 1232 var lineStart = lines.indexOf(line); |
| 348 var swapLineStart = lines.indexOf(swapWith); | 1233 var swapLineStart = lines.indexOf(swapWith); |
| 349 if (lineStart == -1 || swapLineStart == -1) | 1234 if (lineStart == -1 || swapLineStart == -1) |
| 350 return sdp; // This generally happens on the first message. | 1235 return sdp; // This generally happens on the first message. |
| 351 | 1236 |
| 352 var tmp = lines[lineStart]; | 1237 var tmp = lines[lineStart]; |
| 353 lines[lineStart] = lines[swapLineStart]; | 1238 lines[lineStart] = lines[swapLineStart]; |
| 354 lines[swapLineStart] = tmp; | 1239 lines[swapLineStart] = tmp; |
| 355 | 1240 |
| 356 return lines.join('\r\n'); | 1241 return lines.join('\r\n'); |
| 357 } | 1242 } |
| 358 | 1243 |
| 359 // TODO(phoglund): Not currently used; delete once it clear we do not need to | |
| 360 // test opus prioritization. | |
| 361 /** @private */ | |
| 362 function preferOpus_() { | |
| 363 setOutgoingSdpTransform(function(sdp) { | |
| 364 sdp = sdp.replace('103 104 111', '111 103 104'); | |
| 365 | |
| 366 // TODO(phoglund): We need to swap the a= lines too. I don't think this | |
| 367 // should be needed but it apparently is right now. | |
| 368 return swapSdpLines_(sdp, | |
| 369 'a=rtpmap:103 ISAC/16000', | |
| 370 'a=rtpmap:111 opus/48000'); | |
| 371 }); | |
| 372 } | |
| 373 | |
| 374 /** @private */ | 1244 /** @private */ |
| 375 function forceIsac_() { | 1245 function forceIsac_() { |
| 376 setOutgoingSdpTransform(function(sdp) { | 1246 setOutgoingSdpTransform(function(sdp) { |
| 377 // Remove all other codecs (not the video codecs though). | 1247 // Remove all other codecs (not the video codecs though). |
| 378 sdp = sdp.replace(/m=audio (\d+) RTP\/SAVPF.*\r\n/g, | 1248 sdp = sdp.replace(/m=audio (\d+) RTP\/SAVPF.*\r\n/g, |
| 379 'm=audio $1 RTP/SAVPF 104\r\n'); | 1249 'm=audio $1 RTP/SAVPF 104\r\n'); |
| 380 sdp = sdp.replace('a=fmtp:111 minptime=10', 'a=fmtp:104 minptime=10'); | 1250 sdp = sdp.replace('a=fmtp:111 minptime=10', 'a=fmtp:104 minptime=10'); |
| 381 sdp = sdp.replace(/a=rtpmap:(?!104)\d{1,3} (?!VP8|red|ulpfec).*\r\n/g, ''); | 1251 sdp = sdp.replace(/a=rtpmap:(?!104)\d{1,3} (?!VP8|red|ulpfec).*\r\n/g, ''); |
| 382 return sdp; | 1252 return sdp; |
| 383 }); | 1253 }); |
| 384 } | 1254 } |
| 385 | 1255 |
| 386 /** @private */ | 1256 /** @private */ |
| 387 function dontTouchSdp_() { | 1257 function dontTouchSdp_() { |
| 388 setOutgoingSdpTransform(function(sdp) { return sdp; }); | 1258 setOutgoingSdpTransform(function(sdp) { return sdp; }); |
| 389 } | 1259 } |
| 390 | 1260 |
| 391 /** @private */ | 1261 /** @private */ |
| 392 function hookupDataChannelCallbacks_() { | 1262 function hookupDataChannelCallbacks_() { |
| 393 setDataCallbacks(function(status) { | 1263 setDataCallbacks(function(status) { |
| 394 $('data-channel-status').value = status; | 1264 $('data-channel-status').value = status; |
| 395 }, | 1265 }, |
| 396 function(data_message) { | 1266 function(data_message) { |
| 397 debug('Received ' + data_message.data); | 1267 print_('Received ' + data_message.data); |
| 398 $('data-channel-receive').value = | 1268 $('data-channel-receive').value = |
| 399 data_message.data + '\n' + $('data-channel-receive').value; | 1269 data_message.data + '\n' + $('data-channel-receive').value; |
| 400 }); | 1270 }); |
| 401 } | 1271 } |
| 402 | 1272 |
| 403 /** @private */ | 1273 /** @private */ |
| 404 function hookupDtmfSenderCallback_() { | 1274 function hookupDtmfSenderCallback_() { |
| 405 setOnToneChange(function(tone) { | 1275 setOnToneChange(function(tone) { |
| 406 debug('Sent DTMF tone: ' + tone.tone); | 1276 print_('Sent DTMF tone: ' + tone.tone); |
| 407 $('dtmf-tones-sent').value = | 1277 $('dtmf-tones-sent').value = |
| 408 tone.tone + '\n' + $('dtmf-tones-sent').value; | 1278 tone.tone + '\n' + $('dtmf-tones-sent').value; |
| 409 }); | 1279 }); |
| 410 } | 1280 } |
| 411 | 1281 |
| 412 /** | 1282 /** @private */ |
| 413 * A list of element id's to be registered for local storage. | 1283 function toggle_(track, localOrRemote, audioOrVideo) { |
| 414 */ | 1284 if (!track) |
| 415 function setupLocalStorageFieldValues() { | 1285 error_('Tried to toggle ' + localOrRemote + ' ' + audioOrVideo + |
| 416 registerLocalStorage_('pc-server'); | 1286 ' stream, but has no such stream.'); |
| 417 registerLocalStorage_('pc-createanswer-constraints'); | 1287 |
| 418 registerLocalStorage_('pc-createoffer-constraints'); | 1288 track.enabled = !track.enabled; |
| 419 registerLocalStorage_('get-devices-onload'); | 1289 print_('ok-' + audioOrVideo + '-toggled-to-' + track.enabled); |
| 420 } | 1290 } |
| 421 | 1291 |
| 422 /** | 1292 /** @private */ |
| 423 * Register an input element to use local storage to remember its state between | 1293 function connectCallback_(request) { |
| 424 * sessions (using local storage). Only input elements are supported. | 1294 print_('Connect callback: ' + request.status + ', ' + request.readyState); |
| 425 * @private | 1295 if (request.status == 0) { |
| 426 * @param {!string} element_id to be used as a key for local storage and the id | 1296 print_('peerconnection_server doesn\'t seem to be up.'); |
| 427 * of the element to store the state for. | 1297 print_('failed-to-connect'); |
| 428 */ | |
| 429 function registerLocalStorage_(element_id) { | |
| 430 var element = $(element_id); | |
| 431 if (element.tagName != 'INPUT') { | |
| 432 error('You can only use registerLocalStorage_ for input elements. ' + | |
| 433 'Element \"' + element.tagName +'\" is not an input element. '); | |
| 434 } | 1298 } |
| 435 | 1299 if (request.readyState == 4 && request.status == 200) { |
| 436 if (localStorage.getItem(element.id) == 'undefined') { | 1300 global.ourPeerId = parseOurPeerId_(request.responseText); |
| 437 storeLocalStorageField_(element); | 1301 global.remotePeerId = parseRemotePeerIdIfConnected_(request.responseText); |
| 438 } else { | 1302 startHangingGet_(global.serverUrl, global.ourPeerId); |
| 439 getLocalStorageField_(element); | 1303 print_('ok-connected'); |
| 440 } | |
| 441 // Registers the appropriate events for input elements. | |
| 442 if (element.type == 'checkbox') { | |
| 443 element.onclick = function() { storeLocalStorageField_(this); }; | |
| 444 } else if (element.type == 'text') { | |
| 445 element.onblur = function() { storeLocalStorageField_(this); }; | |
| 446 } else { | |
| 447 error('Unsupportered input type: ' + '\"' + element.type + '\"' ); | |
| 448 } | 1304 } |
| 449 } | 1305 } |
| 450 | 1306 |
| 451 /** | 1307 /** @private */ |
| 452 * Fetches the stored values from local storage and updates checkbox status. | 1308 function parseOurPeerId_(responseText) { |
| 453 * @private | 1309 // According to peerconnection_server's protocol. |
| 454 * @param {!Object} element of which id is representing the key parameter for | 1310 var peerList = responseText.split('\n'); |
| 455 * local storage. | 1311 return parseInt(peerList[0].split(',')[1]); |
| 456 */ | 1312 } |
| 457 function getLocalStorageField_(element) { | 1313 |
| 458 // Makes sure the checkbox status is matching the local storage value. | 1314 /** @private */ |
| 459 if (element.type == 'checkbox') { | 1315 function parseRemotePeerIdIfConnected_(responseText) { |
| 460 element.checked = (localStorage.getItem(element.id) == 'true'); | 1316 var peerList = responseText.split('\n'); |
| 461 } else if (element.type == 'text' ) { | 1317 if (peerList.length == 1) { |
| 462 element.value = localStorage.getItem(element.id); | 1318 // No peers have connected yet - we'll get their id later in a notification. |
| 463 } else { | 1319 return null; |
| 464 error('Unsupportered input type: ' + '\"' + element.type + '\"' ); | 1320 } |
| 1321 var remotePeerId = null; | |
| 1322 for (var i = 0; i < peerList.length; i++) { | |
| 1323 if (peerList[i].length == 0) | |
| 1324 continue; | |
| 1325 | |
| 1326 var parsed = peerList[i].split(','); | |
| 1327 var name = parsed[0]; | |
| 1328 var id = parsed[1]; | |
| 1329 | |
| 1330 if (id != global.ourPeerId) { | |
| 1331 print_('Found remote peer with name ' + name + ', id ' + | |
| 1332 id + ' when connecting.'); | |
| 1333 | |
| 1334 // There should be at most one remote peer in this test. | |
| 1335 if (remotePeerId != null) | |
| 1336 error_('Expected just one remote peer in this test: ' + | |
| 1337 'found several.'); | |
| 1338 | |
| 1339 // Found a remote peer. | |
| 1340 remotePeerId = id; | |
| 1341 } | |
| 1342 } | |
| 1343 return remotePeerId; | |
| 1344 } | |
| 1345 | |
| 1346 /** @private */ | |
| 1347 function startHangingGet_(server, ourId) { | |
| 1348 if (isDisconnected_()) | |
|
phoglund_chromium
2014/01/07 09:52:25
Not sure why you removed this?
jansson
2014/01/07 10:22:35
Not sure, what I was thinking, added it back.
| |
| 1349 return; | |
| 1350 hangingGetRequest = new XMLHttpRequest(); | |
| 1351 hangingGetRequest.onreadystatechange = function() { | |
| 1352 hangingGetCallback_(hangingGetRequest, server, ourId); | |
| 1353 }; | |
| 1354 hangingGetRequest.ontimeout = function() { | |
| 1355 hangingGetTimeoutCallback_(hangingGetRequest, server, ourId); | |
| 1356 }; | |
| 1357 callUrl = server + '/wait?peer_id=' + ourId; | |
| 1358 print_('Sending ' + callUrl); | |
| 1359 hangingGetRequest.open('GET', callUrl, true); | |
| 1360 hangingGetRequest.send(); | |
| 1361 } | |
| 1362 | |
| 1363 /** @private */ | |
| 1364 function hangingGetCallback_(hangingGetRequest, server, ourId) { | |
| 1365 if (hangingGetRequest.readyState != 4 || hangingGetRequest.status == 0) { | |
| 1366 // Code 0 is not possible if the server actually responded. Ignore. | |
| 1367 return; | |
| 1368 } | |
| 1369 if (hangingGetRequest.status != 200) { | |
| 1370 error_('Error ' + hangingGetRequest.status + ' from server: ' + | |
| 1371 hangingGetRequest.statusText); | |
| 1372 } | |
| 1373 var targetId = readResponseHeader_(hangingGetRequest, 'Pragma'); | |
| 1374 if (targetId == ourId) | |
| 1375 handleServerNotification_(hangingGetRequest.responseText); | |
| 1376 else | |
| 1377 handlePeerMessage_(targetId, hangingGetRequest.responseText); | |
| 1378 | |
| 1379 hangingGetRequest.abort(); | |
| 1380 restartHangingGet_(server, ourId); | |
| 1381 } | |
| 1382 | |
| 1383 /** @private */ | |
| 1384 function hangingGetTimeoutCallback_(hangingGetRequest, server, ourId) { | |
| 1385 print_('Hanging GET times out, re-issuing...'); | |
| 1386 hangingGetRequest.abort(); | |
| 1387 restartHangingGet_(server, ourId); | |
| 1388 } | |
| 1389 | |
| 1390 /** @private */ | |
| 1391 function handleServerNotification_(message) { | |
| 1392 var parsed = message.split(','); | |
| 1393 if (parseInt(parsed[2]) == 1) { | |
| 1394 // Peer connected - this must be our remote peer, and it must mean we | |
| 1395 // connected before them (except if we happened to connect to the server | |
| 1396 // at precisely the same moment). | |
| 1397 print_('Found remote peer with name ' + parsed[0] + ', id ' + parsed[1] + | |
| 1398 ' when connecting.'); | |
| 1399 global.remotePeerId = parseInt(parsed[1]); | |
| 465 } | 1400 } |
| 466 } | 1401 } |
| 467 | 1402 |
| 468 /** | 1403 /** @private */ |
| 469 * Stores the string value of the element object using local storage. | 1404 function closeCall_() { |
| 470 * @private | 1405 if (global.peerConnection == null) |
| 471 * @param {!Object} element of which id is representing the key parameter for | 1406 debug_('Closing call, but no call active.'); |
| 472 * local storage. | 1407 global.peerConnection.close(); |
| 473 */ | 1408 global.peerConnection = null; |
| 474 function storeLocalStorageField_(element) { | 1409 } |
| 475 if (element.type == 'checkbox') { | 1410 |
| 476 localStorage.setItem(element.id, element.checked); | 1411 /** @private */ |
| 477 } else if (element.type == 'text') { | 1412 function handlePeerMessage_(peerId, message) { |
| 478 localStorage.setItem(element.id, element.value); | 1413 print_('Received message from peer ' + peerId + ': ' + message); |
| 1414 if (peerId != global.remotePeerId) { | |
| 1415 error_('Received notification from unknown peer ' + peerId + | |
| 1416 ' (only know about ' + global.remotePeerId + '.'); | |
| 479 } | 1417 } |
| 1418 if (message.search('BYE') == 0) { | |
| 1419 print_('Received BYE from peer: closing call'); | |
| 1420 closeCall_(); | |
| 1421 return; | |
| 1422 } | |
| 1423 if (global.peerConnection == null && global.acceptsIncomingCalls) { | |
| 1424 // The other side is calling us. | |
| 1425 print_('We are being called: answer...'); | |
| 1426 | |
| 1427 global.peerConnection = createPeerConnection(STUN_SERVER, | |
| 1428 global.useRtpDataChannels); | |
| 1429 if ($('auto-add-stream-oncall') && | |
| 1430 obtainGetUserMediaResult_() == 'ok-got-stream') { | |
| 1431 print_('We have a local stream, so hook it up automatically.'); | |
| 1432 addLocalStreamToPeerConnection(global.peerConnection); | |
| 1433 } | |
| 1434 answerCall(global.peerConnection, message); | |
| 1435 return; | |
| 1436 } | |
| 1437 handleMessage(global.peerConnection, message); | |
| 480 } | 1438 } |
| 1439 | |
| 1440 /** @private */ | |
| 1441 function restartHangingGet_(server, ourId) { | |
| 1442 window.setTimeout(function() { | |
| 1443 startHangingGet_(server, ourId); | |
| 1444 }, 0); | |
| 1445 } | |
| 1446 | |
| 1447 /** @private */ | |
| 1448 function readResponseHeader_(request, key) { | |
| 1449 var value = request.getResponseHeader(key); | |
| 1450 if (value == null || value.length == 0) { | |
| 1451 error_('Received empty value ' + value + | |
| 1452 ' for response header key ' + key + '.'); | |
| 1453 } | |
| 1454 return parseInt(value); | |
| 1455 } | |
| OLD | NEW |