File: source/room-connection.js

  1. /**
  2. * The list of Signaling server reaction states during <a href="#method_joinRoom"><code>joinRoom()</code> method</a>.
  3. * @attribute SYSTEM_ACTION
  4. * @param {String} WARNING <small>Value <code>"warning"</code></small>
  5. * The value of the state when Room session is about to end.
  6. * @param {String} REJECT <small>Value <code>"reject"</code></small>
  7. * The value of the state when Room session has failed to start or has ended.
  8. * @type JSON
  9. * @readOnly
  10. * @for Skylink
  11. * @since 0.5.1
  12. */
  13. Skylink.prototype.SYSTEM_ACTION = {
  14. WARNING: 'warning',
  15. REJECT: 'reject'
  16. };
  17.  
  18. /**
  19. * The list of Signaling server reaction states reason of action code during
  20. * <a href="#method_joinRoom"><code>joinRoom()</code> method</a>.
  21. * @attribute SYSTEM_ACTION_REASON
  22. * @param {String} CREDENTIALS_EXPIRED <small>Value <code>"oldTimeStamp"</code></small>
  23. * The value of the reason code when Room session token has expired.
  24. * <small>Happens during <a href="#method_joinRoom"><code>joinRoom()</code> method</a> request.</small>
  25. * <small>Results with: <code>REJECT</code></small>
  26. * @param {String} CREDENTIALS_ERROR <small>Value <code>"credentialError"</code></small>
  27. * The value of the reason code when Room session token provided is invalid.
  28. * <small>Happens during <a href="#method_joinRoom"><code>joinRoom()</code> method</a> request.</small>
  29. * @param {String} DUPLICATED_LOGIN <small>Value <code>"duplicatedLogin"</code></small>
  30. * The value of the reason code when Room session token has been used already.
  31. * <small>Happens during <a href="#method_joinRoom"><code>joinRoom()</code> method</a> request.</small>
  32. * <small>Results with: <code>REJECT</code></small>
  33. * @param {String} ROOM_NOT_STARTED <small>Value <code>"notStart"</code></small>
  34. * The value of the reason code when Room session has not started.
  35. * <small>Happens during <a href="#method_joinRoom"><code>joinRoom()</code> method</a> request.</small>
  36. * <small>Results with: <code>REJECT</code></small>
  37. * @param {String} EXPIRED <small>Value <code>"expired"</code></small>
  38. * The value of the reason code when Room session has ended already.
  39. * <small>Happens during <a href="#method_joinRoom"><code>joinRoom()</code> method</a> request.</small>
  40. * <small>Results with: <code>REJECT</code></small>
  41. * @param {String} ROOM_LOCKED <small>Value <code>"locked"</code></small>
  42. * The value of the reason code when Room is locked.
  43. * <small>Happens during <a href="#method_joinRoom"><code>joinRoom()</code> method</a> request.</small>
  44. * <small>Results with: <code>REJECT</code></small>
  45. * @param {String} FAST_MESSAGE <small>Value <code>"fastmsg"</code></small>
  46. * The value of the reason code when User is flooding socket messages to the Signaling server
  47. * that is sent too quickly within less than a second interval.
  48. * <small>Happens after Room session has started. This can be caused by various methods like
  49. * <a href="#method_sendMessage"><code>sendMessage()</code> method</a>,
  50. * <a href="#method_setUserData"><code>setUserData()</code> method</a>,
  51. * <a href="#method_muteStream"><code>muteStream()</code> method</a>,
  52. * <a href="#method_enableAudio"><code>enableAudio()</code> method</a>,
  53. * <a href="#method_enableVideo"><code>enableVideo()</code> method</a>,
  54. * <a href="#method_disableAudio"><code>disableAudio()</code> method</a> and
  55. * <a href="#method_disableVideo"><code>disableVideo()</code> method</a></small>
  56. * <small>Results with: <code>WARNING</code></small>
  57. * @param {String} ROOM_CLOSING <small>Value <code>"toClose"</code></small>
  58. * The value of the reason code when Room session is ending.
  59. * <small>Happens after Room session has started. This serves as a prerequisite warning before
  60. * <code>ROOM_CLOSED</code> occurs.</small>
  61. * <small>Results with: <code>WARNING</code></small>
  62. * @param {String} ROOM_CLOSED <small>Value <code>"roomclose"</code></small>
  63. * The value of the reason code when Room session has just ended.
  64. * <small>Happens after Room session has started.</small>
  65. * <small>Results with: <code>REJECT</code></small>
  66. * @param {String} SERVER_ERROR <small>Value <code>"serverError"</code></small>
  67. * The value of the reason code when Room session fails to start due to some technical errors.
  68. * <small>Happens during <a href="#method_joinRoom"><code>joinRoom()</code> method</a> request.</small>
  69. * <small>Results with: <code>REJECT</code></small>
  70. * @param {String} KEY_ERROR <small>Value <code>"keyFailed"</code></small>
  71. * The value of the reason code when Room session fails to start due to some technical error pertaining to
  72. * App Key initialization.
  73. * <small>Happens during <a href="#method_joinRoom"><code>joinRoom()</code> method</a> request.</small>
  74. * <small>Results with: <code>REJECT</code></small>
  75. * @type JSON
  76. * @readOnly
  77. * @for Skylink
  78. * @since 0.5.2
  79. */
  80. Skylink.prototype.SYSTEM_ACTION_REASON = {
  81. CREDENTIALS_EXPIRED: 'oldTimeStamp',
  82. CREDENTIALS_ERROR: 'credentialError',
  83. DUPLICATED_LOGIN: 'duplicatedLogin',
  84. ROOM_NOT_STARTED: 'notStart',
  85. EXPIRED: 'expired',
  86. ROOM_LOCKED: 'locked',
  87. FAST_MESSAGE: 'fastmsg',
  88. ROOM_CLOSING: 'toclose',
  89. ROOM_CLOSED: 'roomclose',
  90. SERVER_ERROR: 'serverError',
  91. KEY_ERROR: 'keyFailed'
  92. };
  93.  
  94. /**
  95. * Stores the current Room name that User is connected to.
  96. * @attribute _selectedRoom
  97. * @type String
  98. * @private
  99. * @for Skylink
  100. * @since 0.3.0
  101. */
  102. Skylink.prototype._selectedRoom = null;
  103.  
  104. /**
  105. * Stores the flag that indicates if Room is locked.
  106. * @attribute _roomLocked
  107. * @type Boolean
  108. * @private
  109. * @for Skylink
  110. * @since 0.5.2
  111. */
  112. Skylink.prototype._roomLocked = false;
  113.  
  114. /**
  115. * Stores the flag that indicates if User is connected to the Room.
  116. * @attribute _inRoom
  117. * @type Boolean
  118. * @private
  119. * @for Skylink
  120. * @since 0.4.0
  121. */
  122. Skylink.prototype._inRoom = false;
  123.  
  124. /**
  125. * Function that starts the Room session.
  126. * @method joinRoom
  127. * @param {String} [room] The Room name.
  128. * - When not provided, its value is the <code>options.defaultRoom</code> provided in the
  129. * <a href="#method_init"><code>init()</code> method</a>.
  130. * <small>Note that if you are using credentials based authentication, you cannot switch the Room
  131. * that is not the same as the <code>options.defaultRoom</code> defined in the
  132. * <a href="#method_init"><code>init()</code> method</a>.</small>
  133. * @param {JSON} [options] The Room session configuration options.
  134. * @param {JSON|String} [options.userData] The User custom data.
  135. * <small>This can be set after Room session has started using the
  136. * <a href="#method_setUserData"><code>setUserData()</code> method</a>.</small>
  137. * @param {Boolean} [options.useExactConstraints] The <a href="#method_getUserMedia"><code>getUserMedia()</code>
  138. * method</a> <code>options.useExactConstraints</code> parameter settings.
  139. * <small>See the <code>options.useExactConstraints</code> parameter in the
  140. * <a href="#method_getUserMedia"><code>getUserMedia()</code> method</a> for more information.</small>
  141. * @param {Boolean|JSON} [options.audio] The <a href="#method_getUserMedia"><code>getUserMedia()</code>
  142. * method</a> <code>options.audio</code> parameter settings.
  143. * <small>When value is defined as <code>true</code> or an object, <a href="#method_getUserMedia">
  144. * <code>getUserMedia()</code> method</a> to be invoked to retrieve new Stream. If
  145. * <code>options.video</code> is not defined, it will be defined as <code>false</code>.</small>
  146. * <small>Object signature matches the <code>options.audio</code> parameter in the
  147. * <a href="#method_getUserMedia"><code>getUserMedia()</code> method</a>.</small>
  148. * @param {Boolean|JSON} [options.video] The <a href="#method_getUserMedia"><code>getUserMedia()</code>
  149. * method</a> <code>options.video</code> parameter settings.
  150. * <small>When value is defined as <code>true</code> or an object, <a href="#method_getUserMedia">
  151. * <code>getUserMedia()</code> method</a> to be invoked to retrieve new Stream. If
  152. * <code>options.audio</code> is not defined, it will be defined as <code>false</code>.</small>
  153. * <small>Object signature matches the <code>options.video</code> parameter in the
  154. * <a href="#method_getUserMedia"><code>getUserMedia()</code> method</a>.</small>
  155. * @param {JSON} [options.bandwidth] <blockquote class="info">Note that this is currently not supported
  156. * with Firefox browsers versions 48 and below as noted in an existing
  157. * <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=976521#c21">bugzilla ticket here</a>.</blockquote>
  158. * The configuration to set the maximum streaming bandwidth sent to Peers.
  159. * @param {Number} [options.bandwidth.audio] The maximum audio streaming bandwidth sent to Peers in kbps.
  160. * <small>Recommended values are <code>50</code> to <code>200</code>. <code>50</code> is sufficient enough for
  161. * an audio call. The higher you go if you want clearer audio and to be able to hear music streaming.</small>
  162. * @param {Number} [options.bandwidth.video] The maximum video streaming bandwidth sent to Peers.
  163. * <small>Recommended values are <code>256</code>-<code>500</code> for 180p quality,
  164. * <code>500</code>-<code>1024</code> for 360p quality, <code>1024</code>-<code>2048</code> for 720p quality
  165. * and <code>2048</code>-<code>4096</code> for 1080p quality.</small>
  166. * @param {Number} [options.bandwidth.data] The maximum data streaming bandwidth sent to Peers.
  167. * <small>This affects the P2P messaging in <a href="#method_sendP2PMessage"><code>sendP2PMessage()</code> method</a>,
  168. * and data transfers in <a href="#method_sendBlobData"><code>sendBlobData()</code> method</a> and
  169. * <a href="#method_sendURLData"><code>sendURLData()</code> method</a>.</small>
  170. * @param {Boolean} [options.manualGetUserMedia] The flag if <code>joinRoom()</code> should trigger
  171. * <a href="#event_mediaAccessRequired"><code>mediaAccessRequired</code> event</a> in which the
  172. * <a href="#method_getUserMedia"><code>getUserMedia()</code> Stream</a> or
  173. * <a href="#method_shareScreen"><code>shareScreen()</code> Stream</a>
  174. * must be retrieved as a requirement before Room session may begin.
  175. * <small>This ignores the <code>options.audio</code> and <code>options.video</code> configuration.</small>
  176. * <small>After 30 seconds without any Stream retrieved, this results in the `callback(error, ..)` result.</small>
  177. * @param {Function} [callback] The callback function fired when request has completed.
  178. * <small>Function parameters signature is <code>function (error, success)</code></small>
  179. * <small>Function request completion is determined by the <a href="#event_peerJoined">
  180. * <code>peerJoined</code> event</a> triggering <code>isSelf</code> parameter payload value as <code>true</code>
  181. * for request success.</small>
  182. * @param {JSON} callback.error The error result in request.
  183. * <small>Defined as <code>null</code> when there are no errors in request</small>
  184. * @param {Error|String} callback.error.error The error received when starting Room session has failed.
  185. * @param {Number} callback.error.errorCode The current <a href="#method_init"><code>init()</code> method</a> ready state.
  186. * [Rel: Skylink.READY_STATE_CHANGE]
  187. * @param {String} callback.error.room The Room name.
  188. * @param {JSON} callback.success The success result in request.
  189. * <small>Defined as <code>null</code> when there are errors in request</small>
  190. * @param {String} callback.success.room The Room name.
  191. * @param {String} callback.success.peerId The User's Room session Peer ID.
  192. * @param {JSON} callback.success.peerInfo The User's current Room session information.
  193. * <small>Object signature matches the <code>peerInfo</code> parameter payload received in the
  194. * <a href="#event_peerJoined"><code>peerJoined</code> event</a>.</small>
  195. * @example
  196. * // Example 1: Connecting to the default Room without Stream
  197. * skylinkDemo.joinRoom(function (error, success) {
  198. * if (error) return;
  199. * console.log("User connected.");
  200. * });
  201. *
  202. * // Example 2: Connecting to Room "testxx" with Stream
  203. * skylinkDemo.joinRoom("testxx", {
  204. * audio: true,
  205. * video: true
  206. * }, function (error, success) {
  207. * if (error) return;
  208. * console.log("User connected with getUserMedia() Stream.")
  209. * });
  210. *
  211. * // Example 3: Connecting to default Room with Stream retrieved earlier
  212. * skylinkDemo.getUserMedia(function (gUMError, gUMSuccess) {
  213. * if (gUMError) return;
  214. * skylinkDemo.joinRoom(function (error, success) {
  215. * if (error) return;
  216. * console.log("User connected with getUserMedia() Stream.");
  217. * });
  218. * });
  219. *
  220. * // Example 4: Connecting to "testxx" Room with shareScreen() Stream retrieved manually
  221. * skylinkDemo.on("mediaAccessRequired", function () {
  222. * skylinkDemo.shareScreen(function (sSError, sSSuccess) {
  223. * if (sSError) return;
  224. * });
  225. * });
  226. *
  227. * skylinkDemo.joinRoom("testxx", {
  228. * manualGetUserMedia: true
  229. * }, function (error, success) {
  230. * if (error) return;
  231. * console.log("User connected with shareScreen() Stream.");
  232. * });
  233. *
  234. * // Example 5: Connecting to "testxx" Room with User custom data
  235. * var data = { username: "myusername" };
  236. * skylinkDemo.joinRoom("testxx", {
  237. * userData: data
  238. * }, function (error, success) {
  239. * if (error) return;
  240. * console.log("User connected with correct user data?", success.peerInfo.userData.username === data.username);
  241. * });
  242. * @trigger <ol class="desc-seq">
  243. * <li>If User is in a Room: <ol>
  244. * <li>Invoke <a href="#method_leaveRoom"><code>leaveRoom()</code> method</a>
  245. * to end current Room connection. <small>Invoked <a href="#method_leaveRoom"><code>leaveRoom()</code>
  246. * method</a> <code>stopMediaOptions</code> parameter value will be <code>false</code>.</small>
  247. * <small>Regardless of request errors, <code>joinRoom()</code> will still proceed.</small></li></ol></li>
  248. * <li>Check if Room name provided matches the Room name of the currently retrieved Room session token. <ol>
  249. * <li>If Room name does not matches: <ol>
  250. * <li>Invoke <a href="#method_init"><code>init()</code> method</a> to retrieve new Room session token. <ol>
  251. * <li>If request has errors: <ol><li><b>ABORT</b> and return error.</li></ol></li></ol></li></ol></li></ol></li>
  252. * <li>Open a new socket connection to Signaling server. <ol><li>If Socket connection fails: <ol>
  253. * <li><a href="#event_socketError"><code>socketError</code> event</a> triggers parameter payload
  254. * <code>errorCode</code> as <code>CONNECTION_FAILED</code>. <ol>
  255. * <li>Checks if there are fallback ports and transports to use. <ol>
  256. * <li>If there are still fallback ports and transports: <ol>
  257. * <li>Attempts to retry socket connection to Signaling server. <ol>
  258. * <li><a href="#event_channelRetry"><code>channelRetry</code> event</a> triggers.</li>
  259. * <li><a href="#event_socketError"><code>socketError</code> event</a> triggers parameter
  260. * payload <code>errorCode</code> as <code>RECONNECTION_ATTEMPT</code>.</li>
  261. * <li>If attempt to retry socket connection to Signaling server has failed: <ol>
  262. * <li><a href="#event_socketError"><code>socketError</code> event</a> triggers parameter payload
  263. * <code>errorCode</code> as <code>RECONNECTION_FAILED</code>.</li>
  264. * <li>Checks if there are still any more fallback ports and transports to use. <ol>
  265. * <li>If there are is no more fallback ports and transports to use: <ol>
  266. * <li><a href="#event_socketError"><code>socketError</code> event</a> triggers
  267. * parameter payload <code>errorCode</code> as <code>RECONNECTION_ABORTED</code>.</li>
  268. * <li><b>ABORT</b> and return error.</li></ol></li><li>Else: <ol><li><b>REPEAT</b> attempt to retry socket connection
  269. * to Signaling server step.</li></ol></li></ol></li></ol></li></ol></li></ol></li><li>Else: <ol>
  270. * <li><a href="#event_socketError"><code>socketError</code> event</a> triggers
  271. * parameter payload <code>errorCode</code> as <code>CONNECTION_ABORTED</code>.</li>
  272. * <li><b>ABORT</b> and return error.</li></ol></li></ol></li></ol></li></ol></li>
  273. * <li>If socket connection to Signaling server has opened: <ol>
  274. * <li><a href="#event_channelOpen"><code>channelOpen</code> event</a> triggers.</li></ol></li></ol></li>
  275. * <li>Checks if there is <code>options.manualGetUserMedia</code> requested <ol><li>If it is requested: <ol>
  276. * <li><a href="#event_mediaAccessRequired"><code>mediaAccessRequired</code> event</a> triggers.</li>
  277. * <li>If more than 30 seconds has passed and no <a href="#method_getUserMedia"><code>getUserMedia()</code> Stream</a>
  278. * or <a href="#method_shareScreen"><code>shareScreen()</code> Stream</a>
  279. * has been retrieved: <ol><li><b>ABORT</b> and return error.</li></ol></li></ol></li><li>Else: <ol>
  280. * <li>If there is <code>options.audio</code> or <code>options.video</code> requested: <ol>
  281. * <li>Invoke <a href="#method_getUserMedia"><code>getUserMedia()</code> method</a>. <ol>
  282. * <li>If request has errors: <ol><li><b>ABORT</b> and return error.</li></ol></li></ol></li></ol></li></ol></li>
  283. * </ol></li><li>Starts the Room session <ol><li>If Room session has started successfully: <ol>
  284. * <li><a href="#event_peerJoined"><code>peerJoined</code> event</a> triggers parameter payload
  285. * <code>isSelf</code> value as <code>true</code>.</li>
  286. * <li>If MCU is enabled for the App Key provided in <a href="#method_init"><code>init()</code>
  287. * method</a> and connected: <ol><li><a href="#event_serverPeerJoined"><code>serverPeerJoined</code>
  288. * event</a> triggers <code>serverPeerType</code> as <code>MCU</code>. <small>MCU has
  289. * to be present in the Room in order for Peer connections to commence.</small></li>
  290. * <li>Checks for any available Stream <ol>
  291. * <li>If <a href="#method_shareScreen"><code>shareScreen()</code> Stream</a> is available: <ol>
  292. * <li><a href="#event_incomingStream"><code>incomingStream</code> event</a>
  293. * triggers parameter payload <code>isSelf</code> value as <code>true</code> and <code>stream</code>
  294. * as <a href="#method_shareScreen"><code>shareScreen()</code> Stream</a>.
  295. * <small>User will be sending <a href="#method_shareScreen"><code>shareScreen()</code> Stream</a>
  296. * to Peers.</small></li></ol></li>
  297. * <li>Else if <a href="#method_getUserMedia"><code>getUserMedia()</code> Stream</a> is available: <ol>
  298. * <li><a href="#event_incomingStream"><code>incomingStream</code> event</a> triggers parameter
  299. * payload <code>isSelf</code> value as <code>true</code> and <code>stream</code> as
  300. * <a href="#method_getUserMedia"><code>getUserMedia()</code> Stream</a>.
  301. * <small>User will be sending <code>getUserMedia()</code> Stream to Peers.</small></li></ol></li><li>Else: <ol>
  302. * <li>No Stream will be sent.</li></ol></li></ol></li></ol></li></ol></li><li>Else: <ol>
  303. * <li><a href="#event_systemAction"><code>systemAction</code> event</a> triggers
  304. * parameter payload <code>action</code> as <code>REJECT</code>.</li>
  305. * <li><b>ABORT</b> and return error.</li></ol></li></ol></li></ol>
  306. * @for Skylink
  307. * @since 0.5.5
  308. */
  309.  
  310. Skylink.prototype.joinRoom = function(room, mediaOptions, callback) {
  311. var self = this;
  312. var error;
  313. var stopStream = false;
  314. var previousRoom = self._selectedRoom;
  315.  
  316. if (room === null) {
  317. error = 'Invalid room name is provided';
  318. log.error(error, room);
  319.  
  320. if (typeof mediaOptions === 'function') {
  321. callback = mediaOptions;
  322. mediaOptions = undefined;
  323. }
  324.  
  325. if (typeof callback === 'function') {
  326. callback({
  327. room: room,
  328. errorCode: self._readyState,
  329. error: new Error(error)
  330. }, null);
  331. }
  332. return;
  333. }
  334. else if (typeof room === 'string') {
  335. //joinRoom(room+); - skip
  336.  
  337. //joinRoom(room+,mediaOptions+) - skip
  338.  
  339. // joinRoom(room+,callback+)
  340. if (typeof mediaOptions === 'function') {
  341. callback = mediaOptions;
  342. mediaOptions = undefined;
  343.  
  344. // joinRoom(room+, mediaOptions-)
  345. } else if (typeof mediaOptions !== 'undefined') {
  346. if (mediaOptions === null || typeof mediaOptions !== 'object') {
  347. error = 'Invalid mediaOptions is provided';
  348. log.error(error, mediaOptions);
  349.  
  350. // joinRoom(room+,mediaOptions-,callback+)
  351. if (typeof callback === 'function') {
  352. callback({
  353. room: room,
  354. errorCode: self._readyState,
  355. error: new Error(error)
  356. }, null);
  357. }
  358. return;
  359. }
  360. }
  361.  
  362. } else if (typeof room === 'object') {
  363. //joinRoom(mediaOptions+, callback);
  364. if (typeof mediaOptions === 'function') {
  365. callback = mediaOptions;
  366. }
  367.  
  368. //joinRoom(mediaOptions);
  369. mediaOptions = room;
  370. room = undefined;
  371.  
  372. } else if (typeof room === 'function') {
  373. //joinRoom(callback);
  374. callback = room;
  375. room = undefined;
  376. mediaOptions = undefined;
  377.  
  378. } else if (typeof room !== 'undefined') {
  379. //joinRoom(mediaOptions-,callback?);
  380. error = 'Invalid mediaOptions is provided';
  381. log.error(error, mediaOptions);
  382.  
  383. if (typeof mediaOptions === 'function') {
  384. callback = mediaOptions;
  385. mediaOptions = undefined;
  386. }
  387.  
  388. if (typeof callback === 'function') {
  389. callback({
  390. room: self._defaultRoom,
  391. errorCode: self._readyState,
  392. error: new Error(error)
  393. }, null);
  394. return;
  395. }
  396. }
  397.  
  398. // If no room provided, join the default room
  399. if (!room) {
  400. room = self._defaultRoom;
  401. }
  402.  
  403. //if none of the above is true --> joinRoom()
  404. var channelCallback = function (error, success) {
  405. if (error) {
  406. if (typeof callback === 'function') {
  407. callback({
  408. error: error,
  409. errorCode: null,
  410. room: self._selectedRoom
  411. }, null);
  412. }
  413. } else {
  414. if (typeof callback === 'function') {
  415. self.once('peerJoined', function(peerId, peerInfo, isSelf) {
  416. // keep returning _inRoom false, so do a wait
  417. self._wait(function () {
  418. log.log([null, 'Socket', self._selectedRoom, 'Peer joined. Firing callback. ' +
  419. 'PeerId ->'
  420. ], peerId);
  421. callback(null, {
  422. room: self._selectedRoom,
  423. peerId: peerId,
  424. peerInfo: peerInfo
  425. });
  426. }, function () {
  427. return self._inRoom;
  428. }, false);
  429. }, function(peerId, peerInfo, isSelf) {
  430. return isSelf;
  431. }, false);
  432. }
  433.  
  434. self._sendChannelMessage({
  435. type: self._SIG_MESSAGE_TYPE.JOIN_ROOM,
  436. uid: self._user.uid,
  437. cid: self._key,
  438. rid: self._room.id,
  439. userCred: self._user.token,
  440. timeStamp: self._user.timeStamp,
  441. apiOwner: self._appKeyOwner,
  442. roomCred: self._room.token,
  443. start: self._room.startDateTime,
  444. len: self._room.duration,
  445. isPrivileged: self._isPrivileged === true, // Default to false if undefined
  446. autoIntroduce: self._autoIntroduce !== false, // Default to true if undefined
  447. key: self._appKey
  448. });
  449. }
  450. };
  451.  
  452. if (self._inRoom) {
  453. if (typeof mediaOptions === 'object') {
  454. if (mediaOptions.audio === false && mediaOptions.video === false) {
  455. stopStream = true;
  456. log.warn([null, 'MediaStream', self._selectedRoom, 'Stopping current MediaStream ' +
  457. 'as provided settings for audio and video is false (' + stopStream + ')'], mediaOptions);
  458. }
  459. }
  460.  
  461. log.log([null, 'Socket', previousRoom, 'Leaving room before joining new room'], self._selectedRoom);
  462.  
  463. self.leaveRoom(stopStream, function(error, success) {
  464. log.log([null, 'Socket', previousRoom, 'Leave room callback result'], {
  465. error: error,
  466. success: success
  467. });
  468. log.log([null, 'Socket', self._selectedRoom, 'Joining room. Media options:'], mediaOptions);
  469. if (typeof room === 'string' ? room !== self._selectedRoom : false) {
  470. self._initSelectedRoom(room, function(errorObj) {
  471. if (errorObj) {
  472. if (typeof callback === 'function') {
  473. callback({
  474. room: self._selectedRoom,
  475. errorCode: self._readyState,
  476. error: new Error(errorObj)
  477. }, null);
  478. }
  479. } else {
  480. self._waitForOpenChannel(mediaOptions, channelCallback);
  481. }
  482. });
  483. } else {
  484. self._waitForOpenChannel(mediaOptions, channelCallback);
  485. }
  486. });
  487.  
  488. } else {
  489. log.log([null, 'Socket', self._selectedRoom, 'Joining room. Media options:'],
  490. mediaOptions);
  491.  
  492. var isNotSameRoom = typeof room === 'string' ? room !== self._selectedRoom : false;
  493.  
  494. if (isNotSameRoom) {
  495. self._initSelectedRoom(room, function(errorObj) {
  496. if (errorObj) {
  497. if (typeof callback === 'function') {
  498. callback({
  499. room: self._selectedRoom,
  500. errorCode: self._readyState,
  501. error: new Error(errorObj)
  502. }, null);
  503. }
  504. } else {
  505. self._waitForOpenChannel(mediaOptions, channelCallback);
  506. }
  507. });
  508. } else {
  509. self._waitForOpenChannel(mediaOptions, channelCallback);
  510. }
  511. }
  512. };
  513.  
  514. /**
  515. * Function that stops Room session.
  516. * @method leaveRoom
  517. * @param {Boolean|JSON} [stopMediaOptions=true] The flag if <code>leaveRoom()</code>
  518. * should stop both <a href="#method_shareScreen"><code>shareScreen()</code> Stream</a>
  519. * and <a href="#method_getUserMedia"><code>getUserMedia()</code> Stream</a>.
  520. * - When provided as a boolean, this sets both <code>stopMediaOptions.userMedia</code>
  521. * and <code>stopMediaOptions.screenshare</code> to its boolean value.
  522. * @param {Boolean} [stopMediaOptions.userMedia=true] The flag if <code>leaveRoom()</code>
  523. * should stop <a href="#method_getUserMedia"><code>getUserMedia()</code> Stream</a>.
  524. * <small>This invokes <a href="#method_stopStream"><code>stopStream()</code> method</a>.</small>
  525. * @param {Boolean} [stopMediaOptions.screenshare=true] The flag if <code>leaveRoom()</code>
  526. * should stop <a href="#method_shareScreen"><code>shareScreen()</code> Stream</a>.
  527. * <small>This invokes <a href="#method_stopScreen"><code>stopScreen()</code> method</a>.</small>
  528. * @param {Function} [callback] The callback function fired when request has completed.
  529. * <small>Function parameters signature is <code>function (error, success)</code></small>
  530. * <small>Function request completion is determined by the <a href="#event_peerLeft">
  531. * <code>peerLeft</code> event</a> triggering <code>isSelf</code> parameter payload value as <code>true</code>
  532. * for request success.</small>
  533. * @param {Error|String} callback.error The error result in request.
  534. * <small>Defined as <code>null</code> when there are no errors in request</small>
  535. * <small>Object signature is the <code>leaveRoom()</code> error when stopping Room session.</small>
  536. * @param {JSON} callback.success The success result in request.
  537. * <small>Defined as <code>null</code> when there are errors in request</small>
  538. * @param {String} callback.success.peerId The User's Room session Peer ID.
  539. * @param {String} callback.success.previousRoom The Room name.
  540. * @trigger <ol class="desc-seq">
  541. * <li>Checks if User is in Room. <ol><li>If User is not in a Room: <ol><li><b>ABORT</b> and return error.</li>
  542. * </ol></li><li>Else: <ol><li>If parameter <code>stopMediaOptions.userMedia</code> value is <code>true</code>: <ol>
  543. * <li>Invoke <a href="#method_stopStream"><code>stopStream()</code> method</a>.
  544. * <small>Regardless of request errors, <code>leaveRoom()</code> will still proceed.</small></li></ol></li>
  545. * <li>If parameter <code>stopMediaOptions.screenshare</code> value is <code>true</code>: <ol>
  546. * <li>Invoke <a href="#method_stopScreen"><code>stopScreen()</code> method</a>.
  547. * <small>Regardless of request errors, <code>leaveRoom()</code> will still proceed.</small></li></ol></li>
  548. * <li><a href="#event_peerLeft"><code>peerLeft</code> event</a> triggers for User and all connected Peers in Room.</li>
  549. * <li>If MCU is enabled for the App Key provided in <a href="#method_init"><code>init()</code> method</a>
  550. * and connected: <ol><li><a href="#event_serverPeerLeft"><code>serverPeerLeft</code> event</a>
  551. * triggers parameter payload <code>serverPeerType</code> as <code>MCU</code>.</li></ol></li>
  552. * <li><a href="#event_channelClose"><code>channelClose</code> event</a> triggers.</li></ol></li></ol></li></ol>
  553. * @for Skylink
  554. * @since 0.5.5
  555. */
  556. Skylink.prototype.leaveRoom = function(stopMediaOptions, callback) {
  557. var self = this;
  558. var error; // j-shint !!!
  559. var stopUserMedia = true;
  560. var stopScreenshare = true;
  561.  
  562. // shift parameters
  563. if (typeof stopMediaOptions === 'function') {
  564. callback = stopMediaOptions;
  565. stopMediaOptions = true;
  566. } else if (typeof stopMediaOptions === 'undefined') {
  567. stopMediaOptions = true;
  568. }
  569.  
  570. // stopMediaOptions === null or {} ?
  571. if (typeof stopMediaOptions === 'object' && stopMediaOptions !== null) {
  572. stopUserMedia = stopMediaOptions.userMedia !== false;
  573. stopScreenshare = stopMediaOptions.screenshare !== false;
  574.  
  575. } else if (typeof stopMediaOptions !== 'boolean') {
  576. error = 'stopMediaOptions parameter provided is not a boolean or valid object';
  577. log.error(error, stopMediaOptions);
  578. if (typeof callback === 'function') {
  579. log.log([null, 'Socket', self._selectedRoom, 'Error occurred. ' +
  580. 'Firing callback with error -> '
  581. ], error);
  582. callback(new Error(error), null);
  583. }
  584. return;
  585.  
  586. } else if (stopMediaOptions === false) {
  587. stopUserMedia = false;
  588. stopScreenshare = false;
  589. }
  590.  
  591. if (!self._inRoom) {
  592. error = 'Unable to leave room as user is not in any room';
  593. log.error(error);
  594. if (typeof callback === 'function') {
  595. log.log([null, 'Socket', self._selectedRoom, 'Error occurred. ' +
  596. 'Firing callback with error -> '
  597. ], error);
  598. callback(new Error(error), null);
  599. }
  600. return;
  601. }
  602.  
  603. // NOTE: ENTER/WELCOME made but no peerconnection...
  604. // which may result in peerLeft not triggered..
  605. // WHY? but to ensure clear all
  606. var peers = Object.keys(self._peerInformations);
  607. var conns = Object.keys(self._peerConnections);
  608. var i;
  609. for (i = 0; i < conns.length; i++) {
  610. if (peers.indexOf(conns[i]) === -1) {
  611. peers.push(conns[i]);
  612. }
  613. }
  614. for (i = 0; i < peers.length; i++) {
  615. self._removePeer(peers[i]);
  616. }
  617. self._inRoom = false;
  618. self._closeChannel();
  619.  
  620. self._stopStreams({
  621. userMedia: stopUserMedia,
  622. screenshare: stopScreenshare
  623. });
  624.  
  625. self._wait(function() {
  626. log.log([null, 'Socket', self._selectedRoom, 'User left the room. Callback fired.']);
  627. self._trigger('peerLeft', self._user.sid, self.getPeerInfo(), true);
  628.  
  629. if (typeof callback === 'function') {
  630. callback(null, {
  631. peerId: self._user.sid,
  632. previousRoom: self._selectedRoom
  633. });
  634. }
  635. }, function() {
  636. return (Object.keys(self._peerConnections).length === 0 &&
  637. self._channelOpen === false); // &&
  638. //self._readyState === self.READY_STATE_CHANGE.COMPLETED);
  639. }, false);
  640. };
  641.  
  642. /**
  643. * <blockquote class="info">
  644. * Note that broadcasted events from <a href="#method_muteStream"><code>muteStream()</code> method</a>,
  645. * <a href="#method_stopStream"><code>stopStream()</code> method</a>,
  646. * <a href="#method_stopScreen"><code>stopScreen()</code> method</a>,
  647. * <a href="#method_sendMessage"><code>sendMessage()</code> method</a>,
  648. * <a href="#method_unlockRoom"><code>unlockRoom()</code> method</a> and
  649. * <a href="#method_lockRoom"><code>lockRoom()</code> method</a> may be queued when
  650. * sent within less than an interval.
  651. * </blockquote>
  652. * Function that locks the current Room when in session to prevent other Peers from joining the Room.
  653. * @method lockRoom
  654. * @trigger <ol class="desc-seq">
  655. * <li>Requests to Signaling server to lock Room <ol>
  656. * <li><a href="#event_roomLock"><code>roomLock</code> event</a> triggers parameter payload
  657. * <code>isLocked</code> value as <code>true</code>.</li></ol></li></ol>
  658. * @for Skylink
  659. * @since 0.5.0
  660. */
  661. Skylink.prototype.lockRoom = function() {
  662. log.log('Update to isRoomLocked status ->', true);
  663. this._sendChannelMessage({
  664. type: this._SIG_MESSAGE_TYPE.ROOM_LOCK,
  665. mid: this._user.sid,
  666. rid: this._room.id,
  667. lock: true
  668. });
  669. this._roomLocked = true;
  670. this._trigger('roomLock', true, this._user.sid,
  671. this.getPeerInfo(), true);
  672. };
  673.  
  674. /**
  675. * <blockquote class="info">
  676. * Note that broadcasted events from <a href="#method_muteStream"><code>muteStream()</code> method</a>,
  677. * <a href="#method_stopStream"><code>stopStream()</code> method</a>,
  678. * <a href="#method_stopScreen"><code>stopScreen()</code> method</a>,
  679. * <a href="#method_sendMessage"><code>sendMessage()</code> method</a>,
  680. * <a href="#method_unlockRoom"><code>unlockRoom()</code> method</a> and
  681. * <a href="#method_lockRoom"><code>lockRoom()</code> method</a> may be queued when
  682. * sent within less than an interval.
  683. * </blockquote>
  684. * Function that unlocks the current Room when in session to allow other Peers to join the Room.
  685. * @method unlockRoom
  686. * @trigger <ol class="desc-seq">
  687. * <li>Requests to Signaling server to unlock Room <ol>
  688. * <li><a href="#event_roomLock"><code>roomLock</code> event</a> triggers parameter payload
  689. * <code>isLocked</code> value as <code>false</code>.</li></ol></li></ol>
  690. * @for Skylink
  691. * @since 0.5.0
  692. */
  693. Skylink.prototype.unlockRoom = function() {
  694. log.log('Update to isRoomLocked status ->', false);
  695. this._sendChannelMessage({
  696. type: this._SIG_MESSAGE_TYPE.ROOM_LOCK,
  697. mid: this._user.sid,
  698. rid: this._room.id,
  699. lock: false
  700. });
  701. this._roomLocked = false;
  702. this._trigger('roomLock', false, this._user.sid,
  703. this.getPeerInfo(), true);
  704. };
  705.  
  706. /**
  707. * Function that waits for Socket connection to Signaling to be opened.
  708. * @method _waitForOpenChannel
  709. * @private
  710. * @for Skylink
  711. * @since 0.5.5
  712. */
  713. Skylink.prototype._waitForOpenChannel = function(mediaOptions, callback) {
  714. var self = this;
  715. // when reopening room, it should stay as 0
  716. self._socketCurrentReconnectionAttempt = 0;
  717.  
  718. // wait for ready state before opening
  719. self._wait(function() {
  720. self._condition('channelOpen', function() {
  721. mediaOptions = mediaOptions || {};
  722.  
  723. self._userData = mediaOptions.userData || self._userData || '';
  724. self._streamsBandwidthSettings = {};
  725.  
  726. if (mediaOptions.bandwidth) {
  727. if (typeof mediaOptions.bandwidth.audio === 'number') {
  728. self._streamsBandwidthSettings.audio = mediaOptions.bandwidth.audio;
  729. }
  730.  
  731. if (typeof mediaOptions.bandwidth.video === 'number') {
  732. self._streamsBandwidthSettings.video = mediaOptions.bandwidth.video;
  733. }
  734.  
  735. if (typeof mediaOptions.bandwidth.data === 'number') {
  736. self._streamsBandwidthSettings.data = mediaOptions.bandwidth.data;
  737. }
  738. }
  739.  
  740. // get the stream
  741. if (mediaOptions.manualGetUserMedia === true) {
  742. self._trigger('mediaAccessRequired');
  743.  
  744. var current50Block = 0;
  745. var mediaAccessRequiredFailure = false;
  746. // wait for available audio or video stream
  747. self._wait(function () {
  748. if (mediaAccessRequiredFailure === true) {
  749. self._onUserMediaError(new Error('Waiting for stream timeout'), false, false);
  750. } else {
  751. callback(null, self._streams.userMedia.stream);
  752. }
  753. }, function () {
  754. current50Block += 1;
  755. if (current50Block === 600) {
  756. mediaAccessRequiredFailure = true;
  757. return true;
  758. }
  759.  
  760. if (self._streams.userMedia && self._streams.userMedia.stream) {
  761. return true;
  762. }
  763. }, 50);
  764. return;
  765. }
  766.  
  767. if (mediaOptions.audio || mediaOptions.video) {
  768. self.getUserMedia({
  769. useExactConstraints: !!mediaOptions.useExactConstraints,
  770. audio: mediaOptions.audio,
  771. video: mediaOptions.video
  772.  
  773. }, function (error, success) {
  774. if (error) {
  775. callback(error, null);
  776. } else {
  777. callback(null, success);
  778. }
  779. });
  780. return;
  781. }
  782.  
  783. callback(null, null);
  784.  
  785. }, function() { // open channel first if it's not opened
  786.  
  787. if (!self._channelOpen) {
  788. self._openChannel();
  789. }
  790. return self._channelOpen;
  791. }, function(state) {
  792. return true;
  793. });
  794. }, function() {
  795. return self._readyState === self.READY_STATE_CHANGE.COMPLETED;
  796. });
  797.  
  798. };
  799.