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._hasMCU && !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. self._handleRecordingStats('error-no-mcu-start', null, null, noMCUError);
  156. if (typeof callback === 'function') {
  157. callback(new Error(noMCUError), null);
  158. }
  159. return;
  160. }
  161.  
  162. if (self._currentRecordingId) {
  163. var hasRecordingSessionError = 'Unable to start recording as there is an existing recording in-progress';
  164. log.error(hasRecordingSessionError);
  165. // TO CHECK: We added new state type "error-start-when-active".
  166. self._handleRecordingStats('error-start-when-active', self._currentRecordingId, null, hasRecordingSessionError);
  167. if (typeof callback === 'function') {
  168. callback(new Error(hasRecordingSessionError), null);
  169. }
  170. return;
  171. }
  172.  
  173. if (typeof callback === 'function') {
  174. self.once('recordingState', function (state, recordingId) {
  175. callback(null, recordingId);
  176. }, function (state) {
  177. return state === self.RECORDING_STATE.START;
  178. });
  179. }
  180.  
  181. self._sendChannelMessage({
  182. type: self._SIG_MESSAGE_TYPE.START_RECORDING,
  183. rid: self._room.id,
  184. target: 'MCU'
  185. });
  186.  
  187. self._handleRecordingStats('request-start');
  188.  
  189. log.debug(['MCU', 'Recording', null, 'Starting recording']);
  190. };
  191.  
  192. /**
  193. * <blockquote class="info">
  194. * Note that this feature requires MCU and recording to be enabled for the App Key provided in the
  195. * <a href="#method_init"><code>init()</code> method</a>. If recording feature is not available to
  196. * be enabled in the <a href="https://console.temasys.io">Developer Console</a>, please
  197. * <a href="http://support.temasys.io">contact us on our support portal</a>.
  198. * </blockquote>
  199. * Stops a recording session.
  200. * @param {Function} [callback] The callback function fired when request has completed.
  201. * <small>Function parameters signature is <code>function (error, success)</code></small>
  202. * <small>Function request completion is determined by the <a href="#event_recordingState">
  203. * <code>recordingState</code> event</a> triggering <code>state</code> parameter payload as <code>STOP</code>
  204. * or as <code>LINK</code> when the value of <code>callbackSuccessWhenLink</code> is <code>true</code>.</small>
  205. * @param {Error|String} callback.error The error result in request.
  206. * <small>Defined as <code>null</code> when there are no errors in request</small>
  207. * <small>Object signature is the <code>stopRecording()</code> error when stopping current recording session.</small>
  208. * @param {String|JSON} callback.success The success result in request.
  209. * - When <code>callbackSuccessWhenLink</code> value is <code>false</code>, it is defined as string as
  210. * the recording session ID.
  211. * - when <code>callbackSuccessWhenLink</code> value is <code>true</code>, it is defined as an object as
  212. * the recording session information.
  213. * <small>Defined as <code>null</code> when there are errors in request</small>
  214. * @param {JSON} callback.success.recordingId The recording session ID.
  215. * @param {JSON} callback.success.link The recording session mixin videos link in
  216. * <a href="https://en.wikipedia.org/wiki/MPEG-4_Part_14">MP4</a> format.
  217. * <small>Object signature matches the <code>link</code> parameter payload received in the
  218. * <a href="#event_recordingState"><code>recordingState</code> event</a>.</small>
  219. * @param {Boolean} [callbackSuccessWhenLink=false] The flag if <code>callback</code> function provided
  220. * should result in success only when <a href="#event_recordingState"><code>recordingState</code> event</a>
  221. * triggering <code>state</code> parameter payload as <code>LINK</code>.
  222. * @method stopRecording
  223. * @example
  224. * // Example 1: Stop recording session
  225. * skylinkDemo.stopRecording(function (error, success) {
  226. * if (error) return;
  227. * console.info("Recording session has stopped. ID ->", success);
  228. * });
  229. *
  230. * // Example 2: Stop recording session with mixin videos link
  231. * skylinkDemo.stopRecording(function (error, success) {
  232. * if (error) return;
  233. * console.info("Recording session has compiled with links ->", success.link);
  234. * }, true);
  235. * @trigger <ol class="desc-seq">
  236. * <li>If MCU is not connected: <ol><li><b>ABORT</b> and return error.</li></ol></li>
  237. * <li>If there is no existing recording session currently going on: <ol>
  238. * <li><b>ABORT</b> and return error.</li></ol></li>
  239. * <li>If existing recording session recording time has not elapsed more than 4 seconds:
  240. * <small>4 seconds is mandatory for recording session to ensure better recording
  241. * experience and stability.</small> <ol><li><b>ABORT</b> and return error.</li></ol></li>
  242. * <li>Sends to MCU via Signaling server to stop recording session: <ol>
  243. * <li>If recording session has been stopped successfully: <ol>
  244. * <li><a href="#event_recordingState"><code>recordingState</code> event</a>
  245. * triggers parameter payload <code>state</code> as <code>START</code>.
  246. * <li>MCU starts mixin recorded session videos: <ol>
  247. * <li>If recording session has been mixin successfully with links: <ol>
  248. * <li><a href="#event_recordingState"><code>recordingState</code> event</a> triggers
  249. * parameter payload <code>state</code> as <code>LINK</code>.<li>Else: <ol>
  250. * <li><a href="#event_recordingState"><code>recordingState</code> event</a> triggers
  251. * parameter payload <code>state</code> as <code>ERROR</code>.<li><b>ABORT</b> and return error.</ol></li>
  252. * </ol></li></ol></li><li>Else: <ol>
  253. * <li><a href="#event_recordingState"><code>recordingState</code> event</a>
  254. * triggers parameter payload <code>state</code> as <code>ERROR</code>.</li><li><b>ABORT</b> and return error.</li>
  255. * </ol></li></ol></li></ol>
  256. * @beta
  257. * @for Skylink
  258. * @since 0.6.16
  259. */
  260. Skylink.prototype.stopRecording = function (callback) {
  261. var self = this;
  262.  
  263. if (!self._hasMCU) {
  264. var noMCUError = 'Unable to stop recording as MCU is not connected';
  265. log.error(noMCUError);
  266. self._handleRecordingStats('error-no-mcu-stop', null, null, noMCUError);
  267. if (typeof callback === 'function') {
  268. callback(new Error(noMCUError), null);
  269. }
  270. return;
  271. }
  272.  
  273. if (!self._currentRecordingId) {
  274. var noRecordingSessionError = 'Unable to stop recording as there is no recording in-progress';
  275. log.error(noRecordingSessionError);
  276. // TO CHECK: We added new state type "error-stop-when-inactive".
  277. self._handleRecordingStats('error-stop-when-inactive', null, null, noRecordingSessionError);
  278. if (typeof callback === 'function') {
  279. callback(new Error(noRecordingSessionError), null);
  280. }
  281. return;
  282. }
  283.  
  284. if (self._recordingStartInterval) {
  285. var recordingSecsRequiredError = 'Unable to stop recording as 4 seconds has not been recorded yet';
  286. log.error(recordingSecsRequiredError);
  287. self._handleRecordingStats('error-min-stop', self._currentRecordingId, null, recordingSecsRequiredError);
  288. if (typeof callback === 'function') {
  289. callback(new Error(recordingSecsRequiredError), null);
  290. }
  291. return;
  292. }
  293.  
  294. if (typeof callback === 'function') {
  295. var expectedRecordingId = self._currentRecordingId;
  296.  
  297. self.once('recordingState', function (state, recordingId, link, error) {
  298. callback(null, recordingId);
  299.  
  300. }, function (state, recordingId) {
  301. if (expectedRecordingId === recordingId) {
  302. return state === self.RECORDING_STATE.STOP;
  303. }
  304. });
  305. }
  306.  
  307. self._sendChannelMessage({
  308. type: self._SIG_MESSAGE_TYPE.STOP_RECORDING,
  309. rid: self._room.id,
  310. target: 'MCU'
  311. });
  312.  
  313. self._handleRecordingStats('request-stop', self._currentRecordingId);
  314.  
  315. log.debug(['MCU', 'Recording', null, 'Stopping recording']);
  316. };
  317.  
  318. /**
  319. * <blockquote class="info">
  320. * Note that this feature requires MCU and recording to be enabled for the App Key provided in the
  321. * <a href="#method_init"><code>init()</code> method</a>. If recording feature is not available to
  322. * be enabled in the <a href="https://console.temasys.io">Developer Console</a>, please
  323. * <a href="http://support.temasys.io">contact us on our support portal</a>.
  324. * </blockquote>
  325. * Gets the list of current recording sessions since User has connected to the Room.
  326. * @method getRecordings
  327. * @return {JSON} The list of recording sessions.<ul>
  328. * <li><code>#recordingId</code><var><b>{</b>JSON<b>}</b></var><p>The recording session.</p><ul>
  329. * <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>
  330. * <li><code>state</code><var><b>{</b>Number<b>}</b></var><p>The current recording state. [Rel: Skylink.RECORDING_STATE]</p></li>
  331. * <li><code>startedDateTime</code><var><b>{</b>String<b>}</b></var><p>The recording session started DateTime in
  332. * <a href="https://en.wikipedia.org/wiki/ISO_8601">ISO 8601 format</a>.<small>Note that this value may not be
  333. * very accurate as this value is recorded when the start event is received.</small></p></li>
  334. * <li><code>endedDateTime</code><var><b>{</b>String<b>}</b></var><p>The recording session ended DateTime in
  335. * <a href="https://en.wikipedia.org/wiki/ISO_8601">ISO 8601 format</a>.<small>Note that this value may not be
  336. * very accurate as this value is recorded when the stop event is received.</small>
  337. * <small>Defined only after <code>state</code> has triggered <code>STOP</code>.</small></p></li>
  338. * <li><code>mixingDateTime</code><var><b>{</b>String<b>}</b></var><p>The recording session mixing completed DateTime in
  339. * <a href="https://en.wikipedia.org/wiki/ISO_8601">ISO 8601 format</a>.<small>Note that this value may not be
  340. * very accurate as this value is recorded when the mixing completed event is received.</small>
  341. * <small>Defined only when <code>state</code> is <code>LINK</code>.</small></p></li>
  342. * <li><code>links</code><var><b>{</b>JSON<b>}</b></var><p>The recording session links.
  343. * <small>Object signature matches the <code>link</code> parameter payload received in the
  344. * <a href="#event_recordingState"><code>recordingState</code> event</a>.</small>
  345. * <small>Defined only when <code>state</code> is <code>LINK</code>.</small></p></li>
  346. * <li><code>error</code><var><b>{</b>Error<b>}</b></var><p>The recording session error.
  347. * <small>Defined only when <code>state</code> is <code>ERROR</code>.</small></p></li></ul></li></ul>
  348. * @example
  349. * // Example 1: Get recording sessions
  350. * skylinkDemo.getRecordings();
  351. * @beta
  352. * @for Skylink
  353. * @since 0.6.16
  354. */
  355. Skylink.prototype.getRecordings = function () {
  356. return clone(this._recordings);
  357. };
  358.  
  359. /**
  360. * Function that handles and processes the socket message received.
  361. * @method _processSigMessage
  362. * @private
  363. * @for Skylink
  364. * @since 0.1.0
  365. */
  366. Skylink.prototype._processSigMessage = function(message, session) {
  367. var origin = message.mid;
  368. if (!origin || origin === this._user.sid) {
  369. origin = 'Server';
  370. }
  371. log.debug([origin, 'Socket', message.type, 'Received from peer ->'], clone(message));
  372. if (message.mid === this._user.sid &&
  373. message.type !== this._SIG_MESSAGE_TYPE.REDIRECT &&
  374. message.type !== this._SIG_MESSAGE_TYPE.IN_ROOM) {
  375. log.debug([origin, 'Socket', message.type, 'Ignoring message ->'], clone(message));
  376. return;
  377. }
  378. switch (message.type) {
  379. //--- BASIC API Messages ----
  380. case this._SIG_MESSAGE_TYPE.PUBLIC_MESSAGE:
  381. this._publicMessageHandler(message);
  382. break;
  383. case this._SIG_MESSAGE_TYPE.PRIVATE_MESSAGE:
  384. this._privateMessageHandler(message);
  385. break;
  386. case this._SIG_MESSAGE_TYPE.IN_ROOM:
  387. this._inRoomHandler(message);
  388. break;
  389. case this._SIG_MESSAGE_TYPE.ENTER:
  390. this._enterHandler(message);
  391. break;
  392. case this._SIG_MESSAGE_TYPE.WELCOME:
  393. this._welcomeHandler(message);
  394. break;
  395. case this._SIG_MESSAGE_TYPE.RESTART:
  396. this._restartHandler(message);
  397. break;
  398. case this._SIG_MESSAGE_TYPE.OFFER:
  399. this._offerHandler(message);
  400. break;
  401. case this._SIG_MESSAGE_TYPE.ANSWER:
  402. this._answerHandler(message);
  403. break;
  404. case this._SIG_MESSAGE_TYPE.CANDIDATE:
  405. this._candidateHandler(message);
  406. break;
  407. case this._SIG_MESSAGE_TYPE.BYE:
  408. this._byeHandler(message);
  409. break;
  410. case this._SIG_MESSAGE_TYPE.REDIRECT:
  411. this._redirectHandler(message);
  412. break;
  413. //--- ADVANCED API Messages ----
  414. case this._SIG_MESSAGE_TYPE.UPDATE_USER:
  415. this._updateUserEventHandler(message);
  416. break;
  417. case this._SIG_MESSAGE_TYPE.MUTE_VIDEO:
  418. this._muteVideoEventHandler(message);
  419. break;
  420. case this._SIG_MESSAGE_TYPE.MUTE_AUDIO:
  421. this._muteAudioEventHandler(message);
  422. break;
  423. case this._SIG_MESSAGE_TYPE.STREAM:
  424. this._streamEventHandler(message);
  425. break;
  426. case this._SIG_MESSAGE_TYPE.ROOM_LOCK:
  427. this._roomLockEventHandler(message);
  428. break;
  429. case this._SIG_MESSAGE_TYPE.PEER_LIST:
  430. this._peerListEventHandler(message);
  431. break;
  432. case this._SIG_MESSAGE_TYPE.INTRODUCE_ERROR:
  433. this._introduceErrorEventHandler(message);
  434. break;
  435. case this._SIG_MESSAGE_TYPE.APPROACH:
  436. this._approachEventHandler(message);
  437. break;
  438. case this._SIG_MESSAGE_TYPE.RECORDING:
  439. this._recordingEventHandler(message);
  440. break;
  441. case this._SIG_MESSAGE_TYPE.RTMP:
  442. this._rtmpEventHandler(message);
  443. break;
  444. case this._SIG_MESSAGE_TYPE.END_OF_CANDIDATES:
  445. this._endOfCandidatesHandler(message);
  446. break;
  447. default:
  448. log.error([message.mid, 'Socket', message.type, 'Unsupported message ->'], clone(message));
  449. break;
  450. }
  451. };
  452.  
  453. /**
  454. * Function that handles the "peerList" socket message received.
  455. * See confluence docs for the "peerList" expected properties to be received
  456. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  457. * @method _peerListEventHandler
  458. * @private
  459. * @for Skylink
  460. * @since 0.6.1
  461. */
  462. Skylink.prototype._peerListEventHandler = function(message){
  463. var self = this;
  464. self._peerList = message.result;
  465. log.log(['Server', null, message.type, 'Received list of peers'], self._peerList);
  466. self._trigger('getPeersStateChange',self.GET_PEERS_STATE.RECEIVED, self._user.sid, self._peerList);
  467. };
  468.  
  469. /**
  470. * Function that handles the "endOfCandidates" socket message received.
  471. * See confluence docs for the "endOfCandidates" expected properties to be received
  472. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  473. * @method _endOfCandidatesHandler
  474. * @private
  475. * @for Skylink
  476. * @since 0.6.1
  477. */
  478. Skylink.prototype._endOfCandidatesHandler = function(message){
  479. var self = this;
  480. var targetMid = message.mid;
  481.  
  482. if (!(self._peerConnections[targetMid] &&
  483. self._peerConnections[targetMid].signalingState !== self.PEER_CONNECTION_STATE.CLOSED)) {
  484. return;
  485. }
  486.  
  487. self._peerEndOfCandidatesCounter[targetMid].expectedLen = message.noOfExpectedCandidates || 0;
  488. self._handleIceGatheringStats('complete', targetMid, true);
  489. self._signalingEndOfCandidates(targetMid);
  490. };
  491.  
  492. /**
  493. * Function that handles the "introduceError" socket message received.
  494. * See confluence docs for the "introduceError" expected properties to be received
  495. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  496. * @method _introduceErrorEventHandler
  497. * @private
  498. * @for Skylink
  499. * @since 0.6.1
  500. */
  501. Skylink.prototype._introduceErrorEventHandler = function(message){
  502. var self = this;
  503. log.log(['Server', null, message.type, 'Introduce failed. Reason: '+message.reason]);
  504. self._handleSessionStats(message);
  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._handleSessionStats(message);
  524. self._trigger('handshakeProgress', self.HANDSHAKE_PROGRESS.ENTER, self._user.sid);
  525.  
  526. var enterMsg = {
  527. type: self._SIG_MESSAGE_TYPE.ENTER,
  528. mid: self._user.sid,
  529. rid: self._room.id,
  530. agent: AdapterJS.webrtcDetectedBrowser,
  531. version: (AdapterJS.webrtcDetectedVersion || 0).toString(),
  532. os: window.navigator.platform,
  533. userInfo: self._getUserInfo(),
  534. receiveOnly: self.getPeerInfo().config.receiveOnly,
  535. target: message.target,
  536. weight: self._peerPriorityWeight,
  537. temasysPluginVersion: AdapterJS.WebRTCPlugin.plugin ? AdapterJS.WebRTCPlugin.plugin.VERSION : null,
  538. enableIceTrickle: self._initOptions.enableIceTrickle,
  539. enableDataChannel: self._initOptions.enableDataChannel,
  540. enableIceRestart: self._enableIceRestart,
  541. SMProtocolVersion: self.SM_PROTOCOL_VERSION,
  542. DTProtocolVersion: self.DT_PROTOCOL_VERSION
  543. };
  544.  
  545. if (self._publishOnly) {
  546. enterMsg.publishOnly = {
  547. type: self._streams.screenshare && self._streams.screenshare.stream ? 'screenshare' : 'video'
  548. };
  549. }
  550.  
  551. if (self._parentId) {
  552. enterMsg.parentId = self._parentId;
  553. }
  554.  
  555. if (self._hasMCU) {
  556. enterMsg.target = 'MCU';
  557. }
  558.  
  559. self._sendChannelMessage(enterMsg);
  560. self._handleSessionStats(enterMsg);
  561. };
  562.  
  563. /**
  564. * Function that handles the "redirect" socket message received.
  565. * See confluence docs for the "redirect" expected properties to be received
  566. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  567. * @method _redirectHandler
  568. * @private
  569. * @for Skylink
  570. * @since 0.5.1
  571. */
  572. Skylink.prototype._redirectHandler = function(message) {
  573. log.log(['Server', null, message.type, 'System action warning:'], {
  574. message: message.info,
  575. reason: message.reason,
  576. action: message.action
  577. });
  578.  
  579. this._handleSessionStats(message);
  580.  
  581. if (message.action === this.SYSTEM_ACTION.REJECT) {
  582. for (var key in this._peerConnections) {
  583. if (this._peerConnections.hasOwnProperty(key)) {
  584. this._removePeer(key);
  585. }
  586. }
  587. }
  588.  
  589. // Handle the differences provided in Signaling server
  590. if (message.reason === 'toClose') {
  591. message.reason = 'toclose';
  592. }
  593.  
  594. this._trigger('systemAction', message.action, message.info, message.reason);
  595. };
  596.  
  597. /**
  598. * Function that handles the "updateUserEvent" socket message received.
  599. * See confluence docs for the "updateUserEvent" expected properties to be received
  600. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  601. * @method _updateUserEventHandler
  602. * @private
  603. * @for Skylink
  604. * @since 0.2.0
  605. */
  606. Skylink.prototype._updateUserEventHandler = function(message) {
  607. var targetMid = message.mid;
  608. log.log([targetMid, null, message.type, 'Peer updated userData:'], message.userData);
  609. if (this._peerInformations[targetMid]) {
  610. if (this._peerMessagesStamps[targetMid] && typeof message.stamp === 'number') {
  611. if (message.stamp < this._peerMessagesStamps[targetMid].userData) {
  612. log.warn([targetMid, null, message.type, 'Dropping outdated status ->'], message);
  613. return;
  614. }
  615. this._peerMessagesStamps[targetMid].userData = message.stamp;
  616. }
  617. this._peerInformations[targetMid].userData = message.userData || {};
  618. this._trigger('peerUpdated', targetMid, this.getPeerInfo(targetMid), false);
  619. } else {
  620. log.log([targetMid, null, message.type, 'Peer does not have any user information']);
  621. }
  622. };
  623.  
  624. /**
  625. * Function that handles the "roomLockEvent" socket message received.
  626. * See confluence docs for the "roomLockEvent" expected properties to be received
  627. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  628. * @method _roomLockEventHandler
  629. * @private
  630. * @for Skylink
  631. * @since 0.2.0
  632. */
  633. Skylink.prototype._roomLockEventHandler = function(message) {
  634. var targetMid = message.mid;
  635. log.log([targetMid, message.type, 'Room lock status:'], message.lock);
  636. this._trigger('roomLock', message.lock, targetMid, this.getPeerInfo(targetMid), false);
  637. };
  638.  
  639. /**
  640. * Function that handles the "muteAudioEvent" socket message received.
  641. * See confluence docs for the "muteAudioEvent" expected properties to be received
  642. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  643. * @method _muteAudioEventHandler
  644. * @private
  645. * @for Skylink
  646. * @since 0.2.0
  647. */
  648. Skylink.prototype._muteAudioEventHandler = function(message) {
  649. var targetMid = message.mid;
  650. log.log([targetMid, null, message.type, 'Peer\'s audio muted:'], message.muted);
  651. if (this._peerInformations[targetMid]) {
  652. if (this._peerMessagesStamps[targetMid] && typeof message.stamp === 'number') {
  653. if (message.stamp < this._peerMessagesStamps[targetMid].audioMuted) {
  654. log.warn([targetMid, null, message.type, 'Dropping outdated status ->'], message);
  655. return;
  656. }
  657. this._peerMessagesStamps[targetMid].audioMuted = message.stamp;
  658. }
  659. this._peerInformations[targetMid].mediaStatus.audioMuted = message.muted;
  660. this._trigger('streamMuted', targetMid, this.getPeerInfo(targetMid), false,
  661. this._peerInformations[targetMid].settings.video &&
  662. this._peerInformations[targetMid].settings.video.screenshare);
  663. this._trigger('peerUpdated', targetMid, this.getPeerInfo(targetMid), false);
  664. } else {
  665. log.log([targetMid, message.type, 'Peer does not have any user information']);
  666. }
  667. };
  668.  
  669. /**
  670. * Function that handles the "muteVideoEvent" socket message received.
  671. * See confluence docs for the "muteVideoEvent" expected properties to be received
  672. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  673. * @method _muteVideoEventHandler
  674. * @private
  675. * @for Skylink
  676. * @since 0.2.0
  677. */
  678. Skylink.prototype._muteVideoEventHandler = function(message) {
  679. var targetMid = message.mid;
  680. log.log([targetMid, null, message.type, 'Peer\'s video muted:'], message.muted);
  681. if (this._peerInformations[targetMid]) {
  682. if (this._peerMessagesStamps[targetMid] && typeof message.stamp === 'number') {
  683. if (message.stamp < this._peerMessagesStamps[targetMid].videoMuted) {
  684. log.warn([targetMid, null, message.type, 'Dropping outdated status ->'], message);
  685. return;
  686. }
  687. this._peerMessagesStamps[targetMid].videoMuted = message.stamp;
  688. }
  689. this._peerInformations[targetMid].mediaStatus.videoMuted = message.muted;
  690. this._trigger('streamMuted', targetMid, this.getPeerInfo(targetMid), false,
  691. this._peerInformations[targetMid].settings.video &&
  692. this._peerInformations[targetMid].settings.video.screenshare);
  693. this._trigger('peerUpdated', targetMid, this.getPeerInfo(targetMid), false);
  694. } else {
  695. log.log([targetMid, null, message.type, 'Peer does not have any user information']);
  696. }
  697. };
  698.  
  699. /**
  700. * Function that handles the "stream" socket message received.
  701. * See confluence docs for the "stream" expected properties to be received
  702. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  703. * @method _streamEventHandler
  704. * @private
  705. * @for Skylink
  706. * @since 0.2.0
  707. */
  708. Skylink.prototype._streamEventHandler = function(message) {
  709. var targetMid = message.mid;
  710. log.log([targetMid, null, message.type, 'Peer\'s stream status:'], message.status);
  711.  
  712. if (this._peerInformations[targetMid] && message.streamId) {
  713. this._streamsSession[targetMid] = this._streamsSession[targetMid] || {};
  714. if (message.status === 'ended') {
  715. if (message.settings && typeof message.settings === 'object' &&
  716. typeof this._streamsSession[targetMid][message.streamId] === 'undefined') {
  717. this._streamsSession[targetMid][message.streamId] = {
  718. audio: message.settings.audio,
  719. video: message.settings.video
  720. };
  721. }
  722.  
  723. this._handleEndedStreams(targetMid, message.streamId);
  724. }
  725. } else {
  726. // Probably left the room already
  727. log.log([targetMid, null, message.type, 'Peer does not have any user information']);
  728. }
  729. };
  730.  
  731. /**
  732. * Function that handles the "bye" socket message received.
  733. * See confluence docs for the "bye" expected properties to be received
  734. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  735. * @method _byeHandler
  736. * @private
  737. * @for Skylink
  738. * @since 0.1.0
  739. */
  740. Skylink.prototype._byeHandler = function(message) {
  741. var targetMid = message.mid;
  742. var selfId = (this._user || {}).sid;
  743.  
  744. if (selfId !== targetMid){
  745. log.log([targetMid, null, message.type, 'Peer has left the room']);
  746. this._removePeer(targetMid);
  747. } else {
  748. log.log([targetMid, null, message.type, 'Self has left the room']);
  749. }
  750. };
  751.  
  752. /**
  753. * Function that handles the "private" socket message received.
  754. * See confluence docs for the "private" expected properties to be received
  755. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  756. * @method _privateMessageHandler
  757. * @private
  758. * @for Skylink
  759. * @since 0.4.0
  760. */
  761. Skylink.prototype._privateMessageHandler = function(message) {
  762. var targetMid = message.mid;
  763. log.log([targetMid, null, message.type,
  764. 'Received private message from peer:'], message.data);
  765. this._trigger('incomingMessage', {
  766. content: message.data,
  767. isPrivate: true,
  768. targetPeerId: message.target, // is not null if there's user
  769. isDataChannel: false,
  770. senderPeerId: targetMid
  771. }, targetMid, this.getPeerInfo(targetMid), false);
  772. };
  773.  
  774. /**
  775. * Function that handles the "public" socket message received.
  776. * See confluence docs for the "public" expected properties to be received
  777. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  778. * @method _publicMessageHandler
  779. * @private
  780. * @for Skylink
  781. * @since 0.4.0
  782. */
  783. Skylink.prototype._publicMessageHandler = function(message) {
  784. var targetMid = message.mid;
  785. log.log([targetMid, null, message.type,
  786. 'Received public message from peer:'], message.data);
  787. this._trigger('incomingMessage', {
  788. content: message.data,
  789. isPrivate: false,
  790. targetPeerId: null, // is not null if there's user
  791. isDataChannel: false,
  792. senderPeerId: targetMid
  793. }, targetMid, this.getPeerInfo(targetMid), false);
  794. };
  795.  
  796. /**
  797. * Handles the RECORDING Protocol message event received from the platform signaling.
  798. * @method _recordingEventHandler
  799. * @param {JSON} message The message object received from platform signaling.
  800. * This should contain the <code>RECORDING</code> payload.
  801. * @param {String} message.url The recording URL if mixing has completed.
  802. * @param {String} message.action The recording action received.
  803. * @param {String} message.error The recording error exception received.
  804. * @private
  805. * @beta
  806. * @for Skylink
  807. * @since 0.6.16
  808. */
  809. Skylink.prototype._recordingEventHandler = function (message) {
  810. var self = this;
  811.  
  812. log.debug(['MCU', 'Recording', null, 'Received recording message ->'], message);
  813.  
  814. if (message.action === 'on') {
  815. self._handleRecordingStats('start', message.recordingId);
  816.  
  817. if (!self._recordings[message.recordingId]) {
  818. log.debug(['MCU', 'Recording', message.recordingId, 'Started recording']);
  819.  
  820. self._currentRecordingId = message.recordingId;
  821. self._recordings[message.recordingId] = {
  822. active: true,
  823. state: self.RECORDING_STATE.START,
  824. startedDateTime: (new Date()).toISOString(),
  825. endedDateTime: null,
  826. mixingDateTime: null,
  827. links: null,
  828. error: null
  829. };
  830. self._recordingStartInterval = setTimeout(function () {
  831. log.log(['MCU', 'Recording', message.recordingId, '4 seconds has been recorded. Recording can be stopped now']);
  832. self._recordingStartInterval = null;
  833. }, 4000);
  834. self._trigger('recordingState', self.RECORDING_STATE.START, message.recordingId, null, null);
  835. }
  836.  
  837. } else if (message.action === 'off') {
  838. self._handleRecordingStats('stop', message.recordingId);
  839.  
  840. if (!self._recordings[message.recordingId]) {
  841. log.error(['MCU', 'Recording', message.recordingId, 'Received request of "off" but the session is empty']);
  842. return;
  843. }
  844.  
  845. self._currentRecordingId = null;
  846.  
  847. if (self._recordingStartInterval) {
  848. clearTimeout(self._recordingStartInterval);
  849. log.warn(['MCU', 'Recording', message.recordingId, 'Recording stopped abruptly before 4 seconds']);
  850. self._recordingStartInterval = null;
  851. }
  852.  
  853. log.debug(['MCU', 'Recording', message.recordingId, 'Stopped recording']);
  854.  
  855. self._recordings[message.recordingId].active = false;
  856. self._recordings[message.recordingId].state = self.RECORDING_STATE.STOP;
  857. self._recordings[message.recordingId].endedDateTime = (new Date()).toISOString();
  858. self._trigger('recordingState', self.RECORDING_STATE.STOP, message.recordingId, null, null);
  859.  
  860. } else {
  861. var recordingError = new Error(message.error || 'Unknown error');
  862.  
  863. self._handleRecordingStats('error', message.recordingId, null, recordingError.message);
  864.  
  865. if (!self._recordings[message.recordingId]) {
  866. log.error(['MCU', 'Recording', message.recordingId, 'Received error but the session is empty ->'], recordingError);
  867. return;
  868. }
  869.  
  870. log.error(['MCU', 'Recording', message.recordingId, 'Recording failure ->'], recordingError);
  871.  
  872. self._recordings[message.recordingId].state = self.RECORDING_STATE.ERROR;
  873. self._recordings[message.recordingId].error = recordingError;
  874.  
  875. if (self._recordings[message.recordingId].active) {
  876. log.debug(['MCU', 'Recording', message.recordingId, 'Stopped recording abruptly']);
  877. self._recordings[message.recordingId].active = false;
  878. //self._trigger('recordingState', self.RECORDING_STATE.STOP, message.recordingId, null, recordingError);
  879. }
  880.  
  881. self._trigger('recordingState', self.RECORDING_STATE.ERROR, message.recordingId, null, recordingError);
  882. }
  883. };
  884.  
  885. /**
  886. * Function that handles the "inRoom" socket message received.
  887. * See confluence docs for the "inRoom" expected properties to be received
  888. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  889. * @method _inRoomHandler
  890. * @private
  891. * @for Skylink
  892. * @since 0.1.0
  893. */
  894. Skylink.prototype._inRoomHandler = function(message) {
  895. var self = this;
  896. log.log(['Server', null, message.type, 'User is now in the room and ' +
  897. 'functionalities are now available. Config received:'], message.pc_config);
  898. self._room.connection.peerConfig = self._setIceServers((message.pc_config || {}).iceServers || []);
  899. self._inRoom = true;
  900. self._user.sid = message.sid;
  901. self._peerPriorityWeight = message.tieBreaker + (self._initOptions.priorityWeightScheme === self.PRIORITY_WEIGHT_SCHEME.AUTO ?
  902. 0 : (self._initOptions.priorityWeightScheme === self.PRIORITY_WEIGHT_SCHEME.ENFORCE_OFFERER ? 2e+15 : -(2e+15)));
  903.  
  904. self._handleSessionStats(message);
  905. self._trigger('peerJoined', self._user.sid, self.getPeerInfo(), true);
  906. self._trigger('handshakeProgress', self.HANDSHAKE_PROGRESS.ENTER, self._user.sid);
  907.  
  908. var streamId = null;
  909.  
  910. if (self._streams.screenshare && self._streams.screenshare.stream) {
  911. streamId = self._streams.screenshare.stream.id || self._streams.screenshare.stream.label;
  912. self._trigger('incomingStream', self._user.sid, self._streams.screenshare.stream, true, self.getPeerInfo(), true, streamId);
  913. } else if (self._streams.userMedia && self._streams.userMedia.stream) {
  914. streamId = self._streams.userMedia.stream.id || self._streams.userMedia.stream.label;
  915. self._trigger('incomingStream', self._user.sid, self._streams.userMedia.stream, true, self.getPeerInfo(), false, streamId);
  916. }
  917. // NOTE ALEX: should we wait for local streams?
  918. // or just go with what we have (if no stream, then one way?)
  919. // do we hardcode the logic here, or give the flexibility?
  920. // It would be better to separate, do we could choose with whom
  921. // we want to communicate, instead of connecting automatically to all.
  922. var enterMsg = {
  923. type: self._SIG_MESSAGE_TYPE.ENTER,
  924. mid: self._user.sid,
  925. rid: self._room.id,
  926. agent: AdapterJS.webrtcDetectedBrowser,
  927. version: (AdapterJS.webrtcDetectedVersion || 0).toString(),
  928. os: window.navigator.platform,
  929. userInfo: self._getUserInfo(),
  930. receiveOnly: self.getPeerInfo().config.receiveOnly,
  931. weight: self._peerPriorityWeight,
  932. temasysPluginVersion: AdapterJS.WebRTCPlugin.plugin ? AdapterJS.WebRTCPlugin.plugin.VERSION : null,
  933. enableIceTrickle: self._initOptions.enableIceTrickle,
  934. enableDataChannel: self._initOptions.enableDataChannel,
  935. enableIceRestart: self._enableIceRestart,
  936. SMProtocolVersion: self.SM_PROTOCOL_VERSION,
  937. DTProtocolVersion: self.DT_PROTOCOL_VERSION
  938. };
  939.  
  940. if (self._publishOnly) {
  941. enterMsg.publishOnly = {
  942. type: self._streams.screenshare && self._streams.screenshare.stream ? 'screenshare' : 'video'
  943. };
  944. }
  945.  
  946. if (self._parentId) {
  947. enterMsg.parentId = self._parentId;
  948. }
  949.  
  950. if (self._hasMCU) {
  951. enterMsg.target = 'MCU';
  952. }
  953.  
  954. self._sendChannelMessage(enterMsg);
  955. self._handleSessionStats(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. self._handleNegotiationStats('enter', targetMid, message, true);
  1012.  
  1013. // Ignore if: User is publishOnly and MCU is enabled
  1014. // : User is parent and parentId is defined and matches
  1015. // : User is child and parent matches
  1016. // Don't if : Is MCU
  1017. if (targetMid !== 'MCU' && ((self._parentId && self._parentId === targetMid) ||
  1018. (self._hasMCU && self._publishOnly) || (message.parentId && self._user && self._user.sid &&
  1019. message.parentId === self._user.sid))) {
  1020. log.warn([targetMid, 'RTCPeerConnection', null, 'Discarding "enter" for parentId or publishOnly case ->'], message);
  1021. return;
  1022. }
  1023.  
  1024. var processPeerFn = function (cert) {
  1025. if ((!self._peerInformations[targetMid] && !self._hasMCU) || (self._hasMCU && !self._peerInformations['MCU'])) {
  1026. isNewPeer = true;
  1027.  
  1028. self._peerInformations[targetMid] = userInfo;
  1029.  
  1030. var hasScreenshare = userInfo.settings.video && typeof userInfo.settings.video === 'object' &&
  1031. !!userInfo.settings.video.screenshare;
  1032.  
  1033. self._addPeer(targetMid, cert || null, {
  1034. agent: userInfo.agent.name,
  1035. version: userInfo.agent.version,
  1036. os: userInfo.agent.os
  1037. }, message.receiveOnly, hasScreenshare);
  1038.  
  1039. if (targetMid === 'MCU') {
  1040. log.info([targetMid, 'RTCPeerConnection', null, 'MCU feature has been enabled']);
  1041.  
  1042. self._hasMCU = true;
  1043. self._trigger('peerJoined', targetMid, self.getPeerInfo(targetMid), false);
  1044. self._trigger('serverPeerJoined', targetMid, self.SERVER_PEER_TYPE.MCU);
  1045.  
  1046. } else {
  1047. self._trigger('peerJoined', targetMid, self.getPeerInfo(targetMid), false);
  1048. }
  1049.  
  1050. self._trigger('handshakeProgress', self.HANDSHAKE_PROGRESS.ENTER, targetMid);
  1051. }
  1052.  
  1053. if (self._hasMCU && targetMid !== self._user.sid) {
  1054. self._trigger('peerJoined', targetMid, self.getPeerInfo(targetMid), false);
  1055. }
  1056.  
  1057. self._peerMessagesStamps[targetMid] = self._peerMessagesStamps[targetMid] || {
  1058. userData: 0,
  1059. audioMuted: 0,
  1060. videoMuted: 0
  1061. };
  1062.  
  1063. if (self._hasMCU !== true) {
  1064. var welcomeMsg = {
  1065. type: self._SIG_MESSAGE_TYPE.WELCOME,
  1066. mid: self._user.sid,
  1067. rid: self._room.id,
  1068. enableIceTrickle: self._initOptions.enableIceTrickle,
  1069. enableDataChannel: self._initOptions.enableDataChannel,
  1070. enableIceRestart: self._enableIceRestart,
  1071. agent: AdapterJS.webrtcDetectedBrowser,
  1072. version: (AdapterJS.webrtcDetectedVersion || 0).toString(),
  1073. receiveOnly: self.getPeerInfo().config.receiveOnly,
  1074. os: window.navigator.platform,
  1075. userInfo: self._getUserInfo(targetMid),
  1076. target: targetMid,
  1077. weight: self._peerPriorityWeight,
  1078. temasysPluginVersion: AdapterJS.WebRTCPlugin.plugin ? AdapterJS.WebRTCPlugin.plugin.VERSION : null,
  1079. SMProtocolVersion: self.SM_PROTOCOL_VERSION,
  1080. DTProtocolVersion: self.DT_PROTOCOL_VERSION
  1081. };
  1082.  
  1083. if (self._publishOnly) {
  1084. welcomeMsg.publishOnly = {
  1085. type: self._streams.screenshare && self._streams.screenshare.stream ? 'screenshare' : 'video'
  1086. };
  1087. }
  1088.  
  1089. if (self._parentId) {
  1090. welcomeMsg.parentId = self._parentId;
  1091. }
  1092.  
  1093. self._sendChannelMessage(welcomeMsg);
  1094. self._handleNegotiationStats('welcome', targetMid, welcomeMsg, false);
  1095. }
  1096.  
  1097. if (isNewPeer) {
  1098. self._trigger('handshakeProgress', self.HANDSHAKE_PROGRESS.WELCOME, targetMid);
  1099. }
  1100. };
  1101.  
  1102. if (self._peerConnectionConfig.certificate !== self.PEER_CERTIFICATE.AUTO &&
  1103. typeof RTCPeerConnection.generateCertificate === 'function') {
  1104. var certOptions = {};
  1105. if (self._peerConnectionConfig.certificate === self.PEER_CERTIFICATE.ECDSA) {
  1106. certOptions = {
  1107. name: 'ECDSA',
  1108. namedCurve: 'P-256'
  1109. };
  1110. } else {
  1111. certOptions = {
  1112. name: 'RSASSA-PKCS1-v1_5',
  1113. modulusLength: 2048,
  1114. publicExponent: new Uint8Array([1, 0, 1]),
  1115. hash: 'SHA-256'
  1116. };
  1117. }
  1118. RTCPeerConnection.generateCertificate(certOptions).then(function (cert) {
  1119. processPeerFn(cert);
  1120. }, function () {
  1121. processPeerFn();
  1122. });
  1123. } else {
  1124. processPeerFn();
  1125. }
  1126. };
  1127.  
  1128. /**
  1129. * Function that handles the "restart" socket message received.
  1130. * See confluence docs for the "restart" expected properties to be received
  1131. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  1132. * @method _restartHandler
  1133. * @private
  1134. * @for Skylink
  1135. * @since 0.5.6
  1136. */
  1137. Skylink.prototype._restartHandler = function(message){
  1138. var self = this;
  1139. var targetMid = message.mid;
  1140. var trackCount = message.trackCount;
  1141. var userInfo = message.userInfo || {};
  1142. userInfo.settings = userInfo.settings || {};
  1143. userInfo.mediaStatus = userInfo.mediaStatus || {};
  1144. userInfo.config = {
  1145. enableIceTrickle: typeof message.enableIceTrickle === 'boolean' ? message.enableIceTrickle : true,
  1146. enableIceRestart: typeof message.enableIceRestart === 'boolean' ? message.enableIceRestart : false,
  1147. enableDataChannel: typeof message.enableDataChannel === 'boolean' ? message.enableDataChannel : true,
  1148. priorityWeight: typeof message.weight === 'number' ? message.weight : 0,
  1149. receiveOnly: message.receiveOnly === true,
  1150. publishOnly: !!message.publishOnly
  1151. };
  1152. userInfo.parentId = message.parentId || null;
  1153. userInfo.agent = {
  1154. name: typeof message.agent === 'string' && message.agent ? message.agent : 'other',
  1155. version: (function () {
  1156. if (!(message.version && typeof message.version === 'string')) {
  1157. return 0;
  1158. }
  1159. // E.g. 0.9.6, replace minor "." with 0
  1160. if (message.version.indexOf('.') > -1) {
  1161. var parts = message.version.split('.');
  1162. if (parts.length > 2) {
  1163. var majorVer = parts[0] || '0';
  1164. parts.splice(0, 1);
  1165. return parseFloat(majorVer + '.' + parts.join('0'), 10);
  1166. }
  1167. return parseFloat(message.version || '0', 10);
  1168. }
  1169. return parseInt(message.version || '0', 10);
  1170. })(),
  1171. os: typeof message.os === 'string' && message.os ? message.os : '',
  1172. pluginVersion: typeof message.temasysPluginVersion === 'string' && message.temasysPluginVersion ?
  1173. message.temasysPluginVersion : null,
  1174. SMProtocolVersion: message.SMProtocolVersion && typeof message.SMProtocolVersion === 'string' ?
  1175. message.SMProtocolVersion : '0.1.1',
  1176. DTProtocolVersion: message.DTProtocolVersion && typeof message.DTProtocolVersion === 'string' ?
  1177. message.DTProtocolVersion : (self._hasMCU || targetMid === 'MCU' ? '0.1.2' : '0.1.0')
  1178. };
  1179.  
  1180. if (trackCount) {
  1181. self._currentRequestedTracks = trackCount;
  1182. }
  1183.  
  1184. log.log([targetMid, 'RTCPeerConnection', null, 'Peer "restart" received ->'], message);
  1185. self._handleNegotiationStats('restart', targetMid, message, true);
  1186.  
  1187. if (!self._peerInformations[targetMid]) {
  1188. log.error([targetMid, 'RTCPeerConnection', null, 'Peer does not have an existing session. Ignoring restart process.']);
  1189. return;
  1190. }
  1191.  
  1192. // Ignore if: User is publishOnly and MCU is enabled
  1193. // : User is parent and parentId is defined and matches
  1194. // : User is child and parent matches
  1195. // Don't if : Is MCU
  1196. if (targetMid !== 'MCU' && ((self._parentId && self._parentId === targetMid) ||
  1197. (self._hasMCU && self._publishOnly) || (message.parentId && self._user && self._user.sid &&
  1198. message.parentId === self._user.sid))) {
  1199. log.warn([targetMid, 'RTCPeerConnection', null, 'Discarding "restart" for parentId or publishOnly case ->'], message);
  1200. return;
  1201. }
  1202.  
  1203. if (self._hasMCU && !self._initOptions.mcuUseRenegoRestart) {
  1204. log.warn([targetMid, 'RTCPeerConnection', null, 'Dropping restart request as MCU does not support re-negotiation. ' +
  1205. 'Restart workaround is to re-join Room for Peer.']);
  1206. self._trigger('peerRestart', targetMid, self.getPeerInfo(targetMid), false, false);
  1207. return;
  1208. }
  1209.  
  1210. self._peerInformations[targetMid] = userInfo;
  1211. self._peerMessagesStamps[targetMid] = self._peerMessagesStamps[targetMid] || {
  1212. userData: 0,
  1213. audioMuted: 0,
  1214. videoMuted: 0
  1215. };
  1216. self._peerEndOfCandidatesCounter[targetMid] = self._peerEndOfCandidatesCounter[targetMid] || {};
  1217. self._peerEndOfCandidatesCounter[targetMid].len = 0;
  1218.  
  1219. // Make peer with highest weight do the offer
  1220. if (self._peerPriorityWeight > message.weight) {
  1221. log.debug([targetMid, 'RTCPeerConnection', null, 'Re-negotiating new offer/answer.']);
  1222.  
  1223. if (self._peerMessagesStamps[targetMid].hasRestart) {
  1224. log.warn([targetMid, 'RTCPeerConnection', null, 'Discarding extra "restart" received.']);
  1225. return;
  1226. }
  1227.  
  1228. self._peerMessagesStamps[targetMid].hasRestart = true;
  1229. self._doOffer(targetMid, message.doIceRestart === true, {
  1230. agent: userInfo.agent.name,
  1231. version: userInfo.agent.version,
  1232. os: userInfo.agent.os
  1233. }, true);
  1234.  
  1235. } else {
  1236. log.debug([targetMid, 'RTCPeerConnection', null, 'Waiting for peer to start re-negotiation.']);
  1237.  
  1238. var restartMsg = {
  1239. type: self._SIG_MESSAGE_TYPE.RESTART,
  1240. mid: self._user.sid,
  1241. rid: self._room.id,
  1242. agent: AdapterJS.webrtcDetectedBrowser,
  1243. version: (AdapterJS.webrtcDetectedVersion || 0).toString(),
  1244. os: window.navigator.platform,
  1245. userInfo: self._getUserInfo(targetMid),
  1246. target: targetMid,
  1247. weight: self._peerPriorityWeight,
  1248. enableIceTrickle: self._initOptions.enableIceTrickle,
  1249. enableDataChannel: self._initOptions.enableDataChannel,
  1250. enableIceRestart: self._enableIceRestart,
  1251. doIceRestart: message.doIceRestart === true,
  1252. receiveOnly: self.getPeerInfo().config.receiveOnly,
  1253. isRestartResend: true,
  1254. temasysPluginVersion: AdapterJS.WebRTCPlugin.plugin ? AdapterJS.WebRTCPlugin.plugin.VERSION : null,
  1255. SMProtocolVersion: self.SM_PROTOCOL_VERSION,
  1256. DTProtocolVersion: self.DT_PROTOCOL_VERSION,
  1257. };
  1258.  
  1259. if (self._publishOnly) {
  1260. restartMsg.publishOnly = {
  1261. type: self._streams.screenshare && self._streams.screenshare.stream ? 'screenshare' : 'video'
  1262. };
  1263. }
  1264.  
  1265. if (self._parentId) {
  1266. restartMsg.parentId = self._parentId;
  1267. }
  1268.  
  1269. self._sendChannelMessage(restartMsg);
  1270. self._handleNegotiationStats('restart', targetMid, restartMsg, false);
  1271. }
  1272.  
  1273. self._trigger('peerRestart', targetMid, self.getPeerInfo(targetMid), false, message.doIceRestart === true);
  1274. };
  1275.  
  1276. /**
  1277. * Function that handles the "welcome" socket message received.
  1278. * See confluence docs for the "welcome" expected properties to be received
  1279. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  1280. * @method _welcomeHandler
  1281. * @private
  1282. * @for Skylink
  1283. * @since 0.5.4
  1284. */
  1285. Skylink.prototype._welcomeHandler = function(message) {
  1286. var self = this;
  1287. var targetMid = message.mid;
  1288. var isNewPeer = false;
  1289. var trackCount = message.trackCount;
  1290. var userInfo = message.userInfo || {};
  1291. userInfo.settings = userInfo.settings || {};
  1292. userInfo.mediaStatus = userInfo.mediaStatus || {};
  1293. userInfo.config = {
  1294. enableIceTrickle: typeof message.enableIceTrickle === 'boolean' ? message.enableIceTrickle : true,
  1295. enableIceRestart: typeof message.enableIceRestart === 'boolean' ? message.enableIceRestart : false,
  1296. enableDataChannel: typeof message.enableDataChannel === 'boolean' ? message.enableDataChannel : true,
  1297. priorityWeight: typeof message.weight === 'number' ? message.weight : 0,
  1298. receiveOnly: message.receiveOnly === true,
  1299. publishOnly: !!message.publishOnly
  1300. };
  1301. userInfo.parentId = message.parentId || null;
  1302. userInfo.agent = {
  1303. name: typeof message.agent === 'string' && message.agent ? message.agent : 'other',
  1304. version: (function () {
  1305. if (!(message.version && typeof message.version === 'string')) {
  1306. return 0;
  1307. }
  1308. // E.g. 0.9.6, replace minor "." with 0
  1309. if (message.version.indexOf('.') > -1) {
  1310. var parts = message.version.split('.');
  1311. if (parts.length > 2) {
  1312. var majorVer = parts[0] || '0';
  1313. parts.splice(0, 1);
  1314. return parseFloat(majorVer + '.' + parts.join('0'), 10);
  1315. }
  1316. return parseFloat(message.version || '0', 10);
  1317. }
  1318. return parseInt(message.version || '0', 10);
  1319. })(),
  1320. os: typeof message.os === 'string' && message.os ? message.os : '',
  1321. pluginVersion: typeof message.temasysPluginVersion === 'string' && message.temasysPluginVersion ?
  1322. message.temasysPluginVersion : null,
  1323. SMProtocolVersion: message.SMProtocolVersion && typeof message.SMProtocolVersion === 'string' ?
  1324. message.SMProtocolVersion : '0.1.1',
  1325. DTProtocolVersion: message.DTProtocolVersion && typeof message.DTProtocolVersion === 'string' ?
  1326. message.DTProtocolVersion : (self._hasMCU || targetMid === 'MCU' ? '0.1.2' : '0.1.0')
  1327. };
  1328.  
  1329. if (trackCount) {
  1330. self._currentRequestedTracks = trackCount;
  1331. }
  1332.  
  1333. log.log([targetMid, 'RTCPeerConnection', null, 'Peer "welcome" received ->'], message);
  1334. self._handleNegotiationStats('welcome', targetMid, message, true);
  1335.  
  1336. // Ignore if: User is publishOnly and MCU is enabled
  1337. // : User is parent and parentId is defined and matches
  1338. // : User is child and parent matches
  1339. // Don't if : Is MCU
  1340. if (targetMid !== 'MCU' && ((self._parentId && self._parentId === targetMid) ||
  1341. (self._hasMCU && self._publishOnly) || (message.parentId && self._user && self._user.sid &&
  1342. message.parentId === self._user.sid))) {
  1343. log.warn([targetMid, 'RTCPeerConnection', null, 'Discarding "welcome" for parentId or publishOnly case ->'], message);
  1344. return;
  1345. }
  1346.  
  1347. var processPeerFn = function (cert) {
  1348. if ((!self._peerInformations[targetMid] && !self._hasMCU) || (self._hasMCU && !self._peerInformations['MCU'])) {
  1349. isNewPeer = true;
  1350.  
  1351. self._peerInformations[targetMid] = userInfo;
  1352.  
  1353. var hasScreenshare = userInfo.settings.video && typeof userInfo.settings.video === 'object' &&
  1354. !!userInfo.settings.video.screenshare;
  1355.  
  1356. self._addPeer(targetMid, cert || null, {
  1357. agent: userInfo.agent.name,
  1358. version: userInfo.agent.version,
  1359. os: userInfo.agent.os
  1360. }, message.receiveOnly, hasScreenshare);
  1361.  
  1362. if (targetMid === 'MCU') {
  1363. log.info([targetMid, 'RTCPeerConnection', null, 'MCU feature has been enabled']);
  1364. self._hasMCU = true;
  1365. self._trigger('serverPeerJoined', targetMid, self.SERVER_PEER_TYPE.MCU);
  1366.  
  1367. } else {
  1368. self._trigger('peerJoined', targetMid, self.getPeerInfo(targetMid), false);
  1369. }
  1370.  
  1371. self._trigger('handshakeProgress', self.HANDSHAKE_PROGRESS.ENTER, targetMid);
  1372. self._trigger('handshakeProgress', self.HANDSHAKE_PROGRESS.WELCOME, targetMid);
  1373. }
  1374.  
  1375. if (self._hasMCU && Array.isArray(message.peersInRoom) && message.peersInRoom.length) {
  1376. var userId = self._user.sid;
  1377. for (var peersInRoomIndex = 0; peersInRoomIndex < message.peersInRoom.length; peersInRoomIndex++) {
  1378. var _PEER_ID = message.peersInRoom[peersInRoomIndex].mid;
  1379. if (_PEER_ID !== userId) {
  1380. self._trigger('peerJoined', _PEER_ID, self.getPeerInfo(_PEER_ID), false);
  1381. }
  1382. }
  1383. }
  1384.  
  1385. self._peerMessagesStamps[targetMid] = self._peerMessagesStamps[targetMid] || {
  1386. userData: 0,
  1387. audioMuted: 0,
  1388. videoMuted: 0,
  1389. hasWelcome: false
  1390. };
  1391.  
  1392. if (self._hasMCU || self._peerPriorityWeight > message.weight) {
  1393. if (self._peerMessagesStamps[targetMid].hasWelcome) {
  1394. log.warn([targetMid, 'RTCPeerConnection', null, 'Discarding extra "welcome" received.']);
  1395. return;
  1396. }
  1397.  
  1398. log.debug([targetMid, 'RTCPeerConnection', null, 'Starting negotiation']);
  1399.  
  1400. self._peerMessagesStamps[targetMid].hasWelcome = true;
  1401. self._doOffer(targetMid, false, {
  1402. agent: userInfo.agent.name,
  1403. version: userInfo.agent.version,
  1404. os: userInfo.agent.os
  1405. }, true);
  1406.  
  1407. } else {
  1408. log.debug([targetMid, 'RTCPeerConnection', null, 'Waiting for peer to start negotiation.']);
  1409.  
  1410. var welcomeMsg = {
  1411. type: self._SIG_MESSAGE_TYPE.WELCOME,
  1412. mid: self._user.sid,
  1413. rid: self._room.id,
  1414. enableIceTrickle: self._initOptions.enableIceTrickle,
  1415. enableDataChannel: self._initOptions.enableDataChannel,
  1416. enableIceRestart: self._enableIceRestart,
  1417. receiveOnly: self.getPeerInfo().config.receiveOnly,
  1418. agent: AdapterJS.webrtcDetectedBrowser,
  1419. version: (AdapterJS.webrtcDetectedVersion || 0).toString(),
  1420. os: window.navigator.platform,
  1421. userInfo: self._getUserInfo(targetMid),
  1422. target: targetMid,
  1423. weight: self._peerPriorityWeight,
  1424. temasysPluginVersion: AdapterJS.WebRTCPlugin.plugin ? AdapterJS.WebRTCPlugin.plugin.VERSION : null,
  1425. SMProtocolVersion: self.SM_PROTOCOL_VERSION,
  1426. DTProtocolVersion: self.DT_PROTOCOL_VERSION
  1427. };
  1428.  
  1429. if (self._publishOnly) {
  1430. welcomeMsg.publishOnly = {
  1431. type: self._streams.screenshare && self._streams.screenshare.stream ? 'screenshare' : 'video'
  1432. };
  1433. }
  1434.  
  1435. if (self._parentId) {
  1436. welcomeMsg.parentId = self._parentId;
  1437. }
  1438.  
  1439. self._sendChannelMessage(welcomeMsg);
  1440. self._handleNegotiationStats('welcome', targetMid, welcomeMsg, false);
  1441. }
  1442. };
  1443.  
  1444. if (self._peerConnectionConfig.certificate !== self.PEER_CERTIFICATE.AUTO &&
  1445. typeof RTCPeerConnection.generateCertificate === 'function') {
  1446. var certOptions = {};
  1447. if (self._peerConnectionConfig.certificate === self.PEER_CERTIFICATE.ECDSA) {
  1448. certOptions = {
  1449. name: 'ECDSA',
  1450. namedCurve: 'P-256'
  1451. };
  1452. } else {
  1453. certOptions = {
  1454. name: 'RSASSA-PKCS1-v1_5',
  1455. modulusLength: 2048,
  1456. publicExponent: new Uint8Array([1, 0, 1]),
  1457. hash: 'SHA-256'
  1458. };
  1459. }
  1460. RTCPeerConnection.generateCertificate(certOptions).then(function (cert) {
  1461. processPeerFn(cert);
  1462. }, function () {
  1463. processPeerFn();
  1464. });
  1465. } else {
  1466. processPeerFn();
  1467. }
  1468. };
  1469.  
  1470. /**
  1471. * Function that handles the "offer" socket message received.
  1472. * See confluence docs for the "offer" expected properties to be received
  1473. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  1474. * @method _offerHandler
  1475. * @private
  1476. * @for Skylink
  1477. * @since 0.5.1
  1478. */
  1479. Skylink.prototype._offerHandler = function(message) {
  1480. var self = this;
  1481. var targetMid = message.mid;
  1482. var pc = self._peerConnections[targetMid];
  1483.  
  1484. log.log([targetMid, null, message.type, 'Received offer from peer. Session description:'], clone(message));
  1485.  
  1486. var offer = {
  1487. type: 'offer',
  1488. sdp: self._hasMCU ? message.sdp.replace(/\r\n/g, '\n').split('\n').join('\r\n') : message.sdp
  1489. };
  1490.  
  1491. if (targetMid === 'MCU') {
  1492. self._transceiverIdPeerIdMap = message.transceiverIdPeerIdMap || {};
  1493. }
  1494.  
  1495. self._handleNegotiationStats('offer', targetMid, offer, true);
  1496.  
  1497. if (!pc) {
  1498. if(!self._hasMCU) {
  1499. log.error([targetMid, null, message.type, 'Peer connection object ' +
  1500. 'not found. Unable to setRemoteDescription for offer']);
  1501. }
  1502. if (targetMid !== 'MCU' && self._hasMCU && self._peerConnections['MCU']) {
  1503. log.warn([targetMid, null, message.type, 'Peer connection object with MCU ' +
  1504. 'already exists. Dropping the offer.']);
  1505. }
  1506. self._handleNegotiationStats('dropped_offer', targetMid, offer, true, 'Peer connection does not exists');
  1507. return;
  1508. }
  1509.  
  1510. /*if (pc.localDescription ? !!pc.localDescription.sdp : false) {
  1511. log.warn([targetMid, null, message.type, 'Peer has an existing connection'],
  1512. pc.localDescription);
  1513. return;
  1514. }*/
  1515.  
  1516. // Add-on by Web SDK fixes
  1517. if (message.userInfo && typeof message.userInfo === 'object') {
  1518. var userInfo = message.userInfo || {};
  1519.  
  1520. self._peerInformations[targetMid].settings = userInfo.settings || {};
  1521. self._peerInformations[targetMid].mediaStatus = userInfo.mediaStatus || {};
  1522. self._peerInformations[targetMid].userData = userInfo.userData;
  1523. }
  1524.  
  1525. log.log([targetMid, 'RTCSessionDescription', message.type, 'Session description object created'], offer);
  1526.  
  1527. // offer.sdp = self._removeSDPFilteredCandidates(targetMid, offer);
  1528. // offer.sdp = self._setSDPCodec(targetMid, offer);
  1529. // offer.sdp = self._setSDPBitrate(targetMid, offer);
  1530. // offer.sdp = self._setSDPCodecParams(targetMid, offer);
  1531. // offer.sdp = self._removeSDPCodecs(targetMid, offer);
  1532. // offer.sdp = self._removeSDPREMBPackets(targetMid, offer);
  1533. // offer.sdp = self._handleSDPConnectionSettings(targetMid, offer, 'remote');
  1534. // offer.sdp = self._removeSDPUnknownAptRtx(targetMid, offer);
  1535.  
  1536. log.log([targetMid, 'RTCSessionDescription', message.type, 'Updated remote offer ->'], offer.sdp);
  1537.  
  1538. // This is always the initial state. or even after negotiation is successful
  1539. if (pc.signalingState !== self.PEER_CONNECTION_STATE.STABLE) {
  1540. log.warn([targetMid, null, message.type, 'Peer connection state is not in ' +
  1541. '"stable" state for re-negotiation. Dropping message.'], {
  1542. signalingState: pc.signalingState,
  1543. isRestart: !!message.resend
  1544. });
  1545. self._handleNegotiationStats('dropped_offer', targetMid, offer, true, 'Peer connection state is "' + pc.signalingState + '"');
  1546. return;
  1547. }
  1548.  
  1549. // Added checks if there is a current remote sessionDescription being processing before processing this one
  1550. if (pc.processingRemoteSDP) {
  1551. log.warn([targetMid, 'RTCSessionDescription', 'offer',
  1552. 'Dropping of setting local offer as there is another ' +
  1553. 'sessionDescription being processed ->'], offer);
  1554. self._handleNegotiationStats('dropped_offer', targetMid, offer, true, 'Peer connection is currently processing an existing sdp');
  1555. return;
  1556. }
  1557.  
  1558. pc.processingRemoteSDP = true;
  1559.  
  1560. if (message.userInfo) {
  1561. self._trigger('peerUpdated', targetMid, self.getPeerInfo(targetMid), false);
  1562. }
  1563.  
  1564. // self._parseSDPMediaStreamIDs(targetMid, offer);
  1565.  
  1566. var onSuccessCbFn = function() {
  1567. log.debug([targetMid, 'RTCSessionDescription', message.type, 'Remote description set']);
  1568. pc.setOffer = 'remote';
  1569. pc.processingRemoteSDP = false;
  1570.  
  1571. self._handleNegotiationStats('set_offer', targetMid, offer, true);
  1572. self._trigger('handshakeProgress', self.HANDSHAKE_PROGRESS.OFFER, targetMid);
  1573. self._addIceCandidateFromQueue(targetMid);
  1574. self._doAnswer(targetMid);
  1575. };
  1576.  
  1577. var onErrorCbFn = function(error) {
  1578. pc.processingRemoteSDP = false;
  1579.  
  1580. self._handleNegotiationStats('error_set_offer', targetMid, offer, true, error);
  1581. self._trigger('handshakeProgress', self.HANDSHAKE_PROGRESS.ERROR, targetMid, error);
  1582.  
  1583. log.error([targetMid, null, message.type, 'Failed setting remote description:'], {
  1584. error: error,
  1585. state: pc.signalingState,
  1586. offer: offer
  1587. });
  1588. };
  1589.  
  1590. if (self.getPeerInfo(targetMid).agent.name === 'edge' && offer.sdp[offer.sdp.length - 1] !== '\n' && offer.sdp[offer.sdp.length - 2] !== '\r') {
  1591. offer.sdp = offer.sdp + '\r\n';
  1592. }
  1593.  
  1594. pc.setRemoteDescription(new RTCSessionDescription(offer), onSuccessCbFn, onErrorCbFn);
  1595. };
  1596.  
  1597.  
  1598. /**
  1599. * Function that handles the "candidate" socket message received.
  1600. * See confluence docs for the "candidate" expected properties to be received
  1601. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  1602. * @method _candidateHandler
  1603. * @private
  1604. * @for Skylink
  1605. * @since 0.5.1
  1606. */
  1607. Skylink.prototype._candidateHandler = function(message) {
  1608. var targetMid = message.mid;
  1609.  
  1610. if (!message.candidate && !message.id) {
  1611. log.warn([targetMid, 'RTCIceCandidate', null, 'Received invalid ICE candidate message ->'], message);
  1612. return;
  1613. }
  1614.  
  1615. var canId = 'can-' + (new Date()).getTime();
  1616. var candidateType = message.candidate.split(' ')[7] || '';
  1617. var candidate = new RTCIceCandidate({
  1618. sdpMLineIndex: message.label,
  1619. candidate: message.candidate,
  1620. sdpMid: message.id
  1621. });
  1622.  
  1623. log.debug([targetMid, 'RTCIceCandidate', canId + ':' + candidateType, 'Received ICE candidate ->'], candidate);
  1624.  
  1625. this._handleIceCandidateStats('received', targetMid, canId, candidate);
  1626. this._peerEndOfCandidatesCounter[targetMid] = this._peerEndOfCandidatesCounter[targetMid] || {};
  1627. this._peerEndOfCandidatesCounter[targetMid].len = this._peerEndOfCandidatesCounter[targetMid].len || 0;
  1628. this._peerEndOfCandidatesCounter[targetMid].hasSet = false;
  1629. this._peerEndOfCandidatesCounter[targetMid].len++;
  1630.  
  1631. this._trigger('candidateProcessingState', this.CANDIDATE_PROCESSING_STATE.RECEIVED,
  1632. targetMid, canId, candidateType, {
  1633. candidate: candidate.candidate,
  1634. sdpMid: candidate.sdpMid,
  1635. sdpMLineIndex: candidate.sdpMLineIndex
  1636. }, null);
  1637.  
  1638. if (!(this._peerConnections[targetMid] &&
  1639. this._peerConnections[targetMid].signalingState !== this.PEER_CONNECTION_STATE.CLOSED)) {
  1640. log.warn([targetMid, 'RTCIceCandidate', canId + ':' + candidateType, 'Dropping ICE candidate ' +
  1641. 'as Peer connection does not exists or is closed']);
  1642. this._handleIceCandidateStats('process_failed', targetMid, canId, candidate, 'Peer connection does not exist');
  1643. this._trigger('candidateProcessingState', this.CANDIDATE_PROCESSING_STATE.DROPPED,
  1644. targetMid, canId, candidateType, {
  1645. candidate: candidate.candidate,
  1646. sdpMid: candidate.sdpMid,
  1647. sdpMLineIndex: candidate.sdpMLineIndex
  1648. }, new Error('Failed processing ICE candidate as Peer connection does not exists or is closed.'));
  1649. this._signalingEndOfCandidates(targetMid);
  1650. return;
  1651. }
  1652.  
  1653. if (this._initOptions.filterCandidatesType[candidateType]) {
  1654. if (!(this._hasMCU && this._initOptions.forceTURN)) {
  1655. log.warn([targetMid, 'RTCIceCandidate', canId + ':' + candidateType, 'Dropping received ICE candidate as ' +
  1656. 'it matches ICE candidate filtering flag ->'], candidate);
  1657. this._handleIceCandidateStats('dropped', targetMid, canId, candidate);
  1658. this._trigger('candidateProcessingState', this.CANDIDATE_PROCESSING_STATE.DROPPED,
  1659. targetMid, canId, candidateType, {
  1660. candidate: candidate.candidate,
  1661. sdpMid: candidate.sdpMid,
  1662. sdpMLineIndex: candidate.sdpMLineIndex
  1663. }, new Error('Dropping of processing ICE candidate as it matches ICE candidate filtering flag.'));
  1664. this._signalingEndOfCandidates(targetMid);
  1665. return;
  1666. }
  1667.  
  1668. log.warn([targetMid, 'RTCIceCandidate', canId + ':' + candidateType, 'Not dropping received ICE candidate as ' +
  1669. 'TURN connections are enforced as MCU is present (and act as a TURN itself) so filtering of ICE candidate ' +
  1670. 'flags are not honoured ->'], candidate);
  1671. }
  1672.  
  1673. if (this._peerConnections[targetMid].remoteDescription && this._peerConnections[targetMid].remoteDescription.sdp &&
  1674. this._peerConnections[targetMid].localDescription && this._peerConnections[targetMid].localDescription.sdp) {
  1675. this._addIceCandidate(targetMid, canId, candidate);
  1676. } else {
  1677. this._addIceCandidateToQueue(targetMid, canId, candidate);
  1678. }
  1679.  
  1680. this._signalingEndOfCandidates(targetMid);
  1681.  
  1682. if (!this._gatheredCandidates[targetMid]) {
  1683. this._gatheredCandidates[targetMid] = {
  1684. sending: { host: [], srflx: [], relay: [] },
  1685. receiving: { host: [], srflx: [], relay: [] }
  1686. };
  1687. }
  1688.  
  1689. this._gatheredCandidates[targetMid].receiving[candidateType].push({
  1690. sdpMid: candidate.sdpMid,
  1691. sdpMLineIndex: candidate.sdpMLineIndex,
  1692. candidate: candidate.candidate
  1693. });
  1694. };
  1695.  
  1696. /**
  1697. * Function that handles the "answer" socket message received.
  1698. * See confluence docs for the "answer" expected properties to be received
  1699. * based on the current <code>SM_PROTOCOL_VERSION</code>.
  1700. * @method _answerHandler
  1701. * @private
  1702. * @for Skylink
  1703. * @since 0.5.1
  1704. */
  1705. Skylink.prototype._answerHandler = function(message) {
  1706. var self = this;
  1707. var targetMid = message.mid;
  1708. var pc = self._peerConnections[targetMid];
  1709.  
  1710. if (targetMid === 'MCU') {
  1711. self._transceiverIdPeerIdMap = message.transceiverIdPeerIdMap || {};
  1712. }
  1713.  
  1714. log.log([targetMid, null, message.type, 'Received answer from peer. Session description:'], clone(message));
  1715.  
  1716. var answer = {
  1717. type: 'answer',
  1718. sdp: self._hasMCU ? message.sdp.replace(/\r\n/g, '\n').split('\n').join('\r\n') : message.sdp
  1719. };
  1720.  
  1721. self._handleNegotiationStats('answer', targetMid, answer, true);
  1722.  
  1723. if (!pc) {
  1724. log.error([targetMid, null, message.type, 'Peer connection object not found. Unable to setRemoteDescription for answer']);
  1725. self._handleNegotiationStats('dropped_answer', targetMid, answer, true, 'Peer connection does not exist');
  1726. return;
  1727. }
  1728.  
  1729. // Add-on by Web SDK fixes
  1730. if (message.userInfo && typeof message.userInfo === 'object') {
  1731. var userInfo = message.userInfo || {};
  1732.  
  1733. self._peerInformations[targetMid].settings = userInfo.settings || {};
  1734. self._peerInformations[targetMid].mediaStatus = userInfo.mediaStatus || {};
  1735. self._peerInformations[targetMid].userData = userInfo.userData;
  1736. }
  1737.  
  1738. log.log([targetMid, 'RTCSessionDescription', message.type, 'Session description object created'], answer);
  1739.  
  1740. /*if (pc.remoteDescription ? !!pc.remoteDescription.sdp : false) {
  1741. log.warn([targetMid, null, message.type, 'Peer has an existing connection'],
  1742. pc.remoteDescription);
  1743. return;
  1744. }
  1745.  
  1746. if (pc.signalingState === self.PEER_CONNECTION_STATE.STABLE) {
  1747. log.error([targetMid, null, message.type, 'Unable to set peer connection ' +
  1748. 'at signalingState "stable". Ignoring remote answer'], pc.signalingState);
  1749. return;
  1750. }*/
  1751.  
  1752. answer.sdp = self._removeSDPFilteredCandidates(targetMid, answer);
  1753. answer.sdp = self._setSDPCodec(targetMid, answer);
  1754. answer.sdp = self._setSDPBitrate(targetMid, answer);
  1755. answer.sdp = self._setSDPCodecParams(targetMid, answer);
  1756. answer.sdp = self._removeSDPCodecs(targetMid, answer);
  1757. answer.sdp = self._removeSDPREMBPackets(targetMid, answer);
  1758. answer.sdp = self._handleSDPConnectionSettings(targetMid, answer, 'remote');
  1759. answer.sdp = self._removeSDPUnknownAptRtx(targetMid, answer);
  1760. answer.sdp = self._setSCTPport(targetMid, answer);
  1761.  
  1762. if (AdapterJS.webrtcDetectedBrowser === 'firefox') {
  1763. self._setOriginalDTLSRole(answer, true);
  1764. }
  1765.  
  1766. log.log([targetMid, 'RTCSessionDescription', message.type, 'Updated remote answer ->'], answer.sdp);
  1767.  
  1768. // This should be the state after offer is received. or even after negotiation is successful
  1769. if (pc.signalingState !== self.PEER_CONNECTION_STATE.HAVE_LOCAL_OFFER) {
  1770. log.warn([targetMid, null, message.type, 'Peer connection state is not in ' +
  1771. '"have-local-offer" state for re-negotiation. Dropping message.'], {
  1772. signalingState: pc.signalingState,
  1773. isRestart: !!message.restart
  1774. });
  1775. self._handleNegotiationStats('dropped_answer', targetMid, answer, true, 'Peer connection state is "' + pc.signalingState + '"');
  1776. return;
  1777. }
  1778.  
  1779. // Added checks if there is a current remote sessionDescription being processing before processing this one
  1780. if (pc.processingRemoteSDP) {
  1781. log.warn([targetMid, 'RTCSessionDescription', 'answer',
  1782. 'Dropping of setting local answer as there is another ' +
  1783. 'sessionDescription being processed ->'], answer);
  1784. self._handleNegotiationStats('dropped_answer', targetMid, answer, true, 'Peer connection is currently processing an existing sdp');
  1785. return;
  1786. }
  1787.  
  1788. pc.processingRemoteSDP = true;
  1789.  
  1790. if (message.userInfo) {
  1791. self._trigger('peerUpdated', targetMid, self.getPeerInfo(targetMid), false);
  1792. }
  1793.  
  1794. self._parseSDPMediaStreamIDs(targetMid, answer);
  1795.  
  1796.  
  1797. var onSuccessCbFn = function() {
  1798. log.debug([targetMid, null, message.type, 'Remote description set']);
  1799. pc.setAnswer = 'remote';
  1800. pc.processingRemoteSDP = false;
  1801.  
  1802. self._acknowledgeAnswer(targetMid, true, null);
  1803. self._handleNegotiationStats('set_answer', targetMid, answer, true);
  1804. self._trigger('handshakeProgress', self.HANDSHAKE_PROGRESS.ANSWER, targetMid);
  1805. self._addIceCandidateFromQueue(targetMid);
  1806.  
  1807. if (self._peerMessagesStamps[targetMid]) {
  1808. self._peerMessagesStamps[targetMid].hasRestart = false;
  1809. }
  1810.  
  1811. if (self._dataChannels[targetMid] && (pc.remoteDescription.sdp.indexOf('m=application') === -1 ||
  1812. pc.remoteDescription.sdp.indexOf('m=application 0') > 0)) {
  1813. log.warn([targetMid, 'RTCPeerConnection', null, 'Closing all datachannels as they were rejected.']);
  1814. self._closeDataChannel(targetMid);
  1815. }
  1816. };
  1817.  
  1818. var onErrorCbFn = function(error) {
  1819. self._acknowledgeAnswer(targetMid, false, error);
  1820. self._handleNegotiationStats('error_set_answer', targetMid, answer, true, error);
  1821. self._trigger('handshakeProgress', self.HANDSHAKE_PROGRESS.ERROR, targetMid, error);
  1822.  
  1823. pc.processingRemoteSDP = false;
  1824.  
  1825. log.error([targetMid, null, message.type, 'Failed setting remote description:'], {
  1826. error: error,
  1827. state: pc.signalingState,
  1828. answer: answer
  1829. });
  1830. };
  1831.  
  1832. pc.setRemoteDescription(new RTCSessionDescription(answer), onSuccessCbFn, onErrorCbFn);
  1833. };
  1834.  
  1835. /**
  1836. * <blockquote class="info">
  1837. * Note that this feature requires MCU to be enabled for the App Key provided in the
  1838. * <a href="#method_init"><code>init()</code> method</a>.
  1839. * </blockquote>
  1840. * Starts a RTMP session.
  1841. * @method startRTMPSession
  1842. * @param {Function} [callback] The callback function fired when request has completed.
  1843. * <small>Function parameters signature is <code>function (error, success)</code></small>
  1844. * <small>Function request completion is determined by the <a href="#event_RTMPState">
  1845. * <code>rtmpState</code> event</a> triggering <code>state</code> parameter payload as <code>START</code>.</small>
  1846. * @param {Error|String} callback.error The error result in request.
  1847. * <small>Defined as <code>null</code> when there are no errors in request</small>
  1848. * <small>Object signature is the <code>startRTMPSession()</code> error when starting a new rtmp session.</small>
  1849. * @param {String|JSON} callback.success The success result in request.
  1850. * <small>Defined as <code>null</code> when there are errors in request</small>
  1851. * <small>Object signature is the <a href="#event_RTMPState">
  1852. * <code>RTMPState</code> event</a> triggered <code>RTMPId</code> parameter payload.</small>
  1853. * @example
  1854. * // Example 1: Start RTMP session
  1855. * skylinkDemo.startRTMPSession(function (error, success) {
  1856. * if (error) return;
  1857. * console.info("RTMP session has started. ID ->", success);
  1858. * });
  1859. * @trigger <ol class="desc-seq">
  1860. * <li>If MCU is not connected: <ol><li><b>ABORT</b> and return error.</li></ol></li>
  1861. * <li>Sends to MCU via Signaling server to start RTMP session. <ol>
  1862. * <li>If RTMP session has been started successfully: <ol>
  1863. * <li><a href="#event_RTMPState"><code>RTMPState</code> event</a> triggers
  1864. * parameter payload <code>state</code> as <code>START</code>.</li></ol></li></ol></li></ol>
  1865. * @beta
  1866. * @for Skylink
  1867. * @since 0.6.36
  1868. */
  1869. Skylink.prototype.startRTMPSession = function (streamId, endpoint, callback) {
  1870. var self = this;
  1871.  
  1872. if (!self._hasMCU) {
  1873. var noMCUError = 'Unable to start RTMP session as MCU is not connected';
  1874. log.error(noMCUError);
  1875. if (typeof callback === 'function') {
  1876. callback(new Error(noMCUError), null);
  1877. }
  1878. return;
  1879. }
  1880. if (!streamId) {
  1881. var nostreamIdError = 'Unable to start RTMP Session stream id is missing';
  1882. log.error(nostreamIdError);
  1883. if (typeof callback === 'function') {
  1884. callback(new Error(nostreamIdError), null);
  1885. }
  1886. return;
  1887. }
  1888. if (!endpoint) {
  1889. var noEndpointError = 'Unable to start RTMP Session as Endpoint is missing';
  1890. log.error(noEndpointError);
  1891. if (typeof callback === 'function') {
  1892. callback(new Error(noEndpointError), null);
  1893. }
  1894. return;
  1895. }
  1896. var rtmpId = self.generateUUID();
  1897.  
  1898. if (typeof callback === 'function') {
  1899. self.once('RTMPState', function (state, RTMPId) {
  1900. callback(RTMPId);
  1901. }, function (state) {
  1902. return state === self.RTMP_STATE.START;
  1903. });
  1904. }
  1905.  
  1906. self._sendChannelMessage({
  1907. type: self._SIG_MESSAGE_TYPE.START_RTMP,
  1908. rid: self._room.id,
  1909. target: 'MCU',
  1910. mid: self._user.sid,
  1911. streamId: streamId,
  1912. endpoint: endpoint,
  1913. rtmpId: rtmpId
  1914. });
  1915.  
  1916. log.debug(['MCU', 'RTMP', null, 'Starting RTMP Session']);
  1917. };
  1918.  
  1919.  
  1920.  
  1921. /**
  1922. * <blockquote class="info">
  1923. * Note that this feature requires MCU to be enabled for the App Key provided in the
  1924. * <a href="#method_init"><code>init()</code> method</a>.
  1925. * </blockquote>
  1926. * Stops a RTMP session.
  1927. * @param {Function} [callback] The callback function fired when request has completed.
  1928. * <small>Function parameters signature is <code>function (error, success)</code></small>
  1929. * <small>Function request completion is determined by the <a href="#event_RTMPState">
  1930. * <code>RTMPState</code> event</a> triggering <code>state</code> parameter payload as <code>STOP</code>
  1931. * @param {Error|String} callback.error The error result in request.
  1932. * <small>Defined as <code>null</code> when there are no errors in request</small>
  1933. * <small>Object signature is the <code>stopRTMPSession()</code> error when stopping current RTMP session.</small>
  1934. * @param {String|JSON} callback.success The success result in request.
  1935. * @method stopRTMPSession
  1936. * @example
  1937. * // Example 1: Stop RTMP session
  1938. * skylinkDemo.stopRTMPSession(function (error, success) {
  1939. * if (error) return;
  1940. * console.info("RTMP session has stopped. ID ->", success);
  1941. * });
  1942. * @beta
  1943. * @for Skylink
  1944. * @since 0.6.36
  1945. */
  1946. Skylink.prototype.stopRTMPSession = function (rtmpId, callback) {
  1947. var self = this;
  1948.  
  1949. if (!self._hasMCU) {
  1950. var noMCUError = 'Unable to stop RTMP as MCU is not connected';
  1951. log.error(noMCUError);
  1952. if (typeof callback === 'function') {
  1953. callback(new Error(noMCUError), null);
  1954. }
  1955. return;
  1956. }
  1957.  
  1958. if (typeof callback === 'function') {
  1959. self.once('RTMPState', function (state, rtmpId) {
  1960. callback(rtmpId);
  1961. }, function (state) {
  1962. return state === self.RTMP_STATE.STOP;
  1963. });
  1964. }
  1965.  
  1966. self._sendChannelMessage({
  1967. type: self._SIG_MESSAGE_TYPE.STOP_RTMP,
  1968. rid: self._room.id,
  1969. rtmpId: rtmpId,
  1970. mid: self._user.sid,
  1971. target: 'MCU'
  1972. });
  1973.  
  1974. log.debug(['MCU', 'RTMP', null, 'Stopping RTMP Session']);
  1975. };
  1976.  
  1977. /**
  1978. * Handles the RTMP Protocol message event received from the platform signaling.
  1979. * @method _rtmpEventHandler
  1980. * @param {JSON} message The message object received from platform signaling.
  1981. * This should contain the <code>RTMP</code> payload.
  1982. * @param {String} message.action The RTMP action received.
  1983. * @param {String} message.error The RTMP error exception received.
  1984. * @private
  1985. * @beta
  1986. * @for Skylink
  1987. * @since 0.6.36
  1988. */
  1989. Skylink.prototype._rtmpEventHandler = function (message) {
  1990. var self = this;
  1991.  
  1992. log.debug(['MCU', 'RTMP', null, 'Received RTMP Session message ->'], message);
  1993.  
  1994. if (message.action === 'startSuccess') {
  1995. if (!self._rtmpSessions[message.rtmpId]) {
  1996. log.debug(['MCU', 'RTMP', message.rtmpId, 'Started RTMP Session']);
  1997.  
  1998. self._rtmpSessions[message.rtmpId] = {
  1999. active: true,
  2000. state: self.RTMP_STATE.START,
  2001. startedDateTime: (new Date()).toISOString(),
  2002. endedDateTime: null,
  2003. peerId: message.peerId,
  2004. streamId: message.streamId
  2005. };
  2006. self._trigger('RTMPState', self.RTMP_STATE.START, message.rtmpId, null, null);
  2007. }
  2008.  
  2009. } else if (message.action === 'stopSuccess') {
  2010.  
  2011. if (!self._rtmpSessions[message.rtmpId]) {
  2012. log.error(['MCU', 'RTMP', message.rtmpId, 'Received request of "off" but the session is empty']);
  2013. return;
  2014. }
  2015.  
  2016. log.debug(['MCU', 'RTMP', message.rtmpId, 'Stopped RTMP Session']);
  2017.  
  2018. self._rtmpSessions[message.rtmpId].active = false;
  2019. self._rtmpSessions[message.rtmpId].state = self.RTMP_STATE.STOP;
  2020. self._rtmpSessions[message.rtmpId].endedDateTime = (new Date()).toISOString();
  2021. self._trigger('RTMPState', self.RTMP_STATE.STOP, message.rtmpId, null, null);
  2022.  
  2023. } else {
  2024. var rtmpError = new Error(message.error || 'Unknown error');
  2025.  
  2026. if (!self._rtmpSessions[message.rtmpId]) {
  2027. log.error(['MCU', 'RTMP', message.rtmpId, 'Received error but the session is empty ->'], rtmpError);
  2028. return;
  2029. }
  2030.  
  2031. log.error(['MCU', 'RTMP', message.rtmpId, 'RTMP session failure ->'], rtmpError);
  2032.  
  2033. self._rtmpSessions[message.rtmpId].state = self.RTMP_STATE.ERROR;
  2034. self._rtmpSessions[message.rtmpId].error = rtmpError;
  2035.  
  2036. if (self._rtmpSessions[message.rtmpId].active) {
  2037. log.debug(['MCU', 'RTMP', message.rtmpId, 'Stopped RTMP session abruptly']);
  2038. self._rtmpSessions[message.rtmpId].active = false;
  2039. }
  2040.  
  2041. self._trigger('RTMPState', self.RTMP_STATE.ERROR, message.rtmpId, null, rtmpError);
  2042. }
  2043. };
  2044.  
  2045. /**
  2046. * Function that compares the SM / DT protocol versions to see if it in the version.
  2047. * @method _isLowerThanVersion
  2048. * @private
  2049. * @for Skylink
  2050. * @since 0.6.16
  2051. */
  2052. Skylink.prototype._isLowerThanVersion = function (agentVer, requiredVer) {
  2053. var partsA = (agentVer || '').split('.');
  2054. var partsB = (requiredVer || '').split('.');
  2055.  
  2056. for (var i = 0; i < partsB.length; i++) {
  2057. if ((partsA[i] || '0') < (partsB[i] || '0')) {
  2058. return true;
  2059. }
  2060. }
  2061.  
  2062. return false;
  2063. };
  2064.  
  2065. /**
  2066. * Function that sends a SIG_MESSAGE_TYPE.ANSWER_ACK message to MCU to denote that SDP negotiation has completed (either with success or error)
  2067. * @method _acknowledgeAnswer
  2068. * @private
  2069. * @for Skylink
  2070. * @since 1.0.0
  2071. */
  2072. Skylink.prototype._acknowledgeAnswer = function (targetMid, isSuccess, error) {
  2073. var self = this;
  2074. if (self._hasMCU) {
  2075. var statsStateKey = isSuccess ? 'set_answer_ack' : 'error_set_answer_ack';
  2076. var answerAckMessage = {
  2077. rid: self._room.id,
  2078. mid: self._user.sid,
  2079. target: targetMid,
  2080. success: isSuccess,
  2081. type: self._SIG_MESSAGE_TYPE.ANSWER_ACK,
  2082. };
  2083. self._sendChannelMessage(answerAckMessage);
  2084. log.debug(['MCU', 'Remote Description', null, 'Answer acknowledgement message sent to MCU via SIG. Message body -->'], answerAckMessage);
  2085. self._handleNegotiationStats(statsStateKey, targetMid, answerAckMessage, true, error);
  2086. }
  2087. return false;
  2088. };
  2089.  
  2090.