OLD | NEW |
1 /** | 1 /** |
2 * Copyright (c) 2012 The Chromium Authors. All rights reserved. | 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 | 3 * Use of this source code is governed by a BSD-style license that can be |
4 * found in the LICENSE file. | 4 * found in the LICENSE file. |
5 */ | 5 */ |
6 | 6 |
7 /** @private */ | 7 // TODO(phoglund): merge into message_handling.js. |
8 var gTransformOutgoingSdp = function(sdp) { return sdp; }; | |
9 | |
10 /** @private */ | |
11 var gCreateAnswerConstraints = {}; | |
12 | |
13 /** @private */ | |
14 var gCreateOfferConstraints = {}; | |
15 | |
16 /** @private */ | |
17 var gDataChannel = null; | |
18 | |
19 /** @private */ | |
20 var gDataStatusCallback = function(status) {}; | |
21 | |
22 /** @private */ | |
23 var gDataCallback = function(data) {}; | |
24 | |
25 /** @private */ | |
26 var gDtmfSender = null; | |
27 | |
28 /** @private */ | |
29 var gDtmfOnToneChange = function(tone) {}; | |
30 | 8 |
31 /** @private */ | 9 /** @private */ |
32 var gHasSeenCryptoInSdp = 'no-crypto-seen'; | 10 var gHasSeenCryptoInSdp = 'no-crypto-seen'; |
33 | 11 |
34 /** | |
35 * Sets the transform to apply just before setting the local description and | |
36 * sending to the peer. | |
37 * @param {function} transformFunction A function which takes one SDP string as | |
38 * argument and returns the modified SDP string. | |
39 */ | |
40 function setOutgoingSdpTransform(transformFunction) { | |
41 gTransformOutgoingSdp = transformFunction; | |
42 } | |
43 | |
44 /** | |
45 * Sets the MediaConstraints to be used for PeerConnection createAnswer() calls. | |
46 * @param {string} mediaConstraints The constraints, as defined in the | |
47 * PeerConnection JS API spec. | |
48 */ | |
49 function setCreateAnswerConstraints(mediaConstraints) { | |
50 gCreateAnswerConstraints = mediaConstraints; | |
51 } | |
52 | |
53 /** | |
54 * Sets the MediaConstraints to be used for PeerConnection createOffer() calls. | |
55 * @param {string} mediaConstraints The constraints, as defined in the | |
56 * PeerConnection JS API spec. | |
57 */ | |
58 function setCreateOfferConstraints(mediaConstraints) { | |
59 gCreateOfferConstraints = mediaConstraints; | |
60 } | |
61 | |
62 /** | |
63 * Sets the callback functions that will receive DataChannel readyState updates | |
64 * and received data. | |
65 * @param {function} status_callback The function that will receive a string | |
66 * with | |
67 * the current DataChannel readyState. | |
68 * @param {function} data_callback The function that will a string with data | |
69 * received from the remote peer. | |
70 */ | |
71 function setDataCallbacks(status_callback, data_callback) { | |
72 gDataStatusCallback = status_callback; | |
73 gDataCallback = data_callback; | |
74 } | |
75 | |
76 /** | |
77 * Sends data on an active DataChannel. | |
78 * @param {string} data The string that will be sent to the remote peer. | |
79 */ | |
80 function sendDataOnChannel(data) { | |
81 if (gDataChannel == null) | |
82 throw failTest('Trying to send data, but there is no DataChannel.'); | |
83 gDataChannel.send(data); | |
84 } | |
85 | |
86 /** | |
87 * Sets the callback function that will receive DTMF sender ontonechange events. | |
88 * @param {function} ontonechange The function that will receive a string with | |
89 * the tone that has just begun playout. | |
90 */ | |
91 function setOnToneChange(ontonechange) { | |
92 gDtmfOnToneChange = ontonechange; | |
93 } | |
94 | |
95 /** | |
96 * Inserts DTMF tones on an active DTMF sender. | |
97 * @param {string} data The string that will be sent to the remote peer. | |
98 */ | |
99 function insertDtmf(tones, duration, interToneGap) { | |
100 if (gDtmfSender == null) | |
101 throw failTest('Trying to send DTMF, but there is no DTMF sender.'); | |
102 gDtmfSender.insertDTMF(tones, duration, interToneGap); | |
103 } | |
104 | |
105 // Public interface towards the other javascript files, such as | 12 // Public interface towards the other javascript files, such as |
106 // message_handling.js. The contract for these functions is described in | 13 // message_handling.js. The contract for these functions is described in |
107 // message_handling.js. | 14 // message_handling.js. |
108 | 15 |
109 function handleMessage(peerConnection, message) { | 16 function receiveOffer(peerConnection, offer, constraints, callback) { |
110 var parsed_msg = JSON.parse(message); | 17 var sessionDescription = new RTCSessionDescription(offer); |
111 if (parsed_msg.type) { | 18 peerConnection.setRemoteDescription( |
112 var session_description = new RTCSessionDescription(parsed_msg); | 19 sessionDescription, |
113 peerConnection.setRemoteDescription( | 20 function() { success_('setRemoteDescription'); }, |
114 session_description, | 21 function() { failure_('setRemoteDescription'); }); |
115 function() { success_('setRemoteDescription'); }, | 22 |
116 function() { failure_('setRemoteDescription'); }); | 23 peerConnection.createAnswer( |
117 if (session_description.type == 'offer') { | 24 function(answer) { |
118 debug('createAnswer with constraints: ' + | 25 success_('createAnswer'); |
119 JSON.stringify(gCreateAnswerConstraints, null, ' ')); | 26 setLocalDescription(peerConnection, answer); |
120 peerConnection.createAnswer( | 27 callback(answer); |
121 setLocalAndSendMessage_, | 28 }, |
122 function() { failure_('createAnswer'); }, | 29 function() { failure_('createAnswer'); }, |
123 gCreateAnswerConstraints); | 30 constraints); |
124 } | 31 } |
125 return; | 32 |
126 } else if (parsed_msg.candidate) { | 33 function receiveAnswer(peerConnection, answer, callback) { |
127 var candidate = new RTCIceCandidate(parsed_msg); | 34 var sessionDescription = new RTCSessionDescription(answer); |
128 peerConnection.addIceCandidate(candidate); | 35 peerConnection.setRemoteDescription( |
129 return; | 36 sessionDescription, |
130 } | 37 function() { |
131 throw failTest('unknown message received'); | 38 success_('setRemoteDescription'); |
| 39 callback(); |
| 40 }, |
| 41 function() { failure_('setRemoteDescription'); }); |
132 } | 42 } |
133 | 43 |
134 function createPeerConnection(stun_server, useRtpDataChannels) { | 44 function createPeerConnection(stun_server, useRtpDataChannels) { |
135 servers = {iceServers: [{url: 'stun:' + stun_server}]}; | 45 servers = {iceServers: [{url: 'stun:' + stun_server}]}; |
136 try { | 46 try { |
137 var constraints = { optional: [{ RtpDataChannels: useRtpDataChannels }]}; | 47 var constraints = { optional: [{ RtpDataChannels: useRtpDataChannels }]}; |
138 peerConnection = new webkitRTCPeerConnection(servers, constraints); | 48 peerConnection = new RTCPeerConnection(servers, constraints); |
139 } catch (exception) { | 49 } catch (exception) { |
140 throw failTest('Failed to create peer connection: ' + exception); | 50 throw failTest('Failed to create peer connection: ' + exception); |
141 } | 51 } |
142 peerConnection.onaddstream = addStreamCallback_; | 52 peerConnection.onaddstream = addStreamCallback_; |
143 peerConnection.onremovestream = removeStreamCallback_; | 53 peerConnection.onremovestream = removeStreamCallback_; |
144 peerConnection.onicecandidate = iceCallback_; | 54 peerConnection.onicecandidate = iceCallback_; |
145 peerConnection.ondatachannel = onCreateDataChannelCallback_; | |
146 return peerConnection; | 55 return peerConnection; |
147 } | 56 } |
148 | 57 |
149 function setupCall(peerConnection) { | 58 function createOffer(peerConnection, constraints, callback) { |
150 debug('createOffer with constraints: ' + | |
151 JSON.stringify(gCreateOfferConstraints, null, ' ')); | |
152 peerConnection.createOffer( | 59 peerConnection.createOffer( |
153 setLocalAndSendMessage_, | 60 function(localDescription) { |
| 61 success_('createOffer'); |
| 62 setLocalDescription(peerConnection, localDescription); |
| 63 callback(localDescription); |
| 64 }, |
154 function() { failure_('createOffer'); }, | 65 function() { failure_('createOffer'); }, |
155 gCreateOfferConstraints); | 66 constraints); |
156 } | |
157 | |
158 function answerCall(peerConnection, message) { | |
159 handleMessage(peerConnection, message); | |
160 } | |
161 | |
162 function createDataChannel(peerConnection, label) { | |
163 if (gDataChannel != null && gDataChannel.readyState != 'closed') { | |
164 throw failTest('Creating DataChannel, but we already have one.'); | |
165 } | |
166 | |
167 gDataChannel = peerConnection.createDataChannel(label, { reliable: false }); | |
168 debug('DataChannel with label ' + gDataChannel.label + ' initiated locally.'); | |
169 hookupDataChannelEvents(); | |
170 } | |
171 | |
172 function closeDataChannel(peerConnection) { | |
173 if (gDataChannel == null) | |
174 throw failTest('Closing DataChannel, but none exists.'); | |
175 debug('DataChannel with label ' + gDataChannel.label + ' is beeing closed.'); | |
176 gDataChannel.close(); | |
177 } | |
178 | |
179 function createDtmfSender(peerConnection) { | |
180 if (gDtmfSender != null) | |
181 throw failTest('Creating DTMF sender, but we already have one.'); | |
182 | |
183 var localStream = getLocalStream(); | |
184 if (localStream == null) | |
185 throw failTest('Creating DTMF sender but local stream is null.'); | |
186 local_audio_track = localStream.getAudioTracks()[0]; | |
187 gDtmfSender = peerConnection.createDTMFSender(local_audio_track); | |
188 gDtmfSender.ontonechange = gDtmfOnToneChange; | |
189 } | 67 } |
190 | 68 |
191 function hasSeenCryptoInSdp() { | 69 function hasSeenCryptoInSdp() { |
192 returnToTest(gHasSeenCryptoInSdp); | 70 returnToTest(gHasSeenCryptoInSdp); |
193 } | 71 } |
194 | 72 |
195 // Internals. | 73 // Internals. |
196 /** @private */ | 74 /** @private */ |
197 function success_(method) { | 75 function success_(method) { |
198 debug(method + '(): success.'); | 76 debug(method + '(): success.'); |
199 } | 77 } |
200 | 78 |
201 /** @private */ | 79 /** @private */ |
202 function failure_(method, error) { | 80 function failure_(method, error) { |
203 throw failTest(method + '() failed: ' + error); | 81 throw failTest(method + '() failed: ' + error); |
204 } | 82 } |
205 | 83 |
206 /** @private */ | 84 /** @private */ |
207 function iceCallback_(event) { | 85 function iceCallback_(event) { |
208 if (event.candidate) | 86 if (event.candidate) |
209 sendToPeer(gRemotePeerId, JSON.stringify(event.candidate)); | 87 sendIceCandidate(event.candidate); |
210 } | 88 } |
211 | 89 |
212 /** @private */ | 90 /** @private */ |
213 function setLocalAndSendMessage_(session_description) { | 91 function setLocalDescription(peerConnection, sessionDescription) { |
214 session_description.sdp = gTransformOutgoingSdp(session_description.sdp); | 92 if (sessionDescription.sdp.search('a=crypto') != -1 || |
215 if (session_description.sdp.search('a=crypto') != -1 || | 93 sessionDescription.sdp.search('a=fingerprint') != -1) |
216 session_description.sdp.search('a=fingerprint') != -1) | |
217 gHasSeenCryptoInSdp = 'crypto-seen'; | 94 gHasSeenCryptoInSdp = 'crypto-seen'; |
| 95 |
218 peerConnection.setLocalDescription( | 96 peerConnection.setLocalDescription( |
219 session_description, | 97 sessionDescription, |
220 function() { success_('setLocalDescription'); }, | 98 function() { success_('setLocalDescription'); }, |
221 function() { failure_('setLocalDescription'); }); | 99 function() { failure_('setLocalDescription'); }); |
222 debug('Sending SDP message:\n' + session_description.sdp); | |
223 sendToPeer(gRemotePeerId, JSON.stringify(session_description)); | |
224 } | 100 } |
225 | 101 |
226 /** @private */ | 102 /** @private */ |
227 function addStreamCallback_(event) { | 103 function addStreamCallback_(event) { |
228 debug('Receiving remote stream...'); | 104 debug('Receiving remote stream...'); |
229 var videoTag = document.getElementById('remote-view'); | 105 var videoTag = document.getElementById('remote-view'); |
230 attachMediaStream(videoTag, event.stream); | 106 attachMediaStream(videoTag, event.stream); |
231 } | 107 } |
232 | 108 |
233 /** @private */ | 109 /** @private */ |
234 function removeStreamCallback_(event) { | 110 function removeStreamCallback_(event) { |
235 debug('Call ended.'); | 111 debug('Call ended.'); |
236 document.getElementById('remote-view').src = ''; | 112 document.getElementById('remote-view').src = ''; |
237 } | 113 } |
238 | |
239 /** @private */ | |
240 function onCreateDataChannelCallback_(event) { | |
241 if (gDataChannel != null && gDataChannel.readyState != 'closed') { | |
242 throw failTest('Received DataChannel, but we already have one.'); | |
243 } | |
244 | |
245 gDataChannel = event.channel; | |
246 debug('DataChannel with label ' + gDataChannel.label + | |
247 ' initiated by remote peer.'); | |
248 hookupDataChannelEvents(); | |
249 } | |
250 | |
251 /** @private */ | |
252 function hookupDataChannelEvents() { | |
253 gDataChannel.onmessage = gDataCallback; | |
254 gDataChannel.onopen = onDataChannelReadyStateChange_; | |
255 gDataChannel.onclose = onDataChannelReadyStateChange_; | |
256 // Trigger gDataStatusCallback so an application is notified | |
257 // about the created data channel. | |
258 onDataChannelReadyStateChange_(); | |
259 } | |
260 | |
261 /** @private */ | |
262 function onDataChannelReadyStateChange_() { | |
263 var readyState = gDataChannel.readyState; | |
264 debug('DataChannel state:' + readyState); | |
265 gDataStatusCallback(readyState); | |
266 } | |
OLD | NEW |