mediasoup-client
Version:
mediasoup client side TypeScript library
877 lines (876 loc) • 35 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Transport = void 0;
const awaitqueue_1 = require("awaitqueue");
const Logger_1 = require("./Logger");
const enhancedEvents_1 = require("./enhancedEvents");
const errors_1 = require("./errors");
const utils = require("./utils");
const ortc = require("./ortc");
const Producer_1 = require("./Producer");
const Consumer_1 = require("./Consumer");
const DataProducer_1 = require("./DataProducer");
const DataConsumer_1 = require("./DataConsumer");
const logger = new Logger_1.Logger('Transport');
class ConsumerCreationTask {
consumerOptions;
promise;
resolve;
reject;
constructor(consumerOptions) {
this.consumerOptions = consumerOptions;
this.promise = new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
});
}
}
class Transport extends enhancedEvents_1.EnhancedEventEmitter {
// Id.
_id;
// Closed flag.
_closed = false;
// Direction.
_direction;
// Callback for sending Transports to request sending extended RTP capabilities
// on demand.
_getSendExtendedRtpCapabilities;
// Recv RTP capabilities.
_recvRtpCapabilities;
// Whether we can produce audio/video based on computed extended RTP
// capabilities.
_canProduceByKind;
// SCTP max message size if enabled.
_maxSctpMessageSize;
// RTC handler isntance.
_handler;
// Transport ICE gathering state.
_iceGatheringState = 'new';
// Transport connection state.
_connectionState = 'new';
// App custom data.
_appData;
// Map of Producers indexed by id.
_producers = new Map();
// Map of Consumers indexed by id.
_consumers = new Map();
// Map of DataProducers indexed by id.
_dataProducers = new Map();
// Map of DataConsumers indexed by id.
_dataConsumers = new Map();
// Whether the Consumer for RTP probation has been created.
_probatorConsumerCreated = false;
// AwaitQueue instance to make async tasks happen sequentially.
_awaitQueue = new awaitqueue_1.AwaitQueue();
// Consumer creation tasks awaiting to be processed.
_pendingConsumerTasks = [];
// Consumer creation in progress flag.
_consumerCreationInProgress = false;
// Consumers pending to be paused.
_pendingPauseConsumers = new Map();
// Consumer pause in progress flag.
_consumerPauseInProgress = false;
// Consumers pending to be resumed.
_pendingResumeConsumers = new Map();
// Consumer resume in progress flag.
_consumerResumeInProgress = false;
// Consumers pending to be closed.
_pendingCloseConsumers = new Map();
// Consumer close in progress flag.
_consumerCloseInProgress = false;
// Observer instance.
_observer = new enhancedEvents_1.EnhancedEventEmitter();
constructor({ direction, id, iceParameters, iceCandidates, dtlsParameters, sctpParameters, iceServers, iceTransportPolicy, additionalSettings, appData, handlerFactory, getSendExtendedRtpCapabilities, recvRtpCapabilities, canProduceByKind, }) {
super();
logger.debug('constructor() [id:%s, direction:%s]', id, direction);
this._id = id;
this._direction = direction;
this._getSendExtendedRtpCapabilities = getSendExtendedRtpCapabilities;
this._recvRtpCapabilities = recvRtpCapabilities;
this._canProduceByKind = canProduceByKind;
this._maxSctpMessageSize = sctpParameters?.maxMessageSize;
// Clone and sanitize additionalSettings.
const clonedAdditionalSettings = utils.clone(additionalSettings) ?? {};
delete clonedAdditionalSettings.iceServers;
delete clonedAdditionalSettings.iceTransportPolicy;
delete clonedAdditionalSettings.bundlePolicy;
delete clonedAdditionalSettings.rtcpMuxPolicy;
this._handler = handlerFactory.factory({
direction,
iceParameters,
iceCandidates,
dtlsParameters,
sctpParameters,
iceServers,
iceTransportPolicy,
additionalSettings: clonedAdditionalSettings,
getSendExtendedRtpCapabilities: this._getSendExtendedRtpCapabilities,
});
this._appData = appData ?? {};
this.handleHandler();
}
/**
* Transport id.
*/
get id() {
return this._id;
}
/**
* Whether the Transport is closed.
*/
get closed() {
return this._closed;
}
/**
* Transport direction.
*/
get direction() {
return this._direction;
}
/**
* RTC handler instance.
*/
get handler() {
return this._handler;
}
/**
* ICE gathering state.
*/
get iceGatheringState() {
return this._iceGatheringState;
}
/**
* Connection state.
*/
get connectionState() {
return this._connectionState;
}
/**
* App custom data.
*/
get appData() {
return this._appData;
}
/**
* App custom data setter.
*/
set appData(appData) {
this._appData = appData;
}
get observer() {
return this._observer;
}
/**
* Close the Transport.
*/
close() {
if (this._closed) {
return;
}
logger.debug('close()');
this._closed = true;
// Stop the AwaitQueue.
this._awaitQueue.stop();
// Close the handler.
this._handler.close();
// Change connection state to 'closed' since the handler may not emit
// '@connectionstatechange' event.
this._connectionState = 'closed';
// Close all Producers.
for (const producer of this._producers.values()) {
producer.transportClosed();
}
this._producers.clear();
// Close all Consumers.
for (const consumer of this._consumers.values()) {
consumer.transportClosed();
}
this._consumers.clear();
// Close all DataProducers.
for (const dataProducer of this._dataProducers.values()) {
dataProducer.transportClosed();
}
this._dataProducers.clear();
// Close all DataConsumers.
for (const dataConsumer of this._dataConsumers.values()) {
dataConsumer.transportClosed();
}
this._dataConsumers.clear();
// Emit observer event.
this._observer.safeEmit('close');
// Invoke close() in EnhancedEventEmitter classes.
super.close();
this._observer.close();
}
/**
* Get associated Transport (RTCPeerConnection) stats.
*
* @returns {RTCStatsReport}
*/
async getStats() {
if (this._closed) {
throw new errors_1.InvalidStateError('closed');
}
return this._handler.getTransportStats();
}
/**
* Restart ICE connection.
*/
async restartIce({ iceParameters, }) {
logger.debug('restartIce()');
if (this._closed) {
throw new errors_1.InvalidStateError('closed');
}
else if (!iceParameters) {
throw new TypeError('missing iceParameters');
}
// Enqueue command.
return this._awaitQueue.push(async () => await this._handler.restartIce(iceParameters), 'transport.restartIce()');
}
/**
* Update ICE servers.
*/
async updateIceServers({ iceServers, } = {}) {
logger.debug('updateIceServers()');
if (this._closed) {
throw new errors_1.InvalidStateError('closed');
}
else if (!Array.isArray(iceServers)) {
throw new TypeError('missing iceServers');
}
// Enqueue command.
return this._awaitQueue.push(async () => this._handler.updateIceServers(iceServers), 'transport.updateIceServers()');
}
/**
* Create a Producer.
*/
async produce({ track, streamId, encodings, codecOptions, headerExtensionOptions, codec, stopTracks = true, disableTrackOnPause = true, zeroRtpOnPause = false, onRtpSender, appData = {}, } = {}) {
logger.debug('produce() [track:%o]', track);
if (this._closed) {
throw new errors_1.InvalidStateError('closed');
}
else if (!track) {
throw new TypeError('missing track');
}
else if (this._direction !== 'send') {
throw new errors_1.UnsupportedError('not a sending Transport');
}
else if (!this._canProduceByKind[track.kind]) {
throw new errors_1.UnsupportedError(`cannot produce ${track.kind}`);
}
else if (track.readyState === 'ended') {
throw new errors_1.InvalidStateError('track ended');
}
else if (this.listenerCount('connect') === 0 &&
this._connectionState === 'new') {
throw new TypeError('no "connect" listener set into this transport');
}
else if (this.listenerCount('produce') === 0) {
throw new TypeError('no "produce" listener set into this transport');
}
else if (appData && typeof appData !== 'object') {
throw new TypeError('if given, appData must be an object');
}
// Enqueue command.
return (this._awaitQueue
.push(async () => {
let normalizedEncodings;
if (encodings && !Array.isArray(encodings)) {
throw TypeError('encodings must be an array');
}
else if (encodings?.length === 0) {
normalizedEncodings = undefined;
}
else if (encodings) {
normalizedEncodings = encodings.map(encoding => {
const normalizedEncoding = {
active: true,
};
if (encoding.active === false) {
normalizedEncoding.active = false;
}
if (typeof encoding.dtx === 'boolean') {
normalizedEncoding.dtx = encoding.dtx;
}
if (typeof encoding.scalabilityMode === 'string') {
normalizedEncoding.scalabilityMode = encoding.scalabilityMode;
}
if (typeof encoding.scaleResolutionDownBy === 'number') {
normalizedEncoding.scaleResolutionDownBy =
encoding.scaleResolutionDownBy;
}
if (typeof encoding.maxBitrate === 'number') {
normalizedEncoding.maxBitrate = encoding.maxBitrate;
}
if (typeof encoding.maxFramerate === 'number') {
normalizedEncoding.maxFramerate = encoding.maxFramerate;
}
if (typeof encoding.adaptivePtime === 'boolean') {
normalizedEncoding.adaptivePtime = encoding.adaptivePtime;
}
if (typeof encoding.priority === 'string') {
normalizedEncoding.priority = encoding.priority;
}
if (typeof encoding.networkPriority === 'string') {
normalizedEncoding.networkPriority = encoding.networkPriority;
}
return normalizedEncoding;
});
}
const { localId, rtpParameters, rtpSender } = await this._handler.send({
track,
streamId,
encodings: normalizedEncodings,
codecOptions,
headerExtensionOptions,
codec,
onRtpSender,
});
try {
// This will fill rtpParameters's missing fields with default values.
ortc.validateAndNormalizeRtpParameters(rtpParameters);
const { id } = await new Promise((resolve, reject) => {
this.safeEmit('produce', {
kind: track.kind,
rtpParameters,
appData,
}, resolve, reject);
});
const producer = new Producer_1.Producer({
id,
localId,
rtpSender,
track,
rtpParameters,
stopTracks,
disableTrackOnPause,
zeroRtpOnPause,
appData,
});
this._producers.set(producer.id, producer);
this.handleProducer(producer);
// Emit observer event.
this._observer.safeEmit('newproducer', producer);
return producer;
}
catch (error) {
this._handler.stopSending(localId).catch(() => { });
throw error;
}
}, 'transport.produce()')
// This catch is needed to stop the given track if the command above
// failed due to closed Transport.
.catch((error) => {
if (stopTracks) {
try {
track.stop();
}
catch (error2) { }
}
throw error;
}));
}
/**
* Create a Consumer to consume a remote Producer.
*/
async consume({ id, producerId, kind, rtpParameters, streamId, onRtpReceiver, appData = {}, }) {
logger.debug('consume()');
if (this._closed) {
throw new errors_1.InvalidStateError('closed');
}
else if (this._direction !== 'recv') {
throw new errors_1.UnsupportedError('not a receiving Transport');
}
else if (typeof id !== 'string') {
throw new TypeError('missing id');
}
else if (typeof producerId !== 'string') {
throw new TypeError('missing producerId');
}
else if (kind !== 'audio' && kind !== 'video') {
throw new TypeError(`invalid kind '${kind}'`);
}
else if (this.listenerCount('connect') === 0 &&
this._connectionState === 'new') {
throw new TypeError('no "connect" listener set into this transport');
}
else if (appData && typeof appData !== 'object') {
throw new TypeError('if given, appData must be an object');
}
// Clone given RTP parameters to not modify input data.
const clonedRtpParameters = utils.clone(rtpParameters);
// Ensure the device can consume it.
const canConsume = ortc.canReceive(clonedRtpParameters, this._recvRtpCapabilities);
if (!canConsume) {
throw new errors_1.UnsupportedError('cannot consume this Producer');
}
const consumerCreationTask = new ConsumerCreationTask({
id,
producerId,
kind,
rtpParameters: clonedRtpParameters,
streamId,
onRtpReceiver,
appData,
});
// Store the Consumer creation task.
this._pendingConsumerTasks.push(consumerCreationTask);
// There is no Consumer creation in progress, create it now.
queueMicrotask(() => {
if (this._closed) {
return;
}
if (this._consumerCreationInProgress === false) {
this.createPendingConsumers();
}
});
return consumerCreationTask.promise;
}
/**
* Create a DataProducer
*/
async produceData({ ordered = true, maxPacketLifeTime, maxRetransmits, label = '', protocol = '', appData = {}, } = {}) {
logger.debug('produceData()');
if (this._closed) {
throw new errors_1.InvalidStateError('closed');
}
else if (this._direction !== 'send') {
throw new errors_1.UnsupportedError('not a sending Transport');
}
else if (!this._maxSctpMessageSize) {
throw new errors_1.UnsupportedError('SCTP not enabled by remote Transport');
}
else if (this.listenerCount('connect') === 0 &&
this._connectionState === 'new') {
throw new TypeError('no "connect" listener set into this transport');
}
else if (this.listenerCount('producedata') === 0) {
throw new TypeError('no "producedata" listener set into this transport');
}
else if (appData && typeof appData !== 'object') {
throw new TypeError('if given, appData must be an object');
}
if (maxPacketLifeTime || maxRetransmits) {
ordered = false;
}
// Enqueue command.
return this._awaitQueue.push(async () => {
const { dataChannel, sctpStreamParameters } = await this._handler.sendDataChannel({
sctpStreamParameters: {
ordered,
maxPacketLifeTime,
maxRetransmits,
label,
protocol,
},
});
// This will fill sctpStreamParameters's missing fields with default values.
ortc.validateAndNormalizeSctpStreamParameters(sctpStreamParameters);
const { id } = await new Promise((resolve, reject) => {
this.safeEmit('producedata', {
sctpStreamParameters,
label,
protocol,
appData,
}, resolve, reject);
});
const dataProducer = new DataProducer_1.DataProducer({
id,
dataChannel,
sctpStreamParameters,
appData,
});
this._dataProducers.set(dataProducer.id, dataProducer);
this.handleDataProducer(dataProducer);
// Emit observer event.
this._observer.safeEmit('newdataproducer', dataProducer);
return dataProducer;
}, 'transport.produceData()');
}
/**
* Create a DataConsumer
*/
async consumeData({ id, dataProducerId, sctpStreamParameters, label = '', protocol = '', appData = {}, }) {
logger.debug('consumeData()');
if (this._closed) {
throw new errors_1.InvalidStateError('closed');
}
else if (this._direction !== 'recv') {
throw new errors_1.UnsupportedError('not a receiving Transport');
}
else if (!this._maxSctpMessageSize) {
throw new errors_1.UnsupportedError('SCTP not enabled by remote Transport');
}
else if (typeof id !== 'string') {
throw new TypeError('missing id');
}
else if (typeof dataProducerId !== 'string') {
throw new TypeError('missing dataProducerId');
}
else if (this.listenerCount('connect') === 0 &&
this._connectionState === 'new') {
throw new TypeError('no "connect" listener set into this transport');
}
else if (appData && typeof appData !== 'object') {
throw new TypeError('if given, appData must be an object');
}
// Clone given SCTP stream parameters to not modify input data.
const clonedSctpStreamParameters = utils.clone(sctpStreamParameters);
// This may throw.
ortc.validateAndNormalizeSctpStreamParameters(clonedSctpStreamParameters);
// Enqueue command.
return this._awaitQueue.push(async () => {
const { dataChannel } = await this._handler.receiveDataChannel({
maxMessageSize: this._maxSctpMessageSize,
sctpStreamParameters: clonedSctpStreamParameters,
label,
protocol,
});
const dataConsumer = new DataConsumer_1.DataConsumer({
id,
dataProducerId,
dataChannel,
sctpStreamParameters: clonedSctpStreamParameters,
appData,
});
this._dataConsumers.set(dataConsumer.id, dataConsumer);
this.handleDataConsumer(dataConsumer);
// Emit observer event.
this._observer.safeEmit('newdataconsumer', dataConsumer);
return dataConsumer;
}, 'transport.consumeData()');
}
getDataChannelMaxMessageSize() {
return this._handler.getDataChannelMaxMessageSize();
}
// This method is guaranteed to never throw.
createPendingConsumers() {
this._consumerCreationInProgress = true;
this._awaitQueue
.push(async () => {
if (this._pendingConsumerTasks.length === 0) {
logger.debug('createPendingConsumers() | there is no Consumer to be created');
return;
}
const pendingConsumerTasks = [...this._pendingConsumerTasks];
// Clear pending Consumer tasks.
this._pendingConsumerTasks = [];
// Video Consumer in order to create the probator.
let videoConsumerForProbator = undefined;
// Fill options list.
const optionsList = [];
for (const task of pendingConsumerTasks) {
const { id, kind, rtpParameters, streamId, onRtpReceiver } = task.consumerOptions;
optionsList.push({
trackId: id,
kind: kind,
rtpParameters,
streamId,
onRtpReceiver,
});
}
try {
const results = await this._handler.receive(optionsList);
for (let idx = 0; idx < results.length; ++idx) {
const task = pendingConsumerTasks[idx];
const result = results[idx];
const { id, producerId, kind, rtpParameters, appData } = task.consumerOptions;
const { localId, rtpReceiver, track } = result;
const consumer = new Consumer_1.Consumer({
id,
localId,
producerId,
rtpReceiver,
track,
rtpParameters,
appData: appData,
});
this._consumers.set(consumer.id, consumer);
this.handleConsumer(consumer);
// If this is the first video Consumer and the Consumer for RTP probation
// has not yet been created, it's time to create it.
if (!this._probatorConsumerCreated &&
!videoConsumerForProbator &&
kind === 'video') {
videoConsumerForProbator = consumer;
}
// Emit observer event.
this._observer.safeEmit('newconsumer', consumer);
task.resolve(consumer);
}
}
catch (error) {
for (const task of pendingConsumerTasks) {
task.reject(error);
}
}
// If RTP probation must be handled, do it now.
if (videoConsumerForProbator) {
try {
const probatorRtpParameters = ortc.generateProbatorRtpParameters(videoConsumerForProbator.rtpParameters);
await this._handler.receive([
{
trackId: 'probator',
kind: 'video',
rtpParameters: probatorRtpParameters,
},
]);
logger.debug('createPendingConsumers() | Consumer for RTP probation created');
this._probatorConsumerCreated = true;
}
catch (error) {
logger.error('createPendingConsumers() | failed to create Consumer for RTP probation:%o', error);
}
}
}, 'transport.createPendingConsumers()')
.then(() => {
this._consumerCreationInProgress = false;
// There are pending Consumer tasks, enqueue their creation.
if (this._pendingConsumerTasks.length > 0) {
this.createPendingConsumers();
}
})
// NOTE: We only get here when the await queue is closed.
.catch(() => { });
}
pausePendingConsumers() {
this._consumerPauseInProgress = true;
this._awaitQueue
.push(async () => {
if (this._pendingPauseConsumers.size === 0) {
logger.debug('pausePendingConsumers() | there is no Consumer to be paused');
return;
}
const pendingPauseConsumers = Array.from(this._pendingPauseConsumers.values());
// Clear pending pause Consumer map.
this._pendingPauseConsumers.clear();
try {
const localIds = pendingPauseConsumers.map(consumer => consumer.localId);
await this._handler.pauseReceiving(localIds);
}
catch (error) {
logger.error('pausePendingConsumers() | failed to pause Consumers:', error);
}
}, 'transport.pausePendingConsumers()')
.then(() => {
this._consumerPauseInProgress = false;
// There are pending Consumers to be paused, do it.
if (this._pendingPauseConsumers.size > 0) {
this.pausePendingConsumers();
}
})
// NOTE: We only get here when the await queue is closed.
.catch(() => { });
}
resumePendingConsumers() {
this._consumerResumeInProgress = true;
this._awaitQueue
.push(async () => {
if (this._pendingResumeConsumers.size === 0) {
logger.debug('resumePendingConsumers() | there is no Consumer to be resumed');
return;
}
const pendingResumeConsumers = Array.from(this._pendingResumeConsumers.values());
// Clear pending resume Consumer map.
this._pendingResumeConsumers.clear();
try {
const localIds = pendingResumeConsumers.map(consumer => consumer.localId);
await this._handler.resumeReceiving(localIds);
}
catch (error) {
logger.error('resumePendingConsumers() | failed to resume Consumers:', error);
}
}, 'transport.resumePendingConsumers()')
.then(() => {
this._consumerResumeInProgress = false;
// There are pending Consumer to be resumed, do it.
if (this._pendingResumeConsumers.size > 0) {
this.resumePendingConsumers();
}
})
// NOTE: We only get here when the await queue is closed.
.catch(() => { });
}
closePendingConsumers() {
this._consumerCloseInProgress = true;
this._awaitQueue
.push(async () => {
if (this._pendingCloseConsumers.size === 0) {
logger.debug('closePendingConsumers() | there is no Consumer to be closed');
return;
}
const pendingCloseConsumers = Array.from(this._pendingCloseConsumers.values());
// Clear pending close Consumer map.
this._pendingCloseConsumers.clear();
try {
await this._handler.stopReceiving(pendingCloseConsumers.map(consumer => consumer.localId));
}
catch (error) {
logger.error('closePendingConsumers() | failed to close Consumers:', error);
}
}, 'transport.closePendingConsumers()')
.then(() => {
this._consumerCloseInProgress = false;
// There are pending Consumer to be resumed, do it.
if (this._pendingCloseConsumers.size > 0) {
this.closePendingConsumers();
}
})
// NOTE: We only get here when the await queue is closed.
.catch(() => { });
}
handleHandler() {
const handler = this._handler;
handler.on('@connect', ({ dtlsParameters }, callback, errback) => {
if (this._closed) {
errback(new errors_1.InvalidStateError('closed'));
return;
}
this.safeEmit('connect', { dtlsParameters }, callback, errback);
});
handler.on('@icegatheringstatechange', (iceGatheringState) => {
if (iceGatheringState === this._iceGatheringState) {
return;
}
logger.debug('ICE gathering state changed to %s', iceGatheringState);
this._iceGatheringState = iceGatheringState;
if (!this._closed) {
this.safeEmit('icegatheringstatechange', iceGatheringState);
}
});
handler.on('@icecandidateerror', (event) => {
logger.warn(`ICE candidate error [url:${event.url}, localAddress:${event.address}, localPort:${event.port}]: ${event.errorCode} "${event.errorText}"`);
this.safeEmit('icecandidateerror', event);
});
handler.on('@connectionstatechange', (connectionState) => {
if (connectionState === this._connectionState) {
return;
}
logger.debug('connection state changed to %s', connectionState);
this._connectionState = connectionState;
if (!this._closed) {
this.safeEmit('connectionstatechange', connectionState);
}
});
}
handleProducer(producer) {
producer.on('@close', () => {
this._producers.delete(producer.id);
if (this._closed) {
return;
}
this._awaitQueue
.push(async () => await this._handler.stopSending(producer.localId), 'producer @close event')
.catch((error) => logger.warn('producer.close() failed:%o', error));
});
producer.on('@pause', (callback, errback) => {
this._awaitQueue
.push(async () => await this._handler.pauseSending(producer.localId), 'producer @pause event')
.then(callback)
.catch(errback);
});
producer.on('@resume', (callback, errback) => {
this._awaitQueue
.push(async () => await this._handler.resumeSending(producer.localId), 'producer @resume event')
.then(callback)
.catch(errback);
});
producer.on('@replacetrack', (track, callback, errback) => {
this._awaitQueue
.push(async () => await this._handler.replaceTrack(producer.localId, track), 'producer @replacetrack event')
.then(callback)
.catch(errback);
});
producer.on('@setmaxspatiallayer', (spatialLayer, callback, errback) => {
this._awaitQueue
.push(async () => await this._handler.setMaxSpatialLayer(producer.localId, spatialLayer), 'producer @setmaxspatiallayer event')
.then(callback)
.catch(errback);
});
producer.on('@setrtpencodingparameters', (params, callback, errback) => {
this._awaitQueue
.push(async () => await this._handler.setRtpEncodingParameters(producer.localId, params), 'producer @setrtpencodingparameters event')
.then(callback)
.catch(errback);
});
producer.on('@getstats', (callback, errback) => {
if (this._closed) {
return errback(new errors_1.InvalidStateError('closed'));
}
this._handler
.getSenderStats(producer.localId)
.then(callback)
.catch(errback);
});
}
handleConsumer(consumer) {
consumer.on('@close', () => {
this._consumers.delete(consumer.id);
this._pendingPauseConsumers.delete(consumer.id);
this._pendingResumeConsumers.delete(consumer.id);
if (this._closed) {
return;
}
// Store the Consumer into the close list.
this._pendingCloseConsumers.set(consumer.id, consumer);
// There is no Consumer close in progress, do it now.
if (this._consumerCloseInProgress === false) {
this.closePendingConsumers();
}
});
consumer.on('@pause', () => {
// If Consumer is pending to be resumed, remove from pending resume list.
if (this._pendingResumeConsumers.has(consumer.id)) {
this._pendingResumeConsumers.delete(consumer.id);
}
// Store the Consumer into the pending list.
this._pendingPauseConsumers.set(consumer.id, consumer);
// There is no Consumer pause in progress, do it now.
queueMicrotask(() => {
if (this._closed) {
return;
}
if (this._consumerPauseInProgress === false) {
this.pausePendingConsumers();
}
});
});
consumer.on('@resume', () => {
// If Consumer is pending to be paused, remove from pending pause list.
if (this._pendingPauseConsumers.has(consumer.id)) {
this._pendingPauseConsumers.delete(consumer.id);
}
// Store the Consumer into the pending list.
this._pendingResumeConsumers.set(consumer.id, consumer);
// There is no Consumer resume in progress, do it now.
queueMicrotask(() => {
if (this._closed) {
return;
}
if (this._consumerResumeInProgress === false) {
this.resumePendingConsumers();
}
});
});
consumer.on('@getstats', (callback, errback) => {
if (this._closed) {
return errback(new errors_1.InvalidStateError('closed'));
}
this._handler
.getReceiverStats(consumer.localId)
.then(callback)
.catch(errback);
});
}
handleDataProducer(dataProducer) {
dataProducer.on('@close', () => {
this._dataProducers.delete(dataProducer.id);
});
}
handleDataConsumer(dataConsumer) {
dataConsumer.on('@close', () => {
this._dataConsumers.delete(dataConsumer.id);
});
}
}
exports.Transport = Transport;