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

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