modified lib-jitsi-meet dev repo
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

ProxyConnectionPC.js 13KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403
  1. import { getLogger } from 'jitsi-meet-logger';
  2. import RTCEvents from '../../service/RTC/RTCEvents';
  3. import XMPPEvents from '../../service/xmpp/XMPPEvents';
  4. import RTC from '../RTC/RTC';
  5. import JingleSessionPC from '../xmpp/JingleSessionPC';
  6. import { DEFAULT_STUN_SERVERS } from '../xmpp/xmpp';
  7. import { ACTIONS } from './constants';
  8. const logger = getLogger(__filename);
  9. /**
  10. * An adapter around {@code JingleSessionPC} so its logic can be re-used without
  11. * an XMPP connection. It is being re-used for consistency with the rest of the
  12. * codebase and to leverage existing peer connection event handling. Also
  13. * this class provides a facade to hide most of the API for
  14. * {@code JingleSessionPC}.
  15. */
  16. export default class ProxyConnectionPC {
  17. /**
  18. * Initializes a new {@code ProxyConnectionPC} instance.
  19. *
  20. * @param {Object} options - Values to initialize the instance with.
  21. * @param {Object} [options.pcConfig] - The {@code RTCConfiguration} to use for the WebRTC peer connection.
  22. * @param {boolean} [options.isInitiator] - If true, the local client should send offers. If false, the local
  23. * client should send answers. Defaults to false.
  24. * @param {Function} options.onRemoteStream - Callback to invoke when a remote media stream has been received
  25. * through the peer connection.
  26. * @param {string} options.peerJid - The jid of the remote client with which the peer connection is being establish
  27. * and which should receive direct messages regarding peer connection updates.
  28. * @param {boolean} [options.receiveVideo] - Whether or not the peer connection should accept incoming video
  29. * streams. Defaults to false.
  30. * @param {Function} options.onSendMessage - Callback to invoke when a message has to be sent (signaled) out.
  31. */
  32. constructor(options = {}) {
  33. this._options = {
  34. pcConfig: {},
  35. isInitiator: false,
  36. receiveAudio: false,
  37. receiveVideo: false,
  38. ...options
  39. };
  40. /**
  41. * Instances of {@code JitsiTrack} associated with this instance of
  42. * {@code ProxyConnectionPC}.
  43. *
  44. * @type {Array<JitsiTrack>}
  45. */
  46. this._tracks = [];
  47. /**
  48. * The active instance of {@code JingleSessionPC}.
  49. *
  50. * @type {JingleSessionPC|null}
  51. */
  52. this._peerConnection = null;
  53. // Bind event handlers so they are only bound once for every instance.
  54. this._onError = this._onError.bind(this);
  55. this._onRemoteStream = this._onRemoteStream.bind(this);
  56. this._onSendMessage = this._onSendMessage.bind(this);
  57. }
  58. /**
  59. * Returns the jid of the remote peer with which this peer connection should
  60. * be established with.
  61. *
  62. * @returns {string}
  63. */
  64. getPeerJid() {
  65. return this._options.peerJid;
  66. }
  67. /**
  68. * Updates the peer connection based on the passed in jingle.
  69. *
  70. * @param {Object} $jingle - An XML jingle element, wrapped in query,
  71. * describing how the peer connection should be updated.
  72. * @returns {void}
  73. */
  74. processMessage($jingle) {
  75. switch ($jingle.attr('action')) {
  76. case ACTIONS.ACCEPT:
  77. this._onSessionAccept($jingle);
  78. break;
  79. case ACTIONS.INITIATE:
  80. this._onSessionInitiate($jingle);
  81. break;
  82. case ACTIONS.TERMINATE:
  83. this._onSessionTerminate($jingle);
  84. break;
  85. case ACTIONS.TRANSPORT_INFO:
  86. this._onTransportInfo($jingle);
  87. break;
  88. }
  89. }
  90. /**
  91. * Instantiates a peer connection and starts the offer/answer cycle to
  92. * establish a connection with a remote peer.
  93. *
  94. * @param {Array<JitsiLocalTrack>} localTracks - Initial local tracks to add
  95. * to add to the peer connection.
  96. * @returns {void}
  97. */
  98. start(localTracks = []) {
  99. if (this._peerConnection) {
  100. return;
  101. }
  102. this._tracks = this._tracks.concat(localTracks);
  103. this._peerConnection = this._createPeerConnection();
  104. this._peerConnection.invite(localTracks);
  105. }
  106. /**
  107. * Begins the process of disconnecting from a remote peer and cleaning up
  108. * the peer connection.
  109. *
  110. * @returns {void}
  111. */
  112. stop() {
  113. if (this._peerConnection) {
  114. this._peerConnection.terminate();
  115. }
  116. this._onSessionTerminate();
  117. }
  118. /**
  119. * Instantiates a new {@code JingleSessionPC} by stubbing out the various
  120. * dependencies of {@code JingleSessionPC}.
  121. *
  122. * @private
  123. * @returns {JingleSessionPC}
  124. */
  125. _createPeerConnection() {
  126. /**
  127. * {@code JingleSessionPC} takes in the entire jitsi-meet config.js
  128. * object, which may not be accessible from the caller.
  129. *
  130. * @type {Object}
  131. */
  132. const configStub = {};
  133. /**
  134. * {@code JingleSessionPC} assumes an XMPP/Strophe connection object is
  135. * passed through, which also has the jingle plugin initialized on it.
  136. * This connection object is used to signal out peer connection updates
  137. * via iqs, and those updates need to be piped back out to the remote
  138. * peer.
  139. *
  140. * @type {Object}
  141. */
  142. const connectionStub = {
  143. // At the time this is used for Spot and it's okay to say the connection is always connected, because if
  144. // spot has no signalling it will not be in a meeting where this is used.
  145. connected: true,
  146. jingle: {
  147. terminate: () => { /** no-op */ }
  148. },
  149. sendIQ: this._onSendMessage,
  150. // Returns empty function, because it does not add any listeners for real
  151. // eslint-disable-next-line no-empty-function
  152. addEventListener: () => () => { }
  153. };
  154. /**
  155. * {@code JingleSessionPC} can take in a custom ice configuration,
  156. * depending on the peer connection type, peer-to-peer or other.
  157. * However, {@code ProxyConnectionPC} always assume a peer-to-peer
  158. * connection so the ice configuration is hard-coded with defaults.
  159. *
  160. * @type {Object}
  161. */
  162. const pcConfigStub = {
  163. iceServers: DEFAULT_STUN_SERVERS,
  164. ...this._options.pcConfig
  165. };
  166. /**
  167. * {@code JingleSessionPC} expects an instance of
  168. * {@code JitsiConference}, which has an event emitter that is used
  169. * to signal various connection updates that the local client should
  170. * act upon. The conference instance is not a dependency of a proxy
  171. * connection, but the emitted events can be relevant to the proxy
  172. * connection so the event emitter is stubbed.
  173. *
  174. * @param {string} event - The constant for the event type.
  175. * @type {Function}
  176. * @returns {void}
  177. */
  178. const emitter = event => {
  179. switch (event) {
  180. case XMPPEvents.CONNECTION_ICE_FAILED:
  181. case XMPPEvents.CONNECTION_FAILED:
  182. this._onError(ACTIONS.CONNECTION_ERROR, event);
  183. break;
  184. }
  185. };
  186. /**
  187. * {@link JingleSessionPC} expects an instance of
  188. * {@link ChatRoom} to be passed in. {@link ProxyConnectionPC}
  189. * is instantiated outside of the {@code JitsiConference}, so it must be
  190. * stubbed to prevent errors.
  191. *
  192. * @type {Object}
  193. */
  194. const roomStub = {
  195. addPresenceListener: () => { /** no-op */ },
  196. connectionTimes: [],
  197. eventEmitter: { emit: emitter },
  198. getMediaPresenceInfo: () => {
  199. // Errors occur if this function does not return an object
  200. return {};
  201. },
  202. removePresenceListener: () => { /** no-op */ },
  203. supportsRestartByTerminate: () => false
  204. };
  205. /**
  206. * A {@code JitsiConference} stub passed to the {@link RTC} module.
  207. * @type {Object}
  208. */
  209. const conferenceStub = {
  210. myUserId: () => ''
  211. };
  212. /**
  213. * Create an instance of {@code RTC} as it is required for peer
  214. * connection creation by {@code JingleSessionPC}. An existing instance
  215. * of {@code RTC} from elsewhere should not be re-used because it is
  216. * a stateful grouping of utilities.
  217. */
  218. this._rtc = new RTC(conferenceStub, {});
  219. /**
  220. * Add the remote track listener here as {@code JingleSessionPC} has
  221. * {@code TraceablePeerConnection} which uses {@code RTC}'s event
  222. * emitter.
  223. */
  224. this._rtc.addListener(
  225. RTCEvents.REMOTE_TRACK_ADDED,
  226. this._onRemoteStream
  227. );
  228. const peerConnection = new JingleSessionPC(
  229. undefined, // sid
  230. undefined, // localJid
  231. this._options.peerJid, // remoteJid
  232. connectionStub, // connection
  233. {
  234. offerToReceiveAudio: this._options.receiveAudio,
  235. offerToReceiveVideo: this._options.receiveVideo
  236. }, // mediaConstraints
  237. pcConfigStub, // pcConfig
  238. true, // isP2P
  239. this._options.isInitiator // isInitiator
  240. );
  241. /**
  242. * An additional initialize call is necessary to properly set instance
  243. * variable for calling.
  244. */
  245. peerConnection.initialize(roomStub, this._rtc, configStub);
  246. return peerConnection;
  247. }
  248. /**
  249. * Invoked when a connection related issue has been encountered.
  250. *
  251. * @param {string} errorType - The constant indicating the type of the error
  252. * that occured.
  253. * @param {string} details - Optional additional data about the error.
  254. * @private
  255. * @returns {void}
  256. */
  257. _onError(errorType, details = '') {
  258. this._options.onError(this._options.peerJid, errorType, details);
  259. }
  260. /**
  261. * Callback invoked when the peer connection has received a remote media
  262. * stream.
  263. *
  264. * @param {JitsiRemoteTrack} jitsiRemoteTrack - The remote media stream
  265. * wrapped in {@code JitsiRemoteTrack}.
  266. * @private
  267. * @returns {void}
  268. */
  269. _onRemoteStream(jitsiRemoteTrack) {
  270. this._tracks.push(jitsiRemoteTrack);
  271. this._options.onRemoteStream(jitsiRemoteTrack);
  272. }
  273. /**
  274. * Callback invoked when {@code JingleSessionPC} needs to signal a message
  275. * out to the remote peer.
  276. *
  277. * @param {XML} iq - The message to signal out.
  278. * @private
  279. * @returns {void}
  280. */
  281. _onSendMessage(iq) {
  282. this._options.onSendMessage(this._options.peerJid, iq);
  283. }
  284. /**
  285. * Callback invoked in response to an agreement to start a proxy connection.
  286. * The passed in jingle element should contain an SDP answer to a previously
  287. * sent SDP offer.
  288. *
  289. * @param {Object} $jingle - The jingle element wrapped in jQuery.
  290. * @private
  291. * @returns {void}
  292. */
  293. _onSessionAccept($jingle) {
  294. if (!this._peerConnection) {
  295. logger.error('Received an answer when no peer connection exists.');
  296. return;
  297. }
  298. this._peerConnection.setAnswer($jingle);
  299. }
  300. /**
  301. * Callback invoked in response to a request to start a proxy connection.
  302. * The passed in jingle element should contain an SDP offer.
  303. *
  304. * @param {Object} $jingle - The jingle element wrapped in jQuery.
  305. * @private
  306. * @returns {void}
  307. */
  308. _onSessionInitiate($jingle) {
  309. if (this._peerConnection) {
  310. logger.error('Received an offer when an offer was already sent.');
  311. return;
  312. }
  313. this._peerConnection = this._createPeerConnection();
  314. this._peerConnection.acceptOffer(
  315. $jingle,
  316. () => { /** no-op */ },
  317. () => this._onError(
  318. this._options.peerJid,
  319. ACTIONS.CONNECTION_ERROR,
  320. 'session initiate error'
  321. )
  322. );
  323. }
  324. /**
  325. * Callback invoked in response to a request to disconnect an active proxy
  326. * connection. Cleans up tracks and the peer connection.
  327. *
  328. * @private
  329. * @returns {void}
  330. */
  331. _onSessionTerminate() {
  332. this._tracks.forEach(track => track.dispose());
  333. this._tracks = [];
  334. if (this._peerConnection) {
  335. this._peerConnection.onTerminated();
  336. }
  337. if (this._rtc) {
  338. this._rtc.removeListener(
  339. RTCEvents.REMOTE_TRACK_ADDED,
  340. this._onRemoteStream
  341. );
  342. this._rtc.destroy();
  343. }
  344. }
  345. /**
  346. * Callback invoked in response to ICE candidates from the remote peer.
  347. * The passed in jingle element should contain an ICE candidate.
  348. *
  349. * @param {Object} $jingle - The jingle element wrapped in jQuery.
  350. * @private
  351. * @returns {void}
  352. */
  353. _onTransportInfo($jingle) {
  354. this._peerConnection.addIceCandidates($jingle);
  355. }
  356. }