File: source/room-connection.js

  1. /**
  2. * Function that starts the Room session.
  3. * @method joinRoom
  4. * @param {String} [room] The Room name.
  5. * - When not provided or is provided as an empty string, its value is the <code>options.defaultRoom</code>
  6. * provided in the <a href="#method_init"><code>init()</code> method</a>.
  7. * <small>Note that if you are using credentials based authentication, you cannot switch the Room
  8. * that is not the same as the <code>options.defaultRoom</code> defined in the
  9. * <a href="#method_init"><code>init()</code> method</a>.</small>
  10. * @param {JSON} [options] The Room session configuration options.
  11. * @param {JSON|String} [options.userData] The User custom data.
  12. * <small>This can be set after Room session has started using the
  13. * <a href="#method_setUserData"><code>setUserData()</code> method</a>.</small>
  14. * @param {Boolean} [options.useExactConstraints] The <a href="#method_getUserMedia"><code>getUserMedia()</code>
  15. * method</a> <code>options.useExactConstraints</code> parameter settings.
  16. * <small>See the <code>options.useExactConstraints</code> parameter in the
  17. * <a href="#method_getUserMedia"><code>getUserMedia()</code> method</a> for more information.</small>
  18. * @param {Boolean|JSON} [options.audio] The <a href="#method_getUserMedia"><code>getUserMedia()</code>
  19. * method</a> <code>options.audio</code> parameter settings.
  20. * <small>When value is defined as <code>true</code> or an object, <a href="#method_getUserMedia">
  21. * <code>getUserMedia()</code> method</a> to be invoked to retrieve new Stream. If
  22. * <code>options.video</code> is not defined, it will be defined as <code>false</code>.</small>
  23. * <small>Object signature matches the <code>options.audio</code> parameter in the
  24. * <a href="#method_getUserMedia"><code>getUserMedia()</code> method</a>.</small>
  25. * @param {Boolean|JSON} [options.video] The <a href="#method_getUserMedia"><code>getUserMedia()</code>
  26. * method</a> <code>options.video</code> parameter settings.
  27. * <small>When value is defined as <code>true</code> or an object, <a href="#method_getUserMedia">
  28. * <code>getUserMedia()</code> method</a> to be invoked to retrieve new Stream. If
  29. * <code>options.audio</code> is not defined, it will be defined as <code>false</code>.</small>
  30. * <small>Object signature matches the <code>options.video</code> parameter in the
  31. * <a href="#method_getUserMedia"><code>getUserMedia()</code> method</a>.</small>
  32. * @param {Boolean} [options.voiceActivityDetection=true] The flag if voice activity detection should be enabled.
  33. * <small>This can only be toggled if User is and for the offerer, which is determined if User's
  34. * <code>peerInfo.config.priorityWeight</code> is higher than Peer's.</small>
  35. * <blockquote class="details">
  36. * This works hand-in-hand with the <code>options.disableComfortNoiseCodec</code> flag in the
  37. * <a href="#method_init"><code>init()</code> method</a> and the <code>options.audio.usedtx</code> setting in
  38. * <a href="#method_getUserMedia"><code>getUserMedia()</code> method</a>. VAD (voice activity detection)
  39. * detects if there is an active voice in the Stream, and if there is no active voice in the Stream, the
  40. * <code>options.audio.usedtx</code> (if enabled) would prevent sending these empty bits. To prevent huge differences
  41. * when there is a silence and an active voice later, the CN codec would produce an empty voice to
  42. * make it sound better.</blockquote>
  43. * @param {JSON} [options.bandwidth] <blockquote class="info">Note that this is currently not supported
  44. * with Firefox browsers versions 48 and below as noted in an existing
  45. * <a href="https://bugzilla.mozilla.org/show_bug.cgi?id=976521#c21">bugzilla ticket here</a>.</blockquote>
  46. * The configuration to set the maximum streaming bandwidth to send to Peers. You can also use the preconfigured
  47. * constant <a href="#attr_VIDEO_QUALITY"><code>VIDEO_QUALITY</code></a> for recommended values.
  48. * @param {Number} [options.bandwidth.audio] The maximum audio streaming bandwidth sent to Peers in kbps.
  49. * <small>Recommended values are <code>50</code> to <code>200</code>. <code>50</code> is sufficient enough for
  50. * an audio call. The higher you go if you want clearer audio and to be able to hear music streaming.</small>
  51. * @param {Number} [options.bandwidth.video] The maximum video streaming bandwidth sent to Peers.
  52. * <small>Recommended values are <code>256</code>-<code>500</code> for 180p quality,
  53. * <code>500</code>-<code>1024</code> for 360p quality, <code>1024</code>-<code>2048</code> for 720p quality
  54. * and <code>2048</code>-<code>4096</code> for 1080p quality.</small>
  55. * @param {Number} [options.bandwidth.data] The maximum data streaming bandwidth sent to Peers.
  56. * <small>This affects the P2P messaging in <a href="#method_sendP2PMessage"><code>sendP2PMessage()</code> method</a>,
  57. * and data transfers in <a href="#method_sendBlobData"><code>sendBlobData()</code> method</a> and
  58. * <a href="#method_sendURLData"><code>sendURLData()</code> method</a>.</small>
  59. * @param {JSON} [options.googleXBandwidth] <blockquote class="info">Note that this is an experimental configuration
  60. * and may cause disruptions in connections or connectivity issues when toggled, or may not work depending on
  61. * browser supports. Currently, this only toggles the video codec bandwidth configuration.</blockquote>
  62. * The configuration to set the experimental google video streaming bandwidth sent to Peers.
  63. * <small>Note that Peers may override the "receive from" streaming bandwidth depending on the Peers configuration.</small>
  64. * @param {Number} [options.googleXBandwidth.min] The minimum experimental google video streaming bandwidth sent to Peers.
  65. * <small>This toggles the <code>"x-google-min-bitrate"</code> flag in the session description.</small>
  66. * @param {Number} [options.googleXBandwidth.max] The maximum experimental google video streaming bandwidth sent to Peers.
  67. * <small>This toggles the <code>"x-google-max-bitrate"</code> flag in the session description.</small>
  68. * @param {Boolean} [options.manualGetUserMedia] The flag if <code>joinRoom()</code> should trigger
  69. * <a href="#event_mediaAccessRequired"><code>mediaAccessRequired</code> event</a> in which the
  70. * <a href="#method_getUserMedia"><code>getUserMedia()</code> Stream</a> or
  71. * <a href="#method_shareScreen"><code>shareScreen()</code> Stream</a>
  72. * must be retrieved as a requirement before Room session may begin.
  73. * <small>This ignores the <code>options.audio</code> and <code>options.video</code> configuration.</small>
  74. * <small>After 30 seconds without any Stream retrieved, this results in the `callback(error, ..)` result.</small>
  75. * @param {JSON} [options.sdpSettings] <blockquote class="info">
  76. * Note that this is mainly used for debugging purposes and that it is an experimental flag, so
  77. * it may cause disruptions in connections or connectivity issues when toggled. Note that it might not work
  78. * with MCU enabled Peer connections or break MCU enabled Peer connections.</blockquote>
  79. * The configuration to set the session description settings.
  80. * @param {JSON} [options.sdpSettings.connection] The configuration to set the session description connection settings.
  81. * <small>Note that this configuration may disable the media streaming and these settings will be enabled for
  82. * MCU server Peer connection regardless of the flags configured.</small>
  83. * @param {Boolean} [options.sdpSettings.connection.audio=true] The configuration to enable audio session description connection.
  84. * @param {Boolean} [options.sdpSettings.connection.video=true] The configuration to enable video session description connection.
  85. * @param {Boolean} [options.sdpSettings.connection.data=true] The configuration to enable Datachannel session description connection.
  86. * @param {JSON} [options.sdpSettings.direction] The configuration to set the session description connection direction
  87. * to enable or disable uploading and downloading audio or video media streaming.
  88. * <small>Note that this configuration does not prevent RTCP packets from being sent and received.</small>
  89. * @param {JSON} [options.sdpSettings.direction.audio] The configuration to set the session description
  90. * connection direction for audio streaming.
  91. * @param {Boolean} [options.sdpSettings.direction.audio.send=true] The flag if uploading audio streaming
  92. * should be enabled when available.
  93. * @param {Boolean} [options.sdpSettings.direction.audio.receive=true] The flag if downloading audio
  94. * streaming should be enabled when available.
  95. * @param {JSON} [options.sdpSettings.direction.video] The configuration to set the session description
  96. * connection direction for video streaming.
  97. * @param {Boolean} [options.sdpSettings.direction.video.send=true] The flag if uploading video streaming
  98. * should be enabled when available.
  99. * @param {Boolean} [options.sdpSettings.direction.video.receive=true] The flag if downloading video streaming
  100. * should be enabled when available.
  101. * @param {JSON|Boolean} [options.publishOnly] <blockquote class="info">
  102. * For MCU enabled Peer connections, defining this flag would make Peer not know other Peers presence in the Room.<br>
  103. * For non-MCU enable Peer connections, defining this flag would cause other Peers in the Room to
  104. * not to send Stream to Peer, and overrides the config
  105. * <code>options.sdpSettings.direction.audio.receive</code> value to <code>false</code>,
  106. * <code>options.sdpSettings.direction.video.receive</code> value to <code>false</code>,
  107. * <code>options.sdpSettings.direction.video.send</code> value to <code>true</code> and
  108. * <code>options.sdpSettings.direction.audio.send</code> value to <code>true</code>.<br>
  109. * Note that this feature is currently is beta, and for any enquiries on enabling and its support for MCU enabled
  110. * Peer connections, please contact <a href="http://support.temasys.io">our support portal</a>.<br><br>
  111. * How does the publish only functionality work? Imagine several Skylink instances like A1, B1, C1 and A1
  112. * opening a new instance A2 with publish only enabled with configured A1 as parent.<br><br>
  113. * <table class="table"><thead>
  114. * <tr><th></th><th colspan="2">MCU enabled room</th><th colspan="2">MCU disabled room</th></tr>
  115. * <tr><th></th><th>Presence</th><th>Stream</th><th>Presence</th><th>Stream</th></tr></thead><tbody>
  116. * <tr><th>A1</th><td>B1, C1</td><td>B1, C1</td><td>B1, C1</td><td>B1, C1</td></tr>
  117. * <tr><th>B1</th><td>A1, C1, A2</td><td>A1, C1, A2</td><td>A1, C1, A2</td><td>A1, C1, A2</td></tr>
  118. * <tr><th>C1</th><td>B1, C1, A2</td><td>B1, C1, A2</td><td>B1, C1, A2</td><td>B1, C1, A2</td></tr>
  119. * <tr><th>A2</th><td></td><td></td><td>B1, C1</td><td></td></tr></tbody></table>
  120. * Parent and child will not receive each other presence and stream because they are related to each other in the same client page,
  121. * hence no uploading or downloading is required. If A2 did not configure A1 as the parent, A1 will receive A2.</blockquote>
  122. * The config if Peer would publish only.
  123. * @param {String} [options.publishOnly.parentId] <blockquote class="info"><b>Deprecation Warning!</b>
  124. * This property has been deprecated. Use <code>options.parentId</code> instead.
  125. * </blockquote> The parent Peer ID to match to when Peer is connected.
  126. * <small>This is useful for identification for users connecting the Room twice simultaneously for multi-streaming.</small>
  127. * <small>If User Peer ID matches the parent Peer ID provided from Peer, User will not be connected to Peer.
  128. * Parent will not be connected to (or receive the presence of) child, so will child will not be connected to
  129. * (or receive the presence of) parent.</small>
  130. * @param {String} [options.parentId] The parent Peer ID to match to when Peer is connected.
  131. * <small>Note that configuring this value overrides the <code>options.publishOnly.parentId</code> value.</small>
  132. * <small>This is useful for identification for users connecting the Room twice simultaneously for multi-streaming.</small>
  133. * <small>If User Peer ID matches the parent Peer ID provided from Peer, User will not be connected to Peer.
  134. * Parent will not be connected to (or receive the presence of) child, so will child will not be connected to
  135. * (or receive the presence of) parent.</small>
  136. * @param {JSON} [options.peerConnection] <blockquote class="info">
  137. * Note that this is mainly used for debugging purposes, so it may cause disruptions in connections or
  138. * connectivity issues when configured. </blockquote> The Peer connection constraints settings.
  139. * @param {String} [options.peerConnection.bundlePolicy] The Peer connection media bundle policy.
  140. * - When not provided, its value is <code>BALANCED</code>.
  141. * [Rel: Skylink.BUNDLE_POLICY]
  142. * @param {String} [options.peerConnection.rtcpMuxPolicy] The Peer connection RTP and RTCP ICE candidates mux policy.
  143. * - When not provided, its value is <code>REQUIRE</code>.
  144. * [Rel: Skylink.RTCP_MUX_POLICY]
  145. * @param {Number} [options.peerConnection.iceCandidatePoolSize=0] The number of ICE candidates to gather before
  146. * gathering it when setting local offer / answer session description.
  147. * @param {String} [options.peerConnection.certificate] The type of certificate that Peer connection should
  148. * generate and use when available.
  149. * - When not provided, its value is <code>AUTO</code>.
  150. * [Rel: Skylink.PEER_CERTIFICATE]
  151. * @param {String} [options.peerConnection.disableBundle=false] The flag if for each Peer connection instead of bundling all
  152. * media connections into 1 connection, should have all of them negotiated as different separate media connections.
  153. * @param {Boolean|JSON} [options.autoBandwidthAdjustment=false] <blockquote class="info">
  154. * Note that this is an experimental feature which may be removed or changed in the future releases.
  155. * This feature is also only available for non-MCU enabled Peer connections and Edge Peer connections.
  156. * </blockquote> The flag if Peer connections uploading and downloading bandwidth should be automatically adjusted
  157. * each time based on a specified interval.
  158. * <small>Note this would cause <a href="#event_peerRestart"><code>peerRestart</code> event</a> to be triggered
  159. * for each specified interval.</small>
  160. * @param {Number} [options.autoBandwidthAdjustment.interval=10] The interval each time to adjust bandwidth
  161. * connections in seconds.
  162. * <small>Note that the minimum value is <code>10</code>.</small>
  163. * @param {Number} [options.autoBandwidthAdjustment.limitAtPercentage=100] The percentage of the average bandwidth to adjust to.
  164. * <small>E.g. <code>avgBandwidth * (limitPercentage / 100)</code>.</small>
  165. * @param {Boolean} [options.autoBandwidthAdjustment.useUploadBwOnly=false] The flag if average bandwidth computation
  166. * should only consist of the upload bandwidth.
  167. * @param {Function} [callback] The callback function fired when request has completed.
  168. * <small>Function parameters signature is <code>function (error, success)</code></small>
  169. * <small>Function request completion is determined by the <a href="#event_peerJoined">
  170. * <code>peerJoined</code> event</a> triggering <code>isSelf</code> parameter payload value as <code>true</code>
  171. * for request success.</small>
  172. * @param {JSON} callback.error The error result in request.
  173. * <small>Defined as <code>null</code> when there are no errors in request</small>
  174. * @param {Error} callback.error.error The error received when starting Room session has failed.
  175. * @param {Number} [callback.error.errorCode] The current <a href="#method_init"><code>init()</code> method</a> ready state.
  176. * <small>Defined as <code>null</code> when no <a href="#method_init"><code>init()</code> method</a>
  177. * has not been called due to invalid configuration.</small>
  178. * [Rel: Skylink.READY_STATE_CHANGE]
  179. * @param {String} callback.error.room The Room name.
  180. * @param {JSON} callback.success The success result in request.
  181. * <small>Defined as <code>null</code> when there are errors in request</small>
  182. * @param {String} callback.success.room The Room name.
  183. * @param {String} callback.success.peerId The User's Room session Peer ID.
  184. * @param {JSON} callback.success.peerInfo The User's current Room session information.
  185. * <small>Object signature matches the <code>peerInfo</code> parameter payload received in the
  186. * <a href="#event_peerJoined"><code>peerJoined</code> event</a>.</small>
  187. * @example
  188. * // Example 1: Connecting to the default Room without Stream
  189. * skylinkDemo.joinRoom(function (error, success) {
  190. * if (error) return;
  191. * console.log("User connected.");
  192. * });
  193. *
  194. * // Example 2: Connecting to Room "testxx" with Stream
  195. * skylinkDemo.joinRoom("testxx", {
  196. * audio: true,
  197. * video: true
  198. * }, function (error, success) {
  199. * if (error) return;
  200. * console.log("User connected with getUserMedia() Stream.")
  201. * });
  202. *
  203. * // Example 3: Connecting to default Room with Stream retrieved earlier
  204. * skylinkDemo.getUserMedia(function (gUMError, gUMSuccess) {
  205. * if (gUMError) return;
  206. * skylinkDemo.joinRoom(function (error, success) {
  207. * if (error) return;
  208. * console.log("User connected with getUserMedia() Stream.");
  209. * });
  210. * });
  211. *
  212. * // Example 4: Connecting to "testxx" Room with shareScreen() Stream retrieved manually
  213. * skylinkDemo.on("mediaAccessRequired", function () {
  214. * skylinkDemo.shareScreen(function (sSError, sSSuccess) {
  215. * if (sSError) return;
  216. * });
  217. * });
  218. *
  219. * skylinkDemo.joinRoom("testxx", {
  220. * manualGetUserMedia: true
  221. * }, function (error, success) {
  222. * if (error) return;
  223. * console.log("User connected with shareScreen() Stream.");
  224. * });
  225. *
  226. * // Example 5: Connecting to "testxx" Room with User custom data
  227. * var data = { username: "myusername" };
  228. * skylinkDemo.joinRoom("testxx", {
  229. * userData: data
  230. * }, function (error, success) {
  231. * if (error) return;
  232. * console.log("User connected with correct user data?", success.peerInfo.userData.username === data.username);
  233. * });
  234. *
  235. * // Example 6: Connecting to "testxx" Room with a pre-configured bandwidth set
  236. * skylinkDemo.joinRoom("testxx", {
  237. * bandwidth: skylinkDemo.VIDEO_QUALITY.HD
  238. * }, function (error, success) {
  239. * if (error) return;
  240. * console.log("User connected with bandwidth quality HD");
  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, options, callback) {
  311. var self = this;
  312. var selectedRoom = self._initOptions.defaultRoom;
  313. var previousRoom = self._selectedRoom;
  314. var mediaOptions = {};
  315. var timestamp = (new Date()).getTime() + Math.floor(Math.random() * 10000);
  316. self._joinRoomManager.timestamp = timestamp;
  317.  
  318. if (room && typeof room === 'string') {
  319. selectedRoom = room;
  320. } else if (room && typeof room === 'object') {
  321. mediaOptions = room;
  322. } else if (typeof room === 'function') {
  323. callback = room;
  324. }
  325.  
  326. if (options && typeof options === 'object') {
  327. mediaOptions = options;
  328. } else if (typeof options === 'function') {
  329. callback = options;
  330. }
  331.  
  332. var resolveAsErrorFn = function (error, tryRoom, readyState) {
  333. log.error(error);
  334.  
  335. if (typeof callback === 'function') {
  336. callback({
  337. room: tryRoom,
  338. errorCode: readyState || null,
  339. error: error instanceof Error ? error : new Error(JSON.stringify(error))
  340. });
  341. }
  342. };
  343.  
  344. var resolveAsWarningFn = function(error, tryRoom) {
  345. log.warn(error + ' ' + 'room: ' + tryRoom);
  346. };
  347.  
  348. var joinRoomFn = function () {
  349. // If room has been stopped but does not matches timestamp skip.
  350. if (self._joinRoomManager.timestamp !== timestamp) {
  351. resolveAsWarningFn('joinRoom() process did not complete', selectedRoom);
  352. return;
  353. }
  354.  
  355. self._initSelectedRoom(selectedRoom, function(initError, initSuccess) {
  356. if (initError) {
  357. resolveAsErrorFn(initError.error, self._selectedRoom, self._readyState);
  358. return;
  359. // If details has been initialised but does not matches timestamp skip.
  360. } else if (self._joinRoomManager.timestamp !== timestamp) {
  361. resolveAsWarningFn('joinRoom() process did not complete', selectedRoom);
  362. return;
  363. }
  364.  
  365. self._waitForOpenChannel(mediaOptions || {}, timestamp, function (error, success) {
  366. if (error) {
  367. resolveAsErrorFn(error, self._selectedRoom, self._readyState);
  368. return;
  369. // If socket and stream has been retrieved but socket connection does not matches timestamp skip.
  370. } else if (self._joinRoomManager.timestamp !== timestamp) {
  371. resolveAsWarningFn('joinRoom() process did not complete', selectedRoom);
  372. return;
  373. }
  374.  
  375. if (AdapterJS.webrtcDetectedType === 'AppleWebKit') {
  376. var checkStream = self._streams.screenshare && self._streams.screenshare.stream ?
  377. self._streams.screenshare.stream : (self._streams.userMedia && self._streams.userMedia.stream ?
  378. self._streams.userMedia.stream : null);
  379.  
  380. if (checkStream ? checkStream.getTracks().length === 0 : true) {
  381. log.warn('Note that receiving audio and video streams may fail as safari 11 needs stream with audio and video tracks');
  382. } else if (checkStream.getAudioTracks().length === 0) {
  383. log.warn('Note that receiving audio streams may fail as safari 11 needs stream ' +
  384. 'with audio and video tracks and not just with video tracks');
  385. } else if (checkStream.getVideoTracks().length === 0) {
  386. log.warn('Note that receiving video streams may fail as safari 11 needs stream ' +
  387. 'with audio and video tracks and not just with audio tracks');
  388. }
  389. }
  390.  
  391. if (typeof callback === 'function') {
  392. var peerOnJoin = function(peerId, peerInfo, isSelf) {
  393. self.off('systemAction', peerFailedJoin);
  394. self.off('channelClose', peerSocketFailedJoin);
  395. log.info([null, 'Room', selectedRoom, 'Connected to Room ->'], peerInfo);
  396. callback(null, {
  397. room: self._selectedRoom,
  398. peerId: peerId,
  399. peerInfo: peerInfo
  400. });
  401. };
  402.  
  403. var peerFailedJoin = function (action, message) {
  404. self.off('peerJoined', peerOnJoin);
  405. self.off('channelClose', peerSocketFailedJoin);
  406. log.error([null, 'Room', selectedRoom, 'Failed connecting to Room ->'], message);
  407. resolveAsErrorFn(new Error(message), self._selectedRoom, self._readyState);
  408. };
  409.  
  410. var peerSocketFailedJoin = function () {
  411. self.off('systemAction', peerFailedJoin);
  412. self.off('peerJoined', peerOnJoin);
  413. log.error([null, 'Room', selectedRoom, 'Failed connecting to Room due to abrupt disconnection.']);
  414. resolveAsErrorFn(new Error('Channel closed abruptly before session was established'), self._selectedRoom, self._readyState);
  415. };
  416.  
  417. self.once('peerJoined', peerOnJoin, function(peerId, peerInfo, isSelf) {
  418. return peerInfo.room === selectedRoom && isSelf;
  419. });
  420.  
  421. self.once('systemAction', peerFailedJoin, function (action) {
  422. return action === self.SYSTEM_ACTION.REJECT;
  423. });
  424.  
  425. self.once('channelClose', peerSocketFailedJoin);
  426. }
  427.  
  428. var joinRoomMsg = {
  429. type: self._SIG_MESSAGE_TYPE.JOIN_ROOM,
  430. uid: self._user.uid,
  431. cid: self._key,
  432. rid: self._room.id,
  433. userCred: self._user.token,
  434. timeStamp: self._user.timeStamp,
  435. apiOwner: self._appKeyOwner,
  436. roomCred: self._room.token,
  437. start: self._room.startDateTime,
  438. len: self._room.duration,
  439. isPrivileged: self._isPrivileged === true, // Default to false if undefined
  440. autoIntroduce: self._autoIntroduce !== false, // Default to true if undefined
  441. key: self._initOptions.appKey
  442. };
  443.  
  444. self._sendChannelMessage(joinRoomMsg);
  445. self._handleSessionStats(joinRoomMsg);
  446. });
  447. });
  448. };
  449.  
  450. if (room === null || ['number', 'boolean'].indexOf(typeof room) > -1) {
  451. resolveAsErrorFn('Invalid room name is provided', room);
  452. return;
  453. }
  454.  
  455. if (options === null || ['number', 'boolean'].indexOf(typeof options) > -1) {
  456. resolveAsErrorFn('Invalid mediaOptions is provided', selectedRoom);
  457. return;
  458. }
  459.  
  460. self._joinRoomManager.socketsFn.forEach(function (fnItem) {
  461. fnItem(timestamp);
  462. });
  463.  
  464. self._joinRoomManager.socketsFn = [];
  465.  
  466. var stopStream = mediaOptions.audio === false && mediaOptions.video === false;
  467.  
  468. if (self._inRoom) {
  469. self.leaveRoom({
  470. userMedia: stopStream
  471. }, function (lRError, lRSuccess) {
  472. log.debug([null, 'Room', previousRoom, 'Leave Room callback result ->'], [lRError, lRSuccess]);
  473. joinRoomFn();
  474. });
  475. } else {
  476. if (stopStream) {
  477. self.stopStream();
  478. }
  479.  
  480. joinRoomFn();
  481. }
  482. };
  483.  
  484. /**
  485. * <blockquote class="info">
  486. * Note that this method will close any existing socket channel connection despite not being in the Room.
  487. * </blockquote>
  488. * Function that stops Room session.
  489. * @method leaveRoom
  490. * @param {Boolean|JSON} [stopMediaOptions=true] The flag if <code>leaveRoom()</code>
  491. * should stop both <a href="#method_shareScreen"><code>shareScreen()</code> Stream</a>
  492. * and <a href="#method_getUserMedia"><code>getUserMedia()</code> Stream</a>.
  493. * - When provided as a boolean, this sets both <code>stopMediaOptions.userMedia</code>
  494. * and <code>stopMediaOptions.screenshare</code> to its boolean value.
  495. * @param {Boolean} [stopMediaOptions.userMedia=true] The flag if <code>leaveRoom()</code>
  496. * should stop <a href="#method_getUserMedia"><code>getUserMedia()</code> Stream</a>.
  497. * <small>This invokes <a href="#method_stopStream"><code>stopStream()</code> method</a>.</small>
  498. * @param {Boolean} [stopMediaOptions.screenshare=true] The flag if <code>leaveRoom()</code>
  499. * should stop <a href="#method_shareScreen"><code>shareScreen()</code> Stream</a>.
  500. * <small>This invokes <a href="#method_stopScreen"><code>stopScreen()</code> method</a>.</small>
  501. * @param {Function} [callback] The callback function fired when request has completed.
  502. * <small>Function parameters signature is <code>function (error, success)</code></small>
  503. * <small>Function request completion is determined by the <a href="#event_peerLeft">
  504. * <code>peerLeft</code> event</a> triggering <code>isSelf</code> parameter payload value as <code>true</code>
  505. * for request success.</small>
  506. * @param {Error|String} callback.error The error result in request.
  507. * <small>Defined as <code>null</code> when there are no errors in request</small>
  508. * <small>Object signature is the <code>leaveRoom()</code> error when stopping Room session.</small>
  509. * @param {JSON} callback.success The success result in request.
  510. * <small>Defined as <code>null</code> when there are errors in request</small>
  511. * @param {String} callback.success.peerId The User's Room session Peer ID.
  512. * @param {String} callback.success.previousRoom The Room name.
  513. * @trigger <ol class="desc-seq">
  514. * <li>If Socket connection is opened: <ol><li><a href="#event_channelClose"><code>channelClose</code> event</a> triggers.</li></ol></li>
  515. * <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>
  516. * </ol></li><li>Else: <ol><li>If parameter <code>stopMediaOptions.userMedia</code> value is <code>true</code>: <ol>
  517. * <li>Invoke <a href="#method_stopStream"><code>stopStream()</code> method</a>.
  518. * <small>Regardless of request errors, <code>leaveRoom()</code> will still proceed.</small></li></ol></li>
  519. * <li>If parameter <code>stopMediaOptions.screenshare</code> value is <code>true</code>: <ol>
  520. * <li>Invoke <a href="#method_stopScreen"><code>stopScreen()</code> method</a>.
  521. * <small>Regardless of request errors, <code>leaveRoom()</code> will still proceed.</small></li></ol></li>
  522. * <li><a href="#event_peerLeft"><code>peerLeft</code> event</a> triggers for User and all connected Peers in Room.</li>
  523. * <li>If MCU is enabled for the App Key provided in <a href="#method_init"><code>init()</code> method</a>
  524. * and connected: <ol><li><a href="#event_serverPeerLeft"><code>serverPeerLeft</code> event</a>
  525. * triggers parameter payload <code>serverPeerType</code> as <code>MCU</code>.</li></ol></li></ol></li></ol></li></ol>
  526. * @for Skylink
  527. * @since 0.5.5
  528. */
  529. Skylink.prototype.leaveRoom = function(stopMediaOptions, callback) {
  530. var self = this;
  531. var stopUserMedia = true;
  532. var stopScreenshare = true;
  533. var previousRoom = self._selectedRoom;
  534. var previousUserPeerId = self._user ? self._user.sid : null;
  535. var peersThatLeft = [];
  536. var isNotInRoom = !self._inRoom;
  537.  
  538. if (typeof stopMediaOptions === 'boolean') {
  539. if (stopMediaOptions === false) {
  540. stopUserMedia = false;
  541. stopScreenshare = false;
  542. }
  543. } else if (stopMediaOptions && typeof stopMediaOptions === 'object') {
  544. stopUserMedia = stopMediaOptions.userMedia !== false;
  545. stopScreenshare = stopMediaOptions.screenshare !== false;
  546. } else if (typeof stopMediaOptions === 'function') {
  547. callback = stopMediaOptions;
  548. }
  549.  
  550. for (var infoPeerId in self._peerInformations) {
  551. if (self._peerInformations.hasOwnProperty(infoPeerId) && self._peerInformations[infoPeerId]) {
  552. peersThatLeft.push(infoPeerId);
  553. self._removePeer(infoPeerId);
  554. }
  555. }
  556.  
  557. for (var connPeerId in self._peerConnections) {
  558. if (self._peerConnections.hasOwnProperty(connPeerId) && self._peerConnections[connPeerId]) {
  559. if (peersThatLeft.indexOf(connPeerId) === -1) {
  560. peersThatLeft.push(connPeerId);
  561. self._removePeer(connPeerId);
  562. }
  563. }
  564. }
  565.  
  566. self._inRoom = false;
  567. self._closeChannel();
  568.  
  569. if (isNotInRoom) {
  570. var notInRoomError = 'Unable to leave room as user is not in any room';
  571. log.error([null, 'Room', previousRoom, notInRoomError]);
  572.  
  573. if (typeof callback === 'function') {
  574. callback(new Error(notInRoomError), null);
  575. }
  576. return;
  577. }
  578.  
  579. self._stopStreams({
  580. userMedia: stopUserMedia,
  581. screenshare: stopScreenshare
  582. });
  583.  
  584. self._wait(function () {
  585. log.log([null, 'Room', previousRoom, 'User left the room']);
  586.  
  587. self._trigger('peerLeft', previousUserPeerId, self.getPeerInfo(), true);
  588.  
  589. if (typeof callback === 'function') {
  590. callback(null, {
  591. peerId: previousUserPeerId,
  592. previousRoom: previousRoom
  593. });
  594. }
  595. }, function () {
  596. return !self._channelOpen;
  597. });
  598. };
  599.  
  600. /**
  601. * <blockquote class="info">
  602. * Note that broadcasted events from <a href="#method_muteStream"><code>muteStream()</code> method</a>,
  603. * <a href="#method_stopStream"><code>stopStream()</code> method</a>,
  604. * <a href="#method_stopScreen"><code>stopScreen()</code> method</a>,
  605. * <a href="#method_sendMessage"><code>sendMessage()</code> method</a>,
  606. * <a href="#method_unlockRoom"><code>unlockRoom()</code> method</a> and
  607. * <a href="#method_lockRoom"><code>lockRoom()</code> method</a> may be queued when
  608. * sent within less than an interval.
  609. * </blockquote>
  610. * Function that locks the current Room when in session to prevent other Peers from joining the Room.
  611. * @method lockRoom
  612. * @trigger <ol class="desc-seq">
  613. * <li>Requests to Signaling server to lock Room <ol>
  614. * <li><a href="#event_roomLock"><code>roomLock</code> event</a> triggers parameter payload
  615. * <code>isLocked</code> value as <code>true</code>.</li></ol></li></ol>
  616. * @for Skylink
  617. * @since 0.5.0
  618. */
  619. Skylink.prototype.lockRoom = function() {
  620. if (!(this._user && this._user.sid)) {
  621. return;
  622. }
  623. log.log('Update to isRoomLocked status ->', true);
  624. this._sendChannelMessage({
  625. type: this._SIG_MESSAGE_TYPE.ROOM_LOCK,
  626. mid: this._user.sid,
  627. rid: this._room.id,
  628. lock: true
  629. });
  630. this._roomLocked = true;
  631. this._trigger('roomLock', true, this._user.sid, this.getPeerInfo(), true);
  632. };
  633.  
  634. /**
  635. * <blockquote class="info">
  636. * Note that broadcasted events from <a href="#method_muteStream"><code>muteStream()</code> method</a>,
  637. * <a href="#method_stopStream"><code>stopStream()</code> method</a>,
  638. * <a href="#method_stopScreen"><code>stopScreen()</code> method</a>,
  639. * <a href="#method_sendMessage"><code>sendMessage()</code> method</a>,
  640. * <a href="#method_unlockRoom"><code>unlockRoom()</code> method</a> and
  641. * <a href="#method_lockRoom"><code>lockRoom()</code> method</a> may be queued when
  642. * sent within less than an interval.
  643. * </blockquote>
  644. * Function that unlocks the current Room when in session to allow other Peers to join the Room.
  645. * @method unlockRoom
  646. * @trigger <ol class="desc-seq">
  647. * <li>Requests to Signaling server to unlock Room <ol>
  648. * <li><a href="#event_roomLock"><code>roomLock</code> event</a> triggers parameter payload
  649. * <code>isLocked</code> value as <code>false</code>.</li></ol></li></ol>
  650. * @for Skylink
  651. * @since 0.5.0
  652. */
  653. Skylink.prototype.unlockRoom = function() {
  654. if (!(this._user && this._user.sid)) {
  655. return;
  656. }
  657. log.log('Update to isRoomLocked status ->', false);
  658. this._sendChannelMessage({
  659. type: this._SIG_MESSAGE_TYPE.ROOM_LOCK,
  660. mid: this._user.sid,
  661. rid: this._room.id,
  662. lock: false
  663. });
  664. this._roomLocked = false;
  665. this._trigger('roomLock', false, this._user.sid, this.getPeerInfo(), true);
  666. };
  667.  
  668. /**
  669. * Function that waits for Socket connection to Signaling to be opened.
  670. * @method _waitForOpenChannel
  671. * @private
  672. * @for Skylink
  673. * @since 0.5.5
  674. */
  675. Skylink.prototype._waitForOpenChannel = function(mediaOptions, joinRoomTimestamp, callback) {
  676. var self = this;
  677. // when reopening room, it should stay as 0
  678. self._socketCurrentReconnectionAttempt = 0;
  679.  
  680. // wait for ready state before opening
  681. self._wait(function() {
  682. var onChannelOpen = function () {
  683. self.off('socketError', onChannelError);
  684.  
  685. // Wait for self._channelOpen flag to be defined first
  686. setTimeout(function () {
  687. mediaOptions = mediaOptions || {};
  688.  
  689. self._userData = mediaOptions.userData || self._userData || '';
  690. self._streamsBandwidthSettings = {
  691. googleX: {},
  692. bAS: {}
  693. };
  694. self._publishOnly = false;
  695. self._sdpSettings = {
  696. connection: {
  697. audio: true,
  698. video: true,
  699. data: true
  700. },
  701. direction: {
  702. audio: { send: true, receive: true },
  703. video: { send: true, receive: true }
  704. }
  705. };
  706. self._voiceActivityDetection = typeof mediaOptions.voiceActivityDetection === 'boolean' ?
  707. mediaOptions.voiceActivityDetection : true;
  708. self._peerConnectionConfig = {
  709. bundlePolicy: self.BUNDLE_POLICY.BALANCED,
  710. rtcpMuxPolicy: self.RTCP_MUX_POLICY.REQUIRE,
  711. iceCandidatePoolSize: 0,
  712. certificate: self.PEER_CERTIFICATE.AUTO,
  713. disableBundle: false
  714. };
  715. self._bandwidthAdjuster = null;
  716.  
  717. if (mediaOptions.bandwidth) {
  718. if (typeof mediaOptions.bandwidth.audio === 'number') {
  719. self._streamsBandwidthSettings.bAS.audio = mediaOptions.bandwidth.audio;
  720. }
  721.  
  722. if (typeof mediaOptions.bandwidth.video === 'number') {
  723. self._streamsBandwidthSettings.bAS.video = mediaOptions.bandwidth.video;
  724. }
  725.  
  726. if (typeof mediaOptions.bandwidth.data === 'number') {
  727. self._streamsBandwidthSettings.bAS.data = mediaOptions.bandwidth.data;
  728. }
  729. }
  730.  
  731. if (mediaOptions.googleXBandwidth) {
  732. if (typeof mediaOptions.googleXBandwidth.min === 'number') {
  733. self._streamsBandwidthSettings.googleX.min = mediaOptions.googleXBandwidth.min;
  734. }
  735.  
  736. if (typeof mediaOptions.googleXBandwidth.max === 'number') {
  737. self._streamsBandwidthSettings.googleX.max = mediaOptions.googleXBandwidth.max;
  738. }
  739. }
  740.  
  741. if (mediaOptions.sdpSettings) {
  742. if (mediaOptions.sdpSettings.direction) {
  743. if (mediaOptions.sdpSettings.direction.audio) {
  744. self._sdpSettings.direction.audio.receive = typeof mediaOptions.sdpSettings.direction.audio.receive === 'boolean' ?
  745. mediaOptions.sdpSettings.direction.audio.receive : true;
  746. self._sdpSettings.direction.audio.send = typeof mediaOptions.sdpSettings.direction.audio.send === 'boolean' ?
  747. mediaOptions.sdpSettings.direction.audio.send : true;
  748. }
  749.  
  750. if (mediaOptions.sdpSettings.direction.video) {
  751. self._sdpSettings.direction.video.receive = typeof mediaOptions.sdpSettings.direction.video.receive === 'boolean' ?
  752. mediaOptions.sdpSettings.direction.video.receive : true;
  753. self._sdpSettings.direction.video.send = typeof mediaOptions.sdpSettings.direction.video.send === 'boolean' ?
  754. mediaOptions.sdpSettings.direction.video.send : true;
  755. }
  756. }
  757. if (mediaOptions.sdpSettings.connection) {
  758. self._sdpSettings.connection.audio = typeof mediaOptions.sdpSettings.connection.audio === 'boolean' ?
  759. mediaOptions.sdpSettings.connection.audio : true;
  760. self._sdpSettings.connection.video = typeof mediaOptions.sdpSettings.connection.video === 'boolean' ?
  761. mediaOptions.sdpSettings.connection.video : true;
  762. self._sdpSettings.connection.data = typeof mediaOptions.sdpSettings.connection.data === 'boolean' ?
  763. mediaOptions.sdpSettings.connection.data : true;
  764. }
  765. }
  766.  
  767. if (mediaOptions.publishOnly) {
  768. self._sdpSettings.direction.audio.send = true;
  769. self._sdpSettings.direction.audio.receive = false;
  770. self._sdpSettings.direction.video.send = true;
  771. self._sdpSettings.direction.video.receive = false;
  772. self._publishOnly = true;
  773.  
  774. if (typeof mediaOptions.publishOnly === 'object' && mediaOptions.publishOnly.parentId &&
  775. typeof mediaOptions.publishOnly.parentId === 'string') {
  776. self._parentId = mediaOptions.publishOnly.parentId;
  777. }
  778. }
  779.  
  780. if (mediaOptions.parentId) {
  781. self._parentId = mediaOptions.parentId;
  782. }
  783.  
  784. if (mediaOptions.peerConnection && typeof mediaOptions.peerConnection === 'object') {
  785. if (typeof mediaOptions.peerConnection.bundlePolicy === 'string') {
  786. for (var bpProp in self.BUNDLE_POLICY) {
  787. if (self.BUNDLE_POLICY.hasOwnProperty(bpProp) &&
  788. self.BUNDLE_POLICY[bpProp] === mediaOptions.peerConnection.bundlePolicy) {
  789. self._peerConnectionConfig.bundlePolicy = mediaOptions.peerConnection.bundlePolicy;
  790. }
  791. }
  792. }
  793. if (typeof mediaOptions.peerConnection.rtcpMuxPolicy === 'string') {
  794. for (var rmpProp in self.RTCP_MUX_POLICY) {
  795. if (self.RTCP_MUX_POLICY.hasOwnProperty(rmpProp) &&
  796. self.RTCP_MUX_POLICY[rmpProp] === mediaOptions.peerConnection.rtcpMuxPolicy) {
  797. self._peerConnectionConfig.rtcpMuxPolicy = mediaOptions.peerConnection.rtcpMuxPolicy;
  798. }
  799. }
  800. }
  801. if (typeof mediaOptions.peerConnection.iceCandidatePoolSize === 'number' &&
  802. mediaOptions.peerConnection.iceCandidatePoolSize > 0) {
  803. self._peerConnectionConfig.iceCandidatePoolSize = mediaOptions.peerConnection.iceCandidatePoolSize;
  804. }
  805. if (typeof mediaOptions.peerConnection.certificate === 'string') {
  806. for (var pcProp in self.PEER_CERTIFICATE) {
  807. if (self.PEER_CERTIFICATE.hasOwnProperty(pcProp) &&
  808. self.PEER_CERTIFICATE[pcProp] === mediaOptions.peerConnection.certificate) {
  809. self._peerConnectionConfig.certificate = mediaOptions.peerConnection.certificate;
  810. }
  811. }
  812. }
  813. self._peerConnectionConfig.disableBundle = mediaOptions.peerConnection.disableBundle === true;
  814. }
  815.  
  816. if (mediaOptions.autoBandwidthAdjustment) {
  817. self._bandwidthAdjuster = {
  818. interval: 10,
  819. limitAtPercentage: 100,
  820. useUploadBwOnly: false
  821. };
  822.  
  823. if (typeof mediaOptions.autoBandwidthAdjustment === 'object') {
  824. if (typeof mediaOptions.autoBandwidthAdjustment.interval === 'number' &&
  825. mediaOptions.autoBandwidthAdjustment.interval >= 10) {
  826. self._bandwidthAdjuster.interval = mediaOptions.autoBandwidthAdjustment.interval;
  827. }
  828. if (typeof mediaOptions.autoBandwidthAdjustment.limitAtPercentage === 'number' &&
  829. (mediaOptions.autoBandwidthAdjustment.limitAtPercentage >= 0 &&
  830. mediaOptions.autoBandwidthAdjustment.limitAtPercentage <= 100)) {
  831. self._bandwidthAdjuster.limitAtPercentage = mediaOptions.autoBandwidthAdjustment.limitAtPercentage;
  832. }
  833. if (typeof mediaOptions.autoBandwidthAdjustment.useUploadBwOnly === 'boolean') {
  834. self._bandwidthAdjuster.useUploadBwOnly = mediaOptions.autoBandwidthAdjustment.useUploadBwOnly;
  835. }
  836. }
  837. }
  838.  
  839. // get the stream
  840. if (mediaOptions.manualGetUserMedia === true) {
  841. self._trigger('mediaAccessRequired');
  842.  
  843. var current50Block = 0;
  844. var mediaAccessRequiredFailure = false;
  845. // wait for available audio or video stream
  846. self._wait(function () {
  847. if (mediaAccessRequiredFailure === true) {
  848. self._onUserMediaError(new Error('Waiting for stream timeout'), false, false);
  849. } else {
  850. callback(null, self._streams.userMedia.stream);
  851. }
  852. }, function () {
  853. current50Block += 1;
  854. if (current50Block === 600) {
  855. mediaAccessRequiredFailure = true;
  856. return true;
  857. }
  858.  
  859. if (self._streams.userMedia && self._streams.userMedia.stream) {
  860. return true;
  861. }
  862. }, 50);
  863. return;
  864. }
  865.  
  866. if (mediaOptions.audio || mediaOptions.video) {
  867. self.getUserMedia({
  868. useExactConstraints: !!mediaOptions.useExactConstraints,
  869. audio: mediaOptions.audio,
  870. video: mediaOptions.video
  871.  
  872. }, function (error, success) {
  873. if (error) {
  874. callback(error, null);
  875. } else {
  876. callback(null, success);
  877. }
  878. });
  879. return;
  880. }
  881. callback(null, null);
  882. }, 1);
  883. };
  884. var onChannelError = function (errorState, error) {
  885. self.off('channelOpen', onChannelOpen);
  886. callback(error);
  887. };
  888.  
  889. if (!self._channelOpen) {
  890. self.once('channelOpen', onChannelOpen);
  891. self.once('socketError', onChannelError, function (errorState) {
  892. return errorState === self.SOCKET_ERROR.RECONNECTION_ABORTED;
  893. });
  894. self._openChannel(joinRoomTimestamp);
  895. } else {
  896. onChannelOpen();
  897. }
  898. }, function() {
  899. return self._readyState === self.READY_STATE_CHANGE.COMPLETED;
  900. });
  901. };
  902.