UNPKG

twilio-video

Version:

Twilio Video JavaScript Library

772 lines (692 loc) 29.7 kB
'use strict'; const { MediaStreamTrack } = require('./webrtc'); const { asLocalTrack, asLocalTrackPublication, trackClass } = require('./util'); const { typeErrors: E, trackPriority } = require('./util/constants'); const { validateLocalTrack } = require('./util/validate'); const { LocalAudioTrack, LocalDataTrack, LocalVideoTrack } = require('./media/track/es5'); const LocalAudioTrackPublication = require('./media/track/localaudiotrackpublication'); const LocalDataTrackPublication = require('./media/track/localdatatrackpublication'); const LocalVideoTrackPublication = require('./media/track/localvideotrackpublication'); const Participant = require('./participant'); /** * A {@link LocalParticipant} represents the local {@link Participant} in a * {@link Room}. * @extends Participant * @property {Map<Track.SID, LocalAudioTrackPublication>} audioTracks - * The {@link LocalParticipant}'s {@link LocalAudioTrackPublication}s * @property {Map<Track.SID, LocalDataTrackPublication>} dataTracks - * The {@link LocalParticipant}'s {@link LocalDataTrackPublication}s * @property {Map<Track.SID, LocalTrackPublication>} tracks - * The {@link LocalParticipant}'s {@link LocalTrackPublication}s * @property {Map<Track.SID, LocalVideoTrackPublication>} videoTracks - * The {@link LocalParticipant}'s {@link LocalVideoTrackPublication}s * @property {string} signalingRegion - The geographical region of the * signaling edge the {@link LocalParticipant} is connected to. * * @emits RemoteParticipant#reconnected * @emits RemoteParticipant#reconnecting * @emits LocalParticipant#trackDimensionsChanged * @emits LocalParticipant#trackDisabled * @emits LocalParticipant#trackEnabled * @emits LocalParticipant#trackPublicationFailed * @emits LocalParticipant#trackPublished * @emits LocalParticipant#trackStarted * @emits LocalParticipant#trackStopped * @emits LocalParticipant#trackWarning * @emits LocalParticipant#trackWarningsCleared */ class LocalParticipant extends Participant { /** * Construct a {@link LocalParticipant}. * @param {ParticipantSignaling} signaling * @param {Array<LocalTrack>} localTracks * @param {Object} options */ constructor(signaling, localTracks, options) { options = Object.assign({ LocalAudioTrack, LocalVideoTrack, LocalDataTrack, MediaStreamTrack, LocalAudioTrackPublication, LocalVideoTrackPublication, LocalDataTrackPublication, shouldStopLocalTracks: false, tracks: localTracks }, options); const tracksToStop = options.shouldStopLocalTracks ? new Set(localTracks.filter(localTrack => localTrack.kind !== 'data')) : new Set(); super(signaling, options); Object.defineProperties(this, { _eventObserver: { value: options.eventObserver }, _LocalAudioTrack: { value: options.LocalAudioTrack }, _LocalDataTrack: { value: options.LocalDataTrack }, _LocalVideoTrack: { value: options.LocalVideoTrack }, _MediaStreamTrack: { value: options.MediaStreamTrack }, _LocalAudioTrackPublication: { value: options.LocalAudioTrackPublication }, _LocalDataTrackPublication: { value: options.LocalDataTrackPublication }, _LocalVideoTrackPublication: { value: options.LocalVideoTrackPublication }, _tracksToStop: { value: tracksToStop }, signalingRegion: { enumerable: true, get() { return signaling.signalingRegion; } } }); this._handleTrackSignalingEvents(); } /** * @private * @param {LocalTrack} track * @param {Track.ID} id * @param {Track.Priority} priority * @returns {?LocalTrack} */ _addTrack(track, id, priority) { const addedTrack = super._addTrack(track, id); if (addedTrack && this.state !== 'disconnected') { this._addLocalTrack(track, priority); } return addedTrack; } /** * @private * @param {LocalTrack} track * @param {Track.Priority} priority * @returns {void} */ _addLocalTrack(track, priority) { // check if track has noise cancellation enabled. const vendor = track.noiseCancellation?.vendor; this._signaling.addTrack(track._trackSender, track.name, priority, vendor); this._log.info(`Added a new ${trackClass(track, true)}:`, track.id); this._log.debug(`${trackClass(track, true)}:`, track); } /** * @private * @param {LocalTrack} track * @param {Track.ID} id * @returns {?LocalTrack} */ _removeTrack(track, id) { const removedTrack = super._removeTrack(track, id); if (removedTrack && this.state !== 'disconnected') { this._signaling.removeTrack(track._trackSender); this._log.info(`Removed a ${trackClass(track, true)}:`, track.id); this._log.debug(`${trackClass(track, true)}:`, track); } return removedTrack; } /** * Get the {@link LocalTrack} events to re-emit. * @private * @returns {Array<Array<string>>} events */ _getTrackEvents() { return super._getTrackEvents.call(this).concat([ ['disabled', 'trackDisabled'], ['enabled', 'trackEnabled'], ['stopped', 'trackStopped'] ]); } toString() { return `[LocalParticipant #${this._instanceId}${this.sid ? `: ${this.sid}` : ''}]`; } /** * @private */ _handleTrackSignalingEvents() { const log = this._log; if (this.state === 'disconnected') { return; } const localTrackDisabled = localTrack => { const trackSignaling = this._signaling.getPublication(localTrack._trackSender); if (trackSignaling) { trackSignaling.disable(); log.debug(`Disabled the ${trackClass(localTrack, true)}:`, localTrack.id); } }; const localTrackEnabled = localTrack => { const trackSignaling = this._signaling.getPublication(localTrack._trackSender); if (trackSignaling) { trackSignaling.enable(); log.debug(`Enabled the ${trackClass(localTrack, true)}:`, localTrack.id); } }; const localTrackStopped = localTrack => { // NOTE(mroberts): We shouldn't need to check for `stop`, since DataTracks // do not emit "stopped". const trackSignaling = this._signaling.getPublication(localTrack._trackSender); if (trackSignaling) { trackSignaling.stop(); } return trackSignaling; }; const stateChanged = state => { log.debug('Transitioned to state:', state); if (state === 'disconnected') { log.debug('Removing LocalTrack event listeners'); this._signaling.removeListener('stateChanged', stateChanged); this.removeListener('trackDisabled', localTrackDisabled); this.removeListener('trackEnabled', localTrackEnabled); this.removeListener('trackStopped', localTrackStopped); // NOTE(mmalavalli): Remove the stale MediaTrackSender clones so that we // do not call replaceTrack() on their RTCRtpSenders. this._tracks.forEach(track => { const trackSignaling = localTrackStopped(track); if (trackSignaling) { track._trackSender.removeClone(trackSignaling._trackTransceiver); } }); log.info(`LocalParticipant disconnected. Stopping ${this._tracksToStop.size} automatically-acquired LocalTracks`); this._tracksToStop.forEach(track => { track.stop(); }); } else if (state === 'connected') { // NOTE(mmalavalli): Any transition to "connected" here is a result of // successful signaling reconnection, and not a first-time establishment // of the signaling connection. log.info('reconnected'); // NOTE(mpatwardhan): `stateChanged` can get emitted with StateMachine locked. // Do not signal public events synchronously with lock held. setTimeout(() => this.emit('reconnected'), 0); } }; this.on('trackDisabled', localTrackDisabled); this.on('trackEnabled', localTrackEnabled); this.on('trackStopped', localTrackStopped); this._signaling.on('stateChanged', stateChanged); this._tracks.forEach(track => { this._addLocalTrack(track, trackPriority.PRIORITY_STANDARD); this._getOrCreateLocalTrackPublication(track).catch(error => { // Just log a warning for now. log.warn(`Failed to get or create LocalTrackPublication for ${track}:`, error); }); }); } /** * @private * @param {LocalTrack} localTrack * @returns {Promise<LocalTrackPublication>} */ _getOrCreateLocalTrackPublication(localTrack) { let localTrackPublication = getTrackPublication(this.tracks, localTrack); if (localTrackPublication) { return Promise.resolve(localTrackPublication); } const log = this._log; const self = this; const trackSignaling = this._signaling.getPublication(localTrack._trackSender); if (!trackSignaling) { return Promise.reject(new Error(`Unexpected error: The ${localTrack} cannot be published`)); } return new Promise((resolve, reject) => { function updated() { const error = trackSignaling.error; if (error) { trackSignaling.removeListener('updated', updated); log.warn(`Failed to publish the ${trackClass(localTrack, true)}: ${error.message}`); self._removeTrack(localTrack, localTrack.id); setTimeout(() => { self.emit('trackPublicationFailed', error, localTrack); }); reject(error); return; } if (!self._tracks.has(localTrack.id)) { trackSignaling.removeListener('updated', updated); reject(new Error(`The ${localTrack} was unpublished`)); return; } const sid = trackSignaling.sid; if (!sid) { return; } trackSignaling.removeListener('updated', updated); const options = { log, LocalAudioTrackPublication: self._LocalAudioTrackPublication, LocalDataTrackPublication: self._LocalDataTrackPublication, LocalVideoTrackPublication: self._LocalVideoTrackPublication }; localTrackPublication = getTrackPublication(self.tracks, localTrack); const warningHandler = twilioWarningName => self.emit('trackWarning', twilioWarningName, localTrackPublication); const warningsClearedHandler = () => self.emit('trackWarningsCleared', localTrackPublication); const unpublish = publication => { localTrackPublication.removeListener('trackWarning', warningHandler); localTrackPublication.removeListener('trackWarningsCleared', warningsClearedHandler); self.unpublishTrack(publication.track); }; if (!localTrackPublication) { localTrackPublication = asLocalTrackPublication(localTrack, trackSignaling, unpublish, options); self._addTrackPublication(localTrackPublication); } localTrackPublication.on('warning', warningHandler); localTrackPublication.on('warningsCleared', warningsClearedHandler); const { state } = self._signaling; if (state === 'connected' || state === 'connecting') { if (localTrack._processorEventObserver) { localTrack._processorEventObserver.on('event', event => { self._eventObserver.emit('event', { name: event.name, payload: event.data, group: 'video-processor', level: 'info' }); }); } // NOTE(csantos): For tracks created before joining a room or already joined but about to publish it if (localTrack.processedTrack) { localTrack._captureFrames(); localTrack._setSenderMediaStreamTrack(true); } } if (state === 'connected') { setTimeout(() => { self.emit('trackPublished', localTrackPublication); }); } resolve(localTrackPublication); } trackSignaling.on('updated', updated); }); } /** * Publishes a {@link LocalTrack} to the {@link Room}. * @param {LocalTrack} localTrack - The {@link LocalTrack} to publish * @param {LocalTrackPublishOptions} [options] - The {@link LocalTrackPublishOptions} * for publishing the {@link LocalTrack} * @returns {Promise<LocalTrackPublication>} - Resolves with the corresponding * {@link LocalTrackPublication} if successful; In a Large Group Room (Maximum * Participants greater than 50), rejects with a {@link ParticipantMaxTracksExceededError} * if either the total number of published Tracks in the Room exceeds 16, or the {@link LocalTrack} * is part of a set of {@link LocalTrack}s which along with the published Tracks exceeds 16. * @throws {TypeError} * @throws {RangeError} * @example * var Video = require('twilio-video'); * * Video.connect(token, { * name: 'my-cool-room', * audio: true * }).then(function(room) { * return Video.createLocalVideoTrack({ * name: 'camera' * }).then(function(localVideoTrack) { * return room.localParticipant.publishTrack(localVideoTrack, { * priority: 'high' * }); * }); * }).then(function(publication) { * console.log('The LocalTrack "' + publication.trackName * + '" was successfully published with priority "' * * publication.priority + '"'); * }); *//** * Publishes a MediaStreamTrack to the {@link Room}. * @param {MediaStreamTrack} mediaStreamTrack - The MediaStreamTrack * to publish; if a corresponding {@link LocalAudioTrack} or * {@link LocalVideoTrack} has not yet been published, this method will * construct one * @param {MediaStreamTrackPublishOptions} [options] - The options for publishing * the MediaStreamTrack * @returns {Promise<LocalTrackPublication>} - Resolves with the corresponding * {@link LocalTrackPublication} if successful; In a Large Group Room (Maximum * Participants greater than 50), rejects with a {@link ParticipantMaxTracksExceededError} * if the total number of published Tracks in the Room exceeds 16, or the {@link LocalTrack} * is part of a set of {@link LocalTrack}s which along with the published Tracks exceeds 16. * @throws {TypeError} * @throws {RangeError} * @example * var Video = require('twilio-video'); * * Video.connect(token, { * name: 'my-cool-room', * audio: true * }).then(function(room) { * return navigator.mediaDevices.getUserMedia({ * video: true * }).then(function(mediaStream) { * var mediaStreamTrack = mediaStream.getTracks()[0]; * return room.localParticipant.publishTrack(mediaStreamTrack, { * name: 'camera', * priority: 'high' * }); * }); * }).then(function(publication) { * console.log('The LocalTrack "' + publication.trackName * + '" was successfully published with priority "' * * publication.priority + '"'); * }); */ publishTrack(localTrackOrMediaStreamTrack, options) { const trackPublication = getTrackPublication(this.tracks, localTrackOrMediaStreamTrack); if (trackPublication) { return Promise.resolve(trackPublication); } options = Object.assign({ log: this._log, priority: trackPriority.PRIORITY_STANDARD, LocalAudioTrack: this._LocalAudioTrack, LocalDataTrack: this._LocalDataTrack, LocalVideoTrack: this._LocalVideoTrack, MediaStreamTrack: this._MediaStreamTrack }, options); let localTrack; try { localTrack = asLocalTrack(localTrackOrMediaStreamTrack, options); } catch (error) { return Promise.reject(error); } const noiseCancellation = localTrack.noiseCancellation; const allowedAudioProcessors = this._signaling.audioProcessors; if (noiseCancellation && !allowedAudioProcessors.includes(noiseCancellation.vendor)) { this._log.warn(`${noiseCancellation.vendor} is not supported in this room. disabling it permanently`); noiseCancellation.disablePermanently(); } const priorityValues = Object.values(trackPriority); if (!priorityValues.includes(options.priority)) { // eslint-disable-next-line new-cap return Promise.reject(E.INVALID_VALUE('LocalTrackPublishOptions.priority', priorityValues)); } let addedLocalTrack = this._addTrack(localTrack, localTrack.id, options.priority) || this._tracks.get(localTrack.id); return this._getOrCreateLocalTrackPublication(addedLocalTrack); } /** * Publishes multiple {@link LocalTrack}s to the {@link Room}. * @param {Array<LocalTrack|MediaStreamTrack>} tracks - The {@link LocalTrack}s * to publish; for any MediaStreamTracks provided, if a corresponding * {@link LocalAudioTrack} or {@link LocalVideoTrack} has not yet been * published, this method will construct one * @returns {Promise<Array<LocalTrackPublication>>} - The resulting * {@link LocalTrackPublication}s if successful; In a Large Group Room (Maximum * Participants greater than 50), rejects with a {@link ParticipantMaxTracksExceededError} * if the total number of published Tracks in the Room exceeds 16, or the {@link LocalTrack}s * along with the published Tracks exceeds 16. * @throws {TypeError} */ publishTracks(tracks) { if (!Array.isArray(tracks)) { // eslint-disable-next-line new-cap throw E.INVALID_TYPE('tracks', 'Array of LocalAudioTrack, LocalVideoTrack, LocalDataTrack, or MediaStreamTrack'); } return Promise.all(tracks.map(this.publishTrack, this)); } setBandwidthProfile() { this._log.warn('setBandwidthProfile is not implemented yet and may be available in future versions of twilio-video.js'); } /** * Sets the {@link NetworkQualityVerbosity} for the {@link LocalParticipant} and * {@link RemoteParticipant}s. It does nothing if Network Quality is not enabled * while calling {@link connect}. * @param {NetworkQualityConfiguration} networkQualityConfiguration - The new * {@link NetworkQualityConfiguration}; If either or both of the local and * remote {@link NetworkQualityVerbosity} values are absent, then the corresponding * existing values are retained * @returns {this} * @example * // Update verbosity levels for both LocalParticipant and RemoteParticipants * localParticipant.setNetworkQualityConfiguration({ * local: 1, * remote: 2 * }); * @example * // Update verbosity level for only the LocalParticipant * localParticipant.setNetworkQualityConfiguration({ * local: 1 * }); * @example * // Update verbosity level for only the RemoteParticipants * localParticipant.setNetworkQualityConfiguration({ * remote: 2 * }); */ setNetworkQualityConfiguration(networkQualityConfiguration) { if (typeof networkQualityConfiguration !== 'object' || networkQualityConfiguration === null) { // eslint-disable-next-line new-cap throw E.INVALID_TYPE('networkQualityConfiguration', 'NetworkQualityConfiguration'); } ['local', 'remote'].forEach(prop => { if (prop in networkQualityConfiguration && (typeof networkQualityConfiguration[prop] !== 'number' || isNaN(networkQualityConfiguration[prop]))) { // eslint-disable-next-line new-cap throw E.INVALID_TYPE(`networkQualityConfiguration.${prop}`, 'number'); } }); this._signaling.setNetworkQualityConfiguration(networkQualityConfiguration); return this; } /** * Set the {@link LocalParticipant}'s {@link EncodingParameters}. * @param {?EncodingParameters} [encodingParameters] - The new * {@link EncodingParameters}; If null, then the bitrate limits are removed; * If not specified, then the existing bitrate limits are preserved * @returns {this} * @throws {TypeError} */ setParameters(encodingParameters) { if (typeof encodingParameters !== 'undefined' && typeof encodingParameters !== 'object') { // eslint-disable-next-line new-cap throw E.INVALID_TYPE('encodingParameters', 'EncodingParameters, null or undefined'); } if (encodingParameters) { if (this._signaling.getParameters().adaptiveSimulcast && encodingParameters.maxVideoBitrate) { // eslint-disable-next-line new-cap throw E.INVALID_TYPE('encodingParameters', 'encodingParameters.maxVideoBitrate is not compatible with "preferredVideoCodecs=auto"'); } ['maxAudioBitrate', 'maxVideoBitrate'].forEach(prop => { if (typeof encodingParameters[prop] !== 'undefined' && typeof encodingParameters[prop] !== 'number' && encodingParameters[prop] !== null) { // eslint-disable-next-line new-cap throw E.INVALID_TYPE(`encodingParameters.${prop}`, 'number, null or undefined'); } }); } else if (encodingParameters === null) { encodingParameters = { maxAudioBitrate: null, maxVideoBitrate: null }; } this._signaling.setParameters(encodingParameters); return this; } /** * Stops publishing a {@link LocalTrack} to the {@link Room}. * @param {LocalTrack|MediaStreamTrack} track - The {@link LocalTrack} * to stop publishing; if a MediaStreamTrack is provided, this method * looks up the corresponding {@link LocalAudioTrack} or * {@link LocalVideoTrack} to stop publishing * @returns {?LocalTrackPublication} - The corresponding * {@link LocalTrackPublication} if the {@link LocalTrack} was previously * published, null otherwise * @throws {TypeError} */ unpublishTrack(track) { validateLocalTrack(track, { LocalAudioTrack: this._LocalAudioTrack, LocalDataTrack: this._LocalDataTrack, LocalVideoTrack: this._LocalVideoTrack, MediaStreamTrack: this._MediaStreamTrack }); let localTrack = this._tracks.get(track.id); if (!localTrack) { return null; } const trackSignaling = this._signaling.getPublication(localTrack._trackSender); trackSignaling.publishFailed(new Error(`The ${localTrack} was unpublished`)); localTrack = this._removeTrack(localTrack, localTrack.id); if (!localTrack) { return null; } const localTrackPublication = getTrackPublication(this.tracks, localTrack); if (localTrackPublication) { this._removeTrackPublication(localTrackPublication); } return localTrackPublication; } /** * Stops publishing multiple {@link LocalTrack}s to the {@link Room}. * @param {Array<LocalTrack|MediaStreamTrack>} tracks - The {@link LocalTrack}s * to stop publishing; for any MediaStreamTracks provided, this method looks * up the corresponding {@link LocalAudioTrack} or {@link LocalVideoTrack} to * stop publishing * @returns {Array<LocalTrackPublication>} - The corresponding * {@link LocalTrackPublication}s that were successfully unpublished * @throws {TypeError} */ unpublishTracks(tracks) { if (!Array.isArray(tracks)) { // eslint-disable-next-line new-cap throw E.INVALID_TYPE('tracks', 'Array of LocalAudioTrack, LocalVideoTrack, LocalDataTrack, or MediaStreamTrack'); } return tracks.reduce((unpublishedTracks, track) => { const unpublishedTrack = this.unpublishTrack(track); return unpublishedTrack ? unpublishedTracks.concat(unpublishedTrack) : unpublishedTracks; }, []); } } /** * The {@link LocalParticipant} has reconnected to the {@link Room} after a signaling connection disruption. * @event LocalParticipant#reconnected */ /** * The {@link LocalParticipant} is reconnecting to the {@link Room} after a signaling connection disruption. * @event LocalParticipant#reconnecting */ /** * One of the {@link LocalParticipant}'s {@link LocalVideoTrack}'s dimensions changed. * @param {LocalVideoTrack} track - The {@link LocalVideoTrack} whose dimensions changed * @event LocalParticipant#trackDimensionsChanged */ /** * A {@link LocalTrack} was disabled by the {@link LocalParticipant}. * @param {LocalTrack} track - The {@link LocalTrack} that was disabled * @event LocalParticipant#trackDisabled */ /** * A {@link LocalTrack} was enabled by the {@link LocalParticipant}. * @param {LocalTrack} track - The {@link LocalTrack} that was enabled * @event LocalParticipant#trackEnabled */ /** * A {@link LocalTrack} failed to publish. Check the error message for more * information. In a Large Group Room (Maximum Participants greater than 50), * this event is raised with a {@link ParticipantMaxTracksExceededError} either * when attempting to publish the {@link LocalTrack} will exceed the Maximum Published * Tracks limit of 16, or the {@link LocalTrack} is part of a set of {@link LocalTrack}s * which along with the published Tracks exceeds 16. * @param {TwilioError} error - A {@link TwilioError} explaining why publication * failed * @param {LocalTrack} localTrack - The {@link LocalTrack} that failed to * publish * @event LocalParticipant#trackPublicationFailed */ /** * A {@link LocalTrack} that was added using {@link LocalParticipant#publishTrack} was successfully published. This event * is not raised for {@link LocalTrack}s added in {@link ConnectOptions}<code>.tracks</code> or auto-created within * <a href="module-twilio-video.html#.connect__anchor"><code>{@link connect}</code></a>. * @param {LocalTrackPublication} publication - The resulting * {@link LocalTrackPublication} for the published {@link LocalTrack} * @event LocalParticipant#trackPublished */ /** * One of the {@link LocalParticipant}'s {@link LocalTrack}s started. * @param {LocalTrack} track - The {@link LocalTrack} that started * @event LocalParticipant#trackStarted */ /** * One of the {@link LocalParticipant}'s {@link LocalTrack}s stopped, either * because {@link LocalTrack#stop} was called or because the underlying * MediaStreamTrack ended). * @param {LocalTrack} track - The {@link LocalTrack} that stopped * @event LocalParticipant#trackStopped */ /** * One of the {@link LocalParticipant}'s {@link LocalTrackPublication}s encountered a warning. * This event is only raised if you enabled warnings using <code>notifyWarnings</code> in <code>ConnectOptions</code>. * @param {string} name - The warning that was raised. * @param {LocalTrackPublication} publication - The {@link LocalTrackPublication} that encountered the warning. * @event LocalParticipant#trackWarning */ /** * One of the {@link LocalParticipant}'s {@link LocalTrackPublication}s cleared all warnings. * This event is only raised if you enabled warnings using <code>notifyWarnings</code> in <code>ConnectOptions</code>. * @param {LocalTrackPublication} publication - The {@link LocalTrackPublication} that cleared all warnings. * @event LocalParticipant#trackWarningsCleared */ /** * Outgoing media encoding parameters. * @typedef {object} EncodingParameters * @property {?number} [maxAudioBitrate] - Max outgoing audio bitrate (bps); * If not specified, retains the existing bitrate limit; A <code>null</code> or a * <code>0</code> value removes any previously set bitrate limit; This value is set * as a hint for variable bitrate codecs, but will not take effect for fixed bitrate * codecs; Based on our tests, Chrome, Firefox and Safari support a bitrate range of * 12000 bps to 256000 bps for Opus codec; This parameter has no effect on iSAC, PCMU * and PCMA codecs * @property {?number} [maxVideoBitrate] - Max outgoing video bitrate (bps); * If not specified, retains the existing bitrate limit; A <code>null</code> or * a <code>0</code> value removes any previously set bitrate limit; This value is * set as a hint for variable bitrate codecs, but will not take effect for fixed * bitrate codecs; Based on our tests, Chrome, Firefox and Safari all seem to support * an average bitrate range of 20000 bps (20 kbps) to 8000000 bps (8 mbps) for a * 720p VideoTrack. * Note: this limit is not applied for screen share tracks published on Chrome. */ /** * Options for publishing a {@link LocalTrack}. * @typedef {object} LocalTrackPublishOptions * @property {Track.Priority} [priority='standard'] - The priority with which the {@link LocalTrack} * is to be published; In Group or Small Group Rooms, the appropriate bandwidth is * allocated to the {@link LocalTrack} based on its {@link Track.Priority}; It has no * effect in Peer-to-Peer Rooms; It defaults to "standard" when not provided */ /** * Options for publishing a {@link MediaStreamTrack}. * @typedef {LocalTrackOptions} MediaStreamTrackPublishOptions * @property {Track.Priority} [priority='standard'] - The priority with which the {@link LocalTrack} * is to be published; In Group or Small Group Rooms, the appropriate bandwidth is * allocated to the {@link LocalTrack} based on its {@link Track.Priority}; It has no * effect in Peer-to-Peer Rooms; It defaults to "standard" when not provided */ /** * @private * @param {Map<Track.SID, LocalTrackPublication>} trackPublications * @param {LocalTrack|MediaStreamTrack} track * @returns {?LocalTrackPublication} trackPublication */ function getTrackPublication(trackPublications, track) { return Array.from(trackPublications.values()).find(trackPublication => trackPublication.track === track || trackPublication.track.mediaStreamTrack === track) || null; } module.exports = LocalParticipant;