mediasoup-client
Version:
mediasoup client side TypeScript library
448 lines (447 loc) • 17.4 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Edge11 = void 0;
const Logger_1 = require("../Logger");
const errors_1 = require("../errors");
const utils = require("../utils");
const ortc = require("../ortc");
const edgeUtils = require("./ortc/edgeUtils");
const HandlerInterface_1 = require("./HandlerInterface");
const logger = new Logger_1.Logger('Edge11');
const NAME = 'Edge11';
class Edge11 extends HandlerInterface_1.HandlerInterface {
// Generic sending RTP parameters for audio and video.
_sendingRtpParametersByKind;
// Transport remote ICE parameters.
_remoteIceParameters;
// Transport remote ICE candidates.
_remoteIceCandidates;
// Transport remote DTLS parameters.
_remoteDtlsParameters;
// ICE gatherer.
_iceGatherer;
// ICE transport.
_iceTransport;
// DTLS transport.
_dtlsTransport;
// Map of RTCRtpSenders indexed by id.
_rtpSenders = new Map();
// Map of RTCRtpReceivers indexed by id.
_rtpReceivers = new Map();
// Next localId for sending tracks.
_nextSendLocalId = 0;
// Local RTCP CNAME.
_cname;
// Got transport local and remote parameters.
_transportReady = false;
/**
* Creates a factory function.
*/
static createFactory() {
return () => new Edge11();
}
constructor() {
super();
}
get name() {
return NAME;
}
close() {
logger.debug('close()');
// Close the ICE gatherer.
// NOTE: Not yet implemented by Edge.
try {
this._iceGatherer.close();
}
catch (error) { }
// Close the ICE transport.
try {
this._iceTransport.stop();
}
catch (error) { }
// Close the DTLS transport.
try {
this._dtlsTransport.stop();
}
catch (error) { }
// Close RTCRtpSenders.
for (const rtpSender of this._rtpSenders.values()) {
try {
rtpSender.stop();
}
catch (error) { }
}
// Close RTCRtpReceivers.
for (const rtpReceiver of this._rtpReceivers.values()) {
try {
rtpReceiver.stop();
}
catch (error) { }
}
this.emit('@close');
}
async getNativeRtpCapabilities() {
logger.debug('getNativeRtpCapabilities()');
return edgeUtils.getCapabilities();
}
async getNativeSctpCapabilities() {
logger.debug('getNativeSctpCapabilities()');
return {
numStreams: { OS: 0, MIS: 0 },
};
}
run({ direction, // eslint-disable-line @typescript-eslint/no-unused-vars
iceParameters, iceCandidates, dtlsParameters, sctpParameters, // eslint-disable-line @typescript-eslint/no-unused-vars
iceServers, iceTransportPolicy, additionalSettings, // eslint-disable-line @typescript-eslint/no-unused-vars
proprietaryConstraints, // eslint-disable-line @typescript-eslint/no-unused-vars
extendedRtpCapabilities, }) {
logger.debug('run()');
this._sendingRtpParametersByKind = {
audio: ortc.getSendingRtpParameters('audio', extendedRtpCapabilities),
video: ortc.getSendingRtpParameters('video', extendedRtpCapabilities),
};
this._remoteIceParameters = iceParameters;
this._remoteIceCandidates = iceCandidates;
this._remoteDtlsParameters = dtlsParameters;
this._cname = `CNAME-${utils.generateRandomNumber()}`;
this.setIceGatherer({ iceServers, iceTransportPolicy });
this.setIceTransport();
this.setDtlsTransport();
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async updateIceServers(iceServers) {
// NOTE: Edge 11 does not implement iceGatherer.gater().
throw new errors_1.UnsupportedError('not supported');
}
async restartIce(iceParameters) {
logger.debug('restartIce()');
this._remoteIceParameters = iceParameters;
if (!this._transportReady) {
return;
}
logger.debug('restartIce() | calling iceTransport.start()');
this._iceTransport.start(this._iceGatherer, iceParameters, 'controlling');
for (const candidate of this._remoteIceCandidates) {
this._iceTransport.addRemoteCandidate(candidate);
}
this._iceTransport.addRemoteCandidate({});
}
async getTransportStats() {
return this._iceTransport.getStats();
}
async send(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
{ track, encodings, codecOptions, codec }) {
logger.debug('send() [kind:%s, track.id:%s]', track.kind, track.id);
if (!this._transportReady) {
await this.setupTransport({ localDtlsRole: 'server' });
}
logger.debug('send() | calling new RTCRtpSender()');
const rtpSender = new RTCRtpSender(track, this._dtlsTransport);
const rtpParameters = utils.clone(this._sendingRtpParametersByKind[track.kind]);
rtpParameters.codecs = ortc.reduceCodecs(rtpParameters.codecs, codec);
const useRtx = rtpParameters.codecs.some((_codec) => /.+\/rtx$/i.test(_codec.mimeType));
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,
};
// NOTE: Convert our standard RTCRtpParameters into those that Edge
// expects.
const edgeRtpParameters = edgeUtils.mangleRtpParameters(rtpParameters);
logger.debug('send() | calling rtpSender.send() [params:%o]', edgeRtpParameters);
await rtpSender.send(edgeRtpParameters);
const localId = String(this._nextSendLocalId);
this._nextSendLocalId++;
// Store it.
this._rtpSenders.set(localId, rtpSender);
return { localId, rtpParameters, rtpSender };
}
async stopSending(localId) {
logger.debug('stopSending() [localId:%s]', localId);
const rtpSender = this._rtpSenders.get(localId);
if (!rtpSender) {
throw new Error('RTCRtpSender not found');
}
this._rtpSenders.delete(localId);
try {
logger.debug('stopSending() | calling rtpSender.stop()');
rtpSender.stop();
}
catch (error) {
logger.warn('stopSending() | rtpSender.stop() failed:%o', error);
throw error;
}
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async pauseSending(localId) {
// Unimplemented.
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async resumeSending(localId) {
// Unimplemented.
}
async replaceTrack(localId, track) {
if (track) {
logger.debug('replaceTrack() [localId:%s, track.id:%s]', localId, track.id);
}
else {
logger.debug('replaceTrack() [localId:%s, no track]', localId);
}
const rtpSender = this._rtpSenders.get(localId);
if (!rtpSender) {
throw new Error('RTCRtpSender not found');
}
rtpSender.setTrack(track);
}
async setMaxSpatialLayer(localId, spatialLayer) {
logger.debug('setMaxSpatialLayer() [localId:%s, spatialLayer:%s]', localId, spatialLayer);
const rtpSender = this._rtpSenders.get(localId);
if (!rtpSender) {
throw new Error('RTCRtpSender not found');
}
const parameters = rtpSender.getParameters();
parameters.encodings.forEach((encoding, idx) => {
if (idx <= spatialLayer) {
encoding.active = true;
}
else {
encoding.active = false;
}
});
await rtpSender.setParameters(parameters);
}
async setRtpEncodingParameters(localId, params) {
logger.debug('setRtpEncodingParameters() [localId:%s, params:%o]', localId, params);
const rtpSender = this._rtpSenders.get(localId);
if (!rtpSender) {
throw new Error('RTCRtpSender not found');
}
const parameters = rtpSender.getParameters();
parameters.encodings.forEach((encoding, idx) => {
parameters.encodings[idx] = { ...encoding, ...params };
});
await rtpSender.setParameters(parameters);
}
async getSenderStats(localId) {
const rtpSender = this._rtpSenders.get(localId);
if (!rtpSender) {
throw new Error('RTCRtpSender not found');
}
return rtpSender.getStats();
}
async sendDataChannel(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
options) {
throw new errors_1.UnsupportedError('not implemented');
}
async receive(optionsList) {
const results = [];
for (const options of optionsList) {
const { trackId, kind } = options;
logger.debug('receive() [trackId:%s, kind:%s]', trackId, kind);
}
if (!this._transportReady) {
await this.setupTransport({ localDtlsRole: 'server' });
}
for (const options of optionsList) {
const { trackId, kind, rtpParameters } = options;
logger.debug('receive() | calling new RTCRtpReceiver()');
const rtpReceiver = new RTCRtpReceiver(this._dtlsTransport, kind);
rtpReceiver.addEventListener('error', (event) => {
logger.error('rtpReceiver "error" event [event:%o]', event);
});
// NOTE: Convert our standard RTCRtpParameters into those that Edge
// expects.
const edgeRtpParameters = edgeUtils.mangleRtpParameters(rtpParameters);
logger.debug('receive() | calling rtpReceiver.receive() [params:%o]', edgeRtpParameters);
await rtpReceiver.receive(edgeRtpParameters);
const localId = trackId;
// Store it.
this._rtpReceivers.set(localId, rtpReceiver);
results.push({
localId,
track: rtpReceiver.track,
rtpReceiver,
});
}
return results;
}
async stopReceiving(localIds) {
for (const localId of localIds) {
logger.debug('stopReceiving() [localId:%s]', localId);
const rtpReceiver = this._rtpReceivers.get(localId);
if (!rtpReceiver) {
throw new Error('RTCRtpReceiver not found');
}
this._rtpReceivers.delete(localId);
try {
logger.debug('stopReceiving() | calling rtpReceiver.stop()');
rtpReceiver.stop();
}
catch (error) {
logger.warn('stopReceiving() | rtpReceiver.stop() failed:%o', error);
}
}
}
async pauseReceiving(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
localIds) {
// Unimplemented.
}
async resumeReceiving(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
localIds) {
// Unimplemented.
}
async getReceiverStats(localId) {
const rtpReceiver = this._rtpReceivers.get(localId);
if (!rtpReceiver) {
throw new Error('RTCRtpReceiver not found');
}
return rtpReceiver.getStats();
}
async receiveDataChannel(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
options) {
throw new errors_1.UnsupportedError('not implemented');
}
setIceGatherer({ iceServers, iceTransportPolicy, }) {
// @ts-expect-error --- On purpose
const iceGatherer = new RTCIceGatherer({
iceServers: iceServers ?? [],
gatherPolicy: iceTransportPolicy ?? 'all',
});
iceGatherer.addEventListener('error', (event) => {
logger.error('iceGatherer "error" event [event:%o]', event);
});
// NOTE: Not yet implemented by Edge, which starts gathering automatically.
try {
iceGatherer.gather();
}
catch (error) {
logger.debug('setIceGatherer() | iceGatherer.gather() failed: %s', error.toString());
}
this._iceGatherer = iceGatherer;
}
setIceTransport() {
const iceTransport = new RTCIceTransport(this._iceGatherer);
// NOTE: Not yet implemented by Edge.
iceTransport.addEventListener('statechange', () => {
switch (iceTransport.state) {
case 'checking': {
this.emit('@connectionstatechange', 'connecting');
break;
}
case 'connected':
case 'completed': {
this.emit('@connectionstatechange', 'connected');
break;
}
case 'failed': {
this.emit('@connectionstatechange', 'failed');
break;
}
case 'disconnected': {
this.emit('@connectionstatechange', 'disconnected');
break;
}
case 'closed': {
this.emit('@connectionstatechange', 'closed');
break;
}
}
});
// NOTE: Not standard, but implemented by Edge.
iceTransport.addEventListener('icestatechange', () => {
switch (iceTransport.state) {
case 'checking': {
this.emit('@connectionstatechange', 'connecting');
break;
}
case 'connected':
case 'completed': {
this.emit('@connectionstatechange', 'connected');
break;
}
case 'failed': {
this.emit('@connectionstatechange', 'failed');
break;
}
case 'disconnected': {
this.emit('@connectionstatechange', 'disconnected');
break;
}
case 'closed': {
this.emit('@connectionstatechange', 'closed');
break;
}
}
});
iceTransport.addEventListener('candidatepairchange', (event) => {
logger.debug('iceTransport "candidatepairchange" event [pair:%o]', event.pair);
});
this._iceTransport = iceTransport;
}
setDtlsTransport() {
const dtlsTransport = new RTCDtlsTransport(this._iceTransport);
// NOTE: Not yet implemented by Edge.
dtlsTransport.addEventListener('statechange', () => {
logger.debug('dtlsTransport "statechange" event [state:%s]', dtlsTransport.state);
});
// NOTE: Not standard, but implemented by Edge.
dtlsTransport.addEventListener('dtlsstatechange', () => {
logger.debug('dtlsTransport "dtlsstatechange" event [state:%s]', dtlsTransport.state);
if (dtlsTransport.state === 'closed') {
this.emit('@connectionstatechange', 'closed');
}
});
dtlsTransport.addEventListener('error', (event) => {
logger.error('dtlsTransport "error" event [event:%o]', event);
});
this._dtlsTransport = dtlsTransport;
}
async setupTransport({ localDtlsRole, }) {
logger.debug('setupTransport()');
// Get our local DTLS parameters.
const dtlsParameters = this._dtlsTransport.getLocalParameters();
dtlsParameters.role = localDtlsRole;
// Need to tell the remote transport about our parameters.
await new Promise((resolve, reject) => {
this.safeEmit('@connect', { dtlsParameters }, resolve, reject);
});
// Start the RTCIceTransport.
this._iceTransport.start(this._iceGatherer, this._remoteIceParameters, 'controlling');
// Add remote ICE candidates.
for (const candidate of this._remoteIceCandidates) {
this._iceTransport.addRemoteCandidate(candidate);
}
// Also signal a 'complete' candidate as per spec.
// NOTE: It should be {complete: true} but Edge prefers {}.
// NOTE: If we don't signal end of candidates, the Edge RTCIceTransport
// won't enter the 'completed' state.
this._iceTransport.addRemoteCandidate({});
// NOTE: Edge does not like SHA less than 256.
this._remoteDtlsParameters.fingerprints =
this._remoteDtlsParameters.fingerprints.filter((fingerprint) => {
return (fingerprint.algorithm === 'sha-256' ||
fingerprint.algorithm === 'sha-384' ||
fingerprint.algorithm === 'sha-512');
});
// Start the RTCDtlsTransport.
this._dtlsTransport.start(this._remoteDtlsParameters);
this._transportReady = true;
}
}
exports.Edge11 = Edge11;