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

Unified Diff: chrome/test/data/webrtc/manual/peerconnection.js

Issue 110853006: Combining all javascript files to one for the manual test page. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@master
Patch Set: fixed nits + rebase Created 6 years, 11 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« 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 »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: chrome/test/data/webrtc/manual/peerconnection.js
diff --git a/chrome/test/data/webrtc/manual/peerconnection.js b/chrome/test/data/webrtc/manual/peerconnection.js
index e6dd8d71d5addbe9e51dae7e16c5188c3768c175..83604e87b0c97782afda95795d3a54ef803879f9 100644
--- a/chrome/test/data/webrtc/manual/peerconnection.js
+++ b/chrome/test/data/webrtc/manual/peerconnection.js
@@ -4,8 +4,38 @@
* found in the LICENSE file.
*/
+ /**
+ * See http://dev.w3.org/2011/webrtc/editor/getusermedia.html for more
+ * information on getUserMedia. See
+ * http://dev.w3.org/2011/webrtc/editor/webrtc.html for more information on
+ * peerconnection and webrtc in general.
+ */
+
+/** TODO(jansson) give it a better name
+ * Global namespace object.
+ */
+var global = {};
+
+/**
+ * We need a STUN server for some API calls.
+ * @private
+ */
+var STUN_SERVER = 'stun.l.google.com:19302';
+
+/** @private */
+global.transformOutgoingSdp = function(sdp) { return sdp; };
+
+/** @private */
+global.dataStatusCallback = function(status) {};
+
+/** @private */
+global.dataCallback = function(data) {};
+
+/** @private */
+global.dtmfOnToneChange = function(tone) {};
+
/**
- * Used as a shortcut. Moved to the top of the page due to race conditions.
+ * Used as a shortcut for finding DOM elements by ID.
* @param {string} id is a case-sensitive string representing the unique ID of
* the element being sought.
* @return {object} id returns the element object specified as a parameter
@@ -15,21 +45,39 @@ $ = function(id) {
};
/**
- * Sets the global variable gUseRtpDataChannel in message_handling.js
- * based on the check box element 'data-channel-type-rtp' checked status on
- * peerconnection.html. Also prints a help text to inform the user.
+ * Prepopulate constraints from JS to the UI. Enumerate devices available
+ * via getUserMedia, register elements to be used for local storage.
*/
-function setPcDataChannelType() {
- var useRtpDataChannels = $('data-channel-type-rtp').checked;
- useRtpDataChannelsForNewPeerConnections(useRtpDataChannels);
- if (useRtpDataChannels) {
- debug('Make sure to tick the RTP check box on both the calling and ' +
- 'answering side to ensure a data channel can be setup properly as ' +
- 'it has to use the same transport protocol (RTP to RTP and SCTP to ' +
- 'SCTP).');
+window.onload = function() {
+ hookupDataChannelCallbacks_();
+ hookupDtmfSenderCallback_();
+ updateGetUserMediaConstraints();
+ setupLocalStorageFieldValues();
+ acceptIncomingCalls();
+ if ($('get-devices-onload').checked == true) {
+ getDevices();
}
+};
+
+/**
+ * Disconnect before the tab is closed.
+ */
+window.onbeforeunload = function() {
+ disconnect_();
+};
+
+/** TODO Add element.id as a parameter and call this function instead?
+ * A list of element id's to be registered for local storage.
+ */
+function setupLocalStorageFieldValues() {
+ registerLocalStorage_('pc-server');
+ registerLocalStorage_('pc-createanswer-constraints');
+ registerLocalStorage_('pc-createoffer-constraints');
+ registerLocalStorage_('get-devices-onload');
}
+// Public HTML functions
+
// The *Here functions are called from peerconnection.html and will make calls
// into our underlying JavaScript library with the values from the page
// (have to be named differently to avoid name clashes with existing functions).
@@ -37,7 +85,7 @@ function setPcDataChannelType() {
function getUserMediaFromHere() {
var constraints = $('getusermedia-constraints').value;
try {
- doGetUserMedia(constraints);
+ doGetUserMedia_(constraints);
} catch (exception) {
print_('getUserMedia says: ' + exception);
}
@@ -48,24 +96,20 @@ function connectFromHere() {
if ($('peer-id').value == '') {
// Generate a random name to distinguish us from other tabs:
$('peer-id').value = 'peer_' + Math.floor(Math.random() * 10000);
- debug('Our name from now on will be ' + $('peer-id').value);
+ print_('Our name from now on will be ' + $('peer-id').value);
}
connect(server, $('peer-id').value);
}
function negotiateCallFromHere() {
- // Set the global variables used in jsep01_call.js with values from our UI.
+ // Set the global variables with values from our UI.
setCreateOfferConstraints(getEvaluatedJavaScript_(
$('pc-createoffer-constraints').value));
setCreateAnswerConstraints(getEvaluatedJavaScript_(
$('pc-createanswer-constraints').value));
ensureHasPeerConnection_();
- try {
- negotiateCall();
- } catch (exception) {
- error(exception);
- }
+ negotiateCall_();
}
function addLocalStreamFromHere() {
@@ -79,7 +123,7 @@ function removeLocalStreamFromHere() {
function hangUpFromHere() {
hangUp();
- acceptIncomingCallsAgain();
+ acceptIncomingCalls();
}
function toggleRemoteVideoFromHere() {
@@ -156,19 +200,19 @@ function forceIsacChanged() {
* capturing.
*/
function updateGetUserMediaConstraints() {
- var audio_selected = $('audiosrc');
- var video_selected = $('videosrc');
+ var audioSelected = $('audiosrc');
+ var videoSelected = $('videosrc');
var constraints = {
audio: $('audio').checked,
video: $('video').checked
};
- if (audio_selected.disabled == false && video_selected.disabled == false) {
- var devices = getSourcesFromField(audio_selected, video_selected);
+
+ if (audioSelected.disabled == false && videoSelected.disabled == false) {
+ var devices = getSourcesFromField_(audioSelected, videoSelected);
if ($('audio').checked == true) {
- if (devices.audio_id != null) {
- constraints.audio = {optional: [{sourceId: devices.audio_id}]};
- }
- else {
+ if (devices.audioId != null) {
+ constraints.audio = {optional: [{sourceId: devices.audioId}]};
+ } else {
constraints.audio = true;
}
}
@@ -179,11 +223,12 @@ function updateGetUserMediaConstraints() {
{googCpuOveruseDetection: true},
{googLeakyBucket: true}]
};
- if (devices.video_id != null) {
- constraints.video.optional.push({sourceId: devices.video_id});
+ if (devices.videoId != null) {
+ constraints.video.optional.push({sourceId: devices.videoId});
}
}
}
+
if ($('screencapture').checked) {
var constraints = {
audio: $('audio').checked,
@@ -192,11 +237,10 @@ function updateGetUserMediaConstraints() {
maxHeight: screen.height}}
};
if ($('audio').checked == true)
- debug('Audio for screencapture is not implemented yet, please ' +
+ print_('Audio for screencapture is not implemented yet, please ' +
'try to set audio = false prior requesting screencapture');
}
- $('getusermedia-constraints').value =
- JSON.stringify(constraints, null, ' ');
+ $('getusermedia-constraints').value = JSON.stringify(constraints, null, ' ');
}
function showServerHelp() {
@@ -207,74 +251,830 @@ function showServerHelp() {
'DME%20package:webrtc%5C.googlecode%5C.com.');
}
-function toggleHelp() {
- var help = $('help');
- if (help.style.display == 'none')
- help.style.display = 'inline';
- else
- help.style.display = 'none';
-}
-
function clearLog() {
$('messages').innerHTML = '';
$('debug').innerHTML = '';
}
/**
- * Prepopulate constraints from JS to the UI and setup callbacks in the scripts
- * shared with browser tests or automated tests. Enumerates devices available
- * via getUserMedia.
+ * Stops the local stream.
*/
-window.onload = function() {
- $('pc-createoffer-constraints').value = JSON.stringify(
- gCreateOfferConstraints, null, ' ');
- $('pc-createanswer-constraints').value = JSON.stringify(
- gCreateAnswerConstraints, null, ' ');
- replaceReturnCallback(print_);
- replaceDebugCallback(debug_);
- doNotAutoAddLocalStreamWhenCalled();
- hookupDataChannelCallbacks_();
- hookupDtmfSenderCallback_();
- displayVideoSize_($('local-view'));
- displayVideoSize_($('remote-view'));
- updateGetUserMediaConstraints();
- setPcDataChannelType();
- setupLocalStorageFieldValues();
- if ($('get-devices-onload').checked == true) {
- getDevices();
+function stopLocalStream() {
+ if (global.localStream == null)
+ error_('Tried to stop local stream, ' +
+ 'but media access is not granted.');
+
+ global.localStream.stop();
+}
+
+/**
+ * Adds the current local media stream to a peer connection.
+ * @param {RTCPeerConnection} peerConnection
+ */
+function addLocalStreamToPeerConnection(peerConnection) {
+ if (global.localStream == null)
+ error_('Tried to add local stream to peer connection, but there is no ' +
+ 'stream yet.');
+ try {
+ peerConnection.addStream(global.localStream, global.addStreamConstraints);
+ } catch (exception) {
+ error_('Failed to add stream with constraints ' +
+ global.addStreamConstraints + ': ' + exception);
}
-};
+ print_('Added local stream.');
+}
+
+/**
+ * Removes the local stream from the peer connection.
+ * @param {rtcpeerconnection} peerConnection
+ */
+function removeLocalStreamFromPeerConnection(peerConnection) {
+ if (global.localStream == null)
+ error_('Tried to remove local stream from peer connection, but there is ' +
+ 'no stream yet.');
+ try {
+ peerConnection.removeStream(global.localStream);
+ } catch (exception) {
+ error_('Could not remove stream: ' + exception);
+ }
+ print_('Removed local stream.');
+}
+
+/**
+ * Enumerates the audio and video devices available in Chrome and adds the
+ * devices to the HTML elements with Id 'audiosrc' and 'videosrc'.
+ * Checks if device enumeration is supported and if the 'audiosrc' + 'videosrc'
+ * elements exists, if not a debug printout will be displayed.
+ * If the device label is empty, audio/video + sequence number will be used to
+ * populate the name. Also makes sure the children has been loaded in order
+ * to update the constraints.
+ */
+function getDevices() {
+ var audio_select = $('audiosrc');
+ var video_select = $('videosrc');
+ var get_devices = $('get-devices');
+ audio_select.innerHTML = '';
+ video_select.innerHTML = '';
+ try {
+ eval(MediaStreamTrack.getSources(function() {}));
+ } catch (exception) {
+ audio_select.disabled = true;
+ video_select.disabled = true;
+ refresh_devices.disabled = true;
+ updateGetUserMediaConstraints();
+ error_('Device enumeration not supported. ' + exception);
+ }
+ MediaStreamTrack.getSources(function(devices) {
+ for (var i = 0; i < devices.length; i++) {
+ var option = document.createElement('option');
+ option.value = devices[i].id;
+ option.text = devices[i].label;
+ if (devices[i].kind == 'audio') {
+ if (option.text == '') {
+ option.text = devices[i].id;
+ }
+ audio_select.appendChild(option);
+ } else if (devices[i].kind == 'video') {
+ if (option.text == '') {
+ option.text = devices[i].id;
+ }
+ video_select.appendChild(option);
+ } else {
+ error_('Device type ' + devices[i].kind + ' not recognized, ' +
+ 'cannot enumerate device. Currently only device types' +
+ '\'audio\' and \'video\' are supported');
+ updateGetUserMediaConstraints();
+ return;
+ }
+ }
+ });
+ checkIfDeviceDropdownsArePopulated_();
+}
+
+/**
+ * Sets the transform to apply just before setting the local description and
+ * sending to the peer.
+ * @param {function} transformFunction A function which takes one SDP string as
+ * argument and returns the modified SDP string.
+ */
+function setOutgoingSdpTransform(transformFunction) {
+ global.transformOutgoingSdp = transformFunction;
+}
+
+/**
+ * Sets the MediaConstraints to be used for PeerConnection createAnswer() calls.
+ * @param {string} mediaConstraints The constraints, as defined in the
+ * PeerConnection JS API spec.
+ */
+function setCreateAnswerConstraints(mediaConstraints) {
+ global.createAnswerConstraints = mediaConstraints;
+}
+
+/**
+ * Sets the MediaConstraints to be used for PeerConnection createOffer() calls.
+ * @param {string} mediaConstraints The constraints, as defined in the
+ * PeerConnection JS API spec.
+ */
+function setCreateOfferConstraints(mediaConstraints) {
+ global.createOfferConstraints = mediaConstraints;
+}
+
+/**
+ * Sets the callback functions that will receive DataChannel readyState updates
+ * and received data.
+ * @param {function} status_callback The function that will receive a string
+ * with
+ * the current DataChannel readyState.
+ * @param {function} data_callback The function that will a string with data
+ * received from the remote peer.
+ */
+function setDataCallbacks(status_callback, data_callback) {
+ global.dataStatusCallback = status_callback;
+ global.dataCallback = data_callback;
+}
+
+/**
+ * Sends data on an active DataChannel.
+ * @param {string} data The string that will be sent to the remote peer.
+ */
+function sendDataOnChannel(data) {
+ if (global.dataChannel == null)
+ error_('Trying to send data, but there is no DataChannel.');
+ global.dataChannel.send(data);
+}
+
+/**
+ * Sets the callback function that will receive DTMF sender ontonechange events.
+ * @param {function} ontonechange The function that will receive a string with
+ * the tone that has just begun playout.
+ */
+function setOnToneChange(ontonechange) {
+ global.dtmfOnToneChange = ontonechange;
+}
+
+/**
+ * Inserts DTMF tones on an active DTMF sender.
+ * @param {string} tones to be sent.
+ * @param {string} duration duration of the tones to be sent.
+ * @param {string} interToneGap gap between the tones to be sent.
+ */
+function insertDtmf(tones, duration, interToneGap) {
+ if (global.dtmfSender == null)
+ error_('Trying to send DTMF, but there is no DTMF sender.');
+ global.dtmfSender.insertDTMF(tones, duration, interToneGap);
+}
+
+function handleMessage(peerConnection, message) {
+ var parsed_msg = JSON.parse(message);
+ if (parsed_msg.type) {
+ var session_description = new RTCSessionDescription(parsed_msg);
+ peerConnection.setRemoteDescription(
+ session_description,
+ function() { success_('setRemoteDescription'); },
+ function() { failure_('setRemoteDescription'); });
+ if (session_description.type == 'offer') {
+ print_('createAnswer with constraints: ' +
+ JSON.stringify(global.createAnswerConstraints, null, ' '));
+ peerConnection.createAnswer(
+ setLocalAndSendMessage_,
+ function() { failure_('createAnswer'); },
+ global.createAnswerConstraints);
+ }
+ return;
+ } else if (parsed_msg.candidate) {
+ var candidate = new RTCIceCandidate(parsed_msg);
+ peerConnection.addIceCandidate(candidate);
+ return;
+ }
+ error_('unknown message received');
+}
+
+function createPeerConnection(stun_server, useRtpDataChannels) {
+ servers = {iceServers: [{url: 'stun:' + stun_server}]};
+ try {
+ var constraints = { optional: [{ RtpDataChannels: useRtpDataChannels }]};
+ peerConnection = new webkitRTCPeerConnection(servers, constraints);
+ } catch (exception) {
+ error_('Failed to create peer connection: ' + exception);
+ }
+ peerConnection.onaddstream = addStreamCallback_;
+ peerConnection.onremovestream = removeStreamCallback_;
+ peerConnection.onicecandidate = iceCallback_;
+ peerConnection.ondatachannel = onCreateDataChannelCallback_;
+ return peerConnection;
+}
+
+function setupCall(peerConnection) {
+ print_('createOffer with constraints: ' +
+ JSON.stringify(global.createOfferConstraints, null, ' '));
+ peerConnection.createOffer(
+ setLocalAndSendMessage_,
+ function() { failure_('createOffer'); },
+ global.createOfferConstraints);
+}
+
+function answerCall(peerConnection, message) {
+ handleMessage(peerConnection, message);
+}
+
+function createDataChannel(peerConnection, label) {
+ if (global.dataChannel != null && global.dataChannel.readyState != 'closed')
+ error_('Creating DataChannel, but we already have one.');
+
+ global.dataChannel = peerConnection.createDataChannel(label,
+ { reliable: false });
+ print_('DataChannel with label ' + global.dataChannel.label + ' initiated ' +
+ 'locally.');
+ hookupDataChannelEvents();
+}
+
+function closeDataChannel(peerConnection) {
+ if (global.dataChannel == null)
+ error_('Closing DataChannel, but none exists.');
+ print_('DataChannel with label ' + global.dataChannel.label +
+ ' is beeing closed.');
+ global.dataChannel.close();
+}
+
+function createDtmfSender(peerConnection) {
+ if (global.dtmfSender != null)
+ error_('Creating DTMF sender, but we already have one.');
+
+ var localStream = global.localStream();
+ if (localStream == null)
+ error_('Creating DTMF sender but local stream is null.');
+ local_audio_track = localStream.getAudioTracks()[0];
+ global.dtmfSender = peerConnection.createDTMFSender(local_audio_track);
+ global.dtmfSender.ontonechange = global.dtmfOnToneChange;
+}
+
+/**
+ * Connects to the provided peerconnection_server.
+ *
+ * @param {string} serverUrl The server URL in string form without an ending
+ * slash, something like http://localhost:8888.
+ * @param {string} clientName The name to use when connecting to the server.
+ */
+function connect(serverUrl, clientName) {
+ if (global.ourPeerId != null)
+ error_('connecting, but is already connected.');
+
+ print_('Connecting to ' + serverUrl + ' as ' + clientName);
+ global.serverUrl = serverUrl;
+ global.ourClientName = clientName;
+
+ request = new XMLHttpRequest();
+ request.open('GET', serverUrl + '/sign_in?' + clientName, true);
+ print_(serverUrl + '/sign_in?' + clientName);
+ request.onreadystatechange = function() {
+ connectCallback_(request);
+ };
+ request.send();
+}
+
+/**
+ * Checks if the remote peer has connected. Returns peer-connected if that is
+ * the case, otherwise no-peer-connected.
+ */
+function remotePeerIsConnected() {
+ if (global.remotePeerId == null)
+ print_('no-peer-connected');
+ else
+ print_('peer-connected');
+}
+
+/**
+ * Creates a peer connection. Must be called before most other public functions
+ * in this file.
+ */
+function preparePeerConnection() {
+ if (global.peerConnection != null)
+ error_('creating peer connection, but we already have one.');
+
+ global.useRtpDataChannels = $('data-channel-type-rtp').checked;
+ global.peerConnection = createPeerConnection(STUN_SERVER,
+ global.useRtpDataChannels);
+ print_('ok-peerconnection-created');
+}
+
+/**
+ * Adds the local stream to the peer connection. You will have to re-negotiate
+ * the call for this to take effect in the call.
+ */
+function addLocalStream() {
+ if (global.peerConnection == null)
+ error_('adding local stream, but we have no peer connection.');
+
+ addLocalStreamToPeerConnection(global.peerConnection);
+ print_('ok-added');
+}
+
+/**
+ * Removes the local stream from the peer connection. You will have to
+ * re-negotiate the call for this to take effect in the call.
+ */
+function removeLocalStream() {
+ if (global.peerConnection == null)
+ error_('attempting to remove local stream, but no call is up');
+
+ removeLocalStreamFromPeerConnection(global.peerConnection);
+ print_('ok-local-stream-removed');
+}
+
+/**
+ * (see getReadyState_)
+ */
+function getPeerConnectionReadyState() {
+ print_(getReadyState_());
+}
+
+/**
+ * Toggles the remote audio stream's enabled state on the peer connection, given
+ * that a call is active. Returns ok-[typeToToggle]-toggled-to-[true/false]
+ * on success.
+ *
+ * @param {function} selectAudioOrVideoTrack A function that takes a remote
+ * stream as argument and returns a track (e.g. either the video or audio
+ * track).
+ * @param {function} typeToToggle Either "audio" or "video" depending on what
+ * the selector function selects.
+ */
+function toggleRemoteStream(selectAudioOrVideoTrack, typeToToggle) {
+ if (global.peerConnection == null)
+ error_('Tried to toggle remote stream, but have no peer connection.');
+ if (global.peerConnection.getRemoteStreams().length == 0)
+ error_('Tried to toggle remote stream, but not receiving any stream.');
+
+ var track = selectAudioOrVideoTrack(
+ global.peerConnection.getRemoteStreams()[0]);
+ toggle_(track, 'remote', typeToToggle);
+}
+
+/**
+ * See documentation on toggleRemoteStream (this function is the same except
+ * we are looking at local streams).
+ */
+function toggleLocalStream(selectAudioOrVideoTrack, typeToToggle) {
+ if (global.peerConnection == null)
+ error_('Tried to toggle local stream, but have no peer connection.');
+ if (global.peerConnection.getLocalStreams().length == 0)
+ error_('Tried to toggle local stream, but there is no local stream in ' +
+ 'the call.');
+
+ var track = selectAudioOrVideoTrack(
+ global.peerConnection.getLocalStreams()[0]);
+ toggle_(track, 'local', typeToToggle);
+}
+
+/**
+ * Hangs up a started call. Returns ok-call-hung-up on success. This tab will
+ * not accept any incoming calls after this call.
+ */
+function hangUp() {
+ if (global.peerConnection == null)
+ error_('hanging up, but has no peer connection');
+ if (getReadyState_() != 'active')
+ error_('hanging up, but ready state is not active (no call up).');
+ sendToPeer(global.remotePeerId, 'BYE');
+ closeCall_();
+ global.acceptsIncomingCalls = false;
+ print_('ok-call-hung-up');
+}
+
+/**
+ * Start accepting incoming calls.
+ */
+function acceptIncomingCalls() {
+ global.acceptsIncomingCalls = true;
+}
+
+/**
+ * Creates a DataChannel on the current PeerConnection. Only one DataChannel can
+ * be created on each PeerConnection.
+ * Returns ok-datachannel-created on success.
+ */
+function createDataChannelOnPeerConnection() {
+ if (global.peerConnection == null)
+ error_('Tried to create data channel, but have no peer connection.');
+
+ createDataChannel(global.peerConnection, global.ourClientName);
+ print_('ok-datachannel-created');
+}
+
+/**
+ * Close the DataChannel on the current PeerConnection.
+ * Returns ok-datachannel-close on success.
+ */
+function closeDataChannelOnPeerConnection() {
+ if (global.peerConnection == null)
+ error_('Tried to close data channel, but have no peer connection.');
+
+ closeDataChannel(global.peerConnection);
+ print_('ok-datachannel-close');
+}
+
+/**
+ * Creates a DTMF sender on the current PeerConnection.
+ * Returns ok-dtmfsender-created on success.
+ */
+function createDtmfSenderOnPeerConnection() {
+ if (global.peerConnection == null)
+ error_('Tried to create DTMF sender, but have no peer connection.');
+
+ createDtmfSender(global.peerConnection);
+ print_('ok-dtmfsender-created');
+}
+
+/**
+ * Send DTMF tones on the global.dtmfSender.
+ * Returns ok-dtmf-sent on success.
+ */
+function insertDtmfOnSender(tones, duration, interToneGap) {
+ if (global.dtmfSender == null)
+ error_('Tried to insert DTMF tones, but have no DTMF sender.');
+
+ insertDtmf(tones, duration, interToneGap);
+ print_('ok-dtmf-sent');
+}
+
+/**
+ * Sends a message to a peer through the peerconnection_server.
+ */
+function sendToPeer(peer, message) {
+ var messageToLog = message.sdp ? message.sdp : message;
+ print_('Sending message ' + messageToLog + ' to peer ' + peer + '.');
+
+ var request = new XMLHttpRequest();
+ var url = global.serverUrl + '/message?peer_id=' + global.ourPeerId + '&to=' +
+ peer;
+ request.open('POST', url, false);
+ request.setRequestHeader('Content-Type', 'text/plain');
+ request.send(message);
+}
+
+/**
+ * @param {!string} videoTagId The ID of the video tag to update.
+ * @param {!string} width The width of the video to update the video tag, if
+ * width or height is 0, size will be taken from videoTag.videoWidth.
+ * @param {!string} height The height of the video to update the video tag, if
+ * width or height is 0 size will be taken from the videoTag.videoHeight.
+ */
+function updateVideoTagSize(videoTagId, width, height) {
+ var videoTag = $(videoTagId);
+ if (width > 0 || height > 0) {
+ videoTag.width = width;
+ videoTag.height = height;
+ }
+ else {
+ if (videoTag.videoWidth > 0 || videoTag.videoHeight > 0) {
+ videoTag.width = videoTag.videoWidth;
+ videoTag.height = videoTag.videoHeight;
+ }
+ else {
+ print_('"' + videoTagId + '" video stream size is 0, skipping resize');
+ }
+ }
+ print_('Set video tag "' + videoTagId + '" size to ' + videoTag.width + 'x' +
+ videoTag.height);
+ displayVideoSize_(videoTag);
+}
+
+// Internals.
+
+/**
+ * Disconnects from the peerconnection server. Returns ok-disconnected on
+ * success.
+ */
+function disconnect_() {
+ if (global.ourPeerId == null)
+ return;
+
+ request = new XMLHttpRequest();
+ request.open('GET', global.serverUrl + '/sign_out?peer_id=' +
+ global.ourPeerId, false);
+ request.send();
+ global.ourPeerId = null;
+ print_('ok-disconnected');
+}
+
+/**
+* Returns true if we are disconnected from peerconnection_server.
+*/
+function isDisconnected_() {
+ return global.ourPeerId == null;
+}
+
+/**
+ * @private
+ * @return {!string} The current peer connection's ready state, or
+ * 'no-peer-connection' if there is no peer connection up.
+ *
+ * NOTE: The PeerConnection states are changing and until chromium has
+ * implemented the new states we have to use this interim solution of
+ * always assuming that the PeerConnection is 'active'.
+ */
+function getReadyState_() {
+ if (global.peerConnection == null)
+ return 'no-peer-connection';
+
+ return 'active';
+}
+
+/**
+ * This function asks permission to use the webcam and mic from the browser. It
+ * will return ok-requested to the test. This does not mean the request was
+ * approved though. The test will then have to click past the dialog that
+ * appears in Chrome, which will run either the OK or failed callback as a
+ * a result. To see which callback was called, use obtainGetUserMediaResult_().
+ * @private
+ * @param {string} constraints Defines what to be requested, with mandatory
+ * and optional constraints defined. The contents of this parameter depends
+ * on the WebRTC version. This should be JavaScript code that we eval().
+ */
+function doGetUserMedia_(constraints) {
+ if (!getUserMedia) {
+ print_('Browser does not support WebRTC.');
+ return;
+ }
+ try {
+ var evaluatedConstraints;
+ eval('evaluatedConstraints = ' + constraints);
+ } catch (exception) {
+ error_('Not valid JavaScript expression: ' + constraints);
+ }
+ print_('Requesting doGetUserMedia: constraints: ' + constraints);
+ getUserMedia(evaluatedConstraints, getUserMediaOkCallback_,
+ getUserMediaFailedCallback_);
+}
+
+/**
+ * Must be called after calling doGetUserMedia.
+ * @private
+ * @return {string} Returns not-called-yet if we have not yet been called back
+ * by WebRTC. Otherwise it returns either ok-got-stream or
+ * failed-with-error-x (where x is the error code from the error
+ * callback) depending on which callback got called by WebRTC.
+ */
+function obtainGetUserMediaResult_() {
+ if (global.requestWebcamAndMicrophoneResult == null)
+ global.requestWebcamAndMicrophoneResult = ' not called yet';
+
+ return global.requestWebcamAndMicrophoneResult;
+
+}
+
+/**
+ * Negotiates a call with the other side. This will create a peer connection on
+ * the other side if there isn't one.
+ *
+ * To call this method we need to be aware of the other side, e.g. we must be
+ * connected to peerconnection_server and we must have exactly one peer on that
+ * server.
+ *
+ * This method may be called any number of times. If you haven't added any
+ * streams to the call, an "empty" call will result. The method will return
+ * ok-negotiating immediately to the test if the negotiation was successfully
+ * sent.
+ * @private
+ */
+function negotiateCall_() {
+ if (global.peerConnection == null)
+ error_('Negotiating call, but we have no peer connection.');
+ if (global.ourPeerId == null)
+ error_('Negotiating call, but not connected.');
+ if (global.remotePeerId == null)
+ error_('Negotiating call, but missing remote peer.');
+
+ setupCall(global.peerConnection);
+ print_('ok-negotiating');
+}
+
+/**
+ * This provides the selected source id from the objects in the parameters
+ * provided to this function. If the audioSelect or video_select objects does
+ * not have any HTMLOptions children it will return null in the source object.
+ * @param {!object} audioSelect HTML drop down element with audio devices added
+ * as HTMLOptionsCollection children.
+ * @param {!object} videoSelect HTML drop down element with audio devices added
+ * as HTMLOptionsCollection children.
+ * @return {!object} source contains audio and video source ID from
+ * the selected devices in the drop down menu elements.
+ * @private
+ */
+function getSourcesFromField_(audioSelect, videoSelect) {
+ var source = {
+ audioId: null,
+ videoId: null
+ };
+ if (audioSelect.options.length > 0) {
+ source.audioId = audioSelect.options[audioSelect.selectedIndex].value;
+ }
+ if (videoSelect.options.length > 0) {
+ source.videoId = videoSelect.options[videoSelect.selectedIndex].value;
+ }
+ return source;
+}
+
+/**
+ * @private
+ * @param {NavigatorUserMediaError} error Error containing details.
+ */
+function getUserMediaFailedCallback_(error) {
+ print_('GetUserMedia FAILED: Maybe the camera is in use by another process?');
+ gRequestWebcamAndMicrophoneResult = 'failed-with-error-' + error.name;
+ print_(gRequestWebcamAndMicrophoneResult);
+}
+
+/** @private */
+function success_(method) {
+ print_(method + '(): success.');
+}
+
+/** @private */
+function failure_(method, error) {
+ error_(method + '() failed: ' + error);
+}
+
+/** @private */
+function iceCallback_(event) {
+ if (event.candidate)
+ sendToPeer(global.remotePeerId, JSON.stringify(event.candidate));
+}
+
+/** @private */
+function setLocalAndSendMessage_(session_description) {
+ session_description.sdp =
+ global.transformOutgoingSdp(session_description.sdp);
+ global.peerConnection.setLocalDescription(
+ session_description,
+ function() { success_('setLocalDescription'); },
+ function() { failure_('setLocalDescription'); });
+ print_('Sending SDP message:\n' + session_description.sdp);
+ sendToPeer(global.remotePeerId, JSON.stringify(session_description));
+}
+
+/** @private */
+function addStreamCallback_(event) {
+ print_('Receiving remote stream...');
+ var videoTag = document.getElementById('remote-view');
+ attachMediaStream(videoTag, event.stream);
+
+ window.addEventListener('loadedmetadata', function() {
+ displayVideoSize_(videoTag);}, true);
+}
+
+/** @private */
+function removeStreamCallback_(event) {
+ print_('Call ended.');
+ document.getElementById('remote-view').src = '';
+}
+
+/** @private */
+function onCreateDataChannelCallback_(event) {
+ if (global.dataChannel != null && global.dataChannel.readyState != 'closed') {
+ error_('Received DataChannel, but we already have one.');
+ }
+
+ global.dataChannel = event.channel;
+ print_('DataChannel with label ' + global.dataChannel.label +
+ ' initiated by remote peer.');
+ hookupDataChannelEvents();
+}
+
+/** @private */
+function hookupDataChannelEvents() {
+ global.dataChannel.onmessage = global.dataCallback;
+ global.dataChannel.onopen = onDataChannelReadyStateChange_;
+ global.dataChannel.onclose = onDataChannelReadyStateChange_;
+ // Trigger global.dataStatusCallback so an application is notified
+ // about the created data channel.
+ onDataChannelReadyStateChange_();
+}
+
+/** @private */
+function onDataChannelReadyStateChange_() {
+ var readyState = global.dataChannel.readyState;
+ print_('DataChannel state:' + readyState);
+ global.dataStatusCallback(readyState);
+}
+
+/**
+ * @private
+ * @param {MediaStream} stream Media stream.
+ */
+function getUserMediaOkCallback_(stream) {
+ global.localStream = stream;
+ global.requestWebcamAndMicrophoneResult = 'ok-got-stream';
+
+ if (stream.getVideoTracks().length > 0) {
+ // Show the video tag if we did request video in the getUserMedia call.
+ var videoTag = $('local-view');
+ attachMediaStream(videoTag, stream);
+
+ window.addEventListener('loadedmetadata', function() {
+ displayVideoSize_(videoTag);}, true);
+ }
+}
+
+/**
+ * @private
+ * @param {string} videoTag The ID of the video tag + stream used to
+ * write the size to a HTML tag based on id if the div's exists.
+ */
+function displayVideoSize_(videoTag) {
+ if ($(videoTag.id + '-stream-size') && $(videoTag.id + '-size')) {
+ if (videoTag.videoWidth > 0 || videoTag.videoHeight > 0) {
+ $(videoTag.id + '-stream-size').innerHTML = '(stream size: ' +
+ videoTag.videoWidth + 'x' +
+ videoTag.videoHeight + ')';
+ $(videoTag.id + '-size').innerHTML = videoTag.width + 'x' +
+ videoTag.height;
+ }
+ } else {
+ print_('Skipping updating -stream-size and -size tags due to div\'s ' +
+ 'are missing');
+ }
+}
/**
* Checks if the 'audiosrc' and 'videosrc' drop down menu elements has had all
* of its children appended in order to provide device ID's to the function
* 'updateGetUserMediaConstraints()', used in turn to populate the getUserMedia
- * constraints text box when the page has loaded. If not the user is informed
- * and instructions on how to populate the field is provided.
+ * constraints text box when the page has loaded.
+ * @private
*/
-function checkIfDeviceDropdownsArePopulated() {
+function checkIfDeviceDropdownsArePopulated_() {
if (document.addEventListener) {
$('audiosrc').addEventListener('DOMNodeInserted',
updateGetUserMediaConstraints, false);
$('videosrc').addEventListener('DOMNodeInserted',
updateGetUserMediaConstraints, false);
+ } else {
+ print_('addEventListener is not supported by your browser, cannot update ' +
+ 'device source ID\'s automatically. Select a device from the audio' +
+ ' or video source drop down menu to update device source id\'s');
}
- else {
- debug('addEventListener is not supported by your browser, cannot update ' +
- 'device source ID\'s automatically. Select a device from the audio ' +
- 'or video source drop down menu to update device source id\'s');
+}
+
+/**
+ * Register an input element to use local storage to remember its state between
+ * sessions (using local storage). Only input elements are supported.
+ * @private
+ * @param {!string} element_id to be used as a key for local storage and the id
+ * of the element to store the state for.
+ */
+function registerLocalStorage_(element_id) {
+ var element = $(element_id);
+ if (element.tagName != 'INPUT') {
+ error_('You can only use registerLocalStorage_ for input elements. ' +
+ 'Element \"' + element.tagName + '\" is not an input element. ');
+ }
+
+ if (localStorage.getItem(element.id) == 'undefined') {
+ storeLocalStorageField_(element);
+ } else {
+ getLocalStorageField_(element);
+ }
+ // Registers the appropriate events for input elements.
+ if (element.type == 'checkbox') {
+ element.onclick = function() { storeLocalStorageField_(this); };
+ } else if (element.type == 'text') {
+ element.onblur = function() { storeLocalStorageField_(this); };
+ } else {
+ error_('Unsupportered input type: ' + '\"' + element.type + '\"');
}
}
/**
- * Disconnect before the tab is closed.
+ * Fetches the stored values from local storage and updates checkbox status.
+ * @private
+ * @param {!Object} element of which id is representing the key parameter for
+ * local storage.
*/
-window.onbeforeunload = function() {
- if (!isDisconnected())
- disconnect();
-};
+function getLocalStorageField_(element) {
+ // Makes sure the checkbox status is matching the local storage value.
+ if (element.type == 'checkbox') {
+ element.checked = (localStorage.getItem(element.id) == 'true');
+ } else if (element.type == 'text') {
+ element.value = localStorage.getItem(element.id);
+ } else {
+ error_('Unsupportered input type: ' + '\"' + element.type + '\"');
+ }
+}
-// Internals.
+/**
+ * Stores the string value of the element object using local storage.
+ * @private
+ * @param {!Object} element of which id is representing the key parameter for
+ * local storage.
+ */
+function storeLocalStorageField_(element) {
+ if (element.type == 'checkbox') {
+ localStorage.setItem(element.id, element.checked);
+ } else if (element.type == 'text') {
+ localStorage.setItem(element.id, element.value);
+ }
+}
/**
* Create the peer connection if none is up (this is just convenience to
@@ -282,7 +1082,7 @@ window.onbeforeunload = function() {
* @private
*/
function ensureHasPeerConnection_() {
- if (getReadyState() == 'no-peer-connection') {
+ if (getReadyState_() == 'no-peer-connection') {
preparePeerConnection();
}
}
@@ -292,10 +1092,6 @@ function ensureHasPeerConnection_() {
* @param {string} message Text to print.
*/
function print_(message) {
- // Filter out uninteresting noise.
- if (message == 'ok-no-errors')
- return;
-
console.log(message);
$('messages').innerHTML += message + '<br>';
}
@@ -314,7 +1110,7 @@ function debug_(message) {
* @private
* @param {string} message Text to print.
*/
-function error(message) {
+function error_(message) {
$('debug').innerHTML += '<span style="color:red;">' + message + '</span><br>';
throw new Error(message);
}
@@ -330,7 +1126,7 @@ function getEvaluatedJavaScript_(stringRepresentation) {
eval('evaluatedJavaScript = ' + stringRepresentation);
return evaluatedJavaScript;
} catch (exception) {
- error('Not valid JavaScript expression: ' + stringRepresentation);
+ error_('Not valid JavaScript expression: ' + stringRepresentation);
}
}
@@ -356,21 +1152,6 @@ function swapSdpLines_(sdp, line, swapWith) {
return lines.join('\r\n');
}
-// TODO(phoglund): Not currently used; delete once it clear we do not need to
-// test opus prioritization.
-/** @private */
-function preferOpus_() {
- setOutgoingSdpTransform(function(sdp) {
- sdp = sdp.replace('103 104 111', '111 103 104');
-
- // TODO(phoglund): We need to swap the a= lines too. I don't think this
- // should be needed but it apparently is right now.
- return swapSdpLines_(sdp,
- 'a=rtpmap:103 ISAC/16000',
- 'a=rtpmap:111 opus/48000');
- });
-}
-
/** @private */
function forceIsac_() {
setOutgoingSdpTransform(function(sdp) {
@@ -394,7 +1175,7 @@ function hookupDataChannelCallbacks_() {
$('data-channel-status').value = status;
},
function(data_message) {
- debug('Received ' + data_message.data);
+ print_('Received ' + data_message.data);
$('data-channel-receive').value =
data_message.data + '\n' + $('data-channel-receive').value;
});
@@ -403,78 +1184,184 @@ function hookupDataChannelCallbacks_() {
/** @private */
function hookupDtmfSenderCallback_() {
setOnToneChange(function(tone) {
- debug('Sent DTMF tone: ' + tone.tone);
+ print_('Sent DTMF tone: ' + tone.tone);
$('dtmf-tones-sent').value =
tone.tone + '\n' + $('dtmf-tones-sent').value;
});
}
-/**
- * A list of element id's to be registered for local storage.
- */
-function setupLocalStorageFieldValues() {
- registerLocalStorage_('pc-server');
- registerLocalStorage_('pc-createanswer-constraints');
- registerLocalStorage_('pc-createoffer-constraints');
- registerLocalStorage_('get-devices-onload');
+/** @private */
+function toggle_(track, localOrRemote, audioOrVideo) {
+ if (!track)
+ error_('Tried to toggle ' + localOrRemote + ' ' + audioOrVideo +
+ ' stream, but has no such stream.');
+
+ track.enabled = !track.enabled;
+ print_('ok-' + audioOrVideo + '-toggled-to-' + track.enabled);
}
-/**
- * Register an input element to use local storage to remember its state between
- * sessions (using local storage). Only input elements are supported.
- * @private
- * @param {!string} element_id to be used as a key for local storage and the id
- * of the element to store the state for.
- */
-function registerLocalStorage_(element_id) {
- var element = $(element_id);
- if (element.tagName != 'INPUT') {
- error('You can only use registerLocalStorage_ for input elements. ' +
- 'Element \"' + element.tagName +'\" is not an input element. ');
+/** @private */
+function connectCallback_(request) {
+ print_('Connect callback: ' + request.status + ', ' + request.readyState);
+ if (request.status == 0) {
+ print_('peerconnection_server doesn\'t seem to be up.');
+ print_('failed-to-connect');
+ }
+ if (request.readyState == 4 && request.status == 200) {
+ global.ourPeerId = parseOurPeerId_(request.responseText);
+ global.remotePeerId = parseRemotePeerIdIfConnected_(request.responseText);
+ startHangingGet_(global.serverUrl, global.ourPeerId);
+ print_('ok-connected');
}
+}
- if (localStorage.getItem(element.id) == 'undefined') {
- storeLocalStorageField_(element);
- } else {
- getLocalStorageField_(element);
+/** @private */
+function parseOurPeerId_(responseText) {
+ // According to peerconnection_server's protocol.
+ var peerList = responseText.split('\n');
+ return parseInt(peerList[0].split(',')[1]);
+}
+
+/** @private */
+function parseRemotePeerIdIfConnected_(responseText) {
+ var peerList = responseText.split('\n');
+ if (peerList.length == 1) {
+ // No peers have connected yet - we'll get their id later in a notification.
+ return null;
}
- // Registers the appropriate events for input elements.
- if (element.type == 'checkbox') {
- element.onclick = function() { storeLocalStorageField_(this); };
- } else if (element.type == 'text') {
- element.onblur = function() { storeLocalStorageField_(this); };
- } else {
- error('Unsupportered input type: ' + '\"' + element.type + '\"' );
+ var remotePeerId = null;
+ for (var i = 0; i < peerList.length; i++) {
+ if (peerList[i].length == 0)
+ continue;
+
+ var parsed = peerList[i].split(',');
+ var name = parsed[0];
+ var id = parsed[1];
+
+ if (id != global.ourPeerId) {
+ print_('Found remote peer with name ' + name + ', id ' +
+ id + ' when connecting.');
+
+ // There should be at most one remote peer in this test.
+ if (remotePeerId != null)
+ error_('Expected just one remote peer in this test: ' +
+ 'found several.');
+
+ // Found a remote peer.
+ remotePeerId = id;
+ }
}
+ return remotePeerId;
}
-/**
- * Fetches the stored values from local storage and updates checkbox status.
- * @private
- * @param {!Object} element of which id is representing the key parameter for
- * local storage.
- */
-function getLocalStorageField_(element) {
- // Makes sure the checkbox status is matching the local storage value.
- if (element.type == 'checkbox') {
- element.checked = (localStorage.getItem(element.id) == 'true');
- } else if (element.type == 'text' ) {
- element.value = localStorage.getItem(element.id);
- } else {
- error('Unsupportered input type: ' + '\"' + element.type + '\"' );
+/** @private */
+function startHangingGet_(server, ourId) {
+ if (isDisconnected_())
+ return;
+
+ hangingGetRequest = new XMLHttpRequest();
+ hangingGetRequest.onreadystatechange = function() {
+ hangingGetCallback_(hangingGetRequest, server, ourId);
+ };
+ hangingGetRequest.ontimeout = function() {
+ hangingGetTimeoutCallback_(hangingGetRequest, server, ourId);
+ };
+ callUrl = server + '/wait?peer_id=' + ourId;
+ print_('Sending ' + callUrl);
+ hangingGetRequest.open('GET', callUrl, true);
+ hangingGetRequest.send();
+}
+
+/** @private */
+function hangingGetCallback_(hangingGetRequest, server, ourId) {
+ if (hangingGetRequest.readyState != 4 || hangingGetRequest.status == 0) {
+ // Code 0 is not possible if the server actually responded. Ignore.
+ return;
}
+ if (hangingGetRequest.status != 200) {
+ error_('Error ' + hangingGetRequest.status + ' from server: ' +
+ hangingGetRequest.statusText);
+ }
+ var targetId = readResponseHeader_(hangingGetRequest, 'Pragma');
+ if (targetId == ourId)
+ handleServerNotification_(hangingGetRequest.responseText);
+ else
+ handlePeerMessage_(targetId, hangingGetRequest.responseText);
+
+ hangingGetRequest.abort();
+ restartHangingGet_(server, ourId);
}
-/**
- * Stores the string value of the element object using local storage.
- * @private
- * @param {!Object} element of which id is representing the key parameter for
- * local storage.
- */
-function storeLocalStorageField_(element) {
- if (element.type == 'checkbox') {
- localStorage.setItem(element.id, element.checked);
- } else if (element.type == 'text') {
- localStorage.setItem(element.id, element.value);
+/** @private */
+function hangingGetTimeoutCallback_(hangingGetRequest, server, ourId) {
+ print_('Hanging GET times out, re-issuing...');
+ hangingGetRequest.abort();
+ restartHangingGet_(server, ourId);
+}
+
+/** @private */
+function handleServerNotification_(message) {
+ var parsed = message.split(',');
+ if (parseInt(parsed[2]) == 1) {
+ // Peer connected - this must be our remote peer, and it must mean we
+ // connected before them (except if we happened to connect to the server
+ // at precisely the same moment).
+ print_('Found remote peer with name ' + parsed[0] + ', id ' + parsed[1] +
+ ' when connecting.');
+ global.remotePeerId = parseInt(parsed[1]);
+ }
+}
+
+/** @private */
+function closeCall_() {
+ if (global.peerConnection == null)
+ debug_('Closing call, but no call active.');
+ global.peerConnection.close();
+ global.peerConnection = null;
+}
+
+/** @private */
+function handlePeerMessage_(peerId, message) {
+ print_('Received message from peer ' + peerId + ': ' + message);
+ if (peerId != global.remotePeerId) {
+ error_('Received notification from unknown peer ' + peerId +
+ ' (only know about ' + global.remotePeerId + '.');
+ }
+ if (message.search('BYE') == 0) {
+ print_('Received BYE from peer: closing call');
+ closeCall_();
+ return;
+ }
+ if (global.peerConnection == null && global.acceptsIncomingCalls) {
+ // The other side is calling us.
+ print_('We are being called: answer...');
+
+ global.peerConnection = createPeerConnection(STUN_SERVER,
+ global.useRtpDataChannels);
+ if ($('auto-add-stream-oncall') &&
+ obtainGetUserMediaResult_() == 'ok-got-stream') {
+ print_('We have a local stream, so hook it up automatically.');
+ addLocalStreamToPeerConnection(global.peerConnection);
+ }
+ answerCall(global.peerConnection, message);
+ return;
+ }
+ handleMessage(global.peerConnection, message);
+}
+
+/** @private */
+function restartHangingGet_(server, ourId) {
+ window.setTimeout(function() {
+ startHangingGet_(server, ourId);
+ }, 0);
+}
+
+/** @private */
+function readResponseHeader_(request, key) {
+ var value = request.getResponseHeader(key);
+ if (value == null || value.length == 0) {
+ error_('Received empty value ' + value +
+ ' for response header key ' + key + '.');
}
+ return parseInt(value);
}
« 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