29 |
30 | /**
31 | * Interface with WebRTC-streamer API
32 | * @constructor
33 | * @param {string} videoElement - id of the video element tag
34 | * @param {string} srvurl - url of webrtc-streamer (default is current location)
35 | */
36 | function WebRtcStreamer (videoElement, srvurl) {
37 | this.videoElement = videoElement;
38 | this.srvurl = srvurl || location.protocol+"//"+window.location.hostname+":"+window.location.port;
39 | this.pc = null;
40 |
41 | this.pcOptions = { "optional": [{"DtlsSrtpKeyAgreement": true} ] };
42 |
43 | this.mediaConstraints = { offerToReceiveAudio: true, offerToReceiveVideo: true };
44 |
45 | this.iceServers = null;
46 | this.earlyCandidates = [];
47 | }
48 |
49 | /**
50 | * Connect a WebRTC Stream to videoElement
51 | * @param {string} videourl - id of WebRTC video stream
52 | * @param {string} audiourl - id of WebRTC audio stream
53 | * @param {string} options - options of WebRTC call
54 | * @param {string} stream - local stream to send
55 | */
56 | WebRtcStreamer.prototype.connect = function(videourl, audiourl, options, localstream) {
57 | this.disconnect();
58 |
59 | // getIceServers is not already received
60 | if (!this.iceServers) {
61 | console.log("Get IceServers");
62 |
63 | var bind = this;
64 | request("GET" , this.srvurl + "/api/getIceServers")
65 | .done( function (response) {
66 | if (response.statusCode === 200) {
67 | bind.onReceiveGetIceServers.call(bind,JSON.parse(response.body), videourl, audiourl, options, localstream);
68 | }
69 | else {
70 | bind.onError(response.statusCode);
71 | }
72 | }
73 | );
74 | } else {
75 | this.onReceiveGetIceServers(this.iceServers, videourl, audiourl, options, localstream);
76 | }
77 | }
78 |
79 | /**
80 | * Disconnect a WebRTC Stream and clear videoElement source
81 | */
82 | WebRtcStreamer.prototype.disconnect = function() {
83 | var videoElement = document.getElementById(this.videoElement);
84 | if (videoElement) {
85 | videoElement.src = "";
86 | }
87 | if (this.pc) {
88 | request("GET" , this.srvurl + "/api/hangup?peerid="+this.pc.peerid);
89 |
90 | try {
91 | this.pc.close();
92 | }
93 | catch (e) {
94 | console.log ("Failure close peer connection:" + e);
95 | }
96 | this.pc = null;
97 | }
98 | }
99 |
100 | /*
101 | * GetIceServers callback
102 | */
103 | WebRtcStreamer.prototype.onReceiveGetIceServers = function(iceServers, videourl, audiourl, options, stream) {
104 | this.iceServers = iceServers;
105 | this.pcConfig = iceServers || {"iceServers": [] };
106 | try {
107 | this.pc = this.createPeerConnection();
108 |
109 | var peerid = Math.random();
110 | this.pc.peerid = peerid;
111 |
112 | var callurl = this.srvurl + "/api/call?peerid="+ peerid+"&url="+encodeURIComponent(videourl);
113 | if (audiourl) {
114 | callurl += "&audiourl="+encodeURIComponent(audiourl);
115 | }
116 | if (options) {
117 | callurl += "&options="+encodeURIComponent(options);
118 | }
119 |
120 | if (stream) {
121 | this.pc.addStream(stream);
122 | }
123 |
124 | // clear early candidates
125 | this.earlyCandidates.length = 0;
126 |
127 | // create Offer
128 | var bind = this;
129 | this.pc.createOffer(this.mediaConstraints).then(function(sessionDescription) {
130 | console.log("Create offer:" + JSON.stringify(sessionDescription));
131 |
132 | bind.pc.setLocalDescription(sessionDescription
133 | , function() {
134 | request("POST" , callurl, { body: JSON.stringify(sessionDescription) })
135 | .done( function (response) {
136 | if (response.statusCode === 200) {
137 | bind.onReceiveCall.call(bind,JSON.parse(response.body));
138 | }
139 | else {
140 | bind.onError(response.statusCode);
141 | }
142 | }
143 | );
144 | }
145 | , function() {} );
146 |
147 | }, function(error) {
148 | alert("Create offer error:" + JSON.stringify(error));
149 | });
150 |
151 | } catch (e) {
152 | this.disconnect();
153 | alert("connect error: " + e);
154 | }
155 | }
156 |
157 | /*
158 | * create RTCPeerConnection
159 | */
160 | WebRtcStreamer.prototype.createPeerConnection = function() {
161 | console.log("createPeerConnection config: " + JSON.stringify(this.pcConfig) + " option:"+ JSON.stringify(this.pcOptions));
162 | var pc = new RTCPeerConnection(this.pcConfig, this.pcOptions);
163 | var streamer = this;
164 | pc.onicecandidate = function(evt) { streamer.onIceCandidate.call(streamer, evt); };
165 | pc.onaddstream = function(evt) { streamer.onAddStream.call(streamer,evt); };
166 | pc.oniceconnectionstatechange = function(evt) {
167 | console.log("oniceconnectionstatechange state: " + pc.iceConnectionState);
168 | var videoElement = document.getElementById(streamer.videoElement);
169 | if (videoElement) {
170 | if (pc.iceConnectionState === "connected") {
171 | videoElement.style.opacity = "1.0";
172 | }
173 | else if (pc.iceConnectionState === "disconnected") {
174 | videoElement.style.opacity = "0.25";
175 | }
176 | else if ( (pc.iceConnectionState === "failed") || (pc.iceConnectionState === "closed") ) {
177 | videoElement.style.opacity = "0.5";
178 | }
179 | }
180 | }
181 | pc.ondatachannel = function(evt) {
182 | console.log("remote datachannel created:"+JSON.stringify(evt));
183 |
184 | evt.channel.onopen = function () {
185 | console.log("remote datachannel open");
186 | this.send("remote channel openned");
187 | }
188 | evt.channel.onmessage = function (event) {
189 | console.log("remote datachannel recv:"+JSON.stringify(event.data));
190 | }
191 | }
192 |
193 | try {
194 | var dataChannel = pc.createDataChannel("ClientDataChannel");
195 | dataChannel.onopen = function() {
196 | console.log("local datachannel open");
197 | this.send("local channel openned");
198 | }
199 | dataChannel.onmessage = function(evt) {
200 | console.log("local datachannel recv:"+JSON.stringify(evt.data));
201 | }
202 | } catch (e) {
203 | console.log("Cannor create datachannel error: " + e);
204 | }
205 |
206 | console.log("Created RTCPeerConnnection with config: " + JSON.stringify(this.pcConfig) + "option:"+ JSON.stringify(this.pcOptions) );
207 | return pc;
208 | }
209 |
210 |
211 | /*
212 | * RTCPeerConnection IceCandidate callback
213 | */
214 | WebRtcStreamer.prototype.onIceCandidate = function (event) {
215 | if (event.candidate) {
216 | if (this.pc.currentRemoteDescription) {
217 | var bind = this;
218 | request("POST" , this.srvurl + "/api/addIceCandidate?peerid="+this.pc.peerid, { body: JSON.stringify(event.candidate) })
219 | .done( function (response) {
220 | if (response.statusCode === 200) {
221 | console.log("addIceCandidate ok:" + response.body);
222 | }
223 | else {
224 | bind.onError(response.statusCode);
225 | }
226 | }
227 | );
228 | } else {
229 | this.earlyCandidates.push(event.candidate);
230 | }
231 | }
232 | else {
233 | console.log("End of candidates.");
234 | }
235 | }
236 |
237 | /*
238 | * RTCPeerConnection AddTrack callback
239 | */
240 | WebRtcStreamer.prototype.onAddStream = function(event) {
241 | console.log("Remote track added:" + JSON.stringify(event));
242 |
243 | var videoElement = document.getElementById(this.videoElement);
244 | videoElement.srcObject = event.stream;
245 | videoElement.setAttribute("playsinline", true);
246 | videoElement.play();
247 | }
248 |
249 | /*
250 | * AJAX /call callback
251 | */
252 | WebRtcStreamer.prototype.onReceiveCall = function(dataJson) {
253 | var bind = this;
254 | console.log("offer: " + JSON.stringify(dataJson));
255 | this.pc.setRemoteDescription(new RTCSessionDescription(dataJson)
256 | , function() {
257 | console.log ("setRemoteDescription ok");
258 | while (bind.earlyCandidates.length) {
259 | var candidate = bind.earlyCandidates.shift();
260 |
261 | request("POST" , bind.srvurl + "/api/addIceCandidate?peerid=" + bind.pc.peerid, { body: JSON.stringify(candidate) })
262 | .done( function (response) {
263 | if (response.statusCode === 200) {
264 | console.log("addIceCandidate ok:" + response.body);
265 | }
266 | else {
267 | bind.onError(response.statusCode);
268 | }
269 | }
270 | );
271 | }
272 |
273 | request("GET" , bind.srvurl + "/api/getIceCandidate?peerid=" + bind.pc.peerid)
274 | .done( function (response) {
275 | if (response.statusCode === 200) {
276 | bind.onReceiveCandidate.call(bind,JSON.parse(response.body));
277 | }
278 | else {
279 | bind.onError(response.statusCode);
280 | }
281 | }
282 | );
283 |
284 | }
285 | , function(error) { console.log ("setRemoteDescription error:" + JSON.stringify(error)); });
286 | }
287 |
288 | /*
289 | * AJAX /getIceCandidate callback
290 | */
291 | WebRtcStreamer.prototype.onReceiveCandidate = function(dataJson) {
292 | console.log("candidate: " + JSON.stringify(dataJson));
293 | if (dataJson) {
294 | for (var i=0; i<dataJson.length; i++) {
295 | var candidate = new RTCIceCandidate(dataJson[i]);
296 |
297 | console.log("Adding ICE candidate :" + JSON.stringify(candidate) );
298 | this.pc.addIceCandidate(candidate
299 | , function() { console.log ("addIceCandidate OK"); }
300 | , function(error) { console.log ("addIceCandidate error:" + JSON.stringify(error)); } );
301 | }
302 | this.pc.addIceCandidate();
303 | }
304 | }
305 |
306 |
307 | /*
308 | * AJAX callback for Error
309 | */
310 | WebRtcStreamer.prototype.onError = function(status) {
311 | console.log("onError:" + status);
312 | }
313 |
314 |