mediasoup
Version:
Cutting Edge WebRTC Video Conferencing
352 lines (351 loc) • 12.7 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProducerImpl = void 0;
exports.producerTypeFromFbs = producerTypeFromFbs;
exports.producerTypeToFbs = producerTypeToFbs;
const Logger_1 = require("./Logger");
const enhancedEvents_1 = require("./enhancedEvents");
const rtpParametersFbsUtils_1 = require("./rtpParametersFbsUtils");
const rtpStreamStatsFbsUtils_1 = require("./rtpStreamStatsFbsUtils");
const fbsUtils = require("./fbsUtils");
const notification_1 = require("./fbs/notification");
const common_1 = require("./fbs/common");
const FbsNotification = require("./fbs/notification");
const FbsRequest = require("./fbs/request");
const FbsTransport = require("./fbs/transport");
const FbsProducer = require("./fbs/producer");
const FbsProducerTraceInfo = require("./fbs/producer/trace-info");
const FbsRtpParameters = require("./fbs/rtp-parameters");
const logger = new Logger_1.Logger('Producer');
class ProducerImpl extends enhancedEvents_1.EnhancedEventEmitter {
// Internal data.
#internal;
// Producer data.
#data;
// Channel instance.
#channel;
// Closed flag.
#closed = false;
// Paused flag.
#paused = false;
// Custom app data.
#appData;
// Current score.
#score = [];
// Observer instance.
#observer = new enhancedEvents_1.EnhancedEventEmitter();
constructor({ internal, data, channel, appData, paused, }) {
super();
logger.debug('constructor()');
this.#internal = internal;
this.#data = data;
this.#channel = channel;
this.#paused = paused;
this.#appData = appData ?? {};
this.handleWorkerNotifications();
this.handleListenerError();
}
get id() {
return this.#internal.producerId;
}
get closed() {
return this.#closed;
}
get kind() {
return this.#data.kind;
}
get rtpParameters() {
return this.#data.rtpParameters;
}
get type() {
return this.#data.type;
}
get consumableRtpParameters() {
return this.#data.consumableRtpParameters;
}
get paused() {
return this.#paused;
}
get score() {
return this.#score;
}
get appData() {
return this.#appData;
}
set appData(appData) {
this.#appData = appData;
}
get observer() {
return this.#observer;
}
/**
* Just for testing purposes.
*
* @private
*/
get channelForTesting() {
return this.#channel;
}
close() {
if (this.#closed) {
return;
}
logger.debug('close()');
this.#closed = true;
// Remove notification subscriptions.
this.#channel.removeAllListeners(this.#internal.producerId);
/* Build Request. */
const requestOffset = new FbsTransport.CloseProducerRequestT(this.#internal.producerId).pack(this.#channel.bufferBuilder);
this.#channel
.request(FbsRequest.Method.TRANSPORT_CLOSE_PRODUCER, FbsRequest.Body.Transport_CloseProducerRequest, 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.producerId);
this.safeEmit('transportclose');
// Emit observer event.
this.#observer.safeEmit('close');
}
async dump() {
logger.debug('dump()');
const response = await this.#channel.request(FbsRequest.Method.PRODUCER_DUMP, undefined, undefined, this.#internal.producerId);
/* Decode Response. */
const dumpResponse = new FbsProducer.DumpResponse();
response.body(dumpResponse);
return parseProducerDump(dumpResponse);
}
async getStats() {
logger.debug('getStats()');
const response = await this.#channel.request(FbsRequest.Method.PRODUCER_GET_STATS, undefined, undefined, this.#internal.producerId);
/* Decode Response. */
const data = new FbsProducer.GetStatsResponse();
response.body(data);
return parseProducerStats(data);
}
async pause() {
logger.debug('pause()');
await this.#channel.request(FbsRequest.Method.PRODUCER_PAUSE, undefined, undefined, this.#internal.producerId);
const wasPaused = this.#paused;
this.#paused = true;
// Emit observer event.
if (!wasPaused) {
this.#observer.safeEmit('pause');
}
}
async resume() {
logger.debug('resume()');
await this.#channel.request(FbsRequest.Method.PRODUCER_RESUME, undefined, undefined, this.#internal.producerId);
const wasPaused = this.#paused;
this.#paused = false;
// Emit observer event.
if (wasPaused) {
this.#observer.safeEmit('resume');
}
}
async enableTraceEvent(types = []) {
logger.debug('enableTraceEvent()');
if (!Array.isArray(types)) {
throw new TypeError('types must be an array');
}
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(producerTraceEventTypeToFbs(eventType));
}
catch (error) {
logger.warn('enableTraceEvent() | [error:${error}]');
}
}
/* Build Request. */
const requestOffset = new FbsProducer.EnableTraceEventRequestT(fbsEventTypes).pack(this.#channel.bufferBuilder);
await this.#channel.request(FbsRequest.Method.PRODUCER_ENABLE_TRACE_EVENT, FbsRequest.Body.Producer_EnableTraceEventRequest, requestOffset, this.#internal.producerId);
}
send(rtpPacket) {
if (!Buffer.isBuffer(rtpPacket)) {
throw new TypeError('rtpPacket must be a Buffer');
}
const builder = this.#channel.bufferBuilder;
const dataOffset = FbsProducer.SendNotification.createDataVector(builder, rtpPacket);
const notificationOffset = FbsProducer.SendNotification.createSendNotification(builder, dataOffset);
this.#channel.notify(FbsNotification.Event.PRODUCER_SEND, FbsNotification.Body.Producer_SendNotification, notificationOffset, this.#internal.producerId);
}
handleWorkerNotifications() {
this.#channel.on(this.#internal.producerId, (event, data) => {
switch (event) {
case notification_1.Event.PRODUCER_SCORE: {
const notification = new FbsProducer.ScoreNotification();
data.body(notification);
const score = fbsUtils.parseVector(notification, 'scores', parseProducerScore);
this.#score = score;
this.safeEmit('score', score);
// Emit observer event.
this.#observer.safeEmit('score', score);
break;
}
case notification_1.Event.PRODUCER_VIDEO_ORIENTATION_CHANGE: {
const notification = new FbsProducer.VideoOrientationChangeNotification();
data.body(notification);
const videoOrientation = notification.unpack();
this.safeEmit('videoorientationchange', videoOrientation);
// Emit observer event.
this.#observer.safeEmit('videoorientationchange', videoOrientation);
break;
}
case notification_1.Event.PRODUCER_TRACE: {
const notification = new FbsProducer.TraceNotification();
data.body(notification);
const trace = parseTraceEventData(notification);
this.safeEmit('trace', trace);
// Emit observer event.
this.#observer.safeEmit('trace', trace);
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.ProducerImpl = ProducerImpl;
function producerTypeFromFbs(type) {
switch (type) {
case FbsRtpParameters.Type.SIMPLE: {
return 'simple';
}
case FbsRtpParameters.Type.SIMULCAST: {
return 'simulcast';
}
case FbsRtpParameters.Type.SVC: {
return 'svc';
}
default: {
throw new TypeError(`invalid FbsRtpParameters.Type: ${type}`);
}
}
}
function producerTypeToFbs(type) {
switch (type) {
case 'simple': {
return FbsRtpParameters.Type.SIMPLE;
}
case 'simulcast': {
return FbsRtpParameters.Type.SIMULCAST;
}
case 'svc': {
return FbsRtpParameters.Type.SVC;
}
default: {
throw new TypeError(`invalid ProducerType: ${type}`);
}
}
}
function producerTraceEventTypeToFbs(eventType) {
switch (eventType) {
case 'keyframe': {
return FbsProducer.TraceEventType.KEYFRAME;
}
case 'fir': {
return FbsProducer.TraceEventType.FIR;
}
case 'nack': {
return FbsProducer.TraceEventType.NACK;
}
case 'pli': {
return FbsProducer.TraceEventType.PLI;
}
case 'rtp': {
return FbsProducer.TraceEventType.RTP;
}
case 'sr': {
return FbsProducer.TraceEventType.SR;
}
default: {
throw new TypeError(`invalid ProducerTraceEventType: ${eventType}`);
}
}
}
function producerTraceEventTypeFromFbs(eventType) {
switch (eventType) {
case FbsProducer.TraceEventType.KEYFRAME: {
return 'keyframe';
}
case FbsProducer.TraceEventType.FIR: {
return 'fir';
}
case FbsProducer.TraceEventType.NACK: {
return 'nack';
}
case FbsProducer.TraceEventType.PLI: {
return 'pli';
}
case FbsProducer.TraceEventType.RTP: {
return 'rtp';
}
case FbsProducer.TraceEventType.SR: {
return 'sr';
}
}
}
function parseProducerDump(data) {
return {
id: data.id(),
kind: data.kind() === FbsRtpParameters.MediaKind.AUDIO ? 'audio' : 'video',
type: producerTypeFromFbs(data.type()),
rtpParameters: (0, rtpParametersFbsUtils_1.parseRtpParameters)(data.rtpParameters()),
// NOTE: optional values are represented with null instead of undefined.
// TODO: Make flatbuffers TS return undefined instead of null.
rtpMapping: data.rtpMapping()?.unpack(),
// NOTE: optional values are represented with null instead of undefined.
// TODO: Make flatbuffers TS return undefined instead of null.
rtpStreams: data.rtpStreamsLength() > 0
? fbsUtils.parseVector(data, 'rtpStreams', rtpStream => rtpStream.unpack())
: undefined,
traceEventTypes: fbsUtils.parseVector(data, 'traceEventTypes', producerTraceEventTypeFromFbs),
paused: data.paused(),
};
}
function parseProducerStats(binary) {
return fbsUtils.parseVector(binary, 'stats', rtpStreamStatsFbsUtils_1.parseRtpStreamRecvStats);
}
function parseProducerScore(binary) {
return {
encodingIdx: binary.encodingIdx(),
ssrc: binary.ssrc(),
rid: binary.rid() ?? undefined,
score: binary.score(),
};
}
function parseTraceEventData(trace) {
// eslint-disable-next-line @typescript-eslint/no-explicit-any
let info;
if (trace.infoType() !== FbsProducer.TraceInfo.NONE) {
const accessor = trace.info.bind(trace);
info = FbsProducerTraceInfo.unionToTraceInfo(trace.infoType(), accessor);
trace.info(info);
}
return {
type: producerTraceEventTypeFromFbs(trace.type()),
timestamp: Number(trace.timestamp()),
direction: trace.direction() === common_1.TraceDirection.DIRECTION_IN ? 'in' : 'out',
info: info?.unpack(),
};
}