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

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 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
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 62 matching lines...) Expand 10 before | Expand all | Expand 10 after
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 audio_selected = $('audiosrc');
160 var video_selected = $('videosrc'); 204 var video_selected = $('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 };
209
165 if (audio_selected.disabled == false && video_selected.disabled == false) { 210 if (audio_selected.disabled == false && video_selected.disabled == false) {
166 var devices = getSourcesFromField(audio_selected, video_selected); 211 var devices = getSourcesFromField_(audio_selected, video_selected);
167 if ($('audio').checked == true) { 212 if ($('audio').checked == true) {
168 if (devices.audio_id != null) { 213 if (devices.audio_id != null) {
169 constraints.audio = {optional: [{sourceId: devices.audio_id}]}; 214 constraints.audio = {optional: [{sourceId: devices.audio_id}]};
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.video_id != null) {
183 constraints.video.optional.push({sourceId: devices.video_id}); 227 constraints.video.optional.push({sourceId: devices.video_id});
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 audio_select or video_select objects does
855 * not have any HTMLOptions children it will return null in the source object.
856 * @param {object} audio_select HTML drop down element with audio devices added
857 * as HTMLOptionsCollection children.
858 * @param {object} video_select HTML drop down element with audio devices added
859 * as HTMLPptionsCollection children.
860 * @return {object} audio_id video_id Containing audio and video source ID from
861 * the selected devices in the drop down menus provided as parameters to
862 * this function.
863 * @private
864 */
865 function getSourcesFromField_(audio_select, video_select) {
kjellander_chromium 2014/01/07 10:34:17 Please correct the function parameter variable nam
866 var source = {
867 audio_id: null,
868 video_id: null
869 };
870 if (audio_select.options.length > 0) {
871 source.audio_id = audio_select.options[audio_select.selectedIndex].value;
872 }
873 if (video_select.options.length > 0) {
874 source.video_id = video_select.options[video_select.selectedIndex].value;
875 }
876 return source;
877 }
878
879 /**
880 * @private
881 * @param {NavigatorUserMediaError} error Error containing details.
882 */
883 function getUserMediaFailedCallback_(error) {
884 print_('GetUserMedia FAILED: Maybe the camera is in use by another process?');
885 gRequestWebcamAndMicrophoneResult = 'failed-with-error-' + error.name;
886 print_(gRequestWebcamAndMicrophoneResult);
887 }
888
889 /** @private */
890 function success_(method) {
891 print_(method + '(): success.');
892 }
893
894 /** @private */
895 function failure_(method, error) {
896 error_(method + '() failed: ' + error);
897 }
898
899 /** @private */
900 function iceCallback_(event) {
901 if (event.candidate)
902 sendToPeer(global.remotePeerId, JSON.stringify(event.candidate));
903 }
904
905 /** @private */
906 function setLocalAndSendMessage_(session_description) {
907 session_description.sdp =
908 global.transformOutgoingSdp(session_description.sdp);
909 global.peerConnection.setLocalDescription(
910 session_description,
911 function() { success_('setLocalDescription'); },
912 function() { failure_('setLocalDescription'); });
913 print_('Sending SDP message:\n' + session_description.sdp);
914 sendToPeer(global.remotePeerId, JSON.stringify(session_description));
915 }
916
917 /** @private */
918 function addStreamCallback_(event) {
919 print_('Receiving remote stream...');
920 var videoTag = document.getElementById('remote-view');
921 attachMediaStream(videoTag, event.stream);
922
923 window.addEventListener('loadedmetadata', function() {
924 displayVideoSize_(videoTag);}, true);
925 }
926
927 /** @private */
928 function removeStreamCallback_(event) {
929 print_('Call ended.');
930 document.getElementById('remote-view').src = '';
931 }
932
933 /** @private */
934 function onCreateDataChannelCallback_(event) {
935 if (global.dataChannel != null && global.dataChannel.readyState != 'closed') {
936 error_('Received DataChannel, but we already have one.');
937 }
938
939 global.dataChannel = event.channel;
940 print_('DataChannel with label ' + global.dataChannel.label +
941 ' initiated by remote peer.');
942 hookupDataChannelEvents();
943 }
944
945 /** @private */
946 function hookupDataChannelEvents() {
947 global.dataChannel.onmessage = global.dataCallback;
948 global.dataChannel.onopen = onDataChannelReadyStateChange_;
949 global.dataChannel.onclose = onDataChannelReadyStateChange_;
950 // Trigger global.dataStatusCallback so an application is notified
951 // about the created data channel.
952 onDataChannelReadyStateChange_();
953 }
954
955 /** @private */
956 function onDataChannelReadyStateChange_() {
957 var readyState = global.dataChannel.readyState;
958 print_('DataChannel state:' + readyState);
959 global.dataStatusCallback(readyState);
960 }
961
962 /**
963 * @private
964 * @param {MediaStream} stream Media stream.
965 */
966 function getUserMediaOkCallback_(stream) {
967 global.localStream = stream;
968 global.requestWebcamAndMicrophoneResult = 'ok-got-stream';
969
970 if (stream.getVideoTracks().length > 0) {
971 // Show the video tag if we did request video in the getUserMedia call.
972 var videoTag = $('local-view');
973 attachMediaStream(videoTag, stream);
974
975 window.addEventListener('loadedmetadata', function() {
976 displayVideoSize_(videoTag);}, true);
977 }
978 }
979
980 /**
981 * @private
982 * @param {string} videoTag The ID of the video tag + stream used to
983 * write the size to a HTML tag based on id if the div's exists.
984 */
985 function displayVideoSize_(videoTag) {
986 if ($(videoTag.id + '-stream-size') && $(videoTag.id + '-size')) {
987 if (videoTag.videoWidth > 0 || videoTag.videoHeight > 0) {
988 $(videoTag.id + '-stream-size').innerHTML = '(stream size: ' +
989 videoTag.videoWidth + 'x' +
990 videoTag.videoHeight + ')';
991 $(videoTag.id + '-size').innerHTML = videoTag.width + 'x' +
992 videoTag.height;
993 }
994 } else {
995 print_('Skipping updating -stream-size and -size tags due to div\'s ' +
996 'are missing');
997 }
998 }
247 999
248 /** 1000 /**
249 * Checks if the 'audiosrc' and 'videosrc' drop down menu elements has had all 1001 * 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 1002 * of its children appended in order to provide device ID's to the function
251 * 'updateGetUserMediaConstraints()', used in turn to populate the getUserMedia 1003 * 'updateGetUserMediaConstraints()', used in turn to populate the getUserMedia
252 * constraints text box when the page has loaded. If not the user is informed 1004 * constraints text box when the page has loaded.
253 * and instructions on how to populate the field is provided. 1005 * @private
254 */ 1006 */
255 function checkIfDeviceDropdownsArePopulated() { 1007 function checkIfDeviceDropdownsArePopulated_() {
256 if (document.addEventListener) { 1008 if (document.addEventListener) {
257 $('audiosrc').addEventListener('DOMNodeInserted', 1009 $('audiosrc').addEventListener('DOMNodeInserted',
258 updateGetUserMediaConstraints, false); 1010 updateGetUserMediaConstraints, false);
259 $('videosrc').addEventListener('DOMNodeInserted', 1011 $('videosrc').addEventListener('DOMNodeInserted',
260 updateGetUserMediaConstraints, false); 1012 updateGetUserMediaConstraints, false);
261 } 1013 } else {
262 else { 1014 print_('addEventListener is not supported by your browser, cannot update ' +
263 debug('addEventListener is not supported by your browser, cannot update ' + 1015 'device source ID\'s automatically. Select a device from the audio' +
264 'device source ID\'s automatically. Select a device from the audio ' + 1016 ' 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'); 1017 }
266 } 1018 }
267 } 1019
268 1020 /**
269 /** 1021 * Register an input element to use local storage to remember its state between
270 * Disconnect before the tab is closed. 1022 * sessions (using local storage). Only input elements are supported.
271 */ 1023 * @private
272 window.onbeforeunload = function() { 1024 * @param {!string} element_id to be used as a key for local storage and the id
273 if (!isDisconnected()) 1025 * of the element to store the state for.
274 disconnect(); 1026 */
275 }; 1027 function registerLocalStorage_(element_id) {
276 1028 var element = $(element_id);
277 // Internals. 1029 if (element.tagName != 'INPUT') {
1030 error_('You can only use registerLocalStorage_ for input elements. ' +
1031 'Element \"' + element.tagName + '\" is not an input element. ');
1032 }
1033
1034 if (localStorage.getItem(element.id) == 'undefined') {
1035 storeLocalStorageField_(element);
1036 } else {
1037 getLocalStorageField_(element);
1038 }
1039 // Registers the appropriate events for input elements.
1040 if (element.type == 'checkbox') {
1041 element.onclick = function() { storeLocalStorageField_(this); };
1042 } else if (element.type == 'text') {
1043 element.onblur = function() { storeLocalStorageField_(this); };
1044 } else {
1045 error_('Unsupportered input type: ' + '\"' + element.type + '\"');
1046 }
1047 }
1048
1049 /**
1050 * Fetches the stored values from local storage and updates checkbox status.
1051 * @private
1052 * @param {!Object} element of which id is representing the key parameter for
1053 * local storage.
1054 */
1055 function getLocalStorageField_(element) {
1056 // Makes sure the checkbox status is matching the local storage value.
1057 if (element.type == 'checkbox') {
1058 element.checked = (localStorage.getItem(element.id) == 'true');
1059 } else if (element.type == 'text') {
1060 element.value = localStorage.getItem(element.id);
1061 } else {
1062 error_('Unsupportered input type: ' + '\"' + element.type + '\"');
1063 }
1064 }
1065
1066 /**
1067 * Stores the string value of the element object using local storage.
1068 * @private
1069 * @param {!Object} element of which id is representing the key parameter for
1070 * local storage.
1071 */
1072 function storeLocalStorageField_(element) {
1073 if (element.type == 'checkbox') {
1074 localStorage.setItem(element.id, element.checked);
1075 } else if (element.type == 'text') {
1076 localStorage.setItem(element.id, element.value);
1077 }
1078 }
278 1079
279 /** 1080 /**
280 * Create the peer connection if none is up (this is just convenience to 1081 * Create the peer connection if none is up (this is just convenience to
281 * avoid having a separate button for that). 1082 * avoid having a separate button for that).
282 * @private 1083 * @private
283 */ 1084 */
284 function ensureHasPeerConnection_() { 1085 function ensureHasPeerConnection_() {
285 if (getReadyState() == 'no-peer-connection') { 1086 if (getReadyState_() == 'no-peer-connection') {
286 preparePeerConnection(); 1087 preparePeerConnection();
287 } 1088 }
288 } 1089 }
289 1090
290 /** 1091 /**
291 * @private 1092 * @private
292 * @param {string} message Text to print. 1093 * @param {string} message Text to print.
293 */ 1094 */
294 function print_(message) { 1095 function print_(message) {
295 // Filter out uninteresting noise.
296 if (message == 'ok-no-errors')
297 return;
298
299 console.log(message); 1096 console.log(message);
300 $('messages').innerHTML += message + '<br>'; 1097 $('messages').innerHTML += message + '<br>';
301 } 1098 }
302 1099
303 /** 1100 /**
304 * @private 1101 * @private
305 * @param {string} message Text to print. 1102 * @param {string} message Text to print.
306 */ 1103 */
307 function debug_(message) { 1104 function debug_(message) {
308 console.log(message); 1105 console.log(message);
309 $('debug').innerHTML += message + '<br>'; 1106 $('debug').innerHTML += message + '<br>';
310 } 1107 }
311 1108
312 /** 1109 /**
313 * Print error message in the debug log + JS console and throw an Error. 1110 * Print error message in the debug log + JS console and throw an Error.
314 * @private 1111 * @private
315 * @param {string} message Text to print. 1112 * @param {string} message Text to print.
316 */ 1113 */
317 function error(message) { 1114 function error_(message) {
318 $('debug').innerHTML += '<span style="color:red;">' + message + '</span><br>'; 1115 $('debug').innerHTML += '<span style="color:red;">' + message + '</span><br>';
319 throw new Error(message); 1116 throw new Error(message);
320 } 1117 }
321 1118
322 /** 1119 /**
323 * @private 1120 * @private
324 * @param {string} stringRepresentation JavaScript as a string. 1121 * @param {string} stringRepresentation JavaScript as a string.
325 * @return {Object} The PeerConnection constraints as a JavaScript dictionary. 1122 * @return {Object} The PeerConnection constraints as a JavaScript dictionary.
326 */ 1123 */
327 function getEvaluatedJavaScript_(stringRepresentation) { 1124 function getEvaluatedJavaScript_(stringRepresentation) {
328 try { 1125 try {
329 var evaluatedJavaScript; 1126 var evaluatedJavaScript;
330 eval('evaluatedJavaScript = ' + stringRepresentation); 1127 eval('evaluatedJavaScript = ' + stringRepresentation);
331 return evaluatedJavaScript; 1128 return evaluatedJavaScript;
332 } catch (exception) { 1129 } catch (exception) {
333 error('Not valid JavaScript expression: ' + stringRepresentation); 1130 error_('Not valid JavaScript expression: ' + stringRepresentation);
334 } 1131 }
335 } 1132 }
336 1133
337 /** 1134 /**
338 * Swaps lines within a SDP message. 1135 * Swaps lines within a SDP message.
339 * @private 1136 * @private
340 * @param {string} sdp The full SDP message. 1137 * @param {string} sdp The full SDP message.
341 * @param {string} line The line to swap with swapWith. 1138 * @param {string} line The line to swap with swapWith.
342 * @param {string} swapWith The other line. 1139 * @param {string} swapWith The other line.
343 * @return {string} The altered SDP message. 1140 * @return {string} The altered SDP message.
344 */ 1141 */
345 function swapSdpLines_(sdp, line, swapWith) { 1142 function swapSdpLines_(sdp, line, swapWith) {
346 var lines = sdp.split('\r\n'); 1143 var lines = sdp.split('\r\n');
347 var lineStart = lines.indexOf(line); 1144 var lineStart = lines.indexOf(line);
348 var swapLineStart = lines.indexOf(swapWith); 1145 var swapLineStart = lines.indexOf(swapWith);
349 if (lineStart == -1 || swapLineStart == -1) 1146 if (lineStart == -1 || swapLineStart == -1)
350 return sdp; // This generally happens on the first message. 1147 return sdp; // This generally happens on the first message.
351 1148
352 var tmp = lines[lineStart]; 1149 var tmp = lines[lineStart];
353 lines[lineStart] = lines[swapLineStart]; 1150 lines[lineStart] = lines[swapLineStart];
354 lines[swapLineStart] = tmp; 1151 lines[swapLineStart] = tmp;
355 1152
356 return lines.join('\r\n'); 1153 return lines.join('\r\n');
357 } 1154 }
358 1155
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 */ 1156 /** @private */
375 function forceIsac_() { 1157 function forceIsac_() {
376 setOutgoingSdpTransform(function(sdp) { 1158 setOutgoingSdpTransform(function(sdp) {
377 // Remove all other codecs (not the video codecs though). 1159 // Remove all other codecs (not the video codecs though).
378 sdp = sdp.replace(/m=audio (\d+) RTP\/SAVPF.*\r\n/g, 1160 sdp = sdp.replace(/m=audio (\d+) RTP\/SAVPF.*\r\n/g,
379 'm=audio $1 RTP/SAVPF 104\r\n'); 1161 'm=audio $1 RTP/SAVPF 104\r\n');
380 sdp = sdp.replace('a=fmtp:111 minptime=10', 'a=fmtp:104 minptime=10'); 1162 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, ''); 1163 sdp = sdp.replace(/a=rtpmap:(?!104)\d{1,3} (?!VP8|red|ulpfec).*\r\n/g, '');
382 return sdp; 1164 return sdp;
383 }); 1165 });
384 } 1166 }
385 1167
386 /** @private */ 1168 /** @private */
387 function dontTouchSdp_() { 1169 function dontTouchSdp_() {
388 setOutgoingSdpTransform(function(sdp) { return sdp; }); 1170 setOutgoingSdpTransform(function(sdp) { return sdp; });
389 } 1171 }
390 1172
391 /** @private */ 1173 /** @private */
392 function hookupDataChannelCallbacks_() { 1174 function hookupDataChannelCallbacks_() {
393 setDataCallbacks(function(status) { 1175 setDataCallbacks(function(status) {
394 $('data-channel-status').value = status; 1176 $('data-channel-status').value = status;
395 }, 1177 },
396 function(data_message) { 1178 function(data_message) {
397 debug('Received ' + data_message.data); 1179 print_('Received ' + data_message.data);
398 $('data-channel-receive').value = 1180 $('data-channel-receive').value =
399 data_message.data + '\n' + $('data-channel-receive').value; 1181 data_message.data + '\n' + $('data-channel-receive').value;
400 }); 1182 });
401 } 1183 }
402 1184
403 /** @private */ 1185 /** @private */
404 function hookupDtmfSenderCallback_() { 1186 function hookupDtmfSenderCallback_() {
405 setOnToneChange(function(tone) { 1187 setOnToneChange(function(tone) {
406 debug('Sent DTMF tone: ' + tone.tone); 1188 print_('Sent DTMF tone: ' + tone.tone);
407 $('dtmf-tones-sent').value = 1189 $('dtmf-tones-sent').value =
408 tone.tone + '\n' + $('dtmf-tones-sent').value; 1190 tone.tone + '\n' + $('dtmf-tones-sent').value;
409 }); 1191 });
410 } 1192 }
411 1193
412 /** 1194 /** @private */
413 * A list of element id's to be registered for local storage. 1195 function toggle_(track, localOrRemote, audioOrVideo) {
414 */ 1196 if (!track)
415 function setupLocalStorageFieldValues() { 1197 error_('Tried to toggle ' + localOrRemote + ' ' + audioOrVideo +
416 registerLocalStorage_('pc-server'); 1198 ' stream, but has no such stream.');
417 registerLocalStorage_('pc-createanswer-constraints'); 1199
418 registerLocalStorage_('pc-createoffer-constraints'); 1200 track.enabled = !track.enabled;
419 registerLocalStorage_('get-devices-onload'); 1201 print_('ok-' + audioOrVideo + '-toggled-to-' + track.enabled);
420 } 1202 }
421 1203
422 /** 1204 /** @private */
423 * Register an input element to use local storage to remember its state between 1205 function connectCallback_(request) {
424 * sessions (using local storage). Only input elements are supported. 1206 print_('Connect callback: ' + request.status + ', ' + request.readyState);
425 * @private 1207 if (request.status == 0) {
426 * @param {!string} element_id to be used as a key for local storage and the id 1208 print_('peerconnection_server doesn\'t seem to be up.');
427 * of the element to store the state for. 1209 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 } 1210 }
435 1211 if (request.readyState == 4 && request.status == 200) {
436 if (localStorage.getItem(element.id) == 'undefined') { 1212 global.ourPeerId = parseOurPeerId_(request.responseText);
437 storeLocalStorageField_(element); 1213 global.remotePeerId = parseRemotePeerIdIfConnected_(request.responseText);
438 } else { 1214 startHangingGet_(global.serverUrl, global.ourPeerId);
439 getLocalStorageField_(element); 1215 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 } 1216 }
449 } 1217 }
450 1218
451 /** 1219 /** @private */
452 * Fetches the stored values from local storage and updates checkbox status. 1220 function parseOurPeerId_(responseText) {
kjellander_chromium 2014/01/07 10:34:17 I don't know why this is called ourPeerId and not
jansson 2014/01/07 15:49:39 Adding to my todo!
453 * @private 1221 // According to peerconnection_server's protocol.
454 * @param {!Object} element of which id is representing the key parameter for 1222 var peerList = responseText.split('\n');
455 * local storage. 1223 return parseInt(peerList[0].split(',')[1]);
456 */ 1224 }
457 function getLocalStorageField_(element) { 1225
458 // Makes sure the checkbox status is matching the local storage value. 1226 /** @private */
459 if (element.type == 'checkbox') { 1227 function parseRemotePeerIdIfConnected_(responseText) {
460 element.checked = (localStorage.getItem(element.id) == 'true'); 1228 var peerList = responseText.split('\n');
461 } else if (element.type == 'text' ) { 1229 if (peerList.length == 1) {
462 element.value = localStorage.getItem(element.id); 1230 // No peers have connected yet - we'll get their id later in a notification.
463 } else { 1231 return null;
464 error('Unsupportered input type: ' + '\"' + element.type + '\"' ); 1232 }
1233 var remotePeerId = null;
1234 for (var i = 0; i < peerList.length; i++) {
1235 if (peerList[i].length == 0)
1236 continue;
1237
1238 var parsed = peerList[i].split(',');
1239 var name = parsed[0];
1240 var id = parsed[1];
1241
1242 if (id != global.ourPeerId) {
1243 print_('Found remote peer with name ' + name + ', id ' +
1244 id + ' when connecting.');
1245
1246 // There should be at most one remote peer in this test.
1247 if (remotePeerId != null)
1248 error_('Expected just one remote peer in this test: ' +
1249 'found several.');
1250
1251 // Found a remote peer.
1252 remotePeerId = id;
1253 }
1254 }
1255 return remotePeerId;
1256 }
1257
1258 /** @private */
1259 function startHangingGet_(server, ourId) {
1260 if (isDisconnected_())
1261 return;
1262
1263 hangingGetRequest = new XMLHttpRequest();
1264 hangingGetRequest.onreadystatechange = function() {
1265 hangingGetCallback_(hangingGetRequest, server, ourId);
1266 };
1267 hangingGetRequest.ontimeout = function() {
1268 hangingGetTimeoutCallback_(hangingGetRequest, server, ourId);
1269 };
1270 callUrl = server + '/wait?peer_id=' + ourId;
1271 print_('Sending ' + callUrl);
1272 hangingGetRequest.open('GET', callUrl, true);
1273 hangingGetRequest.send();
1274 }
1275
1276 /** @private */
1277 function hangingGetCallback_(hangingGetRequest, server, ourId) {
1278 if (hangingGetRequest.readyState != 4 || hangingGetRequest.status == 0) {
1279 // Code 0 is not possible if the server actually responded. Ignore.
1280 return;
1281 }
1282 if (hangingGetRequest.status != 200) {
1283 error_('Error ' + hangingGetRequest.status + ' from server: ' +
1284 hangingGetRequest.statusText);
1285 }
1286 var targetId = readResponseHeader_(hangingGetRequest, 'Pragma');
1287 if (targetId == ourId)
1288 handleServerNotification_(hangingGetRequest.responseText);
1289 else
1290 handlePeerMessage_(targetId, hangingGetRequest.responseText);
1291
1292 hangingGetRequest.abort();
1293 restartHangingGet_(server, ourId);
1294 }
1295
1296 /** @private */
1297 function hangingGetTimeoutCallback_(hangingGetRequest, server, ourId) {
1298 print_('Hanging GET times out, re-issuing...');
1299 hangingGetRequest.abort();
1300 restartHangingGet_(server, ourId);
1301 }
1302
1303 /** @private */
1304 function handleServerNotification_(message) {
1305 var parsed = message.split(',');
1306 if (parseInt(parsed[2]) == 1) {
1307 // Peer connected - this must be our remote peer, and it must mean we
1308 // connected before them (except if we happened to connect to the server
1309 // at precisely the same moment).
1310 print_('Found remote peer with name ' + parsed[0] + ', id ' + parsed[1] +
1311 ' when connecting.');
1312 global.remotePeerId = parseInt(parsed[1]);
465 } 1313 }
466 } 1314 }
467 1315
468 /** 1316 /** @private */
469 * Stores the string value of the element object using local storage. 1317 function closeCall_() {
470 * @private 1318 if (global.peerConnection == null)
471 * @param {!Object} element of which id is representing the key parameter for 1319 debug_('Closing call, but no call active.');
472 * local storage. 1320 global.peerConnection.close();
473 */ 1321 global.peerConnection = null;
474 function storeLocalStorageField_(element) { 1322 }
475 if (element.type == 'checkbox') { 1323
476 localStorage.setItem(element.id, element.checked); 1324 /** @private */
477 } else if (element.type == 'text') { 1325 function handlePeerMessage_(peerId, message) {
478 localStorage.setItem(element.id, element.value); 1326 print_('Received message from peer ' + peerId + ': ' + message);
1327 if (peerId != global.remotePeerId) {
1328 error_('Received notification from unknown peer ' + peerId +
1329 ' (only know about ' + global.remotePeerId + '.');
479 } 1330 }
1331 if (message.search('BYE') == 0) {
1332 print_('Received BYE from peer: closing call');
1333 closeCall_();
1334 return;
1335 }
1336 if (global.peerConnection == null && global.acceptsIncomingCalls) {
1337 // The other side is calling us.
1338 print_('We are being called: answer...');
1339
1340 global.peerConnection = createPeerConnection(STUN_SERVER,
1341 global.useRtpDataChannels);
1342 if ($('auto-add-stream-oncall') &&
1343 obtainGetUserMediaResult_() == 'ok-got-stream') {
1344 print_('We have a local stream, so hook it up automatically.');
1345 addLocalStreamToPeerConnection(global.peerConnection);
1346 }
1347 answerCall(global.peerConnection, message);
1348 return;
1349 }
1350 handleMessage(global.peerConnection, message);
480 } 1351 }
1352
1353 /** @private */
1354 function restartHangingGet_(server, ourId) {
1355 window.setTimeout(function() {
1356 startHangingGet_(server, ourId);
1357 }, 0);
1358 }
1359
1360 /** @private */
1361 function readResponseHeader_(request, key) {
1362 var value = request.getResponseHeader(key);
1363 if (value == null || value.length == 0) {
1364 error_('Received empty value ' + value +
1365 ' for response header key ' + key + '.');
1366 }
1367 return parseInt(value);
1368 }
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