quickblox 2.14.0 → 2.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -12,14 +12,27 @@
12
12
  * - onSessionConnectionStateChangedListener(session, userID, connectionState)
13
13
  * - onSessionCloseListener(session)
14
14
  * - onCallStatsReport(session, userId, stats, error)
15
+ * - onReconnectListener(session, userId, state)
16
+ */
17
+
18
+ /**
19
+ * @typedef {Object} MediaParams
20
+ * @property {boolean | MediaTrackConstraints} [params.audio]
21
+ * @property {boolean | MediaTrackConstraints} [params.video]
22
+ * @property {string} [params.elemId] Id of HTMLVideoElement
23
+ * @property {Object} [params.options]
24
+ * @property {boolean} [params.options.muted]
25
+ * @property {boolean} [params.options.mirror]
15
26
  */
16
27
 
17
28
  var config = require('../../qbConfig');
18
- var RTCPeerConnection = require('./qbRTCPeerConnection');
29
+ var qbRTCPeerConnection = require('./qbRTCPeerConnection');
19
30
  var Utils = require('../../qbUtils');
20
31
  var Helpers = require('./qbWebRTCHelpers');
21
32
  var SignalingConstants = require('./qbWebRTCSignalingConstants');
22
33
 
34
+ var ICE_TIMEOUT = 5000; // 5 seconds
35
+
23
36
  /**
24
37
  * State of a session
25
38
  */
@@ -31,12 +44,24 @@ WebRTCSession.State = {
31
44
  CLOSED: 5
32
45
  };
33
46
 
47
+ var ReconnectionState = {
48
+ RECONNECTING: 'reconnecting',
49
+ RECONNECTED: 'reconnected',
50
+ FAILED: 'failed'
51
+ };
52
+
34
53
 
35
54
  /**
36
- * Creates a session
37
- * @param {number} An ID if the call's initiator
38
- * @param {array} An array with opponents
39
- * @param {enum} Type of a call
55
+ * QuickBlox WebRTC session
56
+ * @param {Object} params
57
+ * @param {1|2} params.callType Type of a call
58
+ * 1 - VIDEO
59
+ * 2 - AUDIO
60
+ * @param {Array<number>} params.opIDs An array with opponents
61
+ * @param {number} params.currentUserID Current user ID
62
+ * @param {number} params.initiatorID Call initiator ID
63
+ * @param {string} [params.sessionID] Session identifier (optional)
64
+ * @param {number} [params.bandwidth] Bandwidth limit
40
65
  */
