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.

RTC.js 24KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765
  1. /* global __filename */
  2. import { getLogger } from 'jitsi-meet-logger';
  3. import BridgeChannel from './BridgeChannel';
  4. import GlobalOnErrorHandler from '../util/GlobalOnErrorHandler';
  5. import * as JitsiConferenceEvents from '../../JitsiConferenceEvents';
  6. import JitsiLocalTrack from './JitsiLocalTrack';
  7. import JitsiTrackError from '../../JitsiTrackError';
  8. import * as JitsiTrackErrors from '../../JitsiTrackErrors';
  9. import Listenable from '../util/Listenable';
  10. import * as MediaType from '../../service/RTC/MediaType';
  11. import RTCEvents from '../../service/RTC/RTCEvents';
  12. import RTCUtils from './RTCUtils';
  13. import TraceablePeerConnection from './TraceablePeerConnection';
  14. import VideoType from '../../service/RTC/VideoType';
  15. const logger = getLogger(__filename);
  16. let rtcTrackIdCounter = 0;
  17. /**
  18. *
  19. * @param tracksInfo
  20. * @param options
  21. */
  22. function createLocalTracks(tracksInfo, options) {
  23. const newTracks = [];
  24. let deviceId = null;
  25. tracksInfo.forEach(trackInfo => {
  26. if (trackInfo.mediaType === MediaType.AUDIO) {
  27. deviceId = options.micDeviceId;
  28. } else if (trackInfo.videoType === VideoType.CAMERA) {
  29. deviceId = options.cameraDeviceId;
  30. }
  31. rtcTrackIdCounter += 1;
  32. const localTrack
  33. = new JitsiLocalTrack(
  34. rtcTrackIdCounter,
  35. trackInfo.stream,
  36. trackInfo.track,
  37. trackInfo.mediaType,
  38. trackInfo.videoType,
  39. trackInfo.resolution,
  40. deviceId,
  41. options.facingMode);
  42. newTracks.push(localTrack);
  43. });
  44. return newTracks;
  45. }
  46. /**
  47. *
  48. */
  49. export default class RTC extends Listenable {
  50. /**
  51. *
  52. * @param conference
  53. * @param options
  54. */
  55. constructor(conference, options = {}) {
  56. super();
  57. this.conference = conference;
  58. /**
  59. * A map of active <tt>TraceablePeerConnection</tt>.
  60. * @type {Map.<number, TraceablePeerConnection>}
  61. */
  62. this.peerConnections = new Map();
  63. /**
  64. * The counter used to generated id numbers assigned to peer connections
  65. * @type {number}
  66. */
  67. this.peerConnectionIdCounter = 1;
  68. this.localTracks = [];
  69. this.options = options;
  70. // BridgeChannel instance.
  71. // @private
  72. // @type {BridgeChannel}
  73. this._channel = null;
  74. // A flag whether we had received that the channel had opened we can
  75. // get this flag out of sync if for some reason channel got closed
  76. // from server, a desired behaviour so we can see errors when this
  77. // happen.
  78. // @private
  79. // @type {boolean}
  80. this._channelOpen = false;
  81. /**
  82. * The value specified to the last invocation of setLastN before the
  83. * channel completed opening. If non-null, the value will be sent
  84. * through a channel (once) as soon as it opens and will then be
  85. * discarded.
  86. * @private
  87. * @type {number}
  88. */
  89. this._lastN = -1;
  90. /**
  91. * Defines the last N endpoints list. It can be null or an array once
  92. * initialised with a channel last N event.
  93. * @type {Array<string>|null}
  94. * @private
  95. */
  96. this._lastNEndpoints = null;
  97. /**
  98. * The endpoint ID of currently pinned participant or <tt>null</tt> if
  99. * no user is pinned.
  100. * @type {string|null}
  101. * @private
  102. */
  103. this._pinnedEndpoint = null;
  104. /**
  105. * The endpoint ID of currently selected participant or <tt>null</tt> if
  106. * no user is selected.
  107. * @type {string|null}
  108. * @private
  109. */
  110. this._selectedEndpoint = null;
  111. // The last N change listener.
  112. this._lastNChangeListener = this._onLastNChanged.bind(this);
  113. // Switch audio output device on all remote audio tracks. Local audio
  114. // tracks handle this event by themselves.
  115. if (RTCUtils.isDeviceChangeAvailable('output')) {
  116. RTCUtils.addListener(RTCEvents.AUDIO_OUTPUT_DEVICE_CHANGED,
  117. deviceId => {
  118. const remoteAudioTracks
  119. = this.getRemoteTracks(MediaType.AUDIO);
  120. for (const track of remoteAudioTracks) {
  121. track.setAudioOutput(deviceId);
  122. }
  123. });
  124. }
  125. }
  126. /**
  127. * Creates the local MediaStreams.
  128. * @param {object} [options] Optional parameters.
  129. * @param {array} options.devices The devices that will be requested.
  130. * @param {string} options.resolution Resolution constraints.
  131. * @param {bool} options.dontCreateJitsiTrack If <tt>true</tt> objects with
  132. * the following structure {stream: the Media Stream, type: "audio" or
  133. * "video", videoType: "camera" or "desktop"} will be returned trough
  134. * the Promise, otherwise JitsiTrack objects will be returned.
  135. * @param {string} options.cameraDeviceId
  136. * @param {string} options.micDeviceId
  137. * @returns {*} Promise object that will receive the new JitsiTracks
  138. */
  139. static obtainAudioAndVideoPermissions(options) {
  140. return RTCUtils.obtainAudioAndVideoPermissions(options).then(
  141. tracksInfo => {
  142. const tracks = createLocalTracks(tracksInfo, options);
  143. return tracks.some(track => !track._isReceivingData())
  144. ? Promise.reject(
  145. new JitsiTrackError(
  146. JitsiTrackErrors.NO_DATA_FROM_SOURCE))
  147. : tracks;
  148. });
  149. }
  150. /**
  151. * Initializes the bridge channel of this instance.
  152. * At least one of both, peerconnection or wsUrl parameters, must be
  153. * given.
  154. * @param {RTCPeerConnection} [peerconnection] WebRTC peer connection
  155. * instance.
  156. * @param {string} [wsUrl] WebSocket URL.
  157. */
  158. initializeBridgeChannel(peerconnection, wsUrl) {
  159. this._channel = new BridgeChannel(
  160. peerconnection, wsUrl, this.eventEmitter);
  161. this._channelOpenListener = () => {
  162. // Mark that channel as opened.
  163. this._channelOpen = true;
  164. // When the channel becomes available, tell the bridge about
  165. // video selections so that it can do adaptive simulcast,
  166. // we want the notification to trigger even if userJid
  167. // is undefined, or null.
  168. try {
  169. this._channel.sendPinnedEndpointMessage(
  170. this._pinnedEndpoint);
  171. this._channel.sendSelectedEndpointMessage(
  172. this._selectedEndpoint);
  173. } catch (error) {
  174. GlobalOnErrorHandler.callErrorHandler(error);
  175. logger.error(
  176. `Cannot send selected(${this._selectedEndpoint})`
  177. + `pinned(${this._pinnedEndpoint}) endpoint message.`,
  178. error);
  179. }
  180. this.removeListener(RTCEvents.DATA_CHANNEL_OPEN,
  181. this._channelOpenListener);
  182. this._channelOpenListener = null;
  183. // If setLastN was invoked before the bridge channel completed
  184. // opening, apply the specified value now that the channel
  185. // is open. NOTE that -1 is the default value assumed by both
  186. // RTC module and the JVB.
  187. if (this._lastN !== -1) {
  188. this._channel.sendSetLastNMessage(this._lastN);
  189. }
  190. };
  191. this.addListener(RTCEvents.DATA_CHANNEL_OPEN,
  192. this._channelOpenListener);
  193. // Add Last N change listener.
  194. this.addListener(RTCEvents.LASTN_ENDPOINT_CHANGED,
  195. this._lastNChangeListener);
  196. }
  197. /**
  198. * Receives events when Last N had changed.
  199. * @param {array} lastNEndpoints The new Last N endpoints.
  200. * @private
  201. */
  202. _onLastNChanged(lastNEndpoints = []) {
  203. const oldLastNEndpoints = this._lastNEndpoints || [];
  204. let leavingLastNEndpoints = [];
  205. let enteringLastNEndpoints = [];
  206. this._lastNEndpoints = lastNEndpoints;
  207. leavingLastNEndpoints = oldLastNEndpoints.filter(
  208. id => !this.isInLastN(id));
  209. enteringLastNEndpoints = lastNEndpoints.filter(
  210. id => oldLastNEndpoints.indexOf(id) === -1);
  211. this.conference.eventEmitter.emit(
  212. JitsiConferenceEvents.LAST_N_ENDPOINTS_CHANGED,
  213. leavingLastNEndpoints,
  214. enteringLastNEndpoints);
  215. }
  216. /**
  217. * Should be called when current media session ends and after the
  218. * PeerConnection has been closed using PeerConnection.close() method.
  219. */
  220. onCallEnded() {
  221. if (this._channel) {
  222. // The BridgeChannel is not explicitly closed as the PeerConnection
  223. // is closed on call ended which triggers datachannel onclose
  224. // events. If using a WebSocket, the channel must be closed since
  225. // it is not managed by the PeerConnection.
  226. // The reference is cleared to disable any logic related to the
  227. // channel.
  228. if (this._channel && this._channel.mode === 'websocket') {
  229. this._channel.close();
  230. }
  231. this._channel = null;
  232. this._channelOpen = false;
  233. }
  234. }
  235. /**
  236. * Elects the participant with the given id to be the selected participant
  237. * in order to always receive video for this participant (even when last n
  238. * is enabled).
  239. * If there is no channel we store it and send it through the channel once
  240. * it is created.
  241. * @param {string} id The user id.
  242. * @throws NetworkError or InvalidStateError or Error if the operation
  243. * fails.
  244. */
  245. selectEndpoint(id) {
  246. // Cache the value if channel is missing, till we open it.
  247. this._selectedEndpoint = id;
  248. if (this._channel && this._channelOpen) {
  249. this._channel.sendSelectedEndpointMessage(id);
  250. }
  251. }
  252. /**
  253. * Elects the participant with the given id to be the pinned participant in
  254. * order to always receive video for this participant (even when last n is
  255. * enabled).
  256. * @param {stirng} id The user id.
  257. * @throws NetworkError or InvalidStateError or Error if the operation
  258. * fails.
  259. */
  260. pinEndpoint(id) {
  261. // Cache the value if channel is missing, till we open it.
  262. this._pinnedEndpoint = id;
  263. if (this._channel && this._channelOpen) {
  264. this._channel.sendPinnedEndpointMessage(id);
  265. }
  266. }
  267. /**
  268. *
  269. * @param eventType
  270. * @param listener
  271. */
  272. static addListener(eventType, listener) {
  273. RTCUtils.addListener(eventType, listener);
  274. }
  275. /**
  276. *
  277. * @param eventType
  278. * @param listener
  279. */
  280. static removeListener(eventType, listener) {
  281. RTCUtils.removeListener(eventType, listener);
  282. }
  283. /**
  284. *
  285. */
  286. static isRTCReady() {
  287. return RTCUtils.isRTCReady();
  288. }
  289. /**
  290. *
  291. * @param options
  292. */
  293. static init(options = {}) {
  294. this.options = options;
  295. return RTCUtils.init(this.options);
  296. }
  297. /**
  298. *
  299. */
  300. static getDeviceAvailability() {
  301. return RTCUtils.getDeviceAvailability();
  302. }
  303. /* eslint-disable max-params */
  304. /**
  305. * Creates new <tt>TraceablePeerConnection</tt>
  306. * @param {SignalingLayer} signaling The signaling layer that will
  307. * provide information about the media or participants which is not
  308. * carried over SDP.
  309. * @param {object} iceConfig An object describing the ICE config like
  310. * defined in the WebRTC specification.
  311. * @param {boolean} isP2P Indicates whether or not the new TPC will be used
  312. * in a peer to peer type of session.
  313. * @param {object} options The config options.
  314. * @param {boolean} options.disableSimulcast If set to 'true' will disable
  315. * the simulcast.
  316. * @param {boolean} options.disableRtx If set to 'true' will disable the
  317. * RTX.
  318. * @param {boolean} options.preferH264 If set to 'true' H264 will be
  319. * preferred over other video codecs.
  320. * @return {TraceablePeerConnection}
  321. */
  322. createPeerConnection(signaling, iceConfig, isP2P, options) {
  323. const newConnection
  324. = new TraceablePeerConnection(
  325. this,
  326. this.peerConnectionIdCounter,
  327. signaling, iceConfig, RTC.getPCConstraints(), isP2P, options);
  328. this.peerConnections.set(newConnection.id, newConnection);
  329. this.peerConnectionIdCounter += 1;
  330. return newConnection;
  331. }
  332. /* eslint-enable max-params */
  333. /**
  334. * Removed given peer connection from this RTC module instance.
  335. * @param {TraceablePeerConnection} traceablePeerConnection
  336. * @return {boolean} <tt>true</tt> if the given peer connection was removed
  337. * successfully or <tt>false</tt> if there was no peer connection mapped in
  338. * this RTC instance.
  339. */
  340. _removePeerConnection(traceablePeerConnection) {
  341. const id = traceablePeerConnection.id;
  342. if (this.peerConnections.has(id)) {
  343. // NOTE Remote tracks are not removed here.
  344. this.peerConnections.delete(id);
  345. return true;
  346. }
  347. return false;
  348. }
  349. /**
  350. *
  351. * @param track
  352. */
  353. addLocalTrack(track) {
  354. if (!track) {
  355. throw new Error('track must not be null nor undefined');
  356. }
  357. this.localTracks.push(track);
  358. track.conference = this.conference;
  359. }
  360. /**
  361. * Returns the current value for "lastN" - the amount of videos are going
  362. * to be delivered. When set to -1 for unlimited or all available videos.
  363. * @return {number}
  364. */
  365. getLastN() {
  366. return this._lastN;
  367. }
  368. /**
  369. * Get local video track.
  370. * @returns {JitsiLocalTrack|undefined}
  371. */
  372. getLocalVideoTrack() {
  373. const localVideo = this.getLocalTracks(MediaType.VIDEO);
  374. return localVideo.length ? localVideo[0] : undefined;
  375. }
  376. /**
  377. * Get local audio track.
  378. * @returns {JitsiLocalTrack|undefined}
  379. */
  380. getLocalAudioTrack() {
  381. const localAudio = this.getLocalTracks(MediaType.AUDIO);
  382. return localAudio.length ? localAudio[0] : undefined;
  383. }
  384. /**
  385. * Returns the local tracks of the given media type, or all local tracks if
  386. * no specific type is given.
  387. * @param {MediaType} [mediaType] Optional media type filter.
  388. * (audio or video).
  389. */
  390. getLocalTracks(mediaType) {
  391. let tracks = this.localTracks.slice();
  392. if (mediaType !== undefined) {
  393. tracks = tracks.filter(
  394. track => track.getType() === mediaType);
  395. }
  396. return tracks;
  397. }
  398. /**
  399. * Obtains all remote tracks currently known to this RTC module instance.
  400. * @param {MediaType} [mediaType] The remote tracks will be filtered
  401. * by their media type if this argument is specified.
  402. * @return {Array<JitsiRemoteTrack>}
  403. */
  404. getRemoteTracks(mediaType) {
  405. let remoteTracks = [];
  406. for (const tpc of this.peerConnections.values()) {
  407. const pcRemoteTracks = tpc.getRemoteTracks(undefined, mediaType);
  408. if (pcRemoteTracks) {
  409. remoteTracks = remoteTracks.concat(pcRemoteTracks);
  410. }
  411. }
  412. return remoteTracks;
  413. }
  414. /**
  415. * Set mute for all local audio streams attached to the conference.
  416. * @param value The mute value.
  417. * @returns {Promise}
  418. */
  419. setAudioMute(value) {
  420. const mutePromises = [];
  421. this.getLocalTracks(MediaType.AUDIO).forEach(audioTrack => {
  422. // this is a Promise
  423. mutePromises.push(value ? audioTrack.mute() : audioTrack.unmute());
  424. });
  425. // We return a Promise from all Promises so we can wait for their
  426. // execution.
  427. return Promise.all(mutePromises);
  428. }
  429. /**
  430. *
  431. * @param track
  432. */
  433. removeLocalTrack(track) {
  434. const pos = this.localTracks.indexOf(track);
  435. if (pos === -1) {
  436. return;
  437. }
  438. this.localTracks.splice(pos, 1);
  439. }
  440. /**
  441. * Removes all JitsiRemoteTracks associated with given MUC nickname
  442. * (resource part of the JID). Returns array of removed tracks.
  443. *
  444. * @param {string} Owner The resource part of the MUC JID.
  445. * @returns {JitsiRemoteTrack[]}
  446. */
  447. removeRemoteTracks(owner) {
  448. let removedTracks = [];
  449. for (const tpc of this.peerConnections.values()) {
  450. const pcRemovedTracks = tpc.removeRemoteTracks(owner);
  451. removedTracks = removedTracks.concat(pcRemovedTracks);
  452. }
  453. logger.debug(
  454. `Removed remote tracks for ${owner}`
  455. + ` count: ${removedTracks.length}`);
  456. return removedTracks;
  457. }
  458. /**
  459. *
  460. */
  461. static getPCConstraints() {
  462. return RTCUtils.pcConstraints;
  463. }
  464. /**
  465. *
  466. * @param elSelector
  467. * @param stream
  468. */
  469. static attachMediaStream(elSelector, stream) {
  470. return RTCUtils.attachMediaStream(elSelector, stream);
  471. }
  472. /**
  473. *
  474. * @param stream
  475. */
  476. static getStreamID(stream) {
  477. return RTCUtils.getStreamID(stream);
  478. }
  479. /**
  480. * Returns true if retrieving the the list of input devices is supported
  481. * and false if not.
  482. */
  483. static isDeviceListAvailable() {
  484. return RTCUtils.isDeviceListAvailable();
  485. }
  486. /**
  487. * Returns true if changing the input (camera / microphone) or output
  488. * (audio) device is supported and false if not.
  489. * @param {string} [deviceType] Type of device to change. Default is
  490. * undefined or 'input', 'output' - for audio output device change.
  491. * @returns {boolean} true if available, false otherwise.
  492. */
  493. static isDeviceChangeAvailable(deviceType) {
  494. return RTCUtils.isDeviceChangeAvailable(deviceType);
  495. }
  496. /**
  497. * Returns currently used audio output device id, '' stands for default
  498. * device
  499. * @returns {string}
  500. */
  501. static getAudioOutputDevice() {
  502. return RTCUtils.getAudioOutputDevice();
  503. }
  504. /**
  505. * Returns list of available media devices if its obtained, otherwise an
  506. * empty array is returned/
  507. * @returns {array} list of available media devices.
  508. */
  509. static getCurrentlyAvailableMediaDevices() {
  510. return RTCUtils.getCurrentlyAvailableMediaDevices();
  511. }
  512. /**
  513. * Returns event data for device to be reported to stats.
  514. * @returns {MediaDeviceInfo} device.
  515. */
  516. static getEventDataForActiveDevice(device) {
  517. return RTCUtils.getEventDataForActiveDevice(device);
  518. }
  519. /**
  520. * Sets current audio output device.
  521. * @param {string} deviceId Id of 'audiooutput' device from
  522. * navigator.mediaDevices.enumerateDevices().
  523. * @returns {Promise} resolves when audio output is changed, is rejected
  524. * otherwise
  525. */
  526. static setAudioOutputDevice(deviceId) {
  527. return RTCUtils.setAudioOutputDevice(deviceId);
  528. }
  529. /**
  530. * Returns <tt>true<tt/> if given WebRTC MediaStream is considered a valid
  531. * "user" stream which means that it's not a "receive only" stream nor a
  532. * "mixed" JVB stream.
  533. *
  534. * Clients that implement Unified Plan, such as Firefox use recvonly
  535. * "streams/channels/tracks" for receiving remote stream/tracks, as opposed
  536. * to Plan B where there are only 3 channels: audio, video and data.
  537. *
  538. * @param {MediaStream} stream The WebRTC MediaStream instance.
  539. * @returns {boolean}
  540. */
  541. static isUserStream(stream) {
  542. return RTC.isUserStreamById(RTCUtils.getStreamID(stream));
  543. }
  544. /**
  545. * Returns <tt>true<tt/> if a WebRTC MediaStream identified by given stream
  546. * ID is considered a valid "user" stream which means that it's not a
  547. * "receive only" stream nor a "mixed" JVB stream.
  548. *
  549. * Clients that implement Unified Plan, such as Firefox use recvonly
  550. * "streams/channels/tracks" for receiving remote stream/tracks, as opposed
  551. * to Plan B where there are only 3 channels: audio, video and data.
  552. *
  553. * @param {string} streamId The id of WebRTC MediaStream.
  554. * @returns {boolean}
  555. */
  556. static isUserStreamById(streamId) {
  557. return streamId && streamId !== 'mixedmslabel'
  558. && streamId !== 'default';
  559. }
  560. /**
  561. * Allows to receive list of available cameras/microphones.
  562. * @param {function} callback Would receive array of devices as an
  563. * argument.
  564. */
  565. static enumerateDevices(callback) {
  566. RTCUtils.enumerateDevices(callback);
  567. }
  568. /**
  569. * A method to handle stopping of the stream.
  570. * One point to handle the differences in various implementations.
  571. * @param {MediaStream} mediaStream MediaStream object to stop.
  572. */
  573. static stopMediaStream(mediaStream) {
  574. RTCUtils.stopMediaStream(mediaStream);
  575. }
  576. /**
  577. * Returns whether the desktop sharing is enabled or not.
  578. * @returns {boolean}
  579. */
  580. static isDesktopSharingEnabled() {
  581. return RTCUtils.isDesktopSharingEnabled();
  582. }
  583. /**
  584. * Closes the currently opened bridge channel.
  585. */
  586. closeBridgeChannel() {
  587. if (this._channel) {
  588. this._channel.close();
  589. this._channelOpen = false;
  590. this.removeListener(RTCEvents.LASTN_ENDPOINT_CHANGED,
  591. this._lastNChangeListener);
  592. }
  593. }
  594. /* eslint-disable max-params */
  595. /**
  596. *
  597. * @param {TraceablePeerConnection} tpc
  598. * @param {number} ssrc
  599. * @param {number} audioLevel
  600. * @param {boolean} isLocal
  601. */
  602. setAudioLevel(tpc, ssrc, audioLevel, isLocal) {
  603. const track = tpc.getTrackBySSRC(ssrc);
  604. if (!track) {
  605. return;
  606. } else if (!track.isAudioTrack()) {
  607. logger.warn(`Received audio level for non-audio track: ${ssrc}`);
  608. return;
  609. } else if (track.isLocal() !== isLocal) {
  610. logger.error(
  611. `${track} was expected to ${isLocal ? 'be' : 'not be'} local`);
  612. }
  613. track.setAudioLevel(audioLevel, tpc);
  614. }
  615. /* eslint-enable max-params */
  616. /**
  617. * Sends message via the bridge channel.
  618. * @param {string} to The id of the endpoint that should receive the
  619. * message. If "" the message will be sent to all participants.
  620. * @param {object} payload The payload of the message.
  621. * @throws NetworkError or InvalidStateError or Error if the operation
  622. * fails or there is no data channel created.
  623. */
  624. sendChannelMessage(to, payload) {
  625. if (this._channel) {
  626. this._channel.sendMessage(to, payload);
  627. } else {
  628. throw new Error('Channel support is disabled!');
  629. }
  630. }
  631. /**
  632. * Selects a new value for "lastN". The requested amount of videos are going
  633. * to be delivered after the value is in effect. Set to -1 for unlimited or
  634. * all available videos.
  635. * @param {number} value the new value for lastN.
  636. */
  637. setLastN(value) {
  638. if (this._lastN !== value) {
  639. this._lastN = value;
  640. if (this._channel && this._channelOpen) {
  641. this._channel.sendSetLastNMessage(value);
  642. }
  643. this.eventEmitter.emit(RTCEvents.LASTN_VALUE_CHANGED, value);
  644. }
  645. }
  646. /**
  647. * Indicates if the endpoint id is currently included in the last N.
  648. * @param {string} id The endpoint id that we check for last N.
  649. * @returns {boolean} true if the endpoint id is in the last N or if we
  650. * don't have bridge channel support, otherwise we return false.
  651. */
  652. isInLastN(id) {
  653. return !this._lastNEndpoints // lastNEndpoints not initialised yet.
  654. || this._lastNEndpoints.indexOf(id) > -1;
  655. }
  656. }