2 * Copyright (c) 2012 The Chromium Authors. All rights reserved.
3 * Use of this source code is governed by a BSD-style license that can be
4 * found in the LICENSE file.
8 var gTransformOutgoingSdp = function(sdp) { return sdp; };
11 var gCreateAnswerConstraints = {};
14 var gCreateOfferConstraints = {};
17 var gDataChannel = null;
20 var gDataStatusCallback = function(status) {};
23 var gDataCallback = function(data) {};
26 var gDtmfSender = null;
29 var gDtmfOnToneChange = function(tone) {};
32 * Sets the transform to apply just before setting the local description and
33 * sending to the peer.
34 * @param {function} transformFunction A function which takes one SDP string as
35 * argument and returns the modified SDP string.
37 function setOutgoingSdpTransform(transformFunction) {
38 gTransformOutgoingSdp = transformFunction;
42 * Sets the MediaConstraints to be used for PeerConnection createAnswer() calls.
43 * @param {string} mediaConstraints The constraints, as defined in the
44 * PeerConnection JS API spec.
46 function setCreateAnswerConstraints(mediaConstraints) {
47 gCreateAnswerConstraints = mediaConstraints;
51 * Sets the MediaConstraints to be used for PeerConnection createOffer() calls.
52 * @param {string} mediaConstraints The constraints, as defined in the
53 * PeerConnection JS API spec.
55 function setCreateOfferConstraints(mediaConstraints) {
56 gCreateOfferConstraints = mediaConstraints;
60 * Sets the callback functions that will receive DataChannel readyState updates
62 * @param {function} status_callback The function that will receive a string
64 * the current DataChannel readyState.
65 * @param {function} data_callback The function that will a string with data
66 * received from the remote peer.
68 function setDataCallbacks(status_callback, data_callback) {
69 gDataStatusCallback = status_callback;
70 gDataCallback = data_callback;
74 * Sends data on an active DataChannel.
75 * @param {string} data The string that will be sent to the remote peer.
77 function sendDataOnChannel(data) {
78 if (gDataChannel == null)
79 throw failTest('Trying to send data, but there is no DataChannel.');
80 gDataChannel.send(data);
84 * Sets the callback function that will receive DTMF sender ontonechange events.
85 * @param {function} ontonechange The function that will receive a string with
86 * the tone that has just begun playout.
88 function setOnToneChange(ontonechange) {
89 gDtmfOnToneChange = ontonechange;
93 * Inserts DTMF tones on an active DTMF sender.
94 * @param {string} data The string that will be sent to the remote peer.
96 function insertDtmf(tones, duration, interToneGap) {
97 if (gDtmfSender == null)
98 throw failTest('Trying to send DTMF, but there is no DTMF sender.');
99 gDtmfSender.insertDTMF(tones, duration, interToneGap);
102 // Public interface towards the other javascript files, such as
103 // message_handling.js. The contract for these functions is described in
104 // message_handling.js.
106 function handleMessage(peerConnection, message) {
107 var parsed_msg = JSON.parse(message);
108 if (parsed_msg.type) {
109 var session_description = new RTCSessionDescription(parsed_msg);
110 peerConnection.setRemoteDescription(
112 function() { success_('setRemoteDescription'); },
113 function() { failure_('setRemoteDescription'); });
114 if (session_description.type == 'offer') {
115 debug('createAnswer with constraints: ' +
116 JSON.stringify(gCreateAnswerConstraints, null, ' '));
117 peerConnection.createAnswer(
118 setLocalAndSendMessage_,
119 function() { failure_('createAnswer'); },
120 gCreateAnswerConstraints);
123 } else if (parsed_msg.candidate) {
124 var candidate = new RTCIceCandidate(parsed_msg);
125 peerConnection.addIceCandidate(candidate);
128 addTestFailure('unknown message received');
132 function createPeerConnection(stun_server, useRtpDataChannels) {
133 servers = {iceServers: [{url: 'stun:' + stun_server}]};
135 var constraints = { optional: [{ RtpDataChannels: useRtpDataChannels }]};
136 peerConnection = new webkitRTCPeerConnection(servers, constraints);
137 } catch (exception) {
138 throw failTest('Failed to create peer connection: ' + exception);
140 peerConnection.onaddstream = addStreamCallback_;
141 peerConnection.onremovestream = removeStreamCallback_;
142 peerConnection.onicecandidate = iceCallback_;
143 peerConnection.ondatachannel = onCreateDataChannelCallback_;
144 return peerConnection;
147 function setupCall(peerConnection) {
148 debug('createOffer with constraints: ' +
149 JSON.stringify(gCreateOfferConstraints, null, ' '));
150 peerConnection.createOffer(
151 setLocalAndSendMessage_,
152 function() { failure_('createOffer'); },
153 gCreateOfferConstraints);
156 function answerCall(peerConnection, message) {
157 handleMessage(peerConnection, message);
160 function createDataChannel(peerConnection, label) {
161 if (gDataChannel != null && gDataChannel.readyState != 'closed') {
162 throw failTest('Creating DataChannel, but we already have one.');
165 gDataChannel = peerConnection.createDataChannel(label, { reliable: false });
166 debug('DataChannel with label ' + gDataChannel.label + ' initiated locally.');
167 hookupDataChannelEvents();
170 function closeDataChannel(peerConnection) {
171 if (gDataChannel == null)
172 throw failTest('Closing DataChannel, but none exists.');
173 debug('DataChannel with label ' + gDataChannel.label + ' is beeing closed.');
174 gDataChannel.close();
177 function createDtmfSender(peerConnection) {
178 if (gDtmfSender != null)
179 throw failTest('Creating DTMF sender, but we already have one.');
181 var localStream = getLocalStream();
182 if (localStream == null)
183 throw failTest('Creating DTMF sender but local stream is null.');
184 local_audio_track = localStream.getAudioTracks()[0];
185 gDtmfSender = peerConnection.createDTMFSender(local_audio_track);
186 gDtmfSender.ontonechange = gDtmfOnToneChange;
191 function success_(method) {
192 debug(method + '(): success.');
196 function failure_(method, error) {
197 throw failTest(method + '() failed: ' + error);
201 function iceCallback_(event) {
203 sendToPeer(gRemotePeerId, JSON.stringify(event.candidate));
207 function setLocalAndSendMessage_(session_description) {
208 session_description.sdp = gTransformOutgoingSdp(session_description.sdp);
209 peerConnection.setLocalDescription(
211 function() { success_('setLocalDescription'); },
212 function() { failure_('setLocalDescription'); });
213 debug('Sending SDP message:\n' + session_description.sdp);
214 sendToPeer(gRemotePeerId, JSON.stringify(session_description));
218 function addStreamCallback_(event) {
219 debug('Receiving remote stream...');
220 var videoTag = document.getElementById('remote-view');
221 attachMediaStream(videoTag, event.stream);
223 // Due to crbug.com/110938 the size is 0 when onloadedmetadata fires.
224 // videoTag.onloadedmetadata = displayVideoSize_(videoTag);
225 // Use setTimeout as a workaround for now.
226 // Displays the remote video size for both the video element and the stream.
227 setTimeout(function() {displayVideoSize_(videoTag);}, 500);
231 function removeStreamCallback_(event) {
232 debug('Call ended.');
233 document.getElementById('remote-view').src = '';
237 function onCreateDataChannelCallback_(event) {
238 if (gDataChannel != null && gDataChannel.readyState != 'closed') {
239 throw failTest('Received DataChannel, but we already have one.');
242 gDataChannel = event.channel;
243 debug('DataChannel with label ' + gDataChannel.label +
244 ' initiated by remote peer.');
245 hookupDataChannelEvents();
249 function hookupDataChannelEvents() {
250 gDataChannel.onmessage = gDataCallback;
251 gDataChannel.onopen = onDataChannelReadyStateChange_;
252 gDataChannel.onclose = onDataChannelReadyStateChange_;
253 // Trigger gDataStatusCallback so an application is notified
254 // about the created data channel.
255 onDataChannelReadyStateChange_();
259 function onDataChannelReadyStateChange_() {
260 var readyState = gDataChannel.readyState;
261 debug('DataChannel state:' + readyState);
262 gDataStatusCallback(readyState);