mediasoup
Version:
Cutting Edge WebRTC Video Conferencing
365 lines (364 loc) • 15 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DataConsumerImpl = void 0;
exports.dataConsumerTypeToFbs = dataConsumerTypeToFbs;
exports.parseDataConsumerDumpResponse = parseDataConsumerDumpResponse;
const Logger_1 = require("./Logger");
const enhancedEvents_1 = require("./enhancedEvents");
const sctpParametersFbsUtils_1 = require("./sctpParametersFbsUtils");
const fbsUtils = require("./fbsUtils");
const notification_1 = require("./fbs/notification");
const FbsTransport = require("./fbs/transport");
const FbsRequest = require("./fbs/request");
const FbsDataConsumer = require("./fbs/data-consumer");
const FbsDataProducer = require("./fbs/data-producer");
const logger = new Logger_1.Logger('DataConsumer');
class DataConsumerImpl extends enhancedEvents_1.EnhancedEventEmitter {
// Internal data.
#internal;
// DataConsumer data.
#data;
// Channel instance.
#channel;
// Closed flag.
#closed = false;
// Paused flag.
#paused = false;
// Associated DataProducer paused flag.
#dataProducerPaused = false;
// Subchannels subscribed to.
#subchannels;
// Custom app data.
#appData;
// Observer instance.
#observer = new enhancedEvents_1.EnhancedEventEmitter();
constructor({ internal, data, channel, paused, dataProducerPaused, subchannels, appData, }) {
super();
logger.debug('constructor()');
this.#internal = internal;
this.#data = data;
this.#channel = channel;
this.#paused = paused;
this.#dataProducerPaused = dataProducerPaused;
this.#subchannels = subchannels;
this.#appData = appData ?? {};
this.handleWorkerNotifications();
this.handleListenerError();
}
get id() {
return this.#internal.dataConsumerId;
}
get dataProducerId() {
return this.#data.dataProducerId;
}
get closed() {
return this.#closed;
}
get type() {
return this.#data.type;
}
get sctpStreamParameters() {
return this.#data.sctpStreamParameters;
}
get label() {
return this.#data.label;
}
get protocol() {
return this.#data.protocol;
}
get paused() {
return this.#paused;
}
get dataProducerPaused() {
return this.#dataProducerPaused;
}
get subchannels() {
return Array.from(this.#subchannels);
}
get appData() {
return this.#appData;
}
set appData(appData) {
this.#appData = appData;
}
get observer() {
return this.#observer;
}
close() {
if (this.#closed) {
return;
}
logger.debug('close()');
this.#closed = true;
// Remove notification subscriptions.
this.#channel.removeAllListeners(this.#internal.dataConsumerId);
/* Build Request. */
const requestOffset = new FbsTransport.CloseDataConsumerRequestT(this.#internal.dataConsumerId).pack(this.#channel.bufferBuilder);
this.#channel
.request(FbsRequest.Method.TRANSPORT_CLOSE_DATACONSUMER, FbsRequest.Body.Transport_CloseDataConsumerRequest, requestOffset, this.#internal.transportId)
.catch(() => { });
this.emit('@close');
// Emit observer event.
this.#observer.safeEmit('close');
}
transportClosed() {
if (this.#closed) {
return;
}
logger.debug('transportClosed()');
this.#closed = true;
// Remove notification subscriptions.
this.#channel.removeAllListeners(this.#internal.dataConsumerId);
this.safeEmit('transportclose');
// Emit observer event.
this.#observer.safeEmit('close');
}
async dump() {
logger.debug('dump()');
const response = await this.#channel.request(FbsRequest.Method.DATACONSUMER_DUMP, undefined, undefined, this.#internal.dataConsumerId);
/* Decode Response. */
const dumpResponse = new FbsDataConsumer.DumpResponse();
response.body(dumpResponse);
return parseDataConsumerDumpResponse(dumpResponse);
}
async getStats() {
logger.debug('getStats()');
const response = await this.#channel.request(FbsRequest.Method.DATACONSUMER_GET_STATS, undefined, undefined, this.#internal.dataConsumerId);
/* Decode Response. */
const data = new FbsDataConsumer.GetStatsResponse();
response.body(data);
return [parseDataConsumerStats(data)];
}
async pause() {
logger.debug('pause()');
await this.#channel.request(FbsRequest.Method.DATACONSUMER_PAUSE, undefined, undefined, this.#internal.dataConsumerId);
const wasPaused = this.#paused;
this.#paused = true;
// Emit observer event.
if (!wasPaused && !this.#dataProducerPaused) {
this.#observer.safeEmit('pause');
}
}
async resume() {
logger.debug('resume()');
await this.#channel.request(FbsRequest.Method.DATACONSUMER_RESUME, undefined, undefined, this.#internal.dataConsumerId);
const wasPaused = this.#paused;
this.#paused = false;
// Emit observer event.
if (wasPaused && !this.#dataProducerPaused) {
this.#observer.safeEmit('resume');
}
}
async setBufferedAmountLowThreshold(threshold) {
logger.debug(`setBufferedAmountLowThreshold() [threshold:${threshold}]`);
/* Build Request. */
const requestOffset = FbsDataConsumer.SetBufferedAmountLowThresholdRequest.createSetBufferedAmountLowThresholdRequest(this.#channel.bufferBuilder, threshold);
await this.#channel.request(FbsRequest.Method.DATACONSUMER_SET_BUFFERED_AMOUNT_LOW_THRESHOLD, FbsRequest.Body.DataConsumer_SetBufferedAmountLowThresholdRequest, requestOffset, this.#internal.dataConsumerId);
}
async getBufferedAmount() {
logger.debug('getBufferedAmount()');
const response = await this.#channel.request(FbsRequest.Method.DATACONSUMER_GET_BUFFERED_AMOUNT, undefined, undefined, this.#internal.dataConsumerId);
const data = new FbsDataConsumer.GetBufferedAmountResponse();
response.body(data);
return data.bufferedAmount();
}
async send(message, ppid) {
if (typeof message !== 'string' && !Buffer.isBuffer(message)) {
throw new TypeError('message must be a string or a Buffer');
}
/*
* +-------------------------------+----------+
* | Value | SCTP |
* | | PPID |
* +-------------------------------+----------+
* | WebRTC String | 51 |
* | WebRTC Binary Partial | 52 |
* | (Deprecated) | |
* | WebRTC Binary | 53 |
* | WebRTC String Partial | 54 |
* | (Deprecated) | |
* | WebRTC String Empty | 56 |
* | WebRTC Binary Empty | 57 |
* +-------------------------------+----------+
*/
if (typeof ppid !== 'number') {
ppid =
typeof message === 'string'
? message.length > 0
? 51
: 56
: message.length > 0
? 53
: 57;
}
// Ensure we honor PPIDs.
if (ppid === 56) {
message = ' ';
}
else if (ppid === 57) {
message = Buffer.alloc(1);
}
const builder = this.#channel.bufferBuilder;
if (typeof message === 'string') {
message = Buffer.from(message);
}
const dataOffset = FbsDataConsumer.SendRequest.createDataVector(builder, message);
const requestOffset = FbsDataConsumer.SendRequest.createSendRequest(builder, ppid, dataOffset);
await this.#channel.request(FbsRequest.Method.DATACONSUMER_SEND, FbsRequest.Body.DataConsumer_SendRequest, requestOffset, this.#internal.dataConsumerId);
}
async setSubchannels(subchannels) {
logger.debug('setSubchannels()');
/* Build Request. */
const requestOffset = new FbsDataConsumer.SetSubchannelsRequestT(subchannels).pack(this.#channel.bufferBuilder);
const response = await this.#channel.request(FbsRequest.Method.DATACONSUMER_SET_SUBCHANNELS, FbsRequest.Body.DataConsumer_SetSubchannelsRequest, requestOffset, this.#internal.dataConsumerId);
/* Decode Response. */
const data = new FbsDataConsumer.SetSubchannelsResponse();
response.body(data);
// Update subchannels.
this.#subchannels = fbsUtils.parseVector(data, 'subchannels');
}
async addSubchannel(subchannel) {
logger.debug('addSubchannel()');
/* Build Request. */
const requestOffset = FbsDataConsumer.AddSubchannelRequest.createAddSubchannelRequest(this.#channel.bufferBuilder, subchannel);
const response = await this.#channel.request(FbsRequest.Method.DATACONSUMER_ADD_SUBCHANNEL, FbsRequest.Body.DataConsumer_AddSubchannelRequest, requestOffset, this.#internal.dataConsumerId);
/* Decode Response. */
const data = new FbsDataConsumer.AddSubchannelResponse();
response.body(data);
// Update subchannels.
this.#subchannels = fbsUtils.parseVector(data, 'subchannels');
}
async removeSubchannel(subchannel) {
logger.debug('removeSubchannel()');
/* Build Request. */
const requestOffset = FbsDataConsumer.RemoveSubchannelRequest.createRemoveSubchannelRequest(this.#channel.bufferBuilder, subchannel);
const response = await this.#channel.request(FbsRequest.Method.DATACONSUMER_REMOVE_SUBCHANNEL, FbsRequest.Body.DataConsumer_RemoveSubchannelRequest, requestOffset, this.#internal.dataConsumerId);
/* Decode Response. */
const data = new FbsDataConsumer.RemoveSubchannelResponse();
response.body(data);
// Update subchannels.
this.#subchannels = fbsUtils.parseVector(data, 'subchannels');
}
handleWorkerNotifications() {
this.#channel.on(this.#internal.dataConsumerId, (event, data) => {
switch (event) {
case notification_1.Event.DATACONSUMER_DATAPRODUCER_CLOSE: {
if (this.#closed) {
break;
}
this.#closed = true;
// Remove notification subscriptions.
this.#channel.removeAllListeners(this.#internal.dataConsumerId);
this.emit('@dataproducerclose');
this.safeEmit('dataproducerclose');
// Emit observer event.
this.#observer.safeEmit('close');
break;
}
case notification_1.Event.DATACONSUMER_DATAPRODUCER_PAUSE: {
if (this.#dataProducerPaused) {
break;
}
this.#dataProducerPaused = true;
this.safeEmit('dataproducerpause');
// Emit observer event.
if (!this.#paused) {
this.#observer.safeEmit('pause');
}
break;
}
case notification_1.Event.DATACONSUMER_DATAPRODUCER_RESUME: {
if (!this.#dataProducerPaused) {
break;
}
this.#dataProducerPaused = false;
this.safeEmit('dataproducerresume');
// Emit observer event.
if (!this.#paused) {
this.#observer.safeEmit('resume');
}
break;
}
case notification_1.Event.DATACONSUMER_SCTP_SENDBUFFER_FULL: {
this.safeEmit('sctpsendbufferfull');
break;
}
case notification_1.Event.DATACONSUMER_BUFFERED_AMOUNT_LOW: {
const notification = new FbsDataConsumer.BufferedAmountLowNotification();
data.body(notification);
const bufferedAmount = notification.bufferedAmount();
this.safeEmit('bufferedamountlow', bufferedAmount);
break;
}
case notification_1.Event.DATACONSUMER_MESSAGE: {
if (this.#closed) {
break;
}
const notification = new FbsDataConsumer.MessageNotification();
data.body(notification);
this.safeEmit('message', Buffer.from(notification.dataArray()), notification.ppid());
break;
}
default: {
logger.error(`ignoring unknown event "${event}"`);
}
}
});
}
handleListenerError() {
this.on('listenererror', (eventName, error) => {
logger.error(`event listener threw an error [eventName:${eventName}]:`, error);
});
}
}
exports.DataConsumerImpl = DataConsumerImpl;
function dataConsumerTypeToFbs(type) {
switch (type) {
case 'sctp': {
return FbsDataProducer.Type.SCTP;
}
case 'direct': {
return FbsDataProducer.Type.DIRECT;
}
default: {
throw new TypeError('invalid DataConsumerType: ${type}');
}
}
}
function dataConsumerTypeFromFbs(type) {
switch (type) {
case FbsDataProducer.Type.SCTP: {
return 'sctp';
}
case FbsDataProducer.Type.DIRECT: {
return 'direct';
}
}
}
function parseDataConsumerDumpResponse(data) {
return {
id: data.id(),
dataProducerId: data.dataProducerId(),
type: dataConsumerTypeFromFbs(data.type()),
sctpStreamParameters: data.sctpStreamParameters() !== null
? (0, sctpParametersFbsUtils_1.parseSctpStreamParameters)(data.sctpStreamParameters())
: undefined,
label: data.label(),
protocol: data.protocol(),
bufferedAmountLowThreshold: data.bufferedAmountLowThreshold(),
paused: data.paused(),
dataProducerPaused: data.dataProducerPaused(),
subchannels: fbsUtils.parseVector(data, 'subchannels'),
};
}
function parseDataConsumerStats(binary) {
return {
type: 'data-consumer',
timestamp: Number(binary.timestamp()),
label: binary.label(),
protocol: binary.protocol(),
messagesSent: Number(binary.messagesSent()),
bytesSent: Number(binary.bytesSent()),
bufferedAmount: binary.bufferedAmount(),
};
}