Chromium Code Reviews| 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..a8d2f9bd8e42ae2fb91ac647ce188e781140b386 100644 |
| --- a/chrome/test/data/webrtc/manual/peerconnection.js |
| +++ b/chrome/test/data/webrtc/manual/peerconnection.js |
| @@ -4,8 +4,36 @@ |
| * found in the LICENSE file. |
| */ |
| + /** |
| + * See http://dev.w3.org/2011/webrtc/editor/getusermedia.html for more |
|
phoglund_chromium
2014/01/03 16:07:55
Also link to the peerconnection spec.
jansson
2014/01/07 08:47:52
This http://dev.w3.org/2011/webrtc/editor/webrtc.h
|
| + * information on getUserMedia. |
| + */ |
| + |
| +/** TODO 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 +43,44 @@ $ = 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 and setup callbacks in the scripts |
| + * shared with browser tests or automated tests. Enumerates devices available |
|
phoglund_chromium
2014/01/03 16:07:55
Except they're not shared anymore, right?
jansson
2014/01/07 08:47:52
Done.
|
| + * via getUserMedia. |
| */ |
| -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(); |
| } |
| + registerElementForEvents_(); |
| +}; |
| + |
| +/** |
| + * Disconnect before the tab is closed. |
| + */ |
| +window.onbeforeunload = function() { |
| + if (!isDisconnected_()) |
|
phoglund_chromium
2014/01/03 16:07:55
Move this check into disconnect_() (just return if
jansson
2014/01/07 08:47:52
Done.
|
| + 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'); |
| + registerLocalStorage_('auto-add-stream-when-called'); |
| + registerLocalStorage_('data-channel-type-rtp'); |
| } |
| +// 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 +88,7 @@ function setPcDataChannelType() { |
| function getUserMediaFromHere() { |
|
phoglund_chromium
2014/01/03 16:07:55
You should be able to get rid of all of these (but
jansson
2014/01/07 08:47:52
I will create a separate patch where I assign appr
|
| var constraints = $('getusermedia-constraints').value; |
| try { |
| - doGetUserMedia(constraints); |
| + doGetUserMedia_(constraints); |
| } catch (exception) { |
| print_('getUserMedia says: ' + exception); |
| } |
| @@ -48,24 +99,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 +126,7 @@ function removeLocalStreamFromHere() { |
| function hangUpFromHere() { |
| hangUp(); |
| - acceptIncomingCallsAgain(); |
| + acceptIncomingCalls(); |
| } |
| function toggleRemoteVideoFromHere() { |
| @@ -162,13 +209,13 @@ function updateGetUserMediaConstraints() { |
| audio: $('audio').checked, |
| video: $('video').checked |
| }; |
| + |
| if (audio_selected.disabled == false && video_selected.disabled == false) { |
| - var devices = getSourcesFromField(audio_selected, video_selected); |
| + var devices = getSourcesFromField_(audio_selected, video_selected); |
| if ($('audio').checked == true) { |
| if (devices.audio_id != null) { |
| constraints.audio = {optional: [{sourceId: devices.audio_id}]}; |
| - } |
| - else { |
| + } else { |
| constraints.audio = true; |
| } |
| } |
| @@ -184,6 +231,7 @@ function updateGetUserMediaConstraints() { |
| } |
| } |
| } |
| + |
| if ($('screencapture').checked) { |
| var constraints = { |
| audio: $('audio').checked, |
| @@ -192,11 +240,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 +254,916 @@ 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) |
|
phoglund_chromium
2014/01/03 16:07:55
In the next patch, make these accessors on the glo
jansson
2014/01/07 08:47:52
Adding it to my notes.
|
| + error_('Tried to stop local stream, ' + |
| + 'but media access is not granted.'); |
| + |
| + global.localStream.stop(); |
| +} |
| + |
| +// Functions callable from other JavaScript modules. |
|
phoglund_chromium
2014/01/03 16:07:55
Remove this comment.
jansson
2014/01/07 08:47:52
Done.
|
| + |
| +/** |
| + * 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_(); |
| +} |
| + |
|
phoglund_chromium
2014/01/03 16:07:55
These setters should be on the global object (perh
jansson
2014/01/07 08:47:52
Adding to my todo list.
|
| +/** |
| + * 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 = function(sdp) { return sdp; }; |
|
phoglund_chromium
2014/01/03 16:07:55
This is wrong: should assign to transformFunction.
jansson
2014/01/07 08:47:52
Done.
|
| +} |
| + |
| +/** |
| + * 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; |
| +} |
| + |
| +/** TODO Fix param definitions |
|
phoglund_chromium
2014/01/03 16:07:55
Remove or fix this comment.
jansson
2014/01/07 08:47:52
Done.
phoglund_chromium
2014/01/07 09:52:25
I rather meant to fix the TODO. In this case, remo
jansson
2014/01/07 10:22:35
Done.
|
| + * Inserts DTMF tones on an active DTMF sender. |
| + * @param {string} tones The string that will be sent to the remote peer. |
| + * @param {string} duration The string that will be sent to the remote peer. |
| + * @param {string} interToneGap The string that will be sent to the remote peer. |
| + */ |
| +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); |
| +} |
| + |
| +// Public interface towards the other javascript files, such as |
|
phoglund_chromium
2014/01/03 16:07:55
Remove this comment, it no longer applies.
jansson
2014/01/07 08:47:52
Done.
|
| +// message_handling.js. The contract for these functions is described in |
| +// message_handling.js. |
| + |
| +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.'); |
|
phoglund_chromium
2014/01/03 16:07:55
Nit: remove {}.
jansson
2014/01/07 08:47:52
Done.
|
| + } |
| + |
| + 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) { |
|
phoglund_chromium
2014/01/03 16:07:55
I think these functions are probably over-generali
jansson
2014/01/07 08:47:52
Adding to my TODO list.
|
| + 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; |
| +} |
| + |
| +/** |
| + * Do not auto-add the local stream when called. |
| + */ |
| +function doNotAutoAddLocalStreamWhenCalled() { |
| + gAutoAddLocalToPeerConnectionStreamWhenCalled = false; |
|
phoglund_chromium
2014/01/03 16:07:55
You missed this global variable?
jansson
2014/01/07 08:47:52
Good catch, also It's not used anymore (used by au
|
| +} |
| + |
| +/** |
| + * 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'); |
| +} |
| + |
| +/** |
| + * An array of elements to be registered for for DOM events. |
| + * @private |
| + */ |
| +function registerElementForEvents_() { |
| + // Add element id's to be registered for events and functions. |
| + // Remember to add the element id to registerFunctionForElements_() as well. |
| + var element_id_list = ['audio', 'video', 'videosrc', 'audiosrc', 're-request', |
| + 'screencapture', 'video-width', 'video-height', |
| + 'get-devices', ]; |
| + |
| + for (var id in element_id_list) { |
| + var element = $(element_id_list[id]); |
| + if (element != null) |
| + registerEventBasedOnElementType_(element); |
| + else |
| + error_('Element does not exist ' + '\"' + element + '\"'); |
| + } |
| +} |
| + |
| +/** |
| + * Assigns the appropriate event type based on element type. |
| + * @private |
| + * @param {!object} element assigned to the appropriate event listener based |
| + * on element.type. |
| + */ |
| +function registerEventBasedOnElementType_(element) { |
|
phoglund_chromium
2014/01/03 16:07:55
I don't understand these. Where did they come from
jansson
2014/01/07 08:47:52
Sorry, my eagerness have added extra functionality
|
| + switch (element.type) { |
| + case 'checkbox': |
| + case 'submit': |
| + element.onclick = function() { registerFunctionForElements_(this); }; |
| + break; |
| + case 'text': |
| + element.onblur = function() { registerFunctionForElements_(this); }; |
| + break; |
| + case 'select-one': |
| + element.onchange = function() { registerFunctionForElements_(this); }; |
| + break; |
| + default: |
| + error_('Unsupportered element type: ' + '\"' + element.type + '\"'); |
| + } |
| +} |
| + |
| +/** |
| + * Registers a function to be called based on element.id. |
| + * @private |
| + * @param {!object} element assigned to the appropriate function based on |
| + * element.id. |
| + */ |
| +function registerFunctionForElements_(element) { |
| + switch (element.id) { |
| + case 'audio': |
| + case 'video': |
| + case 'videosrc': |
| + case 'audiosrc': |
| + case 'screencapture': |
| + case 'video-width': |
| + case 'video-height': |
| + updateGetUserMediaConstraints(); |
| + break; |
| + case 're-request': |
| + getUserMediaFromHere(); |
| + break; |
| + case 'get-devices': |
| + getDevices(); |
| + default: |
| + error_('Element \'' + element.id + '\' is not defined for a function'); |
| + } |
| +} |
| + |
| +/** |
| + * 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_() { |
|
phoglund_chromium
2014/01/07 09:52:25
You forgot to move the check if we're already disc
jansson
2014/01/07 10:22:35
Done.
|
| + if (global.ourPeerId == null) |
| + error_('Disconnecting, but we are not connected.'); |
| + |
| + 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. The other side will automatically add any |
| + * stream it has unless doNotAutoAddLocalStreamWhenCalled() has been called. |
| + * |
| + * 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 audio_select or video_select objects does |
| + * not have any HTMLOptions children it will return null in the source object. |
| + * @param {object} audio_select HTML drop down element with audio devices added |
| + * as HTMLOptionsCollection children. |
| + * @param {object} video_select HTML drop down element with audio devices added |
| + * as HTMLPptionsCollection children. |
| + * @return {object} audio_id video_id Containing audio and video source ID from |
| + * the selected devices in the drop down menus provided as parameters to |
| + * this function. |
| + * @private |
| + */ |
| +function getSourcesFromField_(audio_select, video_select) { |
| + var source = { |
| + audio_id: null, |
| + video_id: null |
| + }; |
| + if (audio_select.options.length > 0) { |
| + source.audio_id = audio_select.options[audio_select.selectedIndex].value; |
| + } |
| + if (video_select.options.length > 0) { |
| + source.video_id = video_select.options[video_select.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 +1171,7 @@ window.onbeforeunload = function() { |
| * @private |
| */ |
| function ensureHasPeerConnection_() { |
| - if (getReadyState() == 'no-peer-connection') { |
| + if (getReadyState_() == 'no-peer-connection') { |
| preparePeerConnection(); |
| } |
| } |
| @@ -292,10 +1181,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 +1199,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 +1215,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 +1241,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 +1264,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 +1273,183 @@ 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_()) |
|
phoglund_chromium
2014/01/07 09:52:25
Not sure why you removed this?
jansson
2014/01/07 10:22:35
Not sure, what I was thinking, added it back.
|
| + 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); |
| } |