mediasoup-client
Version:
mediasoup client side TypeScript library
299 lines (298 loc) • 10.8 kB
JavaScript
"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;