41
66
  function WebRTCSession(params) {
42
67
  this.ID = params.sessionID ? params.sessionID : generateUUID();
@@ -45,12 +70,14 @@ function WebRTCSession(params) {
45
70
  this.initiatorID = parseInt(params.initiatorID);
46
71
  this.opponentsIDs = params.opIDs;
47
72
  this.callType = parseInt(params.callType);
48
-
73
+ /** @type {{[userId: number]: qbRTCPeerConnection}} */
49
74
  this.peerConnections = {};
50
-
51
- this.localStream = null;
52
-
75
+ /** @type {MediaParams} */
53
76
  this.mediaParams = null;
77
+ /** @type {{[userID: number]: number | undefined}} */
78
+ this.iceConnectTimers = {};
79
+ /** @type {{[userID: number]: number | undefined}} */
80
+ this.reconnectTimers = {};
54
81
 
55
82
  this.signalingProvider = params.signalingProvider;
56
83
 
@@ -67,117 +94,110 @@ function WebRTCSession(params) {
67
94
 
68
95
  this.startCallTime = 0;
69
96
  this.acceptCallTime = 0;
97
+ /** @type {MediaStream | undefined} */
98
+ this.localStream = undefined;
70
99
  }
71
100
 
72
101
  /**
73
102
  * Get the user media stream
74
- * @param {map} A map media stream constrains
75
- * @param {function} A callback to get a result of the function
103
+ * @param {MediaParams} params media stream constraints and additional options
104
+ * @param {Function} callback callback to get a result of the function
76
105
  */
77
- WebRTCSession.prototype.getUserMedia = function(params, callback) {
78
- if(!navigator.mediaDevices.getUserMedia) {
106
+ WebRTCSession.prototype.getUserMedia = function (params, callback) {
107
+ if (!navigator.mediaDevices.getUserMedia) {
79
108
  throw new Error('getUserMedia() is not supported in your browser');
80
109
  }
81
110
 
82
111
  var self = this;
83
-
84
- /**
85
- * Additional parameters for Media Constraints
86
- * http://tools.ietf.org/html/draft-alvestrand-constraints-resolution-00
87
- *
88
- * googEchoCancellation: true
89
- * googAutoGainControl: true
90
- * googNoiseSuppression: true
91
- * googHighpassFilter: true
92
- * minWidth: 640
93
- * minHeight: 480
94
- * maxWidth: 1280
95
- * maxHeight: 720
96
- * minFrameRate: 60
97
- * maxAspectRatio: 1.333
98
- */
99
-
100
- navigator.mediaDevices.getUserMedia({
112
+ var mediaConstraints = {
101
113
  audio: params.audio || false,
102
114
  video: params.video || false
103
- }).then(function(stream) {
115
+ };
116
+
117
+ function successCallback(stream) {
104
118
  self.localStream = stream;
105
- self.mediaParams = params;
119
+ self.mediaParams = Object.assign({}, params);
106
120
 
107
121
  if (params.elemId) {
108
122
  self.attachMediaStream(params.elemId, stream, params.options);
109
123
  }
110
124
 
111
- callback(null, stream);
112
- }).catch(function(err) {
113
- callback(err, null);
114
- });
125
+ if (callback && typeof callback === 'function') {
126
+ callback(undefined, stream);
127
+ }
128
+ }
129
+
130
+ navigator
131
+ .mediaDevices
132
+ .getUserMedia(mediaConstraints)
133
+ .then(successCallback)
134
+ .catch(callback);
115
135
  };
116
136
 
117
137
  /**
118
138
  * Get the state of connection
119
139
  * @param {number} The User Id
120
140
  */
121
- WebRTCSession.prototype.connectionStateForUser = function(userID) {
141
+ WebRTCSession.prototype.connectionStateForUser = function (userID) {
122
142
  var peerConnection = this.peerConnections[userID];
123
-
124
- if (peerConnection) {
125
- return peerConnection.state;
126
- }
127
-
128
- return null;
143
+ return peerConnection ? peerConnection.state : undefined;
129
144
  };
130
145
 
131
146
  /**
132
147
  * Attach media stream to audio/video element
133
- * @param {string} The Id of an ellement to attach a stream
134
- * @param {object} The steram to attach
135
- * @param {object} The additional options
148
+ * @param {string} elementId The Id of an ellement to attach a stream
149
+ * @param {MediaStream} stream The stream to attach
150
+ * @param {Object} [options] The additional options
151
+ * @param {boolean} [options.muted] Whether video element should be muted
152
+ * @param {boolean} [options.mirror] Whether video should be "mirrored"
136
153
  */
137
- WebRTCSession.prototype.attachMediaStream = function(id, stream, options) {
138
- var elem = document.getElementById(id);
154
+ WebRTCSession.prototype.attachMediaStream = function (elementId, stream, options) {
155
+ var elem = document.getElementById(elementId);
139
156
 
140
157
  if (elem) {
141
- if (typeof elem.srcObject === 'object') {
142
- elem.srcObject = stream;
143
- } else {
144
- elem.src = window.URL.createObjectURL(stream);
145
- }
158
+ if (elem instanceof HTMLMediaElement) {
159
+ if ('srcObject' in elem) {
160
+ elem.srcObject = stream;
161
+ } else {
162
+ elem.src = window.URL.createObjectURL(stream);
163
+ }
146
164
 
147
- if (options && options.muted) {
148
- elem.muted = true;
149
- }
165
+ if (options && options.muted) {
166
+ elem.muted = true;
167
+ }
150
168
 
151
- if (options && options.mirror) {
152
- elem.style.webkitTransform = 'scaleX(-1)';
153
- elem.style.transform = 'scaleX(-1)';
154
- }
169
+ if (options && options.mirror) {
170
+ elem.style.transform = 'scaleX(-1)';
171
+ }
155
172
 
156
- elem.onloadedmetadata = function(e) {
157
- elem.play();
158
- };
173
+ if (!elem.autoplay) {
174
+ elem.onloadedmetadata = function () {
175
+ elem.play();
176
+ };
177
+ }
178
+ } else {
179
+ throw new Error('Cannot attach media stream to element with id "' + elementId + '" because it is not of type HTMLMediaElement');
180
+ }
159
181
  } else {
160
- throw new Error('Unable to attach media stream, element ' + id + ' is undefined');
182
+ throw new Error('Unable to attach media stream, cannot find element by Id "' + elementId + '"');
161
183
  }
162
184
  };
163
185
 
164
186
  /**
165
187
  * Detach media stream from audio/video element
166
- * @param {string} The Id of an element to detach a stream
188
+ * @param {string} elementId The Id of an element to detach a stream
167
189
  */
168
- WebRTCSession.prototype.detachMediaStream = function(id) {
169
- var elem = document.getElementById(id);
190
+ WebRTCSession.prototype.detachMediaStream = function (elementId) {
191
+ var elem = document.getElementById(elementId);
170
192
 
171
- if (elem) {
193
+ if (elem && elem instanceof HTMLMediaElement) {
172
194
  elem.pause();
173
195
 
174
196
  if (elem.srcObject && typeof elem.srcObject === 'object') {
175
- elem.srcObject.getTracks().forEach(
176
- function(track){
177
- track.stop();
178
- track.enabled = false;
179
- }
180
- );
197
+ elem.srcObject.getTracks().forEach(function (track) {
198
+ track.stop();
199
+ track.enabled = false;
200
+ });
181
201
  elem.srcObject = null;
182
202
  } else {
183
203
  elem.src = '';
@@ -190,151 +210,87 @@ WebRTCSession.prototype.detachMediaStream = function(id) {
190
210
 
191
211
  /**
192
212
  * Switch media tracks in audio/video HTML's element and replace its in peers.
193
- * @param {object} deviceIds - the object with deviceIds of plugged devices
213
+ * @param {Object} deviceIds - the object with deviceIds of plugged devices
194
214
  * @param {string} [deviceIds.audio] - the deviceId, it can be gotten from QB.webrtc.getMediaDevices('audioinput')
195
215
  * @param {string} [deviceIds.video] - the deviceId, it can be gotten from QB.webrtc.getMediaDevices('videoinput')
196
- * @param {switchMediaTracksCallback} callback - the callback to get a result of the function
197
- *
198
- * @example
199
- * var switchMediaTracksBtn = document.getElementById('confirmSwitchMediaTracks');
200
- *
201
- * var webRTCSession = QB.webrtc.createNewSession(params);
202
- *
203
- * QB.webrtc.getMediaDevices('videoinput').then(function(devices) {
204
- * var selectVideoInput = document.createElement('select'),
205
- * selectVideoInput.id = 'videoInput',
206
- * someDocumentElement.appendChild(selectVideoInput);
207
- *
208
- * if (devices.length > 1) {
209
- * for (var i = 0; i !== devices.length; ++i) {
210
- * var device = devices[i],
211
- * option = document.createElement('option');
212
- *
213
- * if (device.kind === 'videoinput') {
214
- * option.value = device.deviceId;
215
- * option.text = device.label;
216
- * selectVideoInput.appendChild(option);
217
- * }
218
- * }
219
- * }
220
- * }).catch(function(error) {
221
- * console.error(error);
222
- * });
223
- *
224
- * QB.webrtc.getMediaDevices('audioinput').then(function(devices) {
225
- * var selectAudioInput = document.createElement('select'),
226
- * selectAudioInput.id = 'audioInput',
227
- * someDocumentElement.appendChild(selectAudioInput);
228
- *
229
- * if (devices.length > 1) {
230
- * for (var i = 0; i !== devices.length; ++i) {
231
- * var device = devices[i],
232
- * option = document.createElement('option');
233
- *
234
- * if (device.kind === 'audioinput') {
235
- * option.value = device.deviceId;
236
- * option.text = device.label;
237
- * selectAudioInput.appendChild(option);
238
- * }
239
- * }
240
- * }
241
- * }).catch(function(error) {
242
- * console.error(error);
243
- * });
244
- *
245
- * switchMediaTracksBtn.onclick = function(event) {
246
- * var audioDeviceId = document.getElementById('audioInput').value || undefined,
247
- * videoDeviceId = document.getElementById('videoInput').value || undefined,
248
- * deviceIds = {
249
- * audio: audioDeviceId,
250
- * video: videoDeviceId,
251
- * };
252
- *
253
- * var callback = function(error, stream) {
254
- * if (err) {
255
- * console.error(error);
256
- * } else {
257
- * console.log(stream);
258
- * }
259
- * };
260
- *
261
- * // Switch media tracks in audio/video HTML's element (the local stream)
262
- * // replace media tracks in peers (will change media tracks for each user in WebRTC session)
263
- * webRTCSession.switchMediaTracks(deviceIds, callback);
264
- * }
216
+ * @param {Function} callback - the callback to get a result of the function
265
217
  */
266
- WebRTCSession.prototype.switchMediaTracks = function(deviceIds, callback) {
267
- /**
268
- * Callback for webRTCSession.switchMediaTracks(deviceIds, callback)
269
- * @callback switchMediaTracksCallback
270
- * @param {object} error - The error object
271
- * @param {object} stream - The stream from new media device
272
- */
273
-
274
- if(!navigator.mediaDevices.getUserMedia) {
218
+ WebRTCSession.prototype.switchMediaTracks = function (deviceIds, callback) {
219
+ if (!navigator.mediaDevices.getUserMedia) {
275
220
  throw new Error('getUserMedia() is not supported in your browser');
276
221
  }
277
222
 
278
- var self = this,
279
- localStream = this.localStream;
223
+ var self = this;
280
224
 
281
225
  if (deviceIds && deviceIds.audio) {
282
- self.mediaParams.audio.deviceId = deviceIds.audio;
226
+ this.mediaParams.audio.deviceId = deviceIds.audio;
283
227
  }
284
228
 
285
229
  if (deviceIds && deviceIds.video) {
286
- self.mediaParams.video.deviceId = deviceIds.video;
230
+ this.mediaParams.video.deviceId = deviceIds.video;
287
231
  }
288
232
 
289
- localStream.getTracks().forEach(function(track) {
233
+ this.localStream.getTracks().forEach(function (track) {
290
234
  track.stop();
291
235
  });
292
236
 
293
237
  navigator.mediaDevices.getUserMedia({
294
238
  audio: self.mediaParams.audio || false,
295
239
  video: self.mediaParams.video || false
296
- }).then(function(stream) {
240
+ }).then(function (stream) {
297
241
  self._replaceTracks(stream);
298
242
  callback(null, stream);
299
- }).catch(function(error) {
243
+ }).catch(function (error) {
300
244
  callback(error, null);
301
245
  });
302
246
  };
303
247
 
304
- WebRTCSession.prototype._replaceTracks = function(stream) {
305
- var peers = this.peerConnections,
306
- localStream = this.localStream,
307
- elemId = this.mediaParams.elemId,
308
- ops = this.mediaParams.options,
309
- newStreamTracks = stream.getTracks();
248
+ WebRTCSession.prototype._replaceTracks = function (stream) {
249
+ var localStream = this.localStream;
250
+ var elemId = this.mediaParams.elemId;
251
+ var ops = this.mediaParams.options;
252
+ var currentStreamTracks = localStream.getTracks();
253
+ var newStreamTracks = stream.getTracks();
310
254
 
311
255
  this.detachMediaStream(elemId);
312
256
 
313
- newStreamTracks.forEach(function(track) {
314
- localStream.addTrack(track);
257
+ newStreamTracks.forEach(function (newTrack) {
258
+ const currentTrack = currentStreamTracks.find(function (track) {
259
+ return track.kind === newTrack.kind;
260
+ });
261
+ if (currentTrack) {
262
+ currentTrack.stop();
263
+ localStream.removeTrack(currentTrack);
264
+ localStream.addTrack(newTrack);
265
+ }
315
266
  });
316
267
 
317
- this.attachMediaStream(elemId, stream, ops);
318
-
319
- for (var userId in peers) {
320
- _replaceTracksForPeer(peers[userId]);
268
+ if (elemId) {
269
+ this.attachMediaStream(elemId, stream, ops);
321
270
  }
322
271
 
272
+ /** @param {RTCPeerConnection} peer */
323
273
  function _replaceTracksForPeer(peer) {
324
- peer.getSenders().map(function(sender) {
325
- sender.replaceTrack(newStreamTracks.find(function(track) {
274
+ return Promise.all(peer.getSenders().map(function (sender) {
275
+ return sender.replaceTrack(newStreamTracks.find(function (track) {
326
276
  return track.kind === sender.track.kind;
327
277
  }));
328
- });
278
+ }));
329
279
  }
280
+
281
+ return Promise.all(Object
282
+ .values(this.peerConnections)
283
+ .map(function (peerConnection) { return peerConnection._pc; })
284
+ .map(_replaceTracksForPeer)
285
+ );
330
286
  };
331
287
 
332
288
  /**
333
- * [Initiate a call]
334
- * @param {object} extension [custom parametrs]
335
- * @param {Function} callback
289
+ * Initiate a call
290
+ * @param {Object} [extension] [custom parametrs]
291
+ * @param {Function} [callback]
336
292
  */
337
- WebRTCSession.prototype.call = function(extension, callback) {
293
+ WebRTCSession.prototype.call = function (extension, callback) {
338
294
  var self = this,
339
295
  ext = _prepareExtension(extension);
340
296
 
@@ -342,9 +298,15 @@ WebRTCSession.prototype.call = function(extension, callback) {
342
298
 
343
299
  self.state = WebRTCSession.State.ACTIVE;
344
300
 
345
- // create a peer connection for each opponent
346
- self.opponentsIDs.forEach(function(userID, i, arr) {
347
- self._callInternal(userID, ext, true);
301
+ // First this check if we connected to the signalling channel
302
+ // to make sure that opponents will receive `call` signal
303
+ self._reconnectToChat(function () {
304
+ if (self.state === WebRTCSession.State.ACTIVE) {
305
+ // create a peer connection for each opponent
306
+ self.opponentsIDs.forEach(function (userID) {
307
+ self._callInternal(userID, ext);
308
+ });
309
+ }
348
310
  });
349
311
 
350
312
  if (typeof callback === 'function') {
@@ -352,49 +314,38 @@ WebRTCSession.prototype.call = function(extension, callback) {
352
314
  }
353
315
  };
354
316
 
355
- WebRTCSession.prototype._callInternal = function(userID, extension, withOnNotAnswerCallback) {
317
+ WebRTCSession.prototype._callInternal = function (userID, extension) {
356
318
  var self = this;
357
- var peer = self._createPeer(userID, 'offer');
358
-
359
- var safariVersion = Helpers.getVersionSafari();
360
-
361
- if (safariVersion && safariVersion >= 11) {
362
- self.localStream.getTracks().forEach(function(track) {
363
- peer.addTrack(track, self.localStream);
364
- });
365
- } else {
366
- peer.addLocalStream(self.localStream);
367
- }
368
-
319
+ var peer = this._createPeer(userID, this.currentUserID < userID);
369
320
  this.peerConnections[userID] = peer;
370
-
371
- peer.getAndSetLocalSessionDescription(this.callType, function(err) {
372
- if (err) {
373
- Helpers.trace("getAndSessionDescription error: " + err);
321
+ peer.addLocalStream(self.localStream);
322
+ peer.setLocalSessionDescription({ type: 'offer' }, function (error) {
323
+ if (error) {
324
+ Helpers.trace("setLocalSessionDescription error: " + error);
374
325
  } else {
375
- Helpers.trace("getAndSessionDescription success");
326
+ Helpers.trace("setLocalSessionDescription success");
376
327
  /** let's send call requests to user */
377
- peer._startDialingTimer(extension, withOnNotAnswerCallback);
328
+ peer._startDialingTimer(extension);
378
329
  }
379
330
  });
380
331
  };
381
332
 
382
333
  /**
383
334
  * Accept a call
384
- * @param {array} A map with custom parameters
335
+ * @param {Object} extension A map with custom parameters
385
336
  */
386
- WebRTCSession.prototype.accept = function(extension) {
337
+ WebRTCSession.prototype.accept = function (extension) {
387
338
  var self = this,
388
339
  ext = _prepareExtension(extension);
389
340
 
390
341
  Helpers.trace('Accept, extension: ' + JSON.stringify(ext.userInfo));
391
342
 
392
- if(self.state === WebRTCSession.State.ACTIVE) {
343
+ if (self.state === WebRTCSession.State.ACTIVE) {
393
344
  Helpers.traceError("Can't accept, the session is already active, return.");
394
345
  return;
395
346
  }
396
347
 
397
- if(self.state === WebRTCSession.State.CLOSED) {
348
+ if (self.state === WebRTCSession.State.CLOSED) {
398
349
  Helpers.traceError("Can't accept, the session is already closed, return.");
399
350
  self.stop({});
400
351
  return;
@@ -412,7 +363,7 @@ WebRTCSession.prototype.accept = function(extension) {
412
363
  var oppIDs = self._uniqueOpponentsIDsWithoutInitiator();
413
364
 
414
365
  /** in a case of group video chat */
415
- if(oppIDs.length > 0){
366
+ if (oppIDs.length > 0) {
416
367
  var offerTime = (self.acceptCallTime - self.startCallTime) / 1000;
417
368
  self._startWaitingOfferOrAnswerTimer(offerTime);
418
369
 
@@ -420,8 +371,8 @@ WebRTCSession.prototype.accept = function(extension) {
420
371
  * here we have to decide to which users the user should call.
421
372
  * We have a rule: If a userID1 > userID2 then a userID1 should call to userID2.
422
373
  */
423
- oppIDs.forEach(function(opID, i, arr) {
424
- if(self.currentUserID > opID){
374
+ oppIDs.forEach(function (opID, i, arr) {
375
+ if (self.currentUserID > opID) {
425
376
  /** call to the user */
426
377
  self._callInternal(opID, {}, true);
427
378
  }
@@ -429,58 +380,61 @@ WebRTCSession.prototype.accept = function(extension) {
429
380
  }
430
381
  };
431
382
 
432
- WebRTCSession.prototype._acceptInternal = function(userID, extension) {
383
+ WebRTCSession.prototype._acceptInternal = function (userID, extension) {
433
384
  var self = this;
434
385
 
435
386
  /** create a peer connection */
436
387
  var peerConnection = this.peerConnections[userID];
437
388
 
438
389
  if (peerConnection) {
439
- var safariVersion = Helpers.getVersionSafari();
440
-
441
- if (safariVersion && safariVersion >= 11) {
442
- self.localStream.getTracks().forEach(function(track) {
443
- peerConnection.addTrack(track, self.localStream);
444
- });
445
- } else {
446
- peerConnection.addLocalStream(self.localStream);
447
- }
448
-
449
- peerConnection.setRemoteSessionDescription('offer', peerConnection.getRemoteSDP(), function(error){
450
- if(error){
390
+ var remoteSDP = peerConnection.getRemoteSDP();
391
+ peerConnection.addLocalStream(self.localStream);
392
+ peerConnection.setRemoteSessionDescription(remoteSDP, function (error) {
393
+ if (error) {
451
394
  Helpers.traceError("'setRemoteSessionDescription' error: " + error);
452
- }else{
395
+ } else {
453
396
  Helpers.trace("'setRemoteSessionDescription' success");
454
-
455
- peerConnection.getAndSetLocalSessionDescription(self.callType, function(err) {
397
+ peerConnection.setLocalSessionDescription({ type: 'answer' }, function (err) {
456
398
  if (err) {
457
- Helpers.trace("getAndSetLocalSessionDescription error: " + err);
399
+ Helpers.trace("setLocalSessionDescription error: " + err);
458
400
  } else {
459
-
401
+ Helpers.trace("'setLocalSessionDescription' success");
460
402
  extension.sessionID = self.ID;
461
403
  extension.callType = self.callType;
462
404
  extension.callerID = self.initiatorID;
463
405
  extension.opponentsIDs = self.opponentsIDs;
464
- extension.sdp = peerConnection.localDescription.sdp;
465
-
466
- self.signalingProvider.sendMessage(userID, extension, SignalingConstants.SignalingType.ACCEPT);
406
+ if (peerConnection._pc.localDescription) {
407
+ extension.sdp = peerConnection
408
+ ._pc
409
+ .localDescription
410
+ .toJSON()
411
+ .sdp;
412
+ }
413
+
414
+ self.signalingProvider.sendMessage(
415
+ userID,
416
+ extension,
417
+ SignalingConstants.SignalingType.ACCEPT
418
+ );
467
419
  }
468
420
  });
469
421
  }
470
422
  });
471
- }else{
472
- Helpers.traceError("Can't accept the call, there is no information about peer connection by some reason.");
423
+ } else {
424
+ Helpers.traceError(
425
+ "Can't accept the call, peer connection for userID " +
426
+ userID + " was not found"
427
+ );
473
428
  }
474
429
  };
475
430
 
476
431
  /**
477
432
  * Reject a call
478
- * @param {array} A map with custom parameters
433
+ * @param {Object} A map with custom parameters
479
434
  */
480
- WebRTCSession.prototype.reject = function(extension) {
435
+ WebRTCSession.prototype.reject = function (extension) {
481
436
  var self = this,
482
437
  ext = _prepareExtension(extension);
483
- var peersLen = Object.keys(self.peerConnections).length;
484
438
 
485
439
  Helpers.trace('Reject, extension: ' + JSON.stringify(ext.userInfo));
486
440
 
@@ -493,30 +447,31 @@ WebRTCSession.prototype.reject = function(extension) {
493
447
  ext.callerID = self.initiatorID;
494
448
  ext.opponentsIDs = self.opponentsIDs;
495
449
 
496
- if(peersLen > 0){
497
- for (var key in self.peerConnections) {
498
- var peerConnection = self.peerConnections[key];
499
- self.signalingProvider.sendMessage(peerConnection.userID, ext, SignalingConstants.SignalingType.REJECT);
500
- }
501
- }
450
+ Object.keys(self.peerConnections).forEach(function (key) {
451
+ var peerConnection = self.peerConnections[key];
452
+ self.signalingProvider.sendMessage(
453
+ peerConnection.userID,
454
+ ext,
455
+ SignalingConstants.SignalingType.REJECT
456
+ );
457
+ });
502
458
 
503
459
  self._close();
504
460
  };
505
461
 
506
462
  /**
507
463
  * Stop a call
508
- * @param {array} A map with custom parameters
464
+ * @param {Object} A map with custom parameters
509
465
  */
510
- WebRTCSession.prototype.stop = function(extension) {
466
+ WebRTCSession.prototype.stop = function (extension) {
511
467
  var self = this,
512
- ext = _prepareExtension(extension),
513
- peersLen = Object.keys(self.peerConnections).length;
468
+ ext = _prepareExtension(extension);
514
469
 
515
470
  Helpers.trace('Stop, extension: ' + JSON.stringify(ext.userInfo));
516
471
 
517
472
  self.state = WebRTCSession.State.HUNGUP;
518
473
 
519
- if(self.answerTimer) {
474
+ if (self.answerTimer) {
520
475
  self._clearAnswerTimer();
521
476
  }
522
477
 
@@ -525,13 +480,14 @@ WebRTCSession.prototype.stop = function(extension) {
525
480
  ext.callerID = self.initiatorID;
526
481
  ext.opponentsIDs = self.opponentsIDs;
527
482
 
528
- if(peersLen > 0){
529
- for (var key in self.peerConnections) {
530
- var peerConnection = self.peerConnections[key];
531
-
532
- self.signalingProvider.sendMessage(peerConnection.userID, ext, SignalingConstants.SignalingType.STOP);
533
- }
534
- }
483
+ Object.keys(self.peerConnections).forEach(function (key) {
484
+ var peerConnection = self.peerConnections[key];
485
+ self.signalingProvider.sendMessage(
486
+ peerConnection.userID,
487
+ ext,
488
+ SignalingConstants.SignalingType.STOP
489
+ );
490
+ });
535
491
 
536
492
  self._close();
537
493
  };
@@ -540,11 +496,11 @@ WebRTCSession.prototype.stop = function(extension) {
540
496
  * [function close connection with user]
541
497
  * @param {Number} userId [id of user]
542
498
  */
543
- WebRTCSession.prototype.closeConnection = function(userId) {
499
+ WebRTCSession.prototype.closeConnection = function (userId) {
544
500
  var self = this,
545
501
  peer = this.peerConnections[userId];
546
502
 
547
- if(!peer) {
503
+ if (!peer) {
548
504
  Helpers.traceWarn('Not found connection with user (' + userId + ')');
549
505
  return false;
550
506
  }
@@ -561,26 +517,40 @@ WebRTCSession.prototype.closeConnection = function(userId) {
561
517
 
562
518
  /**
563
519
  * Update a call
564
- * @param {array} A map with custom parameters
520
+ * @param {Object} extension A map with custom parameters
521
+ * @param {number} [userID]
565
522
  */
566
- WebRTCSession.prototype.update = function(extension) {
523
+ WebRTCSession.prototype.update = function (extension, userID) {
567
524
  var self = this,
568
- ext = {};
525
+ ext = typeof extension === 'object' ? extension : {};
569
526
 
570
527
  Helpers.trace('Update, extension: ' + JSON.stringify(extension));
571
528
 
572
- if(extension === null){
529
+ if (extension === null) {
573
530
  Helpers.trace("extension is null, no parameters to update");
574
531
  return;
575
532
  }
576
533
 
577
- ext = _prepareExtension(extension);
578
534
  ext.sessionID = this.ID;
579
-
580
- for (var key in self.peerConnections) {
581
- var peerConnection = self.peerConnections[key];
582
-
583
- self.signalingProvider.sendMessage(peerConnection.userID, ext, SignalingConstants.SignalingType.PARAMETERS_CHANGED);
535
+ ext.callType = this.callType;
536
+ ext.callerID = this.initiatorID;
537
+ ext.opponentsIDs = this.opponentsIDs;
538
+
539
+ if (userID) {
540
+ self.signalingProvider.sendMessage(
541
+ userID,
542
+ ext,
543
+ SignalingConstants.SignalingType.PARAMETERS_CHANGED
544
+ );
545
+ } else {
546
+ for (var key in self.peerConnections) {
547
+ var peer = self.peerConnections[key];
548
+ self.signalingProvider.sendMessage(
549
+ peer.userID,
550
+ ext,
551
+ SignalingConstants.SignalingType.PARAMETERS_CHANGED
552
+ );
553
+ }
584
554
  }
585
555
  };
586
556
 
@@ -588,7 +558,7 @@ WebRTCSession.prototype.update = function(extension) {
588
558
  * Mutes the stream
589
559
  * @param {string} what to mute: 'audio' or 'video'
590
560
  */
591
- WebRTCSession.prototype.mute = function(type) {
561
+ WebRTCSession.prototype.mute = function (type) {
592
562
  this._muteStream(0, type);
593
563
  };
594
564
 
@@ -596,91 +566,105 @@ WebRTCSession.prototype.mute = function(type) {
596
566
  * Unmutes the stream
597
567
  * @param {string} what to unmute: 'audio' or 'video'
598
568
  */
599
- WebRTCSession.prototype.unmute = function(type) {
569
+ WebRTCSession.prototype.unmute = function (type) {
600
570
  this._muteStream(1, type);
601
571
  };
602
572
 
603
573
  /**
604
574
  * DELEGATES (rtc client)
605
575
  */
606
- WebRTCSession.prototype.processOnCall = function(callerID, extension) {
576
+ WebRTCSession.prototype.processOnCall = function (callerID, extension) {
607
577
  var self = this,
608
- oppIDs = self._uniqueOpponentsIDs();
609
-
610
- oppIDs.forEach(function(opID, i, arr) {
611
- var pConn = self.peerConnections[opID];
578
+ opponentsIds = self._uniqueOpponentsIDs();
612
579
 
613
- if(pConn){
614
- if(opID == callerID){
615
- pConn.updateRemoteSDP(extension.sdp);
580
+ opponentsIds.forEach(function (opponentID) {
581
+ var peer = self.peerConnections[opponentID];
616
582
 
617
- /** The group call logic starts here */
618
- if(callerID != self.initiatorID && self.state === WebRTCSession.State.ACTIVE){
619
- self._acceptInternal(callerID, {});
620
- }
621
- }
622
- } else {
583
+ if (!peer) {
623
584
  /** create peer connections for each opponent */
624
- var peerConnection;
625
- if(opID != callerID && self.currentUserID > opID){
626
- peerConnection = self._createPeer(opID, 'offer');
627
- }else{
628
- peerConnection = self._createPeer(opID, 'answer');
585
+ peer = self._createPeer(
586
+ opponentID,
587
+ self.currentUserID < opponentID
588
+ );
589
+ self.peerConnections[opponentID] = peer;
590
+ if (opponentID == callerID) {
591
+ self._startAnswerTimer();
629
592
  }
593
+ }
594
+ if (opponentID == callerID) {
595
+ peer.setRemoteSDP(new window.RTCSessionDescription({
596
+ sdp: extension.sdp,
597
+ type: 'offer'
598
+ }));
630
599
 
631
- self.peerConnections[opID] = peerConnection;
632
-
633
- if(opID == callerID){
634
- peerConnection.updateRemoteSDP(extension.sdp);
635
- self._startAnswerTimer();
600
+ /** The group call logic starts here */
601
+ if (callerID != self.initiatorID &&
602
+ self.state === WebRTCSession.State.ACTIVE) {
603
+ self._acceptInternal(callerID, {});
636
604
  }
637
605
  }
638
606
  });
639
607
  };
640
608
 
641
- WebRTCSession.prototype.processOnAccept = function(userID, extension) {
609
+ WebRTCSession.prototype.processOnAccept = function (userID, extension) {
610
+ var self = this;
642
611
  var peerConnection = this.peerConnections[userID];
643
612
 
644
- if(peerConnection){
613
+ if (peerConnection) {
645
614
  peerConnection._clearDialingTimer();
646
- if (peerConnection.signalingState !== 'stable') {
647
- peerConnection.setRemoteSessionDescription('answer', extension.sdp, function(error){
648
- if(error){
615
+ if (peerConnection._pc.signalingState !== 'stable') {
616
+ var remoteSessionDescription = new window.RTCSessionDescription({
617
+ sdp: extension.sdp,
618
+ type: 'answer'
619
+ });
620
+ peerConnection.setRemoteSDP(remoteSessionDescription);
621
+ peerConnection.setRemoteSessionDescription(remoteSessionDescription, function (error) {
622
+ if (error) {
649
623
  Helpers.traceError("'setRemoteSessionDescription' error: " + error);
650
- }else{
624
+ } else {
651
625
  Helpers.trace("'setRemoteSessionDescription' success");
626
+ if (self.state !== WebRTCSession.State.ACTIVE) {
627
+ self.state = WebRTCSession.State.ACTIVE;
628
+ }
652
629
  }
653
630
  });
654
631
  } else {
655
632
  Helpers.traceError("Ignore 'onAccept', PeerConnection is already in 'stable' state");
656
633
  }
657
- }else{
658
- Helpers.traceError("Ignore 'OnAccept', there is no information about peer connection by some reason.");
634
+ } else {
635
+ Helpers.traceError(
636
+ "Ignore 'OnAccept': peer connection for user with Id " +
637
+ userID + " was not found"
638
+ );
659
639
  }
660
640
  };
661
641
 
662
- WebRTCSession.prototype.processOnReject = function(userID, extension) {
642
+ WebRTCSession.prototype.processOnReject = function (userID, extension) {
663
643
  var peerConnection = this.peerConnections[userID];
664
644
 
665
645
  this._clearWaitingOfferOrAnswerTimer();
666
646
 
667
- if(peerConnection){
647
+ if (peerConnection) {
668
648
  peerConnection.release();
669
- }else{
649
+ } else {
670
650
  Helpers.traceError("Ignore 'OnReject', there is no information about peer connection by some reason.");
671
651
  }
672
652
 
673
653
  this._closeSessionIfAllConnectionsClosed();
674
654
  };
675
655
 
676
- WebRTCSession.prototype.processOnStop = function(userID, extension) {
656
+ WebRTCSession.prototype.processOnStop = function (userID, extension) {
677
657
  var self = this;
678
658
 
679
659
  this._clearAnswerTimer();
680
660
 
681
- var pc = self.peerConnections[userID];
682
- if (pc) {
683
- pc.release();
661
+ var peerConnection = self.peerConnections[userID];
662
+ if (peerConnection) {
663
+ peerConnection.release();
664
+ if (peerConnection._reconnecting) {
665
+ peerConnection._reconnecting = false;
666
+ }
667
+ this._stopReconnectTimer(userID);
684
668
  } else {
685
669
  Helpers.traceError("Ignore 'OnStop', there is no information about peer connection by some reason.");
686
670
  }
@@ -688,33 +672,125 @@ WebRTCSession.prototype.processOnStop = function(userID, extension) {
688
672
  this._closeSessionIfAllConnectionsClosed();
689
673
  };
690
674
 
691
- WebRTCSession.prototype.processOnIceCandidates = function(userID, extension) {
675
+ WebRTCSession.prototype.processOnIceCandidates = function (userID, extension) {
692
676
  var peerConnection = this.peerConnections[userID];
693
677
 
694
- if(peerConnection){
678
+ if (peerConnection) {
695
679
  peerConnection.addCandidates(extension.iceCandidates);
696
- }else{
680
+ } else {
697
681
  Helpers.traceError("Ignore 'OnIceCandidates', there is no information about peer connection by some reason.");
698
682
  }
699
683
  };
700
684
 
701
- WebRTCSession.prototype.processCall = function(peerConnection, ext) {
702
- var extension = ext || {};
685
+ WebRTCSession.prototype.processOnUpdate = function (userID, extension) {
686
+ var SRD = extension.sessionDescription;
687
+ var reason = extension.reason;
688
+ var sessionIsActive = this.state === WebRTCSession.State.ACTIVE;
689
+ if (sessionIsActive && reason && reason === 'reconnect') {
690
+ var peer = this.peerConnections[userID];
691
+ if (peer) {
692
+ if (SRD) {
693
+ if (SRD.type === 'offer') {
694
+ this._processReconnectOffer(userID, SRD);
695
+ } else {
696
+ this._processReconnectAnswer(userID, SRD);
697
+ }
698
+ }
699
+ } else {
700
+ Helpers.traceError(
701
+ "Ignore 'OnUpdate': peer connection for user with Id " +
702
+ userID + " was not found"
703
+ );
704
+ }
705
+ }
706
+ };
703
707
 
708
+ /**
709
+ * @param {number} userID
710
+ * @param {RTCSessionDescriptionInit} SRD
711
+ */
712
+ WebRTCSession.prototype._processReconnectOffer = function (userID, SRD) {
713
+ var self = this;
714
+ if (this.peerConnections[userID].polite) {
715
+ this._reconnect(this.peerConnections[userID]);
716
+ var peer = this.peerConnections[userID];
717
+ var offerId = SRD.offerId;
718
+ var remoteDescription = new window.RTCSessionDescription({
719
+ sdp: SRD.sdp,
720
+ type: SRD.type
721
+ });
722
+
723
+ peer.setRemoteSDP(remoteDescription);
724
+ peer.setRemoteSessionDescription(remoteDescription, function (e) {
725
+ if (e) {
726
+ Helpers.traceError('"setRemoteSessionDescription" error:' + e.message);
727
+ } else {
728
+ peer.setLocalSessionDescription({ type: 'answer' }, function () {
729
+ var description = peer._pc.localDescription.toJSON();
730
+ var ext = {
731
+ reason: 'reconnect',
732
+ sessionDescription: {
733
+ offerId: offerId,
734
+ sdp: description.sdp,
735
+ type: description.type
736
+ }
737
+ };
738
+ self.update(ext, userID);
739
+ });
740
+ }
741
+ });
742
+ } else {
743
+ this._reconnect(this.peerConnections[userID], true);
744
+ }
745
+ };
746
+
747
+ /**
748
+ * @param {number} userID
749
+ * @param {RTCSessionDescriptionInit & { offerId: string }} SRD
750
+ */
751
+ WebRTCSession.prototype._processReconnectAnswer = function (userID, SRD) {
752
+ var peer = this.peerConnections[userID];
753
+ var offerId = SRD.offerId;
754
+ if (peer && peer.offerId && offerId && peer.offerId === offerId) {
755
+ var remoteDescription = new window.RTCSessionDescription({
756
+ sdp: SRD.sdp,
757
+ type: SRD.type
758
+ });
759
+ peer.setRemoteSDP(remoteDescription);
760
+ peer.setRemoteSessionDescription(remoteDescription, function (e) {
761
+ if (e) {
762
+ Helpers.traceError('"setRemoteSessionDescription" error:' + e.message);
763
+ }
764
+ });
765
+ }
766
+ };
767
+
768
+ /**
769
+ * Send "call" signal to the opponent
770
+ * @param {qbRTCPeerConnection} peerConnection
771
+ * @param {Object} ext
772
+ */
773
+ WebRTCSession.prototype.processCall = function (peerConnection, ext) {
774
+ var extension = ext || {};
775
+ if (!peerConnection._pc.localDescription) return;
704
776
  extension.sessionID = this.ID;
705
777
  extension.callType = this.callType;
706
778
  extension.callerID = this.initiatorID;
707
779
  extension.opponentsIDs = this.opponentsIDs;
708
- extension.sdp = peerConnection.localDescription.sdp;
780
+ extension.sdp = peerConnection._pc.localDescription.sdp;
709
781
 
710
782
  //TODO: set bandwidth to the userInfo object
711
- extension.userInfo = ext.userInfo || {};
783
+ extension.userInfo = ext && ext.userInfo ? ext.userInfo : {};
712
784
  extension.userInfo.bandwidth = this.bandwidth;
713
785
 
714
- this.signalingProvider.sendMessage(peerConnection.userID, extension, SignalingConstants.SignalingType.CALL);
786
+ this.signalingProvider.sendMessage(
787
+ peerConnection.userID,
788
+ extension,
789
+ SignalingConstants.SignalingType.CALL
790
+ );
715
791
  };
716
792
 
717
- WebRTCSession.prototype.processIceCandidates = function(peerConnection, iceCandidates) {
793
+ WebRTCSession.prototype.processIceCandidates = function (peerConnection, iceCandidates) {
718
794
  var extension = {};
719
795
 
720
796
  extension.sessionID = this.ID;
@@ -725,7 +801,7 @@ WebRTCSession.prototype.processIceCandidates = function(peerConnection, iceCandi
725
801
  this.signalingProvider.sendCandidate(peerConnection.userID, iceCandidates, extension);
726
802
  };
727
803
 
728
- WebRTCSession.prototype.processOnNotAnswer = function(peerConnection) {
804
+ WebRTCSession.prototype.processOnNotAnswer = function (peerConnection) {
729
805
  Helpers.trace("Answer timeout callback for session " + this.ID + " for user " + peerConnection.userID);
730
806
 
731
807
  this._clearWaitingOfferOrAnswerTimer();
@@ -742,7 +818,7 @@ WebRTCSession.prototype.processOnNotAnswer = function(peerConnection) {
742
818
  /**
743
819
  * DELEGATES (peer connection)
744
820
  */
745
- WebRTCSession.prototype._onRemoteStreamListener = function(userID, stream) {
821
+ WebRTCSession.prototype._onRemoteStreamListener = function (userID, stream) {
746
822
  if (typeof this.onRemoteStreamListener === 'function') {
747
823
  Utils.safeCallbackCall(this.onRemoteStreamListener, this, userID, stream);
748
824
  }
@@ -756,60 +832,236 @@ WebRTCSession.prototype._onRemoteStreamListener = function(userID, stream) {
756
832
  * Fire onCallStatsReport callbacks with parameters(userId, stats, error).
757
833
  * If stats will be invalid callback return null and error
758
834
  */
759
- WebRTCSession.prototype._onCallStatsReport = function(userId, stats, error) {
835
+ WebRTCSession.prototype._onCallStatsReport = function (userId, stats, error) {
760
836
  if (typeof this.onCallStatsReport === 'function') {
761
837
  Utils.safeCallbackCall(this.onCallStatsReport, this, userId, stats, error);
762
838
  }
763
839
  };
764
840
 
765
- WebRTCSession.prototype._onSessionConnectionStateChangedListener = function(userID, connectionState) {
841
+ WebRTCSession.prototype._onSessionConnectionStateChangedListener = function (userID, connectionState) {
842
+ var StateClosed = Helpers.SessionConnectionState.CLOSED;
843
+ var peer = this.peerConnections[userID];
766
844
  if (typeof this.onSessionConnectionStateChangedListener === 'function') {
767
- Utils.safeCallbackCall(this.onSessionConnectionStateChangedListener, this, userID, connectionState);
768
- if(Helpers.SessionConnectionState.CLOSED === connectionState && this.peerConnections[userID]) {
769
- this.peerConnections[userID].onicecandidate = null;
770
- this.peerConnections[userID].onsignalingstatechange = null;
771
- this.peerConnections[userID].ontrack = null;
772
- this.peerConnections[userID].oniceconnectionstatechange = null;
773
- this.peerConnections[userID]._clearWaitingReconnectTimer();
774
- delete this.peerConnections[userID];
775
- }
845
+ Utils.safeCallbackCall(
846
+ this.onSessionConnectionStateChangedListener,
847
+ this,
848
+ userID,
849
+ connectionState
850
+ );
851
+ }
852
+ if (connectionState === StateClosed && peer) {
853
+ peer._pc.onicecandidate = null;
854
+ peer._pc.onsignalingstatechange = null;
855
+ peer._pc.ontrack = null;
856
+ peer._pc.oniceconnectionstatechange = null;
857
+ delete this.peerConnections[userID];
776
858
  }
777
859
  };
778
860
 
779
861
  /**
780
862
  * Private
863
+ * @param {number} userId
864
+ * @param {boolean} polite
865
+ * @returns {qbRTCPeerConnection}
781
866
  */
782
- WebRTCSession.prototype._createPeer = function(userID, peerConnectionType) {
783
- if (!RTCPeerConnection) throw new Error('_createPeer error: RTCPeerConnection() is not supported in your browser');
867
+ WebRTCSession.prototype._createPeer = function (userId, polite) {
868
+ if (!window.RTCPeerConnection) {
869
+ throw new Error('_createPeer error: RTCPeerConnection is not supported in your browser');
870
+ }
784
871
 
785
872
  this.startCallTime = new Date();
786
873
 
787
- /**
788
- * Additional parameters for RTCPeerConnection options
789
- * new RTCPeerConnection(pcConfig, options)
790
- *
791
- * DtlsSrtpKeyAgreement: true
792
- * RtpDataChannels: true
793
- */
794
-
795
874
  var pcConfig = {
796
875
  iceServers: config.webrtc.iceServers,
797
876
  };
798
877
 
799
- Helpers.trace("_createPeer, iceServers: " + JSON.stringify(pcConfig));
878
+ Helpers.trace("_createPeer configuration: " + JSON.stringify(pcConfig));
800
879
 
801
- var peer = new RTCPeerConnection(pcConfig);
802
- peer._init(this, userID, this.ID, peerConnectionType);
880
+ var peer = new qbRTCPeerConnection(pcConfig);
881
+ peer._init(this, userId, this.ID, polite);
803
882
 
804
883
  return peer;
805
884
  };
806
885
 
886
+ WebRTCSession.prototype._startReconnectTimer = function (userID) {
887
+ var self = this;
888
+ var delay = config.webrtc.disconnectTimeInterval * 1000;
889
+ var peer = this.peerConnections[userID];
890
+
891
+ peer._reconnecting = true;
892
+
893
+ var reconnectTimeoutCallback = function () {
894
+ Helpers.trace('disconnectTimeInterval reached for userID ' + userID);
895
+
896
+ self._stopReconnectTimer(userID);
897
+
898
+ self.peerConnections[userID].release();
899
+ self._onSessionConnectionStateChangedListener(
900
+ userID,
901
+ Helpers.SessionConnectionState.CLOSED
902
+ );
903
+
904
+ self._closeSessionIfAllConnectionsClosed();
905
+ };
906
+
907
+ if (typeof this.onReconnectListener === 'function') {
908
+ Utils.safeCallbackCall(
909
+ this.onReconnectListener,
910
+ this,
911
+ userID,
912
+ ReconnectionState.RECONNECTING
913
+ );
914
+ }
915
+
916
+ Helpers.trace(
917
+ '_startReconnectTimer for userID:' + userID + ', timeout: ' + delay
918
+ );
919
+
920
+ var iceConnectTimeoutCallback = function () {
921
+ Helpers.trace('iceConnectTimeout reached for user ' + userID);
922
+ if (self.iceConnectTimers[userID]) {
923
+ clearTimeout(self.iceConnectTimers[userID]);
924
+ self.iceConnectTimers[userID] = undefined;
925
+ if (!self.reconnectTimers[userID]) {
926
+ /** If connection won't be recovered - close session */
927
+ self.reconnectTimers[userID] = setTimeout(
928
+ reconnectTimeoutCallback,
929
+ delay - ICE_TIMEOUT
930
+ );
931
+ self._reconnectToChat(function () {
932
+ if (self.state === WebRTCSession.State.ACTIVE &&
933
+ self.reconnectTimers[userID]) {
934
+ // only if session is active
935
+ self._reconnect(peer, true);
936
+ }
937
+ });
938
+ }
939
+ }
940
+ };
941
+
942
+ if (!this.iceConnectTimers[userID]) {
943
+ /**
944
+ * Wait a bit before reconnecting. If network has not been changed -
945
+ * ICE candidates are still valid and connection may recover shortly
946
+ */
947
+ this.iceConnectTimers[userID] = setTimeout(
948
+ iceConnectTimeoutCallback,
949
+ ICE_TIMEOUT
950
+ );
951
+ }
952
+ };
953
+
954
+
955
+ WebRTCSession.prototype._stopReconnectTimer = function (userID) {
956
+ var peer = this.peerConnections[userID];
957
+ if (this.iceConnectTimers[userID]) {
958
+ clearTimeout(this.iceConnectTimers[userID]);
959
+ this.iceConnectTimers[userID] = undefined;
960
+ }
961
+ if (this.reconnectTimers[userID]) {
962
+ Helpers.trace('_stopReconnectTimer for userID: ' + userID);
963
+ clearTimeout(this.reconnectTimers[userID]);
964
+ this.reconnectTimers[userID] = undefined;
965
+ }
966
+ if (peer && peer._reconnecting) {
967
+ peer._reconnecting = false;
968
+ if (typeof this.onReconnectListener === 'function') {
969
+ var state = peer._pc.iceConnectionState;
970
+ Utils.safeCallbackCall(
971
+ this.onReconnectListener,
972
+ this,
973
+ userID,
974
+ state === 'connected' ?
975
+ ReconnectionState.RECONNECTED :
976
+ ReconnectionState.FAILED
977
+ );
978
+ }
979
+ }
980
+ };
981
+
982
+ /**
983
+ * Ping server until pong received, then call the `callback`
984
+ * @param {Function} callback
985
+ */
986
+ WebRTCSession.prototype._reconnectToChat = function (callback) {
987
+ var self = this;
988
+ var signalingProvider = this.signalingProvider;
989
+ var reconnectToChat = function () {
990
+ var _onReconnectListener = signalingProvider.chat.onReconnectListener;
991
+ signalingProvider.chat.onReconnectListener = function () {
992
+ if (typeof _onReconnectListener === 'function') {
993
+ _onReconnectListener();
994
+ }
995
+ signalingProvider.chat.onReconnectListener = _onReconnectListener;
996
+ callback();
997
+ };
998
+ signalingProvider.chat.reconnect();
999
+ };
1000
+ if (signalingProvider && signalingProvider.chat) {
1001
+ try {
1002
+ /**
1003
+ * Ping chat server to make sure that chat connection
1004
+ * has been established
1005
+ */
1006
+ signalingProvider.chat.ping(function (e) {
1007
+ if (self.state !== WebRTCSession.State.CLOSED) {
1008
+ if (e) {
1009
+ // If not - reconnect to chat
1010
+ reconnectToChat();
1011
+ } else {
1012
+ // If chat connected - call `callback`
1013
+ callback();
1014
+ }
1015
+ }
1016
+ });
1017
+ } catch (e) {
1018
+ if (self.state !== WebRTCSession.State.CLOSED) {
1019
+ /** Catch `ChatNotConnected` error and reconnect to chat */
1020
+ reconnectToChat();
1021
+ }
1022
+ }
1023
+ }
1024
+ };
1025
+
1026
+ /**
1027
+ * @param {qbRTCPeerConnection} peerConnection
1028
+ * @param {boolean} [negotiate]
1029
+ * @returns {void}
1030
+ */
1031
+ WebRTCSession.prototype._reconnect = function (peerConnection, negotiate) {
1032
+ if (!peerConnection || !peerConnection.userID) {
1033
+ return;
1034
+ }
1035
+ var userId = peerConnection.userID;
1036
+ var polite = peerConnection.polite;
1037
+ var _reconnecting = peerConnection._reconnecting;
1038
+
1039
+ peerConnection.release();
1040
+
1041
+ var pcConfig = {
1042
+ iceServers: config.webrtc.iceServers,
1043
+ };
1044
+
1045
+ Helpers.trace("_reconnect peer configuration: " + JSON.stringify(pcConfig));
1046
+
1047
+ var peer = new qbRTCPeerConnection(pcConfig);
1048
+ this.peerConnections[userId] = peer;
1049
+ peer._init(this, userId, this.ID, polite);
1050
+ peer._reconnecting = _reconnecting;
1051
+ peer.addLocalStream(this.localStream);
1052
+ if (negotiate) {
1053
+ peer.offerId = generateUUID();
1054
+ peer.negotiate();
1055
+ }
1056
+ };
1057
+
807
1058
  /** close peer connection and local stream */
808
- WebRTCSession.prototype._close = function() {
1059
+ WebRTCSession.prototype._close = function () {
809
1060
  Helpers.trace('_close');
810
1061
 
811
1062
  for (var key in this.peerConnections) {
812
1063
  var peer = this.peerConnections[key];
1064
+ this._stopReconnectTimer(peer.userID);
813
1065
 
814
1066
  try {
815
1067
  peer.release();
@@ -819,6 +1071,14 @@ WebRTCSession.prototype._close = function() {
819
1071
  }
820
1072
 
821
1073
  this._closeLocalMediaStream();
1074
+ if (typeof this._detectSilentAudioTaskCleanup === 'function') {
1075
+ this._detectSilentAudioTaskCleanup();
1076
+ this._detectSilentAudioTaskCleanup = undefined;
1077
+ }
1078
+ if (typeof this._detectSilentVideoTaskCleanup === 'function') {
1079
+ this._detectSilentVideoTaskCleanup();
1080
+ this._detectSilentVideoTaskCleanup = undefined;
1081
+ }
822
1082
 
823
1083
  this.state = WebRTCSession.State.CLOSED;
824
1084
 
@@ -827,31 +1087,12 @@ WebRTCSession.prototype._close = function() {
827
1087
  }
828
1088
  };
829
1089
 
830
- WebRTCSession.prototype._closeSessionIfAllConnectionsClosed = function() {
831
- var isAllConnectionsClosed = true;
832
-
833
- for (var key in this.peerConnections) {
834
- var peerCon = this.peerConnections[key],
835
- peerState;
836
-
837
- try {
838
- /*
839
- TODO:
840
- Uses RTCPeerConnection.signalingState instead RTCPeerConnection.iceConnectionState,
841
- because state 'closed' comes after few time from Safari, but signaling state comes instantly
842
- */
843
- peerState = peerCon.iceConnectionState === 'closed' ? 'closed' : peerCon.signalingState;
844
- } catch(err) {
845
- Helpers.traceError(err);
846
- // need to set peerState to 'closed' on error. FF will crashed without this part.
847
- peerState = 'closed';
848
- }
849
-
850
- if (peerState !== 'closed') {
851
- isAllConnectionsClosed = false;
852
- break;
853
- }
854
- }
1090
+ WebRTCSession.prototype._closeSessionIfAllConnectionsClosed = function () {
1091
+ var isAllConnectionsClosed = Object
1092
+ .values(this.peerConnections)
1093
+ .every(function (peer) {
1094
+ return peer.state === qbRTCPeerConnection.State.CLOSED;
1095
+ });
855
1096
 
856
1097
  Helpers.trace("All peer connections closed: " + isAllConnectionsClosed);
857
1098
 
@@ -866,26 +1107,17 @@ WebRTCSession.prototype._closeSessionIfAllConnectionsClosed = function() {
866
1107
  }
867
1108
  };
868
1109
 
869
- WebRTCSession.prototype._closeLocalMediaStream = function(){
870
- /**
871
- * https://developers.google.com/web/updates/2015/07/mediastream-deprecations?hl=en
872
- */
1110
+ WebRTCSession.prototype._closeLocalMediaStream = function () {
873
1111
  if (this.localStream) {
874
- this.localStream.getAudioTracks().forEach(function (audioTrack) {
875
- audioTrack.stop();
876
- audioTrack.enabled = false;
1112
+ this.localStream.getTracks().forEach(function (track) {
1113
+ track.stop();
1114
+ track.enabled = false;
877
1115
  });
878
-
879
- this.localStream.getVideoTracks().forEach(function (videoTrack) {
880
- videoTrack.stop();
881
- videoTrack.enabled = false;
882
- });
883
-
884
1116
  this.localStream = null;
885
1117
  }
886
1118
  };
887
1119
 
888
- WebRTCSession.prototype._muteStream = function(bool, type) {
1120
+ WebRTCSession.prototype._muteStream = function (bool, type) {
889
1121
  if (type === 'audio' && this.localStream.getAudioTracks().length > 0) {
890
1122
  this.localStream.getAudioTracks().forEach(function (track) {
891
1123
  track.enabled = !!bool;
@@ -901,19 +1133,19 @@ WebRTCSession.prototype._muteStream = function(bool, type) {
901
1133
  }
902
1134
  };
903
1135
 
904
- WebRTCSession.prototype._clearAnswerTimer = function(){
905
- if(this.answerTimer){
1136
+ WebRTCSession.prototype._clearAnswerTimer = function () {
1137
+ if (this.answerTimer) {
906
1138
  Helpers.trace("_clearAnswerTimer");
907
1139
  clearTimeout(this.answerTimer);
908
1140
  this.answerTimer = null;
909
1141
  }
910
1142
  };
911
1143
 
912
- WebRTCSession.prototype._startAnswerTimer = function(){
1144
+ WebRTCSession.prototype._startAnswerTimer = function () {
913
1145
  Helpers.trace("_startAnswerTimer");
914
1146
 
915
1147
  var self = this;
916
- var answerTimeoutCallback = function (){
1148
+ var answerTimeoutCallback = function () {
917
1149
  Helpers.trace("_answerTimeoutCallback");
918
1150
 
919
1151
  if (typeof self.onSessionCloseListener === 'function') {
@@ -923,28 +1155,28 @@ WebRTCSession.prototype._startAnswerTimer = function(){
923
1155
  self.answerTimer = null;
924
1156
  };
925
1157
 
926
- var answerTimeInterval = config.webrtc.answerTimeInterval*1000;
1158
+ var answerTimeInterval = config.webrtc.answerTimeInterval * 1000;
927
1159
  this.answerTimer = setTimeout(answerTimeoutCallback, answerTimeInterval);
928
1160
  };
929
1161
 
930
- WebRTCSession.prototype._clearWaitingOfferOrAnswerTimer = function() {
931
- if(this.waitingOfferOrAnswerTimer){
1162
+ WebRTCSession.prototype._clearWaitingOfferOrAnswerTimer = function () {
1163
+ if (this.waitingOfferOrAnswerTimer) {
932
1164
  Helpers.trace("_clearWaitingOfferOrAnswerTimer");
933
1165
  clearTimeout(this.waitingOfferOrAnswerTimer);
934
1166
  this.waitingOfferOrAnswerTimer = null;
935
1167
  }
936
1168
  };
937
1169
 
938
- WebRTCSession.prototype._startWaitingOfferOrAnswerTimer = function(time) {
1170
+ WebRTCSession.prototype._startWaitingOfferOrAnswerTimer = function (time) {
939
1171
  var self = this,
940
1172
  timeout = (config.webrtc.answerTimeInterval - time) < 0 ? 1 : config.webrtc.answerTimeInterval - time,
941
- waitingOfferOrAnswerTimeoutCallback = function() {
1173
+ waitingOfferOrAnswerTimeoutCallback = function () {
942
1174
  Helpers.trace("waitingOfferOrAnswerTimeoutCallback");
943
1175
 
944
- if(Object.keys(self.peerConnections).length > 0) {
945
- Object.keys(self.peerConnections).forEach(function(key) {
1176
+ if (Object.keys(self.peerConnections).length > 0) {
1177
+ Object.keys(self.peerConnections).forEach(function (key) {
946
1178
  var peerConnection = self.peerConnections[key];
947
- if (peerConnection.state === RTCPeerConnection.State.CONNECTING || peerConnection.state === RTCPeerConnection.State.NEW) {
1179
+ if (peerConnection.state === qbRTCPeerConnection.State.CONNECTING || peerConnection.state === qbRTCPeerConnection.State.NEW) {
948
1180
  self.processOnNotAnswer(peerConnection);
949
1181
  }
950
1182
  });
@@ -955,19 +1187,19 @@ WebRTCSession.prototype._startWaitingOfferOrAnswerTimer = function(time) {
955
1187
 
956
1188
  Helpers.trace("_startWaitingOfferOrAnswerTimer, timeout: " + timeout);
957
1189
 
958
- this.waitingOfferOrAnswerTimer = setTimeout(waitingOfferOrAnswerTimeoutCallback, timeout*1000);
1190
+ this.waitingOfferOrAnswerTimer = setTimeout(waitingOfferOrAnswerTimeoutCallback, timeout * 1000);
959
1191
  };
960
1192
 
961
- WebRTCSession.prototype._uniqueOpponentsIDs = function(){
1193
+ WebRTCSession.prototype._uniqueOpponentsIDs = function () {
962
1194
  var self = this;
963
1195
  var opponents = [];
964
1196
 
965
- if(this.initiatorID !== this.currentUserID){
1197
+ if (this.initiatorID !== this.currentUserID) {
966
1198
  opponents.push(this.initiatorID);
967
1199
  }
968
1200
 
969
- this.opponentsIDs.forEach(function(userID, i, arr) {
970
- if(userID != self.currentUserID){
1201
+ this.opponentsIDs.forEach(function (userID, i, arr) {
1202
+ if (userID != self.currentUserID) {
971
1203
  opponents.push(parseInt(userID));
972
1204
  }
973
1205
  });
@@ -975,12 +1207,12 @@ WebRTCSession.prototype._uniqueOpponentsIDs = function(){
975
1207
  return opponents;
976
1208
  };
977
1209
 
978
- WebRTCSession.prototype._uniqueOpponentsIDsWithoutInitiator = function(){
1210
+ WebRTCSession.prototype._uniqueOpponentsIDsWithoutInitiator = function () {
979
1211
  var self = this;
980
1212
  var opponents = [];
981
1213
 
982
- this.opponentsIDs.forEach(function(userID, i, arr) {
983
- if(userID != self.currentUserID){
1214
+ this.opponentsIDs.forEach(function (userID, i, arr) {
1215
+ if (userID != self.currentUserID) {
984
1216
  opponents.push(parseInt(userID));
985
1217
  }
986
1218
  });
@@ -992,12 +1224,12 @@ WebRTCSession.prototype.toString = function sessionToString() {
992
1224
  return 'ID: ' + this.ID + ', initiatorID: ' + this.initiatorID + ', opponentsIDs: ' + this.opponentsIDs + ', state: ' + this.state + ', callType: ' + this.callType;
993
1225
  };
994
1226
 
995
- function generateUUID(){
1227
+ function generateUUID() {
996
1228
  var d = new Date().getTime();
997
- var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function(c) {
998
- var r = (d + Math.random()*16)%16 | 0;
999
- d = Math.floor(d/16);
1000
- return (c=='x' ? r : (r&0x3|0x8)).toString(16);
1229
+ var uuid = 'xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx'.replace(/[xy]/g, function (c) {
1230
+ var r = (d + Math.random() * 16) % 16 | 0;
1231
+ d = Math.floor(d / 16);
1232
+ return (c == 'x' ? r : (r & 0x3 | 0x8)).toString(16);
1001
1233
  });
1002
1234
  return uuid;
1003
1235
  }
@@ -1010,9 +1242,9 @@ function _prepareExtension(extension) {
1010
1242
  var ext = {};
1011
1243
 
1012
1244
  try {
1013
- if ( ({}).toString.call(extension) === '[object Object]' ) {
1245
+ if (({}).toString.call(extension) === '[object Object]') {
1014
1246
  ext.userInfo = extension;
1015
- ext = JSON.parse( JSON.stringify(ext).replace(/null/g, "\"\"") );
1247
+ ext = JSON.parse(JSON.stringify(ext).replace(/null/g, "\"\""));
1016
1248
  } else {
1017
1249
  throw new Error('Invalid type of "extension" object.');
1018
1250
  }