File: source/socket-message.js

  1. /**
  2. * <blockquote class="info">
  3. * Note that broadcasted events from <a href="#method_muteStream"><code>muteStream()</code> method</a>,
  4. * <a href="#method_stopStream"><code>stopStream()</code> method</a>,
  5. * <a href="#method_stopScreen"><code>stopScreen()</code> method</a>,
  6. * <a href="#method_sendMessage"><code>sendMessage()</code> method</a>,
  7. * <a href="#method_unlockRoom"><code>unlockRoom()</code> method</a> and
  8. * <a href="#method_lockRoom"><code>lockRoom()</code> method</a> may be queued when
  9. * sent within less than an interval.
  10. * </blockquote>
  11. * Function that sends a message to Peers via the Signaling socket connection.
  12. * @method sendMessage
  13. * @param {String|JSON} message The message.
  14. * @param {String|Array} [targetPeerId] The target Peer ID to send message to.
  15. * - When provided as an Array, it will send the message to only Peers which IDs are in the list.
  16. * - When not provided, it will broadcast the message to all connected Peers in the Room.
  17. * @example
  18. * // Example 1: Broadcasting to all Peers
  19. * skylinkDemo.sendMessage("Hi all!");
  20. *
  21. * // Example 2: Sending to specific Peers
  22. * var peersInExclusiveParty = [];
  23. *
  24. * skylinkDemo.on("peerJoined", function (peerId, peerInfo, isSelf) {
  25. * if (isSelf) return;
  26. * if (peerInfo.userData.exclusive) {
  27. * peersInExclusiveParty.push(peerId);
  28. * }
  29. * });
  30. *
  31. * function updateExclusivePartyStatus (message) {
  32. * skylinkDemo.sendMessage(message, peersInExclusiveParty);
  33. * }
  34. * @trigger <ol class="desc-seq">
  35. * <li>Sends socket connection message to all targeted Peers via Signaling server. <ol>
  36. * <li><a href="#event_incomingMessage"><code>incomingMessage</code> event</a> triggers parameter payload
  37. * <code>message.isDataChannel</code> value as <code>false</code>.</li></ol></li></ol>
  38. * @for Skylink
  39. * @since 0.4.0
  40. */
  41. Skylink.prototype.sendMessage = function(message, targetPeerId) {
  42. var listOfPeers = Object.keys(this._peerInformations);
  43. var isPrivate = false;
  44.  
  45. if (Array.isArray(targetPeerId)) {
  46. listOfPeers = targetPeerId;
  47. isPrivate = true;
  48. } else if (targetPeerId && typeof targetPeerId === 'string') {
  49. listOfPeers = [targetPeerId];
  50. isPrivate = true;
  51. }
  52.  
  53. if (!this._inRoom || !this._socket || !this._user) {
  54. log.error('Unable to send message as User is not in Room. ->', message);
  55. return;
  56. }
  57.  
  58. // Loop out unwanted Peers
  59. for (var i = 0; i < listOfPeers.length; i++) {
  60. var peerId = listOfPeers[i];
  61.  
  62. if (!this._peerInformations[peerId]) {
  63. log.error([peerId, 'Socket', null, 'Dropping of sending message to Peer as ' +
  64. 'Peer session does not exists']);
  65. listOfPeers.splice(i, 1);
  66. i--;
  67. } else if (peerId === 'MCU') {
  68. listOfPeers.splice(i, 1);
  69. i--;
  70. } else if (isPrivate) {
  71. log.debug([peerId, 'Socket', null, 'Sending private message to Peer']);
  72.  
  73. this._sendChannelMessage({
  74. cid: this._key,
  75. data: message,
  76. mid: this._user.sid,
  77. rid: this._room.id,
  78. target: peerId,
  79. type: this._SIG_MESSAGE_TYPE.PRIVATE_MESSAGE
  80. });
  81. }
  82. }
  83.  
  84. if (listOfPeers.length === 0) {
  85. log.warn('Currently there are no Peers to send message to (unless the message is queued and ' +
  86. 'there are Peer connected by then).');
  87. }
  88.  
  89. if (!isPrivate) {
  90. log.debug([null, 'Socket', null, 'Broadcasting message to Peers']);
  91.  
  92. this._sendChannelMessage({
  93. cid: this._key,
  94. data: message,
  95. mid: this._user.sid,
  96. rid: this._room.id,
  97. type: this._SIG_MESSAGE_TYPE.PUBLIC_MESSAGE
  98. });
  99. } else {
  100. this._trigger('incomingMessage', {
  101. content: message,
  102. isPrivate: isPrivate,
  103. targetPeerId: targetPeerId || null,
  104. listOfPeers: listOfPeers,
  105. isDataChannel: false,
  106. senderPeerId: this._user.sid
  107. }, this._user.sid, this.getPeerInfo(), true);
  108. }
  109. };
  110.  
  111. /**
  112. * <blockquote class="info">
  113. * Note that this feature requires MCU and recording to be enabled for the App Key provided in the
  114. * <a href="#method_init"><code>init()</code> method</a>. If recording feature is not available to
  115. * be enabled in the <a href="https://console.temasys.io">Developer Console</a>, please
  116. * <a href="http://support.temasys.io">contact us on our support portal</a>.
  117. * </blockquote>
  118. * Starts a recording session.
  119. * @method startRecording
  120. * @param {Function} [callback] The callback function fired when request has completed.
  121. * <small>Function parameters signature is <code>function (error, success)</code></small>
  122. * <small>Function request completion is determined by the <a href="#event_recordingState">
  123. * <code>recordingState</code> event</a> triggering <code>state</code> parameter payload as <code>START</code>.</small>
  124. * @param {Error|String} callback.error The error result in request.
  125. * <small>Defined as <code>null</code> when there are no errors in request</small>
  126. * <small>Object signature is the <code>startRecording()</code> error when starting a new recording session.</small>
  127. * @param {String|JSON} callback.success The success result in request.
  128. * <small>Defined as <code>null</code> when there are errors in request</small>
  129. * <small>Object signature is the <a href="#event_recordingState">
  130. * <code>recordingState</code> event</a> triggered <code>recordingId</code> parameter payload.</small>
  131. * @example
  132. * // Example 1: Start recording session
  133. * skylinkDemo.startRecording(function (error, success) {
  134. * if (error) return;
  135. * console.info("Recording session has started. ID ->", success);
  136. * });
  137. * @trigger <ol class="desc-seq">
  138. * <li>If MCU is not connected: <ol><li><b>ABORT</b> and return error.</li></ol></li>
  139. * <li>If there is an existing recording session currently going on: <ol>
  140. * <li><b>ABORT</b> and return error.</li></ol></li>
  141. * <li>Sends to MCU via Signaling server to start recording session. <ol>
  142. * <li>If recording session has been started successfully: <ol>
  143. * <li><a href="#event_recordingState"><code>recordingState</code> event</a> triggers
  144. * parameter payload <code>state</code> as <code>START</code>.</li></ol></li></ol></li></ol>
  145. * @beta
  146. * @for Skylink
  147. * @since 0.6.16
  148. */
  149. Skylink.prototype.startRecording = function (callback) {
  150. var self = this;
  151.  
  152. if (!self._hasMCU) {
  153. var noMCUError = 'Unable to start recording as MCU is not connected';
  154. log.error(noMCUError);
  155. if (typeof callback === 'function') {
  156. callback(new Error(noMCUError), null);
  157. }
  158. return;
  159. }
  160.  
  161. if (self._currentRecordingId) {
  162. var hasRecordingSessionError = 'Unable to start recording as there is an existing recording in-progress';
  163. log.error(hasRecordingSessionError);
  164. if (typeof callback === 'function') {
  165. callback(new Error(hasRecordingSessionError), null);
  166. }
  167. return;
  168. }
  169.  
  170. if (typeof callback === 'function') {
  171. self.once('recordingState', function (state, recordingId) {
  172. callback(null, recordingId);
  173. }, function (state) {
  174. return state === self.RECORDING_STATE.START;
  175. });
  176. }
  177.  
  178. self._sendChannelMessage({
  179. type: self._SIG_MESSAGE_TYPE.START_RECORDING,
  180. rid: self._room.id,
  181. target: 'MCU'
  182. });
  183.  
  184. log.debug(['MCU', 'Recording', null, 'Starting recording']);
  185. };
  186.  
  187. /**
  188. * <blockquote class="info">
  189. * Note that this feature requires MCU and recording to be enabled for the App Key provided in the
  190. * <a href="#method_init"><code>init()</code> method</a>. If recording feature is not available to
  191. * be enabled in the <a href="https://console.temasys.io">Developer Console</a>, please
  192. * <a href="http://support.temasys.io">contact us on our support portal</a>.
  193. * </blockquote>
  194. * Stops a recording session.
  195. * @param {Function} [callback] The callback function fired when request has completed.
  196. * <small>Function parameters signature is <code>function (error, success)</code></small>
  197. * <small>Function request completion is determined by the <a href="#event_recordingState">
  198. * <code>recordingState</code> event</a> triggering <code>state</code> parameter payload as <code>STOP</code>
  199. * or as <code>LINK</code> when the value of <code>callbackSuccessWhenLink</code> is <code>true</code>.</small>
  200. * @param {Error|String} callback.error The error result in request.
  201. * <small>Defined as <code>null</code> when there are no errors in request</small>
  202. * <small>Object signature is the <code>stopRecording()</code> error when stopping current recording session.</small>
  203. * @param {String|JSON} callback.success The success result in request.
  204. * - When <code>callbackSuccessWhenLink</code> value is <code>false</code>, it is defined as string as
  205. * the recording session ID.
  206. * - when <code>callbackSuccessWhenLink</code> value is <code>true</code>, it is defined as an object as
  207. * the recording session information.
  208. * <small>Defined as <code>null</code> when there are errors in request</small>
  209. * @param {JSON} callback.success.recordingId The recording session ID.
  210. * @param {JSON} callback.success.link The recording session mixin videos link in
  211. * <a href="https://en.wikipedia.org/wiki/MPEG-4_Part_14">MP4</a> format.
  212. * <small>Object signature matches the <code>link</code> parameter payload received in the
  213. * <a href="#event_recordingState"><code>recordingState</code> event</a>.</small>
  214. * @param {Boolean} [callbackSuccessWhenLink=false] The flag if <code>callback</code> function provided
  215. * should result in success only when <a href="#event_recordingState"><code>recordingState</code> event</a>
  216. * triggering <code>state</code> parameter payload as <code>LINK</code>.
  217. * @method stopRecording
  218. * @example
  219. * // Example 1: Stop recording session
  220. * skylinkDemo.stopRecording(function (error, success) {
  221. * if (error) return;
  222. * console.info("Recording session has stopped. ID ->", success);
  223. * });
  224. *
  225. * // Example 2: Stop recording session with mixin videos link
  226. * skylinkDemo.stopRecording(function (error, success) {
  227. * if (error) return;
  228. * console.info("Recording session has compiled with links ->", success.link);
  229. * }, true);
  230. * @trigger <ol class="desc-seq">
  231. * <li>If MCU is not connected: <ol><li><b>ABORT</b> and return error.</li></ol></li>
  232. * <li>If there is no existing recording session currently going on: <ol>
  233. * <li><b>ABORT</b> and return error.</li></ol></li>
  234. * <li>If existing recording session recording time has not elapsed more than 4 seconds:
  235. * <small>4 seconds is mandatory for recording session to ensure better recording
  236. * experience and stability.</small> <ol><li><b>ABORT</b> and return error.</li></ol></li>
  237. * <li>Sends to MCU via Signaling server to stop recording session: <ol>
  238. * <li>If recording session has been stopped successfully: <ol>
  239. * <li><a href="#event_recordingState"><code>recordingState</code> event</a>
  240. * triggers parameter payload <code>state</code> as <code>START</code>.
  241. * <li>MCU starts mixin recorded session videos: <ol>
  242. * <li>If recording session has been mixin successfully with links: <ol>
  243. * <li><a href="#event_recordingState"><code>recordingState</code> event</a> triggers
  244. * parameter payload <code>state</code> as <code>LINK</code>.<li>Else: <ol>
  245. * <li><a href="#event_recordingState"><code>recordingState</code> event</a> triggers
  246. * parameter payload <code>state</code> as <code>ERROR</code>.<li><b>ABORT</b> and return error.</ol></li>
  247. * </ol></li></ol></li><li>Else: <ol>
  248. * <li><a href="#event_recordingState"><code>recordingState</code> event</a>
  249. * triggers parameter payload <code>state</code> as <code>ERROR</code>.</li><li><b>ABORT</b> and return error.</li>
  250. * </ol></li></ol></li></ol>
  251. * @beta
  252. * @for Skylink
  253. * @since 0.6.16
  254. */
  255. Skylink.prototype.stopRecording = function (callback, callbackSuccessWhenLink) {
  256. var self = this;
  257.  
  258. if (!self._hasMCU) {
  259. var noMCUError = 'Unable to stop recording as MCU is not connected';
  260. log.error(noMCUError);
  261. if (typeof callback === 'function') {
  262. callback(new Error(noMCUError), null);
  263. }
  264. return;
  265. }
  266.  
  267. if (!self._currentRecordingId) {
  268. var noRecordingSessionError = 'Unable to stop recording as there is no recording in-progress';
  269. log.error(noRecordingSessionError);
  270. if (typeof callback === 'function') {
  271. callback(new Error(noRecordingSessionError), null);
  272. }
  273. return;
  274. }
  275.  
  276. if (self._recordingStartInterval) {
  277. var recordingSecsRequiredError = 'Unable to stop recording as 4 seconds has not been recorded yet';
  278. log.error(recordingSecsRequiredError);
  279. if (typeof callback === 'function') {
  280. callback(new Error(recordingSecsRequiredError), null);
  281. }
  282. return;
  283. }
  284.  
  285. if (typeof callback === 'function') {
  286. var expectedRecordingId = self._currentRecordingId;
  287.  
  288. self.once('recordingState', function (state, recordingId, link, error) {
  289. if (callbackSuccessWhenLink) {
  290. if (error) {
  291. callback(error, null);
  292. return;
  293. }
  294.  
  295. callback(null, {
  296. link: link,
  297. recordingId: recordingId
  298. });
  299. return;
  300. }
  301.  
  302. callback(null, recordingId);
  303.  
  304. }, function (state, recordingId) {
  305. if (expectedRecordingId === recordingId) {
  306. if (callbackSuccessWhenLink) {
  307. return [self.RECORDING_STATE.LINK, self.RECORDING_STATE.ERROR].indexOf(state) > -1;
  308. }
  309. return state === self.RECORDING_STATE.STOP;
  310. }
  311. });
  312. }
  313.  
  314. self._sendChannelMessage({
  315. type: self._SIG_MESSAGE_TYPE.STOP_RECORDING,
  316. rid: self._room.id,
  317. target: 'MCU'
  318. });
  319.  
  320. log.debug(['MCU', 'Recording', null, 'Stopping recording']);
  321. };
  322.  
  323. /**
  324. * <blockquote class="info">
  325. * Note that this feature requires MCU and recording to be enabled for the App Key provided in the
  326. * <a href="#method_init"><code>init()</code> method</a>. If recording feature is not available to
  327. * be enabled in the <a href="https://console.temasys.io">Developer Console</a>, please
  328. * <a href="http://support.temasys.io">contact us on our support portal</a>.
  329. * </blockquote>
  330. * Gets the list of current recording sessions since User has connected to the Room.
  331. * @method getRecordings
  332. * @return {JSON} The list of recording sessions.<ul>
  333. * <li><code>#recordingId</code><var><b>{</b>JSON<b>}</b></var><p>The recording session.</p><ul>
  334. * <li><code>active</code><var><b>{</b>Boolean<b>}</b></var><p>The flag that indicates if the recording session is currently active.</p></li>
  335. * <li><code>state</code><var><b>{</b>Number<b>}</b></var><p>The current recording state. [Rel: Skylink.RECORDING_STATE]</p></li>
  336. * <li><code>startedDateTime</code><var><b>{</b>String<b>}</b></var><p>The recording session started DateTime in
  337. * <a href="https://en.wikipedia.org/wiki/ISO_8601">ISO 8601 format</a>.<small>Note that this value may not be
  338. * very accurate as this value is recorded when the start event is received.</small></p></li>
  339. * <li><code>endedDateTime</code><var><b>{</b>String<b>}</b></var><p>The recording session ended DateTime in
  340. * <a href="https://en.wikipedia.org/wiki/ISO_8601">ISO 8601 format</a>.<small>Note that this value may not be
  341. * very accurate as this value is recorded when the stop event is received.</small>
  342. * <small>Defined only after <code>state</code> has triggered <code>STOP</code>.</small></p></li>
  343. * <li><code>mixingDateTime</code><var><b>{</b>String<b>}</b></var><p>The recording session mixing completed DateTime in
  344. * <a href="https://en.wikipedia.org/wiki/ISO_8601">ISO 8601 format</a>.<small>Note that this value may not be
  345. * very accurate as this value is recorded when the mixing completed event is received.</small>
  346. * <small>Defined only when <code>state</code> is <code>LINK</code>.</small></p></li>
  347. * <li><code>links</code><var><b>{</b>JSON<b>}</b></var><p>The recording session links.
  348. * <small>Object signature matches the <code>link</code> parameter payload received in the
  349. * <a href="#event_recordingState"><code>recordingState</code> event</a>.</small>
  350. * <small>Defined only when <code>state</code> is <code>LINK</code>.</small></p></li>
  351. * <li><code>error</code><var><b>{</b>Error<b>}</b></var><p>The recording session error.
  352. * <small>Defined only when <code>state</code> is <code>ERROR</code>.</small></p></li></ul></li></ul>
  353. * @example
  354. * // Example 1: Get recording sessions
  355. * skylinkDemo.getRecordings();
  356. * @beta
  357. * @for Skylink
  358. * @since 0.6.16
  359. */
  360. Skylink.prototype.getRecordings = function () {
  361. return clone(this._recordings);
  362. };
  363.  
  364. /**
  365. * Function that handles and processes the socket message received.
  366. * @method _processSigMessage
  367. * @private
  368. * @for Skylink
  369. * @since 0.1.0
  370. */
  371. Skylink.prototype._processSigMessage = function(message, session) {
  372. var origin = message.mid;
  373. if (!origin || origin === this._user.sid) {
  374. origin = 'Server';
  375. }
  376. log.debug([origin, 'Socket', message.type, 'Received from peer ->'], clone(message));
  377. if (message.mid === this._user.sid &&
  378. message.type !== this._SIG_MESSAGE_TYPE.REDIRECT &&
  379. message.type !== this._SIG_MESSAGE_TYPE.IN_ROOM) {
  380. log.debug([origin, 'Socket', message.type, 'Ignoring message ->'], clone(message));
  381. return;
  382. }
  383. switch (message.type) {
  384. //--- BASIC API Messages ----
  385. case this._SIG_MESSAGE_TYPE.PUBLIC_MESSAGE:
  386. this._publicMessageHandler(message);
  387. break;
  388. case this._SIG_MESSAGE_TYPE.PRIVATE_MESSAGE:
  389. this._privateMessageHandler(message);
  390. break;
  391. case this._SIG_MESSAGE_TYPE.IN_ROOM:
  392. this._inRoomHandler(message);
  393. break;
  394. case this._SIG_MESSAGE_TYPE.ENTER:
  395. this._enterHandler(message);
  396. break;
  397. case this._SIG_MESSAGE_TYPE.WELCOME:
  398. this._welcomeHandler(message);
  399. break;
  400. case this._SIG_MESSAGE_TYPE.RESTART:
  401. this._restartHandler(message);
  402. break;
  403. case this._SIG_MESSAGE_TYPE.OFFER:
  404. this._offerHandler(message);
  405. break;
  406. case this._SIG_MESSAGE_TYPE.ANSWER:
  407. this._answerHandler(message);
  408. break;
  409. case this._SIG_MESSAGE_TYPE.CANDIDATE:
  410. this._candidateHandler(message);
  411. break;
  412. case this._SIG_MESSAGE_TYPE.BYE:
  413. this._byeHandler(message);
  414. break;
  415. case this._SIG_MESSAGE_TYPE.REDIRECT:
  416. this._redirectHandler(message);
  417. break;
  418. //--- ADVANCED API Messages ----
  419. case this._SIG_MESSAGE_TYPE.UPDATE_USER:
  420. this._updateUserEventHandler(message);
  421. break;
  422. case this._SIG_MESSAGE_TYPE.MUTE_VIDEO:
  423. this._muteVideoEventHandler(message);
  424. break;
  425. case this._SIG_MESSAGE_TYPE.MUTE_AUDIO:
  426. this._muteAudioEventHandler(message);
  427. break;
  428. case this._SIG_MESSAGE_TYPE.STREAM:
  429. this._streamEventHandler(message);
  430. break;
  431. case this._SIG_MESSAGE_TYPE.ROOM_LOCK:
  432. this._roomLockEventHandler(message);
  433. break;
  434. case this._SIG_MESSAGE_TYPE.PEER_LIST:
  435. this._peerListEventHandler(message);
  436. break;
  437. case this._SIG_MESSAGE_TYPE.INTRODUCE_ERROR:
  438. this._introduceErrorEventHandler(message);
  439. break;
  440. case this._SIG_MESSAGE_TYPE.APPROACH:
  441. this._approachEventHandler(message);
  442. break;
  443. case this._SIG_MESSAGE_TYPE.RECORDING:
  444. this._recordingEventHandler(message);
  445. break;
  446. case this._SIG_MESSAGE_TYPE.END_OF_CANDIDATES:
  447. this._endOfCandidatesHandler(message);
  448. break;
  449. default:
  450. log.error([message.mid, 'Socket', message.type, 'Unsupported message ->'], clone(message));
  451. break;
  452. }
  453. };
  454.  
  455. /**
  456. * Function that handles the "peerList" socket message received.
  457. * See confluence docs for the "peerList" expected properties to be received
  458. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  459. * @method _peerListEventHandler
  460. * @private
  461. * @for Skylink
  462. * @since 0.6.1
  463. */
  464. Skylink.prototype._peerListEventHandler = function(message){
  465. var self = this;
  466. self._peerList = message.result;
  467. log.log(['Server', null, message.type, 'Received list of peers'], self._peerList);
  468. self._trigger('getPeersStateChange',self.GET_PEERS_STATE.RECEIVED, self._user.sid, self._peerList);
  469. };
  470.  
  471. /**
  472. * Function that handles the "endOfCandidates" socket message received.
  473. * See confluence docs for the "endOfCandidates" expected properties to be received
  474. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  475. * @method _endOfCandidatesHandler
  476. * @private
  477. * @for Skylink
  478. * @since 0.6.1
  479. */
  480. Skylink.prototype._endOfCandidatesHandler = function(message){
  481. var self = this;
  482. var targetMid = message.mid;
  483.  
  484. if (!(self._peerConnections[targetMid] &&
  485. self._peerConnections[targetMid].signalingState !== self.PEER_CONNECTION_STATE.CLOSED)) {
  486. return;
  487. }
  488.  
  489. self._peerEndOfCandidatesCounter[targetMid].expectedLen = message.noOfExpectedCandidates || 0;
  490. self._signalingEndOfCandidates(targetMid);
  491. };
  492.  
  493. /**
  494. * Function that handles the "introduceError" socket message received.
  495. * See confluence docs for the "introduceError" expected properties to be received
  496. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  497. * @method _introduceErrorEventHandler
  498. * @private
  499. * @for Skylink
  500. * @since 0.6.1
  501. */
  502. Skylink.prototype._introduceErrorEventHandler = function(message){
  503. var self = this;
  504. log.log(['Server', null, message.type, 'Introduce failed. Reason: '+message.reason]);
  505. self._trigger('introduceStateChange',self.INTRODUCE_STATE.ERROR, self._user.sid,
  506. message.sendingPeerId, message.receivingPeerId, message.reason);
  507. };
  508.  
  509. /**
  510. * Function that handles the "approach" socket message received.
  511. * See confluence docs for the "approach" expected properties to be received
  512. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  513. * @method _approachEventHandler
  514. * @private
  515. * @for Skylink
  516. * @since 0.6.1
  517. */
  518. Skylink.prototype._approachEventHandler = function(message){
  519. var self = this;
  520. log.log(['Server', null, message.type, 'Approaching peer'], message.target);
  521. // self._room.connection.peerConfig = self._setIceServers(message.pc_config);
  522. // self._inRoom = true;
  523. self._trigger('handshakeProgress', self.HANDSHAKE_PROGRESS.ENTER, self._user.sid);
  524.  
  525. var enterMsg = {
  526. type: self._SIG_MESSAGE_TYPE.ENTER,
  527. mid: self._user.sid,
  528. rid: self._room.id,
  529. agent: AdapterJS.webrtcDetectedBrowser,
  530. version: (AdapterJS.webrtcDetectedVersion || 0).toString(),
  531. os: window.navigator.platform,
  532. userInfo: self._getUserInfo(),
  533. receiveOnly: self.getPeerInfo().config.receiveOnly,
  534. target: message.target,
  535. weight: self._peerPriorityWeight,
  536. temasysPluginVersion: AdapterJS.WebRTCPlugin.plugin ? AdapterJS.WebRTCPlugin.plugin.VERSION : null,
  537. enableIceTrickle: self._initOptions.enableIceTrickle,
  538. enableDataChannel: self._initOptions.enableDataChannel,
  539. enableIceRestart: self._enableIceRestart,
  540. SMProtocolVersion: self.SM_PROTOCOL_VERSION,
  541. DTProtocolVersion: self.DT_PROTOCOL_VERSION
  542. };
  543.  
  544. if (self._publishOnly) {
  545. enterMsg.publishOnly = {
  546. type: self._streams.screenshare && self._streams.screenshare.stream ? 'screenshare' : 'video'
  547. };
  548. }
  549.  
  550. if (self._parentId) {
  551. enterMsg.parentId = self._parentId;
  552. }
  553.  
  554. self._sendChannelMessage(enterMsg);
  555. };
  556.  
  557. /**
  558. * Function that handles the "redirect" socket message received.
  559. * See confluence docs for the "redirect" expected properties to be received
  560. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  561. * @method _redirectHandler
  562. * @private
  563. * @for Skylink
  564. * @since 0.5.1
  565. */
  566. Skylink.prototype._redirectHandler = function(message) {
  567. log.log(['Server', null, message.type, 'System action warning:'], {
  568. message: message.info,
  569. reason: message.reason,
  570. action: message.action
  571. });
  572.  
  573. if (message.action === this.SYSTEM_ACTION.REJECT) {
  574. for (var key in this._peerConnections) {
  575. if (this._peerConnections.hasOwnProperty(key)) {
  576. this._removePeer(key);
  577. }
  578. }
  579. }
  580.  
  581. // Handle the differences provided in Signaling server
  582. if (message.reason === 'toClose') {
  583. message.reason = 'toclose';
  584. }
  585.  
  586. this._trigger('systemAction', message.action, message.info, message.reason);
  587. };
  588.  
  589. /**
  590. * Function that handles the "updateUserEvent" socket message received.
  591. * See confluence docs for the "updateUserEvent" expected properties to be received
  592. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  593. * @method _updateUserEventHandler
  594. * @private
  595. * @for Skylink
  596. * @since 0.2.0
  597. */
  598. Skylink.prototype._updateUserEventHandler = function(message) {
  599. var targetMid = message.mid;
  600. log.log([targetMid, null, message.type, 'Peer updated userData:'], message.userData);
  601. if (this._peerInformations[targetMid]) {
  602. if (this._peerMessagesStamps[targetMid] && typeof message.stamp === 'number') {
  603. if (message.stamp < this._peerMessagesStamps[targetMid].userData) {
  604. log.warn([targetMid, null, message.type, 'Dropping outdated status ->'], message);
  605. return;
  606. }
  607. this._peerMessagesStamps[targetMid].userData = message.stamp;
  608. }
  609. this._peerInformations[targetMid].userData = message.userData || {};
  610. this._trigger('peerUpdated', targetMid, this.getPeerInfo(targetMid), false);
  611. } else {
  612. log.log([targetMid, null, message.type, 'Peer does not have any user information']);
  613. }
  614. };
  615.  
  616. /**
  617. * Function that handles the "roomLockEvent" socket message received.
  618. * See confluence docs for the "roomLockEvent" expected properties to be received
  619. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  620. * @method _roomLockEventHandler
  621. * @private
  622. * @for Skylink
  623. * @since 0.2.0
  624. */
  625. Skylink.prototype._roomLockEventHandler = function(message) {
  626. var targetMid = message.mid;
  627. log.log([targetMid, message.type, 'Room lock status:'], message.lock);
  628. this._trigger('roomLock', message.lock, targetMid, this.getPeerInfo(targetMid), false);
  629. };
  630.  
  631. /**
  632. * Function that handles the "muteAudioEvent" socket message received.
  633. * See confluence docs for the "muteAudioEvent" expected properties to be received
  634. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  635. * @method _muteAudioEventHandler
  636. * @private
  637. * @for Skylink
  638. * @since 0.2.0
  639. */
  640. Skylink.prototype._muteAudioEventHandler = function(message) {
  641. var targetMid = message.mid;
  642. log.log([targetMid, null, message.type, 'Peer\'s audio muted:'], message.muted);
  643. if (this._peerInformations[targetMid]) {
  644. if (this._peerMessagesStamps[targetMid] && typeof message.stamp === 'number') {
  645. if (message.stamp < this._peerMessagesStamps[targetMid].audioMuted) {
  646. log.warn([targetMid, null, message.type, 'Dropping outdated status ->'], message);
  647. return;
  648. }
  649. this._peerMessagesStamps[targetMid].audioMuted = message.stamp;
  650. }
  651. this._peerInformations[targetMid].mediaStatus.audioMuted = message.muted;
  652. this._trigger('streamMuted', targetMid, this.getPeerInfo(targetMid), false,
  653. this._peerInformations[targetMid].settings.video &&
  654. this._peerInformations[targetMid].settings.video.screenshare);
  655. this._trigger('peerUpdated', targetMid, this.getPeerInfo(targetMid), false);
  656. } else {
  657. log.log([targetMid, message.type, 'Peer does not have any user information']);
  658. }
  659. };
  660.  
  661. /**
  662. * Function that handles the "muteVideoEvent" socket message received.
  663. * See confluence docs for the "muteVideoEvent" expected properties to be received
  664. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  665. * @method _muteVideoEventHandler
  666. * @private
  667. * @for Skylink
  668. * @since 0.2.0
  669. */
  670. Skylink.prototype._muteVideoEventHandler = function(message) {
  671. var targetMid = message.mid;
  672. log.log([targetMid, null, message.type, 'Peer\'s video muted:'], message.muted);
  673. if (this._peerInformations[targetMid]) {
  674. if (this._peerMessagesStamps[targetMid] && typeof message.stamp === 'number') {
  675. if (message.stamp < this._peerMessagesStamps[targetMid].videoMuted) {
  676. log.warn([targetMid, null, message.type, 'Dropping outdated status ->'], message);
  677. return;
  678. }
  679. this._peerMessagesStamps[targetMid].videoMuted = message.stamp;
  680. }
  681. this._peerInformations[targetMid].mediaStatus.videoMuted = message.muted;
  682. this._trigger('streamMuted', targetMid, this.getPeerInfo(targetMid), false,
  683. this._peerInformations[targetMid].settings.video &&
  684. this._peerInformations[targetMid].settings.video.screenshare);
  685. this._trigger('peerUpdated', targetMid, this.getPeerInfo(targetMid), false);
  686. } else {
  687. log.log([targetMid, null, message.type, 'Peer does not have any user information']);
  688. }
  689. };
  690.  
  691. /**
  692. * Function that handles the "stream" socket message received.
  693. * See confluence docs for the "stream" expected properties to be received
  694. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  695. * @method _streamEventHandler
  696. * @private
  697. * @for Skylink
  698. * @since 0.2.0
  699. */
  700. Skylink.prototype._streamEventHandler = function(message) {
  701. var targetMid = message.mid;
  702. log.log([targetMid, null, message.type, 'Peer\'s stream status:'], message.status);
  703.  
  704. if (this._peerInformations[targetMid] && message.streamId) {
  705. this._streamsSession[targetMid] = this._streamsSession[targetMid] || {};
  706. if (message.status === 'ended') {
  707. if (message.settings && typeof message.settings === 'object' &&
  708. typeof this._streamsSession[targetMid][message.streamId] === 'undefined') {
  709. this._streamsSession[targetMid][message.streamId] = {
  710. audio: message.settings.audio,
  711. video: message.settings.video
  712. };
  713. }
  714.  
  715. this._handleEndedStreams(targetMid, message.streamId);
  716. }
  717. } else {
  718. // Probably left the room already
  719. log.log([targetMid, null, message.type, 'Peer does not have any user information']);
  720. }
  721. };
  722.  
  723. /**
  724. * Function that handles the "bye" socket message received.
  725. * See confluence docs for the "bye" expected properties to be received
  726. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  727. * @method _byeHandler
  728. * @private
  729. * @for Skylink
  730. * @since 0.1.0
  731. */
  732. Skylink.prototype._byeHandler = function(message) {
  733. var targetMid = message.mid;
  734. var selfId = (this._user || {}).sid;
  735.  
  736. if (selfId !== targetMid){
  737. log.log([targetMid, null, message.type, 'Peer has left the room']);
  738. this._removePeer(targetMid);
  739. } else {
  740. log.log([targetMid, null, message.type, 'Self has left the room']);
  741. }
  742. };
  743.  
  744. /**
  745. * Function that handles the "private" socket message received.
  746. * See confluence docs for the "private" expected properties to be received
  747. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  748. * @method _privateMessageHandler
  749. * @private
  750. * @for Skylink
  751. * @since 0.4.0
  752. */
  753. Skylink.prototype._privateMessageHandler = function(message) {
  754. var targetMid = message.mid;
  755. log.log([targetMid, null, message.type,
  756. 'Received private message from peer:'], message.data);
  757. this._trigger('incomingMessage', {
  758. content: message.data,
  759. isPrivate: true,
  760. targetPeerId: message.target, // is not null if there's user
  761. isDataChannel: false,
  762. senderPeerId: targetMid
  763. }, targetMid, this.getPeerInfo(targetMid), false);
  764. };
  765.  
  766. /**
  767. * Function that handles the "public" socket message received.
  768. * See confluence docs for the "public" expected properties to be received
  769. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  770. * @method _publicMessageHandler
  771. * @private
  772. * @for Skylink
  773. * @since 0.4.0
  774. */
  775. Skylink.prototype._publicMessageHandler = function(message) {
  776. var targetMid = message.mid;
  777. log.log([targetMid, null, message.type,
  778. 'Received public message from peer:'], message.data);
  779. this._trigger('incomingMessage', {
  780. content: message.data,
  781. isPrivate: false,
  782. targetPeerId: null, // is not null if there's user
  783. isDataChannel: false,
  784. senderPeerId: targetMid
  785. }, targetMid, this.getPeerInfo(targetMid), false);
  786. };
  787.  
  788. /**
  789. * Handles the RECORDING Protocol message event received from the platform signaling.
  790. * @method _recordingEventHandler
  791. * @param {JSON} message The message object received from platform signaling.
  792. * This should contain the <code>RECORDING</code> payload.
  793. * @param {String} message.url The recording URL if mixing has completed.
  794. * @param {String} message.action The recording action received.
  795. * @param {String} message.error The recording error exception received.
  796. * @private
  797. * @beta
  798. * @for Skylink
  799. * @since 0.6.16
  800. */
  801. Skylink.prototype._recordingEventHandler = function (message) {
  802. var self = this;
  803.  
  804. log.debug(['MCU', 'Recording', null, 'Received recording message ->'], message);
  805.  
  806. if (message.action === 'on') {
  807. if (!self._recordings[message.recordingId]) {
  808. log.debug(['MCU', 'Recording', message.recordingId, 'Started recording']);
  809.  
  810. self._currentRecordingId = message.recordingId;
  811. self._recordings[message.recordingId] = {
  812. active: true,
  813. state: self.RECORDING_STATE.START,
  814. startedDateTime: (new Date()).toISOString(),
  815. endedDateTime: null,
  816. mixingDateTime: null,
  817. links: null,
  818. error: null
  819. };
  820. self._recordingStartInterval = setTimeout(function () {
  821. log.log(['MCU', 'Recording', message.recordingId, '4 seconds has been recorded. Recording can be stopped now']);
  822. self._recordingStartInterval = null;
  823. }, 4000);
  824. self._trigger('recordingState', self.RECORDING_STATE.START, message.recordingId, null, null);
  825. }
  826.  
  827. } else if (message.action === 'off') {
  828. if (!self._recordings[message.recordingId]) {
  829. log.error(['MCU', 'Recording', message.recordingId, 'Received request of "off" but the session is empty']);
  830. return;
  831. }
  832.  
  833. self._currentRecordingId = null;
  834.  
  835. if (self._recordingStartInterval) {
  836. clearTimeout(self._recordingStartInterval);
  837. log.warn(['MCU', 'Recording', message.recordingId, 'Recording stopped abruptly before 4 seconds']);
  838. self._recordingStartInterval = null;
  839. }
  840.  
  841. log.debug(['MCU', 'Recording', message.recordingId, 'Stopped recording']);
  842.  
  843. self._recordings[message.recordingId].active = false;
  844. self._recordings[message.recordingId].state = self.RECORDING_STATE.STOP;
  845. self._recordings[message.recordingId].endedDateTime = (new Date()).toISOString();
  846. self._trigger('recordingState', self.RECORDING_STATE.STOP, message.recordingId, null, null);
  847.  
  848. } else if (message.action === 'url') {
  849. if (!self._recordings[message.recordingId]) {
  850. log.error(['MCU', 'Recording', message.recordingId, 'Received URL but the session is empty']);
  851. return;
  852. }
  853.  
  854. var links = {};
  855.  
  856. if (Array.isArray(message.urls)) {
  857. for (var i = 0; i < message.urls.length; i++) {
  858. links[messages.urls[i].id || ''] = messages.urls[i].url || '';
  859. }
  860. } else if (typeof message.url === 'string') {
  861. links.mixin = message.url;
  862. }
  863.  
  864. self._recordings[message.recordingId].links = links;
  865. self._recordings[message.recordingId].state = self.RECORDING_STATE.LINK;
  866. self._recordings[message.recordingId].mixingDateTime = (new Date()).toISOString();
  867. self._trigger('recordingState', self.RECORDING_STATE.LINK, message.recordingId, links, null);
  868.  
  869. } else {
  870. var recordingError = new Error(message.error || 'Unknown error');
  871. if (!self._recordings[message.recordingId]) {
  872. log.error(['MCU', 'Recording', message.recordingId, 'Received error but the session is empty ->'], recordingError);
  873. return;
  874. }
  875.  
  876. log.error(['MCU', 'Recording', message.recordingId, 'Recording failure ->'], recordingError);
  877.  
  878. self._recordings[message.recordingId].state = self.RECORDING_STATE.ERROR;
  879. self._recordings[message.recordingId].error = recordingError;
  880.  
  881. if (self._recordings[message.recordingId].active) {
  882. log.debug(['MCU', 'Recording', message.recordingId, 'Stopped recording abruptly']);
  883. self._recordings[message.recordingId].active = false;
  884. //self._trigger('recordingState', self.RECORDING_STATE.STOP, message.recordingId, null, recordingError);
  885. }
  886.  
  887. self._trigger('recordingState', self.RECORDING_STATE.ERROR, message.recordingId, null, recordingError);
  888. }
  889. };
  890.  
  891. /**
  892. * Function that handles the "inRoom" socket message received.
  893. * See confluence docs for the "inRoom" expected properties to be received
  894. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  895. * @method _inRoomHandler
  896. * @private
  897. * @for Skylink
  898. * @since 0.1.0
  899. */
  900. Skylink.prototype._inRoomHandler = function(message) {
  901. var self = this;
  902. log.log(['Server', null, message.type, 'User is now in the room and ' +
  903. 'functionalities are now available. Config received:'], message.pc_config);
  904. self._room.connection.peerConfig = self._setIceServers((message.pc_config || {}).iceServers || []);
  905. self._inRoom = true;
  906. self._user.sid = message.sid;
  907. self._peerPriorityWeight = message.tieBreaker + (self._initOptions.priorityWeightScheme === self.PRIORITY_WEIGHT_SCHEME.AUTO ?
  908. 0 : (self._initOptions.priorityWeightScheme === self.PRIORITY_WEIGHT_SCHEME.ENFORCE_OFFERER ? 2e+15 : -(2e+15)));
  909.  
  910. self._trigger('peerJoined', self._user.sid, self.getPeerInfo(), true);
  911. self._trigger('handshakeProgress', self.HANDSHAKE_PROGRESS.ENTER, self._user.sid);
  912.  
  913. var streamId = null;
  914.  
  915. if (self._streams.screenshare && self._streams.screenshare.stream) {
  916. streamId = self._streams.screenshare.stream.id || self._streams.screenshare.stream.label;
  917. self._trigger('incomingStream', self._user.sid, self._streams.screenshare.stream, true, self.getPeerInfo(), true, streamId);
  918. } else if (self._streams.userMedia && self._streams.userMedia.stream) {
  919. streamId = self._streams.userMedia.stream.id || self._streams.userMedia.stream.label;
  920. self._trigger('incomingStream', self._user.sid, self._streams.userMedia.stream, true, self.getPeerInfo(), false, streamId);
  921. }
  922. // NOTE ALEX: should we wait for local streams?
  923. // or just go with what we have (if no stream, then one way?)
  924. // do we hardcode the logic here, or give the flexibility?
  925. // It would be better to separate, do we could choose with whom
  926. // we want to communicate, instead of connecting automatically to all.
  927. var enterMsg = {
  928. type: self._SIG_MESSAGE_TYPE.ENTER,
  929. mid: self._user.sid,
  930. rid: self._room.id,
  931. agent: AdapterJS.webrtcDetectedBrowser,
  932. version: (AdapterJS.webrtcDetectedVersion || 0).toString(),
  933. os: window.navigator.platform,
  934. userInfo: self._getUserInfo(),
  935. receiveOnly: self.getPeerInfo().config.receiveOnly,
  936. weight: self._peerPriorityWeight,
  937. temasysPluginVersion: AdapterJS.WebRTCPlugin.plugin ? AdapterJS.WebRTCPlugin.plugin.VERSION : null,
  938. enableIceTrickle: self._initOptions.enableIceTrickle,
  939. enableDataChannel: self._initOptions.enableDataChannel,
  940. enableIceRestart: self._enableIceRestart,
  941. SMProtocolVersion: self.SM_PROTOCOL_VERSION,
  942. DTProtocolVersion: self.DT_PROTOCOL_VERSION
  943. };
  944.  
  945. if (self._publishOnly) {
  946. enterMsg.publishOnly = {
  947. type: self._streams.screenshare && self._streams.screenshare.stream ? 'screenshare' : 'video'
  948. };
  949. }
  950.  
  951. if (self._parentId) {
  952. enterMsg.parentId = self._parentId;
  953. }
  954.  
  955. self._sendChannelMessage(enterMsg);
  956. };
  957.  
  958. /**
  959. * Function that handles the "enter" socket message received.
  960. * See confluence docs for the "enter" expected properties to be received
  961. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  962. * @method _enterHandler
  963. * @private
  964. * @for Skylink
  965. * @since 0.5.1
  966. */
  967. Skylink.prototype._enterHandler = function(message) {
  968. var self = this;
  969. var targetMid = message.mid;
  970. var isNewPeer = false;
  971. var userInfo = message.userInfo || {};
  972. userInfo.settings = userInfo.settings || {};
  973. userInfo.mediaStatus = userInfo.mediaStatus || {};
  974. userInfo.config = {
  975. enableIceTrickle: typeof message.enableIceTrickle === 'boolean' ? message.enableIceTrickle : true,
  976. enableIceRestart: typeof message.enableIceRestart === 'boolean' ? message.enableIceRestart : false,
  977. enableDataChannel: typeof message.enableDataChannel === 'boolean' ? message.enableDataChannel : true,
  978. priorityWeight: typeof message.weight === 'number' ? message.weight : 0,
  979. receiveOnly: message.receiveOnly === true,
  980. publishOnly: !!message.publishOnly
  981. };
  982. userInfo.parentId = message.parentId || null;
  983. userInfo.agent = {
  984. name: typeof message.agent === 'string' && message.agent ? message.agent : 'other',
  985. version: (function () {
  986. if (!(message.version && typeof message.version === 'string')) {
  987. return 0;
  988. }
  989. // E.g. 0.9.6, replace minor "." with 0
  990. if (message.version.indexOf('.') > -1) {
  991. var parts = message.version.split('.');
  992. if (parts.length > 2) {
  993. var majorVer = parts[0] || '0';
  994. parts.splice(0, 1);
  995. return parseFloat(majorVer + '.' + parts.join('0'), 10);
  996. }
  997. return parseFloat(message.version || '0', 10);
  998. }
  999. return parseInt(message.version || '0', 10);
  1000. })(),
  1001. os: typeof message.os === 'string' && message.os ? message.os : '',
  1002. pluginVersion: typeof message.temasysPluginVersion === 'string' && message.temasysPluginVersion ?
  1003. message.temasysPluginVersion : null,
  1004. SMProtocolVersion: message.SMProtocolVersion && typeof message.SMProtocolVersion === 'string' ?
  1005. message.SMProtocolVersion : '0.1.1',
  1006. DTProtocolVersion: message.DTProtocolVersion && typeof message.DTProtocolVersion === 'string' ?
  1007. message.DTProtocolVersion : (self._hasMCU || targetMid === 'MCU' ? '0.1.2' : '0.1.0')
  1008. };
  1009.  
  1010. log.log([targetMid, 'RTCPeerConnection', null, 'Peer "enter" received ->'], message);
  1011.  
  1012. // Ignore if: User is publishOnly and MCU is enabled
  1013. // : User is parent and parentId is defined and matches
  1014. // : User is child and parent matches
  1015. // Don't if : Is MCU
  1016. if (targetMid !== 'MCU' && ((self._parentId && self._parentId === targetMid) ||
  1017. (self._hasMCU && self._publishOnly) || (message.parentId && self._user && self._user.sid &&
  1018. message.parentId === self._user.sid))) {
  1019. log.warn([targetMid, 'RTCPeerConnection', null, 'Discarding "enter" for parentId or publishOnly case ->'], message);
  1020. return;
  1021. }
  1022.  
  1023. var processPeerFn = function (cert) {
  1024. if (!self._peerInformations[targetMid]) {
  1025. isNewPeer = true;
  1026.  
  1027. self._peerInformations[targetMid] = userInfo;
  1028.  
  1029. var hasScreenshare = userInfo.settings.video && typeof userInfo.settings.video === 'object' &&
  1030. !!userInfo.settings.video.screenshare;
  1031.  
  1032. self._addPeer(targetMid, cert || null, {
  1033. agent: userInfo.agent.name,
  1034. version: userInfo.agent.version,
  1035. os: userInfo.agent.os
  1036. }, message.receiveOnly, hasScreenshare);
  1037.  
  1038. if (targetMid === 'MCU') {
  1039. log.info([targetMid, 'RTCPeerConnection', null, 'MCU feature has been enabled']);
  1040.  
  1041. self._hasMCU = true;
  1042. self._trigger('serverPeerJoined', targetMid, self.SERVER_PEER_TYPE.MCU);
  1043.  
  1044. } else {
  1045. self._trigger('peerJoined', targetMid, self.getPeerInfo(targetMid), false);
  1046. }
  1047.  
  1048. self._trigger('handshakeProgress', self.HANDSHAKE_PROGRESS.ENTER, targetMid);
  1049. }
  1050.  
  1051. self._peerMessagesStamps[targetMid] = self._peerMessagesStamps[targetMid] || {
  1052. userData: 0,
  1053. audioMuted: 0,
  1054. videoMuted: 0
  1055. };
  1056.  
  1057. var welcomeMsg = {
  1058. type: self._SIG_MESSAGE_TYPE.WELCOME,
  1059. mid: self._user.sid,
  1060. rid: self._room.id,
  1061. enableIceTrickle: self._initOptions.enableIceTrickle,
  1062. enableDataChannel: self._initOptions.enableDataChannel,
  1063. enableIceRestart: self._enableIceRestart,
  1064. agent: AdapterJS.webrtcDetectedBrowser,
  1065. version: (AdapterJS.webrtcDetectedVersion || 0).toString(),
  1066. receiveOnly: self.getPeerInfo().config.receiveOnly,
  1067. os: window.navigator.platform,
  1068. userInfo: self._getUserInfo(targetMid),
  1069. target: targetMid,
  1070. weight: self._peerPriorityWeight,
  1071. temasysPluginVersion: AdapterJS.WebRTCPlugin.plugin ? AdapterJS.WebRTCPlugin.plugin.VERSION : null,
  1072. SMProtocolVersion: self.SM_PROTOCOL_VERSION,
  1073. DTProtocolVersion: self.DT_PROTOCOL_VERSION
  1074. };
  1075.  
  1076. if (self._publishOnly) {
  1077. welcomeMsg.publishOnly = {
  1078. type: self._streams.screenshare && self._streams.screenshare.stream ? 'screenshare' : 'video'
  1079. };
  1080. }
  1081.  
  1082. if (self._parentId) {
  1083. welcomeMsg.parentId = self._parentId;
  1084. }
  1085.  
  1086. self._sendChannelMessage(welcomeMsg);
  1087.  
  1088. if (isNewPeer) {
  1089. self._trigger('handshakeProgress', self.HANDSHAKE_PROGRESS.WELCOME, targetMid);
  1090. }
  1091. };
  1092.  
  1093. if (self._peerConnectionConfig.certificate !== self.PEER_CERTIFICATE.AUTO &&
  1094. typeof RTCPeerConnection.generateCertificate === 'function') {
  1095. var certOptions = {};
  1096. if (self._peerConnectionConfig.certificate === self.PEER_CERTIFICATE.ECDSA) {
  1097. certOptions = {
  1098. name: 'ECDSA',
  1099. namedCurve: 'P-256'
  1100. };
  1101. } else {
  1102. certOptions = {
  1103. name: 'RSASSA-PKCS1-v1_5',
  1104. modulusLength: 2048,
  1105. publicExponent: new Uint8Array([1, 0, 1]),
  1106. hash: 'SHA-256'
  1107. };
  1108. }
  1109. RTCPeerConnection.generateCertificate(certOptions).then(function (cert) {
  1110. processPeerFn(cert);
  1111. }, function () {
  1112. processPeerFn();
  1113. });
  1114. } else {
  1115. processPeerFn();
  1116. }
  1117. };
  1118.  
  1119. /**
  1120. * Function that handles the "restart" socket message received.
  1121. * See confluence docs for the "restart" expected properties to be received
  1122. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  1123. * @method _restartHandler
  1124. * @private
  1125. * @for Skylink
  1126. * @since 0.5.6
  1127. */
  1128. Skylink.prototype._restartHandler = function(message){
  1129. var self = this;
  1130. var targetMid = message.mid;
  1131. var userInfo = message.userInfo || {};
  1132. userInfo.settings = userInfo.settings || {};
  1133. userInfo.mediaStatus = userInfo.mediaStatus || {};
  1134. userInfo.config = {
  1135. enableIceTrickle: typeof message.enableIceTrickle === 'boolean' ? message.enableIceTrickle : true,
  1136. enableIceRestart: typeof message.enableIceRestart === 'boolean' ? message.enableIceRestart : false,
  1137. enableDataChannel: typeof message.enableDataChannel === 'boolean' ? message.enableDataChannel : true,
  1138. priorityWeight: typeof message.weight === 'number' ? message.weight : 0,
  1139. receiveOnly: message.receiveOnly === true,
  1140. publishOnly: !!message.publishOnly
  1141. };
  1142. userInfo.parentId = message.parentId || null;
  1143. userInfo.agent = {
  1144. name: typeof message.agent === 'string' && message.agent ? message.agent : 'other',
  1145. version: (function () {
  1146. if (!(message.version && typeof message.version === 'string')) {
  1147. return 0;
  1148. }
  1149. // E.g. 0.9.6, replace minor "." with 0
  1150. if (message.version.indexOf('.') > -1) {
  1151. var parts = message.version.split('.');
  1152. if (parts.length > 2) {
  1153. var majorVer = parts[0] || '0';
  1154. parts.splice(0, 1);
  1155. return parseFloat(majorVer + '.' + parts.join('0'), 10);
  1156. }
  1157. return parseFloat(message.version || '0', 10);
  1158. }
  1159. return parseInt(message.version || '0', 10);
  1160. })(),
  1161. os: typeof message.os === 'string' && message.os ? message.os : '',
  1162. pluginVersion: typeof message.temasysPluginVersion === 'string' && message.temasysPluginVersion ?
  1163. message.temasysPluginVersion : null,
  1164. SMProtocolVersion: message.SMProtocolVersion && typeof message.SMProtocolVersion === 'string' ?
  1165. message.SMProtocolVersion : '0.1.1',
  1166. DTProtocolVersion: message.DTProtocolVersion && typeof message.DTProtocolVersion === 'string' ?
  1167. message.DTProtocolVersion : (self._hasMCU || targetMid === 'MCU' ? '0.1.2' : '0.1.0')
  1168. };
  1169.  
  1170. log.log([targetMid, 'RTCPeerConnection', null, 'Peer "restart" received ->'], message);
  1171.  
  1172. if (!self._peerInformations[targetMid]) {
  1173. log.error([targetMid, 'RTCPeerConnection', null, 'Peer does not have an existing session. Ignoring restart process.']);
  1174. return;
  1175. }
  1176.  
  1177. // Ignore if: User is publishOnly and MCU is enabled
  1178. // : User is parent and parentId is defined and matches
  1179. // : User is child and parent matches
  1180. // Don't if : Is MCU
  1181. if (targetMid !== 'MCU' && ((self._parentId && self._parentId === targetMid) ||
  1182. (self._hasMCU && self._publishOnly) || (message.parentId && self._user && self._user.sid &&
  1183. message.parentId === self._user.sid))) {
  1184. log.warn([targetMid, 'RTCPeerConnection', null, 'Discarding "restart" for parentId or publishOnly case ->'], message);
  1185. return;
  1186. }
  1187.  
  1188. if (self._hasMCU && !self._initOptions.mcuUseRenegoRestart) {
  1189. log.warn([targetMid, 'RTCPeerConnection', null, 'Dropping restart request as MCU does not support re-negotiation. ' +
  1190. 'Restart workaround is to re-join Room for Peer.']);
  1191. self._trigger('peerRestart', targetMid, self.getPeerInfo(targetMid), false, false);
  1192. return;
  1193. }
  1194.  
  1195. self._peerInformations[targetMid] = userInfo;
  1196. self._peerMessagesStamps[targetMid] = self._peerMessagesStamps[targetMid] || {
  1197. userData: 0,
  1198. audioMuted: 0,
  1199. videoMuted: 0
  1200. };
  1201. self._peerEndOfCandidatesCounter[targetMid] = self._peerEndOfCandidatesCounter[targetMid] || {};
  1202. self._peerEndOfCandidatesCounter[targetMid].len = 0;
  1203.  
  1204. // Make peer with highest weight do the offer
  1205. if (self._peerPriorityWeight > message.weight) {
  1206. log.debug([targetMid, 'RTCPeerConnection', null, 'Re-negotiating new offer/answer.']);
  1207.  
  1208. if (self._peerMessagesStamps[targetMid].hasRestart) {
  1209. log.warn([targetMid, 'RTCPeerConnection', null, 'Discarding extra "restart" received.']);
  1210. return;
  1211. }
  1212.  
  1213. self._peerMessagesStamps[targetMid].hasRestart = true;
  1214. self._doOffer(targetMid, message.doIceRestart === true, {
  1215. agent: userInfo.agent.name,
  1216. version: userInfo.agent.version,
  1217. os: userInfo.agent.os
  1218. }, true);
  1219.  
  1220. } else {
  1221. log.debug([targetMid, 'RTCPeerConnection', null, 'Waiting for peer to start re-negotiation.']);
  1222.  
  1223. var restartMsg = {
  1224. type: self._SIG_MESSAGE_TYPE.RESTART,
  1225. mid: self._user.sid,
  1226. rid: self._room.id,
  1227. agent: AdapterJS.webrtcDetectedBrowser,
  1228. version: (AdapterJS.webrtcDetectedVersion || 0).toString(),
  1229. os: window.navigator.platform,
  1230. userInfo: self._getUserInfo(targetMid),
  1231. target: targetMid,
  1232. weight: self._peerPriorityWeight,
  1233. enableIceTrickle: self._initOptions.enableIceTrickle,
  1234. enableDataChannel: self._initOptions.enableDataChannel,
  1235. enableIceRestart: self._enableIceRestart,
  1236. doIceRestart: message.doIceRestart === true,
  1237. receiveOnly: self.getPeerInfo().config.receiveOnly,
  1238. isRestartResend: true,
  1239. temasysPluginVersion: AdapterJS.WebRTCPlugin.plugin ? AdapterJS.WebRTCPlugin.plugin.VERSION : null,
  1240. SMProtocolVersion: self.SM_PROTOCOL_VERSION,
  1241. DTProtocolVersion: self.DT_PROTOCOL_VERSION,
  1242. };
  1243.  
  1244. if (self._publishOnly) {
  1245. restartMsg.publishOnly = {
  1246. type: self._streams.screenshare && self._streams.screenshare.stream ? 'screenshare' : 'video'
  1247. };
  1248. }
  1249.  
  1250. if (self._parentId) {
  1251. restartMsg.parentId = self._parentId;
  1252. }
  1253.  
  1254. self._sendChannelMessage(restartMsg);
  1255. }
  1256.  
  1257. self._trigger('peerRestart', targetMid, self.getPeerInfo(targetMid), false, message.doIceRestart === true);
  1258. };
  1259.  
  1260. /**
  1261. * Function that handles the "welcome" socket message received.
  1262. * See confluence docs for the "welcome" expected properties to be received
  1263. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  1264. * @method _welcomeHandler
  1265. * @private
  1266. * @for Skylink
  1267. * @since 0.5.4
  1268. */
  1269. Skylink.prototype._welcomeHandler = function(message) {
  1270. var self = this;
  1271. var targetMid = message.mid;
  1272. var isNewPeer = false;
  1273. var userInfo = message.userInfo || {};
  1274. userInfo.settings = userInfo.settings || {};
  1275. userInfo.mediaStatus = userInfo.mediaStatus || {};
  1276. userInfo.config = {
  1277. enableIceTrickle: typeof message.enableIceTrickle === 'boolean' ? message.enableIceTrickle : true,
  1278. enableIceRestart: typeof message.enableIceRestart === 'boolean' ? message.enableIceRestart : false,
  1279. enableDataChannel: typeof message.enableDataChannel === 'boolean' ? message.enableDataChannel : true,
  1280. priorityWeight: typeof message.weight === 'number' ? message.weight : 0,
  1281. receiveOnly: message.receiveOnly === true,
  1282. publishOnly: !!message.publishOnly
  1283. };
  1284. userInfo.parentId = message.parentId || null;
  1285. userInfo.agent = {
  1286. name: typeof message.agent === 'string' && message.agent ? message.agent : 'other',
  1287. version: (function () {
  1288. if (!(message.version && typeof message.version === 'string')) {
  1289. return 0;
  1290. }
  1291. // E.g. 0.9.6, replace minor "." with 0
  1292. if (message.version.indexOf('.') > -1) {
  1293. var parts = message.version.split('.');
  1294. if (parts.length > 2) {
  1295. var majorVer = parts[0] || '0';
  1296. parts.splice(0, 1);
  1297. return parseFloat(majorVer + '.' + parts.join('0'), 10);
  1298. }
  1299. return parseFloat(message.version || '0', 10);
  1300. }
  1301. return parseInt(message.version || '0', 10);
  1302. })(),
  1303. os: typeof message.os === 'string' && message.os ? message.os : '',
  1304. pluginVersion: typeof message.temasysPluginVersion === 'string' && message.temasysPluginVersion ?
  1305. message.temasysPluginVersion : null,
  1306. SMProtocolVersion: message.SMProtocolVersion && typeof message.SMProtocolVersion === 'string' ?
  1307. message.SMProtocolVersion : '0.1.1',
  1308. DTProtocolVersion: message.DTProtocolVersion && typeof message.DTProtocolVersion === 'string' ?
  1309. message.DTProtocolVersion : (self._hasMCU || targetMid === 'MCU' ? '0.1.2' : '0.1.0')
  1310. };
  1311.  
  1312. log.log([targetMid, 'RTCPeerConnection', null, 'Peer "welcome" received ->'], message);
  1313.  
  1314. // Ignore if: User is publishOnly and MCU is enabled
  1315. // : User is parent and parentId is defined and matches
  1316. // : User is child and parent matches
  1317. // Don't if : Is MCU
  1318. if (targetMid !== 'MCU' && ((self._parentId && self._parentId === targetMid) ||
  1319. (self._hasMCU && self._publishOnly) || (message.parentId && self._user && self._user.sid &&
  1320. message.parentId === self._user.sid))) {
  1321. log.warn([targetMid, 'RTCPeerConnection', null, 'Discarding "welcome" for parentId or publishOnly case ->'], message);
  1322. return;
  1323. }
  1324.  
  1325. var processPeerFn = function (cert) {
  1326. if (!self._peerInformations[targetMid]) {
  1327. isNewPeer = true;
  1328.  
  1329. self._peerInformations[targetMid] = userInfo;
  1330.  
  1331. var hasScreenshare = userInfo.settings.video && typeof userInfo.settings.video === 'object' &&
  1332. !!userInfo.settings.video.screenshare;
  1333.  
  1334. self._addPeer(targetMid, cert || null, {
  1335. agent: userInfo.agent.name,
  1336. version: userInfo.agent.version,
  1337. os: userInfo.agent.os
  1338. }, message.receiveOnly, hasScreenshare);
  1339.  
  1340. if (targetMid === 'MCU') {
  1341. log.info([targetMid, 'RTCPeerConnection', null, 'MCU feature has been enabled']);
  1342.  
  1343. self._hasMCU = true;
  1344. self._trigger('serverPeerJoined', targetMid, self.SERVER_PEER_TYPE.MCU);
  1345.  
  1346. } else {
  1347. self._trigger('peerJoined', targetMid, self.getPeerInfo(targetMid), false);
  1348. }
  1349.  
  1350. self._trigger('handshakeProgress', self.HANDSHAKE_PROGRESS.ENTER, targetMid);
  1351. self._trigger('handshakeProgress', self.HANDSHAKE_PROGRESS.WELCOME, targetMid);
  1352. }
  1353.  
  1354. self._peerMessagesStamps[targetMid] = self._peerMessagesStamps[targetMid] || {
  1355. userData: 0,
  1356. audioMuted: 0,
  1357. videoMuted: 0,
  1358. hasWelcome: false
  1359. };
  1360.  
  1361. if (self._hasMCU || self._peerPriorityWeight > message.weight) {
  1362. if (self._peerMessagesStamps[targetMid].hasWelcome) {
  1363. log.warn([targetMid, 'RTCPeerConnection', null, 'Discarding extra "welcome" received.']);
  1364. return;
  1365. }
  1366.  
  1367. log.debug([targetMid, 'RTCPeerConnection', null, 'Starting negotiation']);
  1368.  
  1369. self._peerMessagesStamps[targetMid].hasWelcome = true;
  1370. self._doOffer(targetMid, false, {
  1371. agent: userInfo.agent.name,
  1372. version: userInfo.agent.version,
  1373. os: userInfo.agent.os
  1374. }, true);
  1375.  
  1376. } else {
  1377. log.debug([targetMid, 'RTCPeerConnection', null, 'Waiting for peer to start negotiation.']);
  1378.  
  1379. var welcomeMsg = {
  1380. type: self._SIG_MESSAGE_TYPE.WELCOME,
  1381. mid: self._user.sid,
  1382. rid: self._room.id,
  1383. enableIceTrickle: self._initOptions.enableIceTrickle,
  1384. enableDataChannel: self._initOptions.enableDataChannel,
  1385. enableIceRestart: self._enableIceRestart,
  1386. receiveOnly: self.getPeerInfo().config.receiveOnly,
  1387. agent: AdapterJS.webrtcDetectedBrowser,
  1388. version: (AdapterJS.webrtcDetectedVersion || 0).toString(),
  1389. os: window.navigator.platform,
  1390. userInfo: self._getUserInfo(targetMid),
  1391. target: targetMid,
  1392. weight: self._peerPriorityWeight,
  1393. temasysPluginVersion: AdapterJS.WebRTCPlugin.plugin ? AdapterJS.WebRTCPlugin.plugin.VERSION : null,
  1394. SMProtocolVersion: self.SM_PROTOCOL_VERSION,
  1395. DTProtocolVersion: self.DT_PROTOCOL_VERSION
  1396. };
  1397.  
  1398. if (self._publishOnly) {
  1399. welcomeMsg.publishOnly = {
  1400. type: self._streams.screenshare && self._streams.screenshare.stream ? 'screenshare' : 'video'
  1401. };
  1402. }
  1403.  
  1404. if (self._parentId) {
  1405. welcomeMsg.parentId = self._parentId;
  1406. }
  1407.  
  1408. self._sendChannelMessage(welcomeMsg);
  1409. }
  1410. };
  1411.  
  1412. if (self._peerConnectionConfig.certificate !== self.PEER_CERTIFICATE.AUTO &&
  1413. typeof RTCPeerConnection.generateCertificate === 'function') {
  1414. var certOptions = {};
  1415. if (self._peerConnectionConfig.certificate === self.PEER_CERTIFICATE.ECDSA) {
  1416. certOptions = {
  1417. name: 'ECDSA',
  1418. namedCurve: 'P-256'
  1419. };
  1420. } else {
  1421. certOptions = {
  1422. name: 'RSASSA-PKCS1-v1_5',
  1423. modulusLength: 2048,
  1424. publicExponent: new Uint8Array([1, 0, 1]),
  1425. hash: 'SHA-256'
  1426. };
  1427. }
  1428. RTCPeerConnection.generateCertificate(certOptions).then(function (cert) {
  1429. processPeerFn(cert);
  1430. }, function () {
  1431. processPeerFn();
  1432. });
  1433. } else {
  1434. processPeerFn();
  1435. }
  1436. };
  1437.  
  1438. /**
  1439. * Function that handles the "offer" socket message received.
  1440. * See confluence docs for the "offer" expected properties to be received
  1441. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  1442. * @method _offerHandler
  1443. * @private
  1444. * @for Skylink
  1445. * @since 0.5.1
  1446. */
  1447. Skylink.prototype._offerHandler = function(message) {
  1448. var self = this;
  1449. var targetMid = message.mid;
  1450. var pc = self._peerConnections[targetMid];
  1451.  
  1452. if (!pc) {
  1453. log.error([targetMid, null, message.type, 'Peer connection object ' +
  1454. 'not found. Unable to setRemoteDescription for offer']);
  1455. return;
  1456. }
  1457.  
  1458. /*if (pc.localDescription ? !!pc.localDescription.sdp : false) {
  1459. log.warn([targetMid, null, message.type, 'Peer has an existing connection'],
  1460. pc.localDescription);
  1461. return;
  1462. }*/
  1463.  
  1464. // Add-on by Web SDK fixes
  1465. if (message.userInfo && typeof message.userInfo === 'object') {
  1466. var userInfo = message.userInfo || {};
  1467.  
  1468. self._peerInformations[targetMid].settings = userInfo.settings || {};
  1469. self._peerInformations[targetMid].mediaStatus = userInfo.mediaStatus || {};
  1470. self._peerInformations[targetMid].userData = userInfo.userData;
  1471. }
  1472.  
  1473. log.log([targetMid, null, message.type, 'Received offer from peer. ' +
  1474. 'Session description:'], clone(message));
  1475.  
  1476. var offer = {
  1477. type: 'offer',
  1478. sdp: self._hasMCU ? message.sdp.replace(/\r\n/g, '\n').split('\n').join('\r\n') : message.sdp
  1479. };
  1480. log.log([targetMid, 'RTCSessionDescription', message.type,
  1481. 'Session description object created'], offer);
  1482.  
  1483. offer.sdp = self._removeSDPFilteredCandidates(targetMid, offer);
  1484. offer.sdp = self._setSDPCodec(targetMid, offer);
  1485. offer.sdp = self._setSDPBitrate(targetMid, offer);
  1486. offer.sdp = self._setSDPCodecParams(targetMid, offer);
  1487. offer.sdp = self._removeSDPCodecs(targetMid, offer);
  1488. offer.sdp = self._removeSDPREMBPackets(targetMid, offer);
  1489. offer.sdp = self._handleSDPConnectionSettings(targetMid, offer, 'remote');
  1490. offer.sdp = self._removeSDPUnknownAptRtx(targetMid, offer);
  1491.  
  1492. log.log([targetMid, 'RTCSessionDescription', message.type, 'Updated remote offer ->'], offer.sdp);
  1493.  
  1494. // This is always the initial state. or even after negotiation is successful
  1495. if (pc.signalingState !== self.PEER_CONNECTION_STATE.STABLE) {
  1496. log.warn([targetMid, null, message.type, 'Peer connection state is not in ' +
  1497. '"stable" state for re-negotiation. Dropping message.'], {
  1498. signalingState: pc.signalingState,
  1499. isRestart: !!message.resend
  1500. });
  1501. return;
  1502. }
  1503.  
  1504. // Added checks if there is a current remote sessionDescription being processing before processing this one
  1505. if (pc.processingRemoteSDP) {
  1506. log.warn([targetMid, 'RTCSessionDescription', 'offer',
  1507. 'Dropping of setting local offer as there is another ' +
  1508. 'sessionDescription being processed ->'], offer);
  1509. return;
  1510. }
  1511.  
  1512. pc.processingRemoteSDP = true;
  1513.  
  1514. if (message.userInfo) {
  1515. self._trigger('peerUpdated', targetMid, self.getPeerInfo(targetMid), false);
  1516. }
  1517.  
  1518. self._parseSDPMediaStreamIDs(targetMid, offer);
  1519.  
  1520. var onSuccessCbFn = function() {
  1521. log.debug([targetMid, 'RTCSessionDescription', message.type, 'Remote description set']);
  1522. pc.setOffer = 'remote';
  1523. pc.processingRemoteSDP = false;
  1524.  
  1525. self._trigger('handshakeProgress', self.HANDSHAKE_PROGRESS.OFFER, targetMid);
  1526. self._addIceCandidateFromQueue(targetMid);
  1527. self._doAnswer(targetMid);
  1528. };
  1529.  
  1530. var onErrorCbFn = function(error) {
  1531. self._trigger('handshakeProgress', self.HANDSHAKE_PROGRESS.ERROR, targetMid, error);
  1532.  
  1533. pc.processingRemoteSDP = false;
  1534.  
  1535. log.error([targetMid, null, message.type, 'Failed setting remote description:'], {
  1536. error: error,
  1537. state: pc.signalingState,
  1538. offer: offer
  1539. });
  1540. };
  1541.  
  1542. pc.setRemoteDescription(new RTCSessionDescription(offer), onSuccessCbFn, onErrorCbFn);
  1543. };
  1544.  
  1545.  
  1546. /**
  1547. * Function that handles the "candidate" socket message received.
  1548. * See confluence docs for the "candidate" expected properties to be received
  1549. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  1550. * @method _candidateHandler
  1551. * @private
  1552. * @for Skylink
  1553. * @since 0.5.1
  1554. */
  1555. Skylink.prototype._candidateHandler = function(message) {
  1556. var targetMid = message.mid;
  1557.  
  1558. if (!message.candidate && !message.id) {
  1559. log.warn([targetMid, 'RTCIceCandidate', null, 'Received invalid ICE candidate message ->'], message);
  1560. return;
  1561. }
  1562.  
  1563. var canId = 'can-' + (new Date()).getTime();
  1564. var candidateType = message.candidate.split(' ')[7] || '';
  1565. var candidate = new RTCIceCandidate({
  1566. sdpMLineIndex: message.label,
  1567. candidate: message.candidate,
  1568. sdpMid: message.id
  1569. });
  1570.  
  1571. log.debug([targetMid, 'RTCIceCandidate', canId + ':' + candidateType, 'Received ICE candidate ->'], candidate);
  1572.  
  1573. this._peerEndOfCandidatesCounter[targetMid] = this._peerEndOfCandidatesCounter[targetMid] || {};
  1574. this._peerEndOfCandidatesCounter[targetMid].len = this._peerEndOfCandidatesCounter[targetMid].len || 0;
  1575. this._peerEndOfCandidatesCounter[targetMid].hasSet = false;
  1576. this._peerEndOfCandidatesCounter[targetMid].len++;
  1577.  
  1578. this._trigger('candidateProcessingState', this.CANDIDATE_PROCESSING_STATE.RECEIVED,
  1579. targetMid, canId, candidateType, {
  1580. candidate: candidate.candidate,
  1581. sdpMid: candidate.sdpMid,
  1582. sdpMLineIndex: candidate.sdpMLineIndex
  1583. }, null);
  1584.  
  1585. if (!(this._peerConnections[targetMid] &&
  1586. this._peerConnections[targetMid].signalingState !== this.PEER_CONNECTION_STATE.CLOSED)) {
  1587. log.warn([targetMid, 'RTCIceCandidate', canId + ':' + candidateType, 'Dropping ICE candidate ' +
  1588. 'as Peer connection does not exists or is closed']);
  1589. this._trigger('candidateProcessingState', this.CANDIDATE_PROCESSING_STATE.DROPPED,
  1590. targetMid, canId, candidateType, {
  1591. candidate: candidate.candidate,
  1592. sdpMid: candidate.sdpMid,
  1593. sdpMLineIndex: candidate.sdpMLineIndex
  1594. }, new Error('Failed processing ICE candidate as Peer connection does not exists or is closed.'));
  1595. this._signalingEndOfCandidates(targetMid);
  1596. return;
  1597. }
  1598.  
  1599. if (this._initOptions.filterCandidatesType[candidateType]) {
  1600. if (!(this._hasMCU && this._initOptions.forceTURN)) {
  1601. log.warn([targetMid, 'RTCIceCandidate', canId + ':' + candidateType, 'Dropping received ICE candidate as ' +
  1602. 'it matches ICE candidate filtering flag ->'], candidate);
  1603. this._trigger('candidateProcessingState', this.CANDIDATE_PROCESSING_STATE.DROPPED,
  1604. targetMid, canId, candidateType, {
  1605. candidate: candidate.candidate,
  1606. sdpMid: candidate.sdpMid,
  1607. sdpMLineIndex: candidate.sdpMLineIndex
  1608. }, new Error('Dropping of processing ICE candidate as it matches ICE candidate filtering flag.'));
  1609. this._signalingEndOfCandidates(targetMid);
  1610. return;
  1611. }
  1612.  
  1613. log.warn([targetMid, 'RTCIceCandidate', canId + ':' + candidateType, 'Not dropping received ICE candidate as ' +
  1614. 'TURN connections are enforced as MCU is present (and act as a TURN itself) so filtering of ICE candidate ' +
  1615. 'flags are not honoured ->'], candidate);
  1616. }
  1617.  
  1618. if (this._peerConnections[targetMid].remoteDescription && this._peerConnections[targetMid].remoteDescription.sdp &&
  1619. this._peerConnections[targetMid].localDescription && this._peerConnections[targetMid].localDescription.sdp) {
  1620. this._addIceCandidate(targetMid, canId, candidate);
  1621. } else {
  1622. this._addIceCandidateToQueue(targetMid, canId, candidate);
  1623. }
  1624.  
  1625. this._signalingEndOfCandidates(targetMid);
  1626.  
  1627. if (!this._gatheredCandidates[targetMid]) {
  1628. this._gatheredCandidates[targetMid] = {
  1629. sending: { host: [], srflx: [], relay: [] },
  1630. receiving: { host: [], srflx: [], relay: [] }
  1631. };
  1632. }
  1633.  
  1634. this._gatheredCandidates[targetMid].receiving[candidateType].push({
  1635. sdpMid: candidate.sdpMid,
  1636. sdpMLineIndex: candidate.sdpMLineIndex,
  1637. candidate: candidate.candidate
  1638. });
  1639. };
  1640.  
  1641. /**
  1642. * Function that handles the "answer" socket message received.
  1643. * See confluence docs for the "answer" expected properties to be received
  1644. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  1645. * @method _answerHandler
  1646. * @private
  1647. * @for Skylink
  1648. * @since 0.5.1
  1649. */
  1650. Skylink.prototype._answerHandler = function(message) {
  1651. var self = this;
  1652. var targetMid = message.mid;
  1653.  
  1654. log.log([targetMid, null, message.type,
  1655. 'Received answer from peer. Session description:'], clone(message));
  1656.  
  1657. var pc = self._peerConnections[targetMid];
  1658.  
  1659. if (!pc) {
  1660. log.error([targetMid, null, message.type, 'Peer connection object ' +
  1661. 'not found. Unable to setRemoteDescription for answer']);
  1662. return;
  1663. }
  1664.  
  1665. // Add-on by Web SDK fixes
  1666. if (message.userInfo && typeof message.userInfo === 'object') {
  1667. var userInfo = message.userInfo || {};
  1668.  
  1669. self._peerInformations[targetMid].settings = userInfo.settings || {};
  1670. self._peerInformations[targetMid].mediaStatus = userInfo.mediaStatus || {};
  1671. self._peerInformations[targetMid].userData = userInfo.userData;
  1672. }
  1673.  
  1674. var answer = {
  1675. type: 'answer',
  1676. sdp: self._hasMCU ? message.sdp.replace(/\r\n/g, '\n').split('\n').join('\r\n') : message.sdp
  1677. };
  1678.  
  1679. log.log([targetMid, 'RTCSessionDescription', message.type,
  1680. 'Session description object created'], answer);
  1681.  
  1682. /*if (pc.remoteDescription ? !!pc.remoteDescription.sdp : false) {
  1683. log.warn([targetMid, null, message.type, 'Peer has an existing connection'],
  1684. pc.remoteDescription);
  1685. return;
  1686. }
  1687.  
  1688. if (pc.signalingState === self.PEER_CONNECTION_STATE.STABLE) {
  1689. log.error([targetMid, null, message.type, 'Unable to set peer connection ' +
  1690. 'at signalingState "stable". Ignoring remote answer'], pc.signalingState);
  1691. return;
  1692. }*/
  1693.  
  1694. answer.sdp = self._removeSDPFilteredCandidates(targetMid, answer);
  1695. answer.sdp = self._setSDPCodec(targetMid, answer);
  1696. answer.sdp = self._setSDPBitrate(targetMid, answer);
  1697. answer.sdp = self._setSDPCodecParams(targetMid, answer);
  1698. answer.sdp = self._removeSDPCodecs(targetMid, answer);
  1699. answer.sdp = self._removeSDPREMBPackets(targetMid, answer);
  1700. answer.sdp = self._handleSDPConnectionSettings(targetMid, answer, 'remote');
  1701. answer.sdp = self._removeSDPUnknownAptRtx(targetMid, answer);
  1702.  
  1703. log.log([targetMid, 'RTCSessionDescription', message.type, 'Updated remote answer ->'], answer.sdp);
  1704.  
  1705.  
  1706. // This should be the state after offer is received. or even after negotiation is successful
  1707. if (pc.signalingState !== self.PEER_CONNECTION_STATE.HAVE_LOCAL_OFFER) {
  1708. log.warn([targetMid, null, message.type, 'Peer connection state is not in ' +
  1709. '"have-local-offer" state for re-negotiation. Dropping message.'], {
  1710. signalingState: pc.signalingState,
  1711. isRestart: !!message.restart
  1712. });
  1713. return;
  1714. }
  1715.  
  1716. // Added checks if there is a current remote sessionDescription being processing before processing this one
  1717. if (pc.processingRemoteSDP) {
  1718. log.warn([targetMid, 'RTCSessionDescription', 'answer',
  1719. 'Dropping of setting local answer as there is another ' +
  1720. 'sessionDescription being processed ->'], answer);
  1721. return;
  1722. }
  1723.  
  1724. pc.processingRemoteSDP = true;
  1725.  
  1726. if (message.userInfo) {
  1727. self._trigger('peerUpdated', targetMid, self.getPeerInfo(targetMid), false);
  1728. }
  1729.  
  1730. self._parseSDPMediaStreamIDs(targetMid, answer);
  1731.  
  1732. function changeSDPFormat(sdp){
  1733. if(sdp.indexOf("m=video")>sdp.indexOf("m=audio")){
  1734. var sIndex = sdp.indexOf("m=video");
  1735. var eIndex = sdp.lastIndexOf("m=");
  1736. var mVideoLineStr = sdp.substring(sIndex,eIndex)+"m=audio";
  1737. sdp = sdp.replace(sdp.substring(sIndex, eIndex), "");
  1738. sdp = sdp.replace("m=audio", mVideoLineStr);
  1739. }
  1740. return sdp;
  1741. }
  1742.  
  1743. if (self._hasMCU && targetMid !== 'MCU'
  1744. && AdapterJS.webrtcDetectedBrowser === 'firefox'
  1745. && AdapterJS.webrtcDetectedVersion >= 59) {
  1746. answer.sdp = changeSDPFormat(answer.sdp);
  1747. }
  1748.  
  1749. var onSuccessCbFn = function() {
  1750. log.debug([targetMid, null, message.type, 'Remote description set']);
  1751. pc.setAnswer = 'remote';
  1752. pc.processingRemoteSDP = false;
  1753.  
  1754. self._trigger('handshakeProgress', self.HANDSHAKE_PROGRESS.ANSWER, targetMid);
  1755. self._addIceCandidateFromQueue(targetMid);
  1756.  
  1757. if (self._peerMessagesStamps[targetMid]) {
  1758. self._peerMessagesStamps[targetMid].hasRestart = false;
  1759. }
  1760.  
  1761. if (self._dataChannels[targetMid] && (pc.remoteDescription.sdp.indexOf('m=application') === -1 ||
  1762. pc.remoteDescription.sdp.indexOf('m=application 0') > 0)) {
  1763. log.warn([targetMid, 'RTCPeerConnection', null, 'Closing all datachannels as they were rejected.']);
  1764. self._closeDataChannel(targetMid);
  1765. }
  1766. };
  1767.  
  1768. var onErrorCbFn = function(error) {
  1769. self._trigger('handshakeProgress', self.HANDSHAKE_PROGRESS.ERROR, targetMid, error);
  1770.  
  1771. pc.processingRemoteSDP = false;
  1772.  
  1773. log.error([targetMid, null, message.type, 'Failed setting remote description:'], {
  1774. error: error,
  1775. state: pc.signalingState,
  1776. answer: answer
  1777. });
  1778. };
  1779.  
  1780. pc.setRemoteDescription(new RTCSessionDescription(answer), onSuccessCbFn, onErrorCbFn);
  1781. };
  1782.  
  1783. /**
  1784. * Function that compares the SM / DT protocol versions to see if it in the version.
  1785. * @method _isLowerThanVersion
  1786. * @private
  1787. * @for Skylink
  1788. * @since 0.6.16
  1789. */
  1790. Skylink.prototype._isLowerThanVersion = function (agentVer, requiredVer) {
  1791. var partsA = (agentVer || '').split('.');
  1792. var partsB = (requiredVer || '').split('.');
  1793.  
  1794. for (var i = 0; i < partsB.length; i++) {
  1795. if ((partsA[i] || '0') < (partsB[i] || '0')) {
  1796. return true;
  1797. }
  1798. }
  1799.  
  1800. return false;
  1801. };
  1802.