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

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