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 throw failTest('unknown message received');
131 function createPeerConnection(stun_server
, useRtpDataChannels
) {
132 servers
= {iceServers
: [{url
: 'stun:' + stun_server
}]};
134 var constraints
= { optional
: [{ RtpDataChannels
: useRtpDataChannels
}]};
135 peerConnection
= new webkitRTCPeerConnection(servers
, constraints
);
136 } catch (exception
) {
137 throw failTest('Failed to create peer connection: ' + exception
);
139 peerConnection
.onaddstream
= addStreamCallback_
;
140 peerConnection
.onremovestream
= removeStreamCallback_
;
141 peerConnection
.onicecandidate
= iceCallback_
;
142 peerConnection
.ondatachannel
= onCreateDataChannelCallback_
;
143 return peerConnection
;
146 function setupCall(peerConnection
) {
147 debug('createOffer with constraints: ' +
148 JSON
.stringify(gCreateOfferConstraints
, null, ' '));
149 peerConnection
.createOffer(
150 setLocalAndSendMessage_
,
151 function() { failure_('createOffer'); },
152 gCreateOfferConstraints
);
155 function answerCall(peerConnection
, message
) {
156 handleMessage(peerConnection
, message
);
159 function createDataChannel(peerConnection
, label
) {
160 if (gDataChannel
!= null && gDataChannel
.readyState
!= 'closed') {
161 throw failTest('Creating DataChannel, but we already have one.');
164 gDataChannel
= peerConnection
.createDataChannel(label
, { reliable
: false });
165 debug('DataChannel with label ' + gDataChannel
.label
+ ' initiated locally.');
166 hookupDataChannelEvents();
169 function closeDataChannel(peerConnection
) {
170 if (gDataChannel
== null)
171 throw failTest('Closing DataChannel, but none exists.');
172 debug('DataChannel with label ' + gDataChannel
.label
+ ' is beeing closed.');
173 gDataChannel
.close();
176 function createDtmfSender(peerConnection
) {
177 if (gDtmfSender
!= null)
178 throw failTest('Creating DTMF sender, but we already have one.');
180 var localStream
= getLocalStream();
181 if (localStream
== null)
182 throw failTest('Creating DTMF sender but local stream is null.');
183 local_audio_track
= localStream
.getAudioTracks()[0];
184 gDtmfSender
= peerConnection
.createDTMFSender(local_audio_track
);
185 gDtmfSender
.ontonechange
= gDtmfOnToneChange
;
190 function success_(method
) {
191 debug(method
+ '(): success.');
195 function failure_(method
, error
) {
196 throw failTest(method
+ '() failed: ' + error
);
200 function iceCallback_(event
) {
202 sendToPeer(gRemotePeerId
, JSON
.stringify(event
.candidate
));
206 function setLocalAndSendMessage_(session_description
) {
207 session_description
.sdp
= gTransformOutgoingSdp(session_description
.sdp
);
208 peerConnection
.setLocalDescription(
210 function() { success_('setLocalDescription'); },
211 function() { failure_('setLocalDescription'); });
212 debug('Sending SDP message:\n' + session_description
.sdp
);
213 sendToPeer(gRemotePeerId
, JSON
.stringify(session_description
));
217 function addStreamCallback_(event
) {
218 debug('Receiving remote stream...');
219 var videoTag
= document
.getElementById('remote-view');
220 attachMediaStream(videoTag
, event
.stream
);
222 // Due to crbug.com/110938 the size is 0 when onloadedmetadata fires.
223 // videoTag.onloadedmetadata = displayVideoSize_(videoTag);
224 // Use setTimeout as a workaround for now.
225 // Displays the remote video size for both the video element and the stream.
226 setTimeout(function() {displayVideoSize_(videoTag
);}, 500);
230 function removeStreamCallback_(event
) {
231 debug('Call ended.');
232 document
.getElementById('remote-view').src
= '';
236 function onCreateDataChannelCallback_(event
) {
237 if (gDataChannel
!= null && gDataChannel
.readyState
!= 'closed') {
238 throw failTest('Received DataChannel, but we already have one.');
241 gDataChannel
= event
.channel
;
242 debug('DataChannel with label ' + gDataChannel
.label
+
243 ' initiated by remote peer.');
244 hookupDataChannelEvents();
248 function hookupDataChannelEvents() {
249 gDataChannel
.onmessage
= gDataCallback
;
250 gDataChannel
.onopen
= onDataChannelReadyStateChange_
;
251 gDataChannel
.onclose
= onDataChannelReadyStateChange_
;
252 // Trigger gDataStatusCallback so an application is notified
253 // about the created data channel.
254 onDataChannelReadyStateChange_();
258 function onDataChannelReadyStateChange_() {
259 var readyState
= gDataChannel
.readyState
;
260 debug('DataChannel state:' + readyState
);
261 gDataStatusCallback(readyState
);