diff --git a/CHANGELOG.md b/CHANGELOG.md index 2cb5c6df80..aadea8be76 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,10 @@ ### NEXT +- Node: Add TypeScript interfaces for all exported classes ([PR #1463](https://github.com/versatica/mediasoup/pull/1463)). +- Node: Add new `transport.type` getter than returns `'webrtc' | 'plain' | 'pipe' | 'direct'` ([PR #1463](https://github.com/versatica/mediasoup/pull/1463)). +- Node: Add new `rtpObserver.type` getter than returns `'activespeaker' | 'audiolevel'` ([PR #1463](https://github.com/versatica/mediasoup/pull/1463)). + ### 3.14.16 - `SimulcastConsumer`: Fix cannot switch layers if initial `tsReferenceSpatialLayer disappears` disappears ([PR #1459](https://github.com/versatica/mediasoup/pull/1459) by @Lynnworld). diff --git a/eslint.config.mjs b/eslint.config.mjs index 858191d02c..1e596554d8 100644 --- a/eslint.config.mjs +++ b/eslint.config.mjs @@ -90,6 +90,7 @@ const config = tsEslint.config( 2, { vars: 'all', args: 'after-used', caughtErrors: 'none' }, ], + 'no-unused-private-class-members': 2, 'no-use-before-define': 0, 'no-useless-call': 2, 'no-useless-computed-key': 2, @@ -127,6 +128,7 @@ const config = tsEslint.config( }, }, rules: { + '@typescript-eslint/class-literal-property-style': [2, 'getters'], '@typescript-eslint/consistent-generic-constructors': [ 2, 'type-annotation', diff --git a/node/src/ActiveSpeakerObserver.ts b/node/src/ActiveSpeakerObserver.ts index 609e6581e5..a46ac16ab4 100644 --- a/node/src/ActiveSpeakerObserver.ts +++ b/node/src/ActiveSpeakerObserver.ts @@ -1,60 +1,33 @@ import { Logger } from './Logger'; import { EnhancedEventEmitter } from './enhancedEvents'; -import { - RtpObserver, - RtpObserverEvents, - RtpObserverObserverEvents, - RtpObserverConstructorOptions, -} from './RtpObserver'; -import { Producer } from './Producer'; -import { AppData } from './types'; +import type { + ActiveSpeakerObserver, + ActiveSpeakerObserverDominantSpeaker, + ActiveSpeakerObserverEvents, + ActiveSpeakerObserverObserver, + ActiveSpeakerObserverObserverEvents, +} from './ActiveSpeakerObserverTypes'; +import type { RtpObserver } from './RtpObserverTypes'; +import { RtpObserverImpl, RtpObserverConstructorOptions } from './RtpObserver'; +import type { AppData } from './types'; import { Event, Notification } from './fbs/notification'; import * as FbsActiveSpeakerObserver from './fbs/active-speaker-observer'; -export type ActiveSpeakerObserverOptions< - ActiveSpeakerObserverAppData extends AppData = AppData, -> = { - interval?: number; - - /** - * Custom application data. - */ - appData?: ActiveSpeakerObserverAppData; -}; - -export type ActiveSpeakerObserverDominantSpeaker = { - /** - * The audio Producer instance. - */ - producer: Producer; -}; - -export type ActiveSpeakerObserverEvents = RtpObserverEvents & { - dominantspeaker: [ActiveSpeakerObserverDominantSpeaker]; -}; - -export type ActiveSpeakerObserverObserver = - EnhancedEventEmitter; - -export type ActiveSpeakerObserverObserverEvents = RtpObserverObserverEvents & { - dominantspeaker: [ActiveSpeakerObserverDominantSpeaker]; -}; - type RtpObserverObserverConstructorOptions = RtpObserverConstructorOptions; const logger = new Logger('ActiveSpeakerObserver'); -export class ActiveSpeakerObserver< - ActiveSpeakerObserverAppData extends AppData = AppData, -> extends RtpObserver< - ActiveSpeakerObserverAppData, - ActiveSpeakerObserverEvents, - ActiveSpeakerObserverObserver -> { - /** - * @private - */ +export class ActiveSpeakerObserverImpl< + ActiveSpeakerObserverAppData extends AppData = AppData, + > + extends RtpObserverImpl< + ActiveSpeakerObserverAppData, + ActiveSpeakerObserverEvents, + ActiveSpeakerObserverObserver + > + implements RtpObserver, ActiveSpeakerObserver +{ constructor( options: RtpObserverObserverConstructorOptions ) { @@ -64,13 +37,13 @@ export class ActiveSpeakerObserver< super(options, observer); this.handleWorkerNotifications(); + this.handleListenerError(); + } + + get type(): 'activespeaker' { + return 'activespeaker'; } - /** - * Observer. - * - * @override - */ get observer(): ActiveSpeakerObserverObserver { return super.observer; } @@ -109,4 +82,13 @@ export class ActiveSpeakerObserver< } ); } + + private handleListenerError(): void { + this.on('listenererror', (eventName, error) => { + logger.error( + `event listener threw an error [eventName:${eventName}]:`, + error + ); + }); + } } diff --git a/node/src/ActiveSpeakerObserverTypes.ts b/node/src/ActiveSpeakerObserverTypes.ts new file mode 100644 index 0000000000..9d00005697 --- /dev/null +++ b/node/src/ActiveSpeakerObserverTypes.ts @@ -0,0 +1,59 @@ +import type { EnhancedEventEmitter } from './enhancedEvents'; +import type { + RtpObserver, + RtpObserverEvents, + RtpObserverObserverEvents, +} from './RtpObserverTypes'; +import type { Producer } from './ProducerTypes'; +import type { AppData } from './types'; + +export type ActiveSpeakerObserverOptions< + ActiveSpeakerObserverAppData extends AppData = AppData, +> = { + interval?: number; + + /** + * Custom application data. + */ + appData?: ActiveSpeakerObserverAppData; +}; + +export type ActiveSpeakerObserverDominantSpeaker = { + /** + * The audio Producer instance. + */ + producer: Producer; +}; + +export type ActiveSpeakerObserverEvents = RtpObserverEvents & { + dominantspeaker: [ActiveSpeakerObserverDominantSpeaker]; +}; + +export type ActiveSpeakerObserverObserver = + EnhancedEventEmitter; + +export type ActiveSpeakerObserverObserverEvents = RtpObserverObserverEvents & { + dominantspeaker: [ActiveSpeakerObserverDominantSpeaker]; +}; + +export interface ActiveSpeakerObserver< + ActiveSpeakerObserverAppData extends AppData = AppData, +> extends RtpObserver< + ActiveSpeakerObserverAppData, + ActiveSpeakerObserverEvents, + ActiveSpeakerObserverObserver + > { + /** + * RtpObserver type. + * + * @override + */ + get type(): 'activespeaker'; + + /** + * Observer. + * + * @override + */ + get observer(): ActiveSpeakerObserverObserver; +} diff --git a/node/src/AudioLevelObserver.ts b/node/src/AudioLevelObserver.ts index d429933954..5890956fdb 100644 --- a/node/src/AudioLevelObserver.ts +++ b/node/src/AudioLevelObserver.ts @@ -1,83 +1,35 @@ import { Logger } from './Logger'; import { EnhancedEventEmitter } from './enhancedEvents'; -import { - RtpObserver, - RtpObserverEvents, - RtpObserverObserverEvents, - RtpObserverConstructorOptions, -} from './RtpObserver'; -import { Producer } from './Producer'; -import { AppData } from './types'; -import * as utils from './utils'; +import type { + AudioLevelObserver, + AudioLevelObserverVolume, + AudioLevelObserverEvents, + AudioLevelObserverObserver, + AudioLevelObserverObserverEvents, +} from './AudioLevelObserverTypes'; +import type { RtpObserver } from './RtpObserverTypes'; +import { RtpObserverImpl, RtpObserverConstructorOptions } from './RtpObserver'; +import type { Producer } from './ProducerTypes'; +import type { AppData } from './types'; +import * as fbsUtils from './fbsUtils'; import { Event, Notification } from './fbs/notification'; import * as FbsAudioLevelObserver from './fbs/audio-level-observer'; -export type AudioLevelObserverOptions< - AudioLevelObserverAppData extends AppData = AppData, -> = { - /** - * Maximum number of entries in the 'volumes”' event. Default 1. - */ - maxEntries?: number; - - /** - * Minimum average volume (in dBvo from -127 to 0) for entries in the - * 'volumes' event. Default -80. - */ - threshold?: number; - - /** - * Interval in ms for checking audio volumes. Default 1000. - */ - interval?: number; - - /** - * Custom application data. - */ - appData?: AudioLevelObserverAppData; -}; - -export type AudioLevelObserverVolume = { - /** - * The audio Producer instance. - */ - producer: Producer; - - /** - * The average volume (in dBvo from -127 to 0) of the audio Producer in the - * last interval. - */ - volume: number; -}; - -export type AudioLevelObserverEvents = RtpObserverEvents & { - volumes: [AudioLevelObserverVolume[]]; - silence: []; -}; - -export type AudioLevelObserverObserver = - EnhancedEventEmitter; - -export type AudioLevelObserverObserverEvents = RtpObserverObserverEvents & { - volumes: [AudioLevelObserverVolume[]]; - silence: []; -}; - type AudioLevelObserverConstructorOptions = RtpObserverConstructorOptions; const logger = new Logger('AudioLevelObserver'); -export class AudioLevelObserver< - AudioLevelObserverAppData extends AppData = AppData, -> extends RtpObserver< - AudioLevelObserverAppData, - AudioLevelObserverEvents, - AudioLevelObserverObserver -> { - /** - * @private - */ +export class AudioLevelObserverImpl< + AudioLevelObserverAppData extends AppData = AppData, + > + extends RtpObserverImpl< + AudioLevelObserverAppData, + AudioLevelObserverEvents, + AudioLevelObserverObserver + > + implements RtpObserver, AudioLevelObserver +{ constructor( options: AudioLevelObserverConstructorOptions ) { @@ -87,13 +39,13 @@ export class AudioLevelObserver< super(options, observer); this.handleWorkerNotifications(); + this.handleListenerError(); + } + + get type(): 'audiolevel' { + return 'audiolevel'; } - /** - * Observer. - * - * @override - */ get observer(): AudioLevelObserverObserver { return super.observer; } @@ -111,7 +63,7 @@ export class AudioLevelObserver< // Get the corresponding Producer instance and remove entries with // no Producer (it may have been closed in the meanwhile). - const volumes: AudioLevelObserverVolume[] = utils + const volumes: AudioLevelObserverVolume[] = fbsUtils .parseVector(notification, 'volumes', parseVolume) .map( ({ @@ -153,6 +105,15 @@ export class AudioLevelObserver< } ); } + + private handleListenerError(): void { + this.on('listenererror', (eventName, error) => { + logger.error( + `event listener threw an error [eventName:${eventName}]:`, + error + ); + }); + } } function parseVolume(binary: FbsAudioLevelObserver.Volume): { diff --git a/node/src/AudioLevelObserverTypes.ts b/node/src/AudioLevelObserverTypes.ts new file mode 100644 index 0000000000..dca5b0e568 --- /dev/null +++ b/node/src/AudioLevelObserverTypes.ts @@ -0,0 +1,81 @@ +import type { EnhancedEventEmitter } from './enhancedEvents'; +import type { + RtpObserver, + RtpObserverEvents, + RtpObserverObserverEvents, +} from './RtpObserverTypes'; +import type { Producer } from './ProducerTypes'; +import type { AppData } from './types'; + +export type AudioLevelObserverOptions< + AudioLevelObserverAppData extends AppData = AppData, +> = { + /** + * Maximum number of entries in the 'volumes”' event. Default 1. + */ + maxEntries?: number; + + /** + * Minimum average volume (in dBvo from -127 to 0) for entries in the + * 'volumes' event. Default -80. + */ + threshold?: number; + + /** + * Interval in ms for checking audio volumes. Default 1000. + */ + interval?: number; + + /** + * Custom application data. + */ + appData?: AudioLevelObserverAppData; +}; + +export type AudioLevelObserverVolume = { + /** + * The audio Producer instance. + */ + producer: Producer; + + /** + * The average volume (in dBvo from -127 to 0) of the audio Producer in the + * last interval. + */ + volume: number; +}; + +export type AudioLevelObserverEvents = RtpObserverEvents & { + volumes: [AudioLevelObserverVolume[]]; + silence: []; +}; + +export type AudioLevelObserverObserver = + EnhancedEventEmitter; + +export type AudioLevelObserverObserverEvents = RtpObserverObserverEvents & { + volumes: [AudioLevelObserverVolume[]]; + silence: []; +}; + +export interface AudioLevelObserver< + AudioLevelObserverAppData extends AppData = AppData, +> extends RtpObserver< + AudioLevelObserverAppData, + AudioLevelObserverEvents, + AudioLevelObserverObserver + > { + /** + * RtpObserver type. + * + * @override + */ + get type(): 'audiolevel'; + + /** + * Observer. + * + * @override + */ + get observer(): AudioLevelObserverObserver; +} diff --git a/node/src/Channel.ts b/node/src/Channel.ts index 97ec868bdb..6621d1e188 100644 --- a/node/src/Channel.ts +++ b/node/src/Channel.ts @@ -53,9 +53,6 @@ export class Channel extends EnhancedEventEmitter { // flatbuffers builder. #bufferBuilder: flatbuffers.Builder = new flatbuffers.Builder(1024); - /** - * @private - */ constructor({ producerSocket, consumerSocket, @@ -200,9 +197,6 @@ export class Channel extends EnhancedEventEmitter { return this.#bufferBuilder; } - /** - * @private - */ close(): void { if (this.#closed) { return; @@ -236,9 +230,6 @@ export class Channel extends EnhancedEventEmitter { } catch (error) {} } - /** - * @private - */ notify( event: Event, bodyType?: NotificationBody, diff --git a/node/src/Consumer.ts b/node/src/Consumer.ts index 5945f8993b..ac092a5d2f 100644 --- a/node/src/Consumer.ts +++ b/node/src/Consumer.ts @@ -1,19 +1,38 @@ import { Logger } from './Logger'; import { EnhancedEventEmitter } from './enhancedEvents'; +import type { + Consumer, + ConsumerType, + ConsumerScore, + ConsumerLayers, + ConsumerDump, + SimpleConsumerDump, + SimulcastConsumerDump, + SvcConsumerDump, + PipeConsumerDump, + BaseConsumerDump, + RtpStreamDump, + RtpStreamParametersDump, + RtxStreamDump, + RtxStreamParameters, + ConsumerStat, + ConsumerTraceEventType, + ConsumerTraceEventData, + ConsumerEvents, + ConsumerObserver, + ConsumerObserverEvents, +} from './ConsumerTypes'; import { Channel } from './Channel'; -import { TransportInternal } from './Transport'; -import { ProducerStat } from './Producer'; +import type { TransportInternal } from './Transport'; +import type { ProducerStat } from './ProducerTypes'; +import type { MediaKind, RtpParameters } from './rtpParametersTypes'; import { - MediaKind, - RtpCapabilities, - RtpEncodingParameters, - RtpParameters, parseRtpEncodingParameters, parseRtpParameters, -} from './RtpParameters'; -import { parseRtpStreamStats, RtpStreamSendStats } from './RtpStream'; -import { AppData } from './types'; -import * as utils from './utils'; +} from './rtpParametersFbsUtils'; +import { parseRtpStreamStats } from './rtpStreamStatsFbsUtils'; +import type { AppData } from './types'; +import * as fbsUtils from './fbsUtils'; import { Event, Notification } from './fbs/notification'; import { TraceDirection as FbsTraceDirection } from './fbs/common'; import * as FbsRequest from './fbs/request'; @@ -25,200 +44,6 @@ import * as FbsRtxStream from './fbs/rtx-stream'; import { Type as FbsRtpParametersType } from './fbs/rtp-parameters'; import * as FbsRtpParameters from './fbs/rtp-parameters'; -export type ConsumerOptions = { - /** - * The id of the Producer to consume. - */ - producerId: string; - - /** - * RTP capabilities of the consuming endpoint. - */ - rtpCapabilities: RtpCapabilities; - - /** - * Whether the consumer must start in paused mode. Default false. - * - * When creating a video Consumer, it's recommended to set paused to true, - * then transmit the Consumer parameters to the consuming endpoint and, once - * the consuming endpoint has created its local side Consumer, unpause the - * server side Consumer using the resume() method. This is an optimization - * to make it possible for the consuming endpoint to render the video as far - * as possible. If the server side Consumer was created with paused: false, - * mediasoup will immediately request a key frame to the remote Producer and - * suych a key frame may reach the consuming endpoint even before it's ready - * to consume it, generating “black” video until the device requests a keyframe - * by itself. - */ - paused?: boolean; - - /** - * The MID for the Consumer. If not specified, a sequentially growing - * number will be assigned. - */ - mid?: string; - - /** - * Preferred spatial and temporal layer for simulcast or SVC media sources. - * If unset, the highest ones are selected. - */ - preferredLayers?: ConsumerLayers; - - /** - * Whether this Consumer should enable RTP retransmissions, storing sent RTP - * and processing the incoming RTCP NACK from the remote Consumer. If not set - * it's true by default for video codecs and false for audio codecs. If set - * to true, NACK will be enabled if both endpoints (mediasoup and the remote - * Consumer) support NACK for this codec. When it comes to audio codecs, just - * OPUS supports NACK. - */ - enableRtx?: boolean; - - /** - * Whether this Consumer should ignore DTX packets (only valid for Opus codec). - * If set, DTX packets are not forwarded to the remote Consumer. - */ - ignoreDtx?: boolean; - - /** - * Whether this Consumer should consume all RTP streams generated by the - * Producer. - */ - pipe?: boolean; - - /** - * Custom application data. - */ - appData?: ConsumerAppData; -}; - -/** - * Valid types for 'trace' event. - */ -export type ConsumerTraceEventType = - | 'rtp' - | 'keyframe' - | 'nack' - | 'pli' - | 'fir'; - -/** - * 'trace' event data. - */ -export type ConsumerTraceEventData = { - /** - * Trace type. - */ - type: ConsumerTraceEventType; - - /** - * Event timestamp. - */ - timestamp: number; - - /** - * Event direction. - */ - direction: 'in' | 'out'; - - /** - * Per type information. - */ - info: any; -}; - -export type ConsumerScore = { - /** - * The score of the RTP stream of the consumer. - */ - score: number; - - /** - * The score of the currently selected RTP stream of the producer. - */ - producerScore: number; - - /** - * The scores of all RTP streams in the producer ordered by encoding (just - * useful when the producer uses simulcast). - */ - producerScores: number[]; -}; - -export type ConsumerLayers = { - /** - * The spatial layer index (from 0 to N). - */ - spatialLayer: number; - - /** - * The temporal layer index (from 0 to N). - */ - temporalLayer?: number; -}; - -export type ConsumerStat = RtpStreamSendStats; - -/** - * Consumer type. - */ -export type ConsumerType = 'simple' | 'simulcast' | 'svc' | 'pipe'; - -export type ConsumerEvents = { - transportclose: []; - producerclose: []; - producerpause: []; - producerresume: []; - score: [ConsumerScore]; - layerschange: [ConsumerLayers?]; - trace: [ConsumerTraceEventData]; - rtp: [Buffer]; - listenererror: [string, Error]; - // Private events. - '@close': []; - '@producerclose': []; -}; - -export type ConsumerObserver = EnhancedEventEmitter; - -export type ConsumerObserverEvents = { - close: []; - pause: []; - resume: []; - score: [ConsumerScore]; - layerschange: [ConsumerLayers?]; - trace: [ConsumerTraceEventData]; -}; - -export type SimpleConsumerDump = BaseConsumerDump & { - type: string; - rtpStream: RtpStreamDump; -}; - -export type SimulcastConsumerDump = BaseConsumerDump & { - type: string; - rtpStream: RtpStreamDump; - preferredSpatialLayer: number; - targetSpatialLayer: number; - currentSpatialLayer: number; - preferredTemporalLayer: number; - targetTemporalLayer: number; - currentTemporalLayer: number; -}; - -export type SvcConsumerDump = SimulcastConsumerDump; - -export type PipeConsumerDump = BaseConsumerDump & { - type: string; - rtpStreams: RtpStreamDump[]; -}; - -export type ConsumerDump = - | SimpleConsumerDump - | SimulcastConsumerDump - | SvcConsumerDump - | PipeConsumerDump; - type ConsumerInternal = TransportInternal & { consumerId: string; }; @@ -230,62 +55,12 @@ type ConsumerData = { type: ConsumerType; }; -type BaseConsumerDump = { - id: string; - producerId: string; - kind: MediaKind; - rtpParameters: RtpParameters; - consumableRtpEncodings?: RtpEncodingParameters[]; - supportedCodecPayloadTypes: number[]; - traceEventTypes: string[]; - paused: boolean; - producerPaused: boolean; - priority: number; -}; - -type RtpStreamParameters = { - encodingIdx: number; - ssrc: number; - payloadType: number; - mimeType: string; - clockRate: number; - rid?: string; - cname: string; - rtxSsrc?: number; - rtxPayloadType?: number; - useNack: boolean; - usePli: boolean; - useFir: boolean; - useInBandFec: boolean; - useDtx: boolean; - spatialLayers: number; - temporalLayers: number; -}; - -type RtpStreamDump = { - params: RtpStreamParameters; - score: number; - rtxStream?: RtxStreamDump; -}; - -type RtxStreamParameters = { - ssrc: number; - payloadType: number; - mimeType: string; - clockRate: number; - rrid?: string; - cname: string; -}; - -type RtxStreamDump = { - params: RtxStreamParameters; -}; - const logger = new Logger('Consumer'); -export class Consumer< - ConsumerAppData extends AppData = AppData, -> extends EnhancedEventEmitter { +export class ConsumerImpl + extends EnhancedEventEmitter + implements Consumer +{ // Internal data. readonly #internal: ConsumerInternal; @@ -323,9 +98,6 @@ export class Consumer< readonly #observer: ConsumerObserver = new EnhancedEventEmitter(); - /** - * @private - */ constructor({ internal, data, @@ -359,124 +131,78 @@ export class Consumer< this.#appData = appData ?? ({} as ConsumerAppData); this.handleWorkerNotifications(); + this.handleListenerError(); } - /** - * Consumer id. - */ get id(): string { return this.#internal.consumerId; } - /** - * Associated Producer id. - */ get producerId(): string { return this.#data.producerId; } - /** - * Whether the Consumer is closed. - */ get closed(): boolean { return this.#closed; } - /** - * Media kind. - */ get kind(): MediaKind { return this.#data.kind; } - /** - * RTP parameters. - */ get rtpParameters(): RtpParameters { return this.#data.rtpParameters; } - /** - * Consumer type. - */ get type(): ConsumerType { return this.#data.type; } - /** - * Whether the Consumer is paused. - */ get paused(): boolean { return this.#paused; } - /** - * Whether the associate Producer is paused. - */ get producerPaused(): boolean { return this.#producerPaused; } - /** - * Current priority. - */ get priority(): number { return this.#priority; } - /** - * Consumer score. - */ get score(): ConsumerScore { return this.#score; } - /** - * Preferred video layers. - */ get preferredLayers(): ConsumerLayers | undefined { return this.#preferredLayers; } - /** - * Current video layers. - */ get currentLayers(): ConsumerLayers | undefined { return this.#currentLayers; } - /** - * App custom data. - */ get appData(): ConsumerAppData { return this.#appData; } - /** - * App custom data setter. - */ set appData(appData: ConsumerAppData) { this.#appData = appData; } - /** - * Observer. - */ get observer(): ConsumerObserver { return this.#observer; } /** - * @private * Just for testing purposes. + * + * @private */ get channelForTesting(): Channel { return this.#channel; } - /** - * Close the Consumer. - */ close(): void { if (this.#closed) { return; @@ -509,11 +235,6 @@ export class Consumer< this.#observer.safeEmit('close'); } - /** - * Transport was closed. - * - * @private - */ transportClosed(): void { if (this.#closed) { return; @@ -532,9 +253,6 @@ export class Consumer< this.#observer.safeEmit('close'); } - /** - * Dump Consumer. - */ async dump(): Promise { logger.debug('dump()'); @@ -553,9 +271,6 @@ export class Consumer< return parseConsumerDumpResponse(data); } - /** - * Get Consumer stats. - */ async getStats(): Promise<(ConsumerStat | ProducerStat)[]> { logger.debug('getStats()'); @@ -574,9 +289,6 @@ export class Consumer< return parseConsumerStats(data); } - /** - * Pause the Consumer. - */ async pause(): Promise { logger.debug('pause()'); @@ -597,9 +309,6 @@ export class Consumer< } } - /** - * Resume the Consumer. - */ async resume(): Promise { logger.debug('resume()'); @@ -620,9 +329,6 @@ export class Consumer< } } - /** - * Set preferred video layers. - */ async setPreferredLayers({ spatialLayer, temporalLayer, @@ -677,9 +383,6 @@ export class Consumer< this.#preferredLayers = preferredLayers; } - /** - * Set priority. - */ async setPriority(priority: number): Promise { logger.debug('setPriority()'); @@ -709,18 +412,12 @@ export class Consumer< this.#priority = status.priority; } - /** - * Unset priority. - */ async unsetPriority(): Promise { logger.debug('unsetPriority()'); await this.setPriority(1); } - /** - * Request a key frame to the Producer. - */ async requestKeyFrame(): Promise { logger.debug('requestKeyFrame()'); @@ -732,9 +429,6 @@ export class Consumer< ); } - /** - * Enable 'trace' event. - */ async enableTraceEvent(types: ConsumerTraceEventType[] = []): Promise { logger.debug('enableTraceEvent()'); @@ -905,6 +599,15 @@ export class Consumer< } ); } + + private handleListenerError(): void { + this.on('listenererror', (eventName, error) => { + logger.error( + `event listener threw an error [eventName:${eventName}]:`, + error + ); + }); + } } export function parseTraceEventData( @@ -1000,9 +703,25 @@ function parseConsumerLayers(data: FbsConsumer.ConsumerLayers): ConsumerLayers { }; } +function parseRtpStream(data: FbsRtpStream.Dump): RtpStreamDump { + const params = parseRtpStreamParameters(data.params()!); + + let rtxStream: RtxStreamDump | undefined; + + if (data.rtxStream()) { + rtxStream = parseRtxStream(data.rtxStream()!); + } + + return { + params, + score: data.score(), + rtxStream, + }; +} + function parseRtpStreamParameters( data: FbsRtpStream.Params -): RtpStreamParameters { +): RtpStreamParametersDump { return { encodingIdx: data.encodingIdx(), ssrc: data.ssrc(), @@ -1024,6 +743,14 @@ function parseRtpStreamParameters( }; } +function parseRtxStream(data: FbsRtxStream.RtxDump): RtxStreamDump { + const params = parseRtxStreamParameters(data.params()!); + + return { + params, + }; +} + function parseRtxStreamParameters( data: FbsRtxStream.Params ): RtxStreamParameters { @@ -1037,30 +764,6 @@ function parseRtxStreamParameters( }; } -function parseRtxStream(data: FbsRtxStream.RtxDump): RtxStreamDump { - const params = parseRtxStreamParameters(data.params()!); - - return { - params, - }; -} - -function parseRtpStream(data: FbsRtpStream.Dump): RtpStreamDump { - const params = parseRtpStreamParameters(data.params()!); - - let rtxStream: RtxStreamDump | undefined; - - if (data.rtxStream()) { - rtxStream = parseRtxStream(data.rtxStream()!); - } - - return { - params, - score: data.score(), - rtxStream, - }; -} - function parseBaseConsumerDump( data: FbsConsumer.BaseConsumerDump ): BaseConsumerDump { @@ -1071,18 +774,18 @@ function parseBaseConsumerDump( rtpParameters: parseRtpParameters(data.rtpParameters()!), consumableRtpEncodings: data.consumableRtpEncodingsLength() > 0 - ? utils.parseVector( + ? fbsUtils.parseVector( data, 'consumableRtpEncodings', parseRtpEncodingParameters ) : undefined, - traceEventTypes: utils.parseVector( + traceEventTypes: fbsUtils.parseVector( data, 'traceEventTypes', consumerTraceEventTypeFromFbs ), - supportedCodecPayloadTypes: utils.parseVector( + supportedCodecPayloadTypes: fbsUtils.parseVector( data, 'supportedCodecPayloadTypes' ), @@ -1136,7 +839,7 @@ function parsePipeConsumerDump( data: FbsConsumer.ConsumerDump ): PipeConsumerDump { const base = parseBaseConsumerDump(data.base()!); - const rtpStreams = utils.parseVector(data, 'rtpStreams', parseRtpStream); + const rtpStreams = fbsUtils.parseVector(data, 'rtpStreams', parseRtpStream); return { ...base, @@ -1192,5 +895,5 @@ function parseConsumerDumpResponse( function parseConsumerStats( binary: FbsConsumer.GetStatsResponse ): (ConsumerStat | ProducerStat)[] { - return utils.parseVector(binary, 'stats', parseRtpStreamStats); + return fbsUtils.parseVector(binary, 'stats', parseRtpStreamStats); } diff --git a/node/src/ConsumerTypes.ts b/node/src/ConsumerTypes.ts new file mode 100644 index 0000000000..2b1fbf7fd0 --- /dev/null +++ b/node/src/ConsumerTypes.ts @@ -0,0 +1,393 @@ +import type { EnhancedEventEmitter } from './enhancedEvents'; +import type { ProducerStat } from './ProducerTypes'; +import type { + MediaKind, + RtpCapabilities, + RtpEncodingParameters, + RtpParameters, +} from './rtpParametersTypes'; +import type { RtpStreamSendStats } from './rtpStreamStatsTypes'; +import type { AppData } from './types'; + +export type ConsumerOptions = { + /** + * The id of the Producer to consume. + */ + producerId: string; + + /** + * RTP capabilities of the consuming endpoint. + */ + rtpCapabilities: RtpCapabilities; + + /** + * Whether the consumer must start in paused mode. Default false. + * + * When creating a video Consumer, it's recommended to set paused to true, + * then transmit the Consumer parameters to the consuming endpoint and, once + * the consuming endpoint has created its local side Consumer, unpause the + * server side Consumer using the resume() method. This is an optimization + * to make it possible for the consuming endpoint to render the video as far + * as possible. If the server side Consumer was created with paused: false, + * mediasoup will immediately request a key frame to the remote Producer and + * suych a key frame may reach the consuming endpoint even before it's ready + * to consume it, generating “black” video until the device requests a keyframe + * by itself. + */ + paused?: boolean; + + /** + * The MID for the Consumer. If not specified, a sequentially growing + * number will be assigned. + */ + mid?: string; + + /** + * Preferred spatial and temporal layer for simulcast or SVC media sources. + * If unset, the highest ones are selected. + */ + preferredLayers?: ConsumerLayers; + + /** + * Whether this Consumer should enable RTP retransmissions, storing sent RTP + * and processing the incoming RTCP NACK from the remote Consumer. If not set + * it's true by default for video codecs and false for audio codecs. If set + * to true, NACK will be enabled if both endpoints (mediasoup and the remote + * Consumer) support NACK for this codec. When it comes to audio codecs, just + * OPUS supports NACK. + */ + enableRtx?: boolean; + + /** + * Whether this Consumer should ignore DTX packets (only valid for Opus codec). + * If set, DTX packets are not forwarded to the remote Consumer. + */ + ignoreDtx?: boolean; + + /** + * Whether this Consumer should consume all RTP streams generated by the + * Producer. + */ + pipe?: boolean; + + /** + * Custom application data. + */ + appData?: ConsumerAppData; +}; + +/** + * Consumer type. + */ +export type ConsumerType = 'simple' | 'simulcast' | 'svc' | 'pipe'; + +export type ConsumerScore = { + /** + * The score of the RTP stream of the consumer. + */ + score: number; + + /** + * The score of the currently selected RTP stream of the producer. + */ + producerScore: number; + + /** + * The scores of all RTP streams in the producer ordered by encoding (just + * useful when the producer uses simulcast). + */ + producerScores: number[]; +}; + +export type ConsumerLayers = { + /** + * The spatial layer index (from 0 to N). + */ + spatialLayer: number; + + /** + * The temporal layer index (from 0 to N). + */ + temporalLayer?: number; +}; + +export type ConsumerDump = + | SimpleConsumerDump + | SimulcastConsumerDump + | SvcConsumerDump + | PipeConsumerDump; + +export type SimpleConsumerDump = BaseConsumerDump & { + type: string; + rtpStream: RtpStreamDump; +}; + +export type SimulcastConsumerDump = BaseConsumerDump & { + type: string; + rtpStream: RtpStreamDump; + preferredSpatialLayer: number; + targetSpatialLayer: number; + currentSpatialLayer: number; + preferredTemporalLayer: number; + targetTemporalLayer: number; + currentTemporalLayer: number; +}; + +export type SvcConsumerDump = SimulcastConsumerDump; + +export type PipeConsumerDump = BaseConsumerDump & { + type: string; + rtpStreams: RtpStreamDump[]; +}; + +export type BaseConsumerDump = { + id: string; + producerId: string; + kind: MediaKind; + rtpParameters: RtpParameters; + consumableRtpEncodings?: RtpEncodingParameters[]; + supportedCodecPayloadTypes: number[]; + traceEventTypes: string[]; + paused: boolean; + producerPaused: boolean; + priority: number; +}; + +export type RtpStreamDump = { + params: RtpStreamParametersDump; + score: number; + rtxStream?: RtxStreamDump; +}; + +export type RtpStreamParametersDump = { + encodingIdx: number; + ssrc: number; + payloadType: number; + mimeType: string; + clockRate: number; + rid?: string; + cname: string; + rtxSsrc?: number; + rtxPayloadType?: number; + useNack: boolean; + usePli: boolean; + useFir: boolean; + useInBandFec: boolean; + useDtx: boolean; + spatialLayers: number; + temporalLayers: number; +}; + +export type RtxStreamDump = { + params: RtxStreamParameters; +}; + +export type RtxStreamParameters = { + ssrc: number; + payloadType: number; + mimeType: string; + clockRate: number; + rrid?: string; + cname: string; +}; + +export type ConsumerStat = RtpStreamSendStats; + +/** + * Valid types for 'trace' event. + */ +export type ConsumerTraceEventType = + | 'rtp' + | 'keyframe' + | 'nack' + | 'pli' + | 'fir'; + +/** + * 'trace' event data. + */ +export type ConsumerTraceEventData = { + /** + * Trace type. + */ + type: ConsumerTraceEventType; + + /** + * Event timestamp. + */ + timestamp: number; + + /** + * Event direction. + */ + direction: 'in' | 'out'; + + /** + * Per type information. + */ + info: any; +}; + +export type ConsumerEvents = { + transportclose: []; + producerclose: []; + producerpause: []; + producerresume: []; + score: [ConsumerScore]; + layerschange: [ConsumerLayers?]; + trace: [ConsumerTraceEventData]; + rtp: [Buffer]; + listenererror: [string, Error]; + // Private events. + '@close': []; + '@producerclose': []; +}; + +export type ConsumerObserver = EnhancedEventEmitter; + +export type ConsumerObserverEvents = { + close: []; + pause: []; + resume: []; + score: [ConsumerScore]; + layerschange: [ConsumerLayers?]; + trace: [ConsumerTraceEventData]; +}; + +export interface Consumer + extends EnhancedEventEmitter { + /** + * Consumer id. + */ + get id(): string; + + /** + * Associated Producer id. + */ + get producerId(): string; + + /** + * Whether the Consumer is closed. + */ + get closed(): boolean; + + /** + * Media kind. + */ + get kind(): MediaKind; + + /** + * RTP parameters. + */ + get rtpParameters(): RtpParameters; + + /** + * Consumer type. + */ + get type(): ConsumerType; + + /** + * Whether the Consumer is paused. + */ + get paused(): boolean; + + /** + * Whether the associate Producer is paused. + */ + get producerPaused(): boolean; + + /** + * Current priority. + */ + get priority(): number; + + /** + * Consumer score. + */ + get score(): ConsumerScore; + + /** + * Preferred video layers. + */ + get preferredLayers(): ConsumerLayers | undefined; + + /** + * Current video layers. + */ + get currentLayers(): ConsumerLayers | undefined; + + /** + * App custom data. + */ + get appData(): ConsumerAppData; + + /** + * App custom data setter. + */ + set appData(appData: ConsumerAppData); + + /** + * Observer. + */ + get observer(): ConsumerObserver; + + /** + * Close the Consumer. + */ + close(): void; + + /** + * Transport was closed. + * + * @private + */ + transportClosed(): void; + + /** + * Dump Consumer. + */ + dump(): Promise; + + /** + * Get Consumer stats. + */ + getStats(): Promise<(ConsumerStat | ProducerStat)[]>; + + /** + * Pause the Consumer. + */ + pause(): Promise; + + /** + * Resume the Consumer. + */ + resume(): Promise; + + /** + * Set preferred video layers. + */ + setPreferredLayers({ + spatialLayer, + temporalLayer, + }: ConsumerLayers): Promise; + + /** + * Set priority. + */ + setPriority(priority: number): Promise; + + /** + * Unset priority. + */ + unsetPriority(): Promise; + + /** + * Request a key frame to the Producer. + */ + requestKeyFrame(): Promise; + + /** + * Enable 'trace' event. + */ + enableTraceEvent(types?: ConsumerTraceEventType[]): Promise; +} diff --git a/node/src/DataConsumer.ts b/node/src/DataConsumer.ts index f05b998d17..3bcc932069 100644 --- a/node/src/DataConsumer.ts +++ b/node/src/DataConsumer.ts @@ -1,113 +1,26 @@ import { Logger } from './Logger'; import { EnhancedEventEmitter } from './enhancedEvents'; +import type { + DataConsumer, + DataConsumerType, + DataConsumerDump, + DataConsumerStat, + DataConsumerEvents, + DataConsumerObserver, + DataConsumerObserverEvents, +} from './DataConsumerTypes'; import { Channel } from './Channel'; -import { TransportInternal } from './Transport'; -import { - SctpStreamParameters, - parseSctpStreamParameters, -} from './SctpParameters'; -import { AppData } from './types'; -import * as utils from './utils'; +import type { TransportInternal } from './Transport'; +import type { SctpStreamParameters } from './sctpParametersTypes'; +import { parseSctpStreamParameters } from './sctpParametersFbsUtils'; +import type { AppData } from './types'; +import * as fbsUtils from './fbsUtils'; import { Event, Notification } from './fbs/notification'; import * as FbsTransport from './fbs/transport'; import * as FbsRequest from './fbs/request'; import * as FbsDataConsumer from './fbs/data-consumer'; import * as FbsDataProducer from './fbs/data-producer'; -export type DataConsumerOptions = - { - /** - * The id of the DataProducer to consume. - */ - dataProducerId: string; - - /** - * Just if consuming over SCTP. - * Whether data messages must be received in order. If true the messages will - * be sent reliably. Defaults to the value in the DataProducer if it has type - * 'sctp' or to true if it has type 'direct'. - */ - ordered?: boolean; - - /** - * Just if consuming over SCTP. - * When ordered is false indicates the time (in milliseconds) after which a - * SCTP packet will stop being retransmitted. Defaults to the value in the - * DataProducer if it has type 'sctp' or unset if it has type 'direct'. - */ - maxPacketLifeTime?: number; - - /** - * Just if consuming over SCTP. - * When ordered is false indicates the maximum number of times a packet will - * be retransmitted. Defaults to the value in the DataProducer if it has type - * 'sctp' or unset if it has type 'direct'. - */ - maxRetransmits?: number; - - /** - * Whether the data consumer must start in paused mode. Default false. - */ - paused?: boolean; - - /** - * Subchannels this data consumer initially subscribes to. - * Only used in case this data consumer receives messages from a local data - * producer that specifies subchannel(s) when calling send(). - */ - subchannels?: number[]; - - /** - * Custom application data. - */ - appData?: DataConsumerAppData; - }; - -export type DataConsumerStat = { - type: string; - timestamp: number; - label: string; - protocol: string; - messagesSent: number; - bytesSent: number; - bufferedAmount: number; -}; - -/** - * DataConsumer type. - */ -export type DataConsumerType = 'sctp' | 'direct'; - -export type DataConsumerEvents = { - transportclose: []; - dataproducerclose: []; - dataproducerpause: []; - dataproducerresume: []; - message: [Buffer, number]; - sctpsendbufferfull: []; - bufferedamountlow: [number]; - listenererror: [string, Error]; - // Private events. - '@close': []; - '@dataproducerclose': []; -}; - -export type DataConsumerObserver = - EnhancedEventEmitter; - -export type DataConsumerObserverEvents = { - close: []; - pause: []; - resume: []; -}; - -type DataConsumerDump = DataConsumerData & { - id: string; - paused: boolean; - dataProducerPaused: boolean; - subchannels: number[]; -}; - type DataConsumerInternal = TransportInternal & { dataConsumerId: string; }; @@ -123,9 +36,10 @@ type DataConsumerData = { const logger = new Logger('DataConsumer'); -export class DataConsumer< - DataConsumerAppData extends AppData = AppData, -> extends EnhancedEventEmitter { +export class DataConsumerImpl + extends EnhancedEventEmitter + implements DataConsumer +{ // Internal data. readonly #internal: DataConsumerInternal; @@ -154,9 +68,6 @@ export class DataConsumer< readonly #observer: DataConsumerObserver = new EnhancedEventEmitter(); - /** - * @private - */ constructor({ internal, data, @@ -187,102 +98,61 @@ export class DataConsumer< this.#appData = appData ?? ({} as DataConsumerAppData); this.handleWorkerNotifications(); + this.handleListenerError(); } - /** - * DataConsumer id. - */ get id(): string { return this.#internal.dataConsumerId; } - /** - * Associated DataProducer id. - */ get dataProducerId(): string { return this.#data.dataProducerId; } - /** - * Whether the DataConsumer is closed. - */ get closed(): boolean { return this.#closed; } - /** - * DataConsumer type. - */ get type(): DataConsumerType { return this.#data.type; } - /** - * SCTP stream parameters. - */ get sctpStreamParameters(): SctpStreamParameters | undefined { return this.#data.sctpStreamParameters; } - /** - * DataChannel label. - */ get label(): string { return this.#data.label; } - /** - * DataChannel protocol. - */ get protocol(): string { return this.#data.protocol; } - /** - * Whether the DataConsumer is paused. - */ get paused(): boolean { return this.#paused; } - /** - * Whether the associate DataProducer is paused. - */ get dataProducerPaused(): boolean { return this.#dataProducerPaused; } - /** - * Get current subchannels this data consumer is subscribed to. - */ get subchannels(): number[] { return Array.from(this.#subchannels); } - /** - * App custom data. - */ get appData(): DataConsumerAppData { return this.#appData; } - /** - * App custom data setter. - */ set appData(appData: DataConsumerAppData) { this.#appData = appData; } - /** - * Observer. - */ get observer(): DataConsumerObserver { return this.#observer; } - /** - * Close the DataConsumer. - */ close(): void { if (this.#closed) { return; @@ -315,11 +185,6 @@ export class DataConsumer< this.#observer.safeEmit('close'); } - /** - * Transport was closed. - * - * @private - */ transportClosed(): void { if (this.#closed) { return; @@ -338,9 +203,6 @@ export class DataConsumer< this.#observer.safeEmit('close'); } - /** - * Dump DataConsumer. - */ async dump(): Promise { logger.debug('dump()'); @@ -359,9 +221,6 @@ export class DataConsumer< return parseDataConsumerDumpResponse(dumpResponse); } - /** - * Get DataConsumer stats. - */ async getStats(): Promise { logger.debug('getStats()'); @@ -380,9 +239,6 @@ export class DataConsumer< return [parseDataConsumerStats(data)]; } - /** - * Pause the DataConsumer. - */ async pause(): Promise { logger.debug('pause()'); @@ -403,9 +259,6 @@ export class DataConsumer< } } - /** - * Resume the DataConsumer. - */ async resume(): Promise { logger.debug('resume()'); @@ -426,9 +279,6 @@ export class DataConsumer< } } - /** - * Set buffered amount low threshold. - */ async setBufferedAmountLowThreshold(threshold: number): Promise { logger.debug(`setBufferedAmountLowThreshold() [threshold:${threshold}]`); @@ -447,9 +297,23 @@ export class DataConsumer< ); } - /** - * Send data. - */ + async getBufferedAmount(): Promise { + 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: string | Buffer, ppid?: number): Promise { if (typeof message !== 'string' && !Buffer.isBuffer(message)) { throw new TypeError('message must be a string or a Buffer'); @@ -513,29 +377,6 @@ export class DataConsumer< ); } - /** - * Get buffered amount size. - */ - async getBufferedAmount(): Promise { - 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(); - } - - /** - * Set subchannels. - */ async setSubchannels(subchannels: number[]): Promise { logger.debug('setSubchannels()'); @@ -557,12 +398,9 @@ export class DataConsumer< response.body(data); // Update subchannels. - this.#subchannels = utils.parseVector(data, 'subchannels'); + this.#subchannels = fbsUtils.parseVector(data, 'subchannels'); } - /** - * Add a subchannel. - */ async addSubchannel(subchannel: number): Promise { logger.debug('addSubchannel()'); @@ -586,12 +424,9 @@ export class DataConsumer< response.body(data); // Update subchannels. - this.#subchannels = utils.parseVector(data, 'subchannels'); + this.#subchannels = fbsUtils.parseVector(data, 'subchannels'); } - /** - * Remove a subchannel. - */ async removeSubchannel(subchannel: number): Promise { logger.debug('removeSubchannel()'); @@ -615,7 +450,7 @@ export class DataConsumer< response.body(data); // Update subchannels. - this.#subchannels = utils.parseVector(data, 'subchannels'); + this.#subchannels = fbsUtils.parseVector(data, 'subchannels'); } private handleWorkerNotifications(): void { @@ -720,6 +555,15 @@ export class DataConsumer< } ); } + + private handleListenerError(): void { + this.on('listenererror', (eventName, error) => { + logger.error( + `event listener threw an error [eventName:${eventName}]:`, + error + ); + }); + } } export function dataConsumerTypeToFbs( @@ -770,7 +614,7 @@ export function parseDataConsumerDumpResponse( bufferedAmountLowThreshold: data.bufferedAmountLowThreshold(), paused: data.paused(), dataProducerPaused: data.dataProducerPaused(), - subchannels: utils.parseVector(data, 'subchannels'), + subchannels: fbsUtils.parseVector(data, 'subchannels'), }; } diff --git a/node/src/DataConsumerTypes.ts b/node/src/DataConsumerTypes.ts new file mode 100644 index 0000000000..5f0d06815c --- /dev/null +++ b/node/src/DataConsumerTypes.ts @@ -0,0 +1,233 @@ +import type { EnhancedEventEmitter } from './enhancedEvents'; +import type { SctpStreamParameters } from './sctpParametersTypes'; +import type { AppData } from './types'; + +export type DataConsumerOptions = + { + /** + * The id of the DataProducer to consume. + */ + dataProducerId: string; + + /** + * Just if consuming over SCTP. + * Whether data messages must be received in order. If true the messages will + * be sent reliably. Defaults to the value in the DataProducer if it has type + * 'sctp' or to true if it has type 'direct'. + */ + ordered?: boolean; + + /** + * Just if consuming over SCTP. + * When ordered is false indicates the time (in milliseconds) after which a + * SCTP packet will stop being retransmitted. Defaults to the value in the + * DataProducer if it has type 'sctp' or unset if it has type 'direct'. + */ + maxPacketLifeTime?: number; + + /** + * Just if consuming over SCTP. + * When ordered is false indicates the maximum number of times a packet will + * be retransmitted. Defaults to the value in the DataProducer if it has type + * 'sctp' or unset if it has type 'direct'. + */ + maxRetransmits?: number; + + /** + * Whether the data consumer must start in paused mode. Default false. + */ + paused?: boolean; + + /** + * Subchannels this data consumer initially subscribes to. + * Only used in case this data consumer receives messages from a local data + * producer that specifies subchannel(s) when calling send(). + */ + subchannels?: number[]; + + /** + * Custom application data. + */ + appData?: DataConsumerAppData; + }; + +/** + * DataConsumer type. + */ +export type DataConsumerType = 'sctp' | 'direct'; + +export type DataConsumerDump = { + id: string; + paused: boolean; + dataProducerPaused: boolean; + subchannels: number[]; + dataProducerId: string; + type: DataConsumerType; + sctpStreamParameters?: SctpStreamParameters; + label: string; + protocol: string; + bufferedAmountLowThreshold: number; +}; + +export type DataConsumerStat = { + type: string; + timestamp: number; + label: string; + protocol: string; + messagesSent: number; + bytesSent: number; + bufferedAmount: number; +}; + +export type DataConsumerEvents = { + transportclose: []; + dataproducerclose: []; + dataproducerpause: []; + dataproducerresume: []; + message: [Buffer, number]; + sctpsendbufferfull: []; + bufferedamountlow: [number]; + listenererror: [string, Error]; + // Private events. + '@close': []; + '@dataproducerclose': []; +}; + +export type DataConsumerObserver = + EnhancedEventEmitter; + +export type DataConsumerObserverEvents = { + close: []; + pause: []; + resume: []; +}; + +export interface DataConsumer + extends EnhancedEventEmitter { + /** + * DataConsumer id. + */ + get id(): string; + + /** + * Associated DataProducer id. + */ + get dataProducerId(): string; + + /** + * Whether the DataConsumer is closed. + */ + get closed(): boolean; + + /** + * DataConsumer type. + */ + get type(): DataConsumerType; + + /** + * SCTP stream parameters. + */ + get sctpStreamParameters(): SctpStreamParameters | undefined; + + /** + * DataChannel label. + */ + get label(): string; + + /** + * DataChannel protocol. + */ + get protocol(): string; + + /** + * Whether the DataConsumer is paused. + */ + get paused(): boolean; + + /** + * Whether the associate DataProducer is paused. + */ + get dataProducerPaused(): boolean; + + /** + * Get current subchannels this data consumer is subscribed to. + */ + get subchannels(): number[]; + + /** + * App custom data. + */ + get appData(): DataConsumerAppData; + + /** + * App custom data setter. + */ + set appData(appData: DataConsumerAppData); + + /** + * Observer. + */ + get observer(): DataConsumerObserver; + + /** + * Close the DataConsumer. + */ + close(): void; + + /** + * Transport was closed. + * + * @private + */ + transportClosed(): void; + + /** + * Dump DataConsumer. + */ + dump(): Promise; + + /** + * Get DataConsumer stats. + */ + getStats(): Promise; + + /** + * Pause the DataConsumer. + */ + pause(): Promise; + + /** + * Resume the DataConsumer. + */ + resume(): Promise; + + /** + * Set buffered amount low threshold. + */ + setBufferedAmountLowThreshold(threshold: number): Promise; + + /** + * Get buffered amount size. + */ + getBufferedAmount(): Promise; + + /** + * Send a message. + */ + send(message: string | Buffer, ppid?: number): Promise; + + /** + * Set subchannels. + */ + setSubchannels(subchannels: number[]): Promise; + + /** + * Add a subchannel. + */ + addSubchannel(subchannel: number): Promise; + + /** + * Remove a subchannel. + */ + removeSubchannel(subchannel: number): Promise; +} diff --git a/node/src/DataProducer.ts b/node/src/DataProducer.ts index e4ac42884e..e1c2c0f6e1 100644 --- a/node/src/DataProducer.ts +++ b/node/src/DataProducer.ts @@ -1,86 +1,24 @@ import { Logger } from './Logger'; import { EnhancedEventEmitter } from './enhancedEvents'; +import type { + DataProducer, + DataProducerType, + DataProducerDump, + DataProducerStat, + DataProducerEvents, + DataProducerObserver, + DataProducerObserverEvents, +} from './DataProducerTypes'; import { Channel } from './Channel'; -import { TransportInternal } from './Transport'; -import { - SctpStreamParameters, - parseSctpStreamParameters, -} from './SctpParameters'; -import { AppData } from './types'; +import type { TransportInternal } from './Transport'; +import type { SctpStreamParameters } from './sctpParametersTypes'; +import { parseSctpStreamParameters } from './sctpParametersFbsUtils'; +import type { AppData } from './types'; import * as FbsTransport from './fbs/transport'; import * as FbsNotification from './fbs/notification'; import * as FbsRequest from './fbs/request'; import * as FbsDataProducer from './fbs/data-producer'; -export type DataProducerOptions = - { - /** - * DataProducer id (just for Router.pipeToRouter() method). - */ - id?: string; - - /** - * SCTP parameters defining how the endpoint is sending the data. - * Just if messages are sent over SCTP. - */ - sctpStreamParameters?: SctpStreamParameters; - - /** - * A label which can be used to distinguish this DataChannel from others. - */ - label?: string; - - /** - * Name of the sub-protocol used by this DataChannel. - */ - protocol?: string; - - /** - * Whether the data producer must start in paused mode. Default false. - */ - paused?: boolean; - - /** - * Custom application data. - */ - appData?: DataProducerAppData; - }; - -export type DataProducerStat = { - type: string; - timestamp: number; - label: string; - protocol: string; - messagesReceived: number; - bytesReceived: number; -}; - -/** - * DataProducer type. - */ -export type DataProducerType = 'sctp' | 'direct'; - -export type DataProducerEvents = { - transportclose: []; - listenererror: [string, Error]; - // Private events. - '@close': []; -}; - -export type DataProducerObserver = - EnhancedEventEmitter; - -export type DataProducerObserverEvents = { - close: []; - pause: []; - resume: []; -}; - -type DataProducerDump = DataProducerData & { - id: string; - paused: boolean; -}; - type DataProducerInternal = TransportInternal & { dataProducerId: string; }; @@ -94,9 +32,10 @@ type DataProducerData = { const logger = new Logger('DataProducer'); -export class DataProducer< - DataProducerAppData extends AppData = AppData, -> extends EnhancedEventEmitter { +export class DataProducerImpl + extends EnhancedEventEmitter + implements DataProducer +{ // Internal data. readonly #internal: DataProducerInternal; @@ -119,9 +58,6 @@ export class DataProducer< readonly #observer: DataProducerObserver = new EnhancedEventEmitter(); - /** - * @private - */ constructor({ internal, data, @@ -146,81 +82,49 @@ export class DataProducer< this.#appData = appData ?? ({} as DataProducerAppData); this.handleWorkerNotifications(); + this.handleListenerError(); } - /** - * DataProducer id. - */ get id(): string { return this.#internal.dataProducerId; } - /** - * Whether the DataProducer is closed. - */ get closed(): boolean { return this.#closed; } - /** - * DataProducer type. - */ get type(): DataProducerType { return this.#data.type; } - /** - * SCTP stream parameters. - */ get sctpStreamParameters(): SctpStreamParameters | undefined { return this.#data.sctpStreamParameters; } - /** - * DataChannel label. - */ get label(): string { return this.#data.label; } - /** - * DataChannel protocol. - */ get protocol(): string { return this.#data.protocol; } - /** - * Whether the DataProducer is paused. - */ get paused(): boolean { return this.#paused; } - /** - * App custom data. - */ get appData(): DataProducerAppData { return this.#appData; } - /** - * App custom data setter. - */ set appData(appData: DataProducerAppData) { this.#appData = appData; } - /** - * Observer. - */ get observer(): DataProducerObserver { return this.#observer; } - /** - * Close the DataProducer. - */ close(): void { if (this.#closed) { return; @@ -253,11 +157,6 @@ export class DataProducer< this.#observer.safeEmit('close'); } - /** - * Transport was closed. - * - * @private - */ transportClosed(): void { if (this.#closed) { return; @@ -276,9 +175,6 @@ export class DataProducer< this.#observer.safeEmit('close'); } - /** - * Dump DataProducer. - */ async dump(): Promise { logger.debug('dump()'); @@ -297,9 +193,6 @@ export class DataProducer< return parseDataProducerDumpResponse(produceResponse); } - /** - * Get DataProducer stats. - */ async getStats(): Promise { logger.debug('getStats()'); @@ -318,9 +211,6 @@ export class DataProducer< return [parseDataProducerStats(data)]; } - /** - * Pause the DataProducer. - */ async pause(): Promise { logger.debug('pause()'); @@ -341,9 +231,6 @@ export class DataProducer< } } - /** - * Resume the DataProducer. - */ async resume(): Promise { logger.debug('resume()'); @@ -364,9 +251,6 @@ export class DataProducer< } } - /** - * Send data (just valid for DataProducers created on a DirectTransport). - */ send( message: string | Buffer, ppid?: number, @@ -450,6 +334,15 @@ export class DataProducer< private handleWorkerNotifications(): void { // No need to subscribe to any event. } + + private handleListenerError(): void { + this.on('listenererror', (eventName, error) => { + logger.error( + `event listener threw an error [eventName:${eventName}]:`, + error + ); + }); + } } export function dataProducerTypeToFbs( diff --git a/node/src/DataProducerTypes.ts b/node/src/DataProducerTypes.ts new file mode 100644 index 0000000000..efb64d35bf --- /dev/null +++ b/node/src/DataProducerTypes.ts @@ -0,0 +1,171 @@ +import type { EnhancedEventEmitter } from './enhancedEvents'; +import type { SctpStreamParameters } from './sctpParametersTypes'; +import type { AppData } from './types'; + +export type DataProducerOptions = + { + /** + * DataProducer id (just for Router.pipeToRouter() method). + */ + id?: string; + + /** + * SCTP parameters defining how the endpoint is sending the data. + * Just if messages are sent over SCTP. + */ + sctpStreamParameters?: SctpStreamParameters; + + /** + * A label which can be used to distinguish this DataChannel from others. + */ + label?: string; + + /** + * Name of the sub-protocol used by this DataChannel. + */ + protocol?: string; + + /** + * Whether the data producer must start in paused mode. Default false. + */ + paused?: boolean; + + /** + * Custom application data. + */ + appData?: DataProducerAppData; + }; + +/** + * DataProducer type. + */ +export type DataProducerType = 'sctp' | 'direct'; + +export type DataProducerDump = { + id: string; + paused: boolean; + type: DataProducerType; + sctpStreamParameters?: SctpStreamParameters; + label: string; + protocol: string; +}; + +export type DataProducerStat = { + type: string; + timestamp: number; + label: string; + protocol: string; + messagesReceived: number; + bytesReceived: number; +}; + +export type DataProducerEvents = { + transportclose: []; + listenererror: [string, Error]; + // Private events. + '@close': []; +}; + +export type DataProducerObserver = + EnhancedEventEmitter; + +export type DataProducerObserverEvents = { + close: []; + pause: []; + resume: []; +}; + +export interface DataProducer + extends EnhancedEventEmitter { + /** + * DataProducer id. + */ + get id(): string; + + /** + * Whether the DataProducer is closed. + */ + get closed(): boolean; + + /** + * DataProducer type. + */ + get type(): DataProducerType; + + /** + * SCTP stream parameters. + */ + get sctpStreamParameters(): SctpStreamParameters | undefined; + + /** + * DataChannel label. + */ + get label(): string; + + /** + * DataChannel protocol. + */ + get protocol(): string; + + /** + * Whether the DataProducer is paused. + */ + get paused(): boolean; + + /** + * App custom data. + */ + get appData(): DataProducerAppData; + + /** + * App custom data setter. + */ + set appData(appData: DataProducerAppData); + + /** + * Observer. + */ + get observer(): DataProducerObserver; + + /** + * Close the DataProducer. + */ + close(): void; + + /** + * Transport was closed. + * + * @private + */ + transportClosed(): void; + + /** + * Dump DataProducer. + */ + dump(): Promise; + + /** + * Get DataProducer stats. + */ + getStats(): Promise; + + /** + * Pause the DataProducer. + */ + pause(): Promise; + + /** + * Resume the DataProducer. + */ + resume(): Promise; + + /** + * Send data (just valid for DataProducers created on a DirectTransport). + */ + send( + message: string | Buffer, + ppid?: number, + subchannels?: number[], + requiredSubchannel?: number + ): void; +} diff --git a/node/src/DirectTransport.ts b/node/src/DirectTransport.ts index 044dd2d443..a75d4b18bb 100644 --- a/node/src/DirectTransport.ts +++ b/node/src/DirectTransport.ts @@ -1,57 +1,30 @@ import { Logger } from './Logger'; import { EnhancedEventEmitter } from './enhancedEvents'; -import { UnsupportedError } from './errors'; +import type { + DirectTransport, + DirectTransportDump, + DirectTransportStat, + DirectTransportEvents, + DirectTransportObserver, + DirectTransportObserverEvents, +} from './DirectTransportTypes'; +import type { Transport, BaseTransportDump } from './TransportTypes'; import { - BaseTransportDump, - BaseTransportStats, + TransportImpl, + TransportConstructorOptions, parseBaseTransportDump, parseBaseTransportStats, parseTransportTraceEventData, - Transport, - TransportEvents, - TransportObserverEvents, - TransportConstructorOptions, } from './Transport'; -import { SctpParameters } from './SctpParameters'; -import { AppData } from './types'; +import type { SctpParameters } from './sctpParametersTypes'; +import type { AppData } from './types'; +import { UnsupportedError } from './errors'; import { Event, Notification } from './fbs/notification'; import * as FbsDirectTransport from './fbs/direct-transport'; import * as FbsTransport from './fbs/transport'; import * as FbsNotification from './fbs/notification'; import * as FbsRequest from './fbs/request'; -export type DirectTransportOptions< - DirectTransportAppData extends AppData = AppData, -> = { - /** - * Maximum allowed size for direct messages sent from DataProducers. - * Default 262144. - */ - maxMessageSize: number; - - /** - * Custom application data. - */ - appData?: DirectTransportAppData; -}; - -export type DirectTransportDump = BaseTransportDump; - -export type DirectTransportStat = BaseTransportStats & { - type: string; -}; - -export type DirectTransportEvents = TransportEvents & { - rtcp: [Buffer]; -}; - -export type DirectTransportObserver = - EnhancedEventEmitter; - -export type DirectTransportObserverEvents = TransportObserverEvents & { - rtcp: [Buffer]; -}; - type DirectTransportConstructorOptions = TransportConstructorOptions & { data: DirectTransportData; @@ -63,20 +36,20 @@ export type DirectTransportData = { const logger = new Logger('DirectTransport'); -export class DirectTransport< - DirectTransportAppData extends AppData = AppData, -> extends Transport< - DirectTransportAppData, - DirectTransportEvents, - DirectTransportObserver -> { +export class DirectTransportImpl< + DirectTransportAppData extends AppData = AppData, + > + extends TransportImpl< + DirectTransportAppData, + DirectTransportEvents, + DirectTransportObserver + > + implements Transport, DirectTransport +{ // DirectTransport data. // eslint-disable-next-line no-unused-private-class-members readonly #data: DirectTransportData; - /** - * @private - */ constructor( options: DirectTransportConstructorOptions ) { @@ -92,22 +65,17 @@ export class DirectTransport< }; this.handleWorkerNotifications(); + this.handleListenerError(); + } + + get type(): 'direct' { + return 'direct'; } - /** - * Observer. - * - * @override - */ get observer(): DirectTransportObserver { return super.observer; } - /** - * Close the DirectTransport. - * - * @override - */ close(): void { if (this.closed) { return; @@ -116,12 +84,6 @@ export class DirectTransport< super.close(); } - /** - * Router was closed. - * - * @private - * @override - */ routerClosed(): void { if (this.closed) { return; @@ -130,9 +92,6 @@ export class DirectTransport< super.routerClosed(); } - /** - * Dump Transport. - */ async dump(): Promise { logger.debug('dump()'); @@ -151,11 +110,6 @@ export class DirectTransport< return parseDirectTransportDumpResponse(data); } - /** - * Get DirectTransport stats. - * - * @override - */ async getStats(): Promise { logger.debug('getStats()'); @@ -174,19 +128,11 @@ export class DirectTransport< return [parseGetStatsResponse(data)]; } - /** - * NO-OP method in DirectTransport. - * - * @override - */ // eslint-disable-next-line @typescript-eslint/require-await async connect(): Promise { logger.debug('connect()'); } - /** - * @override - */ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/require-await async setMaxIncomingBitrate(bitrate: number): Promise { throw new UnsupportedError( @@ -194,9 +140,6 @@ export class DirectTransport< ); } - /** - * @override - */ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/require-await async setMaxOutgoingBitrate(bitrate: number): Promise { throw new UnsupportedError( @@ -204,9 +147,6 @@ export class DirectTransport< ); } - /** - * @override - */ // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/require-await async setMinOutgoingBitrate(bitrate: number): Promise { throw new UnsupportedError( @@ -214,9 +154,6 @@ export class DirectTransport< ); } - /** - * Send RTCP packet. - */ sendRtcp(rtcpPacket: Buffer): void { if (!Buffer.isBuffer(rtcpPacket)) { throw new TypeError('rtcpPacket must be a Buffer'); @@ -282,6 +219,15 @@ export class DirectTransport< } ); } + + private handleListenerError(): void { + this.on('listenererror', (eventName, error) => { + logger.error( + `event listener threw an error [eventName:${eventName}]:`, + error + ); + }); + } } export function parseDirectTransportDumpResponse( diff --git a/node/src/DirectTransportTypes.ts b/node/src/DirectTransportTypes.ts new file mode 100644 index 0000000000..85bb824341 --- /dev/null +++ b/node/src/DirectTransportTypes.ts @@ -0,0 +1,89 @@ +import type { EnhancedEventEmitter } from './enhancedEvents'; +import type { + Transport, + BaseTransportDump, + BaseTransportStats, + TransportEvents, + TransportObserverEvents, +} from './TransportTypes'; +import type { AppData } from './types'; + +export type DirectTransportOptions< + DirectTransportAppData extends AppData = AppData, +> = { + /** + * Maximum allowed size for direct messages sent from DataProducers. + * Default 262144. + */ + maxMessageSize: number; + + /** + * Custom application data. + */ + appData?: DirectTransportAppData; +}; + +export type DirectTransportDump = BaseTransportDump; + +export type DirectTransportStat = BaseTransportStats & { + type: string; +}; + +export type DirectTransportEvents = TransportEvents & { + rtcp: [Buffer]; +}; + +export type DirectTransportObserver = + EnhancedEventEmitter; + +export type DirectTransportObserverEvents = TransportObserverEvents & { + rtcp: [Buffer]; +}; + +export interface DirectTransport< + DirectTransportAppData extends AppData = AppData, +> extends Transport< + DirectTransportAppData, + DirectTransportEvents, + DirectTransportObserver + > { + /** + * Transport type. + * + * @override + */ + get type(): 'direct'; + + /** + * Observer. + * + * @override + */ + get observer(): DirectTransportObserver; + + /** + * Dump DirectTransport. + * + * @override + */ + dump(): Promise; + + /** + * Get DirectTransport stats. + * + * @override + */ + getStats(): Promise; + + /** + * NO-OP method in DirectTransport. + * + * @override + */ + connect(): Promise; + + /** + * Send RTCP packet. + */ + sendRtcp(rtcpPacket: Buffer): void; +} diff --git a/node/src/Logger.ts b/node/src/Logger.ts index 66b5456c0c..1dd0b30a75 100644 --- a/node/src/Logger.ts +++ b/node/src/Logger.ts @@ -1,5 +1,5 @@ import debug from 'debug'; -import { EnhancedEventEmitter } from './enhancedEvents'; +import type { EnhancedEventEmitter } from './enhancedEvents'; const APP_NAME = 'mediasoup'; diff --git a/node/src/PipeTransport.ts b/node/src/PipeTransport.ts index d15dd5025a..266c0d50d4 100644 --- a/node/src/PipeTransport.ts +++ b/node/src/PipeTransport.ts @@ -2,37 +2,40 @@ import * as flatbuffers from 'flatbuffers'; import { Logger } from './Logger'; import { EnhancedEventEmitter } from './enhancedEvents'; import * as ortc from './ortc'; +import type { + PipeTransport, + PipeConsumerOptions, + PipeTransportDump, + PipeTransportStat, + PipeTransportEvents, + PipeTransportObserver, + PipeTransportObserverEvents, +} from './PipeTransportTypes'; +import type { Transport, TransportTuple, SctpState } from './TransportTypes'; import { - BaseTransportDump, - BaseTransportStats, + TransportImpl, + TransportConstructorOptions, parseBaseTransportDump, parseBaseTransportStats, parseSctpState, parseTuple, parseTransportTraceEventData, - Transport, - TransportListenInfo, - TransportListenIp, - TransportTuple, - TransportEvents, - TransportObserverEvents, - TransportConstructorOptions, - SctpState, } from './Transport'; -import { Consumer, ConsumerType } from './Consumer'; -import { Producer } from './Producer'; +import type { Producer } from './ProducerTypes'; +import type { Consumer, ConsumerType } from './ConsumerTypes'; +import { ConsumerImpl } from './Consumer'; +import type { RtpParameters } from './rtpParametersTypes'; import { - RtpParameters, serializeRtpEncodingParameters, serializeRtpParameters, -} from './RtpParameters'; -import { SctpParameters, NumSctpStreams } from './SctpParameters'; +} from './rtpParametersFbsUtils'; +import type { SctpParameters } from './sctpParametersTypes'; +import type { SrtpParameters } from './srtpParametersTypes'; import { parseSrtpParameters, serializeSrtpParameters, - SrtpParameters, -} from './SrtpParameters'; -import { AppData, Either } from './types'; +} from './srtpParametersFbsUtils'; +import type { AppData } from './types'; import { generateUUIDv4 } from './utils'; import { MediaKind as FbsMediaKind } from './fbs/rtp-parameters/media-kind'; import * as FbsRtpParameters from './fbs/rtp-parameters'; @@ -41,104 +44,6 @@ import * as FbsRequest from './fbs/request'; import * as FbsTransport from './fbs/transport'; import * as FbsPipeTransport from './fbs/pipe-transport'; -type PipeTransportListenInfo = { - /** - * Listening info. - */ - listenInfo: TransportListenInfo; -}; - -type PipeTransportListenIp = { - /** - * Listening IP address. - */ - listenIp: TransportListenIp | string; - - /** - * Fixed port to listen on instead of selecting automatically from Worker's port - * range. - */ - port?: number; -}; - -type PipeTransportListen = Either< - PipeTransportListenInfo, - PipeTransportListenIp ->; - -export type PipeTransportOptions< - PipeTransportAppData extends AppData = AppData, -> = { - /** - * Create a SCTP association. Default false. - */ - enableSctp?: boolean; - - /** - * SCTP streams number. - */ - numSctpStreams?: NumSctpStreams; - - /** - * Maximum allowed size for SCTP messages sent by DataProducers. - * Default 268435456. - */ - maxSctpMessageSize?: number; - - /** - * Maximum SCTP send buffer used by DataConsumers. - * Default 268435456. - */ - sctpSendBufferSize?: number; - - /** - * Enable RTX and NACK for RTP retransmission. Useful if both Routers are - * located in different hosts and there is packet lost in the link. For this - * to work, both PipeTransports must enable this setting. Default false. - */ - enableRtx?: boolean; - - /** - * Enable SRTP. Useful to protect the RTP and RTCP traffic if both Routers - * are located in different hosts. For this to work, connect() must be called - * with remote SRTP parameters. Default false. - */ - enableSrtp?: boolean; - - /** - * Custom application data. - */ - appData?: PipeTransportAppData; -} & PipeTransportListen; - -export type PipeTransportStat = BaseTransportStats & { - type: string; - tuple: TransportTuple; -}; - -export type PipeConsumerOptions = { - /** - * The id of the Producer to consume. - */ - producerId: string; - - /** - * Custom application data. - */ - appData?: ConsumerAppData; -}; - -export type PipeTransportEvents = TransportEvents & { - sctpstatechange: [SctpState]; -}; - -export type PipeTransportObserver = - EnhancedEventEmitter; - -export type PipeTransportObserverEvents = TransportObserverEvents & { - sctpstatechange: [SctpState]; -}; - type PipeTransportConstructorOptions = TransportConstructorOptions & { data: PipeTransportData; @@ -152,27 +57,19 @@ export type PipeTransportData = { srtpParameters?: SrtpParameters; }; -export type PipeTransportDump = BaseTransportDump & { - tuple: TransportTuple; - rtx: boolean; - srtpParameters?: SrtpParameters; -}; - const logger = new Logger('PipeTransport'); -export class PipeTransport< - PipeTransportAppData extends AppData = AppData, -> extends Transport< - PipeTransportAppData, - PipeTransportEvents, - PipeTransportObserver -> { +export class PipeTransportImpl + extends TransportImpl< + PipeTransportAppData, + PipeTransportEvents, + PipeTransportObserver + > + implements Transport, PipeTransport +{ // PipeTransport data. readonly #data: PipeTransportData; - /** - * @private - */ constructor(options: PipeTransportConstructorOptions) { const observer: PipeTransportObserver = new EnhancedEventEmitter(); @@ -192,50 +89,33 @@ export class PipeTransport< }; this.handleWorkerNotifications(); + this.handleListenerError(); + } + + get type(): 'pipe' { + return 'pipe'; } - /** - * Observer. - * - * @override - */ get observer(): PipeTransportObserver { return super.observer; } - /** - * Transport tuple. - */ get tuple(): TransportTuple { return this.#data.tuple; } - /** - * SCTP parameters. - */ get sctpParameters(): SctpParameters | undefined { return this.#data.sctpParameters; } - /** - * SCTP state. - */ get sctpState(): SctpState | undefined { return this.#data.sctpState; } - /** - * SRTP parameters. - */ get srtpParameters(): SrtpParameters | undefined { return this.#data.srtpParameters; } - /** - * Close the PipeTransport. - * - * @override - */ close(): void { if (this.closed) { return; @@ -248,12 +128,6 @@ export class PipeTransport< super.close(); } - /** - * Router was closed. - * - * @private - * @override - */ routerClosed(): void { if (this.closed) { return; @@ -266,11 +140,24 @@ export class PipeTransport< super.routerClosed(); } - /** - * Get PipeTransport stats. - * - * @override - */ + async dump(): Promise { + logger.debug('dump()'); + + const response = await this.channel.request( + FbsRequest.Method.TRANSPORT_DUMP, + undefined, + undefined, + this.internal.transportId + ); + + /* Decode Response. */ + const data = new FbsPipeTransport.DumpResponse(); + + response.body(data); + + return parsePipeTransportDumpResponse(data); + } + async getStats(): Promise { logger.debug('getStats()'); @@ -289,11 +176,6 @@ export class PipeTransport< return [parseGetStatsResponse(data)]; } - /** - * Provide the PipeTransport remote parameters. - * - * @override - */ async connect({ ip, port, @@ -331,11 +213,6 @@ export class PipeTransport< } } - /** - * Create a pipe Consumer. - * - * @override - */ async consume({ producerId, appData, @@ -390,7 +267,7 @@ export class PipeTransport< type: 'pipe' as ConsumerType, }; - const consumer: Consumer = new Consumer({ + const consumer: Consumer = new ConsumerImpl({ internal: { ...this.internal, consumerId, @@ -456,6 +333,15 @@ export class PipeTransport< } ); } + + private handleListenerError(): void { + this.on('listenererror', (eventName, error) => { + logger.error( + `event listener threw an error [eventName:${eventName}]:`, + error + ); + }); + } } /* diff --git a/node/src/PipeTransportTypes.ts b/node/src/PipeTransportTypes.ts new file mode 100644 index 0000000000..4bc9734eb8 --- /dev/null +++ b/node/src/PipeTransportTypes.ts @@ -0,0 +1,200 @@ +import type { EnhancedEventEmitter } from './enhancedEvents'; +import type { + Transport, + TransportListenInfo, + TransportListenIp, + TransportTuple, + SctpState, + BaseTransportDump, + BaseTransportStats, + TransportEvents, + TransportObserverEvents, +} from './TransportTypes'; +import type { Consumer } from './ConsumerTypes'; +import type { SrtpParameters } from './srtpParametersTypes'; +import type { SctpParameters, NumSctpStreams } from './sctpParametersTypes'; +import type { Either, AppData } from './types'; + +export type PipeTransportOptions< + PipeTransportAppData extends AppData = AppData, +> = { + /** + * Create a SCTP association. Default false. + */ + enableSctp?: boolean; + + /** + * SCTP streams number. + */ + numSctpStreams?: NumSctpStreams; + + /** + * Maximum allowed size for SCTP messages sent by DataProducers. + * Default 268435456. + */ + maxSctpMessageSize?: number; + + /** + * Maximum SCTP send buffer used by DataConsumers. + * Default 268435456. + */ + sctpSendBufferSize?: number; + + /** + * Enable RTX and NACK for RTP retransmission. Useful if both Routers are + * located in different hosts and there is packet lost in the link. For this + * to work, both PipeTransports must enable this setting. Default false. + */ + enableRtx?: boolean; + + /** + * Enable SRTP. Useful to protect the RTP and RTCP traffic if both Routers + * are located in different hosts. For this to work, connect() must be called + * with remote SRTP parameters. Default false. + */ + enableSrtp?: boolean; + + /** + * Custom application data. + */ + appData?: PipeTransportAppData; +} & PipeTransportListen; + +type PipeTransportListen = Either< + PipeTransportListenInfo, + PipeTransportListenIp +>; + +type PipeTransportListenInfo = { + /** + * Listening info. + */ + listenInfo: TransportListenInfo; +}; + +type PipeTransportListenIp = { + /** + * Listening IP address. + */ + listenIp: TransportListenIp | string; + + /** + * Fixed port to listen on instead of selecting automatically from Worker's + * port range. + */ + port?: number; +}; + +export type PipeConsumerOptions = { + /** + * The id of the Producer to consume. + */ + producerId: string; + + /** + * Custom application data. + */ + appData?: ConsumerAppData; +}; + +export type PipeTransportDump = BaseTransportDump & { + tuple: TransportTuple; + rtx: boolean; + srtpParameters?: SrtpParameters; +}; + +export type PipeTransportStat = BaseTransportStats & { + type: string; + tuple: TransportTuple; +}; + +export type PipeTransportEvents = TransportEvents & { + sctpstatechange: [SctpState]; +}; + +export type PipeTransportObserver = + EnhancedEventEmitter; + +export type PipeTransportObserverEvents = TransportObserverEvents & { + sctpstatechange: [SctpState]; +}; + +export interface PipeTransport + extends Transport< + PipeTransportAppData, + PipeTransportEvents, + PipeTransportObserver + > { + /** + * Transport type. + * + * @override + */ + get type(): 'pipe'; + + /** + * Observer. + * + * @override + */ + get observer(): PipeTransportObserver; + + /** + * PipeTransport tuple. + */ + get tuple(): TransportTuple; + + /** + * SCTP parameters. + */ + get sctpParameters(): SctpParameters | undefined; + + /** + * SCTP state. + */ + get sctpState(): SctpState | undefined; + + /** + * SRTP parameters. + */ + get srtpParameters(): SrtpParameters | undefined; + + /** + * Dump PipeTransport. + * + * @override + */ + dump(): Promise; + + /** + * Get PipeTransport stats. + * + * @override + */ + getStats(): Promise; + + /** + * Provide the PipeTransport remote parameters. + * + * @override + */ + connect({ + ip, + port, + srtpParameters, + }: { + ip: string; + port: number; + srtpParameters?: SrtpParameters; + }): Promise; + + /** + * Create a pipe Consumer. + * + * @override + */ + consume({ + producerId, + appData, + }: PipeConsumerOptions): Promise>; +} diff --git a/node/src/PlainTransport.ts b/node/src/PlainTransport.ts index e1eabcac62..5e595152c1 100644 --- a/node/src/PlainTransport.ts +++ b/node/src/PlainTransport.ts @@ -1,145 +1,36 @@ import * as flatbuffers from 'flatbuffers'; import { Logger } from './Logger'; import { EnhancedEventEmitter } from './enhancedEvents'; +import type { + PlainTransport, + PlainTransportDump, + PlainTransportStat, + PlainTransportEvents, + PlainTransportObserver, + PlainTransportObserverEvents, +} from './PlainTransportTypes'; +import type { Transport, TransportTuple, SctpState } from './TransportTypes'; import { + TransportImpl, + TransportConstructorOptions, parseSctpState, - BaseTransportDump, - BaseTransportStats, parseTuple, parseBaseTransportDump, parseBaseTransportStats, parseTransportTraceEventData, - Transport, - TransportListenInfo, - TransportListenIp, - TransportTuple, - TransportEvents, - TransportObserverEvents, - TransportConstructorOptions, - SctpState, } from './Transport'; -import { SctpParameters, NumSctpStreams } from './SctpParameters'; +import type { SctpParameters } from './sctpParametersTypes'; +import type { SrtpParameters } from './srtpParametersTypes'; import { parseSrtpParameters, serializeSrtpParameters, - SrtpParameters, - SrtpCryptoSuite, -} from './SrtpParameters'; -import { AppData, Either } from './types'; +} from './srtpParametersFbsUtils'; +import type { AppData } from './types'; import { Event, Notification } from './fbs/notification'; import * as FbsRequest from './fbs/request'; import * as FbsTransport from './fbs/transport'; import * as FbsPlainTransport from './fbs/plain-transport'; -type PlainTransportListenInfo = { - /** - * Listening info. - */ - listenInfo: TransportListenInfo; - - /** - * Optional listening info for RTCP. - */ - rtcpListenInfo?: TransportListenInfo; -}; - -type PlainTransportListenIp = { - /** - * Listening IP address. - */ - listenIp: TransportListenIp | string; - - /** - * Fixed port to listen on instead of selecting automatically from Worker's port - * range. - */ - port?: number; -}; - -type PlainTransportListen = Either< - PlainTransportListenInfo, - PlainTransportListenIp ->; - -export type PlainTransportOptions< - PlainTransportAppData extends AppData = AppData, -> = { - /** - * Use RTCP-mux (RTP and RTCP in the same port). Default true. - */ - rtcpMux?: boolean; - - /** - * Whether remote IP:port should be auto-detected based on first RTP/RTCP - * packet received. If enabled, connect() method must not be called unless - * SRTP is enabled. If so, it must be called with just remote SRTP parameters. - * Default false. - */ - comedia?: boolean; - - /** - * Create a SCTP association. Default false. - */ - enableSctp?: boolean; - - /** - * SCTP streams number. - */ - numSctpStreams?: NumSctpStreams; - - /** - * Maximum allowed size for SCTP messages sent by DataProducers. - * Default 262144. - */ - maxSctpMessageSize?: number; - - /** - * Maximum SCTP send buffer used by DataConsumers. - * Default 262144. - */ - sctpSendBufferSize?: number; - - /** - * Enable SRTP. For this to work, connect() must be called - * with remote SRTP parameters. Default false. - */ - enableSrtp?: boolean; - - /** - * The SRTP crypto suite to be used if enableSrtp is set. Default - * 'AES_CM_128_HMAC_SHA1_80'. - */ - srtpCryptoSuite?: SrtpCryptoSuite; - - /** - * Custom application data. - */ - appData?: PlainTransportAppData; -} & PlainTransportListen; - -export type PlainTransportStat = BaseTransportStats & { - type: string; - rtcpMux: boolean; - comedia: boolean; - tuple: TransportTuple; - rtcpTuple?: TransportTuple; -}; - -export type PlainTransportEvents = TransportEvents & { - tuple: [TransportTuple]; - rtcptuple: [TransportTuple]; - sctpstatechange: [SctpState]; -}; - -export type PlainTransportObserver = - EnhancedEventEmitter; - -export type PlainTransportObserverEvents = TransportObserverEvents & { - tuple: [TransportTuple]; - rtcptuple: [TransportTuple]; - sctpstatechange: [SctpState]; -}; - type PlainTransportConstructorOptions = TransportConstructorOptions & { data: PlainTransportData; @@ -155,29 +46,19 @@ export type PlainTransportData = { srtpParameters?: SrtpParameters; }; -type PlainTransportDump = BaseTransportDump & { - rtcpMux: boolean; - comedia: boolean; - tuple: TransportTuple; - rtcpTuple?: TransportTuple; - srtpParameters?: SrtpParameters; -}; - const logger = new Logger('PlainTransport'); -export class PlainTransport< - PlainTransportAppData extends AppData = AppData, -> extends Transport< - PlainTransportAppData, - PlainTransportEvents, - PlainTransportObserver -> { +export class PlainTransportImpl + extends TransportImpl< + PlainTransportAppData, + PlainTransportEvents, + PlainTransportObserver + > + implements Transport, PlainTransport +{ // PlainTransport data. readonly #data: PlainTransportData; - /** - * @private - */ constructor( options: PlainTransportConstructorOptions ) { @@ -201,57 +82,37 @@ export class PlainTransport< }; this.handleWorkerNotifications(); + this.handleListenerError(); + } + + get type(): 'plain' { + return 'plain'; } - /** - * Observer. - * - * @override - */ get observer(): PlainTransportObserver { return super.observer; } - /** - * Transport tuple. - */ get tuple(): TransportTuple { return this.#data.tuple; } - /** - * Transport RTCP tuple. - */ get rtcpTuple(): TransportTuple | undefined { return this.#data.rtcpTuple; } - /** - * SCTP parameters. - */ get sctpParameters(): SctpParameters | undefined { return this.#data.sctpParameters; } - /** - * SCTP state. - */ get sctpState(): SctpState | undefined { return this.#data.sctpState; } - /** - * SRTP parameters. - */ get srtpParameters(): SrtpParameters | undefined { return this.#data.srtpParameters; } - /** - * Close the PlainTransport. - * - * @override - */ close(): void { if (this.closed) { return; @@ -264,12 +125,6 @@ export class PlainTransport< super.close(); } - /** - * Router was closed. - * - * @private - * @override - */ routerClosed(): void { if (this.closed) { return; @@ -282,9 +137,6 @@ export class PlainTransport< super.routerClosed(); } - /** - * Dump Transport. - */ async dump(): Promise { logger.debug('dump()'); @@ -303,11 +155,6 @@ export class PlainTransport< return parsePlainTransportDumpResponse(data); } - /** - * Get PlainTransport stats. - * - * @override - */ async getStats(): Promise { logger.debug('getStats()'); @@ -326,11 +173,6 @@ export class PlainTransport< return [parseGetStatsResponse(data)]; } - /** - * Provide the PlainTransport remote parameters. - * - * @override - */ async connect({ ip, port, @@ -457,6 +299,15 @@ export class PlainTransport< } ); } + + private handleListenerError(): void { + this.on('listenererror', (eventName, error) => { + logger.error( + `event listener threw an error [eventName:${eventName}]:`, + error + ); + }); + } } export function parsePlainTransportDumpResponse( diff --git a/node/src/PlainTransportTypes.ts b/node/src/PlainTransportTypes.ts new file mode 100644 index 0000000000..383a29c04a --- /dev/null +++ b/node/src/PlainTransportTypes.ts @@ -0,0 +1,209 @@ +import type { EnhancedEventEmitter } from './enhancedEvents'; +import type { + Transport, + TransportListenInfo, + TransportListenIp, + TransportTuple, + SctpState, + BaseTransportDump, + BaseTransportStats, + TransportEvents, + TransportObserverEvents, +} from './TransportTypes'; +import type { SrtpParameters, SrtpCryptoSuite } from './srtpParametersTypes'; +import type { SctpParameters, NumSctpStreams } from './sctpParametersTypes'; +import type { Either, AppData } from './types'; + +export type PlainTransportOptions< + PlainTransportAppData extends AppData = AppData, +> = { + /** + * Use RTCP-mux (RTP and RTCP in the same port). Default true. + */ + rtcpMux?: boolean; + + /** + * Whether remote IP:port should be auto-detected based on first RTP/RTCP + * packet received. If enabled, connect() method must not be called unless + * SRTP is enabled. If so, it must be called with just remote SRTP parameters. + * Default false. + */ + comedia?: boolean; + + /** + * Create a SCTP association. Default false. + */ + enableSctp?: boolean; + + /** + * SCTP streams number. + */ + numSctpStreams?: NumSctpStreams; + + /** + * Maximum allowed size for SCTP messages sent by DataProducers. + * Default 262144. + */ + maxSctpMessageSize?: number; + + /** + * Maximum SCTP send buffer used by DataConsumers. + * Default 262144. + */ + sctpSendBufferSize?: number; + + /** + * Enable SRTP. For this to work, connect() must be called + * with remote SRTP parameters. Default false. + */ + enableSrtp?: boolean; + + /** + * The SRTP crypto suite to be used if enableSrtp is set. Default + * 'AES_CM_128_HMAC_SHA1_80'. + */ + srtpCryptoSuite?: SrtpCryptoSuite; + + /** + * Custom application data. + */ + appData?: PlainTransportAppData; +} & PlainTransportListen; + +type PlainTransportListen = Either< + PlainTransportListenInfo, + PlainTransportListenIp +>; + +type PlainTransportListenInfo = { + /** + * Listening info. + */ + listenInfo: TransportListenInfo; + + /** + * Optional listening info for RTCP. + */ + rtcpListenInfo?: TransportListenInfo; +}; + +type PlainTransportListenIp = { + /** + * Listening IP address. + */ + listenIp: TransportListenIp | string; + + /** + * Fixed port to listen on instead of selecting automatically from Worker's + * port range. + */ + port?: number; +}; + +export type PlainTransportDump = BaseTransportDump & { + rtcpMux: boolean; + comedia: boolean; + tuple: TransportTuple; + rtcpTuple?: TransportTuple; + srtpParameters?: SrtpParameters; +}; + +export type PlainTransportStat = BaseTransportStats & { + type: string; + rtcpMux: boolean; + comedia: boolean; + tuple: TransportTuple; + rtcpTuple?: TransportTuple; +}; + +export type PlainTransportEvents = TransportEvents & { + tuple: [TransportTuple]; + rtcptuple: [TransportTuple]; + sctpstatechange: [SctpState]; +}; + +export type PlainTransportObserver = + EnhancedEventEmitter; + +export type PlainTransportObserverEvents = TransportObserverEvents & { + tuple: [TransportTuple]; + rtcptuple: [TransportTuple]; + sctpstatechange: [SctpState]; +}; + +export interface PlainTransport + extends Transport< + PlainTransportAppData, + PlainTransportEvents, + PlainTransportObserver + > { + /** + * Transport type. + * + * @override + */ + get type(): 'plain'; + + /** + * Observer. + * + * @override + */ + get observer(): PlainTransportObserver; + + /** + * PlainTransport tuple. + */ + get tuple(): TransportTuple; + + /** + * PlainTransport RTCP tuple. + */ + get rtcpTuple(): TransportTuple | undefined; + + /** + * SCTP parameters. + */ + get sctpParameters(): SctpParameters | undefined; + + /** + * SCTP state. + */ + get sctpState(): SctpState | undefined; + + /** + * SRTP parameters. + */ + get srtpParameters(): SrtpParameters | undefined; + + /** + * Dump PlainTransport. + * + * @override + */ + dump(): Promise; + + /** + * Get PlainTransport stats. + * + * @override + */ + getStats(): Promise; + + /** + * Provide the PlainTransport remote parameters. + * + * @override + */ + connect({ + ip, + port, + rtcpPort, + srtpParameters, + }: { + ip?: string; + port?: number; + rtcpPort?: number; + srtpParameters?: SrtpParameters; + }): Promise; +} diff --git a/node/src/Producer.ts b/node/src/Producer.ts index 42e2d2b1ee..9420b8f8eb 100644 --- a/node/src/Producer.ts +++ b/node/src/Producer.ts @@ -1,12 +1,26 @@ import { Logger } from './Logger'; import { EnhancedEventEmitter } from './enhancedEvents'; +import type { + Producer, + ProducerType, + ProducerScore, + ProducerVideoOrientation, + ProducerDump, + ProducerStat, + ProducerTraceEventType, + ProducerTraceEventData, + ProducerEvents, + ProducerObserver, + ProducerObserverEvents, +} from './ProducerTypes'; import { Channel } from './Channel'; -import { TransportInternal } from './Transport'; -import { MediaKind, RtpParameters, parseRtpParameters } from './RtpParameters'; +import type { TransportInternal } from './Transport'; +import type { MediaKind, RtpParameters } from './rtpParametersTypes'; +import { parseRtpParameters } from './rtpParametersFbsUtils'; +import { parseRtpStreamRecvStats } from './rtpStreamStatsFbsUtils'; +import type { AppData } from './types'; +import * as fbsUtils from './fbsUtils'; import { Event, Notification } from './fbs/notification'; -import { parseRtpStreamRecvStats, RtpStreamRecvStats } from './RtpStream'; -import { AppData } from './types'; -import * as utils from './utils'; import { TraceDirection as FbsTraceDirection } from './fbs/common'; import * as FbsNotification from './fbs/notification'; import * as FbsRequest from './fbs/request'; @@ -15,157 +29,12 @@ import * as FbsProducer from './fbs/producer'; import * as FbsProducerTraceInfo from './fbs/producer/trace-info'; import * as FbsRtpParameters from './fbs/rtp-parameters'; -export type ProducerOptions = { - /** - * Producer id (just for Router.pipeToRouter() method). - */ - id?: string; - - /** - * Media kind ('audio' or 'video'). - */ - kind: MediaKind; - - /** - * RTP parameters defining what the endpoint is sending. - */ - rtpParameters: RtpParameters; - - /** - * Whether the producer must start in paused mode. Default false. - */ - paused?: boolean; - - /** - * Just for video. Time (in ms) before asking the sender for a new key frame - * after having asked a previous one. Default 0. - */ - keyFrameRequestDelay?: number; - - /** - * Custom application data. - */ - appData?: ProducerAppData; -}; - -/** - * Valid types for 'trace' event. - */ -export type ProducerTraceEventType = - | 'rtp' - | 'keyframe' - | 'nack' - | 'pli' - | 'fir' - | 'sr'; - -/** - * 'trace' event data. - */ -export type ProducerTraceEventData = { - /** - * Trace type. - */ - type: ProducerTraceEventType; - - /** - * Event timestamp. - */ - timestamp: number; - - /** - * Event direction. - */ - direction: 'in' | 'out'; - - /** - * Per type information. - */ - info: any; -}; - -export type ProducerScore = { - /** - * Index of the RTP stream in the rtpParameters.encodings array. - */ - encodingIdx: number; - - /** - * SSRC of the RTP stream. - */ - ssrc: number; - - /** - * RID of the RTP stream. - */ - rid?: string; - - /** - * The score of the RTP stream. - */ - score: number; -}; - -export type ProducerVideoOrientation = { - /** - * Whether the source is a video camera. - */ - camera: boolean; - - /** - * Whether the video source is flipped. - */ - flip: boolean; - - /** - * Rotation degrees (0, 90, 180 or 270). - */ - rotation: number; -}; - -export type ProducerStat = RtpStreamRecvStats; - -/** - * Producer type. - */ -export type ProducerType = 'simple' | 'simulcast' | 'svc'; - -export type ProducerEvents = { - transportclose: []; - score: [ProducerScore[]]; - videoorientationchange: [ProducerVideoOrientation]; - trace: [ProducerTraceEventData]; - listenererror: [string, Error]; - // Private events. - '@close': []; -}; - -export type ProducerObserver = EnhancedEventEmitter; - -export type ProducerObserverEvents = { - close: []; - pause: []; - resume: []; - score: [ProducerScore[]]; - videoorientationchange: [ProducerVideoOrientation]; - trace: [ProducerTraceEventData]; -}; - -type ProducerDump = { - id: string; - kind: string; - type: ProducerType; - rtpParameters: RtpParameters; - rtpMapping: any; - rtpStreams: any; - traceEventTypes: string[]; - paused: boolean; -}; - type ProducerInternal = TransportInternal & { producerId: string; }; +const logger = new Logger('Producer'); + type ProducerData = { kind: MediaKind; rtpParameters: RtpParameters; @@ -173,11 +42,10 @@ type ProducerData = { consumableRtpParameters: RtpParameters; }; -const logger = new Logger('Producer'); - -export class Producer< - ProducerAppData extends AppData = AppData, -> extends EnhancedEventEmitter { +export class ProducerImpl + extends EnhancedEventEmitter + implements Producer +{ // Internal data. readonly #internal: ProducerInternal; @@ -203,9 +71,6 @@ export class Producer< readonly #observer: ProducerObserver = new EnhancedEventEmitter(); - /** - * @private - */ constructor({ internal, data, @@ -230,98 +95,62 @@ export class Producer< this.#appData = appData ?? ({} as ProducerAppData); this.handleWorkerNotifications(); + this.handleListenerError(); } - /** - * Producer id. - */ get id(): string { return this.#internal.producerId; } - /** - * Whether the Producer is closed. - */ get closed(): boolean { return this.#closed; } - /** - * Media kind. - */ get kind(): MediaKind { return this.#data.kind; } - /** - * RTP parameters. - */ get rtpParameters(): RtpParameters { return this.#data.rtpParameters; } - /** - * Producer type. - */ get type(): ProducerType { return this.#data.type; } - /** - * Consumable RTP parameters. - * - * @private - */ get consumableRtpParameters(): RtpParameters { return this.#data.consumableRtpParameters; } - /** - * Whether the Producer is paused. - */ get paused(): boolean { return this.#paused; } - /** - * Producer score list. - */ get score(): ProducerScore[] { return this.#score; } - /** - * App custom data. - */ get appData(): ProducerAppData { return this.#appData; } - /** - * App custom data setter. - */ set appData(appData: ProducerAppData) { this.#appData = appData; } - /** - * Observer. - */ get observer(): ProducerObserver { return this.#observer; } /** - * @private * Just for testing purposes. + * + * @private */ get channelForTesting(): Channel { return this.#channel; } - /** - * Close the Producer. - */ close(): void { if (this.#closed) { return; @@ -354,11 +183,6 @@ export class Producer< this.#observer.safeEmit('close'); } - /** - * Transport was closed. - * - * @private - */ transportClosed(): void { if (this.#closed) { return; @@ -377,9 +201,6 @@ export class Producer< this.#observer.safeEmit('close'); } - /** - * Dump Producer. - */ async dump(): Promise { logger.debug('dump()'); @@ -398,9 +219,6 @@ export class Producer< return parseProducerDump(dumpResponse); } - /** - * Get Producer stats. - */ async getStats(): Promise { logger.debug('getStats()'); @@ -419,9 +237,6 @@ export class Producer< return parseProducerStats(data); } - /** - * Pause the Producer. - */ async pause(): Promise { logger.debug('pause()'); @@ -442,9 +257,6 @@ export class Producer< } } - /** - * Resume the Producer. - */ async resume(): Promise { logger.debug('resume()'); @@ -465,9 +277,6 @@ export class Producer< } } - /** - * Enable 'trace' event. - */ async enableTraceEvent(types: ProducerTraceEventType[] = []): Promise { logger.debug('enableTraceEvent()'); @@ -502,9 +311,6 @@ export class Producer< ); } - /** - * Send RTP packet (just valid for Producers created on a DirectTransport). - */ send(rtpPacket: Buffer): void { if (!Buffer.isBuffer(rtpPacket)) { throw new TypeError('rtpPacket must be a Buffer'); @@ -536,7 +342,7 @@ export class Producer< data!.body(notification); - const score: ProducerScore[] = utils.parseVector( + const score: ProducerScore[] = fbsUtils.parseVector( notification, 'scores', parseProducerScore @@ -592,6 +398,15 @@ export class Producer< } ); } + + private handleListenerError(): void { + this.on('listenererror', (eventName, error) => { + logger.error( + `event listener threw an error [eventName:${eventName}]:`, + error + ); + }); + } } export function producerTypeFromFbs(type: FbsRtpParameters.Type): ProducerType { @@ -713,11 +528,11 @@ export function parseProducerDump( // TODO: Make flatbuffers TS return undefined instead of null. rtpStreams: data.rtpStreamsLength() > 0 - ? utils.parseVector(data, 'rtpStreams', (rtpStream: any) => + ? fbsUtils.parseVector(data, 'rtpStreams', (rtpStream: any) => rtpStream.unpack() ) : undefined, - traceEventTypes: utils.parseVector( + traceEventTypes: fbsUtils.parseVector( data, 'traceEventTypes', producerTraceEventTypeFromFbs @@ -729,7 +544,7 @@ export function parseProducerDump( function parseProducerStats( binary: FbsProducer.GetStatsResponse ): ProducerStat[] { - return utils.parseVector(binary, 'stats', parseRtpStreamRecvStats); + return fbsUtils.parseVector(binary, 'stats', parseRtpStreamRecvStats); } function parseProducerScore(binary: FbsProducer.Score): ProducerScore { diff --git a/node/src/ProducerTypes.ts b/node/src/ProducerTypes.ts new file mode 100644 index 0000000000..989acf72c0 --- /dev/null +++ b/node/src/ProducerTypes.ts @@ -0,0 +1,253 @@ +import type { EnhancedEventEmitter } from './enhancedEvents'; +import type { MediaKind, RtpParameters } from './rtpParametersTypes'; +import type { RtpStreamRecvStats } from './rtpStreamStatsTypes'; +import type { AppData } from './types'; + +export type ProducerOptions = { + /** + * Producer id (just for Router.pipeToRouter() method). + */ + id?: string; + + /** + * Media kind ('audio' or 'video'). + */ + kind: MediaKind; + + /** + * RTP parameters defining what the endpoint is sending. + */ + rtpParameters: RtpParameters; + + /** + * Whether the producer must start in paused mode. Default false. + */ + paused?: boolean; + + /** + * Just for video. Time (in ms) before asking the sender for a new key frame + * after having asked a previous one. Default 0. + */ + keyFrameRequestDelay?: number; + + /** + * Custom application data. + */ + appData?: ProducerAppData; +}; + +/** + * Producer type. + */ +export type ProducerType = 'simple' | 'simulcast' | 'svc'; + +export type ProducerScore = { + /** + * Index of the RTP stream in the rtpParameters.encodings array. + */ + encodingIdx: number; + + /** + * SSRC of the RTP stream. + */ + ssrc: number; + + /** + * RID of the RTP stream. + */ + rid?: string; + + /** + * The score of the RTP stream. + */ + score: number; +}; + +export type ProducerVideoOrientation = { + /** + * Whether the source is a video camera. + */ + camera: boolean; + + /** + * Whether the video source is flipped. + */ + flip: boolean; + + /** + * Rotation degrees (0, 90, 180 or 270). + */ + rotation: number; +}; + +export type ProducerDump = { + id: string; + kind: string; + type: ProducerType; + rtpParameters: RtpParameters; + rtpMapping: any; + rtpStreams: any; + traceEventTypes: string[]; + paused: boolean; +}; + +export type ProducerStat = RtpStreamRecvStats; + +/** + * Valid types for 'trace' event. + */ +export type ProducerTraceEventType = + | 'rtp' + | 'keyframe' + | 'nack' + | 'pli' + | 'fir' + | 'sr'; + +/** + * 'trace' event data. + */ +export type ProducerTraceEventData = { + /** + * Trace type. + */ + type: ProducerTraceEventType; + + /** + * Event timestamp. + */ + timestamp: number; + + /** + * Event direction. + */ + direction: 'in' | 'out'; + + /** + * Per type information. + */ + info: any; +}; + +export type ProducerEvents = { + transportclose: []; + score: [ProducerScore[]]; + videoorientationchange: [ProducerVideoOrientation]; + trace: [ProducerTraceEventData]; + listenererror: [string, Error]; + // Private events. + '@close': []; +}; + +export type ProducerObserver = EnhancedEventEmitter; + +export type ProducerObserverEvents = { + close: []; + pause: []; + resume: []; + score: [ProducerScore[]]; + videoorientationchange: [ProducerVideoOrientation]; + trace: [ProducerTraceEventData]; +}; + +export interface Producer + extends EnhancedEventEmitter { + /** + * Producer id. + */ + get id(): string; + + /** + * Whether the Producer is closed. + */ + get closed(): boolean; + + /** + * Media kind. + */ + get kind(): MediaKind; + + /** + * RTP parameters. + */ + get rtpParameters(): RtpParameters; + + /** + * Producer type. + */ + get type(): ProducerType; + + /** + * Consumable RTP parameters. + * + * @private + */ + get consumableRtpParameters(): RtpParameters; + + /** + * Whether the Producer is paused. + */ + get paused(): boolean; + + /** + * Producer score list. + */ + get score(): ProducerScore[]; + + /** + * App custom data. + */ + get appData(): ProducerAppData; + + /** + * App custom data setter. + */ + set appData(appData: ProducerAppData); + + /** + * Observer. + */ + get observer(): ProducerObserver; + + /** + * Close the Producer. + */ + close(): void; + + /** + * Transport was closed. + * + * @private + */ + transportClosed(): void; + + /** + * Dump Producer. + */ + dump(): Promise; + + /** + * Get Producer stats. + */ + getStats(): Promise; + + /** + * Pause the Producer. + */ + pause(): Promise; + + /** + * Resume the Producer. + */ + resume(): Promise; + + /** + * Enable 'trace' event. + */ + enableTraceEvent(types?: ProducerTraceEventType[]): Promise; + + /** + * Send RTP packet (just valid for Producers created on a DirectTransport). + */ + send(rtpPacket: Buffer): void; +} diff --git a/node/src/Router.ts b/node/src/Router.ts index 755d579f72..ebe6a3f1a7 100644 --- a/node/src/Router.ts +++ b/node/src/Router.ts @@ -2,59 +2,72 @@ import { Logger } from './Logger'; import { EnhancedEventEmitter } from './enhancedEvents'; import * as ortc from './ortc'; import { InvalidStateError } from './errors'; -import { Channel } from './Channel'; -import { +import type { Channel } from './Channel'; +import type { + Router, + PipeToRouterOptions, + PipeToRouterResult, + PipeTransportPair, + RouterDump, + RouterEvents, + RouterObserver, + RouterObserverEvents, +} from './RouterTypes'; +import type { Transport, - TransportListenInfo, TransportListenIp, TransportProtocol, - portRangeToFbs, - socketFlagsToFbs, -} from './Transport'; -import { +} from './TransportTypes'; +import { portRangeToFbs, socketFlagsToFbs } from './Transport'; +import type { WebRtcTransport, WebRtcTransportOptions, +} from './WebRtcTransportTypes'; +import { + WebRtcTransportImpl, parseWebRtcTransportDumpResponse, } from './WebRtcTransport'; -import { +import type { PlainTransport, PlainTransportOptions, +} from './PlainTransportTypes'; +import { + PlainTransportImpl, parsePlainTransportDumpResponse, } from './PlainTransport'; +import type { PipeTransport, PipeTransportOptions } from './PipeTransportTypes'; import { - PipeTransport, - PipeTransportOptions, + PipeTransportImpl, parsePipeTransportDumpResponse, } from './PipeTransport'; -import { +import type { DirectTransport, DirectTransportOptions, +} from './DirectTransportTypes'; +import { + DirectTransportImpl, parseDirectTransportDumpResponse, } from './DirectTransport'; -import { Producer } from './Producer'; -import { Consumer } from './Consumer'; -import { DataProducer } from './DataProducer'; -import { DataConsumer } from './DataConsumer'; -import { RtpObserver } from './RtpObserver'; -import { +import type { Producer } from './ProducerTypes'; +import type { Consumer } from './ConsumerTypes'; +import type { DataProducer } from './DataProducerTypes'; +import type { DataConsumer } from './DataConsumerTypes'; +import type { RtpObserver } from './RtpObserverTypes'; +import type { ActiveSpeakerObserver, ActiveSpeakerObserverOptions, -} from './ActiveSpeakerObserver'; -import { +} from './ActiveSpeakerObserverTypes'; +import { ActiveSpeakerObserverImpl } from './ActiveSpeakerObserver'; +import type { AudioLevelObserver, AudioLevelObserverOptions, -} from './AudioLevelObserver'; -import { RtpCapabilities, RtpCodecCapability } from './RtpParameters'; -import { cryptoSuiteToFbs } from './SrtpParameters'; -import { NumSctpStreams } from './SctpParameters'; -import { AppData, Either } from './types'; -import { - clone, - generateUUIDv4, - parseVector, - parseStringStringVector, - parseStringStringArrayVector, -} from './utils'; +} from './AudioLevelObserverTypes'; +import { AudioLevelObserverImpl } from './AudioLevelObserver'; +import type { RtpCapabilities } from './rtpParametersTypes'; +import { cryptoSuiteToFbs } from './srtpParametersFbsUtils'; +import type { AppData } from './types'; +import * as utils from './utils'; +import * as fbsUtils from './fbsUtils'; import * as FbsActiveSpeakerObserver from './fbs/active-speaker-observer'; import * as FbsAudioLevelObserver from './fbs/audio-level-observer'; import * as FbsRequest from './fbs/request'; @@ -68,144 +81,6 @@ import * as FbsPipeTransport from './fbs/pipe-transport'; import * as FbsDirectTransport from './fbs/direct-transport'; import * as FbsSctpParameters from './fbs/sctp-parameters'; -export type RouterOptions = { - /** - * Router media codecs. - */ - mediaCodecs?: RtpCodecCapability[]; - - /** - * Custom application data. - */ - appData?: RouterAppData; -}; - -type PipeToRouterListenInfo = { - listenInfo: TransportListenInfo; -}; - -type PipeToRouterListenIp = { - /** - * IP used in the PipeTransport pair. Default '127.0.0.1'. - */ - listenIp?: TransportListenIp | string; -}; - -type PipeToRouterListen = Either; - -export type PipeToRouterOptions = { - /** - * The id of the Producer to consume. - */ - producerId?: string; - - /** - * The id of the DataProducer to consume. - */ - dataProducerId?: string; - - /** - * Target Router instance. - */ - router: Router; - - /** - * Create a SCTP association. Default true. - */ - enableSctp?: boolean; - - /** - * SCTP streams number. - */ - numSctpStreams?: NumSctpStreams; - - /** - * Enable RTX and NACK for RTP retransmission. - */ - enableRtx?: boolean; - - /** - * Enable SRTP. - */ - enableSrtp?: boolean; -} & PipeToRouterListen; - -export type PipeToRouterResult = { - /** - * The Consumer created in the current Router. - */ - pipeConsumer?: Consumer; - - /** - * The Producer created in the target Router. - */ - pipeProducer?: Producer; - - /** - * The DataConsumer created in the current Router. - */ - pipeDataConsumer?: DataConsumer; - - /** - * The DataProducer created in the target Router. - */ - pipeDataProducer?: DataProducer; -}; - -export type RouterDump = { - /** - * The Router id. - */ - id: string; - /** - * Id of Transports. - */ - transportIds: string[]; - /** - * Id of RtpObservers. - */ - rtpObserverIds: string[]; - /** - * Array of Producer id and its respective Consumer ids. - */ - mapProducerIdConsumerIds: { key: string; values: string[] }[]; - /** - * Array of Consumer id and its Producer id. - */ - mapConsumerIdProducerId: { key: string; value: string }[]; - /** - * Array of Producer id and its respective Observer ids. - */ - mapProducerIdObserverIds: { key: string; values: string[] }[]; - /** - * Array of Producer id and its respective DataConsumer ids. - */ - mapDataProducerIdDataConsumerIds: { key: string; values: string[] }[]; - /** - * Array of DataConsumer id and its DataProducer id. - */ - mapDataConsumerIdDataProducerId: { key: string; value: string }[]; -}; - -type PipeTransportPair = { - [key: string]: PipeTransport; -}; - -export type RouterEvents = { - workerclose: []; - listenererror: [string, Error]; - // Private events. - '@close': []; -}; - -export type RouterObserver = EnhancedEventEmitter; - -export type RouterObserverEvents = { - close: []; - newtransport: [Transport]; - newrtpobserver: [RtpObserver]; -}; - export type RouterInternal = { routerId: string; }; @@ -216,9 +91,10 @@ type RouterData = { const logger = new Logger('Router'); -export class Router< - RouterAppData extends AppData = AppData, -> extends EnhancedEventEmitter { +export class RouterImpl + extends EnhancedEventEmitter + implements Router +{ // Internal data. readonly #internal: RouterInternal; @@ -257,9 +133,6 @@ export class Router< readonly #observer: RouterObserver = new EnhancedEventEmitter(); - /** - * @private - */ constructor({ internal, data, @@ -279,61 +152,43 @@ export class Router< this.#data = data; this.#channel = channel; this.#appData = appData ?? ({} as RouterAppData); + + this.handleListenerError(); } - /** - * Router id. - */ get id(): string { return this.#internal.routerId; } - /** - * Whether the Router is closed. - */ get closed(): boolean { return this.#closed; } - /** - * RTP capabilities of the Router. - */ get rtpCapabilities(): RtpCapabilities { return this.#data.rtpCapabilities; } - /** - * App custom data. - */ get appData(): RouterAppData { return this.#appData; } - /** - * App custom data setter. - */ set appData(appData: RouterAppData) { this.#appData = appData; } - /** - * Observer. - */ get observer(): RouterObserver { return this.#observer; } /** - * @private * Just for testing purposes. + * + * @private */ get transportsForTesting(): Map { return this.#transports; } - /** - * Close the Router. - */ close(): void { if (this.#closed) { return; @@ -379,11 +234,6 @@ export class Router< this.#observer.safeEmit('close'); } - /** - * Worker was closed. - * - * @private - */ workerClosed(): void { if (this.#closed) { return; @@ -417,9 +267,6 @@ export class Router< this.#observer.safeEmit('close'); } - /** - * Dump Router. - */ async dump(): Promise { logger.debug('dump()'); @@ -439,9 +286,6 @@ export class Router< return parseRouterDumpResponse(dump); } - /** - * Create a WebRtcTransport. - */ async createWebRtcTransport< WebRtcTransportAppData extends AppData = AppData, >({ @@ -538,7 +382,7 @@ export class Router< } } - const transportId = generateUUIDv4(); + const transportId = utils.generateUUIDv4(); /* Build Request. */ let webRtcTransportListenServer: @@ -628,7 +472,7 @@ export class Router< const webRtcTransportData = parseWebRtcTransportDumpResponse(data); const transport: WebRtcTransport = - new WebRtcTransport({ + new WebRtcTransportImpl({ internal: { ...this.#internal, transportId: transportId, @@ -673,9 +517,6 @@ export class Router< return transport; } - /** - * Create a PlainTransport. - */ async createPlainTransport({ listenInfo, rtcpListenInfo, @@ -729,7 +570,7 @@ export class Router< }; } - const transportId = generateUUIDv4(); + const transportId = utils.generateUUIDv4(); /* Build Request. */ const baseTransportOptions = new FbsTransport.OptionsT( @@ -799,8 +640,8 @@ export class Router< const plainTransportData = parsePlainTransportDumpResponse(data); - const transport: PlainTransport = new PlainTransport( - { + const transport: PlainTransport = + new PlainTransportImpl({ internal: { ...this.#internal, transportId: transportId, @@ -815,8 +656,7 @@ export class Router< getDataProducerById: ( dataProducerId: string ): DataProducer | undefined => this.#dataProducers.get(dataProducerId), - } - ); + }); this.#transports.set(transport.id, transport); transport.on('@close', () => this.#transports.delete(transport.id)); @@ -842,9 +682,6 @@ export class Router< return transport; } - /** - * Create a PipeTransport. - */ async createPipeTransport({ listenInfo, listenIp, @@ -886,7 +723,7 @@ export class Router< }; } - const transportId = generateUUIDv4(); + const transportId = utils.generateUUIDv4(); /* Build Request. */ const baseTransportOptions = new FbsTransport.OptionsT( @@ -940,21 +777,23 @@ export class Router< const plainTransportData = parsePipeTransportDumpResponse(data); - const transport: PipeTransport = new PipeTransport({ - internal: { - ...this.#internal, - transportId, - }, - data: plainTransportData, - channel: this.#channel, - appData, - getRouterRtpCapabilities: (): RtpCapabilities => - this.#data.rtpCapabilities, - getProducerById: (producerId: string): Producer | undefined => - this.#producers.get(producerId), - getDataProducerById: (dataProducerId: string): DataProducer | undefined => - this.#dataProducers.get(dataProducerId), - }); + const transport: PipeTransport = + new PipeTransportImpl({ + internal: { + ...this.#internal, + transportId, + }, + data: plainTransportData, + channel: this.#channel, + appData, + getRouterRtpCapabilities: (): RtpCapabilities => + this.#data.rtpCapabilities, + getProducerById: (producerId: string): Producer | undefined => + this.#producers.get(producerId), + getDataProducerById: ( + dataProducerId: string + ): DataProducer | undefined => this.#dataProducers.get(dataProducerId), + }); this.#transports.set(transport.id, transport); transport.on('@close', () => this.#transports.delete(transport.id)); @@ -980,9 +819,6 @@ export class Router< return transport; } - /** - * Create a DirectTransport. - */ async createDirectTransport( { maxMessageSize = 262144, @@ -999,7 +835,7 @@ export class Router< throw new TypeError('if given, appData must be an object'); } - const transportId = generateUUIDv4(); + const transportId = utils.generateUUIDv4(); /* Build Request. */ const baseTransportOptions = new FbsTransport.OptionsT( @@ -1036,7 +872,7 @@ export class Router< const directTransportData = parseDirectTransportDumpResponse(data); const transport: DirectTransport = - new DirectTransport({ + new DirectTransportImpl({ internal: { ...this.#internal, transportId: transportId, @@ -1077,9 +913,6 @@ export class Router< return transport; } - /** - * Pipes the given Producer or DataProducer into another Router in same host. - */ async pipeToRouter({ producerId, dataProducerId, @@ -1340,9 +1173,6 @@ export class Router< } } - /** - * @private - */ addPipeTransportPair( pipeTransportPairKey: string, pipeTransportPairPromise: Promise @@ -1377,9 +1207,6 @@ export class Router< }); } - /** - * Create an ActiveSpeakerObserver - */ async createActiveSpeakerObserver< ActiveSpeakerObserverAppData extends AppData = AppData, >({ @@ -1396,7 +1223,7 @@ export class Router< throw new TypeError('if given, appData must be an object'); } - const rtpObserverId = generateUUIDv4(); + const rtpObserverId = utils.generateUUIDv4(); /* Build Request. */ const activeRtpObserverOptions = @@ -1415,7 +1242,7 @@ export class Router< ); const activeSpeakerObserver: ActiveSpeakerObserver = - new ActiveSpeakerObserver({ + new ActiveSpeakerObserverImpl({ internal: { ...this.#internal, rtpObserverId: rtpObserverId, @@ -1437,9 +1264,6 @@ export class Router< return activeSpeakerObserver; } - /** - * Create an AudioLevelObserver. - */ async createAudioLevelObserver< AudioLevelObserverAppData extends AppData = AppData, >({ @@ -1468,7 +1292,7 @@ export class Router< throw new TypeError('if given, appData must be an object'); } - const rtpObserverId = generateUUIDv4(); + const rtpObserverId = utils.generateUUIDv4(); /* Build Request. */ const audioLevelObserverOptions = @@ -1491,7 +1315,7 @@ export class Router< ); const audioLevelObserver: AudioLevelObserver = - new AudioLevelObserver({ + new AudioLevelObserverImpl({ internal: { ...this.#internal, rtpObserverId: rtpObserverId, @@ -1513,9 +1337,6 @@ export class Router< return audioLevelObserver; } - /** - * Check whether the given RTP capabilities can consume the given Producer. - */ canConsume({ producerId, rtpCapabilities, @@ -1532,7 +1353,7 @@ export class Router< } // Clone given RTP capabilities to not modify input data. - const clonedRtpCapabilities = clone(rtpCapabilities); + const clonedRtpCapabilities = utils.clone(rtpCapabilities); try { return ortc.canConsume( @@ -1545,6 +1366,15 @@ export class Router< return false; } } + + private handleListenerError(): void { + this.on('listenererror', (eventName, error) => { + logger.error( + `event listener threw an error [eventName:${eventName}]:`, + error + ); + }); + } } export function parseRouterDumpResponse( @@ -1552,25 +1382,25 @@ export function parseRouterDumpResponse( ): RouterDump { return { id: binary.id()!, - transportIds: parseVector(binary, 'transportIds'), - rtpObserverIds: parseVector(binary, 'rtpObserverIds'), - mapProducerIdConsumerIds: parseStringStringArrayVector( + transportIds: fbsUtils.parseVector(binary, 'transportIds'), + rtpObserverIds: fbsUtils.parseVector(binary, 'rtpObserverIds'), + mapProducerIdConsumerIds: fbsUtils.parseStringStringArrayVector( binary, 'mapProducerIdConsumerIds' ), - mapConsumerIdProducerId: parseStringStringVector( + mapConsumerIdProducerId: fbsUtils.parseStringStringVector( binary, 'mapConsumerIdProducerId' ), - mapProducerIdObserverIds: parseStringStringArrayVector( + mapProducerIdObserverIds: fbsUtils.parseStringStringArrayVector( binary, 'mapProducerIdObserverIds' ), - mapDataProducerIdDataConsumerIds: parseStringStringArrayVector( + mapDataProducerIdDataConsumerIds: fbsUtils.parseStringStringArrayVector( binary, 'mapDataProducerIdDataConsumerIds' ), - mapDataConsumerIdDataProducerId: parseStringStringVector( + mapDataConsumerIdDataProducerId: fbsUtils.parseStringStringVector( binary, 'mapDataConsumerIdDataProducerId' ), diff --git a/node/src/RouterTypes.ts b/node/src/RouterTypes.ts new file mode 100644 index 0000000000..5a7db9be1e --- /dev/null +++ b/node/src/RouterTypes.ts @@ -0,0 +1,291 @@ +import type { EnhancedEventEmitter } from './enhancedEvents'; +import type { + Transport, + TransportListenInfo, + TransportListenIp, +} from './TransportTypes'; +import type { + WebRtcTransport, + WebRtcTransportOptions, +} from './WebRtcTransportTypes'; +import type { + PlainTransport, + PlainTransportOptions, +} from './PlainTransportTypes'; +import type { PipeTransport, PipeTransportOptions } from './PipeTransportTypes'; +import type { + DirectTransport, + DirectTransportOptions, +} from './DirectTransportTypes'; +import type { Producer } from './ProducerTypes'; +import type { Consumer } from './ConsumerTypes'; +import type { DataProducer } from './DataProducerTypes'; +import type { DataConsumer } from './DataConsumerTypes'; +import type { RtpObserver } from './RtpObserverTypes'; +import type { + ActiveSpeakerObserver, + ActiveSpeakerObserverOptions, +} from './ActiveSpeakerObserverTypes'; +import type { + AudioLevelObserver, + AudioLevelObserverOptions, +} from './AudioLevelObserverTypes'; +import type { RtpCapabilities, RtpCodecCapability } from './rtpParametersTypes'; +import type { NumSctpStreams } from './sctpParametersTypes'; +import type { Either, AppData } from './types'; + +export type RouterOptions = { + /** + * Router media codecs. + */ + mediaCodecs?: RtpCodecCapability[]; + + /** + * Custom application data. + */ + appData?: RouterAppData; +}; + +export type PipeToRouterOptions = { + /** + * The id of the Producer to consume. + */ + producerId?: string; + + /** + * The id of the DataProducer to consume. + */ + dataProducerId?: string; + + /** + * Target Router instance. + */ + router: Router; + + /** + * Create a SCTP association. Default true. + */ + enableSctp?: boolean; + + /** + * SCTP streams number. + */ + numSctpStreams?: NumSctpStreams; + + /** + * Enable RTX and NACK for RTP retransmission. + */ + enableRtx?: boolean; + + /** + * Enable SRTP. + */ + enableSrtp?: boolean; +} & PipeToRouterListen; + +type PipeToRouterListen = Either; + +type PipeToRouterListenInfo = { + listenInfo: TransportListenInfo; +}; + +type PipeToRouterListenIp = { + /** + * IP used in the PipeTransport pair. Default '127.0.0.1'. + */ + listenIp?: TransportListenIp | string; +}; + +export type PipeToRouterResult = { + /** + * The Consumer created in the current Router. + */ + pipeConsumer?: Consumer; + + /** + * The Producer created in the target Router. + */ + pipeProducer?: Producer; + + /** + * The DataConsumer created in the current Router. + */ + pipeDataConsumer?: DataConsumer; + + /** + * The DataProducer created in the target Router. + */ + pipeDataProducer?: DataProducer; +}; + +export type PipeTransportPair = { + [key: string]: PipeTransport; +}; + +export type RouterDump = { + /** + * The Router id. + */ + id: string; + /** + * Id of Transports. + */ + transportIds: string[]; + /** + * Id of RtpObservers. + */ + rtpObserverIds: string[]; + /** + * Array of Producer id and its respective Consumer ids. + */ + mapProducerIdConsumerIds: { key: string; values: string[] }[]; + /** + * Array of Consumer id and its Producer id. + */ + mapConsumerIdProducerId: { key: string; value: string }[]; + /** + * Array of Producer id and its respective Observer ids. + */ + mapProducerIdObserverIds: { key: string; values: string[] }[]; + /** + * Array of Producer id and its respective DataConsumer ids. + */ + mapDataProducerIdDataConsumerIds: { key: string; values: string[] }[]; + /** + * Array of DataConsumer id and its DataProducer id. + */ + mapDataConsumerIdDataProducerId: { key: string; value: string }[]; +}; + +export type RouterEvents = { + workerclose: []; + listenererror: [string, Error]; + // Private events. + '@close': []; +}; + +export type RouterObserver = EnhancedEventEmitter; + +export type RouterObserverEvents = { + close: []; + newtransport: [Transport]; + newrtpobserver: [RtpObserver]; +}; + +export interface Router + extends EnhancedEventEmitter { + /** + * Router id. + */ + get id(): string; + + /** + * Whether the Router is closed. + */ + get closed(): boolean; + + /** + * RTP capabilities of the Router. + */ + get rtpCapabilities(): RtpCapabilities; + + /** + * App custom data. + */ + get appData(): RouterAppData; + + /** + * App custom data setter. + */ + set appData(appData: RouterAppData); + + /** + * Observer. + */ + get observer(): RouterObserver; + + /** + * Close the Router. + */ + close(): void; + + /** + * Worker was closed. + * + * @private + */ + workerClosed(): void; + + /** + * Dump Router. + */ + dump(): Promise; + + /** + * Create a WebRtcTransport. + */ + createWebRtcTransport( + options: WebRtcTransportOptions + ): Promise>; + + /** + * Create a PlainTransport. + */ + createPlainTransport( + options: PlainTransportOptions + ): Promise>; + + /** + * Create a PipeTransport. + */ + createPipeTransport( + options: PipeTransportOptions + ): Promise>; + + /** + * Create a DirectTransport. + */ + createDirectTransport( + options?: DirectTransportOptions + ): Promise>; + + /** + * Pipes the given Producer or DataProducer into another Router in same host. + */ + pipeToRouter(options: PipeToRouterOptions): Promise; + + /** + * @private + */ + addPipeTransportPair( + pipeTransportPairKey: string, + pipeTransportPairPromise: Promise + ): void; + + /** + * Create an ActiveSpeakerObserver + */ + createActiveSpeakerObserver< + ActiveSpeakerObserverAppData extends AppData = AppData, + >( + options?: ActiveSpeakerObserverOptions + ): Promise>; + + /** + * Create an AudioLevelObserver. + */ + createAudioLevelObserver( + options?: AudioLevelObserverOptions + ): Promise>; + + /** + * Check whether the given RTP capabilities can consume the given Producer. + */ + canConsume({ + producerId, + rtpCapabilities, + }: { + producerId: string; + rtpCapabilities: RtpCapabilities; + }): boolean; +} diff --git a/node/src/RtpObserver.ts b/node/src/RtpObserver.ts index a64abc3747..ec42cedc6d 100644 --- a/node/src/RtpObserver.ts +++ b/node/src/RtpObserver.ts @@ -1,31 +1,17 @@ import { Logger } from './Logger'; import { EnhancedEventEmitter } from './enhancedEvents'; -import { Channel } from './Channel'; -import { RouterInternal } from './Router'; -import { Producer } from './Producer'; -import { AppData } from './types'; +import type { + RtpObserverEvents, + RtpObserverObserver, +} from './RtpObserverTypes'; +import type { Channel } from './Channel'; +import type { RouterInternal } from './Router'; +import type { Producer } from './ProducerTypes'; +import type { AppData } from './types'; import * as FbsRequest from './fbs/request'; import * as FbsRouter from './fbs/router'; import * as FbsRtpObserver from './fbs/rtp-observer'; -export type RtpObserverEvents = { - routerclose: []; - listenererror: [string, Error]; - // Private events. - '@close': []; -}; - -export type RtpObserverObserver = - EnhancedEventEmitter; - -export type RtpObserverObserverEvents = { - close: []; - pause: []; - resume: []; - addproducer: [Producer]; - removeproducer: [Producer]; -}; - export type RtpObserverConstructorOptions = { internal: RtpObserverObserverInternal; channel: Channel; @@ -33,20 +19,13 @@ export type RtpObserverConstructorOptions = { getProducerById: (producerId: string) => Producer | undefined; }; -export type RtpObserverObserverInternal = RouterInternal & { +type RtpObserverObserverInternal = RouterInternal & { rtpObserverId: string; }; const logger = new Logger('RtpObserver'); -export type RtpObserverAddRemoveProducerOptions = { - /** - * The id of the Producer to be added or removed. - */ - producerId: string; -}; - -export abstract class RtpObserver< +export abstract class RtpObserverImpl< RtpObserverAppData extends AppData = AppData, Events extends RtpObserverEvents = RtpObserverEvents, Observer extends RtpObserverObserver = RtpObserverObserver, @@ -74,10 +53,6 @@ export abstract class RtpObserver< // Observer instance. readonly #observer: Observer; - /** - * @private - * @interface - */ constructor( { internal, @@ -98,51 +73,30 @@ export abstract class RtpObserver< this.#observer = observer; } - /** - * RtpObserver id. - */ get id(): string { return this.internal.rtpObserverId; } - /** - * Whether the RtpObserver is closed. - */ get closed(): boolean { return this.#closed; } - /** - * Whether the RtpObserver is paused. - */ get paused(): boolean { return this.#paused; } - /** - * App custom data. - */ get appData(): RtpObserverAppData { return this.#appData; } - /** - * App custom data setter. - */ set appData(appData: RtpObserverAppData) { this.#appData = appData; } - /** - * Observer. - */ get observer(): Observer { return this.#observer; } - /** - * Close the RtpObserver. - */ close(): void { if (this.#closed) { return; @@ -175,11 +129,6 @@ export abstract class RtpObserver< this.#observer.safeEmit('close'); } - /** - * Router was closed. - * - * @private - */ routerClosed(): void { if (this.#closed) { return; @@ -198,9 +147,6 @@ export abstract class RtpObserver< this.#observer.safeEmit('close'); } - /** - * Pause the RtpObserver. - */ async pause(): Promise { logger.debug('pause()'); @@ -221,9 +167,6 @@ export abstract class RtpObserver< } } - /** - * Resume the RtpObserver. - */ async resume(): Promise { logger.debug('resume()'); @@ -244,12 +187,7 @@ export abstract class RtpObserver< } } - /** - * Add a Producer to the RtpObserver. - */ - async addProducer({ - producerId, - }: RtpObserverAddRemoveProducerOptions): Promise { + async addProducer({ producerId }: { producerId: string }): Promise { logger.debug('addProducer()'); const producer = this.getProducerById(producerId); @@ -273,12 +211,7 @@ export abstract class RtpObserver< this.#observer.safeEmit('addproducer', producer); } - /** - * Remove a Producer from the RtpObserver. - */ - async removeProducer({ - producerId, - }: RtpObserverAddRemoveProducerOptions): Promise { + async removeProducer({ producerId }: { producerId: string }): Promise { logger.debug('removeProducer()'); const producer = this.getProducerById(producerId); diff --git a/node/src/RtpObserverTypes.ts b/node/src/RtpObserverTypes.ts new file mode 100644 index 0000000000..4e04f9d056 --- /dev/null +++ b/node/src/RtpObserverTypes.ts @@ -0,0 +1,105 @@ +import type { EnhancedEventEmitter } from './enhancedEvents'; +import type { Producer } from './ProducerTypes'; +import type { AppData } from './types'; + +/** + * RtpObserver type. + */ +export type RtpObserverType = 'audiolevel' | 'activespeaker'; + +export type RtpObserverEvents = { + routerclose: []; + listenererror: [string, Error]; + // Private events. + '@close': []; +}; + +export type RtpObserverObserver = + EnhancedEventEmitter; + +export type RtpObserverObserverEvents = { + close: []; + pause: []; + resume: []; + addproducer: [Producer]; + removeproducer: [Producer]; +}; + +export interface RtpObserver< + RtpObserverAppData extends AppData = AppData, + Events extends RtpObserverEvents = RtpObserverEvents, + Observer extends RtpObserverObserver = RtpObserverObserver, +> extends EnhancedEventEmitter { + /** + * RtpObserver id. + */ + get id(): string; + + /** + * Whether the RtpObserver is closed. + */ + get closed(): boolean; + + /** + * RtpObserver type. + * + * @virtual + * @privateRemarks + * - It's marked as virtual getter since each RtpObserver class overrides it. + */ + get type(): RtpObserverType; + + /** + * Whether the RtpObserver is paused. + */ + get paused(): boolean; + + /** + * App custom data. + */ + get appData(): RtpObserverAppData; + + /** + * App custom data setter. + */ + set appData(appData: RtpObserverAppData); + + /** + * Observer. + * + * @virtual + */ + get observer(): Observer; + + /** + * Close the RtpObserver. + */ + close(): void; + + /** + * Router was closed. + * + * @private + */ + routerClosed(): void; + + /** + * Pause the RtpObserver. + */ + pause(): Promise; + + /** + * Resume the RtpObserver. + */ + resume(): Promise; + + /** + * Add a Producer to the RtpObserver. + */ + addProducer({ producerId }: { producerId: string }): Promise; + + /** + * Remove a Producer from the RtpObserver. + */ + removeProducer({ producerId }: { producerId: string }): Promise; +} diff --git a/node/src/Transport.ts b/node/src/Transport.ts index 7448e8a438..7bbc28f4dd 100644 --- a/node/src/Transport.ts +++ b/node/src/Transport.ts @@ -2,53 +2,83 @@ import * as flatbuffers from 'flatbuffers'; import { Logger } from './Logger'; import { EnhancedEventEmitter } from './enhancedEvents'; import * as ortc from './ortc'; -import { Channel } from './Channel'; -import { RouterInternal } from './Router'; -import { WebRtcTransportData } from './WebRtcTransport'; -import { PlainTransportData } from './PlainTransport'; -import { PipeTransportData } from './PipeTransport'; -import { DirectTransportData } from './DirectTransport'; +import type { + Transport, + TransportType, + TransportProtocol, + TransportPortRange, + TransportSocketFlags, + TransportTuple, + SctpState, + RtpListenerDump, + SctpListenerDump, + RecvRtpHeaderExtensions, + BaseTransportDump, + BaseTransportStats, + TransportTraceEventType, + TransportTraceEventData, + TransportEvents, + TransportObserver, +} from './TransportTypes'; +import type { Channel } from './Channel'; +import type { RouterInternal } from './Router'; +import type { WebRtcTransportData } from './WebRtcTransport'; +import type { PlainTransportData } from './PlainTransport'; +import type { PipeTransportData } from './PipeTransport'; +import type { DirectTransportData } from './DirectTransport'; +import type { Producer, ProducerOptions } from './ProducerTypes'; import { - Producer, - ProducerOptions, + ProducerImpl, producerTypeFromFbs, producerTypeToFbs, } from './Producer'; -import { +import type { Consumer, ConsumerOptions, ConsumerType, ConsumerLayers, -} from './Consumer'; -import { +} from './ConsumerTypes'; +import { ConsumerImpl } from './Consumer'; +import type { DataProducer, DataProducerOptions, DataProducerType, +} from './DataProducerTypes'; +import { + DataProducerImpl, dataProducerTypeToFbs, parseDataProducerDumpResponse, } from './DataProducer'; -import { +import type { DataConsumer, DataConsumerOptions, DataConsumerType, +} from './DataConsumerTypes'; +import { + DataConsumerImpl, dataConsumerTypeToFbs, parseDataConsumerDumpResponse, } from './DataConsumer'; -import { +import type { MediaKind, RtpCapabilities, RtpParameters, +} from './rtpParametersTypes'; +import { serializeRtpEncodingParameters, serializeRtpParameters, -} from './RtpParameters'; +} from './rtpParametersFbsUtils'; +import type { + SctpParameters, + SctpStreamParameters, +} from './sctpParametersTypes'; import { parseSctpParametersDump, serializeSctpStreamParameters, - SctpParameters, - SctpStreamParameters, -} from './SctpParameters'; -import { AppData } from './types'; +} from './sctpParametersFbsUtils'; +import type { AppData } from './types'; import * as utils from './utils'; +import * as fbsUtils from './fbsUtils'; import { TraceDirection as FbsTraceDirection } from './fbs/common'; import * as FbsRequest from './fbs/request'; import { MediaKind as FbsMediaKind } from './fbs/rtp-parameters/media-kind'; @@ -60,180 +90,6 @@ import * as FbsRouter from './fbs/router'; import * as FbsRtpParameters from './fbs/rtp-parameters'; import { SctpState as FbsSctpState } from './fbs/sctp-association/sctp-state'; -export type TransportListenInfo = { - /** - * Network protocol. - */ - protocol: TransportProtocol; - - /** - * Listening IPv4 or IPv6. - */ - ip: string; - - /** - * @deprecated Use |announcedAddress| instead. - * - * Announced IPv4, IPv6 or hostname (useful when running mediasoup behind NAT - * with private IP). - */ - announcedIp?: string; - - /** - * Announced IPv4, IPv6 or hostname (useful when running mediasoup behind NAT - * with private IP). - */ - announcedAddress?: string; - - /** - * Listening port. - */ - port?: number; - - /** - * Listening port range. If given then |port| will be ignored. - */ - portRange?: TransportPortRange; - - /** - * Socket flags. - */ - flags?: TransportSocketFlags; - - /** - * Send buffer size (bytes). - */ - sendBufferSize?: number; - - /** - * Recv buffer size (bytes). - */ - recvBufferSize?: number; -}; - -/** - * Use TransportListenInfo instead. - * @deprecated - */ -export type TransportListenIp = { - /** - * Listening IPv4 or IPv6. - */ - ip: string; - - /** - * Announced IPv4, IPv6 or hostname (useful when running mediasoup behind NAT - * with private IP). - */ - announcedIp?: string; -}; - -/** - * Transport protocol. - */ -export type TransportProtocol = 'udp' | 'tcp'; - -/** - * Port range.. - */ -export type TransportPortRange = { - /** - * Lowest port in the range. - */ - min: number; - /** - * Highest port in the range. - */ - max: number; -}; - -/** - * UDP/TCP socket flags. - */ -export type TransportSocketFlags = { - /** - * Disable dual-stack support so only IPv6 is used (only if |ip| is IPv6). - */ - ipv6Only?: boolean; - /** - * Make different transports bind to the same IP and port (only for UDP). - * Useful for multicast scenarios with plain transport. Use with caution. - */ - udpReusePort?: boolean; -}; - -export type TransportTuple = { - // @deprecated Use localAddress instead. - localIp: string; - localAddress: string; - localPort: number; - remoteIp?: string; - remotePort?: number; - protocol: TransportProtocol; -}; - -/** - * Valid types for 'trace' event. - */ -export type TransportTraceEventType = 'probation' | 'bwe'; - -/** - * 'trace' event data. - */ -export type TransportTraceEventData = { - /** - * Trace type. - */ - type: TransportTraceEventType; - - /** - * Event timestamp. - */ - timestamp: number; - - /** - * Event direction. - */ - direction: 'in' | 'out'; - - /** - * Per type information. - */ - info: any; -}; - -export type SctpState = - | 'new' - | 'connecting' - | 'connected' - | 'failed' - | 'closed'; - -export type TransportEvents = { - routerclose: []; - listenserverclose: []; - trace: [TransportTraceEventData]; - listenererror: [string, Error]; - // Private events. - '@close': []; - '@newproducer': [Producer]; - '@producerclose': [Producer]; - '@newdataproducer': [DataProducer]; - '@dataproducerclose': [DataProducer]; - '@listenserverclose': []; -}; - -export type TransportObserver = EnhancedEventEmitter; - -export type TransportObserverEvents = { - close: []; - newproducer: [Producer]; - newconsumer: [Consumer]; - newdataproducer: [DataProducer]; - newdataconsumer: [DataConsumer]; - trace: [TransportTraceEventData]; -}; - export type TransportConstructorOptions = { internal: TransportInternal; data: TransportData; @@ -248,82 +104,22 @@ export type TransportInternal = RouterInternal & { transportId: string; }; -export type BaseTransportDump = { - id: string; - direct: boolean; - producerIds: string[]; - consumerIds: string[]; - mapSsrcConsumerId: { key: number; value: string }[]; - mapRtxSsrcConsumerId: { key: number; value: string }[]; - recvRtpHeaderExtensions: RecvRtpHeaderExtensions; - rtpListener: RtpListenerDump; - maxMessageSize: number; - dataProducerIds: string[]; - dataConsumerIds: string[]; - sctpParameters?: SctpParameters; - sctpState?: SctpState; - sctpListener?: SctpListenerDump; - traceEventTypes?: string[]; -}; - -export type BaseTransportStats = { - transportId: string; - timestamp: number; - sctpState?: SctpState; - bytesReceived: number; - recvBitrate: number; - bytesSent: number; - sendBitrate: number; - rtpBytesReceived: number; - rtpRecvBitrate: number; - rtpBytesSent: number; - rtpSendBitrate: number; - rtxBytesReceived: number; - rtxRecvBitrate: number; - rtxBytesSent: number; - rtxSendBitrate: number; - probationBytesSent: number; - probationSendBitrate: number; - availableOutgoingBitrate?: number; - availableIncomingBitrate?: number; - maxIncomingBitrate?: number; - maxOutgoingBitrate?: number; - minOutgoingBitrate?: number; - rtpPacketLossReceived?: number; - rtpPacketLossSent?: number; -}; - type TransportData = | WebRtcTransportData | PlainTransportData | PipeTransportData | DirectTransportData; -type RtpListenerDump = { - ssrcTable: { key: number; value: string }[]; - midTable: { key: number; value: string }[]; - ridTable: { key: number; value: string }[]; -}; - -type SctpListenerDump = { - streamIdTable: { key: number; value: string }[]; -}; - -type RecvRtpHeaderExtensions = { - mid?: number; - rid?: number; - rrid?: number; - absSendTime?: number; - transportWideCc01?: number; -}; - const logger = new Logger('Transport'); -export abstract class Transport< - TransportAppData extends AppData = AppData, - Events extends TransportEvents = TransportEvents, - Observer extends TransportObserver = TransportObserver, -> extends EnhancedEventEmitter { +export abstract class TransportImpl< + TransportAppData extends AppData = AppData, + Events extends TransportEvents = TransportEvents, + Observer extends TransportObserver = TransportObserver, + > + extends EnhancedEventEmitter + implements Transport +{ // Internal data. protected readonly internal: TransportInternal; @@ -379,10 +175,6 @@ export abstract class Transport< // Observer instance. readonly #observer: Observer; - /** - * @private - * @interface - */ constructor( { internal, @@ -409,52 +201,35 @@ export abstract class Transport< this.#observer = observer; } - /** - * Transport id. - */ get id(): string { return this.internal.transportId; } - /** - * Whether the Transport is closed. - */ get closed(): boolean { return this.#closed; } - /** - * App custom data. - */ + abstract get type(): TransportType; + get appData(): TransportAppData { return this.#appData; } - /** - * App custom data setter. - */ set appData(appData: TransportAppData) { this.#appData = appData; } - /** - * Observer. - */ get observer(): Observer { return this.#observer; } /** - * @private * Just for testing purposes. */ get channelForTesting(): Channel { return this.channel; } - /** - * Close the Transport. - */ close(): void { if (this.#closed) { return; @@ -517,12 +292,6 @@ export abstract class Transport< this.#observer.safeEmit('close'); } - /** - * Router was closed. - * - * @private - * @virtual - */ routerClosed(): void { if (this.#closed) { return; @@ -571,12 +340,6 @@ export abstract class Transport< this.#observer.safeEmit('close'); } - /** - * Listen server was closed (this just happens in WebRtcTransports when their - * associated WebRtcServer is closed). - * - * @private - */ listenServerClosed(): void { if (this.#closed) { return; @@ -630,42 +393,12 @@ export abstract class Transport< this.#observer.safeEmit('close'); } - /** - * Dump Transport. - * - * @abstract - */ - // eslint-disable-next-line @typescript-eslint/require-await - async dump(): Promise { - // Should not happen. - throw new Error('method implemented in the subclass'); - } + abstract dump(): Promise; - /** - * Get Transport stats. - * - * @abstract - */ - // eslint-disable-next-line @typescript-eslint/require-await - async getStats(): Promise { - // Should not happen. - throw new Error('method implemented in the subclass'); - } + abstract getStats(): Promise; - /** - * Provide the Transport remote parameters. - * - * @abstract - */ - // eslint-disable-next-line @typescript-eslint/no-unused-vars, @typescript-eslint/require-await - async connect(params: any): Promise { - // Should not happen. - throw new Error('method implemented in the subclass'); - } + abstract connect(params: any): Promise; - /** - * Set maximum incoming bitrate for receiving media. - */ async setMaxIncomingBitrate(bitrate: number): Promise { logger.debug(`setMaxIncomingBitrate() [bitrate:${bitrate}]`); @@ -684,9 +417,6 @@ export abstract class Transport< ); } - /** - * Set maximum outgoing bitrate for sending media. - */ async setMaxOutgoingBitrate(bitrate: number): Promise { logger.debug(`setMaxOutgoingBitrate() [bitrate:${bitrate}]`); @@ -703,9 +433,6 @@ export abstract class Transport< ); } - /** - * Set minimum outgoing bitrate for sending media. - */ async setMinOutgoingBitrate(bitrate: number): Promise { logger.debug(`setMinOutgoingBitrate() [bitrate:${bitrate}]`); @@ -722,9 +449,6 @@ export abstract class Transport< ); } - /** - * Create a Producer. - */ async produce({ id = undefined, kind, @@ -760,7 +484,7 @@ export abstract class Transport< // Don't do this in PipeTransports since there we must keep CNAME value in // each Producer. - if (this.constructor.name !== 'PipeTransport') { + if (this.type !== 'pipe') { // If CNAME is given and we don't have yet a CNAME for Producers in this // Transport, take it. if (!this.#cnameForProducers && clonedRtpParameters.rtcp?.cname) { @@ -825,7 +549,7 @@ export abstract class Transport< consumableRtpParameters, }; - const producer: Producer = new Producer({ + const producer: Producer = new ProducerImpl({ internal: { ...this.internal, producerId, @@ -850,11 +574,6 @@ export abstract class Transport< return producer; } - /** - * Create a Consumer. - * - * @virtual - */ async consume({ producerId, rtpCapabilities, @@ -952,7 +671,7 @@ export abstract class Transport< type: pipe ? 'pipe' : (producer.type as ConsumerType), }; - const consumer: Consumer = new Consumer({ + const consumer: Consumer = new ConsumerImpl({ internal: { ...this.internal, consumerId, @@ -981,9 +700,6 @@ export abstract class Transport< return consumer; } - /** - * Create a DataProducer. - */ async produceData({ id = undefined, sctpStreamParameters, @@ -1010,7 +726,7 @@ export abstract class Transport< >(sctpStreamParameters); // If this is not a DirectTransport, sctpStreamParameters are required. - if (this.constructor.name !== 'DirectTransport') { + if (this.type !== 'direct') { type = 'sctp'; // This may throw. @@ -1054,21 +770,22 @@ export abstract class Transport< const dump = parseDataProducerDumpResponse(produceDataResponse); - const dataProducer: DataProducer = new DataProducer({ - internal: { - ...this.internal, - dataProducerId, - }, - data: { - type: dump.type, - sctpStreamParameters: dump.sctpStreamParameters, - label: dump.label, - protocol: dump.protocol, - }, - channel: this.channel, - paused, - appData, - }); + const dataProducer: DataProducer = + new DataProducerImpl({ + 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', () => { @@ -1084,9 +801,6 @@ export abstract class Transport< return dataProducer; } - /** - * Create a DataConsumer. - */ async consumeData({ dataProducerId, ordered, @@ -1118,7 +832,7 @@ export abstract class Transport< // 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') { + if (this.type !== 'direct') { type = 'sctp'; sctpStreamParameters = @@ -1189,25 +903,26 @@ export abstract class Transport< const dump = parseDataConsumerDumpResponse(consumeDataResponse); - const dataConsumer: DataConsumer = new 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, - }); + const dataConsumer: DataConsumer = + new DataConsumerImpl({ + 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', () => { @@ -1231,9 +946,6 @@ export abstract class Transport< return dataConsumer; } - /** - * Enable 'trace' event. - */ async enableTraceEvent(types: TransportTraceEventType[] = []): Promise { logger.debug('enableTraceEvent()'); @@ -1381,23 +1093,29 @@ export function parseBaseTransportDump( binary: FbsTransport.Dump ): BaseTransportDump { // Retrieve producerIds. - const producerIds = utils.parseVector(binary, 'producerIds'); + const producerIds = fbsUtils.parseVector(binary, 'producerIds'); // Retrieve consumerIds. - const consumerIds = utils.parseVector(binary, 'consumerIds'); + const consumerIds = fbsUtils.parseVector(binary, 'consumerIds'); // Retrieve map SSRC consumerId. - const mapSsrcConsumerId = utils.parseUint32StringVector( + const mapSsrcConsumerId = fbsUtils.parseUint32StringVector( binary, 'mapSsrcConsumerId' ); // Retrieve map RTX SSRC consumerId. - const mapRtxSsrcConsumerId = utils.parseUint32StringVector( + const mapRtxSsrcConsumerId = fbsUtils.parseUint32StringVector( binary, 'mapRtxSsrcConsumerId' ); // Retrieve dataProducerIds. - const dataProducerIds = utils.parseVector(binary, 'dataProducerIds'); + const dataProducerIds = fbsUtils.parseVector( + binary, + 'dataProducerIds' + ); // Retrieve dataConsumerIds. - const dataConsumerIds = utils.parseVector(binary, 'dataConsumerIds'); + const dataConsumerIds = fbsUtils.parseVector( + binary, + 'dataConsumerIds' + ); // Retrieve recvRtpHeaderExtesions. const recvRtpHeaderExtensions = parseRecvRtpHeaderExtensions( binary.recvRtpHeaderExtensions()! @@ -1425,7 +1143,7 @@ export function parseBaseTransportDump( : undefined; // Retrieve traceEventTypes. - const traceEventTypes = utils.parseVector( + const traceEventTypes = fbsUtils.parseVector( binary, 'traceEventTypes', transportTraceEventTypeFromFbs @@ -1433,7 +1151,6 @@ export function parseBaseTransportDump( return { id: binary.id()!, - direct: binary.direct(), producerIds: producerIds, consumerIds: consumerIds, mapSsrcConsumerId: mapSsrcConsumerId, @@ -1703,7 +1420,7 @@ function createProduceRequest({ producerId: string; kind: MediaKind; rtpParameters: RtpParameters; - rtpMapping: ortc.RtpMapping; + rtpMapping: ortc.RtpCodecsEncodingsMapping; keyFrameRequestDelay?: number; paused: boolean; }): number { @@ -1850,11 +1567,11 @@ function parseRtpListenerDump( binary: FbsTransport.RtpListener ): RtpListenerDump { // Retrieve ssrcTable. - const ssrcTable = utils.parseUint32StringVector(binary, 'ssrcTable'); + const ssrcTable = fbsUtils.parseUint32StringVector(binary, 'ssrcTable'); // Retrieve midTable. - const midTable = utils.parseUint32StringVector(binary, 'midTable'); + const midTable = fbsUtils.parseUint32StringVector(binary, 'midTable'); // Retrieve ridTable. - const ridTable = utils.parseUint32StringVector(binary, 'ridTable'); + const ridTable = fbsUtils.parseUint32StringVector(binary, 'ridTable'); return { ssrcTable, @@ -1867,7 +1584,10 @@ function parseSctpListenerDump( binary: FbsTransport.SctpListener ): SctpListenerDump { // Retrieve streamIdTable. - const streamIdTable = utils.parseUint32StringVector(binary, 'streamIdTable'); + const streamIdTable = fbsUtils.parseUint32StringVector( + binary, + 'streamIdTable' + ); return { streamIdTable }; } diff --git a/node/src/TransportTypes.ts b/node/src/TransportTypes.ts new file mode 100644 index 0000000000..26aac8cbd5 --- /dev/null +++ b/node/src/TransportTypes.ts @@ -0,0 +1,399 @@ +import type { EnhancedEventEmitter } from './enhancedEvents'; +import type { Producer, ProducerOptions } from './ProducerTypes'; +import type { Consumer, ConsumerOptions } from './ConsumerTypes'; +import type { DataProducer, DataProducerOptions } from './DataProducerTypes'; +import type { DataConsumer, DataConsumerOptions } from './DataConsumerTypes'; +import type { SctpParameters } from './sctpParametersTypes'; +import type { AppData } from './types'; + +/** + * Transport type. + */ +export type TransportType = 'webrtc' | 'plain' | 'pipe' | 'direct'; + +export type TransportListenInfo = { + /** + * Network protocol. + */ + protocol: TransportProtocol; + + /** + * Listening IPv4 or IPv6. + */ + ip: string; + + /** + * @deprecated Use |announcedAddress| instead. + * + * Announced IPv4, IPv6 or hostname (useful when running mediasoup behind NAT + * with private IP). + */ + announcedIp?: string; + + /** + * Announced IPv4, IPv6 or hostname (useful when running mediasoup behind NAT + * with private IP). + */ + announcedAddress?: string; + + /** + * Listening port. + */ + port?: number; + + /** + * Listening port range. If given then |port| will be ignored. + */ + portRange?: TransportPortRange; + + /** + * Socket flags. + */ + flags?: TransportSocketFlags; + + /** + * Send buffer size (bytes). + */ + sendBufferSize?: number; + + /** + * Recv buffer size (bytes). + */ + recvBufferSize?: number; +}; + +/** + * Use TransportListenInfo instead. + * @deprecated + */ +export type TransportListenIp = { + /** + * Listening IPv4 or IPv6. + */ + ip: string; + + /** + * Announced IPv4, IPv6 or hostname (useful when running mediasoup behind NAT + * with private IP). + */ + announcedIp?: string; +}; + +/** + * Transport protocol. + */ +export type TransportProtocol = 'udp' | 'tcp'; + +/** + * Port range.. + */ +export type TransportPortRange = { + /** + * Lowest port in the range. + */ + min: number; + /** + * Highest port in the range. + */ + max: number; +}; + +/** + * UDP/TCP socket flags. + */ +export type TransportSocketFlags = { + /** + * Disable dual-stack support so only IPv6 is used (only if |ip| is IPv6). + */ + ipv6Only?: boolean; + /** + * Make different transports bind to the same IP and port (only for UDP). + * Useful for multicast scenarios with plain transport. Use with caution. + */ + udpReusePort?: boolean; +}; + +export type TransportTuple = { + // @deprecated Use localAddress instead. + localIp: string; + localAddress: string; + localPort: number; + remoteIp?: string; + remotePort?: number; + protocol: TransportProtocol; +}; + +export type SctpState = + | 'new' + | 'connecting' + | 'connected' + | 'failed' + | 'closed'; + +export type RtpListenerDump = { + ssrcTable: { key: number; value: string }[]; + midTable: { key: number; value: string }[]; + ridTable: { key: number; value: string }[]; +}; + +export type SctpListenerDump = { + streamIdTable: { key: number; value: string }[]; +}; + +export type RecvRtpHeaderExtensions = { + mid?: number; + rid?: number; + rrid?: number; + absSendTime?: number; + transportWideCc01?: number; +}; + +export type BaseTransportDump = { + id: string; + producerIds: string[]; + consumerIds: string[]; + mapSsrcConsumerId: { key: number; value: string }[]; + mapRtxSsrcConsumerId: { key: number; value: string }[]; + recvRtpHeaderExtensions: RecvRtpHeaderExtensions; + rtpListener: RtpListenerDump; + maxMessageSize: number; + dataProducerIds: string[]; + dataConsumerIds: string[]; + sctpParameters?: SctpParameters; + sctpState?: SctpState; + sctpListener?: SctpListenerDump; + traceEventTypes?: string[]; +}; + +export type BaseTransportStats = { + transportId: string; + timestamp: number; + sctpState?: SctpState; + bytesReceived: number; + recvBitrate: number; + bytesSent: number; + sendBitrate: number; + rtpBytesReceived: number; + rtpRecvBitrate: number; + rtpBytesSent: number; + rtpSendBitrate: number; + rtxBytesReceived: number; + rtxRecvBitrate: number; + rtxBytesSent: number; + rtxSendBitrate: number; + probationBytesSent: number; + probationSendBitrate: number; + availableOutgoingBitrate?: number; + availableIncomingBitrate?: number; + maxIncomingBitrate?: number; + maxOutgoingBitrate?: number; + minOutgoingBitrate?: number; + rtpPacketLossReceived?: number; + rtpPacketLossSent?: number; +}; + +/** + * Valid types for 'trace' event. + */ +export type TransportTraceEventType = 'probation' | 'bwe'; + +/** + * 'trace' event data. + */ +export type TransportTraceEventData = { + /** + * Trace type. + */ + type: TransportTraceEventType; + + /** + * Event timestamp. + */ + timestamp: number; + + /** + * Event direction. + */ + direction: 'in' | 'out'; + + /** + * Per type information. + */ + info: unknown; +}; + +export type TransportEvents = { + routerclose: []; + listenserverclose: []; + trace: [TransportTraceEventData]; + listenererror: [string, Error]; + // Private events. + '@close': []; + '@newproducer': [Producer]; + '@producerclose': [Producer]; + '@newdataproducer': [DataProducer]; + '@dataproducerclose': [DataProducer]; + '@listenserverclose': []; +}; + +export type TransportObserver = EnhancedEventEmitter; + +export type TransportObserverEvents = { + close: []; + newproducer: [Producer]; + newconsumer: [Consumer]; + newdataproducer: [DataProducer]; + newdataconsumer: [DataConsumer]; + trace: [TransportTraceEventData]; +}; + +export interface Transport< + TransportAppData extends AppData = AppData, + Events extends TransportEvents = TransportEvents, + Observer extends TransportObserver = TransportObserver, +> extends EnhancedEventEmitter { + /** + * Transport id. + */ + get id(): string; + + /** + * Whether the Transport is closed. + */ + get closed(): boolean; + + /** + * Transport type. + * + * @virtual + * @privateRemarks + * - It's marked as virtual getter since each Transport class overrides it. + */ + get type(): TransportType; + + /** + * App custom data. + */ + get appData(): TransportAppData; + + /** + * App custom data setter. + */ + set appData(appData: TransportAppData); + + /** + * Observer. + * + * @virtual + */ + get observer(): Observer; + + /** + * Close the Transport. + * + * @virtual + */ + close(): void; + + /** + * Router was closed. + * + * @private + * @virtual + */ + routerClosed(): void; + + /** + * Listen server was closed (this just happens in WebRtcTransports when their + * associated WebRtcServer is closed). + * + * @private + * @virtual + */ + listenServerClosed(): void; + + /** + * Dump Transport. + * + * @abstract + */ + dump(): Promise; + + /** + * Get Transport stats. + * + * @abstract + */ + getStats(): Promise; + + /** + * Provide the Transport remote parameters. + * + * @abstract + */ + connect(params: unknown): Promise; + + /** + * Set maximum incoming bitrate for receiving media. + * + * @virtual + * @privateRemarks + * - It's marked as virtual method because DirectTransport overrides it. + */ + setMaxIncomingBitrate(bitrate: number): Promise; + + /** + * Set maximum outgoing bitrate for sending media. + * + * @virtual + * @privateRemarks + * - It's marked as virtual method because DirectTransport overrides it. + */ + setMaxOutgoingBitrate(bitrate: number): Promise; + + /** + * Set minimum outgoing bitrate for sending media. + * + * @virtual + * @privateRemarks + * - It's marked as virtual method because DirectTransport overrides it. + */ + setMinOutgoingBitrate(bitrate: number): Promise; + + /** + * Create a Producer. + */ + produce( + options: ProducerOptions + ): Promise>; + + /** + * Create a Consumer. + * + * @virtual + * @privateRemarks + * - It's marked as virtual method because PipeTransport overrides it. + */ + consume( + options: ConsumerOptions + ): Promise>; + + /** + * Create a DataProducer. + */ + produceData( + options?: DataProducerOptions + ): Promise>; + + /** + * Create a DataConsumer. + */ + consumeData( + options: DataConsumerOptions + ): Promise>; + + /** + * Enable 'trace' event. + */ + enableTraceEvent(types?: TransportTraceEventType[]): Promise; +} diff --git a/node/src/WebRtcServer.ts b/node/src/WebRtcServer.ts index 385a49e638..1a04379e2a 100644 --- a/node/src/WebRtcServer.ts +++ b/node/src/WebRtcServer.ts @@ -1,81 +1,33 @@ import { Logger } from './Logger'; import { EnhancedEventEmitter } from './enhancedEvents'; -import { Channel } from './Channel'; -import { TransportListenInfo } from './Transport'; -import { WebRtcTransport } from './WebRtcTransport'; -import { AppData } from './types'; -import * as utils from './utils'; +import type { Channel } from './Channel'; +import type { + WebRtcServer, + IpPort, + IceUserNameFragment, + TupleHash, + WebRtcServerDump, + WebRtcServerEvents, + WebRtcServerObserver, + WebRtcServerObserverEvents, +} from './WebRtcServerTypes'; +import type { WebRtcTransport } from './WebRtcTransportTypes'; +import type { AppData } from './types'; +import * as fbsUtils from './fbsUtils'; import { Body as RequestBody, Method } from './fbs/request'; import * as FbsWorker from './fbs/worker'; import * as FbsWebRtcServer from './fbs/web-rtc-server'; -export type WebRtcServerOptions = - { - /** - * Listen infos. - */ - listenInfos: TransportListenInfo[]; - - /** - * Custom application data. - */ - appData?: WebRtcServerAppData; - }; - -/** - * @deprecated Use TransportListenInfo instead. - */ -export type WebRtcServerListenInfo = TransportListenInfo; - -export type WebRtcServerEvents = { - workerclose: []; - listenererror: [string, Error]; - // Private events. - '@close': []; -}; - -export type WebRtcServerObserver = - EnhancedEventEmitter; - -export type WebRtcServerObserverEvents = { - close: []; - webrtctransporthandled: [WebRtcTransport]; - webrtctransportunhandled: [WebRtcTransport]; -}; - -export type WebRtcServerDump = { - id: string; - udpSockets: IpPort[]; - tcpServers: IpPort[]; - webRtcTransportIds: string[]; - localIceUsernameFragments: IceUserNameFragment[]; - tupleHashes: TupleHash[]; -}; - -type IpPort = { - ip: string; - port: number; -}; - -type IceUserNameFragment = { - localIceUsernameFragment: string; - webRtcTransportId: string; -}; - -type TupleHash = { - tupleHash: number; - webRtcTransportId: string; -}; - type WebRtcServerInternal = { webRtcServerId: string; }; const logger = new Logger('WebRtcServer'); -export class WebRtcServer< - WebRtcServerAppData extends AppData = AppData, -> extends EnhancedEventEmitter { +export class WebRtcServerImpl + extends EnhancedEventEmitter + implements WebRtcServer +{ // Internal data. readonly #internal: WebRtcServerInternal; @@ -95,9 +47,6 @@ export class WebRtcServer< readonly #observer: WebRtcServerObserver = new EnhancedEventEmitter(); - /** - * @private - */ constructor({ internal, channel, @@ -114,54 +63,37 @@ export class WebRtcServer< this.#internal = internal; this.#channel = channel; this.#appData = appData ?? ({} as WebRtcServerAppData); + + this.handleListenerError(); } - /** - * WebRtcServer id. - */ get id(): string { return this.#internal.webRtcServerId; } - /** - * Whether the WebRtcServer is closed. - */ get closed(): boolean { return this.#closed; } - /** - * App custom data. - */ get appData(): WebRtcServerAppData { return this.#appData; } - /** - * App custom data setter. - */ set appData(appData: WebRtcServerAppData) { this.#appData = appData; } - /** - * Observer. - */ get observer(): WebRtcServerObserver { return this.#observer; } /** - * @private * Just for testing purposes. */ get webRtcTransportsForTesting(): Map { return this.#webRtcTransports; } - /** - * Close the WebRtcServer. - */ close(): void { if (this.#closed) { return; @@ -199,11 +131,6 @@ export class WebRtcServer< this.#observer.safeEmit('close'); } - /** - * Worker was closed. - * - * @private - */ workerClosed(): void { if (this.#closed) { return; @@ -223,9 +150,6 @@ export class WebRtcServer< this.#observer.safeEmit('close'); } - /** - * Dump WebRtcServer. - */ async dump(): Promise { logger.debug('dump()'); @@ -244,9 +168,6 @@ export class WebRtcServer< return parseWebRtcServerDump(dump); } - /** - * @private - */ handleWebRtcTransport(webRtcTransport: WebRtcTransport): void { this.#webRtcTransports.set(webRtcTransport.id, webRtcTransport); @@ -260,6 +181,15 @@ export class WebRtcServer< this.#observer.safeEmit('webrtctransportunhandled', webRtcTransport); }); } + + private handleListenerError(): void { + this.on('listenererror', (eventName, error) => { + logger.error( + `event listener threw an error [eventName:${eventName}]:`, + error + ); + }); + } } function parseIpPort(binary: FbsWebRtcServer.IpPort): IpPort { @@ -290,14 +220,14 @@ function parseWebRtcServerDump( ): WebRtcServerDump { return { id: data.id()!, - udpSockets: utils.parseVector(data, 'udpSockets', parseIpPort), - tcpServers: utils.parseVector(data, 'tcpServers', parseIpPort), - webRtcTransportIds: utils.parseVector(data, 'webRtcTransportIds'), - localIceUsernameFragments: utils.parseVector( + udpSockets: fbsUtils.parseVector(data, 'udpSockets', parseIpPort), + tcpServers: fbsUtils.parseVector(data, 'tcpServers', parseIpPort), + webRtcTransportIds: fbsUtils.parseVector(data, 'webRtcTransportIds'), + localIceUsernameFragments: fbsUtils.parseVector( data, 'localIceUsernameFragments', parseIceUserNameFragment ), - tupleHashes: utils.parseVector(data, 'tupleHashes', parseTupleHash), + tupleHashes: fbsUtils.parseVector(data, 'tupleHashes', parseTupleHash), }; } diff --git a/node/src/WebRtcServerTypes.ts b/node/src/WebRtcServerTypes.ts new file mode 100644 index 0000000000..936e4af7cd --- /dev/null +++ b/node/src/WebRtcServerTypes.ts @@ -0,0 +1,112 @@ +import type { EnhancedEventEmitter } from './enhancedEvents'; +import type { TransportListenInfo } from './TransportTypes'; +import type { WebRtcTransport } from './WebRtcTransportTypes'; +import type { AppData } from './types'; + +export type WebRtcServerOptions = + { + /** + * Listen infos. + */ + listenInfos: TransportListenInfo[]; + + /** + * Custom application data. + */ + appData?: WebRtcServerAppData; + }; + +/** + * @deprecated Use TransportListenInfo instead. + */ +export type WebRtcServerListenInfo = TransportListenInfo; + +export type IpPort = { + ip: string; + port: number; +}; + +export type IceUserNameFragment = { + localIceUsernameFragment: string; + webRtcTransportId: string; +}; + +export type TupleHash = { + tupleHash: number; + webRtcTransportId: string; +}; + +export type WebRtcServerDump = { + id: string; + udpSockets: IpPort[]; + tcpServers: IpPort[]; + webRtcTransportIds: string[]; + localIceUsernameFragments: IceUserNameFragment[]; + tupleHashes: TupleHash[]; +}; + +export type WebRtcServerEvents = { + workerclose: []; + listenererror: [string, Error]; + // Private events. + '@close': []; +}; + +export type WebRtcServerObserver = + EnhancedEventEmitter; + +export type WebRtcServerObserverEvents = { + close: []; + webrtctransporthandled: [WebRtcTransport]; + webrtctransportunhandled: [WebRtcTransport]; +}; + +export interface WebRtcServer + extends EnhancedEventEmitter { + /** + * WebRtcServer id. + */ + get id(): string; + + /** + * Whether the WebRtcServer is closed. + */ + get closed(): boolean; + + /** + * App custom data. + */ + get appData(): WebRtcServerAppData; + + /** + * App custom data setter. + */ + set appData(appData: WebRtcServerAppData); + + /** + * Observer. + */ + get observer(): WebRtcServerObserver; + + /** + * Close the WebRtcServer. + */ + close(): void; + + /** + * Worker was closed. + * + * @private + */ + workerClosed(): void; + + /** + * Dump WebRtcServer. + */ + dump(): Promise; + + /** + * @private + */ + handleWebRtcTransport(webRtcTransport: WebRtcTransport): void; +} diff --git a/node/src/WebRtcTransport.ts b/node/src/WebRtcTransport.ts index 9f908ec19c..522882cd65 100644 --- a/node/src/WebRtcTransport.ts +++ b/node/src/WebRtcTransport.ts @@ -1,29 +1,39 @@ import * as flatbuffers from 'flatbuffers'; import { Logger } from './Logger'; import { EnhancedEventEmitter } from './enhancedEvents'; +import type { + WebRtcTransport, + IceParameters, + IceCandidate, + DtlsParameters, + FingerprintAlgorithm, + DtlsFingerprint, + IceRole, + IceState, + IceCandidateType, + IceCandidateTcpType, + DtlsRole, + DtlsState, + WebRtcTransportDump, + WebRtcTransportStat, + WebRtcTransportEvents, + WebRtcTransportObserver, + WebRtcTransportObserverEvents, +} from './WebRtcTransportTypes'; +import type { Transport, TransportTuple, SctpState } from './TransportTypes'; import { + TransportImpl, + TransportConstructorOptions, parseSctpState, parseBaseTransportDump, parseBaseTransportStats, parseProtocol, parseTransportTraceEventData, parseTuple, - BaseTransportDump, - BaseTransportStats, - Transport, - TransportListenInfo, - TransportListenIp, - TransportProtocol, - TransportTuple, - TransportEvents, - TransportObserverEvents, - TransportConstructorOptions, - SctpState, } from './Transport'; -import { WebRtcServer } from './WebRtcServer'; -import { SctpParameters, NumSctpStreams } from './SctpParameters'; -import { AppData, Either } from './types'; -import { parseVector } from './utils'; +import type { SctpParameters } from './sctpParametersTypes'; +import type { AppData } from './types'; +import * as fbsUtils from './fbsUtils'; import { Event, Notification } from './fbs/notification'; import * as FbsRequest from './fbs/request'; import * as FbsTransport from './fbs/transport'; @@ -36,196 +46,6 @@ import { IceRole as FbsIceRole } from './fbs/web-rtc-transport/ice-role'; import { IceCandidateType as FbsIceCandidateType } from './fbs/web-rtc-transport/ice-candidate-type'; import { IceCandidateTcpType as FbsIceCandidateTcpType } from './fbs/web-rtc-transport/ice-candidate-tcp-type'; -export type WebRtcTransportOptions< - WebRtcTransportAppData extends AppData = AppData, -> = WebRtcTransportOptionsBase & WebRtcTransportListen; - -type WebRtcTransportListenIndividualListenInfo = { - /** - * Listening info. - */ - listenInfos: TransportListenInfo[]; -}; - -type WebRtcTransportListenIndividualListenIp = { - /** - * Listening IP address or addresses in order of preference (first one is the - * preferred one). - */ - listenIps: (TransportListenIp | string)[]; - - /** - * Fixed port to listen on instead of selecting automatically from Worker's port - * range. - */ - port?: number; -}; - -type WebRtcTransportListenServer = { - /** - * Instance of WebRtcServer. - */ - webRtcServer: WebRtcServer; -}; - -type WebRtcTransportListen = Either< - Either< - WebRtcTransportListenIndividualListenInfo, - WebRtcTransportListenIndividualListenIp - >, - WebRtcTransportListenServer ->; - -export type WebRtcTransportOptionsBase = { - /** - * Listen in UDP. Default true. - */ - enableUdp?: boolean; - - /** - * Listen in TCP. Default true if webrtcServer is given, false otherwise. - */ - enableTcp?: boolean; - - /** - * Prefer UDP. Default false. - */ - preferUdp?: boolean; - - /** - * Prefer TCP. Default false. - */ - preferTcp?: boolean; - - /** - * ICE consent timeout (in seconds). If 0 it is disabled. Default 30. - */ - iceConsentTimeout?: number; - - /** - * Initial available outgoing bitrate (in bps). Default 600000. - */ - initialAvailableOutgoingBitrate?: number; - - /** - * Create a SCTP association. Default false. - */ - enableSctp?: boolean; - - /** - * SCTP streams number. - */ - numSctpStreams?: NumSctpStreams; - - /** - * Maximum allowed size for SCTP messages sent by DataProducers. - * Default 262144. - */ - maxSctpMessageSize?: number; - - /** - * Maximum SCTP send buffer used by DataConsumers. - * Default 262144. - */ - sctpSendBufferSize?: number; - - /** - * Custom application data. - */ - appData?: WebRtcTransportAppData; -}; - -export type IceParameters = { - usernameFragment: string; - password: string; - iceLite?: boolean; -}; - -export type IceCandidate = { - foundation: string; - priority: number; - // @deprecated Use |address| instead. - ip: string; - address: string; - protocol: TransportProtocol; - port: number; - type: IceCandidateType; - tcpType?: IceCandidateTcpType; -}; - -export type DtlsParameters = { - role?: DtlsRole; - fingerprints: DtlsFingerprint[]; -}; - -/** - * The hash function algorithm (as defined in the "Hash function Textual Names" - * registry initially specified in RFC 4572 Section 8). - */ -export type FingerprintAlgorithm = - | 'sha-1' - | 'sha-224' - | 'sha-256' - | 'sha-384' - | 'sha-512'; - -/** - * The hash function algorithm and its corresponding certificate fingerprint - * value (in lowercase hex string as expressed utilizing the syntax of - * "fingerprint" in RFC 4572 Section 5). - */ -export type DtlsFingerprint = { - algorithm: FingerprintAlgorithm; - value: string; -}; - -export type IceRole = 'controlled' | 'controlling'; - -export type IceState = - | 'new' - | 'connected' - | 'completed' - | 'disconnected' - | 'closed'; - -export type IceCandidateType = 'host'; - -export type IceCandidateTcpType = 'passive'; - -export type DtlsRole = 'auto' | 'client' | 'server'; - -export type DtlsState = - | 'new' - | 'connecting' - | 'connected' - | 'failed' - | 'closed'; - -export type WebRtcTransportStat = BaseTransportStats & { - type: string; - iceRole: string; - iceState: IceState; - iceSelectedTuple?: TransportTuple; - dtlsState: DtlsState; -}; - -export type WebRtcTransportEvents = TransportEvents & { - icestatechange: [IceState]; - iceselectedtuplechange: [TransportTuple]; - dtlsstatechange: [DtlsState]; - sctpstatechange: [SctpState]; -}; - -export type WebRtcTransportObserver = - EnhancedEventEmitter; - -export type WebRtcTransportObserverEvents = TransportObserverEvents & { - icestatechange: [IceState]; - iceselectedtuplechange: [TransportTuple]; - dtlsstatechange: [DtlsState]; - sctpstatechange: [SctpState]; -}; - type WebRtcTransportConstructorOptions = TransportConstructorOptions & { data: WebRtcTransportData; @@ -244,32 +64,21 @@ export type WebRtcTransportData = { sctpState?: SctpState; }; -type WebRtcTransportDump = BaseTransportDump & { - iceRole: 'controlled'; - iceParameters: IceParameters; - iceCandidates: IceCandidate[]; - iceState: IceState; - iceSelectedTuple?: TransportTuple; - dtlsParameters: DtlsParameters; - dtlsState: DtlsState; - dtlsRemoteCert?: string; -}; - const logger = new Logger('WebRtcTransport'); -export class WebRtcTransport< - WebRtcTransportAppData extends AppData = AppData, -> extends Transport< - WebRtcTransportAppData, - WebRtcTransportEvents, - WebRtcTransportObserver -> { +export class WebRtcTransportImpl< + WebRtcTransportAppData extends AppData = AppData, + > + extends TransportImpl< + WebRtcTransportAppData, + WebRtcTransportEvents, + WebRtcTransportObserver + > + implements Transport, WebRtcTransport +{ // WebRtcTransport data. readonly #data: WebRtcTransportData; - /** - * @private - */ constructor( options: WebRtcTransportConstructorOptions ) { @@ -296,92 +105,57 @@ export class WebRtcTransport< }; this.handleWorkerNotifications(); + this.handleListenerError(); + } + + get type(): 'webrtc' { + return 'webrtc'; } - /** - * Observer. - * - * @override - */ get observer(): WebRtcTransportObserver { return super.observer; } - /** - * ICE role. - */ get iceRole(): 'controlled' { return this.#data.iceRole; } - /** - * ICE parameters. - */ get iceParameters(): IceParameters { return this.#data.iceParameters; } - /** - * ICE candidates. - */ get iceCandidates(): IceCandidate[] { return this.#data.iceCandidates; } - /** - * ICE state. - */ get iceState(): IceState { return this.#data.iceState; } - /** - * ICE selected tuple. - */ get iceSelectedTuple(): TransportTuple | undefined { return this.#data.iceSelectedTuple; } - /** - * DTLS parameters. - */ get dtlsParameters(): DtlsParameters { return this.#data.dtlsParameters; } - /** - * DTLS state. - */ get dtlsState(): DtlsState { return this.#data.dtlsState; } - /** - * Remote certificate in PEM format. - */ get dtlsRemoteCert(): string | undefined { return this.#data.dtlsRemoteCert; } - /** - * SCTP parameters. - */ get sctpParameters(): SctpParameters | undefined { return this.#data.sctpParameters; } - /** - * SCTP state. - */ get sctpState(): SctpState | undefined { return this.#data.sctpState; } - /** - * Close the WebRtcTransport. - * - * @override - */ close(): void { if (this.closed) { return; @@ -398,12 +172,6 @@ export class WebRtcTransport< super.close(); } - /** - * Router was closed. - * - * @private - * @override - */ routerClosed(): void { if (this.closed) { return; @@ -420,11 +188,6 @@ export class WebRtcTransport< super.routerClosed(); } - /** - * Called when closing the associated listenServer (WebRtcServer). - * - * @private - */ listenServerClosed(): void { if (this.closed) { return; @@ -441,9 +204,6 @@ export class WebRtcTransport< super.listenServerClosed(); } - /** - * Dump Transport. - */ async dump(): Promise { logger.debug('dump()'); @@ -462,11 +222,6 @@ export class WebRtcTransport< return parseWebRtcTransportDumpResponse(data); } - /** - * Get WebRtcTransport stats. - * - * @override - */ async getStats(): Promise { logger.debug('getStats()'); @@ -485,11 +240,6 @@ export class WebRtcTransport< return [parseGetStatsResponse(data)]; } - /** - * Provide the WebRtcTransport remote parameters. - * - * @override - */ async connect({ dtlsParameters, }: { @@ -519,9 +269,6 @@ export class WebRtcTransport< this.#data.dtlsParameters.role = dtlsRoleFromFbs(data.dtlsLocalRole()); } - /** - * Restart ICE. - */ async restartIce(): Promise { logger.debug('restartIce()'); @@ -650,6 +397,15 @@ export class WebRtcTransport< } ); } + + private handleListenerError(): void { + this.on('listenererror', (eventName, error) => { + logger.error( + `event listener threw an error [eventName:${eventName}]:`, + error + ); + }); + } } function iceStateFromFbs(fbsIceState: FbsIceState): IceState { @@ -824,7 +580,7 @@ export function parseWebRtcTransportDumpResponse( // Retrieve BaseTransportDump. const baseTransportDump = parseBaseTransportDump(binary.base()!); // Retrieve ICE candidates. - const iceCandidates = parseVector( + const iceCandidates = fbsUtils.parseVector( binary, 'iceCandidates', parseIceCandidate diff --git a/node/src/WebRtcTransportTypes.ts b/node/src/WebRtcTransportTypes.ts new file mode 100644 index 0000000000..b466da87e1 --- /dev/null +++ b/node/src/WebRtcTransportTypes.ts @@ -0,0 +1,319 @@ +import type { EnhancedEventEmitter } from './enhancedEvents'; +import type { + Transport, + TransportListenInfo, + TransportListenIp, + TransportProtocol, + TransportTuple, + SctpState, + BaseTransportDump, + BaseTransportStats, + TransportEvents, + TransportObserverEvents, +} from './TransportTypes'; +import type { WebRtcServer } from './WebRtcServerTypes'; +import type { SctpParameters, NumSctpStreams } from './sctpParametersTypes'; +import type { Either, AppData } from './types'; + +export type WebRtcTransportOptions< + WebRtcTransportAppData extends AppData = AppData, +> = WebRtcTransportOptionsBase & WebRtcTransportListen; + +type WebRtcTransportOptionsBase = { + /** + * Listen in UDP. Default true. + */ + enableUdp?: boolean; + + /** + * Listen in TCP. Default true if webrtcServer is given, false otherwise. + */ + enableTcp?: boolean; + + /** + * Prefer UDP. Default false. + */ + preferUdp?: boolean; + + /** + * Prefer TCP. Default false. + */ + preferTcp?: boolean; + + /** + * ICE consent timeout (in seconds). If 0 it is disabled. Default 30. + */ + iceConsentTimeout?: number; + + /** + * Initial available outgoing bitrate (in bps). Default 600000. + */ + initialAvailableOutgoingBitrate?: number; + + /** + * Create a SCTP association. Default false. + */ + enableSctp?: boolean; + + /** + * SCTP streams number. + */ + numSctpStreams?: NumSctpStreams; + + /** + * Maximum allowed size for SCTP messages sent by DataProducers. + * Default 262144. + */ + maxSctpMessageSize?: number; + + /** + * Maximum SCTP send buffer used by DataConsumers. + * Default 262144. + */ + sctpSendBufferSize?: number; + + /** + * Custom application data. + */ + appData?: WebRtcTransportAppData; +}; + +type WebRtcTransportListen = Either< + Either< + WebRtcTransportListenIndividualListenInfo, + WebRtcTransportListenIndividualListenIp + >, + WebRtcTransportListenServer +>; + +type WebRtcTransportListenIndividualListenInfo = { + /** + * Listening info. + */ + listenInfos: TransportListenInfo[]; +}; + +type WebRtcTransportListenIndividualListenIp = { + /** + * Listening IP address or addresses in order of preference (first one is the + * preferred one). + */ + listenIps: (TransportListenIp | string)[]; + + /** + * Fixed port to listen on instead of selecting automatically from Worker's port + * range. + */ + port?: number; +}; + +type WebRtcTransportListenServer = { + /** + * Instance of WebRtcServer. + */ + webRtcServer: WebRtcServer; +}; + +export type IceParameters = { + usernameFragment: string; + password: string; + iceLite?: boolean; +}; + +export type IceCandidate = { + foundation: string; + priority: number; + // @deprecated Use |address| instead. + ip: string; + address: string; + protocol: TransportProtocol; + port: number; + type: IceCandidateType; + tcpType?: IceCandidateTcpType; +}; + +export type DtlsParameters = { + role?: DtlsRole; + fingerprints: DtlsFingerprint[]; +}; + +/** + * The hash function algorithm (as defined in the "Hash function Textual Names" + * registry initially specified in RFC 4572 Section 8). + */ +export type FingerprintAlgorithm = + | 'sha-1' + | 'sha-224' + | 'sha-256' + | 'sha-384' + | 'sha-512'; + +/** + * The hash function algorithm and its corresponding certificate fingerprint + * value (in lowercase hex string as expressed utilizing the syntax of + * "fingerprint" in RFC 4572 Section 5). + */ +export type DtlsFingerprint = { + algorithm: FingerprintAlgorithm; + value: string; +}; + +export type IceRole = 'controlled' | 'controlling'; + +export type IceState = + | 'new' + | 'connected' + | 'completed' + | 'disconnected' + | 'closed'; + +export type IceCandidateType = 'host'; + +export type IceCandidateTcpType = 'passive'; + +export type DtlsRole = 'auto' | 'client' | 'server'; + +export type DtlsState = + | 'new' + | 'connecting' + | 'connected' + | 'failed' + | 'closed'; + +export type WebRtcTransportDump = BaseTransportDump & { + iceRole: 'controlled'; + iceParameters: IceParameters; + iceCandidates: IceCandidate[]; + iceState: IceState; + iceSelectedTuple?: TransportTuple; + dtlsParameters: DtlsParameters; + dtlsState: DtlsState; + dtlsRemoteCert?: string; +}; + +export type WebRtcTransportStat = BaseTransportStats & { + type: string; + iceRole: string; + iceState: IceState; + iceSelectedTuple?: TransportTuple; + dtlsState: DtlsState; +}; + +export type WebRtcTransportEvents = TransportEvents & { + icestatechange: [IceState]; + iceselectedtuplechange: [TransportTuple]; + dtlsstatechange: [DtlsState]; + sctpstatechange: [SctpState]; +}; + +export type WebRtcTransportObserver = + EnhancedEventEmitter; + +export type WebRtcTransportObserverEvents = TransportObserverEvents & { + icestatechange: [IceState]; + iceselectedtuplechange: [TransportTuple]; + dtlsstatechange: [DtlsState]; + sctpstatechange: [SctpState]; +}; + +export interface WebRtcTransport< + WebRtcTransportAppData extends AppData = AppData, +> extends Transport< + WebRtcTransportAppData, + WebRtcTransportEvents, + WebRtcTransportObserver + > { + /** + * Transport type. + * + * @override + */ + get type(): 'webrtc'; + + /** + * Observer. + * + * @override + */ + get observer(): WebRtcTransportObserver; + + /** + * ICE role. + */ + get iceRole(): 'controlled'; + + /** + * ICE parameters. + */ + get iceParameters(): IceParameters; + + /** + * ICE candidates. + */ + get iceCandidates(): IceCandidate[]; + + /** + * ICE state. + */ + get iceState(): IceState; + + /** + * ICE selected tuple. + */ + get iceSelectedTuple(): TransportTuple | undefined; + + /** + * DTLS parameters. + */ + get dtlsParameters(): DtlsParameters; + + /** + * DTLS state. + */ + get dtlsState(): DtlsState; + + /** + * Remote certificate in PEM format. + */ + get dtlsRemoteCert(): string | undefined; + + /** + * SCTP parameters. + */ + get sctpParameters(): SctpParameters | undefined; + + /** + * SCTP state. + */ + get sctpState(): SctpState | undefined; + + /** + * Dump WebRtcTransport. + * + * @override + */ + dump(): Promise; + + /** + * Get WebRtcTransport stats. + * + * @override + */ + getStats(): Promise; + + /** + * Provide the WebRtcTransport remote parameters. + * + * @override + */ + connect({ + dtlsParameters, + }: { + dtlsParameters: DtlsParameters; + }): Promise; + + /** + * Restart ICE. + */ + restartIce(): Promise; +} diff --git a/node/src/Worker.ts b/node/src/Worker.ts index c1d2e20f7c..b30c5d70ba 100644 --- a/node/src/Worker.ts +++ b/node/src/Worker.ts @@ -5,220 +5,32 @@ import { version } from './'; import { Logger } from './Logger'; import { EnhancedEventEmitter } from './enhancedEvents'; import * as ortc from './ortc'; +import type { + Worker, + WorkerSettings, + WorkerUpdateableSettings, + WorkerResourceUsage, + WorkerDump, + WorkerEvents, + WorkerObserver, + WorkerObserverEvents, +} from './WorkerTypes'; import { Channel } from './Channel'; -import { Router, RouterOptions } from './Router'; -import { WebRtcServer, WebRtcServerOptions } from './WebRtcServer'; +import type { WebRtcServer, WebRtcServerOptions } from './WebRtcServerTypes'; +import { WebRtcServerImpl } from './WebRtcServer'; +import type { Router, RouterOptions } from './RouterTypes'; +import { RouterImpl } from './Router'; import { portRangeToFbs, socketFlagsToFbs } from './Transport'; -import { RtpCodecCapability } from './RtpParameters'; -import { AppData } from './types'; +import type { RtpCodecCapability } from './rtpParametersTypes'; import * as utils from './utils'; +import * as fbsUtils from './fbsUtils'; +import type { AppData } from './types'; import { Event } from './fbs/notification'; import * as FbsRequest from './fbs/request'; import * as FbsWorker from './fbs/worker'; import * as FbsTransport from './fbs/transport'; import { Protocol as FbsTransportProtocol } from './fbs/transport/protocol'; -export type WorkerLogLevel = 'debug' | 'warn' | 'error' | 'none'; - -export type WorkerLogTag = - | 'info' - | 'ice' - | 'dtls' - | 'rtp' - | 'srtp' - | 'rtcp' - | 'rtx' - | 'bwe' - | 'score' - | 'simulcast' - | 'svc' - | 'sctp' - | 'message'; - -export type WorkerSettings = { - /** - * Logging level for logs generated by the media worker subprocesses (check - * the Debugging documentation). Valid values are 'debug', 'warn', 'error' and - * 'none'. Default 'error'. - */ - logLevel?: WorkerLogLevel; - - /** - * Log tags for debugging. Check the meaning of each available tag in the - * Debugging documentation. - */ - logTags?: WorkerLogTag[]; - - /** - * Minimun RTC port for ICE, DTLS, RTP, etc. Default 10000. - * @deprecated Use |portRange| in TransportListenInfo object instead. - */ - rtcMinPort?: number; - - /** - * Maximum RTC port for ICE, DTLS, RTP, etc. Default 59999. - * @deprecated Use |portRange| in TransportListenInfo object instead. - */ - rtcMaxPort?: number; - - /** - * Path to the DTLS public certificate file in PEM format. If unset, a - * certificate is dynamically created. - */ - dtlsCertificateFile?: string; - - /** - * Path to the DTLS certificate private key file in PEM format. If unset, a - * certificate is dynamically created. - */ - dtlsPrivateKeyFile?: string; - - /** - * Field trials for libwebrtc. - * @private - * - * NOTE: For advanced users only. An invalid value will make the worker crash. - * Default value is - * "WebRTC-Bwe-AlrLimitedBackoff/Enabled/". - */ - libwebrtcFieldTrials?: string; - - /** - * Disable liburing (io_uring) despite it's supported in current host. - */ - disableLiburing?: boolean; - - /** - * Custom application data. - */ - appData?: WorkerAppData; -}; - -export type WorkerUpdateableSettings = Pick< - WorkerSettings, - 'logLevel' | 'logTags' ->; - -/** - * An object with the fields of the uv_rusage_t struct. - * - * - http://docs.libuv.org/en/v1.x/misc.html#c.uv_rusage_t - * - https://linux.die.net/man/2/getrusage - */ -export type WorkerResourceUsage = { - /** - * User CPU time used (in ms). - */ - ru_utime: number; - - /** - * System CPU time used (in ms). - */ - ru_stime: number; - - /** - * Maximum resident set size. - */ - ru_maxrss: number; - - /** - * Integral shared memory size. - */ - ru_ixrss: number; - - /** - * Integral unshared data size. - */ - ru_idrss: number; - - /** - * Integral unshared stack size. - */ - ru_isrss: number; - - /** - * Page reclaims (soft page faults). - */ - ru_minflt: number; - - /** - * Page faults (hard page faults). - */ - ru_majflt: number; - - /** - * Swaps. - */ - ru_nswap: number; - - /** - * Block input operations. - */ - ru_inblock: number; - - /** - * Block output operations. - */ - ru_oublock: number; - - /** - * IPC messages sent. - */ - ru_msgsnd: number; - - /** - * IPC messages received. - */ - ru_msgrcv: number; - - /** - * Signals received. - */ - ru_nsignals: number; - - /** - * Voluntary context switches. - */ - ru_nvcsw: number; - - /** - * Involuntary context switches. - */ - ru_nivcsw: number; -}; - -export type WorkerDump = { - pid: number; - webRtcServerIds: string[]; - routerIds: string[]; - channelMessageHandlers: { - channelRequestHandlers: string[]; - channelNotificationHandlers: string[]; - }; - liburing?: { - sqeProcessCount: number; - sqeMissCount: number; - userDataMissCount: number; - }; -}; - -export type WorkerEvents = { - died: [Error]; - subprocessclose: []; - listenererror: [string, Error]; - // Private events. - '@success': []; - '@failure': [Error]; -}; - -export type WorkerObserver = EnhancedEventEmitter; - -export type WorkerObserverEvents = { - close: []; - newwebrtcserver: [WebRtcServer]; - newrouter: [Router]; -}; - // If env MEDIASOUP_WORKER_BIN is given, use it as worker binary. // Otherwise if env MEDIASOUP_BUILDTYPE is 'Debug' use the Debug binary. // Otherwise use the Release binary. @@ -247,9 +59,10 @@ export const workerBin = process.env.MEDIASOUP_WORKER_BIN const logger = new Logger('Worker'); const workerLogger = new Logger('Worker'); -export class Worker< - WorkerAppData extends AppData = AppData, -> extends EnhancedEventEmitter { +export class WorkerImpl + extends EnhancedEventEmitter + implements Worker +{ // mediasoup-worker child process. #child: ChildProcess; @@ -281,9 +94,6 @@ export class Worker< readonly #observer: WorkerObserver = new EnhancedEventEmitter(); - /** - * @private - */ constructor({ logLevel, logTags, @@ -489,59 +299,39 @@ export class Worker< } } }); + + this.handleListenerError(); } - /** - * Worker process identifier (PID). - */ get pid(): number { return this.#pid; } - /** - * Whether the Worker is closed. - */ get closed(): boolean { return this.#closed; } - /** - * Whether the Worker died. - */ get died(): boolean { return this.#died; } - /** - * Whether the Worker subprocess is closed. - */ get subprocessClosed(): boolean { return this.#subprocessClosed; } - /** - * App custom data. - */ get appData(): WorkerAppData { return this.#appData; } - /** - * App custom data setter. - */ set appData(appData: WorkerAppData) { this.#appData = appData; } - /** - * Observer. - */ get observer(): WorkerObserver { return this.#observer; } /** - * @private * Just for testing purposes. */ get webRtcServersForTesting(): Set { @@ -549,16 +339,12 @@ export class Worker< } /** - * @private * Just for testing purposes. */ get routersForTesting(): Set { return this.#routers; } - /** - * Close the Worker. - */ close(): void { if (this.#closed) { return; @@ -590,9 +376,6 @@ export class Worker< this.#observer.safeEmit('close'); } - /** - * Dump Worker. - */ async dump(): Promise { logger.debug('dump()'); @@ -607,9 +390,6 @@ export class Worker< return parseWorkerDumpResponse(dump); } - /** - * Get mediasoup-worker process resource usage. - */ async getResourceUsage(): Promise { logger.debug('getResourceUsage()'); @@ -644,9 +424,6 @@ export class Worker< }; } - /** - * Update settings. - */ async updateSettings({ logLevel, logTags, @@ -666,9 +443,6 @@ export class Worker< ); } - /** - * Create a WebRtcServer. - */ async createWebRtcServer({ listenInfos, appData, @@ -715,11 +489,12 @@ export class Worker< createWebRtcServerRequestOffset ); - const webRtcServer: WebRtcServer = new WebRtcServer({ - internal: { webRtcServerId }, - channel: this.#channel, - appData, - }); + const webRtcServer: WebRtcServer = + new WebRtcServerImpl({ + internal: { webRtcServerId }, + channel: this.#channel, + appData, + }); this.#webRtcServers.add(webRtcServer); webRtcServer.on('@close', () => this.#webRtcServers.delete(webRtcServer)); @@ -730,9 +505,6 @@ export class Worker< return webRtcServer; } - /** - * Create a Router. - */ async createRouter({ mediaCodecs, appData, @@ -766,7 +538,7 @@ export class Worker< ); const data = { rtpCapabilities }; - const router: Router = new Router({ + const router: Router = new RouterImpl({ internal: { routerId, }, @@ -814,6 +586,15 @@ export class Worker< // Emit observer event. this.#observer.safeEmit('close'); } + + private handleListenerError(): void { + this.on('listenererror', (eventName, error) => { + logger.error( + `event listener threw an error [eventName:${eventName}]:`, + error + ); + }); + } } export function parseWorkerDumpResponse( @@ -821,14 +602,14 @@ export function parseWorkerDumpResponse( ): WorkerDump { const dump: WorkerDump = { pid: binary.pid(), - webRtcServerIds: utils.parseVector(binary, 'webRtcServerIds'), - routerIds: utils.parseVector(binary, 'routerIds'), + webRtcServerIds: fbsUtils.parseVector(binary, 'webRtcServerIds'), + routerIds: fbsUtils.parseVector(binary, 'routerIds'), channelMessageHandlers: { - channelRequestHandlers: utils.parseVector( + channelRequestHandlers: fbsUtils.parseVector( binary.channelMessageHandlers()!, 'channelRequestHandlers' ), - channelNotificationHandlers: utils.parseVector( + channelNotificationHandlers: fbsUtils.parseVector( binary.channelMessageHandlers()!, 'channelNotificationHandlers' ), diff --git a/node/src/WorkerTypes.ts b/node/src/WorkerTypes.ts new file mode 100644 index 0000000000..337b0c4ea8 --- /dev/null +++ b/node/src/WorkerTypes.ts @@ -0,0 +1,279 @@ +import type { EnhancedEventEmitter } from './enhancedEvents'; +import type { WebRtcServer, WebRtcServerOptions } from './WebRtcServerTypes'; +import type { Router, RouterOptions } from './RouterTypes'; +import type { AppData } from './types'; + +export type WorkerLogLevel = 'debug' | 'warn' | 'error' | 'none'; + +export type WorkerLogTag = + | 'info' + | 'ice' + | 'dtls' + | 'rtp' + | 'srtp' + | 'rtcp' + | 'rtx' + | 'bwe' + | 'score' + | 'simulcast' + | 'svc' + | 'sctp' + | 'message'; + +export type WorkerSettings = { + /** + * Logging level for logs generated by the media worker subprocesses (check + * the Debugging documentation). Valid values are 'debug', 'warn', 'error' and + * 'none'. Default 'error'. + */ + logLevel?: WorkerLogLevel; + + /** + * Log tags for debugging. Check the meaning of each available tag in the + * Debugging documentation. + */ + logTags?: WorkerLogTag[]; + + /** + * Minimun RTC port for ICE, DTLS, RTP, etc. Default 10000. + * @deprecated Use |portRange| in TransportListenInfo object instead. + */ + rtcMinPort?: number; + + /** + * Maximum RTC port for ICE, DTLS, RTP, etc. Default 59999. + * @deprecated Use |portRange| in TransportListenInfo object instead. + */ + rtcMaxPort?: number; + + /** + * Path to the DTLS public certificate file in PEM format. If unset, a + * certificate is dynamically created. + */ + dtlsCertificateFile?: string; + + /** + * Path to the DTLS certificate private key file in PEM format. If unset, a + * certificate is dynamically created. + */ + dtlsPrivateKeyFile?: string; + + /** + * Field trials for libwebrtc. + * @private + * + * NOTE: For advanced users only. An invalid value will make the worker crash. + * Default value is + * "WebRTC-Bwe-AlrLimitedBackoff/Enabled/". + */ + libwebrtcFieldTrials?: string; + + /** + * Disable liburing (io_uring) despite it's supported in current host. + */ + disableLiburing?: boolean; + + /** + * Custom application data. + */ + appData?: WorkerAppData; +}; + +export type WorkerUpdateableSettings = Pick< + WorkerSettings, + 'logLevel' | 'logTags' +>; + +/** + * An object with the fields of the uv_rusage_t struct. + * + * - http://docs.libuv.org/en/v1.x/misc.html#c.uv_rusage_t + * - https://linux.die.net/man/2/getrusage + */ +export type WorkerResourceUsage = { + /** + * User CPU time used (in ms). + */ + ru_utime: number; + + /** + * System CPU time used (in ms). + */ + ru_stime: number; + + /** + * Maximum resident set size. + */ + ru_maxrss: number; + + /** + * Integral shared memory size. + */ + ru_ixrss: number; + + /** + * Integral unshared data size. + */ + ru_idrss: number; + + /** + * Integral unshared stack size. + */ + ru_isrss: number; + + /** + * Page reclaims (soft page faults). + */ + ru_minflt: number; + + /** + * Page faults (hard page faults). + */ + ru_majflt: number; + + /** + * Swaps. + */ + ru_nswap: number; + + /** + * Block input operations. + */ + ru_inblock: number; + + /** + * Block output operations. + */ + ru_oublock: number; + + /** + * IPC messages sent. + */ + ru_msgsnd: number; + + /** + * IPC messages received. + */ + ru_msgrcv: number; + + /** + * Signals received. + */ + ru_nsignals: number; + + /** + * Voluntary context switches. + */ + ru_nvcsw: number; + + /** + * Involuntary context switches. + */ + ru_nivcsw: number; +}; + +export type WorkerDump = { + pid: number; + webRtcServerIds: string[]; + routerIds: string[]; + channelMessageHandlers: { + channelRequestHandlers: string[]; + channelNotificationHandlers: string[]; + }; + liburing?: { + sqeProcessCount: number; + sqeMissCount: number; + userDataMissCount: number; + }; +}; + +export type WorkerEvents = { + died: [Error]; + subprocessclose: []; + listenererror: [string, Error]; + // Private events. + '@success': []; + '@failure': [Error]; +}; + +export type WorkerObserver = EnhancedEventEmitter; + +export type WorkerObserverEvents = { + close: []; + newwebrtcserver: [WebRtcServer]; + newrouter: [Router]; +}; + +export interface Worker + extends EnhancedEventEmitter { + /** + * Worker process identifier (PID). + */ + get pid(): number; + + /** + * Whether the Worker is closed. + */ + get closed(): boolean; + + /** + * Whether the Worker died. + */ + get died(): boolean; + + /** + * Whether the Worker subprocess is closed. + */ + get subprocessClosed(): boolean; + + /** + * App custom data. + */ + get appData(): WorkerAppData; + + /** + * App custom data setter. + */ + set appData(appData: WorkerAppData); + + /** + * Observer. + */ + get observer(): WorkerObserver; + + /** + * Close the Worker. + */ + close(): void; + + /** + * Dump Worker. + */ + dump(): Promise; + + /** + * Get mediasoup-worker process resource usage. + */ + getResourceUsage(): Promise; + + /** + * Update settings. + */ + updateSettings( + options?: WorkerUpdateableSettings + ): Promise; + + /** + * Create a WebRtcServer. + */ + createWebRtcServer( + options: WebRtcServerOptions + ): Promise>; + + /** + * Create a Router. + */ + createRouter( + options?: RouterOptions + ): Promise>; +} diff --git a/node/src/enhancedEvents.ts b/node/src/enhancedEvents.ts index a0fbfba558..8f7a5eb59a 100644 --- a/node/src/enhancedEvents.ts +++ b/node/src/enhancedEvents.ts @@ -1,7 +1,4 @@ import { EventEmitter, once } from 'node:events'; -import { Logger } from './Logger'; - -const enhancedEventEmitterLogger = new Logger('EnhancedEventEmitter'); type Events = Record; @@ -10,6 +7,7 @@ export class EnhancedEventEmitter< > extends EventEmitter { constructor() { super(); + this.setMaxListeners(Infinity); } @@ -24,11 +22,6 @@ export class EnhancedEventEmitter< try { return super.emit(eventName, ...args); } catch (error) { - enhancedEventEmitterLogger.error( - `safeEmit() | event listener threw an error [eventName:${eventName}]:`, - error as Error - ); - try { super.emit('listenererror', eventName, error); } catch (error2) { diff --git a/node/src/fbsUtils.ts b/node/src/fbsUtils.ts new file mode 100644 index 0000000000..502a9bc2a3 --- /dev/null +++ b/node/src/fbsUtils.ts @@ -0,0 +1,115 @@ +/** + * Parse flatbuffers vector into an array of the given T. + */ +export function parseVector( + binary: any, + methodName: string, + parseFn?: (binary2: any) => T +): T[] { + const array: T[] = []; + + for (let i = 0; i < binary[`${methodName}Length`](); ++i) { + if (parseFn) { + array.push(parseFn(binary[methodName](i))); + } else { + array.push(binary[methodName](i) as T); + } + } + + return array; +} + +/** + * Parse flatbuffers vector of StringString into the corresponding array. + */ +export function parseStringStringVector( + binary: any, + methodName: string +): { key: string; value: string }[] { + const array: { key: string; value: string }[] = []; + + for (let i = 0; i < binary[`${methodName}Length`](); ++i) { + const kv = binary[methodName](i)!; + + array.push({ key: kv.key(), value: kv.value() }); + } + + return array; +} + +/** + * Parse flatbuffers vector of StringUint8 into the corresponding array. + */ +export function parseStringUint8Vector( + binary: any, + methodName: string +): { key: string; value: number }[] { + const array: { key: string; value: number }[] = []; + + for (let i = 0; i < binary[`${methodName}Length`](); ++i) { + const kv = binary[methodName](i)!; + + array.push({ key: kv.key(), value: kv.value() }); + } + + return array; +} + +/** + * Parse flatbuffers vector of Uint16String into the corresponding array. + */ +export function parseUint16StringVector( + binary: any, + methodName: string +): { key: number; value: string }[] { + const array: { key: number; value: string }[] = []; + + for (let i = 0; i < binary[`${methodName}Length`](); ++i) { + const kv = binary[methodName](i)!; + + array.push({ key: kv.key(), value: kv.value() }); + } + + return array; +} + +/** + * Parse flatbuffers vector of Uint32String into the corresponding array. + */ +export function parseUint32StringVector( + binary: any, + methodName: string +): { key: number; value: string }[] { + const array: { key: number; value: string }[] = []; + + for (let i = 0; i < binary[`${methodName}Length`](); ++i) { + const kv = binary[methodName](i)!; + + array.push({ key: kv.key(), value: kv.value() }); + } + + return array; +} + +/** + * Parse flatbuffers vector of StringStringArray into the corresponding array. + */ +export function parseStringStringArrayVector( + binary: any, + methodName: string +): { key: string; values: string[] }[] { + const array: { key: string; values: string[] }[] = []; + + for (let i = 0; i < binary[`${methodName}Length`](); ++i) { + const kv = binary[methodName](i)!; + const values: string[] = []; + + for (let i2 = 0; i2 < kv.valuesLength(); ++i2) { + values.push(kv.values(i2)! as string); + } + + array.push({ key: kv.key(), values }); + } + + return array; +} diff --git a/node/src/index.ts b/node/src/index.ts index 9239dc77ac..c366b0d357 100644 --- a/node/src/index.ts +++ b/node/src/index.ts @@ -1,11 +1,11 @@ import { Logger, LoggerEmitter } from './Logger'; import { EnhancedEventEmitter } from './enhancedEvents'; -import { workerBin, Worker, WorkerSettings } from './Worker'; -import * as utils from './utils'; +import type { Worker, WorkerSettings } from './WorkerTypes'; +import { WorkerImpl, workerBin } from './Worker'; import { supportedRtpCapabilities } from './supportedRtpCapabilities'; -import { RtpCapabilities } from './RtpParameters'; - -import * as types from './types'; +import type { RtpCapabilities } from './rtpParametersTypes'; +import type * as types from './types'; +import * as utils from './utils'; /** * Expose all types. @@ -18,11 +18,6 @@ export { types }; // eslint-disable-next-line @typescript-eslint/no-require-imports export const version: string = require('../../package.json').version; -/** - * Expose parseScalabilityMode() function. - */ -export { parse as parseScalabilityMode } from './scalabilityModes'; - export type Observer = EnhancedEventEmitter; export type ObserverEvents = { @@ -43,15 +38,6 @@ export { workerBin }; const logger = new Logger(); -/** - * Event listeners for mediasoup generated logs. - */ -export type LogEventListeners = { - ondebug?: (namespace: string, log: string) => void; - onwarn?: (namespace: string, log: string) => void; - onerror?: (namespace: string, log: string, error?: Error) => void; -}; - /** * Set event listeners for mediasoup generated logs. If called with no arguments * then no events will be emitted. @@ -73,7 +59,9 @@ export type LogEventListeners = { * }); * ``` */ -export function setLogEventListeners(listeners?: LogEventListeners): void { +export function setLogEventListeners( + listeners?: types.LogEventListeners +): void { logger.debug('setLogEventListeners()'); let debugLogEmitter: LoggerEmitter | undefined; @@ -123,7 +111,7 @@ export async function createWorker< throw new TypeError('if given, appData must be an object'); } - const worker: Worker = new Worker({ + const worker: Worker = new WorkerImpl({ logLevel, logTags, rtcMinPort, @@ -153,3 +141,8 @@ export async function createWorker< export function getSupportedRtpCapabilities(): RtpCapabilities { return utils.clone(supportedRtpCapabilities); } + +/** + * Expose parseScalabilityMode() function. + */ +export { parseScalabilityMode } from './scalabilityModesUtils'; diff --git a/node/src/ortc.ts b/node/src/ortc.ts index 3b36297493..be79bab7c2 100644 --- a/node/src/ortc.ts +++ b/node/src/ortc.ts @@ -1,8 +1,8 @@ import * as h264 from 'h264-profile-level-id'; import * as flatbuffers from 'flatbuffers'; import { supportedRtpCapabilities } from './supportedRtpCapabilities'; -import { parse as parseScalabilityMode } from './scalabilityModes'; -import { +import { parseScalabilityMode } from './scalabilityModesUtils'; +import type { RtpCapabilities, MediaKind, RtpCodecCapability, @@ -13,18 +13,17 @@ import { RtpEncodingParameters, RtpHeaderExtensionParameters, RtcpParameters, -} from './RtpParameters'; -import { SctpStreamParameters } from './SctpParameters'; +} from './rtpParametersTypes'; +import type { SctpStreamParameters } from './sctpParametersTypes'; import * as utils from './utils'; import { UnsupportedError } from './errors'; import * as FbsRtpParameters from './fbs/rtp-parameters'; -export type RtpMapping = { +export type RtpCodecsEncodingsMapping = { codecs: { payloadType: number; mappedPayloadType: number; }[]; - encodings: { ssrc?: number; rid?: string; @@ -309,8 +308,8 @@ export function generateRouterRtpCapabilities( export function getProducerRtpParametersMapping( params: RtpParameters, caps: RtpCapabilities -): RtpMapping { - const rtpMapping: RtpMapping = { +): RtpCodecsEncodingsMapping { + const rtpMapping: RtpCodecsEncodingsMapping = { codecs: [], encodings: [], }; @@ -415,7 +414,7 @@ export function getConsumableRtpParameters( kind: string, params: RtpParameters, caps: RtpCapabilities, - rtpMapping: RtpMapping + rtpMapping: RtpCodecsEncodingsMapping ): RtpParameters { const consumableParams: RtpParameters = { codecs: [], @@ -915,7 +914,7 @@ function matchCodecs( export function serializeRtpMapping( builder: flatbuffers.Builder, - rtpMapping: RtpMapping + rtpMapping: RtpCodecsEncodingsMapping ): number { const codecs: number[] = []; diff --git a/node/src/RtpParameters.ts b/node/src/rtpParametersFbsUtils.ts similarity index 55% rename from node/src/RtpParameters.ts rename to node/src/rtpParametersFbsUtils.ts index 4c1e7631dc..727eb1cd1e 100644 --- a/node/src/RtpParameters.ts +++ b/node/src/rtpParametersFbsUtils.ts @@ -1,4 +1,14 @@ import * as flatbuffers from 'flatbuffers'; +import type { + RtpParameters, + RtpCodecParameters, + RtcpFeedback, + RtpEncodingParameters, + RtpHeaderExtensionUri, + RtpHeaderExtensionParameters, + RtcpParameters, +} from './rtpParametersTypes'; +import * as fbsUtils from './fbsUtils'; import { Boolean as FbsBoolean, Double as FbsDouble, @@ -16,365 +26,6 @@ import { Rtx as FbsRtx, Value as FbsValue, } from './fbs/rtp-parameters'; -import * as utils from './utils'; - -/** - * The RTP capabilities define what mediasoup or an endpoint can receive at - * media level. - */ -export type RtpCapabilities = { - /** - * Supported media and RTX codecs. - */ - codecs?: RtpCodecCapability[]; - - /** - * Supported RTP header extensions. - */ - headerExtensions?: RtpHeaderExtension[]; -}; - -/** - * Media kind ('audio' or 'video'). - */ -export type MediaKind = 'audio' | 'video'; - -/** - * Provides information on the capabilities of a codec within the RTP - * capabilities. The list of media codecs supported by mediasoup and their - * settings is defined in the supportedRtpCapabilities.ts file. - * - * Exactly one RtpCodecCapability will be present for each supported combination - * of parameters that requires a distinct value of preferredPayloadType. For - * example: - * - * - Multiple H264 codecs, each with their own distinct 'packetization-mode' and - * 'profile-level-id' values. - * - Multiple VP9 codecs, each with their own distinct 'profile-id' value. - * - * RtpCodecCapability entries in the mediaCodecs array of RouterOptions do not - * require preferredPayloadType field (if unset, mediasoup will choose a random - * one). If given, make sure it's in the 96-127 range. - */ -export type RtpCodecCapability = { - /** - * Media kind. - */ - kind: MediaKind; - - /** - * The codec MIME media type/subtype (e.g. 'audio/opus', 'video/VP8'). - */ - mimeType: string; - - /** - * The preferred RTP payload type. - */ - preferredPayloadType?: number; - - /** - * Codec clock rate expressed in Hertz. - */ - clockRate: number; - - /** - * The number of channels supported (e.g. two for stereo). Just for audio. - * Default 1. - */ - channels?: number; - - /** - * Codec specific parameters. Some parameters (such as 'packetization-mode' - * and 'profile-level-id' in H264 or 'profile-id' in VP9) are critical for - * codec matching. - */ - parameters?: any; - - /** - * Transport layer and codec-specific feedback messages for this codec. - */ - rtcpFeedback?: RtcpFeedback[]; -}; - -/** - * Direction of RTP header extension. - */ -export type RtpHeaderExtensionDirection = - | 'sendrecv' - | 'sendonly' - | 'recvonly' - | 'inactive'; - -/** - * Provides information relating to supported header extensions. The list of - * RTP header extensions supported by mediasoup is defined in the - * supportedRtpCapabilities.ts file. - * - * mediasoup does not currently support encrypted RTP header extensions. The - * direction field is just present in mediasoup RTP capabilities (retrieved via - * router.rtpCapabilities or mediasoup.getSupportedRtpCapabilities()). It's - * ignored if present in endpoints' RTP capabilities. - */ -export type RtpHeaderExtension = { - /** - * Media kind. - */ - kind: MediaKind; - - /* - * The URI of the RTP header extension, as defined in RFC 5285. - */ - uri: RtpHeaderExtensionUri; - - /** - * The preferred numeric identifier that goes in the RTP packet. Must be - * unique. - */ - preferredId: number; - - /** - * If true, it is preferred that the value in the header be encrypted as per - * RFC 6904. Default false. - */ - preferredEncrypt?: boolean; - - /** - * If 'sendrecv', mediasoup supports sending and receiving this RTP extension. - * 'sendonly' means that mediasoup can send (but not receive) it. 'recvonly' - * means that mediasoup can receive (but not send) it. - */ - direction?: RtpHeaderExtensionDirection; -}; - -/** - * The RTP send parameters describe a media stream received by mediasoup from - * an endpoint through its corresponding mediasoup Producer. These parameters - * may include a mid value that the mediasoup transport will use to match - * received RTP packets based on their MID RTP extension value. - * - * mediasoup allows RTP send parameters with a single encoding and with multiple - * encodings (simulcast). In the latter case, each entry in the encodings array - * must include a ssrc field or a rid field (the RID RTP extension value). Check - * the Simulcast and SVC sections for more information. - * - * The RTP receive parameters describe a media stream as sent by mediasoup to - * an endpoint through its corresponding mediasoup Consumer. The mid value is - * unset (mediasoup does not include the MID RTP extension into RTP packets - * being sent to endpoints). - * - * There is a single entry in the encodings array (even if the corresponding - * producer uses simulcast). The consumer sends a single and continuous RTP - * stream to the endpoint and spatial/temporal layer selection is possible via - * consumer.setPreferredLayers(). - * - * As an exception, previous bullet is not true when consuming a stream over a - * PipeTransport, in which all RTP streams from the associated producer are - * forwarded verbatim through the consumer. - * - * The RTP receive parameters will always have their ssrc values randomly - * generated for all of its encodings (and optional rtx: { ssrc: XXXX } if the - * endpoint supports RTX), regardless of the original RTP send parameters in - * the associated producer. This applies even if the producer's encodings have - * rid set. - */ -export type RtpParameters = { - /** - * The MID RTP extension value as defined in the BUNDLE specification. - */ - mid?: string; - - /** - * Media and RTX codecs in use. - */ - codecs: RtpCodecParameters[]; - - /** - * RTP header extensions in use. - */ - headerExtensions?: RtpHeaderExtensionParameters[]; - - /** - * Transmitted RTP streams and their settings. - */ - encodings?: RtpEncodingParameters[]; - - /** - * Parameters used for RTCP. - */ - rtcp?: RtcpParameters; -}; - -/** - * Provides information on codec settings within the RTP parameters. The list - * of media codecs supported by mediasoup and their settings is defined in the - * supportedRtpCapabilities.ts file. - */ -export type RtpCodecParameters = { - /** - * The codec MIME media type/subtype (e.g. 'audio/opus', 'video/VP8'). - */ - mimeType: string; - - /** - * The value that goes in the RTP Payload Type Field. Must be unique. - */ - payloadType: number; - - /** - * Codec clock rate expressed in Hertz. - */ - clockRate: number; - - /** - * The number of channels supported (e.g. two for stereo). Just for audio. - * Default 1. - */ - channels?: number; - - /** - * Codec-specific parameters available for signaling. Some parameters (such - * as 'packetization-mode' and 'profile-level-id' in H264 or 'profile-id' in - * VP9) are critical for codec matching. - */ - parameters?: any; - - /** - * Transport layer and codec-specific feedback messages for this codec. - */ - rtcpFeedback?: RtcpFeedback[]; -}; - -/** - * Provides information on RTCP feedback messages for a specific codec. Those - * messages can be transport layer feedback messages or codec-specific feedback - * messages. The list of RTCP feedbacks supported by mediasoup is defined in the - * supportedRtpCapabilities.ts file. - */ -export type RtcpFeedback = { - /** - * RTCP feedback type. - */ - type: string; - - /** - * RTCP feedback parameter. - */ - parameter?: string; -}; - -/** - * Provides information relating to an encoding, which represents a media RTP - * stream and its associated RTX stream (if any). - */ -export type RtpEncodingParameters = { - /** - * The media SSRC. - */ - ssrc?: number; - - /** - * The RID RTP extension value. Must be unique. - */ - rid?: string; - - /** - * Codec payload type this encoding affects. If unset, first media codec is - * chosen. - */ - codecPayloadType?: number; - - /** - * RTX stream information. It must contain a numeric ssrc field indicating - * the RTX SSRC. - */ - rtx?: { ssrc: number }; - - /** - * It indicates whether discontinuous RTP transmission will be used. Useful - * for audio (if the codec supports it) and for video screen sharing (when - * static content is being transmitted, this option disables the RTP - * inactivity checks in mediasoup). Default false. - */ - dtx?: boolean; - - /** - * Number of spatial and temporal layers in the RTP stream (e.g. 'L1T3'). - * See webrtc-svc. - */ - scalabilityMode?: string; - - /** - * Others. - */ - scaleResolutionDownBy?: number; - maxBitrate?: number; -}; - -export type RtpHeaderExtensionUri = - | 'urn:ietf:params:rtp-hdrext:sdes:mid' - | 'urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id' - | 'urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id' - | 'http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07' - | 'urn:ietf:params:rtp-hdrext:framemarking' - | 'urn:ietf:params:rtp-hdrext:ssrc-audio-level' - | 'urn:3gpp:video-orientation' - | 'urn:ietf:params:rtp-hdrext:toffset' - | 'http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01' - | 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time' - | 'http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time' - | 'http://www.webrtc.org/experiments/rtp-hdrext/playout-delay'; - -/** - * Defines a RTP header extension within the RTP parameters. The list of RTP - * header extensions supported by mediasoup is defined in the - * supportedRtpCapabilities.ts file. - * - * mediasoup does not currently support encrypted RTP header extensions and no - * parameters are currently considered. - */ -export type RtpHeaderExtensionParameters = { - /** - * The URI of the RTP header extension, as defined in RFC 5285. - */ - uri: RtpHeaderExtensionUri; - - /** - * The numeric identifier that goes in the RTP packet. Must be unique. - */ - id: number; - - /** - * If true, the value in the header is encrypted as per RFC 6904. Default false. - */ - encrypt?: boolean; - - /** - * Configuration parameters for the header extension. - */ - parameters?: any; -}; - -/** - * Provides information on RTCP settings within the RTP parameters. - * - * If no cname is given in a producer's RTP parameters, the mediasoup transport - * will choose a random one that will be used into RTCP SDES messages sent to - * all its associated consumers. - * - * mediasoup assumes reducedSize to always be true. - */ -export type RtcpParameters = { - /** - * The Canonical Name (CNAME) used by RTCP (e.g. in SDES messages). - */ - cname?: string; - - /** - * Whether reduced size RTCP RFC 5506 is configured (if true) or compound RTCP - * as specified in RFC 3550 (if false). Default true. - */ - reducedSize?: boolean; -}; export function serializeRtpParameters( builder: flatbuffers.Builder, @@ -694,7 +345,11 @@ export function parseRtpCodecParameters( let rtcpFeedback: RtcpFeedback[] = []; if (data.rtcpFeedbackLength() > 0) { - rtcpFeedback = utils.parseVector(data, 'rtcpFeedback', parseRtcpFeedback); + rtcpFeedback = fbsUtils.parseVector( + data, + 'rtcpFeedback', + parseRtcpFeedback + ); } return { @@ -846,12 +501,12 @@ export function parseRtpEncodingParameters( } export function parseRtpParameters(data: FbsRtpParameters): RtpParameters { - const codecs = utils.parseVector(data, 'codecs', parseRtpCodecParameters); + const codecs = fbsUtils.parseVector(data, 'codecs', parseRtpCodecParameters); let headerExtensions: RtpHeaderExtensionParameters[] = []; if (data.headerExtensionsLength() > 0) { - headerExtensions = utils.parseVector( + headerExtensions = fbsUtils.parseVector( data, 'headerExtensions', parseRtpHeaderExtensionParameters @@ -861,7 +516,7 @@ export function parseRtpParameters(data: FbsRtpParameters): RtpParameters { let encodings: RtpEncodingParameters[] = []; if (data.encodingsLength() > 0) { - encodings = utils.parseVector( + encodings = fbsUtils.parseVector( data, 'encodings', parseRtpEncodingParameters diff --git a/node/src/rtpParametersTypes.ts b/node/src/rtpParametersTypes.ts new file mode 100644 index 0000000000..fa7e618863 --- /dev/null +++ b/node/src/rtpParametersTypes.ts @@ -0,0 +1,357 @@ +/** + * The RTP capabilities define what mediasoup or an endpoint can receive at + * media level. + */ +export type RtpCapabilities = { + /** + * Supported media and RTX codecs. + */ + codecs?: RtpCodecCapability[]; + + /** + * Supported RTP header extensions. + */ + headerExtensions?: RtpHeaderExtension[]; +}; + +/** + * Media kind ('audio' or 'video'). + */ +export type MediaKind = 'audio' | 'video'; + +/** + * Provides information on the capabilities of a codec within the RTP + * capabilities. The list of media codecs supported by mediasoup and their + * settings is defined in the supportedRtpCapabilities.ts file. + * + * Exactly one RtpCodecCapability will be present for each supported combination + * of parameters that requires a distinct value of preferredPayloadType. For + * example: + * + * - Multiple H264 codecs, each with their own distinct 'packetization-mode' and + * 'profile-level-id' values. + * - Multiple VP9 codecs, each with their own distinct 'profile-id' value. + * + * RtpCodecCapability entries in the mediaCodecs array of RouterOptions do not + * require preferredPayloadType field (if unset, mediasoup will choose a random + * one). If given, make sure it's in the 96-127 range. + */ +export type RtpCodecCapability = { + /** + * Media kind. + */ + kind: MediaKind; + + /** + * The codec MIME media type/subtype (e.g. 'audio/opus', 'video/VP8'). + */ + mimeType: string; + + /** + * The preferred RTP payload type. + */ + preferredPayloadType?: number; + + /** + * Codec clock rate expressed in Hertz. + */ + clockRate: number; + + /** + * The number of channels supported (e.g. two for stereo). Just for audio. + * Default 1. + */ + channels?: number; + + /** + * Codec specific parameters. Some parameters (such as 'packetization-mode' + * and 'profile-level-id' in H264 or 'profile-id' in VP9) are critical for + * codec matching. + */ + parameters?: any; + + /** + * Transport layer and codec-specific feedback messages for this codec. + */ + rtcpFeedback?: RtcpFeedback[]; +}; + +/** + * Direction of RTP header extension. + */ +export type RtpHeaderExtensionDirection = + | 'sendrecv' + | 'sendonly' + | 'recvonly' + | 'inactive'; + +/** + * Provides information relating to supported header extensions. The list of + * RTP header extensions supported by mediasoup is defined in the + * supportedRtpCapabilities.ts file. + * + * mediasoup does not currently support encrypted RTP header extensions. The + * direction field is just present in mediasoup RTP capabilities (retrieved via + * router.rtpCapabilities or mediasoup.getSupportedRtpCapabilities()). It's + * ignored if present in endpoints' RTP capabilities. + */ +export type RtpHeaderExtension = { + /** + * Media kind. + */ + kind: MediaKind; + + /* + * The URI of the RTP header extension, as defined in RFC 5285. + */ + uri: RtpHeaderExtensionUri; + + /** + * The preferred numeric identifier that goes in the RTP packet. Must be + * unique. + */ + preferredId: number; + + /** + * If true, it is preferred that the value in the header be encrypted as per + * RFC 6904. Default false. + */ + preferredEncrypt?: boolean; + + /** + * If 'sendrecv', mediasoup supports sending and receiving this RTP extension. + * 'sendonly' means that mediasoup can send (but not receive) it. 'recvonly' + * means that mediasoup can receive (but not send) it. + */ + direction?: RtpHeaderExtensionDirection; +}; + +/** + * The RTP send parameters describe a media stream received by mediasoup from + * an endpoint through its corresponding mediasoup Producer. These parameters + * may include a mid value that the mediasoup transport will use to match + * received RTP packets based on their MID RTP extension value. + * + * mediasoup allows RTP send parameters with a single encoding and with multiple + * encodings (simulcast). In the latter case, each entry in the encodings array + * must include a ssrc field or a rid field (the RID RTP extension value). Check + * the Simulcast and SVC sections for more information. + * + * The RTP receive parameters describe a media stream as sent by mediasoup to + * an endpoint through its corresponding mediasoup Consumer. The mid value is + * unset (mediasoup does not include the MID RTP extension into RTP packets + * being sent to endpoints). + * + * There is a single entry in the encodings array (even if the corresponding + * producer uses simulcast). The consumer sends a single and continuous RTP + * stream to the endpoint and spatial/temporal layer selection is possible via + * consumer.setPreferredLayers(). + * + * As an exception, previous bullet is not true when consuming a stream over a + * PipeTransport, in which all RTP streams from the associated producer are + * forwarded verbatim through the consumer. + * + * The RTP receive parameters will always have their ssrc values randomly + * generated for all of its encodings (and optional rtx: { ssrc: XXXX } if the + * endpoint supports RTX), regardless of the original RTP send parameters in + * the associated producer. This applies even if the producer's encodings have + * rid set. + */ +export type RtpParameters = { + /** + * The MID RTP extension value as defined in the BUNDLE specification. + */ + mid?: string; + + /** + * Media and RTX codecs in use. + */ + codecs: RtpCodecParameters[]; + + /** + * RTP header extensions in use. + */ + headerExtensions?: RtpHeaderExtensionParameters[]; + + /** + * Transmitted RTP streams and their settings. + */ + encodings?: RtpEncodingParameters[]; + + /** + * Parameters used for RTCP. + */ + rtcp?: RtcpParameters; +}; + +/** + * Provides information on codec settings within the RTP parameters. The list + * of media codecs supported by mediasoup and their settings is defined in the + * supportedRtpCapabilities.ts file. + */ +export type RtpCodecParameters = { + /** + * The codec MIME media type/subtype (e.g. 'audio/opus', 'video/VP8'). + */ + mimeType: string; + + /** + * The value that goes in the RTP Payload Type Field. Must be unique. + */ + payloadType: number; + + /** + * Codec clock rate expressed in Hertz. + */ + clockRate: number; + + /** + * The number of channels supported (e.g. two for stereo). Just for audio. + * Default 1. + */ + channels?: number; + + /** + * Codec-specific parameters available for signaling. Some parameters (such + * as 'packetization-mode' and 'profile-level-id' in H264 or 'profile-id' in + * VP9) are critical for codec matching. + */ + parameters?: any; + + /** + * Transport layer and codec-specific feedback messages for this codec. + */ + rtcpFeedback?: RtcpFeedback[]; +}; + +/** + * Provides information on RTCP feedback messages for a specific codec. Those + * messages can be transport layer feedback messages or codec-specific feedback + * messages. The list of RTCP feedbacks supported by mediasoup is defined in the + * supportedRtpCapabilities.ts file. + */ +export type RtcpFeedback = { + /** + * RTCP feedback type. + */ + type: string; + + /** + * RTCP feedback parameter. + */ + parameter?: string; +}; + +/** + * Provides information relating to an encoding, which represents a media RTP + * stream and its associated RTX stream (if any). + */ +export type RtpEncodingParameters = { + /** + * The media SSRC. + */ + ssrc?: number; + + /** + * The RID RTP extension value. Must be unique. + */ + rid?: string; + + /** + * Codec payload type this encoding affects. If unset, first media codec is + * chosen. + */ + codecPayloadType?: number; + + /** + * RTX stream information. It must contain a numeric ssrc field indicating + * the RTX SSRC. + */ + rtx?: { ssrc: number }; + + /** + * It indicates whether discontinuous RTP transmission will be used. Useful + * for audio (if the codec supports it) and for video screen sharing (when + * static content is being transmitted, this option disables the RTP + * inactivity checks in mediasoup). Default false. + */ + dtx?: boolean; + + /** + * Number of spatial and temporal layers in the RTP stream (e.g. 'L1T3'). + * See webrtc-svc. + */ + scalabilityMode?: string; + + /** + * Others. + */ + scaleResolutionDownBy?: number; + maxBitrate?: number; +}; + +export type RtpHeaderExtensionUri = + | 'urn:ietf:params:rtp-hdrext:sdes:mid' + | 'urn:ietf:params:rtp-hdrext:sdes:rtp-stream-id' + | 'urn:ietf:params:rtp-hdrext:sdes:repaired-rtp-stream-id' + | 'http://tools.ietf.org/html/draft-ietf-avtext-framemarking-07' + | 'urn:ietf:params:rtp-hdrext:framemarking' + | 'urn:ietf:params:rtp-hdrext:ssrc-audio-level' + | 'urn:3gpp:video-orientation' + | 'urn:ietf:params:rtp-hdrext:toffset' + | 'http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01' + | 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time' + | 'http://www.webrtc.org/experiments/rtp-hdrext/abs-capture-time' + | 'http://www.webrtc.org/experiments/rtp-hdrext/playout-delay'; + +/** + * Defines a RTP header extension within the RTP parameters. The list of RTP + * header extensions supported by mediasoup is defined in the + * supportedRtpCapabilities.ts file. + * + * mediasoup does not currently support encrypted RTP header extensions and no + * parameters are currently considered. + */ +export type RtpHeaderExtensionParameters = { + /** + * The URI of the RTP header extension, as defined in RFC 5285. + */ + uri: RtpHeaderExtensionUri; + + /** + * The numeric identifier that goes in the RTP packet. Must be unique. + */ + id: number; + + /** + * If true, the value in the header is encrypted as per RFC 6904. Default false. + */ + encrypt?: boolean; + + /** + * Configuration parameters for the header extension. + */ + parameters?: any; +}; + +/** + * Provides information on RTCP settings within the RTP parameters. + * + * If no cname is given in a producer's RTP parameters, the mediasoup transport + * will choose a random one that will be used into RTCP SDES messages sent to + * all its associated consumers. + * + * mediasoup assumes reducedSize to always be true. + */ +export type RtcpParameters = { + /** + * The Canonical Name (CNAME) used by RTCP (e.g. in SDES messages). + */ + cname?: string; + + /** + * Whether reduced size RTCP RFC 5506 is configured (if true) or compound RTCP + * as specified in RFC 3550 (if false). Default true. + */ + reducedSize?: boolean; +}; diff --git a/node/src/RtpStream.ts b/node/src/rtpStreamStatsFbsUtils.ts similarity index 78% rename from node/src/RtpStream.ts rename to node/src/rtpStreamStatsFbsUtils.ts index 47acfd44a0..96d941e739 100644 --- a/node/src/RtpStream.ts +++ b/node/src/rtpStreamStatsFbsUtils.ts @@ -1,45 +1,12 @@ +import { + RtpStreamRecvStats, + RtpStreamSendStats, + BaseRtpStreamStats, + BitrateByLayer, +} from './rtpStreamStatsTypes'; import * as FbsRtpStream from './fbs/rtp-stream'; import * as FbsRtpParameters from './fbs/rtp-parameters'; -type BitrateByLayer = { [key: string]: number }; - -export type RtpStreamRecvStats = BaseRtpStreamStats & { - type: string; - jitter: number; - packetCount: number; - byteCount: number; - bitrate: number; - bitrateByLayer: BitrateByLayer; -}; - -export type RtpStreamSendStats = BaseRtpStreamStats & { - type: string; - packetCount: number; - byteCount: number; - bitrate: number; -}; - -type BaseRtpStreamStats = { - timestamp: number; - ssrc: number; - rtxSsrc?: number; - rid?: string; - kind: string; - mimeType: string; - packetsLost: number; - fractionLost: number; - packetsDiscarded: number; - packetsRetransmitted: number; - packetsRepaired: number; - nackCount: number; - nackPacketCount: number; - pliCount: number; - firCount: number; - score: number; - roundTripTime?: number; - rtxPacketsDiscarded?: number; -}; - export function parseRtpStreamStats( binary: FbsRtpStream.Stats ): RtpStreamRecvStats | RtpStreamSendStats { diff --git a/node/src/rtpStreamStatsTypes.ts b/node/src/rtpStreamStatsTypes.ts new file mode 100644 index 0000000000..aa806d6c7c --- /dev/null +++ b/node/src/rtpStreamStatsTypes.ts @@ -0,0 +1,38 @@ +export type RtpStreamRecvStats = BaseRtpStreamStats & { + type: string; + jitter: number; + packetCount: number; + byteCount: number; + bitrate: number; + bitrateByLayer: BitrateByLayer; +}; + +export type RtpStreamSendStats = BaseRtpStreamStats & { + type: string; + packetCount: number; + byteCount: number; + bitrate: number; +}; + +export type BaseRtpStreamStats = { + timestamp: number; + ssrc: number; + rtxSsrc?: number; + rid?: string; + kind: string; + mimeType: string; + packetsLost: number; + fractionLost: number; + packetsDiscarded: number; + packetsRetransmitted: number; + packetsRepaired: number; + nackCount: number; + nackPacketCount: number; + pliCount: number; + firCount: number; + score: number; + roundTripTime?: number; + rtxPacketsDiscarded?: number; +}; + +export type BitrateByLayer = { [key: string]: number }; diff --git a/node/src/scalabilityModesTypes.ts b/node/src/scalabilityModesTypes.ts new file mode 100644 index 0000000000..8d66dd8c0d --- /dev/null +++ b/node/src/scalabilityModesTypes.ts @@ -0,0 +1,5 @@ +export type ScalabilityMode = { + spatialLayers: number; + temporalLayers: number; + ksvc: boolean; +}; diff --git a/node/src/scalabilityModes.ts b/node/src/scalabilityModesUtils.ts similarity index 69% rename from node/src/scalabilityModes.ts rename to node/src/scalabilityModesUtils.ts index 291255092a..3cbb70ab3e 100644 --- a/node/src/scalabilityModes.ts +++ b/node/src/scalabilityModesUtils.ts @@ -1,14 +1,12 @@ +import { ScalabilityMode } from './scalabilityModesTypes'; + const ScalabilityModeRegex = new RegExp( '^[LS]([1-9]\\d{0,1})T([1-9]\\d{0,1})(_KEY)?' ); -export type ScalabilityMode = { - spatialLayers: number; - temporalLayers: number; - ksvc: boolean; -}; - -export function parse(scalabilityMode?: string): ScalabilityMode { +export function parseScalabilityMode( + scalabilityMode?: string +): ScalabilityMode { const match = ScalabilityModeRegex.exec(scalabilityMode ?? ''); if (match) { diff --git a/node/src/sctpParametersFbsUtils.ts b/node/src/sctpParametersFbsUtils.ts new file mode 100644 index 0000000000..a99f86d88b --- /dev/null +++ b/node/src/sctpParametersFbsUtils.ts @@ -0,0 +1,54 @@ +import * as flatbuffers from 'flatbuffers'; +import type { + SctpStreamParameters, + SctpParametersDump, +} from './sctpParametersTypes'; +import * as FbsSctpParameters from './fbs/sctp-parameters'; + +export function parseSctpParametersDump( + binary: FbsSctpParameters.SctpParameters +): SctpParametersDump { + return { + port: binary.port(), + OS: binary.os(), + MIS: binary.mis(), + maxMessageSize: binary.maxMessageSize(), + sendBufferSize: binary.sendBufferSize(), + sctpBufferedAmount: binary.sctpBufferedAmount(), + isDataChannel: binary.isDataChannel(), + }; +} + +export function serializeSctpStreamParameters( + builder: flatbuffers.Builder, + parameters: SctpStreamParameters +): number { + return FbsSctpParameters.SctpStreamParameters.createSctpStreamParameters( + builder, + parameters.streamId, + parameters.ordered!, + typeof parameters.maxPacketLifeTime === 'number' + ? parameters.maxPacketLifeTime + : null, + typeof parameters.maxRetransmits === 'number' + ? parameters.maxRetransmits + : null + ); +} + +export function parseSctpStreamParameters( + parameters: FbsSctpParameters.SctpStreamParameters +): SctpStreamParameters { + return { + streamId: parameters.streamId(), + ordered: parameters.ordered()!, + maxPacketLifeTime: + parameters.maxPacketLifeTime() !== null + ? parameters.maxPacketLifeTime()! + : undefined, + maxRetransmits: + parameters.maxRetransmits() !== null + ? parameters.maxRetransmits()! + : undefined, + }; +} diff --git a/node/src/SctpParameters.ts b/node/src/sctpParametersTypes.ts similarity index 67% rename from node/src/SctpParameters.ts rename to node/src/sctpParametersTypes.ts index 849ff20143..3e8803408a 100644 --- a/node/src/SctpParameters.ts +++ b/node/src/sctpParametersTypes.ts @@ -1,6 +1,3 @@ -import * as flatbuffers from 'flatbuffers'; -import * as FbsSctpParameters from './fbs/sctp-parameters'; - export type SctpCapabilities = { numStreams: NumSctpStreams; }; @@ -102,51 +99,3 @@ export type SctpParametersDump = { sctpBufferedAmount: number; isDataChannel: boolean; }; - -export function parseSctpParametersDump( - binary: FbsSctpParameters.SctpParameters -): SctpParametersDump { - return { - port: binary.port(), - OS: binary.os(), - MIS: binary.mis(), - maxMessageSize: binary.maxMessageSize(), - sendBufferSize: binary.sendBufferSize(), - sctpBufferedAmount: binary.sctpBufferedAmount(), - isDataChannel: binary.isDataChannel(), - }; -} - -export function serializeSctpStreamParameters( - builder: flatbuffers.Builder, - parameters: SctpStreamParameters -): number { - return FbsSctpParameters.SctpStreamParameters.createSctpStreamParameters( - builder, - parameters.streamId, - parameters.ordered!, - typeof parameters.maxPacketLifeTime === 'number' - ? parameters.maxPacketLifeTime - : null, - typeof parameters.maxRetransmits === 'number' - ? parameters.maxRetransmits - : null - ); -} - -export function parseSctpStreamParameters( - parameters: FbsSctpParameters.SctpStreamParameters -): SctpStreamParameters { - return { - streamId: parameters.streamId(), - ordered: parameters.ordered()!, - maxPacketLifeTime: - parameters.maxPacketLifeTime() !== null - ? parameters.maxPacketLifeTime()! - : undefined, - maxRetransmits: - parameters.maxRetransmits() !== null - ? parameters.maxRetransmits()! - : undefined, - }; -} diff --git a/node/src/SrtpParameters.ts b/node/src/srtpParametersFbsUtils.ts similarity index 81% rename from node/src/SrtpParameters.ts rename to node/src/srtpParametersFbsUtils.ts index 1077ed5207..b7b5f0dc48 100644 --- a/node/src/SrtpParameters.ts +++ b/node/src/srtpParametersFbsUtils.ts @@ -1,30 +1,7 @@ import * as flatbuffers from 'flatbuffers'; +import type { SrtpParameters, SrtpCryptoSuite } from './srtpParametersTypes'; import * as FbsSrtpParameters from './fbs/srtp-parameters'; -/** - * SRTP parameters. - */ -export type SrtpParameters = { - /** - * Encryption and authentication transforms to be used. - */ - cryptoSuite: SrtpCryptoSuite; - - /** - * SRTP keying material (master key and salt) in Base64. - */ - keyBase64: string; -}; - -/** - * SRTP crypto suite. - */ -export type SrtpCryptoSuite = - | 'AEAD_AES_256_GCM' - | 'AEAD_AES_128_GCM' - | 'AES_CM_128_HMAC_SHA1_80' - | 'AES_CM_128_HMAC_SHA1_32'; - export function cryptoSuiteFromFbs( binary: FbsSrtpParameters.SrtpCryptoSuite ): SrtpCryptoSuite { diff --git a/node/src/srtpParametersTypes.ts b/node/src/srtpParametersTypes.ts new file mode 100644 index 0000000000..d9150bbeb3 --- /dev/null +++ b/node/src/srtpParametersTypes.ts @@ -0,0 +1,23 @@ +/** + * SRTP parameters. + */ +export type SrtpParameters = { + /** + * Encryption and authentication transforms to be used. + */ + cryptoSuite: SrtpCryptoSuite; + + /** + * SRTP keying material (master key and salt) in Base64. + */ + keyBase64: string; +}; + +/** + * SRTP crypto suite. + */ +export type SrtpCryptoSuite = + | 'AEAD_AES_256_GCM' + | 'AEAD_AES_128_GCM' + | 'AES_CM_128_HMAC_SHA1_80' + | 'AES_CM_128_HMAC_SHA1_32'; diff --git a/node/src/supportedRtpCapabilities.ts b/node/src/supportedRtpCapabilities.ts index 042e2746cb..2a0d1f5788 100644 --- a/node/src/supportedRtpCapabilities.ts +++ b/node/src/supportedRtpCapabilities.ts @@ -1,4 +1,4 @@ -import { RtpCapabilities } from './RtpParameters'; +import type { RtpCapabilities } from './rtpParametersTypes'; const supportedRtpCapabilities: RtpCapabilities = { codecs: [ diff --git a/node/src/test/test-ActiveSpeakerObserver.ts b/node/src/test/test-ActiveSpeakerObserver.ts index aaa922390d..7467dac16a 100644 --- a/node/src/test/test-ActiveSpeakerObserver.ts +++ b/node/src/test/test-ActiveSpeakerObserver.ts @@ -1,6 +1,6 @@ import * as mediasoup from '../'; import { enhancedOnce } from '../enhancedEvents'; -import { WorkerEvents, ActiveSpeakerObserverEvents } from '../types'; +import type { WorkerEvents, ActiveSpeakerObserverEvents } from '../types'; import * as utils from '../utils'; type TestContext = { @@ -48,6 +48,7 @@ test('router.createActiveSpeakerObserver() succeeds', async () => { expect(onObserverNewRtpObserver).toHaveBeenCalledWith(activeSpeakerObserver); expect(typeof activeSpeakerObserver.id).toBe('string'); expect(activeSpeakerObserver.closed).toBe(false); + expect(activeSpeakerObserver.type).toBe('activespeaker'); expect(activeSpeakerObserver.paused).toBe(false); expect(activeSpeakerObserver.appData).toEqual({}); @@ -85,8 +86,8 @@ test('activeSpeakerObserver.pause() and resume() succeed', async () => { }, 2000); test('activeSpeakerObserver.close() succeeds', async () => { - const activeSpeakerObserver = await ctx.router!.createAudioLevelObserver({ - maxEntries: 8, + const activeSpeakerObserver = await ctx.router!.createActiveSpeakerObserver({ + interval: 500, }); let dump = await ctx.router!.dump(); @@ -103,7 +104,7 @@ test('activeSpeakerObserver.close() succeeds', async () => { }, 2000); test('ActiveSpeakerObserver emits "routerclose" if Router is closed', async () => { - const activeSpeakerObserver = await ctx.router!.createAudioLevelObserver(); + const activeSpeakerObserver = await ctx.router!.createActiveSpeakerObserver(); const promise = enhancedOnce( activeSpeakerObserver, @@ -117,7 +118,7 @@ test('ActiveSpeakerObserver emits "routerclose" if Router is closed', async () = }, 2000); test('ActiveSpeakerObserver emits "routerclose" if Worker is closed', async () => { - const activeSpeakerObserver = await ctx.router!.createAudioLevelObserver(); + const activeSpeakerObserver = await ctx.router!.createActiveSpeakerObserver(); const promise = enhancedOnce( activeSpeakerObserver, diff --git a/node/src/test/test-AudioLevelObserver.ts b/node/src/test/test-AudioLevelObserver.ts index f1a1a5fb17..e10d1245a8 100644 --- a/node/src/test/test-AudioLevelObserver.ts +++ b/node/src/test/test-AudioLevelObserver.ts @@ -1,6 +1,6 @@ import * as mediasoup from '../'; import { enhancedOnce } from '../enhancedEvents'; -import { WorkerEvents, AudioLevelObserverEvents } from '../types'; +import type { WorkerEvents, AudioLevelObserverEvents } from '../types'; import * as utils from '../utils'; type TestContext = { @@ -48,6 +48,7 @@ test('router.createAudioLevelObserver() succeeds', async () => { expect(onObserverNewRtpObserver).toHaveBeenCalledWith(audioLevelObserver); expect(typeof audioLevelObserver.id).toBe('string'); expect(audioLevelObserver.closed).toBe(false); + expect(audioLevelObserver.type).toBe('audiolevel'); expect(audioLevelObserver.paused).toBe(false); expect(audioLevelObserver.appData).toEqual({}); diff --git a/node/src/test/test-Consumer.ts b/node/src/test/test-Consumer.ts index aad102f089..286076c27a 100644 --- a/node/src/test/test-Consumer.ts +++ b/node/src/test/test-Consumer.ts @@ -1,7 +1,8 @@ import * as flatbuffers from 'flatbuffers'; import * as mediasoup from '../'; import { enhancedOnce } from '../enhancedEvents'; -import { WorkerEvents, ConsumerEvents } from '../types'; +import type { WorkerEvents, ConsumerEvents } from '../types'; +import type { ConsumerImpl } from '../Consumer'; import { UnsupportedError } from '../errors'; import * as utils from '../utils'; import { @@ -975,7 +976,7 @@ test('consumer.enableTraceEvent() succeed', async () => { expect(dump1.traceEventTypes).toEqual(expect.arrayContaining(['rtp', 'pli'])); - await audioConsumer.enableTraceEvent([]); + await audioConsumer.enableTraceEvent(); const dump2 = await audioConsumer.dump(); @@ -1052,8 +1053,8 @@ test('Consumer emits "score"', async () => { rtpCapabilities: ctx.consumerDeviceCapabilities, }); - // Private API. - const channel = audioConsumer.channelForTesting; + // API not exposed in the interface. + const channel = (audioConsumer as ConsumerImpl).channelForTesting; const onScore = jest.fn(); audioConsumer.on('score', onScore); diff --git a/node/src/test/test-DataConsumer.ts b/node/src/test/test-DataConsumer.ts index f61bb8291c..d8e800855b 100644 --- a/node/src/test/test-DataConsumer.ts +++ b/node/src/test/test-DataConsumer.ts @@ -1,6 +1,6 @@ import * as mediasoup from '../'; import { enhancedOnce } from '../enhancedEvents'; -import { WorkerEvents, DataConsumerEvents } from '../types'; +import type { WorkerEvents, DataConsumerEvents } from '../types'; import * as utils from '../utils'; type TestContext = { diff --git a/node/src/test/test-DataProducer.ts b/node/src/test/test-DataProducer.ts index 4f490792ce..1dbd8691e7 100644 --- a/node/src/test/test-DataProducer.ts +++ b/node/src/test/test-DataProducer.ts @@ -1,6 +1,6 @@ import * as mediasoup from '../'; import { enhancedOnce } from '../enhancedEvents'; -import { WorkerEvents, DataProducerEvents } from '../types'; +import type { WorkerEvents, DataProducerEvents } from '../types'; import * as utils from '../utils'; type TestContext = { diff --git a/node/src/test/test-DirectTransport.ts b/node/src/test/test-DirectTransport.ts index e8812257b4..a552e78b39 100644 --- a/node/src/test/test-DirectTransport.ts +++ b/node/src/test/test-DirectTransport.ts @@ -1,6 +1,7 @@ import * as mediasoup from '../'; import { enhancedOnce } from '../enhancedEvents'; -import { WorkerEvents, DirectTransportEvents } from '../types'; +import type { DirectTransportEvents } from '../DirectTransportTypes'; +import type { WorkerEvents } from '../types'; type TestContext = { worker?: mediasoup.types.Worker; @@ -39,13 +40,13 @@ test('router.createDirectTransport() succeeds', async () => { expect(onObserverNewTransport).toHaveBeenCalledTimes(1); expect(onObserverNewTransport).toHaveBeenCalledWith(directTransport); expect(typeof directTransport.id).toBe('string'); + expect(directTransport.type).toBe('direct'); expect(directTransport.closed).toBe(false); expect(directTransport.appData).toEqual({ foo: 'bar' }); const dump = await directTransport.dump(); expect(dump.id).toBe(directTransport.id); - expect(dump.direct).toBe(true); expect(dump.producerIds).toEqual([]); expect(dump.consumerIds).toEqual([]); expect(dump.dataProducerIds).toEqual([]); diff --git a/node/src/test/test-PipeTransport.ts b/node/src/test/test-PipeTransport.ts index 854636ffa4..2bffccc5a8 100644 --- a/node/src/test/test-PipeTransport.ts +++ b/node/src/test/test-PipeTransport.ts @@ -1,7 +1,7 @@ import { pickPort } from 'pick-port'; import * as mediasoup from '../'; import { enhancedOnce } from '../enhancedEvents'; -import { +import type { WorkerEvents, ConsumerEvents, ProducerObserverEvents, @@ -524,6 +524,8 @@ test('router.createPipeTransport() with enableRtx succeeds', async () => { enableRtx: true, }); + expect(pipeTransport.type).toBe('pipe'); + const pipeConsumer = await pipeTransport.consume({ producerId: ctx.videoProducer!.id, }); @@ -1081,7 +1083,7 @@ test('router.pipeToRouter() called in two Routers passing one to each other as a const pipeTransportsB = new Map(); routerA.observer.on('newtransport', transport => { - if (transport.constructor.name !== 'PipeTransport') { + if (transport.constructor.name !== 'PipeTransportImpl') { return; } @@ -1090,7 +1092,7 @@ test('router.pipeToRouter() called in two Routers passing one to each other as a }); routerB.observer.on('newtransport', transport => { - if (transport.constructor.name !== 'PipeTransport') { + if (transport.constructor.name !== 'PipeTransportImpl') { return; } diff --git a/node/src/test/test-PlainTransport.ts b/node/src/test/test-PlainTransport.ts index abd7c326b9..9fd58564a6 100644 --- a/node/src/test/test-PlainTransport.ts +++ b/node/src/test/test-PlainTransport.ts @@ -2,7 +2,7 @@ import * as os from 'node:os'; import { pickPort } from 'pick-port'; import * as mediasoup from '../'; import { enhancedOnce } from '../enhancedEvents'; -import { WorkerEvents, PlainTransportEvents } from '../types'; +import type { WorkerEvents, PlainTransportEvents } from '../types'; import * as utils from '../utils'; const IS_WINDOWS = os.platform() === 'win32'; @@ -91,6 +91,7 @@ test('router.createPlainTransport() succeeds', async () => { expect(onObserverNewTransport).toHaveBeenCalledWith(plainTransport2); expect(typeof plainTransport2.id).toBe('string'); expect(plainTransport2.closed).toBe(false); + expect(plainTransport2.type).toBe('plain'); expect(plainTransport2.appData).toEqual({ foo: 'bar' }); expect(typeof plainTransport2.tuple).toBe('object'); // @deprecated Use tuple.localAddress instead. @@ -111,7 +112,6 @@ test('router.createPlainTransport() succeeds', async () => { const dump1 = await plainTransport2.dump(); expect(dump1.id).toBe(plainTransport2.id); - expect(dump1.direct).toBe(false); expect(dump1.producerIds).toEqual([]); expect(dump1.consumerIds).toEqual([]); expect(dump1.tuple).toEqual(plainTransport2.tuple); @@ -166,7 +166,6 @@ test('router.createPlainTransport() succeeds', async () => { const dump2 = await transport2.dump(); expect(dump2.id).toBe(transport2.id); - expect(dump2.direct).toBe(false); expect(dump2.tuple).toEqual(transport2.tuple); expect(dump2.rtcpTuple).toEqual(transport2.rtcpTuple); expect(dump2.sctpState).toBeUndefined(); @@ -196,7 +195,7 @@ test('router.createPlainTransport() with wrong arguments rejects with TypeError' ).rejects.toThrow(TypeError); await expect( - ctx.router!.createPipeTransport({ + ctx.router!.createPlainTransport({ listenInfo: { protocol: 'tcp', ip: '127.0.0.1' }, }) ).rejects.toThrow(TypeError); diff --git a/node/src/test/test-Producer.ts b/node/src/test/test-Producer.ts index 661c60faf1..38ad33607f 100644 --- a/node/src/test/test-Producer.ts +++ b/node/src/test/test-Producer.ts @@ -1,7 +1,8 @@ import * as flatbuffers from 'flatbuffers'; import * as mediasoup from '../'; import { enhancedOnce } from '../enhancedEvents'; -import { WorkerEvents, ProducerEvents } from '../types'; +import type { WorkerEvents, ProducerEvents } from '../types'; +import type { ProducerImpl } from '../Producer'; import { UnsupportedError } from '../errors'; import * as utils from '../utils'; import { @@ -680,7 +681,7 @@ test('producer.enableTraceEvent() succeed', async () => { expect(dump1.traceEventTypes).toEqual(expect.arrayContaining(['rtp', 'pli'])); - await audioProducer.enableTraceEvent([]); + await audioProducer.enableTraceEvent(); const dump2 = await audioProducer.dump(); @@ -726,8 +727,8 @@ test('Producer emits "score"', async () => { ctx.videoProducerOptions ); - // Private API. - const channel = videoProducer.channelForTesting; + // API not exposed in the interface. + const channel = (videoProducer as ProducerImpl).channelForTesting; const onScore = jest.fn(); videoProducer.on('score', onScore); diff --git a/node/src/test/test-Router.ts b/node/src/test/test-Router.ts index 615ff4176c..0affb75ef6 100644 --- a/node/src/test/test-Router.ts +++ b/node/src/test/test-Router.ts @@ -1,6 +1,7 @@ import * as mediasoup from '../'; import { enhancedOnce } from '../enhancedEvents'; -import { WorkerEvents, RouterEvents } from '../types'; +import type { WorkerImpl } from '../Worker'; +import type { WorkerEvents, RouterEvents } from '../types'; import { InvalidStateError } from '../errors'; import * as utils from '../utils'; @@ -94,15 +95,15 @@ test('worker.createRouter() succeeds', async () => { mapDataConsumerIdDataProducerId: {}, }); - // Private API. - expect(ctx.worker!.routersForTesting.size).toBe(1); + // API not exposed in the interface. + expect((ctx.worker! as WorkerImpl).routersForTesting.size).toBe(1); ctx.worker!.close(); expect(router.closed).toBe(true); - // Private API. - expect(ctx.worker!.routersForTesting.size).toBe(0); + // API not exposed in the interface. + expect((ctx.worker! as WorkerImpl).routersForTesting.size).toBe(0); }, 2000); test('worker.createRouter() with wrong arguments rejects with TypeError', async () => { diff --git a/node/src/test/test-WebRtcServer.ts b/node/src/test/test-WebRtcServer.ts index a6c5e8bec0..669e8ab8ef 100644 --- a/node/src/test/test-WebRtcServer.ts +++ b/node/src/test/test-WebRtcServer.ts @@ -1,7 +1,10 @@ import { pickPort } from 'pick-port'; import * as mediasoup from '../'; import { enhancedOnce } from '../enhancedEvents'; -import { WorkerEvents, WebRtcServerEvents } from '../types'; +import type { WorkerImpl } from '../Worker'; +import type { WorkerEvents, WebRtcServerEvents } from '../types'; +import type { WebRtcServerImpl } from '../WebRtcServer'; +import type { RouterImpl } from '../Router'; import { InvalidStateError } from '../errors'; type TestContext = { @@ -80,15 +83,15 @@ test('worker.createWebRtcServer() succeeds', async () => { tupleHashes: [], }); - // Private API. - expect(ctx.worker!.webRtcServersForTesting.size).toBe(1); + // API not exposed in the interface. + expect((ctx.worker! as WorkerImpl).webRtcServersForTesting.size).toBe(1); ctx.worker!.close(); expect(webRtcServer.closed).toBe(true); - // Private API. - expect(ctx.worker!.webRtcServersForTesting.size).toBe(0); + // API not exposed in the interface. + expect((ctx.worker! as WorkerImpl).webRtcServersForTesting.size).toBe(0); }, 2000); test('worker.createWebRtcServer() with portRange succeeds', async () => { @@ -149,15 +152,15 @@ test('worker.createWebRtcServer() with portRange succeeds', async () => { tupleHashes: [], }); - // Private API. - expect(ctx.worker!.webRtcServersForTesting.size).toBe(1); + // API not exposed in the interface. + expect((ctx.worker! as WorkerImpl).webRtcServersForTesting.size).toBe(1); ctx.worker!.close(); expect(webRtcServer.closed).toBe(true); - // Private API. - expect(ctx.worker!.webRtcServersForTesting.size).toBe(0); + // API not exposed in the interface. + expect((ctx.worker! as WorkerImpl).webRtcServersForTesting.size).toBe(0); }, 2000); test('worker.createWebRtcServer() without specifying port/portRange succeeds', async () => { @@ -205,15 +208,15 @@ test('worker.createWebRtcServer() without specifying port/portRange succeeds', a tupleHashes: [], }); - // Private API. - expect(ctx.worker!.webRtcServersForTesting.size).toBe(1); + // API not exposed in the interface. + expect((ctx.worker! as WorkerImpl).webRtcServersForTesting.size).toBe(1); ctx.worker!.close(); expect(webRtcServer.closed).toBe(true); - // Private API. - expect(ctx.worker!.webRtcServersForTesting.size).toBe(0); + // API not exposed in the interface. + expect((ctx.worker! as WorkerImpl).webRtcServersForTesting.size).toBe(0); }, 2000); test('worker.createWebRtcServer() with wrong arguments rejects with TypeError', async () => { @@ -435,8 +438,12 @@ test('router.createWebRtcTransport() with webRtcServer succeeds and transport is expect(transport.iceState).toBe('new'); expect(transport.iceSelectedTuple).toBeUndefined(); - expect(webRtcServer.webRtcTransportsForTesting.size).toBe(1); - expect(router.transportsForTesting.size).toBe(1); + // API not exposed in the interface. + expect( + (webRtcServer as WebRtcServerImpl).webRtcTransportsForTesting.size + ).toBe(1); + // API not exposed in the interface. + expect((router as RouterImpl).transportsForTesting.size).toBe(1); await expect(webRtcServer.dump()).resolves.toMatchObject({ id: webRtcServer.id, @@ -454,8 +461,12 @@ test('router.createWebRtcTransport() with webRtcServer succeeds and transport is expect(transport.closed).toBe(true); expect(onObserverWebRtcTransportUnhandled).toHaveBeenCalledTimes(1); expect(onObserverWebRtcTransportUnhandled).toHaveBeenCalledWith(transport); - expect(webRtcServer.webRtcTransportsForTesting.size).toBe(0); - expect(router.transportsForTesting.size).toBe(0); + // API not exposed in the interface. + expect( + (webRtcServer as WebRtcServerImpl).webRtcTransportsForTesting.size + ).toBe(0); + // API not exposed in the interface. + expect((router as RouterImpl).transportsForTesting.size).toBe(0); await expect(webRtcServer.dump()).resolves.toMatchObject({ id: webRtcServer.id, @@ -531,8 +542,12 @@ test('router.createWebRtcTransport() with webRtcServer succeeds and webRtcServer expect(transport.iceState).toBe('new'); expect(transport.iceSelectedTuple).toBeUndefined(); - expect(webRtcServer.webRtcTransportsForTesting.size).toBe(1); - expect(router.transportsForTesting.size).toBe(1); + // API not exposed in the interface. + expect( + (webRtcServer as WebRtcServerImpl).webRtcTransportsForTesting.size + ).toBe(1); + // API not exposed in the interface. + expect((router as RouterImpl).transportsForTesting.size).toBe(1); await expect(webRtcServer.dump()).resolves.toMatchObject({ id: webRtcServer.id, @@ -581,8 +596,12 @@ test('router.createWebRtcTransport() with webRtcServer succeeds and webRtcServer expect(transport.iceSelectedTuple).toBe(undefined); expect(transport.dtlsState).toBe('closed'); expect(transport.sctpState).toBe(undefined); - expect(webRtcServer.webRtcTransportsForTesting.size).toBe(0); - expect(router.transportsForTesting.size).toBe(0); + // API not exposed in the interface. + expect( + (webRtcServer as WebRtcServerImpl).webRtcTransportsForTesting.size + ).toBe(0); + // API not exposed in the interface. + expect((router as RouterImpl).transportsForTesting.size).toBe(0); await expect(ctx.worker!.dump()).resolves.toMatchObject({ pid: ctx.worker!.pid, diff --git a/node/src/test/test-WebRtcTransport.ts b/node/src/test/test-WebRtcTransport.ts index 400f25c11e..a241bcf2f7 100644 --- a/node/src/test/test-WebRtcTransport.ts +++ b/node/src/test/test-WebRtcTransport.ts @@ -2,9 +2,11 @@ import { pickPort } from 'pick-port'; import * as flatbuffers from 'flatbuffers'; import * as mediasoup from '../'; import { enhancedOnce } from '../enhancedEvents'; -import { WorkerEvents, WebRtcTransportEvents } from '../types'; +import type { WorkerEvents, WebRtcTransportEvents } from '../types'; +import type { WebRtcTransportImpl } from '../WebRtcTransport'; +import type { TransportTuple } from '../TransportTypes'; +import { serializeProtocol } from '../Transport'; import * as utils from '../utils'; -import { serializeProtocol, TransportTuple } from '../Transport'; import { Notification, Body as NotificationBody, @@ -123,6 +125,7 @@ test('router.createWebRtcTransport() succeeds', async () => { expect(onObserverNewTransport).toHaveBeenCalledWith(webRtcTransport); expect(typeof webRtcTransport.id).toBe('string'); expect(webRtcTransport.closed).toBe(false); + expect(webRtcTransport.type).toBe('webrtc'); expect(webRtcTransport.appData).toEqual({ foo: 'bar' }); expect(webRtcTransport.iceRole).toBe('controlled'); expect(typeof webRtcTransport.iceParameters).toBe('object'); @@ -182,7 +185,6 @@ test('router.createWebRtcTransport() succeeds', async () => { const dump = await webRtcTransport.dump(); expect(dump.id).toBe(webRtcTransport.id); - expect(dump.direct).toBe(false); expect(dump.producerIds).toEqual([]); expect(dump.consumerIds).toEqual([]); expect(dump.iceRole).toBe(webRtcTransport.iceRole); @@ -620,7 +622,7 @@ test('transport.enableTraceEvent() succeed', async () => { traceEventTypes: ['probation'], }); - await webRtcTransport.enableTraceEvent([]); + await webRtcTransport.enableTraceEvent(); await expect(webRtcTransport.dump()).resolves.toMatchObject({ traceEventTypes: [], }); @@ -667,8 +669,8 @@ test('WebRtcTransport events succeed', async () => { ], }); - // Private API. - const channel = webRtcTransport.channelForTesting; + // API not exposed in the interface. + const channel = (webRtcTransport as WebRtcTransportImpl).channelForTesting; const onIceStateChange = jest.fn(); webRtcTransport.on('icestatechange', onIceStateChange); diff --git a/node/src/test/test-Worker.ts b/node/src/test/test-Worker.ts index b9e9a1ec16..ecf6210059 100644 --- a/node/src/test/test-Worker.ts +++ b/node/src/test/test-Worker.ts @@ -3,7 +3,7 @@ import * as process from 'node:process'; import * as path from 'node:path'; import * as mediasoup from '../'; import { enhancedOnce } from '../enhancedEvents'; -import { WorkerEvents } from '../types'; +import type { WorkerEvents } from '../types'; import { InvalidStateError } from '../errors'; test('Worker.workerBin matches mediasoup-worker absolute path', () => { @@ -43,7 +43,7 @@ test('createWorker() succeeds', async () => { expect(onObserverNewWorker).toHaveBeenCalledTimes(1); expect(onObserverNewWorker).toHaveBeenCalledWith(worker1); - expect(worker1.constructor.name).toBe('Worker'); + expect(worker1.constructor.name).toBe('WorkerImpl'); expect(typeof worker1.pid).toBe('number'); expect(worker1.closed).toBe(false); expect(worker1.died).toBe(false); @@ -67,7 +67,7 @@ test('createWorker() succeeds', async () => { appData: { foo: 456 }, }); - expect(worker2.constructor.name).toBe('Worker'); + expect(worker2.constructor.name).toBe('WorkerImpl'); expect(typeof worker2.pid).toBe('number'); expect(worker2.closed).toBe(false); expect(worker2.died).toBe(false); diff --git a/node/src/test/test-mediasoup.ts b/node/src/test/test-mediasoup.ts index bfafad5f7f..98dad17115 100644 --- a/node/src/test/test-mediasoup.ts +++ b/node/src/test/test-mediasoup.ts @@ -2,7 +2,7 @@ import * as fs from 'node:fs'; import * as path from 'node:path'; import { enhancedOnce } from '../enhancedEvents'; import * as mediasoup from '../'; -import { WorkerEvents } from '../types'; +import type { WorkerEvents } from '../types'; const PKG = JSON.parse( fs.readFileSync(path.join(__dirname, '..', '..', '..', 'package.json'), { diff --git a/node/src/test/test-multiopus.ts b/node/src/test/test-multiopus.ts index 4b8ab50450..e1925fe7a1 100644 --- a/node/src/test/test-multiopus.ts +++ b/node/src/test/test-multiopus.ts @@ -1,6 +1,6 @@ import * as mediasoup from '../'; import { enhancedOnce } from '../enhancedEvents'; -import { WorkerEvents } from '../types'; +import type { WorkerEvents } from '../types'; import { UnsupportedError } from '../errors'; import * as utils from '../utils'; diff --git a/node/src/test/test-node-sctp.ts b/node/src/test/test-node-sctp.ts index e344c1a484..87a79cbcc6 100644 --- a/node/src/test/test-node-sctp.ts +++ b/node/src/test/test-node-sctp.ts @@ -3,7 +3,7 @@ import * as dgram from 'node:dgram'; import * as sctp from 'sctp'; import * as mediasoup from '../'; import { enhancedOnce } from '../enhancedEvents'; -import { WorkerEvents } from '../types'; +import type { WorkerEvents } from '../types'; type TestContext = { worker?: mediasoup.types.Worker; diff --git a/node/src/types.ts b/node/src/types.ts index 14c9e4a0c1..53fda44fc1 100644 --- a/node/src/types.ts +++ b/node/src/types.ts @@ -1,30 +1,29 @@ -export type { Observer, ObserverEvents, LogEventListeners } from './index'; -export type * from './RtpParameters'; -export type * from './SctpParameters'; -export type * from './SrtpParameters'; -export type * from './scalabilityModes'; +export type { Observer, ObserverEvents } from './index'; +export type * from './WorkerTypes'; +export type * from './WebRtcServerTypes'; +export type * from './RouterTypes'; +export type * from './TransportTypes'; +export type * from './WebRtcTransportTypes'; +export type * from './PlainTransportTypes'; +export type * from './PipeTransportTypes'; +export type * from './DirectTransportTypes'; +export type * from './ProducerTypes'; +export type * from './ConsumerTypes'; +export type * from './DataProducerTypes'; +export type * from './DataConsumerTypes'; +export type * from './RtpObserverTypes'; +export type * from './ActiveSpeakerObserverTypes'; +export type * from './AudioLevelObserverTypes'; +export type * from './rtpParametersTypes'; +export type * from './rtpStreamStatsTypes'; +export type * from './sctpParametersTypes'; +export type * from './srtpParametersTypes'; +export type * from './scalabilityModesTypes'; -export * from './Worker'; -export * from './WebRtcServer'; -export * from './Router'; -export * from './Transport'; -export * from './WebRtcTransport'; -export * from './PlainTransport'; -export * from './PipeTransport'; -export * from './DirectTransport'; -export * from './Producer'; -export * from './Consumer'; -export * from './DataProducer'; -export * from './DataConsumer'; -export * from './RtpObserver'; -export * from './ActiveSpeakerObserver'; -export * from './AudioLevelObserver'; +// TODO: Here we are exporting real classes rather than types. This should +// be exported somehow else rather than in mediasoup.types namespace. export * from './errors'; -export type AppData = { - [key: string]: unknown; -}; - type Only = { [P in keyof T]: T[P]; } & { @@ -32,3 +31,16 @@ type Only = { }; export type Either = Only | Only; + +export type AppData = { + [key: string]: unknown; +}; + +/** + * Event listeners for mediasoup generated logs. + */ +export type LogEventListeners = { + ondebug?: (namespace: string, log: string) => void; + onwarn?: (namespace: string, log: string) => void; + onerror?: (namespace: string, log: string, error?: Error) => void; +}; diff --git a/node/src/utils.ts b/node/src/utils.ts index f7c8e8c8b9..e3cba37a8f 100644 --- a/node/src/utils.ts +++ b/node/src/utils.ts @@ -1,6 +1,4 @@ import { randomUUID, randomInt } from 'node:crypto'; -import { ProducerType } from './Producer'; -import { Type as FbsRtpParametersType } from './fbs/rtp-parameters'; /** * Clones the given value. @@ -32,148 +30,6 @@ export function generateRandomNumber(): number { return randomInt(100_000_000, 999_999_999); } -/** - * Get the flatbuffers RtpParameters type for a given Producer. - */ -export function getRtpParametersType( - producerType: ProducerType, - pipe: boolean -): FbsRtpParametersType { - if (pipe) { - return FbsRtpParametersType.PIPE; - } - - switch (producerType) { - case 'simple': { - return FbsRtpParametersType.SIMPLE; - } - - case 'simulcast': { - return FbsRtpParametersType.SIMULCAST; - } - - case 'svc': { - return FbsRtpParametersType.SVC; - } - } -} - -/** - * Parse flatbuffers vector into an array of the given type. - */ -export function parseVector( - binary: any, - methodName: string, - parseFn?: (binary2: any) => Type -): Type[] { - const array: Type[] = []; - - for (let i = 0; i < binary[`${methodName}Length`](); ++i) { - if (parseFn) { - array.push(parseFn(binary[methodName](i))); - } else { - array.push(binary[methodName](i) as Type); - } - } - - return array; -} - -/** - * Parse flatbuffers vector of StringString into the corresponding array. - */ -export function parseStringStringVector( - binary: any, - methodName: string -): { key: string; value: string }[] { - const array: { key: string; value: string }[] = []; - - for (let i = 0; i < binary[`${methodName}Length`](); ++i) { - const kv = binary[methodName](i)!; - - array.push({ key: kv.key(), value: kv.value() }); - } - - return array; -} - -/** - * Parse flatbuffers vector of StringUint8 into the corresponding array. - */ -export function parseStringUint8Vector( - binary: any, - methodName: string -): { key: string; value: number }[] { - const array: { key: string; value: number }[] = []; - - for (let i = 0; i < binary[`${methodName}Length`](); ++i) { - const kv = binary[methodName](i)!; - - array.push({ key: kv.key(), value: kv.value() }); - } - - return array; -} - -/** - * Parse flatbuffers vector of Uint16String into the corresponding array. - */ -export function parseUint16StringVector( - binary: any, - methodName: string -): { key: number; value: string }[] { - const array: { key: number; value: string }[] = []; - - for (let i = 0; i < binary[`${methodName}Length`](); ++i) { - const kv = binary[methodName](i)!; - - array.push({ key: kv.key(), value: kv.value() }); - } - - return array; -} - -/** - * Parse flatbuffers vector of Uint32String into the corresponding array. - */ -export function parseUint32StringVector( - binary: any, - methodName: string -): { key: number; value: string }[] { - const array: { key: number; value: string }[] = []; - - for (let i = 0; i < binary[`${methodName}Length`](); ++i) { - const kv = binary[methodName](i)!; - - array.push({ key: kv.key(), value: kv.value() }); - } - - return array; -} - -/** - * Parse flatbuffers vector of StringStringArray into the corresponding array. - */ -export function parseStringStringArrayVector( - binary: any, - methodName: string -): { key: string; values: string[] }[] { - const array: { key: string; values: string[] }[] = []; - - for (let i = 0; i < binary[`${methodName}Length`](); ++i) { - const kv = binary[methodName](i)!; - const values: string[] = []; - - for (let i2 = 0; i2 < kv.valuesLength(); ++i2) { - values.push(kv.values(i2)! as string); - } - - array.push({ key: kv.key(), values }); - } - - return array; -} - /** * Make an object or array recursively immutable. * https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/freeze.