mediasoup
Version:
Cutting Edge WebRTC Video Conferencing
406 lines (405 loc) • 13.3 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Producer = void 0;
exports.producerTypeFromFbs = producerTypeFromFbs;
exports.producerTypeToFbs = producerTypeToFbs;
exports.parseProducerDump = parseProducerDump;
const Logger_1 = require("./Logger");
const enhancedEvents_1 = require("./enhancedEvents");
const RtpParameters_1 = require("./RtpParameters");
const notification_1 = require("./fbs/notification");
const RtpStream_1 = require("./RtpStream");
const utils = require("./utils");
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 Producer 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();
/**
* @private
*/
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();
}
/**
* Producer id.
*/
get id() {
return this.#internal.producerId;
}
/**
* Whether the Producer is closed.
*/
get closed() {
return this.#closed;
}
/**
* Media kind.
*/
get kind() {
return this.#data.kind;
}
/**
* RTP parameters.
*/
get rtpParameters() {
return this.#data.rtpParameters;
}
/**
* Producer type.
*/
get type() {
return this.#data.type;
}
/**
* Consumable RTP parameters.
*
* @private
*/
get consumableRtpParameters() {
return this.#data.consumableRtpParameters;
}
/**
* Whether the Producer is paused.
*/
get paused() {
return this.#paused;
}
/**
* Producer score list.
*/
get score() {
return this.#score;
}
/**
* 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 Producer.
*/
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');
}
/**
* Transport was closed.
*
* @private
*/
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');
}
/**
* Dump Producer.
*/
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);
}
/**
* Get Producer stats.
*/
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);
}
/**
* Pause the Producer.
*/
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');
}
}
/**
* Resume the Producer.
*/
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');
}
}
/**
* Enable 'trace' event.
*/
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 RTP packet (just valid for Producers created on a DirectTransport).
*/
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 = utils.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 "%s"', event);
}
}
});
}
}
exports.Producer = Producer;
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;
}
}
}
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, RtpParameters_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() ? data.rtpMapping().unpack() : undefined,
// NOTE: optional values are represented with null instead of undefined.
// TODO: Make flatbuffers TS return undefined instead of null.
rtpStreams: data.rtpStreamsLength() > 0
? utils.parseVector(data, 'rtpStreams', (rtpStream) => rtpStream.unpack())
: undefined,
traceEventTypes: utils.parseVector(data, 'traceEventTypes', producerTraceEventTypeFromFbs),
paused: data.paused(),
};
}
function parseProducerStats(binary) {
return utils.parseVector(binary, 'stats', RtpStream_1.parseRtpStreamRecvStats);
}
function parseProducerScore(binary) {
return {
encodingIdx: binary.encodingIdx(),
ssrc: binary.ssrc(),
rid: binary.rid() ?? undefined,
score: binary.score(),
};
}
function parseTraceEventData(trace) {
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 ? info.unpack() : undefined,
};
}