| 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);
|
| }
|
|
|