UNPKG

mediasoup-client

Version:

mediasoup client side TypeScript library

299 lines (298 loc) 10.8 kB
"use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.FakeHandler = void 0; const fake_mediastreamtrack_1 = require("fake-mediastreamtrack"); const enhancedEvents_1 = require("../enhancedEvents"); const Logger_1 = require("../Logger"); const utils = require("../utils"); const ortc = require("../ortc"); const errors_1 = require("../errors"); const HandlerInterface_1 = require("./HandlerInterface"); const logger = new Logger_1.Logger('FakeHandler'); const NAME = 'FakeHandler'; class FakeDataChannel extends enhancedEvents_1.EnhancedEventEmitter { id; ordered; maxPacketLifeTime; maxRetransmits; label; protocol; constructor({ id, ordered, maxPacketLifeTime, maxRetransmits, label, protocol, }) { super(); this.id = id; this.ordered = ordered; this.maxPacketLifeTime = maxPacketLifeTime; this.maxRetransmits = maxRetransmits; this.label = label; this.protocol = protocol; } close() { this.safeEmit('close'); this.emit('@close'); } send(data) { this.safeEmit('message', data); } addEventListener(event, fn) { this.on(event, fn); } } class FakeHandler extends HandlerInterface_1.HandlerInterface { // Closed flag. _closed = false; // Fake parameters source of RTP and SCTP parameters and capabilities. fakeParameters; // Generic sending RTP parameters for audio and video. _rtpParametersByKind; // Local RTCP CNAME. _cname = `CNAME-${utils.generateRandomNumber()}`; // Got transport local and remote parameters. _transportReady = false; // Next localId. _nextLocalId = 1; // Sending and receiving tracks indexed by localId. _tracks = new Map(); // DataChannel id value counter. It must be incremented for each new DataChannel. _nextSctpStreamId = 0; /** * Creates a factory function. */ static createFactory(fakeParameters) { return () => new FakeHandler(fakeParameters); } constructor(fakeParameters) { super(); this.fakeParameters = fakeParameters; } get name() { return NAME; } close() { logger.debug('close()'); if (this._closed) { return; } this._closed = true; } // NOTE: Custom method for simulation purposes. setIceGatheringState(iceGatheringState) { this.emit('@icegatheringstatechange', iceGatheringState); } // NOTE: Custom method for simulation purposes. setConnectionState(connectionState) { this.emit('@connectionstatechange', connectionState); } async getNativeRtpCapabilities() { logger.debug('getNativeRtpCapabilities()'); return this.fakeParameters.generateNativeRtpCapabilities(); } async getNativeSctpCapabilities() { logger.debug('getNativeSctpCapabilities()'); return this.fakeParameters.generateNativeSctpCapabilities(); } run({ /* eslint-disable @typescript-eslint/no-unused-vars */ direction, iceParameters, iceCandidates, dtlsParameters, sctpParameters, iceServers, iceTransportPolicy, proprietaryConstraints, extendedRtpCapabilities, /* eslint-enable @typescript-eslint/no-unused-vars */ }) { this.assertNotClosed(); logger.debug('run()'); // Generic sending RTP parameters for audio and video. // @type {Object} this._rtpParametersByKind = { audio: ortc.getSendingRtpParameters('audio', extendedRtpCapabilities), video: ortc.getSendingRtpParameters('video', extendedRtpCapabilities), }; } // eslint-disable-next-line @typescript-eslint/no-unused-vars async updateIceServers(iceServers) { this.assertNotClosed(); logger.debug('updateIceServers()'); } // eslint-disable-next-line @typescript-eslint/no-unused-vars async restartIce(iceParameters) { this.assertNotClosed(); logger.debug('restartIce()'); } async getTransportStats() { this.assertNotClosed(); return new Map(); // NOTE: Whatever. } async send( // eslint-disable-next-line @typescript-eslint/no-unused-vars { track, encodings, codecOptions, codec }) { this.assertNotClosed(); logger.debug('send() [kind:%s, track.id:%s]', track.kind, track.id); if (!this._transportReady) { await this.setupTransport({ localDtlsRole: 'server' }); } const rtpParameters = utils.clone(this._rtpParametersByKind[track.kind]); const useRtx = rtpParameters.codecs.some((_codec) => /.+\/rtx$/i.test(_codec.mimeType)); rtpParameters.mid = `mid-${utils.generateRandomNumber()}`; if (!encodings) { encodings = [{}]; } for (const encoding of encodings) { encoding.ssrc = utils.generateRandomNumber(); if (useRtx) { encoding.rtx = { ssrc: utils.generateRandomNumber() }; } } rtpParameters.encodings = encodings; // Fill RTCRtpParameters.rtcp. rtpParameters.rtcp = { cname: this._cname, reducedSize: true, mux: true, }; const localId = this._nextLocalId++; this._tracks.set(localId, track); return { localId: String(localId), rtpParameters }; } async stopSending(localId) { logger.debug('stopSending() [localId:%s]', localId); if (this._closed) { return; } if (!this._tracks.has(Number(localId))) { throw new Error('local track not found'); } this._tracks.delete(Number(localId)); } // eslint-disable-next-line @typescript-eslint/no-unused-vars async pauseSending(localId) { this.assertNotClosed(); // Unimplemented. } // eslint-disable-next-line @typescript-eslint/no-unused-vars async resumeSending(localId) { this.assertNotClosed(); // Unimplemented. } async replaceTrack(localId, track) { this.assertNotClosed(); if (track) { logger.debug('replaceTrack() [localId:%s, track.id:%s]', localId, track.id); } else { logger.debug('replaceTrack() [localId:%s, no track]', localId); } this._tracks.delete(Number(localId)); this._tracks.set(Number(localId), track); } async setMaxSpatialLayer(localId, spatialLayer) { this.assertNotClosed(); logger.debug('setMaxSpatialLayer() [localId:%s, spatialLayer:%s]', localId, spatialLayer); } async setRtpEncodingParameters(localId, params) { this.assertNotClosed(); logger.debug('setRtpEncodingParameters() [localId:%s, params:%o]', localId, params); } // eslint-disable-next-line @typescript-eslint/no-unused-vars async getSenderStats(localId) { this.assertNotClosed(); return new Map(); // NOTE: Whatever. } async sendDataChannel({ ordered, maxPacketLifeTime, maxRetransmits, label, protocol, }) { this.assertNotClosed(); if (!this._transportReady) { await this.setupTransport({ localDtlsRole: 'server' }); } logger.debug('sendDataChannel()'); const dataChannel = new FakeDataChannel({ id: this._nextSctpStreamId++, ordered, maxPacketLifeTime, maxRetransmits, label, protocol, }); const sctpStreamParameters = { streamId: this._nextSctpStreamId, ordered: ordered, maxPacketLifeTime: maxPacketLifeTime, maxRetransmits: maxRetransmits, }; // @ts-expect-error --- On purpose. return { dataChannel, sctpStreamParameters }; } async receive(optionsList) { this.assertNotClosed(); const results = []; for (const options of optionsList) { const { trackId, kind } = options; if (!this._transportReady) { await this.setupTransport({ localDtlsRole: 'client' }); } logger.debug('receive() [trackId:%s, kind:%s]', trackId, kind); const localId = this._nextLocalId++; const track = new fake_mediastreamtrack_1.FakeMediaStreamTrack({ kind }); this._tracks.set(localId, track); results.push({ localId: String(localId), track }); } return results; } async stopReceiving(localIds) { if (this._closed) { return; } for (const localId of localIds) { logger.debug('stopReceiving() [localId:%s]', localId); this._tracks.delete(Number(localId)); } } async pauseReceiving( // eslint-disable-next-line @typescript-eslint/no-unused-vars localIds) { this.assertNotClosed(); // Unimplemented. } async resumeReceiving( // eslint-disable-next-line @typescript-eslint/no-unused-vars localIds) { this.assertNotClosed(); // Unimplemented. } // eslint-disable-next-line @typescript-eslint/no-unused-vars async getReceiverStats(localId) { this.assertNotClosed(); return new Map(); // } async receiveDataChannel({ sctpStreamParameters, label, protocol, }) { this.assertNotClosed(); if (!this._transportReady) { await this.setupTransport({ localDtlsRole: 'client' }); } logger.debug('receiveDataChannel()'); const dataChannel = new FakeDataChannel({ id: sctpStreamParameters.streamId, ordered: sctpStreamParameters.ordered, maxPacketLifeTime: sctpStreamParameters.maxPacketLifeTime, maxRetransmits: sctpStreamParameters.maxRetransmits, label, protocol, }); // @ts-expect-error --- On purpose. return { dataChannel }; } async setupTransport({ localDtlsRole, // eslint-disable-next-line @typescript-eslint/no-unused-vars localSdpObject, }) { const dtlsParameters = utils.clone(this.fakeParameters.generateLocalDtlsParameters()); // Set our DTLS role. if (localDtlsRole) { dtlsParameters.role = localDtlsRole; } // Assume we are connecting now. this.emit('@connectionstatechange', 'connecting'); // Need to tell the remote transport about our parameters. await new Promise((resolve, reject) => this.emit('@connect', { dtlsParameters }, resolve, reject)); this._transportReady = true; } assertNotClosed() { if (this._closed) { throw new errors_1.InvalidStateError('method called in a closed handler'); } } } exports.FakeHandler = FakeHandler;