twilio-video
Version:
Twilio Video JavaScript Library
632 lines • 26.2 kB
JavaScript
'use strict';
var __extends = (this && this.__extends) || (function () {
var extendStatics = function (d, b) {
extendStatics = Object.setPrototypeOf ||
({ __proto__: [] } instanceof Array && function (d, b) { d.__proto__ = b; }) ||
function (d, b) { for (var p in b) if (Object.prototype.hasOwnProperty.call(b, p)) d[p] = b[p]; };
return extendStatics(d, b);
};
return function (d, b) {
if (typeof b !== "function" && b !== null)
throw new TypeError("Class extends value " + String(b) + " is not a constructor or null");
extendStatics(d, b);
function __() { this.constructor = d; }
d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
})();
var __read = (this && this.__read) || function (o, n) {
var m = typeof Symbol === "function" && o[Symbol.iterator];
if (!m) return o;
var i = m.call(o), r, ar = [], e;
try {
while ((n === void 0 || n-- > 0) && !(r = i.next()).done) ar.push(r.value);
}
catch (error) { e = { error: error }; }
finally {
try {
if (r && !r.done && (m = i["return"])) m.call(i);
}
finally { if (e) throw e.error; }
}
return ar;
};
var __spreadArray = (this && this.__spreadArray) || function (to, from, pack) {
if (pack || arguments.length === 2) for (var i = 0, l = from.length, ar; i < l; i++) {
if (ar || !(i in from)) {
if (!ar) ar = Array.prototype.slice.call(from, 0, i);
ar[i] = from[i];
}
}
return to.concat(ar || Array.prototype.slice.call(from));
};
var EventEmitter = require('./eventemitter');
var RemoteAudioTrack = require('./media/track/remoteaudiotrack');
var RemoteAudioTrackPublication = require('./media/track/remoteaudiotrackpublication');
var RemoteDataTrack = require('./media/track/remotedatatrack');
var RemoteDataTrackPublication = require('./media/track/remotedatatrackpublication');
var RemoteVideoTrack = require('./media/track/remotevideotrack');
var RemoteVideoTrackPublication = require('./media/track/remotevideotrackpublication');
var util = require('./util');
var nInstances = 0;
/**
* {@link NetworkQualityLevel} is a value from 0–5, inclusive, representing the
* quality of a network connection.
* @typedef {number} NetworkQualityLevel
*/
/**
* @extends EventEmitter
* @property {Map<Track.SID, AudioTrackPublication>} audioTracks -
* The {@link Participant}'s {@link AudioTrackPublication}s
* @property {Map<Track.SID, DataTrackPublication>} dataTracks -
* The {@link Participant}'s {@link DataTrackPublication}s.
* @property {Participant.Identity} identity - The identity of the {@link Participant}
* @property {?NetworkQualityLevel} networkQualityLevel - The
* {@link Participant}'s current {@link NetworkQualityLevel}, if any
* @property {?NetworkQualityStats} networkQualityStats - The
* {@link Participant}'s current {@link NetworkQualityStats}, if any
* @property {Participant.SID} sid - The {@link Participant}'s SID
* @property {string} state - "connected", "disconnected" or "reconnecting"
* @property {Map<Track.SID, TrackPublication>} tracks -
* The {@link Participant}'s {@link TrackPublication}s
* @property {Map<Track.SID, VideoTrackPublication>} videoTracks -
* The {@link Participant}'s {@link VideoTrackPublication}s
* @emits Participant#disconnected
* @emits Participant#networkQualityLevelChanged
* @emits Participant#reconnected
* @emits Participant#reconnecting
* @emits Participant#trackDimensionsChanged
* @emits Participant#trackStarted
*/
var Participant = /** @class */ (function (_super) {
__extends(Participant, _super);
/**
* Construct a {@link Participant}.
* @param {ParticipantSignaling} signaling
* @param {object} [options]
*/
function Participant(signaling, options) {
var _this = _super.call(this) || this;
options = Object.assign({
RemoteAudioTrack: RemoteAudioTrack,
RemoteAudioTrackPublication: RemoteAudioTrackPublication,
RemoteDataTrack: RemoteDataTrack,
RemoteDataTrackPublication: RemoteDataTrackPublication,
RemoteVideoTrack: RemoteVideoTrack,
RemoteVideoTrackPublication: RemoteVideoTrackPublication,
tracks: []
}, options);
var indexed = indexTracksById(options.tracks);
var log = options.log.createLog('default', _this);
var audioTracks = new Map(indexed.audioTracks);
var dataTracks = new Map(indexed.dataTracks);
var tracks = new Map(indexed.tracks);
var videoTracks = new Map(indexed.videoTracks);
Object.defineProperties(_this, {
_RemoteAudioTrack: {
value: options.RemoteAudioTrack
},
_RemoteAudioTrackPublication: {
value: options.RemoteAudioTrackPublication
},
_RemoteDataTrack: {
value: options.RemoteDataTrack
},
_RemoteDataTrackPublication: {
value: options.RemoteDataTrackPublication
},
_RemoteVideoTrack: {
value: options.RemoteVideoTrack
},
_RemoteVideoTrackPublication: {
value: options.RemoteVideoTrackPublication
},
_audioTracks: {
value: audioTracks
},
_dataTracks: {
value: dataTracks
},
_instanceId: {
value: ++nInstances
},
_clientTrackSwitchOffControl: {
value: options.clientTrackSwitchOffControl,
},
_contentPreferencesMode: {
value: options.contentPreferencesMode,
},
_log: {
value: log
},
_signaling: {
value: signaling
},
_tracks: {
value: tracks
},
_trackEventReemitters: {
value: new Map()
},
_trackPublicationEventReemitters: {
value: new Map()
},
_trackSignalingUpdatedEventCallbacks: {
value: new Map()
},
_videoTracks: {
value: videoTracks
},
audioTracks: {
enumerable: true,
value: new Map()
},
dataTracks: {
enumerable: true,
value: new Map()
},
identity: {
enumerable: true,
get: function () {
return signaling.identity;
}
},
networkQualityLevel: {
enumerable: true,
get: function () {
return signaling.networkQualityLevel;
}
},
networkQualityStats: {
enumerable: true,
get: function () {
return signaling.networkQualityStats;
}
},
sid: {
enumerable: true,
get: function () {
return signaling.sid;
}
},
state: {
enumerable: true,
get: function () {
return signaling.state;
}
},
tracks: {
enumerable: true,
value: new Map()
},
videoTracks: {
enumerable: true,
value: new Map()
},
_MediaStream: {
value: options.MediaStream
},
_mapMediaElement: {
value: options.mapMediaElement
},
_disposeMediaElement: {
value: options.disposeMediaElement
}
});
_this._tracks.forEach(reemitTrackEvents.bind(null, _this));
signaling.on('networkQualityLevelChanged', function () {
return _this.emit('networkQualityLevelChanged', _this.networkQualityLevel, _this.networkQualityStats &&
(_this.networkQualityStats.audio || _this.networkQualityStats.video)
? _this.networkQualityStats
: null);
});
reemitSignalingStateChangedEvents(_this, signaling);
log.info("Created a new Participant".concat(_this.identity ? ": ".concat(_this.identity) : ''));
return _this;
}
/**
* Get the {@link RemoteTrack} events to re-emit.
* @private
* @returns {Array<Array<string>>} events
*/
Participant.prototype._getTrackEvents = function () {
return [
['dimensionsChanged', 'trackDimensionsChanged'],
['message', 'trackMessage'],
['started', 'trackStarted']
];
};
/**
* @private
*/
Participant.prototype._getTrackPublicationEvents = function () {
return [];
};
Participant.prototype.toString = function () {
return "[Participant #".concat(this._instanceId, ": ").concat(this.sid, "]");
};
/**
* @private
* @param {RemoteTrack} track
* @param {Track.ID} id
* @returns {?RemoteTrack}
*/
Participant.prototype._addTrack = function (track, id) {
var log = this._log;
if (this._tracks.has(id)) {
return null;
}
this._tracks.set(id, track);
var tracksByKind = {
audio: this._audioTracks,
video: this._videoTracks,
data: this._dataTracks
}[track.kind];
tracksByKind.set(id, track);
reemitTrackEvents(this, track, id);
log.info("Added a new ".concat(util.trackClass(track), ":"), id);
log.debug("".concat(util.trackClass(track), ":"), track);
return track;
};
/**
* @private
* @param {RemoteTrackPublication} publication
* @returns {?RemoteTrackPublication}
*/
Participant.prototype._addTrackPublication = function (publication) {
var log = this._log;
if (this.tracks.has(publication.trackSid)) {
return null;
}
this.tracks.set(publication.trackSid, publication);
var trackPublicationsByKind = {
audio: this.audioTracks,
data: this.dataTracks,
video: this.videoTracks
}[publication.kind];
trackPublicationsByKind.set(publication.trackSid, publication);
reemitTrackPublicationEvents(this, publication);
log.info("Added a new ".concat(util.trackPublicationClass(publication), ":"), publication.trackSid);
log.debug("".concat(util.trackPublicationClass(publication), ":"), publication);
return publication;
};
/**
* @private
*/
Participant.prototype._handleTrackSignalingEvents = function () {
var _a = this, log = _a._log, clientTrackSwitchOffControl = _a._clientTrackSwitchOffControl, contentPreferencesMode = _a._contentPreferencesMode, MediaStream = _a._MediaStream, mapMediaElement = _a._mapMediaElement, disposeMediaElement = _a._disposeMediaElement;
var self = this;
if (this.state === 'disconnected') {
return;
}
var RemoteAudioTrack = this._RemoteAudioTrack;
var RemoteAudioTrackPublication = this._RemoteAudioTrackPublication;
var RemoteVideoTrack = this._RemoteVideoTrack;
var RemoteVideoTrackPublication = this._RemoteVideoTrackPublication;
var RemoteDataTrack = this._RemoteDataTrack;
var RemoteDataTrackPublication = this._RemoteDataTrackPublication;
var participantSignaling = this._signaling;
function trackSignalingAdded(signaling) {
var RemoteTrackPublication = {
audio: RemoteAudioTrackPublication,
data: RemoteDataTrackPublication,
video: RemoteVideoTrackPublication
}[signaling.kind];
var publication = new RemoteTrackPublication(signaling, { log: log });
self._addTrackPublication(publication);
var isSubscribed = signaling.isSubscribed;
if (isSubscribed) {
trackSignalingSubscribed(signaling);
}
self._trackSignalingUpdatedEventCallbacks.set(signaling.sid, function () {
if (isSubscribed !== signaling.isSubscribed) {
isSubscribed = signaling.isSubscribed;
if (isSubscribed) {
trackSignalingSubscribed(signaling);
return;
}
trackSignalingUnsubscribed(signaling);
}
});
signaling.on('updated', self._trackSignalingUpdatedEventCallbacks.get(signaling.sid));
}
function trackSignalingRemoved(signaling) {
if (signaling.isSubscribed) {
signaling.setTrackTransceiver(null);
}
var updated = self._trackSignalingUpdatedEventCallbacks.get(signaling.sid);
if (updated) {
signaling.removeListener('updated', updated);
self._trackSignalingUpdatedEventCallbacks.delete(signaling.sid);
}
var publication = self.tracks.get(signaling.sid);
if (publication) {
self._removeTrackPublication(publication);
}
}
function trackSignalingSubscribed(signaling) {
var isEnabled = signaling.isEnabled, name = signaling.name, kind = signaling.kind, sid = signaling.sid, trackTransceiver = signaling.trackTransceiver, isSwitchedOff = signaling.isSwitchedOff;
var RemoteTrack = {
audio: RemoteAudioTrack,
video: RemoteVideoTrack,
data: RemoteDataTrack
}[kind];
var publication = self.tracks.get(sid);
// NOTE(mroberts): It should never be the case that the TrackSignaling and
// MediaStreamTrack or DataTrackReceiver kinds disagree; however, just in
// case, we handle it here.
if (!RemoteTrack || kind !== trackTransceiver.kind) {
return;
}
var options = { log: log, name: name, clientTrackSwitchOffControl: clientTrackSwitchOffControl, contentPreferencesMode: contentPreferencesMode, mapMediaElement: mapMediaElement, disposeMediaElement: disposeMediaElement };
if (MediaStream) {
options.MediaStream = MediaStream;
}
var setPriority = function (newPriority) { return participantSignaling.updateSubscriberTrackPriority(sid, newPriority); };
var setRenderHint = function (renderHint) {
if (signaling.isSubscribed) {
participantSignaling.updateTrackRenderHint(sid, renderHint);
}
};
var track = kind === 'data'
? new RemoteTrack(sid, trackTransceiver, options)
: new RemoteTrack(sid, trackTransceiver, isEnabled, isSwitchedOff, setPriority, setRenderHint, options);
self._addTrack(track, publication, trackTransceiver.id);
}
function trackSignalingUnsubscribed(signaling) {
var _a = __read(Array.from(self._tracks.entries()).find(function (_a) {
var _b = __read(_a, 2), track = _b[1];
return track.sid === signaling.sid;
}), 2), id = _a[0], track = _a[1];
var publication = self.tracks.get(signaling.sid);
if (track) {
self._removeTrack(track, publication, id);
}
}
participantSignaling.on('trackAdded', trackSignalingAdded);
participantSignaling.on('trackRemoved', trackSignalingRemoved);
participantSignaling.tracks.forEach(trackSignalingAdded);
participantSignaling.on('stateChanged', function stateChanged(state) {
if (state === 'disconnected') {
log.debug('Removing event listeners');
participantSignaling.removeListener('stateChanged', stateChanged);
participantSignaling.removeListener('trackAdded', trackSignalingAdded);
participantSignaling.removeListener('trackRemoved', trackSignalingRemoved);
}
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(function () { return self.emit('reconnected'); }, 0);
}
});
};
/**
* @private
* @param {RemoteTrack} track
* @param {Track.ID} id
* @returns {?RemoteTrack}
*/
Participant.prototype._removeTrack = function (track, id) {
if (!this._tracks.has(id)) {
return null;
}
this._tracks.delete(id);
var tracksByKind = {
audio: this._audioTracks,
video: this._videoTracks,
data: this._dataTracks
}[track.kind];
tracksByKind.delete(id);
var reemitters = this._trackEventReemitters.get(id) || new Map();
reemitters.forEach(function (reemitter, event) {
track.removeListener(event, reemitter);
});
var log = this._log;
log.info("Removed a ".concat(util.trackClass(track), ":"), id);
log.debug("".concat(util.trackClass(track), ":"), track);
return track;
};
/**
* @private
* @param {RemoteTrackPublication} publication
* @returns {?RemoteTrackPublication}
*/
Participant.prototype._removeTrackPublication = function (publication) {
publication = this.tracks.get(publication.trackSid);
if (!publication) {
return null;
}
this.tracks.delete(publication.trackSid);
var trackPublicationsByKind = {
audio: this.audioTracks,
data: this.dataTracks,
video: this.videoTracks
}[publication.kind];
trackPublicationsByKind.delete(publication.trackSid);
var reemitters = this._trackPublicationEventReemitters.get(publication.trackSid) || new Map();
reemitters.forEach(function (reemitter, event) {
publication.removeListener(event, reemitter);
});
var log = this._log;
log.info("Removed a ".concat(util.trackPublicationClass(publication), ":"), publication.trackSid);
log.debug("".concat(util.trackPublicationClass(publication), ":"), publication);
return publication;
};
Participant.prototype.toJSON = function () {
return util.valueToJSON(this);
};
return Participant;
}(EventEmitter));
/**
* A {@link Participant.SID} is a 34-character string starting with "PA"
* that uniquely identifies a {@link Participant}.
* @type string
* @typedef Participant.SID
*/
/**
* A {@link Participant.Identity} is a string that identifies a
* {@link Participant}. You can think of it like a name.
* @typedef {string} Participant.Identity
*/
/**
* The {@link Participant} has disconnected.
* @param {Participant} participant - The {@link Participant} that disconnected.
* @event Participant#disconnected
*/
/**
* The {@link Participant}'s {@link NetworkQualityLevel} changed.
* @param {NetworkQualityLevel} networkQualityLevel - The new
* {@link NetworkQualityLevel}
* @param {?NetworkQualityStats} networkQualityStats - The {@link NetworkQualityStats}
* based on which {@link NetworkQualityLevel} is calculated, if any
* @event Participant#networkQualityLevelChanged
*/
/**
* The {@link Participant} has reconnected to the {@link Room} after a signaling connection disruption.
* @event Participant#reconnected
*/
/**
* The {@link Participant} is reconnecting to the {@link Room} after a signaling connection disruption.
* @event Participant#reconnecting
*/
/**
* One of the {@link Participant}'s {@link VideoTrack}'s dimensions changed.
* @param {VideoTrack} track - The {@link VideoTrack} whose dimensions changed
* @event Participant#trackDimensionsChanged
*/
/**
* One of the {@link Participant}'s {@link Track}s started.
* @param {Track} track - The {@link Track} that started
* @event Participant#trackStarted
*/
/**
* Indexed {@link Track}s by {@link Track.ID}.
* @typedef {object} IndexedTracks
* @property {Array<{0: Track.ID, 1: AudioTrack}>} audioTracks - Indexed
* {@link AudioTrack}s
* @property {Array<{0: Track.ID, 1: DataTrack}>} dataTracks - Indexed
* {@link DataTrack}s
* @property {Array<{0: Track.ID, 1: Track}>} tracks - Indexed {@link Track}s
* @property {Array<{0: Track.ID, 1: VideoTrack}>} videoTracks - Indexed
* {@link VideoTrack}s
* @private
*/
/**
* Index tracks by {@link Track.ID}.
* @param {Array<Track>} tracks
* @returns {IndexedTracks}
* @private
*/
function indexTracksById(tracks) {
var indexedTracks = tracks.map(function (track) { return [track.id, track]; });
var indexedAudioTracks = indexedTracks.filter(function (keyValue) { return keyValue[1].kind === 'audio'; });
var indexedVideoTracks = indexedTracks.filter(function (keyValue) { return keyValue[1].kind === 'video'; });
var indexedDataTracks = indexedTracks.filter(function (keyValue) { return keyValue[1].kind === 'data'; });
return {
audioTracks: indexedAudioTracks,
dataTracks: indexedDataTracks,
tracks: indexedTracks,
videoTracks: indexedVideoTracks
};
}
/**
* Re-emit {@link ParticipantSignaling} 'stateChanged' events.
* @param {Participant} participant
* @param {ParticipantSignaling} signaling
* @private
*/
function reemitSignalingStateChangedEvents(participant, signaling) {
var log = participant._log;
if (participant.state === 'disconnected') {
return;
}
// Reemit state transition events from the ParticipantSignaling.
signaling.on('stateChanged', function stateChanged(state) {
log.debug('Transitioned to state:', state);
participant.emit(state, participant);
if (state === 'disconnected') {
log.debug('Removing Track event reemitters');
signaling.removeListener('stateChanged', stateChanged);
participant._tracks.forEach(function (track) {
var reemitters = participant._trackEventReemitters.get(track.id);
if (track && reemitters) {
reemitters.forEach(function (reemitter, event) {
track.removeListener(event, reemitter);
});
}
});
// eslint-disable-next-line no-warning-comments
// TODO(joma): Removing this introduced unit test failures in the RemoteParticipant.
// Investigate further before removing.
signaling.tracks.forEach(function (trackSignaling) {
var track = participant._tracks.get(trackSignaling.id);
var reemitters = participant._trackEventReemitters.get(trackSignaling.id);
if (track && reemitters) {
reemitters.forEach(function (reemitter, event) {
track.removeListener(event, reemitter);
});
}
});
participant._trackEventReemitters.clear();
participant.tracks.forEach(function (publication) {
participant._trackPublicationEventReemitters.get(publication.trackSid)
.forEach(function (reemitter, event) {
publication.removeListener(event, reemitter);
});
});
participant._trackPublicationEventReemitters.clear();
}
});
}
/**
* Re-emit {@link Track} events.
* @param {Participant} participant
* @param {Track} track
* @param {Track.ID} id
* @private
*/
function reemitTrackEvents(participant, track, id) {
var trackEventReemitters = new Map();
if (participant.state === 'disconnected') {
return;
}
participant._getTrackEvents().forEach(function (eventPair) {
var trackEvent = eventPair[0];
var participantEvent = eventPair[1];
trackEventReemitters.set(trackEvent, function () {
var args = [participantEvent].concat([].slice.call(arguments));
return participant.emit.apply(participant, __spreadArray([], __read(args), false));
});
track.on(trackEvent, trackEventReemitters.get(trackEvent));
});
participant._trackEventReemitters.set(id, trackEventReemitters);
}
/**
* Re-emit {@link TrackPublication} events.
* @private
* @param {Participant} participant
* @param {TrackPublication} publication
*/
function reemitTrackPublicationEvents(participant, publication) {
var publicationEventReemitters = new Map();
if (participant.state === 'disconnected') {
return;
}
participant._getTrackPublicationEvents().forEach(function (_a) {
var _b = __read(_a, 2), publicationEvent = _b[0], participantEvent = _b[1];
publicationEventReemitters.set(publicationEvent, function () {
var args = [];
for (var _i = 0; _i < arguments.length; _i++) {
args[_i] = arguments[_i];
}
participant.emit.apply(participant, __spreadArray(__spreadArray([participantEvent], __read(args), false), [publication], false));
});
publication.on(publicationEvent, publicationEventReemitters.get(publicationEvent));
});
participant._trackPublicationEventReemitters.set(publication.trackSid, publicationEventReemitters);
}
module.exports = Participant;
//# sourceMappingURL=participant.js.map