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

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

Issue 293123009: Cleaned up WebRTC browser test js, removed unneeded stuff. (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Rebased Created 6 years, 7 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 | Annotate | Revision Log
OLDNEW
(Empty)
1 /**
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
4 * found in the LICENSE file.
5 */
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
19 /**
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.
39 * @param {string} id is a case-sensitive string representing the unique ID of
40 * the element being sought.
41 * @return {object} id returns the element object specified as a parameter
42 */
43 $ = function(id) {
44 return document.getElementById(id);
45 };
46
47 /**
48 * Prepopulate constraints from JS to the UI. Enumerate devices available
49 * via getUserMedia, register elements to be used for local storage.
50 */
51 window.onload = function() {
52 hookupDataChannelCallbacks_();
53 hookupDtmfSenderCallback_();
54 updateGetUserMediaConstraints();
55 setupLocalStorageFieldValues();
56 acceptIncomingCalls();
57 if ($('get-devices-onload').checked == true) {
58 getDevices();
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');
77 registerLocalStorage_('data-channel-type-rtp');
78 }
79
80 // Public HTML functions
81
82 // The *Here functions are called from peerconnection.html and will make calls
83 // into our underlying JavaScript library with the values from the page
84 // (have to be named differently to avoid name clashes with existing functions).
85
86 function getUserMediaFromHere() {
87 var constraints = $('getusermedia-constraints').value;
88 try {
89 doGetUserMedia_(constraints);
90 } catch (exception) {
91 print_('getUserMedia says: ' + exception);
92 }
93 }
94
95 function connectFromHere() {
96 var server = $('pc-server').value;
97 if ($('peer-id').value == '') {
98 // Generate a random name to distinguish us from other tabs:
99 $('peer-id').value = 'peer_' + Math.floor(Math.random() * 10000);
100 print_('Our name from now on will be ' + $('peer-id').value);
101 }
102 connect(server, $('peer-id').value);
103 }
104
105 function negotiateCallFromHere() {
106 // Set the global variables with values from our UI.
107 setCreateOfferConstraints(getEvaluatedJavaScript_(
108 $('pc-createoffer-constraints').value));
109 setCreateAnswerConstraints(getEvaluatedJavaScript_(
110 $('pc-createanswer-constraints').value));
111
112 ensureHasPeerConnection_();
113 negotiateCall_();
114 }
115
116 function addLocalStreamFromHere() {
117 ensureHasPeerConnection_();
118 addLocalStream();
119 }
120
121 function removeLocalStreamFromHere() {
122 removeLocalStream();
123 }
124
125 function hangUpFromHere() {
126 hangUp();
127 acceptIncomingCalls();
128 }
129
130 function toggleRemoteVideoFromHere() {
131 toggleRemoteStream(function(remoteStream) {
132 return remoteStream.getVideoTracks()[0];
133 }, 'video');
134 }
135
136 function toggleRemoteAudioFromHere() {
137 toggleRemoteStream(function(remoteStream) {
138 return remoteStream.getAudioTracks()[0];
139 }, 'audio');
140 }
141
142 function toggleLocalVideoFromHere() {
143 toggleLocalStream(function(localStream) {
144 return localStream.getVideoTracks()[0];
145 }, 'video');
146 }
147
148 function toggleLocalAudioFromHere() {
149 toggleLocalStream(function(localStream) {
150 return localStream.getAudioTracks()[0];
151 }, 'audio');
152 }
153
154 function stopLocalFromHere() {
155 stopLocalStream();
156 }
157
158 function createDataChannelFromHere() {
159 ensureHasPeerConnection_();
160 createDataChannelOnPeerConnection();
161 }
162
163 function closeDataChannelFromHere() {
164 ensureHasPeerConnection_();
165 closeDataChannelOnPeerConnection();
166 }
167
168 function sendDataFromHere() {
169 var data = $('data-channel-send').value;
170 sendDataOnChannel(data);
171 }
172
173 function createDtmfSenderFromHere() {
174 ensureHasPeerConnection_();
175 createDtmfSenderOnPeerConnection();
176 }
177
178 function insertDtmfFromHere() {
179 var tones = $('dtmf-tones').value;
180 var duration = $('dtmf-tones-duration').value;
181 var gap = $('dtmf-tones-gap').value;
182 insertDtmfOnSender(tones, duration, gap);
183 }
184
185 function forceIsacChanged() {
186 var forceIsac = $('force-isac').checked;
187 if (forceIsac) {
188 forceIsac_();
189 } else {
190 dontTouchSdp_();
191 }
192 }
193
194 /**
195 * Updates the constraints in the getusermedia-constraints text box with a
196 * MediaStreamConstraints string. This string is created based on the status of
197 * the checkboxes for audio and video. If device enumeration is supported and
198 * device source id's are not null they will be added to the constraints string.
199 * Fetches the screen size using "screen" in Chrome as we need to pass a max
200 * resolution else it defaults to 640x480 in the constraints for screen
201 * capturing.
202 */
203 function updateGetUserMediaConstraints() {
204 var audioSelected = $('audiosrc');
205 var videoSelected = $('videosrc');
206 var constraints = {
207 audio: $('audio').checked,
208 video: $('video').checked
209 };
210
211 if (audioSelected.disabled == false && videoSelected.disabled == false) {
212 var devices = getSourcesFromField_(audioSelected, videoSelected);
213 if ($('audio').checked == true) {
214 if (devices.audioId != null) {
215 constraints.audio = {optional: [{sourceId: devices.audioId}]};
216 } else {
217 constraints.audio = true;
218 }
219 }
220 if ($('video').checked == true) {
221 // Default optional constraints placed here.
222 constraints.video = {optional: [{minWidth: $('video-width').value},
223 {minHeight: $('video-height').value},
224 {googCpuOveruseDetection: true},
225 {googLeakyBucket: true}]
226 };
227 if (devices.videoId != null) {
228 constraints.video.optional.push({sourceId: devices.videoId});
229 }
230 }
231 }
232
233 if ($('screencapture').checked) {
234 var constraints = {
235 audio: $('audio').checked,
236 video: {mandatory: {chromeMediaSource: 'screen',
237 maxWidth: screen.width,
238 maxHeight: screen.height}}
239 };
240 if ($('audio').checked == true)
241 print_('Audio for screencapture is not implemented yet, please ' +
242 'try to set audio = false prior requesting screencapture');
243 }
244 $('getusermedia-constraints').value = JSON.stringify(constraints, null, ' ');
245 }
246
247 function showServerHelp() {
248 alert('You need to build and run a peerconnection_server on some ' +
249 'suitable machine. To build it in chrome, just run make/ninja ' +
250 'peerconnection_server. Otherwise, read in https://code.google' +
251 '.com/searchframe#xSWYf0NTG_Q/trunk/peerconnection/README&q=REA' +
252 'DME%20package:webrtc%5C.googlecode%5C.com.');
253 }
254
255 function clearLog() {
256 $('messages').innerHTML = '';
257 $('debug').innerHTML = '';
258 }
259
260 /**
261 * Stops the local stream.
262 */
263 function stopLocalStream() {
264 if (global.localStream == null)
265 error_('Tried to stop local stream, ' +
266 'but media access is not granted.');
267
268 global.localStream.stop();
269 }
270
271 /**
272 * Adds the current local media stream to a peer connection.
273 * @param {RTCPeerConnection} peerConnection
274 */
275 function addLocalStreamToPeerConnection(peerConnection) {
276 if (global.localStream == null)
277 error_('Tried to add local stream to peer connection, but there is no ' +
278 'stream yet.');
279 try {
280 peerConnection.addStream(global.localStream, global.addStreamConstraints);
281 } catch (exception) {
282 error_('Failed to add stream with constraints ' +
283 global.addStreamConstraints + ': ' + exception);
284 }
285 print_('Added local stream.');
286 }
287
288 /**
289 * Removes the local stream from the peer connection.
290 * @param {rtcpeerconnection} peerConnection
291 */
292 function removeLocalStreamFromPeerConnection(peerConnection) {
293 if (global.localStream == null)
294 error_('Tried to remove local stream from peer connection, but there is ' +
295 'no stream yet.');
296 try {
297 peerConnection.removeStream(global.localStream);
298 } catch (exception) {
299 error_('Could not remove stream: ' + exception);
300 }
301 print_('Removed local stream.');
302 }
303
304 /**
305 * Enumerates the audio and video devices available in Chrome and adds the
306 * devices to the HTML elements with Id 'audiosrc' and 'videosrc'.
307 * Checks if device enumeration is supported and if the 'audiosrc' + 'videosrc'
308 * elements exists, if not a debug printout will be displayed.
309 * If the device label is empty, audio/video + sequence number will be used to
310 * populate the name. Also makes sure the children has been loaded in order
311 * to update the constraints.
312 */
313 function getDevices() {
314 var audio_select = $('audiosrc');
315 var video_select = $('videosrc');
316 var get_devices = $('get-devices');
317 audio_select.innerHTML = '';
318 video_select.innerHTML = '';
319 try {
320 eval(MediaStreamTrack.getSources(function() {}));
321 } catch (exception) {
322 audio_select.disabled = true;
323 video_select.disabled = true;
324 refresh_devices.disabled = true;
325 updateGetUserMediaConstraints();
326 error_('Device enumeration not supported. ' + exception);
327 }
328 MediaStreamTrack.getSources(function(devices) {
329 for (var i = 0; i < devices.length; i++) {
330 var option = document.createElement('option');
331 option.value = devices[i].id;
332 option.text = devices[i].label;
333 if (devices[i].kind == 'audio') {
334 if (option.text == '') {
335 option.text = devices[i].id;
336 }
337 audio_select.appendChild(option);
338 } else if (devices[i].kind == 'video') {
339 if (option.text == '') {
340 option.text = devices[i].id;
341 }
342 video_select.appendChild(option);
343 } else {
344 error_('Device type ' + devices[i].kind + ' not recognized, ' +
345 'cannot enumerate device. Currently only device types' +
346 '\'audio\' and \'video\' are supported');
347 updateGetUserMediaConstraints();
348 return;
349 }
350 }
351 });
352 checkIfDeviceDropdownsArePopulated_();
353 }
354
355 /**
356 * Sets the transform to apply just before setting the local description and
357 * sending to the peer.
358 * @param {function} transformFunction A function which takes one SDP string as
359 * argument and returns the modified SDP string.
360 */
361 function setOutgoingSdpTransform(transformFunction) {
362 global.transformOutgoingSdp = transformFunction;
363 }
364
365 /**
366 * Sets the MediaConstraints to be used for PeerConnection createAnswer() calls.
367 * @param {string} mediaConstraints The constraints, as defined in the
368 * PeerConnection JS API spec.
369 */
370 function setCreateAnswerConstraints(mediaConstraints) {
371 global.createAnswerConstraints = mediaConstraints;
372 }
373
374 /**
375 * Sets the MediaConstraints to be used for PeerConnection createOffer() calls.
376 * @param {string} mediaConstraints The constraints, as defined in the
377 * PeerConnection JS API spec.
378 */
379 function setCreateOfferConstraints(mediaConstraints) {
380 global.createOfferConstraints = mediaConstraints;
381 }
382
383 /**
384 * Sets the callback functions that will receive DataChannel readyState updates
385 * and received data.
386 * @param {function} status_callback The function that will receive a string
387 * with
388 * the current DataChannel readyState.
389 * @param {function} data_callback The function that will a string with data
390 * received from the remote peer.
391 */
392 function setDataCallbacks(status_callback, data_callback) {
393 global.dataStatusCallback = status_callback;
394 global.dataCallback = data_callback;
395 }
396
397 /**
398 * Sends data on an active DataChannel.
399 * @param {string} data The string that will be sent to the remote peer.
400 */
401 function sendDataOnChannel(data) {
402 if (global.dataChannel == null)
403 error_('Trying to send data, but there is no DataChannel.');
404 global.dataChannel.send(data);
405 }
406
407 /**
408 * Sets the callback function that will receive DTMF sender ontonechange events.
409 * @param {function} ontonechange The function that will receive a string with
410 * the tone that has just begun playout.
411 */
412 function setOnToneChange(ontonechange) {
413 global.dtmfOnToneChange = ontonechange;
414 }
415
416 /**
417 * Inserts DTMF tones on an active DTMF sender.
418 * @param {string} tones to be sent.
419 * @param {string} duration duration of the tones to be sent.
420 * @param {string} interToneGap gap between the tones to be sent.
421 */
422 function insertDtmf(tones, duration, interToneGap) {
423 if (global.dtmfSender == null)
424 error_('Trying to send DTMF, but there is no DTMF sender.');
425 global.dtmfSender.insertDTMF(tones, duration, interToneGap);
426 }
427
428 function handleMessage(peerConnection, message) {
429 var parsed_msg = JSON.parse(message);
430 if (parsed_msg.type) {
431 var session_description = new RTCSessionDescription(parsed_msg);
432 peerConnection.setRemoteDescription(
433 session_description,
434 function() { success_('setRemoteDescription'); },
435 function() { failure_('setRemoteDescription'); });
436 if (session_description.type == 'offer') {
437 print_('createAnswer with constraints: ' +
438 JSON.stringify(global.createAnswerConstraints, null, ' '));
439 peerConnection.createAnswer(
440 setLocalAndSendMessage_,
441 function() { failure_('createAnswer'); },
442 global.createAnswerConstraints);
443 }
444 return;
445 } else if (parsed_msg.candidate) {
446 var candidate = new RTCIceCandidate(parsed_msg);
447 peerConnection.addIceCandidate(candidate);
448 return;
449 }
450 error_('unknown message received');
451 }
452
453 function createPeerConnection(stun_server, useRtpDataChannels) {
454 servers = {iceServers: [{url: 'stun:' + stun_server}]};
455 try {
456 var constraints = { optional: [{ RtpDataChannels: useRtpDataChannels }]};
457 peerConnection = new webkitRTCPeerConnection(servers, constraints);
458 } catch (exception) {
459 error_('Failed to create peer connection: ' + exception);
460 }
461 peerConnection.onaddstream = addStreamCallback_;
462 peerConnection.onremovestream = removeStreamCallback_;
463 peerConnection.onicecandidate = iceCallback_;
464 peerConnection.ondatachannel = onCreateDataChannelCallback_;
465 return peerConnection;
466 }
467
468 function setupCall(peerConnection) {
469 print_('createOffer with constraints: ' +
470 JSON.stringify(global.createOfferConstraints, null, ' '));
471 peerConnection.createOffer(
472 setLocalAndSendMessage_,
473 function() { failure_('createOffer'); },
474 global.createOfferConstraints);
475 }
476
477 function answerCall(peerConnection, message) {
478 handleMessage(peerConnection, message);
479 }
480
481 function createDataChannel(peerConnection, label) {
482 if (global.dataChannel != null && global.dataChannel.readyState != 'closed')
483 error_('Creating DataChannel, but we already have one.');
484
485 global.dataChannel = peerConnection.createDataChannel(label,
486 { reliable: false });
487 print_('DataChannel with label ' + global.dataChannel.label + ' initiated ' +
488 'locally.');
489 hookupDataChannelEvents();
490 }
491
492 function closeDataChannel(peerConnection) {
493 if (global.dataChannel == null)
494 error_('Closing DataChannel, but none exists.');
495 print_('DataChannel with label ' + global.dataChannel.label +
496 ' is beeing closed.');
497 global.dataChannel.close();
498 }
499
500 function createDtmfSender(peerConnection) {
501 if (global.dtmfSender != null)
502 error_('Creating DTMF sender, but we already have one.');
503
504 var localStream = global.localStream();
505 if (localStream == null)
506 error_('Creating DTMF sender but local stream is null.');
507 local_audio_track = localStream.getAudioTracks()[0];
508 global.dtmfSender = peerConnection.createDTMFSender(local_audio_track);
509 global.dtmfSender.ontonechange = global.dtmfOnToneChange;
510 }
511
512 /**
513 * Connects to the provided peerconnection_server.
514 *
515 * @param {string} serverUrl The server URL in string form without an ending
516 * slash, something like http://localhost:8888.
517 * @param {string} clientName The name to use when connecting to the server.
518 */
519 function connect(serverUrl, clientName) {
520 if (global.ourPeerId != null)
521 error_('connecting, but is already connected.');
522
523 print_('Connecting to ' + serverUrl + ' as ' + clientName);
524 global.serverUrl = serverUrl;
525 global.ourClientName = clientName;
526
527 request = new XMLHttpRequest();
528 request.open('GET', serverUrl + '/sign_in?' + clientName, true);
529 print_(serverUrl + '/sign_in?' + clientName);
530 request.onreadystatechange = function() {
531 connectCallback_(request);
532 };
533 request.send();
534 }
535
536 /**
537 * Checks if the remote peer has connected. Returns peer-connected if that is
538 * the case, otherwise no-peer-connected.
539 */
540 function remotePeerIsConnected() {
541 if (global.remotePeerId == null)
542 print_('no-peer-connected');
543 else
544 print_('peer-connected');
545 }
546
547 /**
548 * Creates a peer connection. Must be called before most other public functions
549 * in this file.
550 */
551 function preparePeerConnection() {
552 if (global.peerConnection != null)
553 error_('creating peer connection, but we already have one.');
554
555 global.peerConnection = createPeerConnection(STUN_SERVER,
556 $('data-channel-type-rtp').checked);
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 {!number} width of the video to update the video tag, if width or
718 * height is 0, size will be taken from videoTag.videoWidth.
719 * @param {!number} height of the video to update the video tag, if width or
720 * 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 print_('Set video tag "' + videoTagId + '" size to ' + videoTag.width +
733 'x' + videoTag.height);
734 }
735 else {
736 print_('"' + videoTagId + '" video stream size is 0, skipping resize');
737 }
738 }
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 }
998
999 /**
1000 * Checks if the 'audiosrc' and 'videosrc' drop down menu elements has had all
1001 * of its children appended in order to provide device ID's to the function
1002 * 'updateGetUserMediaConstraints()', used in turn to populate the getUserMedia
1003 * constraints text box when the page has loaded.
1004 * @private
1005 */
1006 function checkIfDeviceDropdownsArePopulated_() {
1007 if (document.addEventListener) {
1008 $('audiosrc').addEventListener('DOMNodeInserted',
1009 updateGetUserMediaConstraints, false);
1010 $('videosrc').addEventListener('DOMNodeInserted',
1011 updateGetUserMediaConstraints, false);
1012 } else {
1013 print_('addEventListener is not supported by your browser, cannot update ' +
1014 'device source ID\'s automatically. Select a device from the audio' +
1015 ' or video source drop down menu to update device source id\'s');
1016 }
1017 }
1018
1019 /**
1020 * Register an input element to use local storage to remember its state between
1021 * sessions (using local storage). Only input elements are supported.
1022 * @private
1023 * @param {!string} element_id to be used as a key for local storage and the id
1024 * of the element to store the state for.
1025 */
1026 function registerLocalStorage_(element_id) {
1027 var element = $(element_id);
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 }
1078
1079 /**
1080 * Create the peer connection if none is up (this is just convenience to
1081 * avoid having a separate button for that).
1082 * @private
1083 */
1084 function ensureHasPeerConnection_() {
1085 if (getReadyState_() == 'no-peer-connection') {
1086 preparePeerConnection();
1087 }
1088 }
1089
1090 /**
1091 * @private
1092 * @param {string} message Text to print.
1093 */
1094 function print_(message) {
1095 console.log(message);
1096 $('messages').innerHTML += message + '<br>';
1097 }
1098
1099 /**
1100 * @private
1101 * @param {string} message Text to print.
1102 */
1103 function debug_(message) {
1104 console.log(message);
1105 $('debug').innerHTML += message + '<br>';
1106 }
1107
1108 /**
1109 * Print error message in the debug log + JS console and throw an Error.
1110 * @private
1111 * @param {string} message Text to print.
1112 */
1113 function error_(message) {
1114 $('debug').innerHTML += '<span style="color:red;">' + message + '</span><br>';
1115 throw new Error(message);
1116 }
1117
1118 /**
1119 * @private
1120 * @param {string} stringRepresentation JavaScript as a string.
1121 * @return {Object} The PeerConnection constraints as a JavaScript dictionary.
1122 */
1123 function getEvaluatedJavaScript_(stringRepresentation) {
1124 try {
1125 var evaluatedJavaScript;
1126 eval('evaluatedJavaScript = ' + stringRepresentation);
1127 return evaluatedJavaScript;
1128 } catch (exception) {
1129 error_('Not valid JavaScript expression: ' + stringRepresentation);
1130 }
1131 }
1132
1133 /**
1134 * Swaps lines within a SDP message.
1135 * @private
1136 * @param {string} sdp The full SDP message.
1137 * @param {string} line The line to swap with swapWith.
1138 * @param {string} swapWith The other line.
1139 * @return {string} The altered SDP message.
1140 */
1141 function swapSdpLines_(sdp, line, swapWith) {
1142 var lines = sdp.split('\r\n');
1143 var lineStart = lines.indexOf(line);
1144 var swapLineStart = lines.indexOf(swapWith);
1145 if (lineStart == -1 || swapLineStart == -1)
1146 return sdp; // This generally happens on the first message.
1147
1148 var tmp = lines[lineStart];
1149 lines[lineStart] = lines[swapLineStart];
1150 lines[swapLineStart] = tmp;
1151
1152 return lines.join('\r\n');
1153 }
1154
1155 /** @private */
1156 function forceIsac_() {
1157 setOutgoingSdpTransform(function(sdp) {
1158 // Remove all other codecs (not the video codecs though).
1159 sdp = sdp.replace(/m=audio (\d+) RTP\/SAVPF.*\r\n/g,
1160 'm=audio $1 RTP/SAVPF 104\r\n');
1161 sdp = sdp.replace('a=fmtp:111 minptime=10', 'a=fmtp:104 minptime=10');
1162 sdp = sdp.replace(/a=rtpmap:(?!104)\d{1,3} (?!VP8|red|ulpfec).*\r\n/g, '');
1163 return sdp;
1164 });
1165 }
1166
1167 /** @private */
1168 function dontTouchSdp_() {
1169 setOutgoingSdpTransform(function(sdp) { return sdp; });
1170 }
1171
1172 /** @private */
1173 function hookupDataChannelCallbacks_() {
1174 setDataCallbacks(function(status) {
1175 $('data-channel-status').value = status;
1176 },
1177 function(data_message) {
1178 print_('Received ' + data_message.data);
1179 $('data-channel-receive').value =
1180 data_message.data + '\n' + $('data-channel-receive').value;
1181 });
1182 }
1183
1184 /** @private */
1185 function hookupDtmfSenderCallback_() {
1186 setOnToneChange(function(tone) {
1187 print_('Sent DTMF tone: ' + tone.tone);
1188 $('dtmf-tones-sent').value =
1189 tone.tone + '\n' + $('dtmf-tones-sent').value;
1190 });
1191 }
1192
1193 /** @private */
1194 function toggle_(track, localOrRemote, audioOrVideo) {
1195 if (!track)
1196 error_('Tried to toggle ' + localOrRemote + ' ' + audioOrVideo +
1197 ' stream, but has no such stream.');
1198
1199 track.enabled = !track.enabled;
1200 print_('ok-' + audioOrVideo + '-toggled-to-' + track.enabled);
1201 }
1202
1203 /** @private */
1204 function connectCallback_(request) {
1205 print_('Connect callback: ' + request.status + ', ' + request.readyState);
1206 if (request.status == 0) {
1207 print_('peerconnection_server doesn\'t seem to be up.');
1208 print_('failed-to-connect');
1209 }
1210 if (request.readyState == 4 && request.status == 200) {
1211 global.ourPeerId = parseOurPeerId_(request.responseText);
1212 global.remotePeerId = parseRemotePeerIdIfConnected_(request.responseText);
1213 startHangingGet_(global.serverUrl, global.ourPeerId);
1214 print_('ok-connected');
1215 }
1216 }
1217
1218 /** @private */
1219 function parseOurPeerId_(responseText) {
1220 // According to peerconnection_server's protocol.
1221 var peerList = responseText.split('\n');
1222 return parseInt(peerList[0].split(',')[1]);
1223 }
1224
1225 /** @private */
1226 function parseRemotePeerIdIfConnected_(responseText) {
1227 var peerList = responseText.split('\n');
1228 if (peerList.length == 1) {
1229 // No peers have connected yet - we'll get their id later in a notification.
1230 return null;
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]);
1312 }
1313 }
1314
1315 /** @private */
1316 function closeCall_() {
1317 if (global.peerConnection == null)
1318 debug_('Closing call, but no call active.');
1319 global.peerConnection.close();
1320 global.peerConnection = null;
1321 }
1322
1323 /** @private */
1324 function handlePeerMessage_(peerId, message) {
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 + '.');
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 $('data-channel-type-rtp').checked);
1341
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);
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/peerconnection_manual.js » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698