Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(224)

Side by Side Diff: chrome/test/data/webrtc/manual/peerconnection.js

Issue 110853006: Combining all javascript files to one for the manual test page. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: fixing nits Created 6 years, 12 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
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
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 }
OLDNEW
« no previous file with comments | « chrome/test/data/webrtc/manual/peerconnection.html ('k') | chrome/test/data/webrtc/manual/stylesheet.css » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698