mediasoup
Version:
Cutting Edge WebRTC Video Conferencing
1,047 lines (1,046 loc) • 43.1 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Transport = void 0;
exports.portRangeToFbs = portRangeToFbs;
exports.socketFlagsToFbs = socketFlagsToFbs;
exports.parseSctpState = parseSctpState;
exports.parseProtocol = parseProtocol;
exports.serializeProtocol = serializeProtocol;
exports.parseTuple = parseTuple;
exports.parseBaseTransportDump = parseBaseTransportDump;
exports.parseBaseTransportStats = parseBaseTransportStats;
exports.parseTransportTraceEventData = parseTransportTraceEventData;
const Logger_1 = require("./Logger");
const enhancedEvents_1 = require("./enhancedEvents");
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 RtpParameters_1 = require("./RtpParameters");
const SctpParameters_1 = require("./SctpParameters");
const utils = require("./utils");
const common_1 = require("./fbs/common");
const FbsRequest = require("./fbs/request");
const media_kind_1 = require("./fbs/rtp-parameters/media-kind");
const FbsConsumer = require("./fbs/consumer");
const FbsDataConsumer = require("./fbs/data-consumer");
const FbsDataProducer = require("./fbs/data-producer");
const FbsTransport = require("./fbs/transport");
const FbsRouter = require("./fbs/router");
const FbsRtpParameters = require("./fbs/rtp-parameters");
const sctp_state_1 = require("./fbs/sctp-association/sctp-state");
const logger = new Logger_1.Logger('Transport');
class Transport extends enhancedEvents_1.EnhancedEventEmitter {
// Internal data.
internal;
// Transport data. This is set by the subclass.
#data;
// Channel instance.
channel;
// Close flag.
#closed = false;
// Custom app data.
#appData;
// Method to retrieve Router RTP capabilities.
#getRouterRtpCapabilities;
// Method to retrieve a Producer.
getProducerById;
// Method to retrieve a DataProducer.
getDataProducerById;
// Producers map.
#producers = new Map();
// Consumers map.
consumers = new Map();
// DataProducers map.
dataProducers = new Map();
// DataConsumers map.
dataConsumers = new Map();
// RTCP CNAME for Producers.
#cnameForProducers;
// Next MID for Consumers. It's converted into string when used.
#nextMidForConsumers = 0;
// Buffer with available SCTP stream ids.
#sctpStreamIds;
// Next SCTP stream id.
#nextSctpStreamId = 0;
// Observer instance.
#observer;
/**
* @private
* @interface
*/
constructor({ internal, data, channel, appData, getRouterRtpCapabilities, getProducerById, getDataProducerById, }, observer) {
super();
logger.debug('constructor()');
this.internal = internal;
this.#data = data;
this.channel = channel;
this.#appData = appData || {};
this.#getRouterRtpCapabilities = getRouterRtpCapabilities;
this.getProducerById = getProducerById;
this.getDataProducerById = getDataProducerById;
this.#observer = observer;
}
/**
* Transport id.
*/
get id() {
return this.internal.transportId;
}
/**
* Whether the Transport is closed.
*/
get closed() {
return this.#closed;
}
/**
* App custom data.
*/
get appData() {
return this.#appData;
}
/**
* App custom data setter.
*/
set appData(appData) {
this.#appData = appData;
}
/**
* Observer.
*/
get observer() {
return this.#observer;
}
/**
* @private
* Just for testing purposes.
*/
get channelForTesting() {
return this.channel;
}
/**
* Close the Transport.
*/
close() {
if (this.#closed) {
return;
}
logger.debug('close()');
this.#closed = true;
// Remove notification subscriptions.
this.channel.removeAllListeners(this.internal.transportId);
/* Build Request. */
const requestOffset = new FbsRouter.CloseTransportRequestT(this.internal.transportId).pack(this.channel.bufferBuilder);
this.channel
.request(FbsRequest.Method.ROUTER_CLOSE_TRANSPORT, FbsRequest.Body.Router_CloseTransportRequest, requestOffset, this.internal.routerId)
.catch(() => { });
// Close every Producer.
for (const producer of this.#producers.values()) {
producer.transportClosed();
// Must tell the Router.
this.emit('@producerclose', producer);
}
this.#producers.clear();
// Close every Consumer.
for (const consumer of this.consumers.values()) {
consumer.transportClosed();
}
this.consumers.clear();
// Close every DataProducer.
for (const dataProducer of this.dataProducers.values()) {
dataProducer.transportClosed();
// Must tell the Router.
this.emit('@dataproducerclose', dataProducer);
}
this.dataProducers.clear();
// Close every DataConsumer.
for (const dataConsumer of this.dataConsumers.values()) {
dataConsumer.transportClosed();
}
this.dataConsumers.clear();
this.emit('@close');
// Emit observer event.
this.#observer.safeEmit('close');
}
/**
* Router was closed.
*
* @private
* @virtual
*/
routerClosed() {
if (this.#closed) {
return;
}
logger.debug('routerClosed()');
this.#closed = true;
// Remove notification subscriptions.
this.channel.removeAllListeners(this.internal.transportId);
// Close every Producer.
for (const producer of this.#producers.values()) {
producer.transportClosed();
// NOTE: No need to tell the Router since it already knows (it has
// been closed in fact).
}
this.#producers.clear();
// Close every Consumer.
for (const consumer of this.consumers.values()) {
consumer.transportClosed();
}
this.consumers.clear();
// Close every DataProducer.
for (const dataProducer of this.dataProducers.values()) {
dataProducer.transportClosed();
// NOTE: No need to tell the Router since it already knows (it has
// been closed in fact).
}
this.dataProducers.clear();
// Close every DataConsumer.
for (const dataConsumer of this.dataConsumers.values()) {
dataConsumer.transportClosed();
}
this.dataConsumers.clear();
this.safeEmit('routerclose');
// Emit observer event.
this.#observer.safeEmit('close');
}
/**
* Listen server was closed (this just happens in WebRtcTransports when their
* associated WebRtcServer is closed).
*
* @private
*/
listenServerClosed() {
if (this.#closed) {
return;
}
logger.debug('listenServerClosed()');
this.#closed = true;
// Remove notification subscriptions.
this.channel.removeAllListeners(this.internal.transportId);
// Close every Producer.
for (const producer of this.#producers.values()) {
producer.transportClosed();
// NOTE: No need to tell the Router since it already knows (it has
// been closed in fact).
}
this.#producers.clear();
// Close every Consumer.
for (const consumer of this.consumers.values()) {
consumer.transportClosed();
}
this.consumers.clear();
// Close every DataProducer.
for (const dataProducer of this.dataProducers.values()) {
dataProducer.transportClosed();
// NOTE: No need to tell the Router since it already knows (it has
// been closed in fact).
}
this.dataProducers.clear();
// Close every DataConsumer.
for (const dataConsumer of this.dataConsumers.values()) {
dataConsumer.transportClosed();
}
this.dataConsumers.clear();
// Need to emit this event to let the parent Router know since
// transport.listenServerClosed() is called by the listen server.
// NOTE: Currently there is just WebRtcServer for WebRtcTransports.
this.emit('@listenserverclose');
this.safeEmit('listenserverclose');
// Emit observer event.
this.#observer.safeEmit('close');
}
/**
* Dump Transport.
*
* @abstract
*/
async dump() {
// Should not happen.
throw new Error('method implemented in the subclass');
}
/**
* Get Transport stats.
*
* @abstract
*/
async getStats() {
// Should not happen.
throw new Error('method implemented in the subclass');
}
/**
* Provide the Transport remote parameters.
*
* @abstract
*/
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async connect(params) {
// Should not happen.
throw new Error('method implemented in the subclass');
}
/**
* Set maximum incoming bitrate for receiving media.
*/
async setMaxIncomingBitrate(bitrate) {
logger.debug('setMaxIncomingBitrate() [bitrate:%s]', bitrate);
/* Build Request. */
const requestOffset = FbsTransport.SetMaxIncomingBitrateRequest.createSetMaxIncomingBitrateRequest(this.channel.bufferBuilder, bitrate);
await this.channel.request(FbsRequest.Method.TRANSPORT_SET_MAX_INCOMING_BITRATE, FbsRequest.Body.Transport_SetMaxIncomingBitrateRequest, requestOffset, this.internal.transportId);
}
/**
* Set maximum outgoing bitrate for sending media.
*/
async setMaxOutgoingBitrate(bitrate) {
logger.debug('setMaxOutgoingBitrate() [bitrate:%s]', bitrate);
/* Build Request. */
const requestOffset = new FbsTransport.SetMaxOutgoingBitrateRequestT(bitrate).pack(this.channel.bufferBuilder);
await this.channel.request(FbsRequest.Method.TRANSPORT_SET_MAX_OUTGOING_BITRATE, FbsRequest.Body.Transport_SetMaxOutgoingBitrateRequest, requestOffset, this.internal.transportId);
}
/**
* Set minimum outgoing bitrate for sending media.
*/
async setMinOutgoingBitrate(bitrate) {
logger.debug('setMinOutgoingBitrate() [bitrate:%s]', bitrate);
/* Build Request. */
const requestOffset = new FbsTransport.SetMinOutgoingBitrateRequestT(bitrate).pack(this.channel.bufferBuilder);
await this.channel.request(FbsRequest.Method.TRANSPORT_SET_MIN_OUTGOING_BITRATE, FbsRequest.Body.Transport_SetMinOutgoingBitrateRequest, requestOffset, this.internal.transportId);
}
/**
* Create a Producer.
*/
async produce({ id = undefined, kind, rtpParameters, paused = false, keyFrameRequestDelay, appData, }) {
logger.debug('produce()');
if (id && this.#producers.has(id)) {
throw new TypeError(`a Producer with same id "${id}" already exists`);
}
else if (!['audio', 'video'].includes(kind)) {
throw new TypeError(`invalid kind "${kind}"`);
}
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);
// This may throw.
ortc.validateRtpParameters(clonedRtpParameters);
// If missing or empty encodings, add one.
if (!clonedRtpParameters.encodings ||
!Array.isArray(clonedRtpParameters.encodings) ||
clonedRtpParameters.encodings.length === 0) {
clonedRtpParameters.encodings = [{}];
}
// Don't do this in PipeTransports since there we must keep CNAME value in
// each Producer.
if (this.constructor.name !== 'PipeTransport') {
// If CNAME is given and we don't have yet a CNAME for Producers in this
// Transport, take it.
if (!this.#cnameForProducers &&
clonedRtpParameters.rtcp &&
clonedRtpParameters.rtcp.cname) {
this.#cnameForProducers = clonedRtpParameters.rtcp.cname;
}
// Otherwise if we don't have yet a CNAME for Producers and the RTP
// parameters do not include CNAME, create a random one.
else if (!this.#cnameForProducers) {
this.#cnameForProducers = utils.generateUUIDv4().substr(0, 8);
}
// Override Producer's CNAME.
clonedRtpParameters.rtcp = clonedRtpParameters.rtcp ?? {};
clonedRtpParameters.rtcp.cname = this.#cnameForProducers;
}
const routerRtpCapabilities = this.#getRouterRtpCapabilities();
// This may throw.
const rtpMapping = ortc.getProducerRtpParametersMapping(clonedRtpParameters, routerRtpCapabilities);
// This may throw.
const consumableRtpParameters = ortc.getConsumableRtpParameters(kind, clonedRtpParameters, routerRtpCapabilities, rtpMapping);
const producerId = id || utils.generateUUIDv4();
const requestOffset = createProduceRequest({
builder: this.channel.bufferBuilder,
producerId,
kind,
rtpParameters: clonedRtpParameters,
rtpMapping,
keyFrameRequestDelay,
paused,
});
const response = await this.channel.request(FbsRequest.Method.TRANSPORT_PRODUCE, FbsRequest.Body.Transport_ProduceRequest, requestOffset, this.internal.transportId);
/* Decode Response. */
const produceResponse = new FbsTransport.ProduceResponse();
response.body(produceResponse);
const status = produceResponse.unpack();
const data = {
kind,
rtpParameters: clonedRtpParameters,
type: (0, Producer_1.producerTypeFromFbs)(status.type),
consumableRtpParameters,
};
const producer = new Producer_1.Producer({
internal: {
...this.internal,
producerId,
},
data,
channel: this.channel,
appData,
paused,
});
this.#producers.set(producer.id, producer);
producer.on('@close', () => {
this.#producers.delete(producer.id);
this.emit('@producerclose', producer);
});
this.emit('@newproducer', producer);
// Emit observer event.
this.#observer.safeEmit('newproducer', producer);
return producer;
}
/**
* Create a Consumer.
*
* @virtual
*/
async consume({ producerId, rtpCapabilities, paused = false, mid, preferredLayers, ignoreDtx = false, enableRtx, pipe = false, appData, }) {
logger.debug('consume()');
if (!producerId || typeof producerId !== 'string') {
throw new TypeError('missing producerId');
}
else if (appData && typeof appData !== 'object') {
throw new TypeError('if given, appData must be an object');
}
else if (mid && (typeof mid !== 'string' || mid.length === 0)) {
throw new TypeError('if given, mid must be non empty string');
}
// Clone given RTP capabilities to not modify input data.
const clonedRtpCapabilities = utils.clone(rtpCapabilities);
// This may throw.
ortc.validateRtpCapabilities(clonedRtpCapabilities);
const producer = this.getProducerById(producerId);
if (!producer) {
throw Error(`Producer with id "${producerId}" not found`);
}
// If enableRtx is not given, set it to true if video and false if audio.
if (enableRtx === undefined) {
enableRtx = producer.kind === 'video';
}
// This may throw.
const rtpParameters = ortc.getConsumerRtpParameters({
consumableRtpParameters: producer.consumableRtpParameters,
remoteRtpCapabilities: clonedRtpCapabilities,
pipe,
enableRtx,
});
// Set MID.
if (!pipe) {
if (mid) {
rtpParameters.mid = mid;
}
else {
rtpParameters.mid = `${this.#nextMidForConsumers++}`;
// We use up to 8 bytes for MID (string).
if (this.#nextMidForConsumers === 100000000) {
logger.error(`consume() | reaching max MID value "${this.#nextMidForConsumers}"`);
this.#nextMidForConsumers = 0;
}
}
}
const consumerId = utils.generateUUIDv4();
const requestOffset = createConsumeRequest({
builder: this.channel.bufferBuilder,
producer,
consumerId,
rtpParameters,
paused,
preferredLayers,
ignoreDtx,
pipe,
});
const response = await this.channel.request(FbsRequest.Method.TRANSPORT_CONSUME, FbsRequest.Body.Transport_ConsumeRequest, requestOffset, this.internal.transportId);
/* Decode Response. */
const consumeResponse = new FbsTransport.ConsumeResponse();
response.body(consumeResponse);
const status = consumeResponse.unpack();
const data = {
producerId,
kind: producer.kind,
rtpParameters,
type: pipe ? 'pipe' : producer.type,
};
const consumer = new Consumer_1.Consumer({
internal: {
...this.internal,
consumerId,
},
data,
channel: this.channel,
appData,
paused: status.paused,
producerPaused: status.producerPaused,
score: status.score ?? undefined,
preferredLayers: status.preferredLayers
? {
spatialLayer: status.preferredLayers.spatialLayer,
temporalLayer: status.preferredLayers.temporalLayer !== null
? status.preferredLayers.temporalLayer
: undefined,
}
: undefined,
});
this.consumers.set(consumer.id, consumer);
consumer.on('@close', () => this.consumers.delete(consumer.id));
consumer.on('@producerclose', () => this.consumers.delete(consumer.id));
// Emit observer event.
this.#observer.safeEmit('newconsumer', consumer);
return consumer;
}
/**
* Create a DataProducer.
*/
async produceData({ id = undefined, sctpStreamParameters, label = '', protocol = '', paused = false, appData, } = {}) {
logger.debug('produceData()');
if (id && this.dataProducers.has(id)) {
throw new TypeError(`a DataProducer with same id "${id}" already exists`);
}
else if (appData && typeof appData !== 'object') {
throw new TypeError('if given, appData must be an object');
}
let type;
// Clone given SCTP stream parameters to not modify input data.
let clonedSctpStreamParameters = utils.clone(sctpStreamParameters);
// If this is not a DirectTransport, sctpStreamParameters are required.
if (this.constructor.name !== 'DirectTransport') {
type = 'sctp';
// This may throw.
ortc.validateSctpStreamParameters(clonedSctpStreamParameters);
}
// If this is a DirectTransport, sctpStreamParameters must not be given.
else {
type = 'direct';
if (sctpStreamParameters) {
logger.warn('produceData() | sctpStreamParameters are ignored when producing data on a DirectTransport');
clonedSctpStreamParameters = undefined;
}
}
const dataProducerId = id || utils.generateUUIDv4();
const requestOffset = createProduceDataRequest({
builder: this.channel.bufferBuilder,
dataProducerId,
type,
sctpStreamParameters: clonedSctpStreamParameters,
label,
protocol,
paused,
});
const response = await this.channel.request(FbsRequest.Method.TRANSPORT_PRODUCE_DATA, FbsRequest.Body.Transport_ProduceDataRequest, requestOffset, this.internal.transportId);
/* Decode Response. */
const produceDataResponse = new FbsDataProducer.DumpResponse();
response.body(produceDataResponse);
const dump = (0, DataProducer_1.parseDataProducerDumpResponse)(produceDataResponse);
const dataProducer = new DataProducer_1.DataProducer({
internal: {
...this.internal,
dataProducerId,
},
data: {
type: dump.type,
sctpStreamParameters: dump.sctpStreamParameters,
label: dump.label,
protocol: dump.protocol,
},
channel: this.channel,
paused,
appData,
});
this.dataProducers.set(dataProducer.id, dataProducer);
dataProducer.on('@close', () => {
this.dataProducers.delete(dataProducer.id);
this.emit('@dataproducerclose', dataProducer);
});
this.emit('@newdataproducer', dataProducer);
// Emit observer event.
this.#observer.safeEmit('newdataproducer', dataProducer);
return dataProducer;
}
/**
* Create a DataConsumer.
*/
async consumeData({ dataProducerId, ordered, maxPacketLifeTime, maxRetransmits, paused = false, subchannels, appData, }) {
logger.debug('consumeData()');
if (!dataProducerId || typeof dataProducerId !== 'string') {
throw new TypeError('missing dataProducerId');
}
else if (appData && typeof appData !== 'object') {
throw new TypeError('if given, appData must be an object');
}
const dataProducer = this.getDataProducerById(dataProducerId);
if (!dataProducer) {
throw Error(`DataProducer with id "${dataProducerId}" not found`);
}
let type;
let sctpStreamParameters;
let sctpStreamId;
// If this is not a DirectTransport, use sctpStreamParameters from the
// DataProducer (if type 'sctp') unless they are given in method parameters.
if (this.constructor.name !== 'DirectTransport') {
type = 'sctp';
sctpStreamParameters =
utils.clone(dataProducer.sctpStreamParameters) ?? {};
// Override if given.
if (ordered !== undefined) {
sctpStreamParameters.ordered = ordered;
}
if (maxPacketLifeTime !== undefined) {
sctpStreamParameters.maxPacketLifeTime = maxPacketLifeTime;
}
if (maxRetransmits !== undefined) {
sctpStreamParameters.maxRetransmits = maxRetransmits;
}
// This may throw.
sctpStreamId = this.getNextSctpStreamId();
this.#sctpStreamIds[sctpStreamId] = 1;
sctpStreamParameters.streamId = sctpStreamId;
}
// If this is a DirectTransport, sctpStreamParameters must not be used.
else {
type = 'direct';
if (ordered !== undefined ||
maxPacketLifeTime !== undefined ||
maxRetransmits !== undefined) {
logger.warn('consumeData() | ordered, maxPacketLifeTime and maxRetransmits are ignored when consuming data on a DirectTransport');
}
}
const { label, protocol } = dataProducer;
const dataConsumerId = utils.generateUUIDv4();
const requestOffset = createConsumeDataRequest({
builder: this.channel.bufferBuilder,
dataConsumerId,
dataProducerId,
type,
sctpStreamParameters,
label,
protocol,
paused,
subchannels,
});
const response = await this.channel.request(FbsRequest.Method.TRANSPORT_CONSUME_DATA, FbsRequest.Body.Transport_ConsumeDataRequest, requestOffset, this.internal.transportId);
/* Decode Response. */
const consumeDataResponse = new FbsDataConsumer.DumpResponse();
response.body(consumeDataResponse);
const dump = (0, DataConsumer_1.parseDataConsumerDumpResponse)(consumeDataResponse);
const dataConsumer = new DataConsumer_1.DataConsumer({
internal: {
...this.internal,
dataConsumerId,
},
data: {
dataProducerId: dump.dataProducerId,
type: dump.type,
sctpStreamParameters: dump.sctpStreamParameters,
label: dump.label,
protocol: dump.protocol,
bufferedAmountLowThreshold: dump.bufferedAmountLowThreshold,
},
channel: this.channel,
paused: dump.paused,
subchannels: dump.subchannels,
dataProducerPaused: dump.dataProducerPaused,
appData,
});
this.dataConsumers.set(dataConsumer.id, dataConsumer);
dataConsumer.on('@close', () => {
this.dataConsumers.delete(dataConsumer.id);
if (this.#sctpStreamIds) {
this.#sctpStreamIds[sctpStreamId] = 0;
}
});
dataConsumer.on('@dataproducerclose', () => {
this.dataConsumers.delete(dataConsumer.id);
if (this.#sctpStreamIds) {
this.#sctpStreamIds[sctpStreamId] = 0;
}
});
// Emit observer event.
this.#observer.safeEmit('newdataconsumer', dataConsumer);
return dataConsumer;
}
/**
* Enable 'trace' event.
*/
async enableTraceEvent(types = []) {
logger.debug('enableTraceEvent()');
if (!Array.isArray(types)) {
throw new TypeError('types must be an array');
}
else if (types.find(type => typeof type !== 'string')) {
throw new TypeError('every type must be a string');
}
// Convert event types.
const fbsEventTypes = [];
for (const eventType of types) {
try {
fbsEventTypes.push(transportTraceEventTypeToFbs(eventType));
}
catch (error) {
// Ignore invalid event types.
}
}
/* Build Request. */
const requestOffset = new FbsTransport.EnableTraceEventRequestT(fbsEventTypes).pack(this.channel.bufferBuilder);
await this.channel.request(FbsRequest.Method.TRANSPORT_ENABLE_TRACE_EVENT, FbsRequest.Body.Transport_EnableTraceEventRequest, requestOffset, this.internal.transportId);
}
getNextSctpStreamId() {
if (!this.#data.sctpParameters ||
typeof this.#data.sctpParameters.MIS !== 'number') {
throw new TypeError('missing sctpParameters.MIS');
}
const numStreams = this.#data.sctpParameters.MIS;
if (!this.#sctpStreamIds) {
this.#sctpStreamIds = Buffer.alloc(numStreams, 0);
}
let sctpStreamId;
for (let idx = 0; idx < this.#sctpStreamIds.length; ++idx) {
sctpStreamId =
(this.#nextSctpStreamId + idx) % this.#sctpStreamIds.length;
if (!this.#sctpStreamIds[sctpStreamId]) {
this.#nextSctpStreamId = sctpStreamId + 1;
return sctpStreamId;
}
}
throw new Error('no sctpStreamId available');
}
}
exports.Transport = Transport;
function portRangeToFbs(portRange = { min: 0, max: 0 }) {
return new FbsTransport.PortRangeT(portRange.min, portRange.max);
}
function socketFlagsToFbs(flags = {}) {
return new FbsTransport.SocketFlagsT(Boolean(flags.ipv6Only), Boolean(flags.udpReusePort));
}
function parseSctpState(fbsSctpState) {
switch (fbsSctpState) {
case sctp_state_1.SctpState.NEW: {
return 'new';
}
case sctp_state_1.SctpState.CONNECTING: {
return 'connecting';
}
case sctp_state_1.SctpState.CONNECTED: {
return 'connected';
}
case sctp_state_1.SctpState.FAILED: {
return 'failed';
}
case sctp_state_1.SctpState.CLOSED: {
return 'closed';
}
default: {
throw new TypeError(`invalid SctpState: ${fbsSctpState}`);
}
}
}
function parseProtocol(protocol) {
switch (protocol) {
case FbsTransport.Protocol.UDP: {
return 'udp';
}
case FbsTransport.Protocol.TCP: {
return 'tcp';
}
}
}
function serializeProtocol(protocol) {
switch (protocol) {
case 'udp': {
return FbsTransport.Protocol.UDP;
}
case 'tcp': {
return FbsTransport.Protocol.TCP;
}
}
}
function parseTuple(binary) {
return {
// @deprecated Use localAddress instead.
localIp: binary.localAddress(),
localAddress: binary.localAddress(),
localPort: binary.localPort(),
remoteIp: binary.remoteIp() ?? undefined,
remotePort: binary.remotePort(),
protocol: parseProtocol(binary.protocol()),
};
}
function parseBaseTransportDump(binary) {
// Retrieve producerIds.
const producerIds = utils.parseVector(binary, 'producerIds');
// Retrieve consumerIds.
const consumerIds = utils.parseVector(binary, 'consumerIds');
// Retrieve map SSRC consumerId.
const mapSsrcConsumerId = utils.parseUint32StringVector(binary, 'mapSsrcConsumerId');
// Retrieve map RTX SSRC consumerId.
const mapRtxSsrcConsumerId = utils.parseUint32StringVector(binary, 'mapRtxSsrcConsumerId');
// Retrieve dataProducerIds.
const dataProducerIds = utils.parseVector(binary, 'dataProducerIds');
// Retrieve dataConsumerIds.
const dataConsumerIds = utils.parseVector(binary, 'dataConsumerIds');
// Retrieve recvRtpHeaderExtesions.
const recvRtpHeaderExtensions = parseRecvRtpHeaderExtensions(binary.recvRtpHeaderExtensions());
// Retrieve RtpListener.
const rtpListener = parseRtpListenerDump(binary.rtpListener());
// Retrieve SctpParameters.
const fbsSctpParameters = binary.sctpParameters();
let sctpParameters;
if (fbsSctpParameters) {
sctpParameters = (0, SctpParameters_1.parseSctpParametersDump)(fbsSctpParameters);
}
// Retrieve sctpState.
const sctpState = binary.sctpState() === null
? undefined
: parseSctpState(binary.sctpState());
// Retrive sctpListener.
const sctpListener = binary.sctpListener()
? parseSctpListenerDump(binary.sctpListener())
: undefined;
// Retrieve traceEventTypes.
const traceEventTypes = utils.parseVector(binary, 'traceEventTypes', transportTraceEventTypeFromFbs);
return {
id: binary.id(),
direct: binary.direct(),
producerIds: producerIds,
consumerIds: consumerIds,
mapSsrcConsumerId: mapSsrcConsumerId,
mapRtxSsrcConsumerId: mapRtxSsrcConsumerId,
dataProducerIds: dataProducerIds,
dataConsumerIds: dataConsumerIds,
recvRtpHeaderExtensions: recvRtpHeaderExtensions,
rtpListener: rtpListener,
maxMessageSize: binary.maxMessageSize(),
sctpParameters: sctpParameters,
sctpState: sctpState,
sctpListener: sctpListener,
traceEventTypes: traceEventTypes,
};
}
function parseBaseTransportStats(binary) {
const sctpState = binary.sctpState() === null
? undefined
: parseSctpState(binary.sctpState());
return {
transportId: binary.transportId(),
timestamp: Number(binary.timestamp()),
sctpState,
bytesReceived: Number(binary.bytesReceived()),
recvBitrate: Number(binary.recvBitrate()),
bytesSent: Number(binary.bytesSent()),
sendBitrate: Number(binary.sendBitrate()),
rtpBytesReceived: Number(binary.rtpBytesReceived()),
rtpRecvBitrate: Number(binary.rtpRecvBitrate()),
rtpBytesSent: Number(binary.rtpBytesSent()),
rtpSendBitrate: Number(binary.rtpSendBitrate()),
rtxBytesReceived: Number(binary.rtxBytesReceived()),
rtxRecvBitrate: Number(binary.rtxRecvBitrate()),
rtxBytesSent: Number(binary.rtxBytesSent()),
rtxSendBitrate: Number(binary.rtxSendBitrate()),
probationBytesSent: Number(binary.probationBytesSent()),
probationSendBitrate: Number(binary.probationSendBitrate()),
availableOutgoingBitrate: typeof binary.availableOutgoingBitrate() === 'number'
? Number(binary.availableOutgoingBitrate())
: undefined,
availableIncomingBitrate: typeof binary.availableIncomingBitrate() === 'number'
? Number(binary.availableIncomingBitrate())
: undefined,
maxIncomingBitrate: typeof binary.maxIncomingBitrate() === 'number'
? Number(binary.maxIncomingBitrate())
: undefined,
maxOutgoingBitrate: typeof binary.maxOutgoingBitrate() === 'number'
? Number(binary.maxOutgoingBitrate())
: undefined,
minOutgoingBitrate: typeof binary.minOutgoingBitrate() === 'number'
? Number(binary.minOutgoingBitrate())
: undefined,
rtpPacketLossReceived: typeof binary.rtpPacketLossReceived() === 'number'
? Number(binary.rtpPacketLossReceived())
: undefined,
rtpPacketLossSent: typeof binary.rtpPacketLossSent() === 'number'
? Number(binary.rtpPacketLossSent())
: undefined,
};
}
function parseTransportTraceEventData(trace) {
switch (trace.type()) {
case FbsTransport.TraceEventType.BWE: {
const info = new FbsTransport.BweTraceInfo();
trace.info(info);
return {
type: 'bwe',
timestamp: Number(trace.timestamp()),
direction: trace.direction() === common_1.TraceDirection.DIRECTION_IN ? 'in' : 'out',
info: parseBweTraceInfo(info),
};
}
case FbsTransport.TraceEventType.PROBATION: {
return {
type: 'probation',
timestamp: Number(trace.timestamp()),
direction: trace.direction() === common_1.TraceDirection.DIRECTION_IN ? 'in' : 'out',
info: {},
};
}
}
}
function parseRecvRtpHeaderExtensions(binary) {
return {
mid: binary.mid() !== null ? binary.mid() : undefined,
rid: binary.rid() !== null ? binary.rid() : undefined,
rrid: binary.rrid() !== null ? binary.rrid() : undefined,
absSendTime: binary.absSendTime() !== null ? binary.absSendTime() : undefined,
transportWideCc01: binary.transportWideCc01() !== null
? binary.transportWideCc01()
: undefined,
};
}
function transportTraceEventTypeToFbs(eventType) {
switch (eventType) {
case 'probation': {
return FbsTransport.TraceEventType.PROBATION;
}
case 'bwe': {
return FbsTransport.TraceEventType.BWE;
}
default: {
throw new TypeError(`invalid TransportTraceEventType: ${eventType}`);
}
}
}
function transportTraceEventTypeFromFbs(eventType) {
switch (eventType) {
case FbsTransport.TraceEventType.PROBATION: {
return 'probation';
}
case FbsTransport.TraceEventType.BWE: {
return 'bwe';
}
}
}
function parseBweTraceInfo(binary) {
return {
desiredBitrate: binary.desiredBitrate(),
effectiveDesiredBitrate: binary.effectiveDesiredBitrate(),
minBitrate: binary.minBitrate(),
maxBitrate: binary.maxBitrate(),
startBitrate: binary.startBitrate(),
maxPaddingBitrate: binary.maxPaddingBitrate(),
availableBitrate: binary.availableBitrate(),
bweType: binary.bweType() === FbsTransport.BweType.TRANSPORT_CC
? 'transport-cc'
: 'remb',
};
}
function createConsumeRequest({ builder, producer, consumerId, rtpParameters, paused, preferredLayers, ignoreDtx, pipe, }) {
const rtpParametersOffset = (0, RtpParameters_1.serializeRtpParameters)(builder, rtpParameters);
const consumerIdOffset = builder.createString(consumerId);
const producerIdOffset = builder.createString(producer.id);
let consumableRtpEncodingsOffset;
let preferredLayersOffset;
if (producer.consumableRtpParameters.encodings) {
consumableRtpEncodingsOffset = (0, RtpParameters_1.serializeRtpEncodingParameters)(builder, producer.consumableRtpParameters.encodings);
}
if (preferredLayers) {
FbsConsumer.ConsumerLayers.startConsumerLayers(builder);
FbsConsumer.ConsumerLayers.addSpatialLayer(builder, preferredLayers.spatialLayer);
if (preferredLayers.temporalLayer !== undefined) {
FbsConsumer.ConsumerLayers.addTemporalLayer(builder, preferredLayers.temporalLayer);
}
preferredLayersOffset =
FbsConsumer.ConsumerLayers.endConsumerLayers(builder);
}
const ConsumeRequest = FbsTransport.ConsumeRequest;
// Create Consume Request.
ConsumeRequest.startConsumeRequest(builder);
ConsumeRequest.addConsumerId(builder, consumerIdOffset);
ConsumeRequest.addProducerId(builder, producerIdOffset);
ConsumeRequest.addKind(builder, producer.kind === 'audio' ? media_kind_1.MediaKind.AUDIO : media_kind_1.MediaKind.VIDEO);
ConsumeRequest.addRtpParameters(builder, rtpParametersOffset);
ConsumeRequest.addType(builder, pipe ? FbsRtpParameters.Type.PIPE : (0, Producer_1.producerTypeToFbs)(producer.type));
if (consumableRtpEncodingsOffset) {
ConsumeRequest.addConsumableRtpEncodings(builder, consumableRtpEncodingsOffset);
}
ConsumeRequest.addPaused(builder, paused);
if (preferredLayersOffset) {
ConsumeRequest.addPreferredLayers(builder, preferredLayersOffset);
}
ConsumeRequest.addIgnoreDtx(builder, Boolean(ignoreDtx));
return ConsumeRequest.endConsumeRequest(builder);
}
function createProduceRequest({ builder, producerId, kind, rtpParameters, rtpMapping, keyFrameRequestDelay, paused, }) {
const producerIdOffset = builder.createString(producerId);
const rtpParametersOffset = (0, RtpParameters_1.serializeRtpParameters)(builder, rtpParameters);
const rtpMappingOffset = ortc.serializeRtpMapping(builder, rtpMapping);
FbsTransport.ProduceRequest.startProduceRequest(builder);
FbsTransport.ProduceRequest.addProducerId(builder, producerIdOffset);
FbsTransport.ProduceRequest.addKind(builder, kind === 'audio' ? media_kind_1.MediaKind.AUDIO : media_kind_1.MediaKind.VIDEO);
FbsTransport.ProduceRequest.addRtpParameters(builder, rtpParametersOffset);
FbsTransport.ProduceRequest.addRtpMapping(builder, rtpMappingOffset);
FbsTransport.ProduceRequest.addKeyFrameRequestDelay(builder, keyFrameRequestDelay ?? 0);
FbsTransport.ProduceRequest.addPaused(builder, paused);
return FbsTransport.ProduceRequest.endProduceRequest(builder);
}
function createProduceDataRequest({ builder, dataProducerId, type, sctpStreamParameters, label, protocol, paused, }) {
const dataProducerIdOffset = builder.createString(dataProducerId);
const labelOffset = builder.createString(label);
const protocolOffset = builder.createString(protocol);
let sctpStreamParametersOffset = 0;
if (sctpStreamParameters) {
sctpStreamParametersOffset = (0, SctpParameters_1.serializeSctpStreamParameters)(builder, sctpStreamParameters);
}
FbsTransport.ProduceDataRequest.startProduceDataRequest(builder);
FbsTransport.ProduceDataRequest.addDataProducerId(builder, dataProducerIdOffset);
FbsTransport.ProduceDataRequest.addType(builder, (0, DataProducer_1.dataProducerTypeToFbs)(type));
if (sctpStreamParametersOffset) {
FbsTransport.ProduceDataRequest.addSctpStreamParameters(builder, sctpStreamParametersOffset);
}
FbsTransport.ProduceDataRequest.addLabel(builder, labelOffset);
FbsTransport.ProduceDataRequest.addProtocol(builder, protocolOffset);
FbsTransport.ProduceDataRequest.addPaused(builder, paused);
return FbsTransport.ProduceDataRequest.endProduceDataRequest(builder);
}
function createConsumeDataRequest({ builder, dataConsumerId, dataProducerId, type, sctpStreamParameters, label, protocol, paused, subchannels = [], }) {
const dataConsumerIdOffset = builder.createString(dataConsumerId);
const dataProducerIdOffset = builder.createString(dataProducerId);
const labelOffset = builder.createString(label);
const protocolOffset = builder.createString(protocol);
let sctpStreamParametersOffset = 0;
if (sctpStreamParameters) {
sctpStreamParametersOffset = (0, SctpParameters_1.serializeSctpStreamParameters)(builder, sctpStreamParameters);
}
const subchannelsOffset = FbsTransport.ConsumeDataRequest.createSubchannelsVector(builder, subchannels);
FbsTransport.ConsumeDataRequest.startConsumeDataRequest(builder);
FbsTransport.ConsumeDataRequest.addDataConsumerId(builder, dataConsumerIdOffset);
FbsTransport.ConsumeDataRequest.addDataProducerId(builder, dataProducerIdOffset);
FbsTransport.ConsumeDataRequest.addType(builder, (0, DataConsumer_1.dataConsumerTypeToFbs)(type));
if (sctpStreamParametersOffset) {
FbsTransport.ConsumeDataRequest.addSctpStreamParameters(builder, sctpStreamParametersOffset);
}
FbsTransport.ConsumeDataRequest.addLabel(builder, labelOffset);
FbsTransport.ConsumeDataRequest.addProtocol(builder, protocolOffset);
FbsTransport.ConsumeDataRequest.addPaused(builder, paused);
FbsTransport.ConsumeDataRequest.addSubchannels(builder, subchannelsOffset);
return FbsTransport.ConsumeDataRequest.endConsumeDataRequest(builder);
}
function parseRtpListenerDump(binary) {
// Retrieve ssrcTable.
const ssrcTable = utils.parseUint32StringVector(binary, 'ssrcTable');
// Retrieve midTable.
const midTable = utils.parseUint32StringVector(binary, 'midTable');
// Retrieve ridTable.
const ridTable = utils.parseUint32StringVector(binary, 'ridTable');
return {
ssrcTable,
midTable,
ridTable,
};
}
function parseSctpListenerDump(binary) {
// Retrieve streamIdTable.
const streamIdTable = utils.parseUint32StringVector(binary, 'streamIdTable');
return { streamIdTable };
}