選択できるのは25トピックまでです。 トピックは、先頭が英数字で、英数字とダッシュ('-')を使用した35文字以内のものにしてください。

ProxyConnectionPC.js 13KB

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