UNPKG

mediasoup-client

Version:

mediasoup client side TypeScript library

448 lines (447 loc) 17.4 kB
"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;