From 47ece7d82766803c7d95c350e88b56fd42fed70a Mon Sep 17 00:00:00 2001 From: Miroslav Pejic Date: Sun, 19 Sep 2021 14:02:15 +0200 Subject: [PATCH 1/8] ignore min.css --- .prettierignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.prettierignore b/.prettierignore index a80b18f..4231c41 100644 --- a/.prettierignore +++ b/.prettierignore @@ -1,3 +1,4 @@ README.md -*.min.js \ No newline at end of file +*.min.js +*.min.css \ No newline at end of file From 22a643688fa4d04280f032ca52ef0c5b13dde4b3 Mon Sep 17 00:00:00 2001 From: Miroslav Pejic Date: Sun, 19 Sep 2021 14:03:24 +0200 Subject: [PATCH 2/8] automated audio-video change --- public/RoomClient.js | 5 +++++ public/index.html | 14 ++++++++++++-- public/index.js | 4 ++-- 3 files changed, 19 insertions(+), 4 deletions(-) diff --git a/public/RoomClient.js b/public/RoomClient.js index 29d11cd..c8cd701 100644 --- a/public/RoomClient.js +++ b/public/RoomClient.js @@ -521,6 +521,11 @@ class RoomClient { } } + closeThenProduce(type, deviceId) { + this.closeProducer(type) + this.produce(type, deviceId) + } + pauseProducer(type) { if (!this.producerLabel.has(type)) { console.log('There is no producer for this type ' + type) diff --git a/public/index.html b/public/index.html index be3e730..b515929 100644 --- a/public/index.html +++ b/public/index.html @@ -68,10 +68,20 @@


diff --git a/public/index.js b/public/index.js index 28ffcfb..59c9313 100644 --- a/public/index.js +++ b/public/index.js @@ -4,6 +4,8 @@ const socket = io() let producer = null +let isEnumerateDevices = false + nameInput.value = 'user_' + Math.round(Math.random() * 1000) socket.request = function request(type, data = {}) { @@ -93,8 +95,6 @@ function addListeners() { }) } -let isEnumerateDevices = false - function initEnumerateDevices() { // Many browsers, without the consent of getUserMedia, cannot enumerate the devices. if (isEnumerateDevices) return From 9e92f75955baba7af30db3ac59cc0509c71fdb8e Mon Sep 17 00:00:00 2001 From: Miroslav Pejic Date: Sun, 19 Sep 2021 14:05:10 +0200 Subject: [PATCH 3/8] change document.execCommand (depecrated) --- public/RoomClient.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/public/RoomClient.js b/public/RoomClient.js index c8cd701..6f33174 100644 --- a/public/RoomClient.js +++ b/public/RoomClient.js @@ -621,9 +621,10 @@ class RoomClient { document.body.appendChild(tmpInput) tmpInput.value = window.location.href tmpInput.select() - document.execCommand('copy') - document.body.removeChild(tmpInput) - console.log('URL copied to clipboard 👍') + tmpInput.setSelectionRange(0, 99999); // For mobile devices + navigator.clipboard.writeText(tmpInput.value); + document.body.removeChild(tmpInput); + alert('ROOM URL copied to clipboard 👍') } showDevices() { From f4c083645c796f42f635846eb1bfc554c44e6943 Mon Sep 17 00:00:00 2001 From: Miroslav Pejic Date: Thu, 6 Jan 2022 20:14:32 +0100 Subject: [PATCH 4/8] update dependencies --- package.json | 10 +- public/RoomClient.js | 6 +- public/modules/mediasoupclient.min.js | 1244 +++++++++++++++++++++---- 3 files changed, 1092 insertions(+), 168 deletions(-) diff --git a/package.json b/package.json index 0c15b88..6165dfc 100644 --- a/package.json +++ b/package.json @@ -18,13 +18,13 @@ "author": "", "license": "ISC", "dependencies": { - "express": "^4.17.1", + "express": "^4.17.2", "httpolyglot": "^0.1.2", - "mediasoup": "^3.8.2", - "mediasoup-client": "^3.6.37", - "socket.io": "^4.1.3" + "mediasoup": "^3.9.3", + "mediasoup-client": "^3.6.47", + "socket.io": "^4.4.1" }, "devDependencies": { - "prettier": "2.3.2" + "prettier": "2.5.1" } } diff --git a/public/RoomClient.js b/public/RoomClient.js index 6f33174..4629b91 100644 --- a/public/RoomClient.js +++ b/public/RoomClient.js @@ -621,9 +621,9 @@ class RoomClient { document.body.appendChild(tmpInput) tmpInput.value = window.location.href tmpInput.select() - tmpInput.setSelectionRange(0, 99999); // For mobile devices - navigator.clipboard.writeText(tmpInput.value); - document.body.removeChild(tmpInput); + tmpInput.setSelectionRange(0, 99999) // For mobile devices + navigator.clipboard.writeText(tmpInput.value) + document.body.removeChild(tmpInput) alert('ROOM URL copied to clipboard 👍') } diff --git a/public/modules/mediasoupclient.min.js b/public/modules/mediasoupclient.min.js index 7b8efd0..7bf3640 100644 --- a/public/modules/mediasoupclient.min.js +++ b/public/modules/mediasoupclient.min.js @@ -2,6 +2,7 @@ const client = require('mediasoup-client') window.mediasoupClient = client + },{"mediasoup-client":35}],2:[function(require,module,exports){ "use strict"; var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { @@ -623,21 +624,12 @@ function isLevelAsymmetryAllowed(params = {}) * This is the web browser implementation of `debug()`. */ +exports.log = log; exports.formatArgs = formatArgs; exports.save = save; exports.load = load; exports.useColors = useColors; exports.storage = localstorage(); -exports.destroy = (() => { - let warned = false; - - return () => { - if (!warned) { - warned = true; - console.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.'); - } - }; -})(); /** * Colors. @@ -798,14 +790,18 @@ function formatArgs(args) { } /** - * Invokes `console.debug()` when available. - * No-op when `console.debug` is not a "function". - * If `console.debug` is not available, falls back - * to `console.log`. + * Invokes `console.log()` when available. + * No-op when `console.log` is not a "function". * * @api public */ -exports.log = console.debug || console.log || (() => {}); +function log(...args) { + // This hackery is required for IE8/9, where + // the `console.log` function doesn't have 'apply' + return typeof console === 'object' && + console.log && + console.log(...args); +} /** * Save `namespaces`. @@ -903,12 +899,16 @@ function setup(env) { createDebug.enable = enable; createDebug.enabled = enabled; createDebug.humanize = require('ms'); - createDebug.destroy = destroy; Object.keys(env).forEach(key => { createDebug[key] = env[key]; }); + /** + * Active `debug` instances. + */ + createDebug.instances = []; + /** * The currently active debug mode names, and names to skip. */ @@ -950,9 +950,6 @@ function setup(env) { */ function createDebug(namespace) { let prevTime; - let enableOverride = null; - let namespacesCache; - let enabledCache; function debug(...args) { // Disabled? @@ -982,7 +979,7 @@ function setup(env) { args[0] = args[0].replace(/%([a-zA-Z%])/g, (match, format) => { // If we encounter an escaped % then don't increase the array index if (match === '%%') { - return '%'; + return match; } index++; const formatter = createDebug.formatters[format]; @@ -1005,38 +1002,33 @@ function setup(env) { } debug.namespace = namespace; + debug.enabled = createDebug.enabled(namespace); debug.useColors = createDebug.useColors(); - debug.color = createDebug.selectColor(namespace); + debug.color = selectColor(namespace); + debug.destroy = destroy; debug.extend = extend; - debug.destroy = createDebug.destroy; // XXX Temporary. Will be removed in the next major release. - - Object.defineProperty(debug, 'enabled', { - enumerable: true, - configurable: false, - get: () => { - if (enableOverride !== null) { - return enableOverride; - } - if (namespacesCache !== createDebug.namespaces) { - namespacesCache = createDebug.namespaces; - enabledCache = createDebug.enabled(namespace); - } - - return enabledCache; - }, - set: v => { - enableOverride = v; - } - }); + // Debug.formatArgs = formatArgs; + // debug.rawLog = rawLog; - // Env-specific initialization logic for debug instances + // env-specific initialization logic for debug instances if (typeof createDebug.init === 'function') { createDebug.init(debug); } + createDebug.instances.push(debug); + return debug; } + function destroy() { + const index = createDebug.instances.indexOf(this); + if (index !== -1) { + createDebug.instances.splice(index, 1); + return true; + } + return false; + } + function extend(namespace, delimiter) { const newDebug = createDebug(this.namespace + (typeof delimiter === 'undefined' ? ':' : delimiter) + namespace); newDebug.log = this.log; @@ -1052,7 +1044,6 @@ function setup(env) { */ function enable(namespaces) { createDebug.save(namespaces); - createDebug.namespaces = namespaces; createDebug.names = []; createDebug.skips = []; @@ -1075,6 +1066,11 @@ function setup(env) { createDebug.names.push(new RegExp('^' + namespaces + '$')); } } + + for (i = 0; i < createDebug.instances.length; i++) { + const instance = createDebug.instances[i]; + instance.enabled = createDebug.enabled(instance.namespace); + } } /** @@ -1149,14 +1145,6 @@ function setup(env) { return val; } - /** - * XXX DO NOT USE. This is a temporary stub function. - * XXX It WILL be removed in the next major release. - */ - function destroy() { - console.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.'); - } - createDebug.enable(createDebug.load()); return createDebug; @@ -1342,6 +1330,8 @@ class Consumer extends EnhancedEventEmitter_1.EnhancedEventEmitter { * @emits trackended * @emits @getstats * @emits @close + * @emits @pause + * @emits @resume */ constructor({ id, localId, producerId, rtpReceiver, track, rtpParameters, appData }) { super(); @@ -1483,6 +1473,7 @@ class Consumer extends EnhancedEventEmitter_1.EnhancedEventEmitter { } this._paused = true; this._track.enabled = false; + this.emit('@pause'); // Emit observer event. this._observer.safeEmit('pause'); } @@ -1497,6 +1488,7 @@ class Consumer extends EnhancedEventEmitter_1.EnhancedEventEmitter { } this._paused = false; this._track.enabled = true; + this.emit('@resume'); // Emit observer event. this._observer.safeEmit('resume'); } @@ -1889,7 +1881,7 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? ( var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; @@ -1931,8 +1923,8 @@ function detectDevice() { const ua = navigator.userAgent; const browser = bowser_1.default.getParser(ua); const engine = browser.getEngine(); - // Chrome and Chromium. - if (browser.satisfies({ chrome: '>=74', chromium: '>=74' })) { + // Chrome, Chromium, and Edge. + if (browser.satisfies({ chrome: '>=74', chromium: '>=74', 'microsoft edge': '>=88' })) { return 'Chrome74'; } else if (browser.satisfies({ chrome: '>=70', chromium: '>=70' })) { @@ -1948,6 +1940,10 @@ function detectDevice() { else if (browser.satisfies({ firefox: '>=60' })) { return 'Firefox60'; } + // Firefox on iOS. + else if (browser.satisfies({ ios: { OS: '>=14.3', firefox: '>=30.0' } })) { + return 'Safari12'; + } // Safari with Unified-Plan support enabled. else if (browser.satisfies({ safari: '>=12.0' }) && typeof RTCRtpTransceiver !== 'undefined' && @@ -2311,14 +2307,14 @@ const APP_NAME = 'mediasoup-client'; class Logger { constructor(prefix) { if (prefix) { - this._debug = debug_1.default(`${APP_NAME}:${prefix}`); - this._warn = debug_1.default(`${APP_NAME}:WARN:${prefix}`); - this._error = debug_1.default(`${APP_NAME}:ERROR:${prefix}`); + this._debug = (0, debug_1.default)(`${APP_NAME}:${prefix}`); + this._warn = (0, debug_1.default)(`${APP_NAME}:WARN:${prefix}`); + this._error = (0, debug_1.default)(`${APP_NAME}:ERROR:${prefix}`); } else { - this._debug = debug_1.default(APP_NAME); - this._warn = debug_1.default(`${APP_NAME}:WARN`); - this._error = debug_1.default(`${APP_NAME}:ERROR`); + this._debug = (0, debug_1.default)(APP_NAME); + this._warn = (0, debug_1.default)(`${APP_NAME}:WARN`); + this._error = (0, debug_1.default)(`${APP_NAME}:ERROR`); } /* eslint-disable no-console */ this._debug.log = console.info.bind(console); @@ -2654,7 +2650,7 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? ( var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; @@ -3155,6 +3151,14 @@ class Transport extends EnhancedEventEmitter_1.EnhancedEventEmitter { this._awaitQueue.push(async () => this._handler.stopReceiving(consumer.localId), 'consumer @close event') .catch(() => { }); }); + consumer.on('@pause', () => { + this._awaitQueue.push(async () => this._handler.pauseReceiving(consumer.localId), 'consumer @pause event') + .catch(() => { }); + }); + consumer.on('@resume', () => { + this._awaitQueue.push(async () => this._handler.resumeReceiving(consumer.localId), 'consumer @resume event') + .catch(() => { }); + }); consumer.on('@getstats', (callback, errback) => { if (this._closed) return errback(new errors_1.InvalidStateError('closed')); @@ -3234,7 +3238,7 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? ( var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; @@ -3345,7 +3349,19 @@ class Chrome55 extends HandlerInterface_1.HandlerInterface { audio: ortc.getSendingRemoteRtpParameters('audio', extendedRtpCapabilities), video: ortc.getSendingRemoteRtpParameters('video', extendedRtpCapabilities) }; - this._pc = new RTCPeerConnection(Object.assign({ iceServers: iceServers || [], iceTransportPolicy: iceTransportPolicy || 'all', bundlePolicy: 'max-bundle', rtcpMuxPolicy: 'require', sdpSemantics: 'plan-b' }, additionalSettings), proprietaryConstraints); + if (dtlsParameters.role && dtlsParameters.role !== 'auto') { + this._forcedLocalDtlsRole = dtlsParameters.role === 'server' + ? 'client' + : 'server'; + } + this._pc = new RTCPeerConnection({ + iceServers: iceServers || [], + iceTransportPolicy: iceTransportPolicy || 'all', + bundlePolicy: 'max-bundle', + rtcpMuxPolicy: 'require', + sdpSemantics: 'plan-b', + ...additionalSettings + }, proprietaryConstraints); // Handle RTCPeerConnection connection status. this._pc.addEventListener('iceconnectionstatechange', () => { switch (this._pc.iceConnectionState) { @@ -3401,6 +3417,7 @@ class Chrome55 extends HandlerInterface_1.HandlerInterface { return this._pc.getStats(); } async send({ track, encodings, codecOptions, codec }) { + var _a; this._assertSendDirection(); logger.debug('send() [kind:%s, track.id:%s]', track.kind, track.id); if (codec) { @@ -3417,8 +3434,12 @@ class Chrome55 extends HandlerInterface_1.HandlerInterface { const sendingRemoteRtpParameters = utils.clone(this._sendingRemoteRtpParametersByKind[track.kind], {}); sendingRemoteRtpParameters.codecs = ortc.reduceCodecs(sendingRemoteRtpParameters.codecs); - if (!this._transportReady) - await this._setupTransport({ localDtlsRole: 'server', localSdpObject }); + if (!this._transportReady) { + await this._setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); + } if (track.kind === 'video' && encodings && encodings.length > 1) { logger.debug('send() | enabling simulcast'); localSdpObject = sdpTransform.parse(offer.sdp); @@ -3521,6 +3542,7 @@ class Chrome55 extends HandlerInterface_1.HandlerInterface { throw new errors_1.UnsupportedError('not implemented'); } async sendDataChannel({ ordered, maxPacketLifeTime, maxRetransmits, label, protocol }) { + var _a; this._assertSendDirection(); const options = { negotiated: true, @@ -3543,8 +3565,12 @@ class Chrome55 extends HandlerInterface_1.HandlerInterface { const localSdpObject = sdpTransform.parse(offer.sdp); const offerMediaObject = localSdpObject.media .find((m) => m.type === 'application'); - if (!this._transportReady) - await this._setupTransport({ localDtlsRole: 'server', localSdpObject }); + if (!this._transportReady) { + await this._setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); + } logger.debug('sendDataChannel() | calling pc.setLocalDescription() [offer:%o]', offer); await this._pc.setLocalDescription(offer); this._remoteSdp.sendSctpAssociation({ offerMediaObject }); @@ -3562,6 +3588,7 @@ class Chrome55 extends HandlerInterface_1.HandlerInterface { return { dataChannel, sctpStreamParameters }; } async receive({ trackId, kind, rtpParameters }) { + var _a; this._assertRecvDirection(); logger.debug('receive() [trackId:%s, kind:%s]', trackId, kind); const localId = trackId; @@ -3588,8 +3615,12 @@ class Chrome55 extends HandlerInterface_1.HandlerInterface { answerMediaObject }); answer = { type: 'answer', sdp: sdpTransform.write(localSdpObject) }; - if (!this._transportReady) - await this._setupTransport({ localDtlsRole: 'client', localSdpObject }); + if (!this._transportReady) { + await this._setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); + } logger.debug('receive() | calling pc.setLocalDescription() [answer:%o]', answer); await this._pc.setLocalDescription(answer); const stream = this._pc.getRemoteStreams() @@ -3615,11 +3646,22 @@ class Chrome55 extends HandlerInterface_1.HandlerInterface { logger.debug('stopReceiving() | calling pc.setLocalDescription() [answer:%o]', answer); await this._pc.setLocalDescription(answer); } + async pauseReceiving( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + localId) { + // Unimplemented. + } + async resumeReceiving( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + localId) { + // Unimplemented. + } // eslint-disable-next-line @typescript-eslint/no-unused-vars async getReceiverStats(localId) { throw new errors_1.UnsupportedError('not implemented'); } async receiveDataChannel({ sctpStreamParameters, label, protocol }) { + var _a; this._assertRecvDirection(); const { streamId, ordered, maxPacketLifeTime, maxRetransmits } = sctpStreamParameters; const options = { @@ -3643,7 +3685,10 @@ class Chrome55 extends HandlerInterface_1.HandlerInterface { const answer = await this._pc.createAnswer(); if (!this._transportReady) { const localSdpObject = sdpTransform.parse(answer.sdp); - await this._setupTransport({ localDtlsRole: 'client', localSdpObject }); + await this._setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); } logger.debug('receiveDataChannel() | calling pc.setRemoteDescription() [answer:%o]', answer); await this._pc.setLocalDescription(answer); @@ -3694,7 +3739,7 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? ( var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; @@ -3804,7 +3849,19 @@ class Chrome67 extends HandlerInterface_1.HandlerInterface { audio: ortc.getSendingRemoteRtpParameters('audio', extendedRtpCapabilities), video: ortc.getSendingRemoteRtpParameters('video', extendedRtpCapabilities) }; - this._pc = new RTCPeerConnection(Object.assign({ iceServers: iceServers || [], iceTransportPolicy: iceTransportPolicy || 'all', bundlePolicy: 'max-bundle', rtcpMuxPolicy: 'require', sdpSemantics: 'plan-b' }, additionalSettings), proprietaryConstraints); + if (dtlsParameters.role && dtlsParameters.role !== 'auto') { + this._forcedLocalDtlsRole = dtlsParameters.role === 'server' + ? 'client' + : 'server'; + } + this._pc = new RTCPeerConnection({ + iceServers: iceServers || [], + iceTransportPolicy: iceTransportPolicy || 'all', + bundlePolicy: 'max-bundle', + rtcpMuxPolicy: 'require', + sdpSemantics: 'plan-b', + ...additionalSettings + }, proprietaryConstraints); // Handle RTCPeerConnection connection status. this._pc.addEventListener('iceconnectionstatechange', () => { switch (this._pc.iceConnectionState) { @@ -3860,6 +3917,7 @@ class Chrome67 extends HandlerInterface_1.HandlerInterface { return this._pc.getStats(); } async send({ track, encodings, codecOptions, codec }) { + var _a; this._assertSendDirection(); logger.debug('send() [kind:%s, track.id:%s]', track.kind, track.id); if (codec) { @@ -3876,8 +3934,12 @@ class Chrome67 extends HandlerInterface_1.HandlerInterface { const sendingRemoteRtpParameters = utils.clone(this._sendingRemoteRtpParametersByKind[track.kind], {}); sendingRemoteRtpParameters.codecs = ortc.reduceCodecs(sendingRemoteRtpParameters.codecs); - if (!this._transportReady) - await this._setupTransport({ localDtlsRole: 'server', localSdpObject }); + if (!this._transportReady) { + await this._setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); + } if (track.kind === 'video' && encodings && encodings.length > 1) { logger.debug('send() | enabling simulcast'); localSdpObject = sdpTransform.parse(offer.sdp); @@ -4010,7 +4072,7 @@ class Chrome67 extends HandlerInterface_1.HandlerInterface { throw new Error('associated RTCRtpSender not found'); const parameters = rtpSender.getParameters(); parameters.encodings.forEach((encoding, idx) => { - parameters.encodings[idx] = Object.assign(Object.assign({}, encoding), params); + parameters.encodings[idx] = { ...encoding, ...params }; }); await rtpSender.setParameters(parameters); } @@ -4022,6 +4084,7 @@ class Chrome67 extends HandlerInterface_1.HandlerInterface { return rtpSender.getStats(); } async sendDataChannel({ ordered, maxPacketLifeTime, maxRetransmits, label, protocol }) { + var _a; this._assertSendDirection(); const options = { negotiated: true, @@ -4044,8 +4107,12 @@ class Chrome67 extends HandlerInterface_1.HandlerInterface { const localSdpObject = sdpTransform.parse(offer.sdp); const offerMediaObject = localSdpObject.media .find((m) => m.type === 'application'); - if (!this._transportReady) - await this._setupTransport({ localDtlsRole: 'server', localSdpObject }); + if (!this._transportReady) { + await this._setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); + } logger.debug('sendDataChannel() | calling pc.setLocalDescription() [offer:%o]', offer); await this._pc.setLocalDescription(offer); this._remoteSdp.sendSctpAssociation({ offerMediaObject }); @@ -4063,6 +4130,7 @@ class Chrome67 extends HandlerInterface_1.HandlerInterface { return { dataChannel, sctpStreamParameters }; } async receive({ trackId, kind, rtpParameters }) { + var _a; this._assertRecvDirection(); logger.debug('receive() [trackId:%s, kind:%s]', trackId, kind); const localId = trackId; @@ -4088,8 +4156,12 @@ class Chrome67 extends HandlerInterface_1.HandlerInterface { answerMediaObject }); answer = { type: 'answer', sdp: sdpTransform.write(localSdpObject) }; - if (!this._transportReady) - await this._setupTransport({ localDtlsRole: 'client', localSdpObject }); + if (!this._transportReady) { + await this._setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); + } logger.debug('receive() | calling pc.setLocalDescription() [answer:%o]', answer); await this._pc.setLocalDescription(answer); const rtpReceiver = this._pc.getReceivers() @@ -4118,6 +4190,16 @@ class Chrome67 extends HandlerInterface_1.HandlerInterface { logger.debug('stopReceiving() | calling pc.setLocalDescription() [answer:%o]', answer); await this._pc.setLocalDescription(answer); } + async pauseReceiving( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + localId) { + // Unimplemented. + } + async resumeReceiving( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + localId) { + // Unimplemented. + } async getReceiverStats(localId) { this._assertRecvDirection(); const { rtpReceiver } = this._mapRecvLocalIdInfo.get(localId) || {}; @@ -4126,6 +4208,7 @@ class Chrome67 extends HandlerInterface_1.HandlerInterface { return rtpReceiver.getStats(); } async receiveDataChannel({ sctpStreamParameters, label, protocol }) { + var _a; this._assertRecvDirection(); const { streamId, ordered, maxPacketLifeTime, maxRetransmits } = sctpStreamParameters; const options = { @@ -4149,7 +4232,10 @@ class Chrome67 extends HandlerInterface_1.HandlerInterface { const answer = await this._pc.createAnswer(); if (!this._transportReady) { const localSdpObject = sdpTransform.parse(answer.sdp); - await this._setupTransport({ localDtlsRole: 'client', localSdpObject }); + await this._setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); } logger.debug('receiveDataChannel() | calling pc.setRemoteDescription() [answer:%o]', answer); await this._pc.setLocalDescription(answer); @@ -4200,7 +4286,7 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? ( var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; @@ -4304,7 +4390,19 @@ class Chrome70 extends HandlerInterface_1.HandlerInterface { audio: ortc.getSendingRemoteRtpParameters('audio', extendedRtpCapabilities), video: ortc.getSendingRemoteRtpParameters('video', extendedRtpCapabilities) }; - this._pc = new RTCPeerConnection(Object.assign({ iceServers: iceServers || [], iceTransportPolicy: iceTransportPolicy || 'all', bundlePolicy: 'max-bundle', rtcpMuxPolicy: 'require', sdpSemantics: 'unified-plan' }, additionalSettings), proprietaryConstraints); + if (dtlsParameters.role && dtlsParameters.role !== 'auto') { + this._forcedLocalDtlsRole = dtlsParameters.role === 'server' + ? 'client' + : 'server'; + } + this._pc = new RTCPeerConnection({ + iceServers: iceServers || [], + iceTransportPolicy: iceTransportPolicy || 'all', + bundlePolicy: 'max-bundle', + rtcpMuxPolicy: 'require', + sdpSemantics: 'unified-plan', + ...additionalSettings + }, proprietaryConstraints); // Handle RTCPeerConnection connection status. this._pc.addEventListener('iceconnectionstatechange', () => { switch (this._pc.iceConnectionState) { @@ -4360,6 +4458,7 @@ class Chrome70 extends HandlerInterface_1.HandlerInterface { return this._pc.getStats(); } async send({ track, encodings, codecOptions, codec }) { + var _a; this._assertSendDirection(); logger.debug('send() [kind:%s, track.id:%s]', track.kind, track.id); const sendingRtpParameters = utils.clone(this._sendingRtpParametersByKind[track.kind], {}); @@ -4375,8 +4474,12 @@ class Chrome70 extends HandlerInterface_1.HandlerInterface { let offer = await this._pc.createOffer(); let localSdpObject = sdpTransform.parse(offer.sdp); let offerMediaObject; - if (!this._transportReady) - await this._setupTransport({ localDtlsRole: 'server', localSdpObject }); + if (!this._transportReady) { + await this._setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); + } if (encodings && encodings.length > 1) { logger.debug('send() | enabling legacy simulcast'); localSdpObject = sdpTransform.parse(offer.sdp); @@ -4389,7 +4492,7 @@ class Chrome70 extends HandlerInterface_1.HandlerInterface { } // Special case for VP9 with SVC. let hackVp9Svc = false; - const layers = scalabilityModes_1.parse((encodings || [{}])[0].scalabilityMode); + const layers = (0, scalabilityModes_1.parse)((encodings || [{}])[0].scalabilityMode); if (encodings && encodings.length === 1 && layers.spatialLayers > 1 && @@ -4485,6 +4588,7 @@ class Chrome70 extends HandlerInterface_1.HandlerInterface { const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; logger.debug('stopSending() | calling pc.setRemoteDescription() [answer:%o]', answer); await this._pc.setRemoteDescription(answer); + this._mapMidTransceiver.delete(localId); } async replaceTrack(localId, track) { this._assertSendDirection(); @@ -4522,7 +4626,7 @@ class Chrome70 extends HandlerInterface_1.HandlerInterface { throw new Error('associated RTCRtpTransceiver not found'); const parameters = transceiver.sender.getParameters(); parameters.encodings.forEach((encoding, idx) => { - parameters.encodings[idx] = Object.assign(Object.assign({}, encoding), params); + parameters.encodings[idx] = { ...encoding, ...params }; }); await transceiver.sender.setParameters(parameters); } @@ -4534,6 +4638,7 @@ class Chrome70 extends HandlerInterface_1.HandlerInterface { return transceiver.sender.getStats(); } async sendDataChannel({ ordered, maxPacketLifeTime, maxRetransmits, label, protocol }) { + var _a; this._assertSendDirection(); const options = { negotiated: true, @@ -4556,8 +4661,12 @@ class Chrome70 extends HandlerInterface_1.HandlerInterface { const localSdpObject = sdpTransform.parse(offer.sdp); const offerMediaObject = localSdpObject.media .find((m) => m.type === 'application'); - if (!this._transportReady) - await this._setupTransport({ localDtlsRole: 'server', localSdpObject }); + if (!this._transportReady) { + await this._setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); + } logger.debug('sendDataChannel() | calling pc.setLocalDescription() [offer:%o]', offer); await this._pc.setLocalDescription(offer); this._remoteSdp.sendSctpAssociation({ offerMediaObject }); @@ -4575,6 +4684,7 @@ class Chrome70 extends HandlerInterface_1.HandlerInterface { return { dataChannel, sctpStreamParameters }; } async receive({ trackId, kind, rtpParameters }) { + var _a; this._assertRecvDirection(); logger.debug('receive() [trackId:%s, kind:%s]', trackId, kind); const localId = rtpParameters.mid || String(this._mapMidTransceiver.size); @@ -4599,8 +4709,12 @@ class Chrome70 extends HandlerInterface_1.HandlerInterface { answerMediaObject }); answer = { type: 'answer', sdp: sdpTransform.write(localSdpObject) }; - if (!this._transportReady) - await this._setupTransport({ localDtlsRole: 'client', localSdpObject }); + if (!this._transportReady) { + await this._setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); + } logger.debug('receive() | calling pc.setLocalDescription() [answer:%o]', answer); await this._pc.setLocalDescription(answer); const transceiver = this._pc.getTransceivers() @@ -4628,6 +4742,17 @@ class Chrome70 extends HandlerInterface_1.HandlerInterface { const answer = await this._pc.createAnswer(); logger.debug('stopReceiving() | calling pc.setLocalDescription() [answer:%o]', answer); await this._pc.setLocalDescription(answer); + this._mapMidTransceiver.delete(localId); + } + async pauseReceiving( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + localId) { + // Unimplemented. + } + async resumeReceiving( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + localId) { + // Unimplemented. } async getReceiverStats(localId) { this._assertRecvDirection(); @@ -4637,6 +4762,7 @@ class Chrome70 extends HandlerInterface_1.HandlerInterface { return transceiver.receiver.getStats(); } async receiveDataChannel({ sctpStreamParameters, label, protocol }) { + var _a; this._assertRecvDirection(); const { streamId, ordered, maxPacketLifeTime, maxRetransmits } = sctpStreamParameters; const options = { @@ -4660,7 +4786,10 @@ class Chrome70 extends HandlerInterface_1.HandlerInterface { const answer = await this._pc.createAnswer(); if (!this._transportReady) { const localSdpObject = sdpTransform.parse(answer.sdp); - await this._setupTransport({ localDtlsRole: 'client', localSdpObject }); + await this._setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); } logger.debug('receiveDataChannel() | calling pc.setRemoteDescription() [answer:%o]', answer); await this._pc.setLocalDescription(answer); @@ -4711,7 +4840,7 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? ( var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; @@ -4815,7 +4944,19 @@ class Chrome74 extends HandlerInterface_1.HandlerInterface { audio: ortc.getSendingRemoteRtpParameters('audio', extendedRtpCapabilities), video: ortc.getSendingRemoteRtpParameters('video', extendedRtpCapabilities) }; - this._pc = new RTCPeerConnection(Object.assign({ iceServers: iceServers || [], iceTransportPolicy: iceTransportPolicy || 'all', bundlePolicy: 'max-bundle', rtcpMuxPolicy: 'require', sdpSemantics: 'unified-plan' }, additionalSettings), proprietaryConstraints); + if (dtlsParameters.role && dtlsParameters.role !== 'auto') { + this._forcedLocalDtlsRole = dtlsParameters.role === 'server' + ? 'client' + : 'server'; + } + this._pc = new RTCPeerConnection({ + iceServers: iceServers || [], + iceTransportPolicy: iceTransportPolicy || 'all', + bundlePolicy: 'max-bundle', + rtcpMuxPolicy: 'require', + sdpSemantics: 'unified-plan', + ...additionalSettings + }, proprietaryConstraints); // Handle RTCPeerConnection connection status. this._pc.addEventListener('iceconnectionstatechange', () => { switch (this._pc.iceConnectionState) { @@ -4871,6 +5012,7 @@ class Chrome74 extends HandlerInterface_1.HandlerInterface { return this._pc.getStats(); } async send({ track, encodings, codecOptions, codec }) { + var _a; this._assertSendDirection(); logger.debug('send() [kind:%s, track.id:%s]', track.kind, track.id); if (encodings && encodings.length > 1) { @@ -4895,11 +5037,15 @@ class Chrome74 extends HandlerInterface_1.HandlerInterface { let offer = await this._pc.createOffer(); let localSdpObject = sdpTransform.parse(offer.sdp); let offerMediaObject; - if (!this._transportReady) - await this._setupTransport({ localDtlsRole: 'server', localSdpObject }); + if (!this._transportReady) { + await this._setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); + } // Special case for VP9 with SVC. let hackVp9Svc = false; - const layers = scalabilityModes_1.parse((encodings || [{}])[0].scalabilityMode); + const layers = (0, scalabilityModes_1.parse)((encodings || [{}])[0].scalabilityMode); if (encodings && encodings.length === 1 && layers.spatialLayers > 1 && @@ -4987,6 +5133,7 @@ class Chrome74 extends HandlerInterface_1.HandlerInterface { const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; logger.debug('stopSending() | calling pc.setRemoteDescription() [answer:%o]', answer); await this._pc.setRemoteDescription(answer); + this._mapMidTransceiver.delete(localId); } async replaceTrack(localId, track) { this._assertSendDirection(); @@ -5024,7 +5171,7 @@ class Chrome74 extends HandlerInterface_1.HandlerInterface { throw new Error('associated RTCRtpTransceiver not found'); const parameters = transceiver.sender.getParameters(); parameters.encodings.forEach((encoding, idx) => { - parameters.encodings[idx] = Object.assign(Object.assign({}, encoding), params); + parameters.encodings[idx] = { ...encoding, ...params }; }); await transceiver.sender.setParameters(parameters); } @@ -5036,6 +5183,7 @@ class Chrome74 extends HandlerInterface_1.HandlerInterface { return transceiver.sender.getStats(); } async sendDataChannel({ ordered, maxPacketLifeTime, maxRetransmits, label, protocol }) { + var _a; this._assertSendDirection(); const options = { negotiated: true, @@ -5057,8 +5205,12 @@ class Chrome74 extends HandlerInterface_1.HandlerInterface { const localSdpObject = sdpTransform.parse(offer.sdp); const offerMediaObject = localSdpObject.media .find((m) => m.type === 'application'); - if (!this._transportReady) - await this._setupTransport({ localDtlsRole: 'server', localSdpObject }); + if (!this._transportReady) { + await this._setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); + } logger.debug('sendDataChannel() | calling pc.setLocalDescription() [offer:%o]', offer); await this._pc.setLocalDescription(offer); this._remoteSdp.sendSctpAssociation({ offerMediaObject }); @@ -5076,6 +5228,7 @@ class Chrome74 extends HandlerInterface_1.HandlerInterface { return { dataChannel, sctpStreamParameters }; } async receive({ trackId, kind, rtpParameters }) { + var _a; this._assertRecvDirection(); logger.debug('receive() [trackId:%s, kind:%s]', trackId, kind); const localId = rtpParameters.mid || String(this._mapMidTransceiver.size); @@ -5100,8 +5253,12 @@ class Chrome74 extends HandlerInterface_1.HandlerInterface { answerMediaObject }); answer = { type: 'answer', sdp: sdpTransform.write(localSdpObject) }; - if (!this._transportReady) - await this._setupTransport({ localDtlsRole: 'client', localSdpObject }); + if (!this._transportReady) { + await this._setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); + } logger.debug('receive() | calling pc.setLocalDescription() [answer:%o]', answer); await this._pc.setLocalDescription(answer); const transceiver = this._pc.getTransceivers() @@ -5129,6 +5286,35 @@ class Chrome74 extends HandlerInterface_1.HandlerInterface { const answer = await this._pc.createAnswer(); logger.debug('stopReceiving() | calling pc.setLocalDescription() [answer:%o]', answer); await this._pc.setLocalDescription(answer); + this._mapMidTransceiver.delete(localId); + } + async pauseReceiving(localId) { + this._assertRecvDirection(); + logger.debug('pauseReceiving() [localId:%s]', localId); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) + throw new Error('associated RTCRtpTransceiver not found'); + transceiver.direction = 'inactive'; + const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; + logger.debug('pauseReceiving() | calling pc.setRemoteDescription() [offer:%o]', offer); + await this._pc.setRemoteDescription(offer); + const answer = await this._pc.createAnswer(); + logger.debug('pauseReceiving() | calling pc.setLocalDescription() [answer:%o]', answer); + await this._pc.setLocalDescription(answer); + } + async resumeReceiving(localId) { + this._assertRecvDirection(); + logger.debug('resumeReceiving() [localId:%s]', localId); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) + throw new Error('associated RTCRtpTransceiver not found'); + transceiver.direction = 'recvonly'; + const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; + logger.debug('resumeReceiving() | calling pc.setRemoteDescription() [offer:%o]', offer); + await this._pc.setRemoteDescription(offer); + const answer = await this._pc.createAnswer(); + logger.debug('resumeReceiving() | calling pc.setLocalDescription() [answer:%o]', answer); + await this._pc.setLocalDescription(answer); } async getReceiverStats(localId) { this._assertRecvDirection(); @@ -5138,6 +5324,7 @@ class Chrome74 extends HandlerInterface_1.HandlerInterface { return transceiver.receiver.getStats(); } async receiveDataChannel({ sctpStreamParameters, label, protocol }) { + var _a; this._assertRecvDirection(); const { streamId, ordered, maxPacketLifeTime, maxRetransmits } = sctpStreamParameters; const options = { @@ -5160,7 +5347,10 @@ class Chrome74 extends HandlerInterface_1.HandlerInterface { const answer = await this._pc.createAnswer(); if (!this._transportReady) { const localSdpObject = sdpTransform.parse(answer.sdp); - await this._setupTransport({ localDtlsRole: 'client', localSdpObject }); + await this._setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); } logger.debug('receiveDataChannel() | calling pc.setRemoteDescription() [answer:%o]', answer); await this._pc.setLocalDescription(answer); @@ -5211,7 +5401,7 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? ( var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; @@ -5414,7 +5604,7 @@ class Edge11 extends HandlerInterface_1.HandlerInterface { throw new Error('RTCRtpSender not found'); const parameters = rtpSender.getParameters(); parameters.encodings.forEach((encoding, idx) => { - parameters.encodings[idx] = Object.assign(Object.assign({}, encoding), params); + parameters.encodings[idx] = { ...encoding, ...params }; }); await rtpSender.setParameters(parameters); } @@ -5466,6 +5656,16 @@ class Edge11 extends HandlerInterface_1.HandlerInterface { logger.warn('stopReceiving() | rtpReceiver.stop() failed:%o', error); } } + async pauseReceiving( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + localId) { + // Unimplemented. + } + async resumeReceiving( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + localId) { + // Unimplemented. + } async getReceiverStats(localId) { const rtpReceiver = this._rtpReceivers.get(localId); if (!rtpReceiver) @@ -5478,6 +5678,7 @@ class Edge11 extends HandlerInterface_1.HandlerInterface { throw new errors_1.UnsupportedError('not implemented'); } _setIceGatherer({ iceServers, iceTransportPolicy }) { + // @ts-ignore const iceGatherer = new RTCIceGatherer({ iceServers: iceServers || [], gatherPolicy: iceTransportPolicy || 'all' @@ -5609,7 +5810,7 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? ( var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; @@ -5741,7 +5942,13 @@ class Firefox60 extends HandlerInterface_1.HandlerInterface { audio: ortc.getSendingRemoteRtpParameters('audio', extendedRtpCapabilities), video: ortc.getSendingRemoteRtpParameters('video', extendedRtpCapabilities) }; - this._pc = new RTCPeerConnection(Object.assign({ iceServers: iceServers || [], iceTransportPolicy: iceTransportPolicy || 'all', bundlePolicy: 'max-bundle', rtcpMuxPolicy: 'require' }, additionalSettings), proprietaryConstraints); + this._pc = new RTCPeerConnection({ + iceServers: iceServers || [], + iceTransportPolicy: iceTransportPolicy || 'all', + bundlePolicy: 'max-bundle', + rtcpMuxPolicy: 'require', + ...additionalSettings + }, proprietaryConstraints); // Handle RTCPeerConnection connection status. this._pc.addEventListener('iceconnectionstatechange', () => { switch (this._pc.iceConnectionState) { @@ -5908,6 +6115,7 @@ class Firefox60 extends HandlerInterface_1.HandlerInterface { const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; logger.debug('stopSending() | calling pc.setRemoteDescription() [answer:%o]', answer); await this._pc.setRemoteDescription(answer); + this._mapMidTransceiver.delete(localId); } async replaceTrack(localId, track) { this._assertSendDirection(); @@ -5948,7 +6156,7 @@ class Firefox60 extends HandlerInterface_1.HandlerInterface { throw new Error('associated RTCRtpTransceiver not found'); const parameters = transceiver.sender.getParameters(); parameters.encodings.forEach((encoding, idx) => { - parameters.encodings[idx] = Object.assign(Object.assign({}, encoding), params); + parameters.encodings[idx] = { ...encoding, ...params }; }); await transceiver.sender.setParameters(parameters); } @@ -5982,7 +6190,7 @@ class Firefox60 extends HandlerInterface_1.HandlerInterface { const offerMediaObject = localSdpObject.media .find((m) => m.type === 'application'); if (!this._transportReady) - await this._setupTransport({ localDtlsRole: 'server', localSdpObject }); + await this._setupTransport({ localDtlsRole: 'client', localSdpObject }); logger.debug('sendDataChannel() | calling pc.setLocalDescription() [offer:%o]', offer); await this._pc.setLocalDescription(offer); this._remoteSdp.sendSctpAssociation({ offerMediaObject }); @@ -6053,6 +6261,35 @@ class Firefox60 extends HandlerInterface_1.HandlerInterface { const answer = await this._pc.createAnswer(); logger.debug('stopReceiving() | calling pc.setLocalDescription() [answer:%o]', answer); await this._pc.setLocalDescription(answer); + this._mapMidTransceiver.delete(localId); + } + async pauseReceiving(localId) { + this._assertRecvDirection(); + logger.debug('pauseReceiving() [localId:%s]', localId); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) + throw new Error('associated RTCRtpTransceiver not found'); + transceiver.direction = 'inactive'; + const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; + logger.debug('pauseReceiving() | calling pc.setRemoteDescription() [offer:%o]', offer); + await this._pc.setRemoteDescription(offer); + const answer = await this._pc.createAnswer(); + logger.debug('pauseReceiving() | calling pc.setLocalDescription() [answer:%o]', answer); + await this._pc.setLocalDescription(answer); + } + async resumeReceiving(localId) { + this._assertRecvDirection(); + logger.debug('resumeReceiving() [localId:%s]', localId); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) + throw new Error('associated RTCRtpTransceiver not found'); + transceiver.direction = 'recvonly'; + const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; + logger.debug('resumeReceiving() | calling pc.setRemoteDescription() [offer:%o]', offer); + await this._pc.setRemoteDescription(offer); + const answer = await this._pc.createAnswer(); + logger.debug('resumeReceiving() | calling pc.setLocalDescription() [answer:%o]', answer); + await this._pc.setLocalDescription(answer); } async getReceiverStats(localId) { this._assertRecvDirection(); @@ -6155,7 +6392,7 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? ( var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; @@ -6202,9 +6439,10 @@ class ReactNative extends HandlerInterface_1.HandlerInterface { } close() { logger.debug('close()'); - // Free/dispose native stream and tracks. + // Free/dispose native MediaStream but DO NOT free/dispose native + // MediaStreamTracks (that is parent's business). // @ts-ignore (proprietary API in react-native-webrtc). - this._sendStream.release(); + this._sendStream.release(/* releaseTracks */ false); // Close RTCPeerConnection. if (this._pc) { try { @@ -6269,7 +6507,19 @@ class ReactNative extends HandlerInterface_1.HandlerInterface { audio: ortc.getSendingRemoteRtpParameters('audio', extendedRtpCapabilities), video: ortc.getSendingRemoteRtpParameters('video', extendedRtpCapabilities) }; - this._pc = new RTCPeerConnection(Object.assign({ iceServers: iceServers || [], iceTransportPolicy: iceTransportPolicy || 'all', bundlePolicy: 'max-bundle', rtcpMuxPolicy: 'require', sdpSemantics: 'plan-b' }, additionalSettings), proprietaryConstraints); + if (dtlsParameters.role && dtlsParameters.role !== 'auto') { + this._forcedLocalDtlsRole = dtlsParameters.role === 'server' + ? 'client' + : 'server'; + } + this._pc = new RTCPeerConnection({ + iceServers: iceServers || [], + iceTransportPolicy: iceTransportPolicy || 'all', + bundlePolicy: 'max-bundle', + rtcpMuxPolicy: 'require', + sdpSemantics: 'plan-b', + ...additionalSettings + }, proprietaryConstraints); // Handle RTCPeerConnection connection status. this._pc.addEventListener('iceconnectionstatechange', () => { switch (this._pc.iceConnectionState) { @@ -6325,6 +6575,7 @@ class ReactNative extends HandlerInterface_1.HandlerInterface { return this._pc.getStats(); } async send({ track, encodings, codecOptions, codec }) { + var _a; this._assertSendDirection(); logger.debug('send() [kind:%s, track.id:%s]', track.kind, track.id); if (codec) { @@ -6341,8 +6592,12 @@ class ReactNative extends HandlerInterface_1.HandlerInterface { const sendingRemoteRtpParameters = utils.clone(this._sendingRemoteRtpParametersByKind[track.kind], {}); sendingRemoteRtpParameters.codecs = ortc.reduceCodecs(sendingRemoteRtpParameters.codecs); - if (!this._transportReady) - await this._setupTransport({ localDtlsRole: 'server', localSdpObject }); + if (!this._transportReady) { + await this._setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); + } if (track.kind === 'video' && encodings && encodings.length > 1) { logger.debug('send() | enabling simulcast'); localSdpObject = sdpTransform.parse(offer.sdp); @@ -6447,6 +6702,7 @@ class ReactNative extends HandlerInterface_1.HandlerInterface { throw new errors_1.UnsupportedError('not implemented'); } async sendDataChannel({ ordered, maxPacketLifeTime, maxRetransmits, label, protocol }) { + var _a; this._assertSendDirection(); const options = { negotiated: true, @@ -6469,8 +6725,12 @@ class ReactNative extends HandlerInterface_1.HandlerInterface { const localSdpObject = sdpTransform.parse(offer.sdp); const offerMediaObject = localSdpObject.media .find((m) => m.type === 'application'); - if (!this._transportReady) - await this._setupTransport({ localDtlsRole: 'server', localSdpObject }); + if (!this._transportReady) { + await this._setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); + } logger.debug('sendDataChannel() | calling pc.setLocalDescription() [offer:%o]', offer); await this._pc.setLocalDescription(offer); this._remoteSdp.sendSctpAssociation({ offerMediaObject }); @@ -6488,6 +6748,7 @@ class ReactNative extends HandlerInterface_1.HandlerInterface { return { dataChannel, sctpStreamParameters }; } async receive({ trackId, kind, rtpParameters }) { + var _a; this._assertRecvDirection(); logger.debug('receive() [trackId:%s, kind:%s]', trackId, kind); const localId = trackId; @@ -6520,8 +6781,12 @@ class ReactNative extends HandlerInterface_1.HandlerInterface { answerMediaObject }); answer = { type: 'answer', sdp: sdpTransform.write(localSdpObject) }; - if (!this._transportReady) - await this._setupTransport({ localDtlsRole: 'client', localSdpObject }); + if (!this._transportReady) { + await this._setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); + } logger.debug('receive() | calling pc.setLocalDescription() [answer:%o]', answer); await this._pc.setLocalDescription(answer); const stream = this._pc.getRemoteStreams() @@ -6547,11 +6812,22 @@ class ReactNative extends HandlerInterface_1.HandlerInterface { logger.debug('stopReceiving() | calling pc.setLocalDescription() [answer:%o]', answer); await this._pc.setLocalDescription(answer); } + async pauseReceiving( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + localId) { + // Unimplemented. + } + async resumeReceiving( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + localId) { + // Unimplemented. + } // eslint-disable-next-line @typescript-eslint/no-unused-vars async getReceiverStats(localId) { throw new errors_1.UnsupportedError('not implemented'); } async receiveDataChannel({ sctpStreamParameters, label, protocol }) { + var _a; this._assertRecvDirection(); const { streamId, ordered, maxPacketLifeTime, maxRetransmits } = sctpStreamParameters; const options = { @@ -6575,7 +6851,10 @@ class ReactNative extends HandlerInterface_1.HandlerInterface { const answer = await this._pc.createAnswer(); if (!this._transportReady) { const localSdpObject = sdpTransform.parse(answer.sdp); - await this._setupTransport({ localDtlsRole: 'client', localSdpObject }); + await this._setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); } logger.debug('receiveDataChannel() | calling pc.setRemoteDescription() [answer:%o]', answer); await this._pc.setLocalDescription(answer); @@ -6626,7 +6905,7 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? ( var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; @@ -6736,7 +7015,18 @@ class Safari11 extends HandlerInterface_1.HandlerInterface { audio: ortc.getSendingRemoteRtpParameters('audio', extendedRtpCapabilities), video: ortc.getSendingRemoteRtpParameters('video', extendedRtpCapabilities) }; - this._pc = new RTCPeerConnection(Object.assign({ iceServers: iceServers || [], iceTransportPolicy: iceTransportPolicy || 'all', bundlePolicy: 'max-bundle', rtcpMuxPolicy: 'require' }, additionalSettings), proprietaryConstraints); + if (dtlsParameters.role && dtlsParameters.role !== 'auto') { + this._forcedLocalDtlsRole = dtlsParameters.role === 'server' + ? 'client' + : 'server'; + } + this._pc = new RTCPeerConnection({ + iceServers: iceServers || [], + iceTransportPolicy: iceTransportPolicy || 'all', + bundlePolicy: 'max-bundle', + rtcpMuxPolicy: 'require', + ...additionalSettings + }, proprietaryConstraints); // Handle RTCPeerConnection connection status. this._pc.addEventListener('iceconnectionstatechange', () => { switch (this._pc.iceConnectionState) { @@ -6792,6 +7082,7 @@ class Safari11 extends HandlerInterface_1.HandlerInterface { return this._pc.getStats(); } async send({ track, encodings, codecOptions, codec }) { + var _a; this._assertSendDirection(); logger.debug('send() [kind:%s, track.id:%s]', track.kind, track.id); if (codec) { @@ -6808,8 +7099,12 @@ class Safari11 extends HandlerInterface_1.HandlerInterface { const sendingRemoteRtpParameters = utils.clone(this._sendingRemoteRtpParametersByKind[track.kind], {}); sendingRemoteRtpParameters.codecs = ortc.reduceCodecs(sendingRemoteRtpParameters.codecs); - if (!this._transportReady) - await this._setupTransport({ localDtlsRole: 'server', localSdpObject }); + if (!this._transportReady) { + await this._setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); + } if (track.kind === 'video' && encodings && encodings.length > 1) { logger.debug('send() | enabling simulcast'); localSdpObject = sdpTransform.parse(offer.sdp); @@ -6939,7 +7234,7 @@ class Safari11 extends HandlerInterface_1.HandlerInterface { throw new Error('associated RTCRtpSender not found'); const parameters = rtpSender.getParameters(); parameters.encodings.forEach((encoding, idx) => { - parameters.encodings[idx] = Object.assign(Object.assign({}, encoding), params); + parameters.encodings[idx] = { ...encoding, ...params }; }); await rtpSender.setParameters(parameters); } @@ -6951,6 +7246,7 @@ class Safari11 extends HandlerInterface_1.HandlerInterface { return rtpSender.getStats(); } async sendDataChannel({ ordered, maxPacketLifeTime, maxRetransmits, label, protocol }) { + var _a; this._assertSendDirection(); const options = { negotiated: true, @@ -6972,8 +7268,12 @@ class Safari11 extends HandlerInterface_1.HandlerInterface { const localSdpObject = sdpTransform.parse(offer.sdp); const offerMediaObject = localSdpObject.media .find((m) => m.type === 'application'); - if (!this._transportReady) - await this._setupTransport({ localDtlsRole: 'server', localSdpObject }); + if (!this._transportReady) { + await this._setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); + } logger.debug('sendDataChannel() | calling pc.setLocalDescription() [offer:%o]', offer); await this._pc.setLocalDescription(offer); this._remoteSdp.sendSctpAssociation({ offerMediaObject }); @@ -6991,6 +7291,7 @@ class Safari11 extends HandlerInterface_1.HandlerInterface { return { dataChannel, sctpStreamParameters }; } async receive({ trackId, kind, rtpParameters }) { + var _a; this._assertRecvDirection(); logger.debug('receive() [trackId:%s, kind:%s]', trackId, kind); const localId = trackId; @@ -7016,8 +7317,12 @@ class Safari11 extends HandlerInterface_1.HandlerInterface { answerMediaObject }); answer = { type: 'answer', sdp: sdpTransform.write(localSdpObject) }; - if (!this._transportReady) - await this._setupTransport({ localDtlsRole: 'client', localSdpObject }); + if (!this._transportReady) { + await this._setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); + } logger.debug('receive() | calling pc.setLocalDescription() [answer:%o]', answer); await this._pc.setLocalDescription(answer); const rtpReceiver = this._pc.getReceivers() @@ -7053,7 +7358,18 @@ class Safari11 extends HandlerInterface_1.HandlerInterface { throw new Error('associated RTCRtpReceiver not found'); return rtpReceiver.getStats(); } + async pauseReceiving( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + localId) { + // Unimplemented. + } + async resumeReceiving( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + localId) { + // Unimplemented. + } async receiveDataChannel({ sctpStreamParameters, label, protocol }) { + var _a; this._assertRecvDirection(); const { streamId, ordered, maxPacketLifeTime, maxRetransmits } = sctpStreamParameters; const options = { @@ -7076,7 +7392,10 @@ class Safari11 extends HandlerInterface_1.HandlerInterface { const answer = await this._pc.createAnswer(); if (!this._transportReady) { const localSdpObject = sdpTransform.parse(answer.sdp); - await this._setupTransport({ localDtlsRole: 'client', localSdpObject }); + await this._setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); } logger.debug('receiveDataChannel() | calling pc.setRemoteDescription() [answer:%o]', answer); await this._pc.setLocalDescription(answer); @@ -7127,7 +7446,7 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? ( var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; @@ -7229,7 +7548,18 @@ class Safari12 extends HandlerInterface_1.HandlerInterface { audio: ortc.getSendingRemoteRtpParameters('audio', extendedRtpCapabilities), video: ortc.getSendingRemoteRtpParameters('video', extendedRtpCapabilities) }; - this._pc = new RTCPeerConnection(Object.assign({ iceServers: iceServers || [], iceTransportPolicy: iceTransportPolicy || 'all', bundlePolicy: 'max-bundle', rtcpMuxPolicy: 'require' }, additionalSettings), proprietaryConstraints); + if (dtlsParameters.role && dtlsParameters.role !== 'auto') { + this._forcedLocalDtlsRole = dtlsParameters.role === 'server' + ? 'client' + : 'server'; + } + this._pc = new RTCPeerConnection({ + iceServers: iceServers || [], + iceTransportPolicy: iceTransportPolicy || 'all', + bundlePolicy: 'max-bundle', + rtcpMuxPolicy: 'require', + ...additionalSettings + }, proprietaryConstraints); // Handle RTCPeerConnection connection status. this._pc.addEventListener('iceconnectionstatechange', () => { switch (this._pc.iceConnectionState) { @@ -7285,6 +7615,7 @@ class Safari12 extends HandlerInterface_1.HandlerInterface { return this._pc.getStats(); } async send({ track, encodings, codecOptions, codec }) { + var _a; this._assertSendDirection(); logger.debug('send() [kind:%s, track.id:%s]', track.kind, track.id); const sendingRtpParameters = utils.clone(this._sendingRtpParametersByKind[track.kind], {}); @@ -7300,8 +7631,12 @@ class Safari12 extends HandlerInterface_1.HandlerInterface { let offer = await this._pc.createOffer(); let localSdpObject = sdpTransform.parse(offer.sdp); let offerMediaObject; - if (!this._transportReady) - await this._setupTransport({ localDtlsRole: 'server', localSdpObject }); + if (!this._transportReady) { + await this._setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); + } if (encodings && encodings.length > 1) { logger.debug('send() | enabling legacy simulcast'); localSdpObject = sdpTransform.parse(offer.sdp); @@ -7375,6 +7710,7 @@ class Safari12 extends HandlerInterface_1.HandlerInterface { const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; logger.debug('stopSending() | calling pc.setRemoteDescription() [answer:%o]', answer); await this._pc.setRemoteDescription(answer); + this._mapMidTransceiver.delete(localId); } async replaceTrack(localId, track) { this._assertSendDirection(); @@ -7412,7 +7748,7 @@ class Safari12 extends HandlerInterface_1.HandlerInterface { throw new Error('associated RTCRtpTransceiver not found'); const parameters = transceiver.sender.getParameters(); parameters.encodings.forEach((encoding, idx) => { - parameters.encodings[idx] = Object.assign(Object.assign({}, encoding), params); + parameters.encodings[idx] = { ...encoding, ...params }; }); await transceiver.sender.setParameters(parameters); } @@ -7424,6 +7760,7 @@ class Safari12 extends HandlerInterface_1.HandlerInterface { return transceiver.sender.getStats(); } async sendDataChannel({ ordered, maxPacketLifeTime, maxRetransmits, label, protocol }) { + var _a; this._assertSendDirection(); const options = { negotiated: true, @@ -7445,8 +7782,12 @@ class Safari12 extends HandlerInterface_1.HandlerInterface { const localSdpObject = sdpTransform.parse(offer.sdp); const offerMediaObject = localSdpObject.media .find((m) => m.type === 'application'); - if (!this._transportReady) - await this._setupTransport({ localDtlsRole: 'server', localSdpObject }); + if (!this._transportReady) { + await this._setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); + } logger.debug('sendDataChannel() | calling pc.setLocalDescription() [offer:%o]', offer); await this._pc.setLocalDescription(offer); this._remoteSdp.sendSctpAssociation({ offerMediaObject }); @@ -7464,6 +7805,7 @@ class Safari12 extends HandlerInterface_1.HandlerInterface { return { dataChannel, sctpStreamParameters }; } async receive({ trackId, kind, rtpParameters }) { + var _a; this._assertRecvDirection(); logger.debug('receive() [trackId:%s, kind:%s]', trackId, kind); const localId = rtpParameters.mid || String(this._mapMidTransceiver.size); @@ -7488,8 +7830,12 @@ class Safari12 extends HandlerInterface_1.HandlerInterface { answerMediaObject }); answer = { type: 'answer', sdp: sdpTransform.write(localSdpObject) }; - if (!this._transportReady) - await this._setupTransport({ localDtlsRole: 'client', localSdpObject }); + if (!this._transportReady) { + await this._setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); + } logger.debug('receive() | calling pc.setLocalDescription() [answer:%o]', answer); await this._pc.setLocalDescription(answer); const transceiver = this._pc.getTransceivers() @@ -7517,15 +7863,45 @@ class Safari12 extends HandlerInterface_1.HandlerInterface { const answer = await this._pc.createAnswer(); logger.debug('stopReceiving() | calling pc.setLocalDescription() [answer:%o]', answer); await this._pc.setLocalDescription(answer); + this._mapMidTransceiver.delete(localId); } - async getReceiverStats(localId) { + async pauseReceiving(localId) { this._assertRecvDirection(); + logger.debug('pauseReceiving() [localId:%s]', localId); const transceiver = this._mapMidTransceiver.get(localId); if (!transceiver) throw new Error('associated RTCRtpTransceiver not found'); - return transceiver.receiver.getStats(); - } - async receiveDataChannel({ sctpStreamParameters, label, protocol }) { + transceiver.direction = 'inactive'; + const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; + logger.debug('pauseReceiving() | calling pc.setRemoteDescription() [offer:%o]', offer); + await this._pc.setRemoteDescription(offer); + const answer = await this._pc.createAnswer(); + logger.debug('pauseReceiving() | calling pc.setLocalDescription() [answer:%o]', answer); + await this._pc.setLocalDescription(answer); + } + async resumeReceiving(localId) { + this._assertRecvDirection(); + logger.debug('resumeReceiving() [localId:%s]', localId); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) + throw new Error('associated RTCRtpTransceiver not found'); + transceiver.direction = 'recvonly'; + const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; + logger.debug('resumeReceiving() | calling pc.setRemoteDescription() [offer:%o]', offer); + await this._pc.setRemoteDescription(offer); + const answer = await this._pc.createAnswer(); + logger.debug('resumeReceiving() | calling pc.setLocalDescription() [answer:%o]', answer); + await this._pc.setLocalDescription(answer); + } + async getReceiverStats(localId) { + this._assertRecvDirection(); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) + throw new Error('associated RTCRtpTransceiver not found'); + return transceiver.receiver.getStats(); + } + async receiveDataChannel({ sctpStreamParameters, label, protocol }) { + var _a; this._assertRecvDirection(); const { streamId, ordered, maxPacketLifeTime, maxRetransmits } = sctpStreamParameters; const options = { @@ -7548,7 +7924,10 @@ class Safari12 extends HandlerInterface_1.HandlerInterface { const answer = await this._pc.createAnswer(); if (!this._transportReady) { const localSdpObject = sdpTransform.parse(answer.sdp); - await this._setupTransport({ localDtlsRole: 'client', localSdpObject }); + await this._setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); } logger.debug('receiveDataChannel() | calling pc.setRemoteDescription() [answer:%o]', answer); await this._pc.setLocalDescription(answer); @@ -7599,7 +7978,7 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? ( var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; @@ -7679,7 +8058,7 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? ( var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; @@ -8209,7 +8588,7 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? ( var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; @@ -8486,7 +8865,7 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? ( var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; @@ -8949,7 +9328,7 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? ( var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; @@ -8957,7 +9336,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.debug = exports.detectDevice = exports.Device = exports.version = exports.types = void 0; +exports.debug = exports.parseScalabilityMode = exports.detectDevice = exports.Device = exports.version = exports.types = void 0; const debug_1 = __importDefault(require("debug")); exports.debug = debug_1.default; const Device_1 = require("./Device"); @@ -8968,7 +9347,7 @@ exports.types = types; /** * Expose mediasoup-client version. */ -exports.version = '3.6.37'; +exports.version = '3.6.47'; /** * Expose parseScalabilityMode() function. */ @@ -8992,7 +9371,7 @@ var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? ( var __importStar = (this && this.__importStar) || function (mod) { if (mod && mod.__esModule) return mod; var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); __setModuleDefault(result, mod); return result; }; @@ -9758,12 +10137,12 @@ function matchCodecs(aCodec, bCodec, { strict = false, modify = false } = {}) { switch (aMimeType) { case 'video/h264': { - const aPacketizationMode = aCodec.parameters['packetization-mode'] || 0; - const bPacketizationMode = bCodec.parameters['packetization-mode'] || 0; - if (aPacketizationMode !== bPacketizationMode) - return false; // If strict matching check profile-level-id. if (strict) { + const aPacketizationMode = aCodec.parameters['packetization-mode'] || 0; + const bPacketizationMode = bCodec.parameters['packetization-mode'] || 0; + if (aPacketizationMode !== bPacketizationMode) + return false; if (!h264.isSameProfile(aCodec.parameters, bCodec.parameters)) return false; let selectedProfileLevelId; @@ -9852,7 +10231,7 @@ var __createBinding = (this && this.__createBinding) || (Object.create ? (functi o[k2] = m[k]; })); var __exportStar = (this && this.__exportStar) || function(m, exports) { - for (var p in m) if (p !== "default" && !exports.hasOwnProperty(p)) __createBinding(exports, m, p); + for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); }; Object.defineProperty(exports, "__esModule", { value: true }); __exportStar(require("./Device"), exports); @@ -9888,10 +10267,555 @@ function generateRandomNumber() { exports.generateRandomNumber = generateRandomNumber; },{}],40:[function(require,module,exports){ -arguments[4][5][0].apply(exports,arguments) -},{"./common":41,"_process":48,"dup":5}],41:[function(require,module,exports){ -arguments[4][6][0].apply(exports,arguments) -},{"dup":6,"ms":42}],42:[function(require,module,exports){ +(function (process){(function (){ +/* eslint-env browser */ + +/** + * This is the web browser implementation of `debug()`. + */ + +exports.formatArgs = formatArgs; +exports.save = save; +exports.load = load; +exports.useColors = useColors; +exports.storage = localstorage(); +exports.destroy = (() => { + let warned = false; + + return () => { + if (!warned) { + warned = true; + console.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.'); + } + }; +})(); + +/** + * Colors. + */ + +exports.colors = [ + '#0000CC', + '#0000FF', + '#0033CC', + '#0033FF', + '#0066CC', + '#0066FF', + '#0099CC', + '#0099FF', + '#00CC00', + '#00CC33', + '#00CC66', + '#00CC99', + '#00CCCC', + '#00CCFF', + '#3300CC', + '#3300FF', + '#3333CC', + '#3333FF', + '#3366CC', + '#3366FF', + '#3399CC', + '#3399FF', + '#33CC00', + '#33CC33', + '#33CC66', + '#33CC99', + '#33CCCC', + '#33CCFF', + '#6600CC', + '#6600FF', + '#6633CC', + '#6633FF', + '#66CC00', + '#66CC33', + '#9900CC', + '#9900FF', + '#9933CC', + '#9933FF', + '#99CC00', + '#99CC33', + '#CC0000', + '#CC0033', + '#CC0066', + '#CC0099', + '#CC00CC', + '#CC00FF', + '#CC3300', + '#CC3333', + '#CC3366', + '#CC3399', + '#CC33CC', + '#CC33FF', + '#CC6600', + '#CC6633', + '#CC9900', + '#CC9933', + '#CCCC00', + '#CCCC33', + '#FF0000', + '#FF0033', + '#FF0066', + '#FF0099', + '#FF00CC', + '#FF00FF', + '#FF3300', + '#FF3333', + '#FF3366', + '#FF3399', + '#FF33CC', + '#FF33FF', + '#FF6600', + '#FF6633', + '#FF9900', + '#FF9933', + '#FFCC00', + '#FFCC33' +]; + +/** + * Currently only WebKit-based Web Inspectors, Firefox >= v31, + * and the Firebug extension (any Firefox version) are known + * to support "%c" CSS customizations. + * + * TODO: add a `localStorage` variable to explicitly enable/disable colors + */ + +// eslint-disable-next-line complexity +function useColors() { + // NB: In an Electron preload script, document will be defined but not fully + // initialized. Since we know we're in Chrome, we'll just detect this case + // explicitly + if (typeof window !== 'undefined' && window.process && (window.process.type === 'renderer' || window.process.__nwjs)) { + return true; + } + + // Internet Explorer and Edge do not support colors. + if (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)) { + return false; + } + + // Is webkit? http://stackoverflow.com/a/16459606/376773 + // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632 + return (typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) || + // Is firebug? http://stackoverflow.com/a/398120/376773 + (typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) || + // Is firefox >= v31? + // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages + (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31) || + // Double check webkit in userAgent just in case we are in a worker + (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)); +} + +/** + * Colorize log arguments if enabled. + * + * @api public + */ + +function formatArgs(args) { + args[0] = (this.useColors ? '%c' : '') + + this.namespace + + (this.useColors ? ' %c' : ' ') + + args[0] + + (this.useColors ? '%c ' : ' ') + + '+' + module.exports.humanize(this.diff); + + if (!this.useColors) { + return; + } + + const c = 'color: ' + this.color; + args.splice(1, 0, c, 'color: inherit'); + + // The final "%c" is somewhat tricky, because there could be other + // arguments passed either before or after the %c, so we need to + // figure out the correct index to insert the CSS into + let index = 0; + let lastC = 0; + args[0].replace(/%[a-zA-Z%]/g, match => { + if (match === '%%') { + return; + } + index++; + if (match === '%c') { + // We only are interested in the *last* %c + // (the user may have provided their own) + lastC = index; + } + }); + + args.splice(lastC, 0, c); +} + +/** + * Invokes `console.debug()` when available. + * No-op when `console.debug` is not a "function". + * If `console.debug` is not available, falls back + * to `console.log`. + * + * @api public + */ +exports.log = console.debug || console.log || (() => {}); + +/** + * Save `namespaces`. + * + * @param {String} namespaces + * @api private + */ +function save(namespaces) { + try { + if (namespaces) { + exports.storage.setItem('debug', namespaces); + } else { + exports.storage.removeItem('debug'); + } + } catch (error) { + // Swallow + // XXX (@Qix-) should we be logging these? + } +} + +/** + * Load `namespaces`. + * + * @return {String} returns the previously persisted debug modes + * @api private + */ +function load() { + let r; + try { + r = exports.storage.getItem('debug'); + } catch (error) { + // Swallow + // XXX (@Qix-) should we be logging these? + } + + // If debug isn't set in LS, and we're in Electron, try to load $DEBUG + if (!r && typeof process !== 'undefined' && 'env' in process) { + r = process.env.DEBUG; + } + + return r; +} + +/** + * Localstorage attempts to return the localstorage. + * + * This is necessary because safari throws + * when a user disables cookies/localstorage + * and you attempt to access it. + * + * @return {LocalStorage} + * @api private + */ + +function localstorage() { + try { + // TVMLKit (Apple TV JS Runtime) does not have a window object, just localStorage in the global context + // The Browser also has localStorage in the global context. + return localStorage; + } catch (error) { + // Swallow + // XXX (@Qix-) should we be logging these? + } +} + +module.exports = require('./common')(exports); + +const {formatters} = module.exports; + +/** + * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default. + */ + +formatters.j = function (v) { + try { + return JSON.stringify(v); + } catch (error) { + return '[UnexpectedJSONParseError]: ' + error.message; + } +}; + +}).call(this)}).call(this,require('_process')) +},{"./common":41,"_process":48}],41:[function(require,module,exports){ + +/** + * This is the common logic for both the Node.js and web browser + * implementations of `debug()`. + */ + +function setup(env) { + createDebug.debug = createDebug; + createDebug.default = createDebug; + createDebug.coerce = coerce; + createDebug.disable = disable; + createDebug.enable = enable; + createDebug.enabled = enabled; + createDebug.humanize = require('ms'); + createDebug.destroy = destroy; + + Object.keys(env).forEach(key => { + createDebug[key] = env[key]; + }); + + /** + * The currently active debug mode names, and names to skip. + */ + + createDebug.names = []; + createDebug.skips = []; + + /** + * Map of special "%n" handling functions, for the debug "format" argument. + * + * Valid key names are a single, lower or upper-case letter, i.e. "n" and "N". + */ + createDebug.formatters = {}; + + /** + * Selects a color for a debug namespace + * @param {String} namespace The namespace string for the for the debug instance to be colored + * @return {Number|String} An ANSI color code for the given namespace + * @api private + */ + function selectColor(namespace) { + let hash = 0; + + for (let i = 0; i < namespace.length; i++) { + hash = ((hash << 5) - hash) + namespace.charCodeAt(i); + hash |= 0; // Convert to 32bit integer + } + + return createDebug.colors[Math.abs(hash) % createDebug.colors.length]; + } + createDebug.selectColor = selectColor; + + /** + * Create a debugger with the given `namespace`. + * + * @param {String} namespace + * @return {Function} + * @api public + */ + function createDebug(namespace) { + let prevTime; + let enableOverride = null; + let namespacesCache; + let enabledCache; + + function debug(...args) { + // Disabled? + if (!debug.enabled) { + return; + } + + const self = debug; + + // Set `diff` timestamp + const curr = Number(new Date()); + const ms = curr - (prevTime || curr); + self.diff = ms; + self.prev = prevTime; + self.curr = curr; + prevTime = curr; + + args[0] = createDebug.coerce(args[0]); + + if (typeof args[0] !== 'string') { + // Anything else let's inspect with %O + args.unshift('%O'); + } + + // Apply any `formatters` transformations + let index = 0; + args[0] = args[0].replace(/%([a-zA-Z%])/g, (match, format) => { + // If we encounter an escaped % then don't increase the array index + if (match === '%%') { + return '%'; + } + index++; + const formatter = createDebug.formatters[format]; + if (typeof formatter === 'function') { + const val = args[index]; + match = formatter.call(self, val); + + // Now we need to remove `args[index]` since it's inlined in the `format` + args.splice(index, 1); + index--; + } + return match; + }); + + // Apply env-specific formatting (colors, etc.) + createDebug.formatArgs.call(self, args); + + const logFn = self.log || createDebug.log; + logFn.apply(self, args); + } + + debug.namespace = namespace; + debug.useColors = createDebug.useColors(); + debug.color = createDebug.selectColor(namespace); + debug.extend = extend; + debug.destroy = createDebug.destroy; // XXX Temporary. Will be removed in the next major release. + + Object.defineProperty(debug, 'enabled', { + enumerable: true, + configurable: false, + get: () => { + if (enableOverride !== null) { + return enableOverride; + } + if (namespacesCache !== createDebug.namespaces) { + namespacesCache = createDebug.namespaces; + enabledCache = createDebug.enabled(namespace); + } + + return enabledCache; + }, + set: v => { + enableOverride = v; + } + }); + + // Env-specific initialization logic for debug instances + if (typeof createDebug.init === 'function') { + createDebug.init(debug); + } + + return debug; + } + + function extend(namespace, delimiter) { + const newDebug = createDebug(this.namespace + (typeof delimiter === 'undefined' ? ':' : delimiter) + namespace); + newDebug.log = this.log; + return newDebug; + } + + /** + * Enables a debug mode by namespaces. This can include modes + * separated by a colon and wildcards. + * + * @param {String} namespaces + * @api public + */ + function enable(namespaces) { + createDebug.save(namespaces); + createDebug.namespaces = namespaces; + + createDebug.names = []; + createDebug.skips = []; + + let i; + const split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/); + const len = split.length; + + for (i = 0; i < len; i++) { + if (!split[i]) { + // ignore empty strings + continue; + } + + namespaces = split[i].replace(/\*/g, '.*?'); + + if (namespaces[0] === '-') { + createDebug.skips.push(new RegExp('^' + namespaces.substr(1) + '$')); + } else { + createDebug.names.push(new RegExp('^' + namespaces + '$')); + } + } + } + + /** + * Disable debug output. + * + * @return {String} namespaces + * @api public + */ + function disable() { + const namespaces = [ + ...createDebug.names.map(toNamespace), + ...createDebug.skips.map(toNamespace).map(namespace => '-' + namespace) + ].join(','); + createDebug.enable(''); + return namespaces; + } + + /** + * Returns true if the given mode name is enabled, false otherwise. + * + * @param {String} name + * @return {Boolean} + * @api public + */ + function enabled(name) { + if (name[name.length - 1] === '*') { + return true; + } + + let i; + let len; + + for (i = 0, len = createDebug.skips.length; i < len; i++) { + if (createDebug.skips[i].test(name)) { + return false; + } + } + + for (i = 0, len = createDebug.names.length; i < len; i++) { + if (createDebug.names[i].test(name)) { + return true; + } + } + + return false; + } + + /** + * Convert regexp to namespace + * + * @param {RegExp} regxep + * @return {String} namespace + * @api private + */ + function toNamespace(regexp) { + return regexp.toString() + .substring(2, regexp.toString().length - 2) + .replace(/\.\*\?$/, '*'); + } + + /** + * Coerce `val`. + * + * @param {Mixed} val + * @return {Mixed} + * @api private + */ + function coerce(val) { + if (val instanceof Error) { + return val.stack || val.message; + } + return val; + } + + /** + * XXX DO NOT USE. This is a temporary stub function. + * XXX It WILL be removed in the next major release. + */ + function destroy() { + console.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.'); + } + + createDebug.enable(createDebug.load()); + + return createDebug; +} + +module.exports = setup; + +},{"ms":42}],42:[function(require,module,exports){ arguments[4][7][0].apply(exports,arguments) },{"dup":7}],43:[function(require,module,exports){ var grammar = module.exports = { From 781122d77db05342197f143817cace7db110a362 Mon Sep 17 00:00:00 2001 From: Miroslav Pejic Date: Wed, 2 Mar 2022 19:07:26 +0100 Subject: [PATCH 5/8] update dep. --- .gitignore | 3 +++ README.md | 18 +++++++++++------- package.json | 6 +++--- src/app.js | 19 ++----------------- 4 files changed, 19 insertions(+), 27 deletions(-) diff --git a/.gitignore b/.gitignore index 26fece0..89b03c0 100644 --- a/.gitignore +++ b/.gitignore @@ -75,6 +75,9 @@ typings/ .env .env.test +# mac +.DS_Store + # parcel-bundler cache (https://parceljs.org/) .cache diff --git a/README.md b/README.md index b7144da..a290b1a 100644 --- a/README.md +++ b/README.md @@ -2,22 +2,26 @@ Example website for multi-party video/audio/screen conferencing using mediasoup. This project is intended to better understand how mediasoup works with a simple example. -This project is featured on the [mediasoup examples page](https://mediasoup.org/documentation/examples/) with many other examples. Checkout mediasoup at [mediasoup.org](https://mediasoup.org) +This project is featured on the [mediasoup examples page](https://mediasoup.org/documentation/examples/) with many other examples. Checkout mediasoup at [mediasoup.org](https://mediasoup.org). -# Running the code +## Running the code - run `npm install` then `npm start` to run the application. Then open your browser at `https://localhost:3016` or your own defined port/url in the config file. + - (optional) edit the `src/config.js` file according to your needs and replace the `ssl/key.pem ssl/cert.pem` certificates with your own. -# Deployment +## Deployment - in `config.js` replace the `announcedIP` with your public ip address of the server and modify the port you want to serve it in. + - add firewall rules of the port of the webpage (default 3016) and the rtc connections (default udp 10000-10100) for the machine. -# Pull Requests +## Pull Requests + +- Please run `npm run lint` before to make a PR. -- Please run `npx prettier --write .` before to make a PR. +## Notes -notes : Best to run the project on a linux system as the mediasoup installation could have issues by installing on windows. If you have a windows system consider installing WSL to be able to run it. +Best to run the project on a linux system as the mediasoup installation could have issues by installing on windows. -[installing wsl on windows 10](https://docs.microsoft.com/en-us/windows/wsl/install-win10) +More details regarding mediasoup requirements can be found [here](https://mediasoup.org/documentation/v3/mediasoup/installation/#requirements). diff --git a/package.json b/package.json index 6165dfc..657c9fc 100644 --- a/package.json +++ b/package.json @@ -18,10 +18,10 @@ "author": "", "license": "ISC", "dependencies": { - "express": "^4.17.2", + "express": "^4.17.3", "httpolyglot": "^0.1.2", - "mediasoup": "^3.9.3", - "mediasoup-client": "^3.6.47", + "mediasoup": "^3.9.8", + "mediasoup-client": "^3.6.50", "socket.io": "^4.4.1" }, "devDependencies": { diff --git a/src/app.js b/src/app.js index 9efd546..0c3023e 100644 --- a/src/app.js +++ b/src/app.js @@ -20,7 +20,7 @@ const io = require('socket.io')(httpsServer) app.use(express.static(path.join(__dirname, '..', 'public'))) httpsServer.listen(config.listenPort, () => { - console.log('Listening on https://' + config.listenIp + ':' + config.listenPort) + console.log('Listening on https://' + 'localhost' + ':' + config.listenPort) }) // all mediasoup workers @@ -167,7 +167,7 @@ io.on('connection', (socket) => { console.log('Produce', { type: `${kind}`, name: `${roomList.get(socket.room_id).getPeers().get(socket.id).name}`, - id: `${producer_id}` + producer_id: `${producer_id}` }) callback({ @@ -237,21 +237,6 @@ io.on('connection', (socket) => { }) }) -// TODO remove - never used? -function room() { - return Object.values(roomList).map((r) => { - return { - router: r.router.id, - peers: Object.values(r.peers).map((p) => { - return { - name: p.name - } - }), - id: r.id - } - }) -} - /** * Get next mediasoup Worker. */ From dd5c15c2d6db4bfd0dbe18fd99f4a1844c1617a7 Mon Sep 17 00:00:00 2001 From: Miroslav Pejic Date: Fri, 12 Aug 2022 13:48:28 +0200 Subject: [PATCH 6/8] Start video-audio on join room --- public/RoomClient.js | 2 ++ 1 file changed, 2 insertions(+) diff --git a/public/RoomClient.js b/public/RoomClient.js index 4629b91..ab9a88a 100644 --- a/public/RoomClient.js +++ b/public/RoomClient.js @@ -86,6 +86,8 @@ class RoomClient { this.device = device await this.initTransports(device) this.socket.emit('getProducers') + startVideoButton.click() + startAudioButton.click() }.bind(this) ) .catch((err) => { From c7290d120615515fa88a2f7c8155dc232ad59d2d Mon Sep 17 00:00:00 2001 From: Miroslav Pejic Date: Sun, 26 Feb 2023 14:23:13 +0100 Subject: [PATCH 7/8] Update dep & fix simulcast layers --- package.json | 12 +- public/RoomClient.js | 10 +- public/modules/mediasoupclient.min.js | 18585 +++++++++++++----------- src/Peer.js | 6 +- src/app.js | 8 +- 5 files changed, 10402 insertions(+), 8219 deletions(-) diff --git a/package.json b/package.json index 657c9fc..e557325 100644 --- a/package.json +++ b/package.json @@ -15,16 +15,16 @@ "docker-stop": "docker stop dirvann-mediasoup-rooms", "compile-mediasoup-client": "npx browserify mediasoup-client-compile.js -o public/modules/mediasoupclient.min.js" }, - "author": "", + "author": "Dirk Vanbeveren & Miroslav Pejic", "license": "ISC", "dependencies": { - "express": "^4.17.3", + "express": "^4.18.2", "httpolyglot": "^0.1.2", - "mediasoup": "^3.9.8", - "mediasoup-client": "^3.6.50", - "socket.io": "^4.4.1" + "mediasoup": "^3.11.11", + "mediasoup-client": "^3.6.82", + "socket.io": "^4.6.1" }, "devDependencies": { - "prettier": "2.5.1" + "prettier": "2.8.4" } } diff --git a/public/RoomClient.js b/public/RoomClient.js index ab9a88a..9a69a9e 100644 --- a/public/RoomClient.js +++ b/public/RoomClient.js @@ -292,9 +292,6 @@ class RoomClient { ideal: 1080 }, deviceId: deviceId - /*aspectRatio: { - ideal: 1.7777777778 - }*/ } } break @@ -330,18 +327,17 @@ class RoomClient { { rid: 'r0', maxBitrate: 100000, - //scaleResolutionDownBy: 10.0, - scalabilityMode: 'S1T3' + scalabilityMode: 'L3T3' }, { rid: 'r1', maxBitrate: 300000, - scalabilityMode: 'S1T3' + scalabilityMode: 'L3T3' }, { rid: 'r2', maxBitrate: 900000, - scalabilityMode: 'S1T3' + scalabilityMode: 'L3T3' } ] params.codecOptions = { diff --git a/public/modules/mediasoupclient.min.js b/public/modules/mediasoupclient.min.js index 7bf3640..02ad4e7 100644 --- a/public/modules/mediasoupclient.min.js +++ b/public/modules/mediasoupclient.min.js @@ -3,1054 +3,674 @@ const client = require('mediasoup-client') window.mediasoupClient = client -},{"mediasoup-client":35}],2:[function(require,module,exports){ +},{"mediasoup-client":42}],2:[function(require,module,exports){ "use strict"; -var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { - function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } - return new (P || (P = Promise))(function (resolve, reject) { - function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } - function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } - function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } - step((generator = generator.apply(thisArg, _arguments || [])).next()); - }); +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); +exports.Logger = void 0; +const debug_1 = __importDefault(require("debug")); +const LIB_NAME = 'awaitqueue'; +class Logger { + constructor(prefix) { + if (prefix) { + this._debug = (0, debug_1.default)(`${LIB_NAME}:${prefix}`); + this._warn = (0, debug_1.default)(`${LIB_NAME}:WARN:${prefix}`); + this._error = (0, debug_1.default)(`${LIB_NAME}:ERROR:${prefix}`); + } + else { + this._debug = (0, debug_1.default)(LIB_NAME); + this._warn = (0, debug_1.default)(`${LIB_NAME}:WARN`); + this._error = (0, debug_1.default)(`${LIB_NAME}:ERROR`); + } + /* eslint-disable no-console */ + this._debug.log = console.info.bind(console); + this._warn.log = console.warn.bind(console); + this._error.log = console.error.bind(console); + /* eslint-enable no-console */ + } + get debug() { + return this._debug; + } + get warn() { + return this._warn; + } + get error() { + return this._error; + } +} +exports.Logger = Logger; + +},{"debug":4}],3:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.AwaitQueue = exports.AwaitQueueRemovedTaskError = exports.AwaitQueueStoppedError = void 0; +const Logger_1 = require("./Logger"); +const logger = new Logger_1.Logger(); +/** + * Custom Error derived class used to reject pending tasks once stop() method + * has been called. + */ +class AwaitQueueStoppedError extends Error { + constructor(message) { + super(message !== null && message !== void 0 ? message : 'AwaitQueue stopped'); + this.name = 'AwaitQueueStoppedError'; + // @ts-ignore + if (typeof Error.captureStackTrace === 'function') { + // @ts-ignore + Error.captureStackTrace(this, AwaitQueueStoppedError); + } + } +} +exports.AwaitQueueStoppedError = AwaitQueueStoppedError; +/** + * Custom Error derived class used to reject pending tasks once removeTask() + * method has been called. + */ +class AwaitQueueRemovedTaskError extends Error { + constructor(message) { + super(message !== null && message !== void 0 ? message : 'AwaitQueue task removed'); + this.name = 'AwaitQueueRemovedTaskError'; + // @ts-ignore + if (typeof Error.captureStackTrace === 'function') { + // @ts-ignore + Error.captureStackTrace(this, AwaitQueueRemovedTaskError); + } + } +} +exports.AwaitQueueRemovedTaskError = AwaitQueueRemovedTaskError; class AwaitQueue { - constructor({ ClosedErrorClass = Error, StoppedErrorClass = Error } = { - ClosedErrorClass: Error, - StoppedErrorClass: Error - }) { - // Closed flag. - this.closed = false; - // Queue of pending tasks. - this.pendingTasks = []; - // Error class used when rejecting a task due to AwaitQueue being closed. - this.ClosedErrorClass = Error; - // Error class used when rejecting a task due to AwaitQueue being stopped. - this.StoppedErrorClass = Error; - this.ClosedErrorClass = ClosedErrorClass; - this.StoppedErrorClass = StoppedErrorClass; + constructor() { + // Queue of pending tasks (map of PendingTasks indexed by id). + this.pendingTasks = new Map(); + // Incrementing PendingTask id. + this.nextTaskId = 0; + // Whether stop() method is stopping all pending tasks. + this.stopping = false; } - /** - * The number of ongoing enqueued tasks. - */ get size() { - return this.pendingTasks.length; + return this.pendingTasks.size; } - /** - * Closes the AwaitQueue. Pending tasks will be rejected with ClosedErrorClass - * error. - */ - close() { - if (this.closed) - return; - this.closed = true; - for (const pendingTask of this.pendingTasks) { - pendingTask.stopped = true; - pendingTask.reject(new this.ClosedErrorClass('AwaitQueue closed')); + async push(task, name) { + name = name !== null && name !== void 0 ? name : task.name; + logger.debug(`push() [name:${name}]`); + if (typeof task !== 'function') { + throw new TypeError('given task is not a function'); } - // Enpty the pending tasks array. - this.pendingTasks.length = 0; - } - /** - * Accepts a task as argument (and an optional task name) and enqueues it after - * pending tasks. Once processed, the push() method resolves (or rejects) with - * the result returned by the given task. - * - * The given task must return a Promise or directly a value. - */ - push(task, name) { - return __awaiter(this, void 0, void 0, function* () { - if (this.closed) - throw new this.ClosedErrorClass('AwaitQueue closed'); - if (typeof task !== 'function') - throw new TypeError('given task is not a function'); - if (!task.name && name) { - try { - Object.defineProperty(task, 'name', { value: name }); + if (name) { + try { + Object.defineProperty(task, 'name', { value: name }); + } + catch (error) { } + } + return new Promise((resolve, reject) => { + const pendingTask = { + id: this.nextTaskId++, + task: task, + name: name, + enqueuedAt: Date.now(), + executedAt: undefined, + completed: false, + resolve: (result) => { + // pendingTask.resolve() can only be called in execute() method. Since + // resolve() was called it means that the task successfully completed. + // However the task may have been stopped before it completed (via + // stop() or remove()) so its completed flag was already set. If this + // is the case, abort here since next task (if any) is already being + // executed. + if (pendingTask.completed) { + return; + } + pendingTask.completed = true; + // Remove the task from the queue. + this.pendingTasks.delete(pendingTask.id); + logger.debug(`resolving task [name:${pendingTask.name}]`); + // Resolve the task with the obtained result. + resolve(result); + // Execute the next pending task (if any). + const [nextPendingTask] = this.pendingTasks.values(); + // NOTE: During the resolve() callback the user app may have interacted + // with the queue. For instance, the app may have pushed a task while + // the queue was empty so such a task is already being executed. If so, + // don't execute it twice. + if (nextPendingTask && !nextPendingTask.executedAt) { + void this.execute(nextPendingTask); + } + }, + reject: (error) => { + // pendingTask.reject() can be called within execute() method if the + // task completed with error. However it may have also been called in + // stop() or remove() methods (before or while being executed) so its + // completed flag was already set. If so, abort here since next task + // (if any) is already being executed. + if (pendingTask.completed) { + return; + } + pendingTask.completed = true; + // Remove the task from the queue. + this.pendingTasks.delete(pendingTask.id); + logger.debug(`rejecting task [name:${pendingTask.name}]: %s`, String(error)); + // Reject the task with the obtained error. + reject(error); + // Execute the next pending task (if any) unless stop() is running. + if (!this.stopping) { + const [nextPendingTask] = this.pendingTasks.values(); + // NOTE: During the reject() callback the user app may have interacted + // with the queue. For instance, the app may have pushed a task while + // the queue was empty so such a task is already being executed. If so, + // don't execute it twice. + if (nextPendingTask && !nextPendingTask.executedAt) { + void this.execute(nextPendingTask); + } + } } - catch (error) { } + }; + // Append task to the queue. + this.pendingTasks.set(pendingTask.id, pendingTask); + // And execute it if this is the only task in the queue. + if (this.pendingTasks.size === 1) { + void this.execute(pendingTask); } - return new Promise((resolve, reject) => { - const pendingTask = { - task, - name, - resolve, - reject, - stopped: false, - enqueuedAt: new Date(), - executedAt: undefined - }; - // Append task to the queue. - this.pendingTasks.push(pendingTask); - // And run it if this is the only task in the queue. - if (this.pendingTasks.length === 1) - this.next(); - }); }); } - /** - * Make ongoing pending tasks reject with the given StoppedErrorClass error. - * The AwaitQueue instance is still usable for future tasks added via push() - * method. - */ stop() { - if (this.closed) + logger.debug('stop()'); + this.stopping = true; + for (const pendingTask of this.pendingTasks.values()) { + logger.debug(`stop() | stopping task [name:${pendingTask.name}]`); + pendingTask.reject(new AwaitQueueStoppedError()); + } + this.stopping = false; + } + remove(taskIdx) { + logger.debug(`remove() [taskIdx:${taskIdx}]`); + const pendingTask = Array.from(this.pendingTasks.values())[taskIdx]; + if (!pendingTask) { + logger.debug(`stop() | no task with given idx [taskIdx:${taskIdx}]`); return; - for (const pendingTask of this.pendingTasks) { - pendingTask.stopped = true; - pendingTask.reject(new this.StoppedErrorClass('AwaitQueue stopped')); } - // Enpty the pending tasks array. - this.pendingTasks.length = 0; + pendingTask.reject(new AwaitQueueRemovedTaskError()); } dump() { - const now = new Date(); - return this.pendingTasks.map((pendingTask) => { - return { - task: pendingTask.task, - name: pendingTask.name, - enqueuedTime: pendingTask.executedAt - ? pendingTask.executedAt.getTime() - pendingTask.enqueuedAt.getTime() - : now.getTime() - pendingTask.enqueuedAt.getTime(), - executingTime: pendingTask.executedAt - ? now.getTime() - pendingTask.executedAt.getTime() - : 0 - }; - }); - } - next() { - return __awaiter(this, void 0, void 0, function* () { - // Take the first pending task. - const pendingTask = this.pendingTasks[0]; - if (!pendingTask) - return; - // Execute it. - yield this.executeTask(pendingTask); - // Remove the first pending task (the completed one) from the queue. - this.pendingTasks.shift(); - // And continue. - this.next(); - }); - } - executeTask(pendingTask) { - return __awaiter(this, void 0, void 0, function* () { - // If the task is stopped, ignore it. - if (pendingTask.stopped) - return; - pendingTask.executedAt = new Date(); - try { - const result = yield pendingTask.task(); - // If the task is stopped, ignore it. - if (pendingTask.stopped) - return; - // Resolve the task with the returned result (if any). - pendingTask.resolve(result); - } - catch (error) { - // If the task is stopped, ignore it. - if (pendingTask.stopped) - return; - // Reject the task with its own error. - pendingTask.reject(error); - } - }); + const now = Date.now(); + let idx = 0; + return Array.from(this.pendingTasks.values()).map((pendingTask) => ({ + idx: idx++, + task: pendingTask.task, + name: pendingTask.name, + enqueuedTime: pendingTask.executedAt + ? pendingTask.executedAt - pendingTask.enqueuedAt + : now - pendingTask.enqueuedAt, + executionTime: pendingTask.executedAt + ? now - pendingTask.executedAt + : 0 + })); + } + async execute(pendingTask) { + logger.debug(`execute() [name:${pendingTask.name}]`); + if (pendingTask.executedAt) { + throw new Error('task already being executed'); + } + pendingTask.executedAt = Date.now(); + try { + const result = await pendingTask.task(); + // Resolve the task with its resolved result (if any). + pendingTask.resolve(result); + } + catch (error) { + // Reject the task with its rejected error. + pendingTask.reject(error); + } } } exports.AwaitQueue = AwaitQueue; -},{}],3:[function(require,module,exports){ -!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.bowser=t():e.bowser=t()}(this,(function(){return function(e){var t={};function r(n){if(t[n])return t[n].exports;var i=t[n]={i:n,l:!1,exports:{}};return e[n].call(i.exports,i,i.exports,r),i.l=!0,i.exports}return r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)r.d(n,i,function(t){return e[t]}.bind(null,i));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=90)}({17:function(e,t,r){"use strict";t.__esModule=!0,t.default=void 0;var n=r(18),i=function(){function e(){}return e.getFirstMatch=function(e,t){var r=t.match(e);return r&&r.length>0&&r[1]||""},e.getSecondMatch=function(e,t){var r=t.match(e);return r&&r.length>1&&r[2]||""},e.matchAndReturnConst=function(e,t,r){if(e.test(t))return r},e.getWindowsVersionName=function(e){switch(e){case"NT":return"NT";case"XP":return"XP";case"NT 5.0":return"2000";case"NT 5.1":return"XP";case"NT 5.2":return"2003";case"NT 6.0":return"Vista";case"NT 6.1":return"7";case"NT 6.2":return"8";case"NT 6.3":return"8.1";case"NT 10.0":return"10";default:return}},e.getMacOSVersionName=function(e){var t=e.split(".").splice(0,2).map((function(e){return parseInt(e,10)||0}));if(t.push(0),10===t[0])switch(t[1]){case 5:return"Leopard";case 6:return"Snow Leopard";case 7:return"Lion";case 8:return"Mountain Lion";case 9:return"Mavericks";case 10:return"Yosemite";case 11:return"El Capitan";case 12:return"Sierra";case 13:return"High Sierra";case 14:return"Mojave";case 15:return"Catalina";default:return}},e.getAndroidVersionName=function(e){var t=e.split(".").splice(0,2).map((function(e){return parseInt(e,10)||0}));if(t.push(0),!(1===t[0]&&t[1]<5))return 1===t[0]&&t[1]<6?"Cupcake":1===t[0]&&t[1]>=6?"Donut":2===t[0]&&t[1]<2?"Eclair":2===t[0]&&2===t[1]?"Froyo":2===t[0]&&t[1]>2?"Gingerbread":3===t[0]?"Honeycomb":4===t[0]&&t[1]<1?"Ice Cream Sandwich":4===t[0]&&t[1]<4?"Jelly Bean":4===t[0]&&t[1]>=4?"KitKat":5===t[0]?"Lollipop":6===t[0]?"Marshmallow":7===t[0]?"Nougat":8===t[0]?"Oreo":9===t[0]?"Pie":void 0},e.getVersionPrecision=function(e){return e.split(".").length},e.compareVersions=function(t,r,n){void 0===n&&(n=!1);var i=e.getVersionPrecision(t),s=e.getVersionPrecision(r),a=Math.max(i,s),o=0,u=e.map([t,r],(function(t){var r=a-e.getVersionPrecision(t),n=t+new Array(r+1).join(".0");return e.map(n.split("."),(function(e){return new Array(20-e.length).join("0")+e})).reverse()}));for(n&&(o=a-Math.min(i,s)),a-=1;a>=o;){if(u[0][a]>u[1][a])return 1;if(u[0][a]===u[1][a]){if(a===o)return 0;a-=1}else if(u[0][a]1?i-1:0),a=1;a0){var a=Object.keys(r),u=o.default.find(a,(function(e){return t.isOS(e)}));if(u){var d=this.satisfies(r[u]);if(void 0!==d)return d}var c=o.default.find(a,(function(e){return t.isPlatform(e)}));if(c){var f=this.satisfies(r[c]);if(void 0!==f)return f}}if(s>0){var l=Object.keys(i),h=o.default.find(l,(function(e){return t.isBrowser(e,!0)}));if(void 0!==h)return this.compareVersion(i[h])}},t.isBrowser=function(e,t){void 0===t&&(t=!1);var r=this.getBrowserName().toLowerCase(),n=e.toLowerCase(),i=o.default.getBrowserTypeByAlias(n);return t&&i&&(n=i.toLowerCase()),n===r},t.compareVersion=function(e){var t=[0],r=e,n=!1,i=this.getBrowserVersion();if("string"==typeof i)return">"===e[0]||"<"===e[0]?(r=e.substr(1),"="===e[1]?(n=!0,r=e.substr(2)):t=[],">"===e[0]?t.push(1):t.push(-1)):"="===e[0]?r=e.substr(1):"~"===e[0]&&(n=!0,r=e.substr(1)),t.indexOf(o.default.compareVersions(i,r,n))>-1},t.isOS=function(e){return this.getOSName(!0)===String(e).toLowerCase()},t.isPlatform=function(e){return this.getPlatformType(!0)===String(e).toLowerCase()},t.isEngine=function(e){return this.getEngineName(!0)===String(e).toLowerCase()},t.is=function(e,t){return void 0===t&&(t=!1),this.isBrowser(e,t)||this.isOS(e)||this.isPlatform(e)},t.some=function(e){var t=this;return void 0===e&&(e=[]),e.some((function(e){return t.is(e)}))},e}();t.default=d,e.exports=t.default},92:function(e,t,r){"use strict";t.__esModule=!0,t.default=void 0;var n,i=(n=r(17))&&n.__esModule?n:{default:n};var s=/version\/(\d+(\.?_?\d+)+)/i,a=[{test:[/googlebot/i],describe:function(e){var t={name:"Googlebot"},r=i.default.getFirstMatch(/googlebot\/(\d+(\.\d+))/i,e)||i.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/opera/i],describe:function(e){var t={name:"Opera"},r=i.default.getFirstMatch(s,e)||i.default.getFirstMatch(/(?:opera)[\s/](\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/opr\/|opios/i],describe:function(e){var t={name:"Opera"},r=i.default.getFirstMatch(/(?:opr|opios)[\s/](\S+)/i,e)||i.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/SamsungBrowser/i],describe:function(e){var t={name:"Samsung Internet for Android"},r=i.default.getFirstMatch(s,e)||i.default.getFirstMatch(/(?:SamsungBrowser)[\s/](\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/Whale/i],describe:function(e){var t={name:"NAVER Whale Browser"},r=i.default.getFirstMatch(s,e)||i.default.getFirstMatch(/(?:whale)[\s/](\d+(?:\.\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/MZBrowser/i],describe:function(e){var t={name:"MZ Browser"},r=i.default.getFirstMatch(/(?:MZBrowser)[\s/](\d+(?:\.\d+)+)/i,e)||i.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/focus/i],describe:function(e){var t={name:"Focus"},r=i.default.getFirstMatch(/(?:focus)[\s/](\d+(?:\.\d+)+)/i,e)||i.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/swing/i],describe:function(e){var t={name:"Swing"},r=i.default.getFirstMatch(/(?:swing)[\s/](\d+(?:\.\d+)+)/i,e)||i.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/coast/i],describe:function(e){var t={name:"Opera Coast"},r=i.default.getFirstMatch(s,e)||i.default.getFirstMatch(/(?:coast)[\s/](\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/opt\/\d+(?:.?_?\d+)+/i],describe:function(e){var t={name:"Opera Touch"},r=i.default.getFirstMatch(/(?:opt)[\s/](\d+(\.?_?\d+)+)/i,e)||i.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/yabrowser/i],describe:function(e){var t={name:"Yandex Browser"},r=i.default.getFirstMatch(/(?:yabrowser)[\s/](\d+(\.?_?\d+)+)/i,e)||i.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/ucbrowser/i],describe:function(e){var t={name:"UC Browser"},r=i.default.getFirstMatch(s,e)||i.default.getFirstMatch(/(?:ucbrowser)[\s/](\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/Maxthon|mxios/i],describe:function(e){var t={name:"Maxthon"},r=i.default.getFirstMatch(s,e)||i.default.getFirstMatch(/(?:Maxthon|mxios)[\s/](\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/epiphany/i],describe:function(e){var t={name:"Epiphany"},r=i.default.getFirstMatch(s,e)||i.default.getFirstMatch(/(?:epiphany)[\s/](\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/puffin/i],describe:function(e){var t={name:"Puffin"},r=i.default.getFirstMatch(s,e)||i.default.getFirstMatch(/(?:puffin)[\s/](\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/sleipnir/i],describe:function(e){var t={name:"Sleipnir"},r=i.default.getFirstMatch(s,e)||i.default.getFirstMatch(/(?:sleipnir)[\s/](\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/k-meleon/i],describe:function(e){var t={name:"K-Meleon"},r=i.default.getFirstMatch(s,e)||i.default.getFirstMatch(/(?:k-meleon)[\s/](\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/micromessenger/i],describe:function(e){var t={name:"WeChat"},r=i.default.getFirstMatch(/(?:micromessenger)[\s/](\d+(\.?_?\d+)+)/i,e)||i.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/qqbrowser/i],describe:function(e){var t={name:/qqbrowserlite/i.test(e)?"QQ Browser Lite":"QQ Browser"},r=i.default.getFirstMatch(/(?:qqbrowserlite|qqbrowser)[/](\d+(\.?_?\d+)+)/i,e)||i.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/msie|trident/i],describe:function(e){var t={name:"Internet Explorer"},r=i.default.getFirstMatch(/(?:msie |rv:)(\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/\sedg\//i],describe:function(e){var t={name:"Microsoft Edge"},r=i.default.getFirstMatch(/\sedg\/(\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/edg([ea]|ios)/i],describe:function(e){var t={name:"Microsoft Edge"},r=i.default.getSecondMatch(/edg([ea]|ios)\/(\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/vivaldi/i],describe:function(e){var t={name:"Vivaldi"},r=i.default.getFirstMatch(/vivaldi\/(\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/seamonkey/i],describe:function(e){var t={name:"SeaMonkey"},r=i.default.getFirstMatch(/seamonkey\/(\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/sailfish/i],describe:function(e){var t={name:"Sailfish"},r=i.default.getFirstMatch(/sailfish\s?browser\/(\d+(\.\d+)?)/i,e);return r&&(t.version=r),t}},{test:[/silk/i],describe:function(e){var t={name:"Amazon Silk"},r=i.default.getFirstMatch(/silk\/(\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/phantom/i],describe:function(e){var t={name:"PhantomJS"},r=i.default.getFirstMatch(/phantomjs\/(\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/slimerjs/i],describe:function(e){var t={name:"SlimerJS"},r=i.default.getFirstMatch(/slimerjs\/(\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/blackberry|\bbb\d+/i,/rim\stablet/i],describe:function(e){var t={name:"BlackBerry"},r=i.default.getFirstMatch(s,e)||i.default.getFirstMatch(/blackberry[\d]+\/(\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/(web|hpw)[o0]s/i],describe:function(e){var t={name:"WebOS Browser"},r=i.default.getFirstMatch(s,e)||i.default.getFirstMatch(/w(?:eb)?[o0]sbrowser\/(\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/bada/i],describe:function(e){var t={name:"Bada"},r=i.default.getFirstMatch(/dolfin\/(\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/tizen/i],describe:function(e){var t={name:"Tizen"},r=i.default.getFirstMatch(/(?:tizen\s?)?browser\/(\d+(\.?_?\d+)+)/i,e)||i.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/qupzilla/i],describe:function(e){var t={name:"QupZilla"},r=i.default.getFirstMatch(/(?:qupzilla)[\s/](\d+(\.?_?\d+)+)/i,e)||i.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/firefox|iceweasel|fxios/i],describe:function(e){var t={name:"Firefox"},r=i.default.getFirstMatch(/(?:firefox|iceweasel|fxios)[\s/](\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/electron/i],describe:function(e){var t={name:"Electron"},r=i.default.getFirstMatch(/(?:electron)\/(\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/MiuiBrowser/i],describe:function(e){var t={name:"Miui"},r=i.default.getFirstMatch(/(?:MiuiBrowser)[\s/](\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/chromium/i],describe:function(e){var t={name:"Chromium"},r=i.default.getFirstMatch(/(?:chromium)[\s/](\d+(\.?_?\d+)+)/i,e)||i.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/chrome|crios|crmo/i],describe:function(e){var t={name:"Chrome"},r=i.default.getFirstMatch(/(?:chrome|crios|crmo)\/(\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/GSA/i],describe:function(e){var t={name:"Google Search"},r=i.default.getFirstMatch(/(?:GSA)\/(\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:function(e){var t=!e.test(/like android/i),r=e.test(/android/i);return t&&r},describe:function(e){var t={name:"Android Browser"},r=i.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/playstation 4/i],describe:function(e){var t={name:"PlayStation 4"},r=i.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/safari|applewebkit/i],describe:function(e){var t={name:"Safari"},r=i.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/.*/i],describe:function(e){var t=-1!==e.search("\\(")?/^(.*)\/(.*)[ \t]\((.*)/:/^(.*)\/(.*) /;return{name:i.default.getFirstMatch(t,e),version:i.default.getSecondMatch(t,e)}}}];t.default=a,e.exports=t.default},93:function(e,t,r){"use strict";t.__esModule=!0,t.default=void 0;var n,i=(n=r(17))&&n.__esModule?n:{default:n},s=r(18);var a=[{test:[/Roku\/DVP/],describe:function(e){var t=i.default.getFirstMatch(/Roku\/DVP-(\d+\.\d+)/i,e);return{name:s.OS_MAP.Roku,version:t}}},{test:[/windows phone/i],describe:function(e){var t=i.default.getFirstMatch(/windows phone (?:os)?\s?(\d+(\.\d+)*)/i,e);return{name:s.OS_MAP.WindowsPhone,version:t}}},{test:[/windows /i],describe:function(e){var t=i.default.getFirstMatch(/Windows ((NT|XP)( \d\d?.\d)?)/i,e),r=i.default.getWindowsVersionName(t);return{name:s.OS_MAP.Windows,version:t,versionName:r}}},{test:[/Macintosh(.*?) FxiOS(.*?)\//],describe:function(e){var t={name:s.OS_MAP.iOS},r=i.default.getSecondMatch(/(Version\/)(\d[\d.]+)/,e);return r&&(t.version=r),t}},{test:[/macintosh/i],describe:function(e){var t=i.default.getFirstMatch(/mac os x (\d+(\.?_?\d+)+)/i,e).replace(/[_\s]/g,"."),r=i.default.getMacOSVersionName(t),n={name:s.OS_MAP.MacOS,version:t};return r&&(n.versionName=r),n}},{test:[/(ipod|iphone|ipad)/i],describe:function(e){var t=i.default.getFirstMatch(/os (\d+([_\s]\d+)*) like mac os x/i,e).replace(/[_\s]/g,".");return{name:s.OS_MAP.iOS,version:t}}},{test:function(e){var t=!e.test(/like android/i),r=e.test(/android/i);return t&&r},describe:function(e){var t=i.default.getFirstMatch(/android[\s/-](\d+(\.\d+)*)/i,e),r=i.default.getAndroidVersionName(t),n={name:s.OS_MAP.Android,version:t};return r&&(n.versionName=r),n}},{test:[/(web|hpw)[o0]s/i],describe:function(e){var t=i.default.getFirstMatch(/(?:web|hpw)[o0]s\/(\d+(\.\d+)*)/i,e),r={name:s.OS_MAP.WebOS};return t&&t.length&&(r.version=t),r}},{test:[/blackberry|\bbb\d+/i,/rim\stablet/i],describe:function(e){var t=i.default.getFirstMatch(/rim\stablet\sos\s(\d+(\.\d+)*)/i,e)||i.default.getFirstMatch(/blackberry\d+\/(\d+([_\s]\d+)*)/i,e)||i.default.getFirstMatch(/\bbb(\d+)/i,e);return{name:s.OS_MAP.BlackBerry,version:t}}},{test:[/bada/i],describe:function(e){var t=i.default.getFirstMatch(/bada\/(\d+(\.\d+)*)/i,e);return{name:s.OS_MAP.Bada,version:t}}},{test:[/tizen/i],describe:function(e){var t=i.default.getFirstMatch(/tizen[/\s](\d+(\.\d+)*)/i,e);return{name:s.OS_MAP.Tizen,version:t}}},{test:[/linux/i],describe:function(){return{name:s.OS_MAP.Linux}}},{test:[/CrOS/],describe:function(){return{name:s.OS_MAP.ChromeOS}}},{test:[/PlayStation 4/],describe:function(e){var t=i.default.getFirstMatch(/PlayStation 4[/\s](\d+(\.\d+)*)/i,e);return{name:s.OS_MAP.PlayStation4,version:t}}}];t.default=a,e.exports=t.default},94:function(e,t,r){"use strict";t.__esModule=!0,t.default=void 0;var n,i=(n=r(17))&&n.__esModule?n:{default:n},s=r(18);var a=[{test:[/googlebot/i],describe:function(){return{type:"bot",vendor:"Google"}}},{test:[/huawei/i],describe:function(e){var t=i.default.getFirstMatch(/(can-l01)/i,e)&&"Nova",r={type:s.PLATFORMS_MAP.mobile,vendor:"Huawei"};return t&&(r.model=t),r}},{test:[/nexus\s*(?:7|8|9|10).*/i],describe:function(){return{type:s.PLATFORMS_MAP.tablet,vendor:"Nexus"}}},{test:[/ipad/i],describe:function(){return{type:s.PLATFORMS_MAP.tablet,vendor:"Apple",model:"iPad"}}},{test:[/Macintosh(.*?) FxiOS(.*?)\//],describe:function(){return{type:s.PLATFORMS_MAP.tablet,vendor:"Apple",model:"iPad"}}},{test:[/kftt build/i],describe:function(){return{type:s.PLATFORMS_MAP.tablet,vendor:"Amazon",model:"Kindle Fire HD 7"}}},{test:[/silk/i],describe:function(){return{type:s.PLATFORMS_MAP.tablet,vendor:"Amazon"}}},{test:[/tablet(?! pc)/i],describe:function(){return{type:s.PLATFORMS_MAP.tablet}}},{test:function(e){var t=e.test(/ipod|iphone/i),r=e.test(/like (ipod|iphone)/i);return t&&!r},describe:function(e){var t=i.default.getFirstMatch(/(ipod|iphone)/i,e);return{type:s.PLATFORMS_MAP.mobile,vendor:"Apple",model:t}}},{test:[/nexus\s*[0-6].*/i,/galaxy nexus/i],describe:function(){return{type:s.PLATFORMS_MAP.mobile,vendor:"Nexus"}}},{test:[/[^-]mobi/i],describe:function(){return{type:s.PLATFORMS_MAP.mobile}}},{test:function(e){return"blackberry"===e.getBrowserName(!0)},describe:function(){return{type:s.PLATFORMS_MAP.mobile,vendor:"BlackBerry"}}},{test:function(e){return"bada"===e.getBrowserName(!0)},describe:function(){return{type:s.PLATFORMS_MAP.mobile}}},{test:function(e){return"windows phone"===e.getBrowserName()},describe:function(){return{type:s.PLATFORMS_MAP.mobile,vendor:"Microsoft"}}},{test:function(e){var t=Number(String(e.getOSVersion()).split(".")[0]);return"android"===e.getOSName(!0)&&t>=3},describe:function(){return{type:s.PLATFORMS_MAP.tablet}}},{test:function(e){return"android"===e.getOSName(!0)},describe:function(){return{type:s.PLATFORMS_MAP.mobile}}},{test:function(e){return"macos"===e.getOSName(!0)},describe:function(){return{type:s.PLATFORMS_MAP.desktop,vendor:"Apple"}}},{test:function(e){return"windows"===e.getOSName(!0)},describe:function(){return{type:s.PLATFORMS_MAP.desktop}}},{test:function(e){return"linux"===e.getOSName(!0)},describe:function(){return{type:s.PLATFORMS_MAP.desktop}}},{test:function(e){return"playstation 4"===e.getOSName(!0)},describe:function(){return{type:s.PLATFORMS_MAP.tv}}},{test:function(e){return"roku"===e.getOSName(!0)},describe:function(){return{type:s.PLATFORMS_MAP.tv}}}];t.default=a,e.exports=t.default},95:function(e,t,r){"use strict";t.__esModule=!0,t.default=void 0;var n,i=(n=r(17))&&n.__esModule?n:{default:n},s=r(18);var a=[{test:function(e){return"microsoft edge"===e.getBrowserName(!0)},describe:function(e){if(/\sedg\//i.test(e))return{name:s.ENGINE_MAP.Blink};var t=i.default.getFirstMatch(/edge\/(\d+(\.?_?\d+)+)/i,e);return{name:s.ENGINE_MAP.EdgeHTML,version:t}}},{test:[/trident/i],describe:function(e){var t={name:s.ENGINE_MAP.Trident},r=i.default.getFirstMatch(/trident\/(\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:function(e){return e.test(/presto/i)},describe:function(e){var t={name:s.ENGINE_MAP.Presto},r=i.default.getFirstMatch(/presto\/(\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:function(e){var t=e.test(/gecko/i),r=e.test(/like gecko/i);return t&&!r},describe:function(e){var t={name:s.ENGINE_MAP.Gecko},r=i.default.getFirstMatch(/gecko\/(\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/(apple)?webkit\/537\.36/i],describe:function(){return{name:s.ENGINE_MAP.Blink}}},{test:[/(apple)?webkit/i],describe:function(e){var t={name:s.ENGINE_MAP.WebKit},r=i.default.getFirstMatch(/webkit\/(\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}}];t.default=a,e.exports=t.default}})})); -},{}],4:[function(require,module,exports){ -const debug = require('debug')('h264-profile-level-id'); - -/* eslint-disable no-console */ -debug.log = console.info.bind(console); -/* eslint-enable no-console */ - -const ProfileConstrainedBaseline = 1; -const ProfileBaseline = 2; -const ProfileMain = 3; -const ProfileConstrainedHigh = 4; -const ProfileHigh = 5; - -exports.ProfileConstrainedBaseline = ProfileConstrainedBaseline; -exports.ProfileBaseline = ProfileBaseline; -exports.ProfileMain = ProfileMain; -exports.ProfileConstrainedHigh = ProfileConstrainedHigh; -exports.ProfileHigh = ProfileHigh; - -// All values are equal to ten times the level number, except level 1b which is -// special. -const Level1_b = 0; -const Level1 = 10; -const Level1_1 = 11; -const Level1_2 = 12; -const Level1_3 = 13; -const Level2 = 20; -const Level2_1 = 21; -const Level2_2 = 22; -const Level3 = 30; -const Level3_1 = 31; -const Level3_2 = 32; -const Level4 = 40; -const Level4_1 = 41; -const Level4_2 = 42; -const Level5 = 50; -const Level5_1 = 51; -const Level5_2 = 52; - -exports.Level1_b = Level1_b; -exports.Level1 = Level1; -exports.Level1_1 = Level1_1; -exports.Level1_2 = Level1_2; -exports.Level1_3 = Level1_3; -exports.Level2 = Level2; -exports.Level2_1 = Level2_1; -exports.Level2_2 = Level2_2; -exports.Level3 = Level3; -exports.Level3_1 = Level3_1; -exports.Level3_2 = Level3_2; -exports.Level4 = Level4; -exports.Level4_1 = Level4_1; -exports.Level4_2 = Level4_2; -exports.Level5 = Level5; -exports.Level5_1 = Level5_1; -exports.Level5_2 = Level5_2; - -class ProfileLevelId -{ - constructor(profile, level) - { - this.profile = profile; - this.level = level; - } -} - -exports.ProfileLevelId = ProfileLevelId; - -// Default ProfileLevelId. -// -// TODO: The default should really be profile Baseline and level 1 according to -// the spec: https://tools.ietf.org/html/rfc6184#section-8.1. In order to not -// break backwards compatibility with older versions of WebRTC where external -// codecs don't have any parameters, use profile ConstrainedBaseline level 3_1 -// instead. This workaround will only be done in an interim period to allow -// external clients to update their code. -// -// http://crbug/webrtc/6337. -const DefaultProfileLevelId = - new ProfileLevelId(ProfileConstrainedBaseline, Level3_1); - -// For level_idc=11 and profile_idc=0x42, 0x4D, or 0x58, the constraint set3 -// flag specifies if level 1b or level 1.1 is used. -const ConstraintSet3Flag = 0x10; - -// Class for matching bit patterns such as "x1xx0000" where 'x' is allowed to be -// either 0 or 1. -class BitPattern -{ - constructor(str) - { - this._mask = ~byteMaskString('x', str); - this._maskedValue = byteMaskString('1', str); - } +},{"./Logger":2}],4:[function(require,module,exports){ +(function (process){(function (){ +/* eslint-env browser */ - isMatch(value) - { - return this._maskedValue === (value & this._mask); - } -} +/** + * This is the web browser implementation of `debug()`. + */ -// Class for converting between profile_idc/profile_iop to Profile. -class ProfilePattern -{ - constructor(profile_idc, profile_iop, profile) - { - this.profile_idc = profile_idc; - this.profile_iop = profile_iop; - this.profile = profile; - } -} +exports.formatArgs = formatArgs; +exports.save = save; +exports.load = load; +exports.useColors = useColors; +exports.storage = localstorage(); +exports.destroy = (() => { + let warned = false; -// This is from https://tools.ietf.org/html/rfc6184#section-8.1. -const ProfilePatterns = -[ - new ProfilePattern(0x42, new BitPattern('x1xx0000'), ProfileConstrainedBaseline), - new ProfilePattern(0x4D, new BitPattern('1xxx0000'), ProfileConstrainedBaseline), - new ProfilePattern(0x58, new BitPattern('11xx0000'), ProfileConstrainedBaseline), - new ProfilePattern(0x42, new BitPattern('x0xx0000'), ProfileBaseline), - new ProfilePattern(0x58, new BitPattern('10xx0000'), ProfileBaseline), - new ProfilePattern(0x4D, new BitPattern('0x0x0000'), ProfileMain), - new ProfilePattern(0x64, new BitPattern('00000000'), ProfileHigh), - new ProfilePattern(0x64, new BitPattern('00001100'), ProfileConstrainedHigh) -]; + return () => { + if (!warned) { + warned = true; + console.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.'); + } + }; +})(); /** - * Parse profile level id that is represented as a string of 3 hex bytes. - * Nothing will be returned if the string is not a recognized H264 profile - * level id. - * - * @param {String} str - profile-level-id value as a string of 3 hex bytes. - * - * @returns {ProfileLevelId} + * Colors. */ -exports.parseProfileLevelId = function(str) -{ - // The string should consist of 3 bytes in hexadecimal format. - if (typeof str !== 'string' || str.length !== 6) - return null; - - const profile_level_id_numeric = parseInt(str, 16); - - if (profile_level_id_numeric === 0) - return null; - // Separate into three bytes. - const level_idc = profile_level_id_numeric & 0xFF; - const profile_iop = (profile_level_id_numeric >> 8) & 0xFF; - const profile_idc = (profile_level_id_numeric >> 16) & 0xFF; - - // Parse level based on level_idc and constraint set 3 flag. - let level; +exports.colors = [ + '#0000CC', + '#0000FF', + '#0033CC', + '#0033FF', + '#0066CC', + '#0066FF', + '#0099CC', + '#0099FF', + '#00CC00', + '#00CC33', + '#00CC66', + '#00CC99', + '#00CCCC', + '#00CCFF', + '#3300CC', + '#3300FF', + '#3333CC', + '#3333FF', + '#3366CC', + '#3366FF', + '#3399CC', + '#3399FF', + '#33CC00', + '#33CC33', + '#33CC66', + '#33CC99', + '#33CCCC', + '#33CCFF', + '#6600CC', + '#6600FF', + '#6633CC', + '#6633FF', + '#66CC00', + '#66CC33', + '#9900CC', + '#9900FF', + '#9933CC', + '#9933FF', + '#99CC00', + '#99CC33', + '#CC0000', + '#CC0033', + '#CC0066', + '#CC0099', + '#CC00CC', + '#CC00FF', + '#CC3300', + '#CC3333', + '#CC3366', + '#CC3399', + '#CC33CC', + '#CC33FF', + '#CC6600', + '#CC6633', + '#CC9900', + '#CC9933', + '#CCCC00', + '#CCCC33', + '#FF0000', + '#FF0033', + '#FF0066', + '#FF0099', + '#FF00CC', + '#FF00FF', + '#FF3300', + '#FF3333', + '#FF3366', + '#FF3399', + '#FF33CC', + '#FF33FF', + '#FF6600', + '#FF6633', + '#FF9900', + '#FF9933', + '#FFCC00', + '#FFCC33' +]; - switch (level_idc) - { - case Level1_1: - { - level = (profile_iop & ConstraintSet3Flag) !== 0 ? Level1_b : Level1_1; - break; - } - case Level1: - case Level1_2: - case Level1_3: - case Level2: - case Level2_1: - case Level2_2: - case Level3: - case Level3_1: - case Level3_2: - case Level4: - case Level4_1: - case Level4_2: - case Level5: - case Level5_1: - case Level5_2: - { - level = level_idc; - break; - } - // Unrecognized level_idc. - default: - { - debug('parseProfileLevelId() | unrecognized level_idc:%s', level_idc); +/** + * Currently only WebKit-based Web Inspectors, Firefox >= v31, + * and the Firebug extension (any Firefox version) are known + * to support "%c" CSS customizations. + * + * TODO: add a `localStorage` variable to explicitly enable/disable colors + */ - return null; - } +// eslint-disable-next-line complexity +function useColors() { + // NB: In an Electron preload script, document will be defined but not fully + // initialized. Since we know we're in Chrome, we'll just detect this case + // explicitly + if (typeof window !== 'undefined' && window.process && (window.process.type === 'renderer' || window.process.__nwjs)) { + return true; } - // Parse profile_idc/profile_iop into a Profile enum. - for (const pattern of ProfilePatterns) - { - if ( - profile_idc === pattern.profile_idc && - pattern.profile_iop.isMatch(profile_iop) - ) - { - return new ProfileLevelId(pattern.profile, level); - } + // Internet Explorer and Edge do not support colors. + if (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)) { + return false; } - debug('parseProfileLevelId() | unrecognized profile_idc/profile_iop combination'); - - return null; -}; + // Is webkit? http://stackoverflow.com/a/16459606/376773 + // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632 + return (typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) || + // Is firebug? http://stackoverflow.com/a/398120/376773 + (typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) || + // Is firefox >= v31? + // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages + (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31) || + // Double check webkit in userAgent just in case we are in a worker + (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)); +} /** - * Returns canonical string representation as three hex bytes of the profile - * level id, or returns nothing for invalid profile level ids. - * - * @param {ProfileLevelId} profile_level_id + * Colorize log arguments if enabled. * - * @returns {String} + * @api public */ -exports.profileLevelIdToString = function(profile_level_id) -{ - // Handle special case level == 1b. - if (profile_level_id.level == Level1_b) - { - switch (profile_level_id.profile) - { - case ProfileConstrainedBaseline: - { - return '42f00b'; - } - case ProfileBaseline: - { - return '42100b'; - } - case ProfileMain: - { - return '4d100b'; - } - // Level 1_b is not allowed for other profiles. - default: - { - debug( - 'profileLevelIdToString() | Level 1_b not is allowed for profile:%s', - profile_level_id.profile); - return null; - } - } +function formatArgs(args) { + args[0] = (this.useColors ? '%c' : '') + + this.namespace + + (this.useColors ? ' %c' : ' ') + + args[0] + + (this.useColors ? '%c ' : ' ') + + '+' + module.exports.humanize(this.diff); + + if (!this.useColors) { + return; } - let profile_idc_iop_string; + const c = 'color: ' + this.color; + args.splice(1, 0, c, 'color: inherit'); - switch (profile_level_id.profile) - { - case ProfileConstrainedBaseline: - { - profile_idc_iop_string = '42e0'; - break; - } - case ProfileBaseline: - { - profile_idc_iop_string = '4200'; - break; - } - case ProfileMain: - { - profile_idc_iop_string = '4d00'; - break; - } - case ProfileConstrainedHigh: - { - profile_idc_iop_string = '640c'; - break; - } - case ProfileHigh: - { - profile_idc_iop_string = '6400'; - break; + // The final "%c" is somewhat tricky, because there could be other + // arguments passed either before or after the %c, so we need to + // figure out the correct index to insert the CSS into + let index = 0; + let lastC = 0; + args[0].replace(/%[a-zA-Z%]/g, match => { + if (match === '%%') { + return; } - default: - { - debug( - 'profileLevelIdToString() | unrecognized profile:%s', - profile_level_id.profile); - - return null; + index++; + if (match === '%c') { + // We only are interested in the *last* %c + // (the user may have provided their own) + lastC = index; } - } + }); - let levelStr = (profile_level_id.level).toString(16); - - if (levelStr.length === 1) - levelStr = `0${levelStr}`; - - return `${profile_idc_iop_string}${levelStr}`; -}; + args.splice(lastC, 0, c); +} /** - * Parse profile level id that is represented as a string of 3 hex bytes - * contained in an SDP key-value map. A default profile level id will be - * returned if the profile-level-id key is missing. Nothing will be returned if - * the key is present but the string is invalid. - * - * @param {Object} [params={}] - Codec parameters object. + * Invokes `console.debug()` when available. + * No-op when `console.debug` is not a "function". + * If `console.debug` is not available, falls back + * to `console.log`. * - * @returns {ProfileLevelId} + * @api public */ -exports.parseSdpProfileLevelId = function(params = {}) -{ - const profile_level_id = params['profile-level-id']; - - return !profile_level_id - ? DefaultProfileLevelId - : exports.parseProfileLevelId(profile_level_id); -}; +exports.log = console.debug || console.log || (() => {}); /** - * Returns true if the parameters have the same H264 profile, i.e. the same - * H264 profile (Baseline, High, etc). + * Save `namespaces`. * - * @param {Object} [params1={}] - Codec parameters object. - * @param {Object} [params2={}] - Codec parameters object. + * @param {String} namespaces + * @api private + */ +function save(namespaces) { + try { + if (namespaces) { + exports.storage.setItem('debug', namespaces); + } else { + exports.storage.removeItem('debug'); + } + } catch (error) { + // Swallow + // XXX (@Qix-) should we be logging these? + } +} + +/** + * Load `namespaces`. * - * @returns {Boolean} + * @return {String} returns the previously persisted debug modes + * @api private */ -exports.isSameProfile = function(params1 = {}, params2 = {}) -{ - const profile_level_id_1 = exports.parseSdpProfileLevelId(params1); - const profile_level_id_2 = exports.parseSdpProfileLevelId(params2); +function load() { + let r; + try { + r = exports.storage.getItem('debug'); + } catch (error) { + // Swallow + // XXX (@Qix-) should we be logging these? + } - // Compare H264 profiles, but not levels. - return Boolean( - profile_level_id_1 && - profile_level_id_2 && - profile_level_id_1.profile === profile_level_id_2.profile - ); -}; + // If debug isn't set in LS, and we're in Electron, try to load $DEBUG + if (!r && typeof process !== 'undefined' && 'env' in process) { + r = process.env.DEBUG; + } + + return r; +} /** - * Generate codec parameters that will be used as answer in an SDP negotiation - * based on local supported parameters and remote offered parameters. Both - * local_supported_params and remote_offered_params represent sendrecv media - * descriptions, i.e they are a mix of both encode and decode capabilities. In - * theory, when the profile in local_supported_params represent a strict superset - * of the profile in remote_offered_params, we could limit the profile in the - * answer to the profile in remote_offered_params. - * - * However, to simplify the code, each supported H264 profile should be listed - * explicitly in the list of local supported codecs, even if they are redundant. - * Then each local codec in the list should be tested one at a time against the - * remote codec, and only when the profiles are equal should this function be - * called. Therefore, this function does not need to handle profile intersection, - * and the profile of local_supported_params and remote_offered_params must be - * equal before calling this function. The parameters that are used when - * negotiating are the level part of profile-level-id and level-asymmetry-allowed. - * - * @param {Object} [local_supported_params={}] - * @param {Object} [remote_offered_params={}] + * Localstorage attempts to return the localstorage. * - * @returns {String} Canonical string representation as three hex bytes of the - * profile level id, or null if no one of the params have profile-level-id. + * This is necessary because safari throws + * when a user disables cookies/localstorage + * and you attempt to access it. * - * @throws {TypeError} If Profile mismatch or invalid params. + * @return {LocalStorage} + * @api private */ -exports.generateProfileLevelIdForAnswer = function( - local_supported_params = {}, - remote_offered_params = {} -) -{ - // If both local and remote params do not contain profile-level-id, they are - // both using the default profile. In this case, don't return anything. - if ( - !local_supported_params['profile-level-id'] && - !remote_offered_params['profile-level-id'] - ) - { - debug( - 'generateProfileLevelIdForAnswer() | no profile-level-id in local and remote params'); - return null; +function localstorage() { + try { + // TVMLKit (Apple TV JS Runtime) does not have a window object, just localStorage in the global context + // The Browser also has localStorage in the global context. + return localStorage; + } catch (error) { + // Swallow + // XXX (@Qix-) should we be logging these? } +} - // Parse profile-level-ids. - const local_profile_level_id = - exports.parseSdpProfileLevelId(local_supported_params); - const remote_profile_level_id = - exports.parseSdpProfileLevelId(remote_offered_params); +module.exports = require('./common')(exports); - // The local and remote codec must have valid and equal H264 Profiles. - if (!local_profile_level_id) - throw new TypeError('invalid local_profile_level_id'); +const {formatters} = module.exports; - if (!remote_profile_level_id) - throw new TypeError('invalid remote_profile_level_id'); +/** + * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default. + */ - if (local_profile_level_id.profile !== remote_profile_level_id.profile) - throw new TypeError('H264 Profile mismatch'); +formatters.j = function (v) { + try { + return JSON.stringify(v); + } catch (error) { + return '[UnexpectedJSONParseError]: ' + error.message; + } +}; - // Parse level information. - const level_asymmetry_allowed = ( - isLevelAsymmetryAllowed(local_supported_params) && - isLevelAsymmetryAllowed(remote_offered_params) - ); +}).call(this)}).call(this,require('_process')) +},{"./common":5,"_process":56}],5:[function(require,module,exports){ - const local_level = local_profile_level_id.level; - const remote_level = remote_profile_level_id.level; - const min_level = minLevel(local_level, remote_level); +/** + * This is the common logic for both the Node.js and web browser + * implementations of `debug()`. + */ - // Determine answer level. When level asymmetry is not allowed, level upgrade - // is not allowed, i.e., the level in the answer must be equal to or lower - // than the level in the offer. - const answer_level = level_asymmetry_allowed ? local_level : min_level; +function setup(env) { + createDebug.debug = createDebug; + createDebug.default = createDebug; + createDebug.coerce = coerce; + createDebug.disable = disable; + createDebug.enable = enable; + createDebug.enabled = enabled; + createDebug.humanize = require('ms'); + createDebug.destroy = destroy; - debug( - 'generateProfileLevelIdForAnswer() | result: [profile:%s, level:%s]', - local_profile_level_id.profile, answer_level); + Object.keys(env).forEach(key => { + createDebug[key] = env[key]; + }); - // Return the resulting profile-level-id for the answer parameters. - return exports.profileLevelIdToString( - new ProfileLevelId(local_profile_level_id.profile, answer_level)); -}; + /** + * The currently active debug mode names, and names to skip. + */ -// Convert a string of 8 characters into a byte where the positions containing -// character c will have their bit set. For example, c = 'x', str = "x1xx0000" -// will return 0b10110000. -function byteMaskString(c, str) -{ - return ( - ((str[0] === c) << 7) | ((str[1] === c) << 6) | ((str[2] === c) << 5) | - ((str[3] === c) << 4) | ((str[4] === c) << 3) | ((str[5] === c) << 2) | - ((str[6] === c) << 1) | ((str[7] === c) << 0) - ); -} + createDebug.names = []; + createDebug.skips = []; -// Compare H264 levels and handle the level 1b case. -function isLessLevel(a, b) -{ - if (a === Level1_b) - return b !== Level1 && b !== Level1_b; + /** + * Map of special "%n" handling functions, for the debug "format" argument. + * + * Valid key names are a single, lower or upper-case letter, i.e. "n" and "N". + */ + createDebug.formatters = {}; - if (b === Level1_b) - return a !== Level1; + /** + * Selects a color for a debug namespace + * @param {String} namespace The namespace string for the debug instance to be colored + * @return {Number|String} An ANSI color code for the given namespace + * @api private + */ + function selectColor(namespace) { + let hash = 0; - return a < b; -} + for (let i = 0; i < namespace.length; i++) { + hash = ((hash << 5) - hash) + namespace.charCodeAt(i); + hash |= 0; // Convert to 32bit integer + } -function minLevel(a, b) -{ - return isLessLevel(a, b) ? a : b; -} + return createDebug.colors[Math.abs(hash) % createDebug.colors.length]; + } + createDebug.selectColor = selectColor; -function isLevelAsymmetryAllowed(params = {}) -{ - const level_asymmetry_allowed = params['level-asymmetry-allowed']; + /** + * Create a debugger with the given `namespace`. + * + * @param {String} namespace + * @return {Function} + * @api public + */ + function createDebug(namespace) { + let prevTime; + let enableOverride = null; + let namespacesCache; + let enabledCache; - return ( - level_asymmetry_allowed === 1 || - level_asymmetry_allowed === '1' - ); -} + function debug(...args) { + // Disabled? + if (!debug.enabled) { + return; + } -},{"debug":5}],5:[function(require,module,exports){ -(function (process){(function (){ -/* eslint-env browser */ + const self = debug; -/** - * This is the web browser implementation of `debug()`. - */ + // Set `diff` timestamp + const curr = Number(new Date()); + const ms = curr - (prevTime || curr); + self.diff = ms; + self.prev = prevTime; + self.curr = curr; + prevTime = curr; -exports.log = log; -exports.formatArgs = formatArgs; -exports.save = save; -exports.load = load; -exports.useColors = useColors; -exports.storage = localstorage(); + args[0] = createDebug.coerce(args[0]); -/** - * Colors. - */ + if (typeof args[0] !== 'string') { + // Anything else let's inspect with %O + args.unshift('%O'); + } -exports.colors = [ - '#0000CC', - '#0000FF', - '#0033CC', - '#0033FF', - '#0066CC', - '#0066FF', - '#0099CC', - '#0099FF', - '#00CC00', - '#00CC33', - '#00CC66', - '#00CC99', - '#00CCCC', - '#00CCFF', - '#3300CC', - '#3300FF', - '#3333CC', - '#3333FF', - '#3366CC', - '#3366FF', - '#3399CC', - '#3399FF', - '#33CC00', - '#33CC33', - '#33CC66', - '#33CC99', - '#33CCCC', - '#33CCFF', - '#6600CC', - '#6600FF', - '#6633CC', - '#6633FF', - '#66CC00', - '#66CC33', - '#9900CC', - '#9900FF', - '#9933CC', - '#9933FF', - '#99CC00', - '#99CC33', - '#CC0000', - '#CC0033', - '#CC0066', - '#CC0099', - '#CC00CC', - '#CC00FF', - '#CC3300', - '#CC3333', - '#CC3366', - '#CC3399', - '#CC33CC', - '#CC33FF', - '#CC6600', - '#CC6633', - '#CC9900', - '#CC9933', - '#CCCC00', - '#CCCC33', - '#FF0000', - '#FF0033', - '#FF0066', - '#FF0099', - '#FF00CC', - '#FF00FF', - '#FF3300', - '#FF3333', - '#FF3366', - '#FF3399', - '#FF33CC', - '#FF33FF', - '#FF6600', - '#FF6633', - '#FF9900', - '#FF9933', - '#FFCC00', - '#FFCC33' -]; + // Apply any `formatters` transformations + let index = 0; + args[0] = args[0].replace(/%([a-zA-Z%])/g, (match, format) => { + // If we encounter an escaped % then don't increase the array index + if (match === '%%') { + return '%'; + } + index++; + const formatter = createDebug.formatters[format]; + if (typeof formatter === 'function') { + const val = args[index]; + match = formatter.call(self, val); -/** - * Currently only WebKit-based Web Inspectors, Firefox >= v31, - * and the Firebug extension (any Firefox version) are known - * to support "%c" CSS customizations. - * - * TODO: add a `localStorage` variable to explicitly enable/disable colors - */ + // Now we need to remove `args[index]` since it's inlined in the `format` + args.splice(index, 1); + index--; + } + return match; + }); -// eslint-disable-next-line complexity -function useColors() { - // NB: In an Electron preload script, document will be defined but not fully - // initialized. Since we know we're in Chrome, we'll just detect this case - // explicitly - if (typeof window !== 'undefined' && window.process && (window.process.type === 'renderer' || window.process.__nwjs)) { - return true; - } + // Apply env-specific formatting (colors, etc.) + createDebug.formatArgs.call(self, args); - // Internet Explorer and Edge do not support colors. - if (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)) { - return false; - } + const logFn = self.log || createDebug.log; + logFn.apply(self, args); + } - // Is webkit? http://stackoverflow.com/a/16459606/376773 - // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632 - return (typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) || - // Is firebug? http://stackoverflow.com/a/398120/376773 - (typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) || - // Is firefox >= v31? - // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages - (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31) || - // Double check webkit in userAgent just in case we are in a worker - (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)); -} + debug.namespace = namespace; + debug.useColors = createDebug.useColors(); + debug.color = createDebug.selectColor(namespace); + debug.extend = extend; + debug.destroy = createDebug.destroy; // XXX Temporary. Will be removed in the next major release. -/** - * Colorize log arguments if enabled. - * - * @api public - */ + Object.defineProperty(debug, 'enabled', { + enumerable: true, + configurable: false, + get: () => { + if (enableOverride !== null) { + return enableOverride; + } + if (namespacesCache !== createDebug.namespaces) { + namespacesCache = createDebug.namespaces; + enabledCache = createDebug.enabled(namespace); + } -function formatArgs(args) { - args[0] = (this.useColors ? '%c' : '') + - this.namespace + - (this.useColors ? ' %c' : ' ') + - args[0] + - (this.useColors ? '%c ' : ' ') + - '+' + module.exports.humanize(this.diff); + return enabledCache; + }, + set: v => { + enableOverride = v; + } + }); - if (!this.useColors) { - return; + // Env-specific initialization logic for debug instances + if (typeof createDebug.init === 'function') { + createDebug.init(debug); + } + + return debug; } - const c = 'color: ' + this.color; - args.splice(1, 0, c, 'color: inherit'); + function extend(namespace, delimiter) { + const newDebug = createDebug(this.namespace + (typeof delimiter === 'undefined' ? ':' : delimiter) + namespace); + newDebug.log = this.log; + return newDebug; + } - // The final "%c" is somewhat tricky, because there could be other - // arguments passed either before or after the %c, so we need to - // figure out the correct index to insert the CSS into - let index = 0; - let lastC = 0; - args[0].replace(/%[a-zA-Z%]/g, match => { - if (match === '%%') { - return; - } - index++; - if (match === '%c') { - // We only are interested in the *last* %c - // (the user may have provided their own) - lastC = index; - } - }); + /** + * Enables a debug mode by namespaces. This can include modes + * separated by a colon and wildcards. + * + * @param {String} namespaces + * @api public + */ + function enable(namespaces) { + createDebug.save(namespaces); + createDebug.namespaces = namespaces; - args.splice(lastC, 0, c); -} + createDebug.names = []; + createDebug.skips = []; -/** - * Invokes `console.log()` when available. - * No-op when `console.log` is not a "function". - * - * @api public - */ -function log(...args) { - // This hackery is required for IE8/9, where - // the `console.log` function doesn't have 'apply' - return typeof console === 'object' && - console.log && - console.log(...args); -} - -/** - * Save `namespaces`. - * - * @param {String} namespaces - * @api private - */ -function save(namespaces) { - try { - if (namespaces) { - exports.storage.setItem('debug', namespaces); - } else { - exports.storage.removeItem('debug'); - } - } catch (error) { - // Swallow - // XXX (@Qix-) should we be logging these? - } -} - -/** - * Load `namespaces`. - * - * @return {String} returns the previously persisted debug modes - * @api private - */ -function load() { - let r; - try { - r = exports.storage.getItem('debug'); - } catch (error) { - // Swallow - // XXX (@Qix-) should we be logging these? - } - - // If debug isn't set in LS, and we're in Electron, try to load $DEBUG - if (!r && typeof process !== 'undefined' && 'env' in process) { - r = process.env.DEBUG; - } - - return r; -} - -/** - * Localstorage attempts to return the localstorage. - * - * This is necessary because safari throws - * when a user disables cookies/localstorage - * and you attempt to access it. - * - * @return {LocalStorage} - * @api private - */ - -function localstorage() { - try { - // TVMLKit (Apple TV JS Runtime) does not have a window object, just localStorage in the global context - // The Browser also has localStorage in the global context. - return localStorage; - } catch (error) { - // Swallow - // XXX (@Qix-) should we be logging these? - } -} - -module.exports = require('./common')(exports); - -const {formatters} = module.exports; - -/** - * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default. - */ - -formatters.j = function (v) { - try { - return JSON.stringify(v); - } catch (error) { - return '[UnexpectedJSONParseError]: ' + error.message; - } -}; - -}).call(this)}).call(this,require('_process')) -},{"./common":6,"_process":48}],6:[function(require,module,exports){ - -/** - * This is the common logic for both the Node.js and web browser - * implementations of `debug()`. - */ - -function setup(env) { - createDebug.debug = createDebug; - createDebug.default = createDebug; - createDebug.coerce = coerce; - createDebug.disable = disable; - createDebug.enable = enable; - createDebug.enabled = enabled; - createDebug.humanize = require('ms'); - - Object.keys(env).forEach(key => { - createDebug[key] = env[key]; - }); - - /** - * Active `debug` instances. - */ - createDebug.instances = []; - - /** - * The currently active debug mode names, and names to skip. - */ - - createDebug.names = []; - createDebug.skips = []; - - /** - * Map of special "%n" handling functions, for the debug "format" argument. - * - * Valid key names are a single, lower or upper-case letter, i.e. "n" and "N". - */ - createDebug.formatters = {}; - - /** - * Selects a color for a debug namespace - * @param {String} namespace The namespace string for the for the debug instance to be colored - * @return {Number|String} An ANSI color code for the given namespace - * @api private - */ - function selectColor(namespace) { - let hash = 0; - - for (let i = 0; i < namespace.length; i++) { - hash = ((hash << 5) - hash) + namespace.charCodeAt(i); - hash |= 0; // Convert to 32bit integer - } - - return createDebug.colors[Math.abs(hash) % createDebug.colors.length]; - } - createDebug.selectColor = selectColor; - - /** - * Create a debugger with the given `namespace`. - * - * @param {String} namespace - * @return {Function} - * @api public - */ - function createDebug(namespace) { - let prevTime; - - function debug(...args) { - // Disabled? - if (!debug.enabled) { - return; - } - - const self = debug; - - // Set `diff` timestamp - const curr = Number(new Date()); - const ms = curr - (prevTime || curr); - self.diff = ms; - self.prev = prevTime; - self.curr = curr; - prevTime = curr; - - args[0] = createDebug.coerce(args[0]); - - if (typeof args[0] !== 'string') { - // Anything else let's inspect with %O - args.unshift('%O'); - } - - // Apply any `formatters` transformations - let index = 0; - args[0] = args[0].replace(/%([a-zA-Z%])/g, (match, format) => { - // If we encounter an escaped % then don't increase the array index - if (match === '%%') { - return match; - } - index++; - const formatter = createDebug.formatters[format]; - if (typeof formatter === 'function') { - const val = args[index]; - match = formatter.call(self, val); - - // Now we need to remove `args[index]` since it's inlined in the `format` - args.splice(index, 1); - index--; - } - return match; - }); - - // Apply env-specific formatting (colors, etc.) - createDebug.formatArgs.call(self, args); - - const logFn = self.log || createDebug.log; - logFn.apply(self, args); - } - - debug.namespace = namespace; - debug.enabled = createDebug.enabled(namespace); - debug.useColors = createDebug.useColors(); - debug.color = selectColor(namespace); - debug.destroy = destroy; - debug.extend = extend; - // Debug.formatArgs = formatArgs; - // debug.rawLog = rawLog; - - // env-specific initialization logic for debug instances - if (typeof createDebug.init === 'function') { - createDebug.init(debug); - } - - createDebug.instances.push(debug); - - return debug; - } - - function destroy() { - const index = createDebug.instances.indexOf(this); - if (index !== -1) { - createDebug.instances.splice(index, 1); - return true; - } - return false; - } - - function extend(namespace, delimiter) { - const newDebug = createDebug(this.namespace + (typeof delimiter === 'undefined' ? ':' : delimiter) + namespace); - newDebug.log = this.log; - return newDebug; - } - - /** - * Enables a debug mode by namespaces. This can include modes - * separated by a colon and wildcards. - * - * @param {String} namespaces - * @api public - */ - function enable(namespaces) { - createDebug.save(namespaces); - - createDebug.names = []; - createDebug.skips = []; - - let i; - const split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/); - const len = split.length; + let i; + const split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/); + const len = split.length; for (i = 0; i < len; i++) { if (!split[i]) { @@ -1061,16 +681,11 @@ function setup(env) { namespaces = split[i].replace(/\*/g, '.*?'); if (namespaces[0] === '-') { - createDebug.skips.push(new RegExp('^' + namespaces.substr(1) + '$')); + createDebug.skips.push(new RegExp('^' + namespaces.slice(1) + '$')); } else { createDebug.names.push(new RegExp('^' + namespaces + '$')); } } - - for (i = 0; i < createDebug.instances.length; i++) { - const instance = createDebug.instances[i]; - instance.enabled = createDebug.enabled(instance.namespace); - } } /** @@ -1145,6 +760,14 @@ function setup(env) { return val; } + /** + * XXX DO NOT USE. This is a temporary stub function. + * XXX It WILL be removed in the next major release. + */ + function destroy() { + console.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.'); + } + createDebug.enable(createDebug.load()); return createDebug; @@ -1152,7 +775,7 @@ function setup(env) { module.exports = setup; -},{"ms":7}],7:[function(require,module,exports){ +},{"ms":6}],6:[function(require,module,exports){ /** * Helpers. */ @@ -1316,1043 +939,671 @@ function plural(ms, msAbs, n, name) { return Math.round(ms / n) + ' ' + name + (isPlural ? 's' : ''); } +},{}],7:[function(require,module,exports){ +!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.bowser=t():e.bowser=t()}(this,(function(){return function(e){var t={};function r(n){if(t[n])return t[n].exports;var i=t[n]={i:n,l:!1,exports:{}};return e[n].call(i.exports,i,i.exports,r),i.l=!0,i.exports}return r.m=e,r.c=t,r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{enumerable:!0,get:n})},r.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},r.t=function(e,t){if(1&t&&(e=r(e)),8&t)return e;if(4&t&&"object"==typeof e&&e&&e.__esModule)return e;var n=Object.create(null);if(r.r(n),Object.defineProperty(n,"default",{enumerable:!0,value:e}),2&t&&"string"!=typeof e)for(var i in e)r.d(n,i,function(t){return e[t]}.bind(null,i));return n},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,t){return Object.prototype.hasOwnProperty.call(e,t)},r.p="",r(r.s=90)}({17:function(e,t,r){"use strict";t.__esModule=!0,t.default=void 0;var n=r(18),i=function(){function e(){}return e.getFirstMatch=function(e,t){var r=t.match(e);return r&&r.length>0&&r[1]||""},e.getSecondMatch=function(e,t){var r=t.match(e);return r&&r.length>1&&r[2]||""},e.matchAndReturnConst=function(e,t,r){if(e.test(t))return r},e.getWindowsVersionName=function(e){switch(e){case"NT":return"NT";case"XP":return"XP";case"NT 5.0":return"2000";case"NT 5.1":return"XP";case"NT 5.2":return"2003";case"NT 6.0":return"Vista";case"NT 6.1":return"7";case"NT 6.2":return"8";case"NT 6.3":return"8.1";case"NT 10.0":return"10";default:return}},e.getMacOSVersionName=function(e){var t=e.split(".").splice(0,2).map((function(e){return parseInt(e,10)||0}));if(t.push(0),10===t[0])switch(t[1]){case 5:return"Leopard";case 6:return"Snow Leopard";case 7:return"Lion";case 8:return"Mountain Lion";case 9:return"Mavericks";case 10:return"Yosemite";case 11:return"El Capitan";case 12:return"Sierra";case 13:return"High Sierra";case 14:return"Mojave";case 15:return"Catalina";default:return}},e.getAndroidVersionName=function(e){var t=e.split(".").splice(0,2).map((function(e){return parseInt(e,10)||0}));if(t.push(0),!(1===t[0]&&t[1]<5))return 1===t[0]&&t[1]<6?"Cupcake":1===t[0]&&t[1]>=6?"Donut":2===t[0]&&t[1]<2?"Eclair":2===t[0]&&2===t[1]?"Froyo":2===t[0]&&t[1]>2?"Gingerbread":3===t[0]?"Honeycomb":4===t[0]&&t[1]<1?"Ice Cream Sandwich":4===t[0]&&t[1]<4?"Jelly Bean":4===t[0]&&t[1]>=4?"KitKat":5===t[0]?"Lollipop":6===t[0]?"Marshmallow":7===t[0]?"Nougat":8===t[0]?"Oreo":9===t[0]?"Pie":void 0},e.getVersionPrecision=function(e){return e.split(".").length},e.compareVersions=function(t,r,n){void 0===n&&(n=!1);var i=e.getVersionPrecision(t),s=e.getVersionPrecision(r),a=Math.max(i,s),o=0,u=e.map([t,r],(function(t){var r=a-e.getVersionPrecision(t),n=t+new Array(r+1).join(".0");return e.map(n.split("."),(function(e){return new Array(20-e.length).join("0")+e})).reverse()}));for(n&&(o=a-Math.min(i,s)),a-=1;a>=o;){if(u[0][a]>u[1][a])return 1;if(u[0][a]===u[1][a]){if(a===o)return 0;a-=1}else if(u[0][a]1?i-1:0),a=1;a0){var a=Object.keys(r),u=o.default.find(a,(function(e){return t.isOS(e)}));if(u){var d=this.satisfies(r[u]);if(void 0!==d)return d}var c=o.default.find(a,(function(e){return t.isPlatform(e)}));if(c){var f=this.satisfies(r[c]);if(void 0!==f)return f}}if(s>0){var l=Object.keys(i),h=o.default.find(l,(function(e){return t.isBrowser(e,!0)}));if(void 0!==h)return this.compareVersion(i[h])}},t.isBrowser=function(e,t){void 0===t&&(t=!1);var r=this.getBrowserName().toLowerCase(),n=e.toLowerCase(),i=o.default.getBrowserTypeByAlias(n);return t&&i&&(n=i.toLowerCase()),n===r},t.compareVersion=function(e){var t=[0],r=e,n=!1,i=this.getBrowserVersion();if("string"==typeof i)return">"===e[0]||"<"===e[0]?(r=e.substr(1),"="===e[1]?(n=!0,r=e.substr(2)):t=[],">"===e[0]?t.push(1):t.push(-1)):"="===e[0]?r=e.substr(1):"~"===e[0]&&(n=!0,r=e.substr(1)),t.indexOf(o.default.compareVersions(i,r,n))>-1},t.isOS=function(e){return this.getOSName(!0)===String(e).toLowerCase()},t.isPlatform=function(e){return this.getPlatformType(!0)===String(e).toLowerCase()},t.isEngine=function(e){return this.getEngineName(!0)===String(e).toLowerCase()},t.is=function(e,t){return void 0===t&&(t=!1),this.isBrowser(e,t)||this.isOS(e)||this.isPlatform(e)},t.some=function(e){var t=this;return void 0===e&&(e=[]),e.some((function(e){return t.is(e)}))},e}();t.default=d,e.exports=t.default},92:function(e,t,r){"use strict";t.__esModule=!0,t.default=void 0;var n,i=(n=r(17))&&n.__esModule?n:{default:n};var s=/version\/(\d+(\.?_?\d+)+)/i,a=[{test:[/googlebot/i],describe:function(e){var t={name:"Googlebot"},r=i.default.getFirstMatch(/googlebot\/(\d+(\.\d+))/i,e)||i.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/opera/i],describe:function(e){var t={name:"Opera"},r=i.default.getFirstMatch(s,e)||i.default.getFirstMatch(/(?:opera)[\s/](\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/opr\/|opios/i],describe:function(e){var t={name:"Opera"},r=i.default.getFirstMatch(/(?:opr|opios)[\s/](\S+)/i,e)||i.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/SamsungBrowser/i],describe:function(e){var t={name:"Samsung Internet for Android"},r=i.default.getFirstMatch(s,e)||i.default.getFirstMatch(/(?:SamsungBrowser)[\s/](\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/Whale/i],describe:function(e){var t={name:"NAVER Whale Browser"},r=i.default.getFirstMatch(s,e)||i.default.getFirstMatch(/(?:whale)[\s/](\d+(?:\.\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/MZBrowser/i],describe:function(e){var t={name:"MZ Browser"},r=i.default.getFirstMatch(/(?:MZBrowser)[\s/](\d+(?:\.\d+)+)/i,e)||i.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/focus/i],describe:function(e){var t={name:"Focus"},r=i.default.getFirstMatch(/(?:focus)[\s/](\d+(?:\.\d+)+)/i,e)||i.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/swing/i],describe:function(e){var t={name:"Swing"},r=i.default.getFirstMatch(/(?:swing)[\s/](\d+(?:\.\d+)+)/i,e)||i.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/coast/i],describe:function(e){var t={name:"Opera Coast"},r=i.default.getFirstMatch(s,e)||i.default.getFirstMatch(/(?:coast)[\s/](\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/opt\/\d+(?:.?_?\d+)+/i],describe:function(e){var t={name:"Opera Touch"},r=i.default.getFirstMatch(/(?:opt)[\s/](\d+(\.?_?\d+)+)/i,e)||i.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/yabrowser/i],describe:function(e){var t={name:"Yandex Browser"},r=i.default.getFirstMatch(/(?:yabrowser)[\s/](\d+(\.?_?\d+)+)/i,e)||i.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/ucbrowser/i],describe:function(e){var t={name:"UC Browser"},r=i.default.getFirstMatch(s,e)||i.default.getFirstMatch(/(?:ucbrowser)[\s/](\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/Maxthon|mxios/i],describe:function(e){var t={name:"Maxthon"},r=i.default.getFirstMatch(s,e)||i.default.getFirstMatch(/(?:Maxthon|mxios)[\s/](\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/epiphany/i],describe:function(e){var t={name:"Epiphany"},r=i.default.getFirstMatch(s,e)||i.default.getFirstMatch(/(?:epiphany)[\s/](\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/puffin/i],describe:function(e){var t={name:"Puffin"},r=i.default.getFirstMatch(s,e)||i.default.getFirstMatch(/(?:puffin)[\s/](\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/sleipnir/i],describe:function(e){var t={name:"Sleipnir"},r=i.default.getFirstMatch(s,e)||i.default.getFirstMatch(/(?:sleipnir)[\s/](\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/k-meleon/i],describe:function(e){var t={name:"K-Meleon"},r=i.default.getFirstMatch(s,e)||i.default.getFirstMatch(/(?:k-meleon)[\s/](\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/micromessenger/i],describe:function(e){var t={name:"WeChat"},r=i.default.getFirstMatch(/(?:micromessenger)[\s/](\d+(\.?_?\d+)+)/i,e)||i.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/qqbrowser/i],describe:function(e){var t={name:/qqbrowserlite/i.test(e)?"QQ Browser Lite":"QQ Browser"},r=i.default.getFirstMatch(/(?:qqbrowserlite|qqbrowser)[/](\d+(\.?_?\d+)+)/i,e)||i.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/msie|trident/i],describe:function(e){var t={name:"Internet Explorer"},r=i.default.getFirstMatch(/(?:msie |rv:)(\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/\sedg\//i],describe:function(e){var t={name:"Microsoft Edge"},r=i.default.getFirstMatch(/\sedg\/(\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/edg([ea]|ios)/i],describe:function(e){var t={name:"Microsoft Edge"},r=i.default.getSecondMatch(/edg([ea]|ios)\/(\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/vivaldi/i],describe:function(e){var t={name:"Vivaldi"},r=i.default.getFirstMatch(/vivaldi\/(\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/seamonkey/i],describe:function(e){var t={name:"SeaMonkey"},r=i.default.getFirstMatch(/seamonkey\/(\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/sailfish/i],describe:function(e){var t={name:"Sailfish"},r=i.default.getFirstMatch(/sailfish\s?browser\/(\d+(\.\d+)?)/i,e);return r&&(t.version=r),t}},{test:[/silk/i],describe:function(e){var t={name:"Amazon Silk"},r=i.default.getFirstMatch(/silk\/(\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/phantom/i],describe:function(e){var t={name:"PhantomJS"},r=i.default.getFirstMatch(/phantomjs\/(\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/slimerjs/i],describe:function(e){var t={name:"SlimerJS"},r=i.default.getFirstMatch(/slimerjs\/(\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/blackberry|\bbb\d+/i,/rim\stablet/i],describe:function(e){var t={name:"BlackBerry"},r=i.default.getFirstMatch(s,e)||i.default.getFirstMatch(/blackberry[\d]+\/(\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/(web|hpw)[o0]s/i],describe:function(e){var t={name:"WebOS Browser"},r=i.default.getFirstMatch(s,e)||i.default.getFirstMatch(/w(?:eb)?[o0]sbrowser\/(\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/bada/i],describe:function(e){var t={name:"Bada"},r=i.default.getFirstMatch(/dolfin\/(\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/tizen/i],describe:function(e){var t={name:"Tizen"},r=i.default.getFirstMatch(/(?:tizen\s?)?browser\/(\d+(\.?_?\d+)+)/i,e)||i.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/qupzilla/i],describe:function(e){var t={name:"QupZilla"},r=i.default.getFirstMatch(/(?:qupzilla)[\s/](\d+(\.?_?\d+)+)/i,e)||i.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/firefox|iceweasel|fxios/i],describe:function(e){var t={name:"Firefox"},r=i.default.getFirstMatch(/(?:firefox|iceweasel|fxios)[\s/](\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/electron/i],describe:function(e){var t={name:"Electron"},r=i.default.getFirstMatch(/(?:electron)\/(\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/MiuiBrowser/i],describe:function(e){var t={name:"Miui"},r=i.default.getFirstMatch(/(?:MiuiBrowser)[\s/](\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/chromium/i],describe:function(e){var t={name:"Chromium"},r=i.default.getFirstMatch(/(?:chromium)[\s/](\d+(\.?_?\d+)+)/i,e)||i.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/chrome|crios|crmo/i],describe:function(e){var t={name:"Chrome"},r=i.default.getFirstMatch(/(?:chrome|crios|crmo)\/(\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/GSA/i],describe:function(e){var t={name:"Google Search"},r=i.default.getFirstMatch(/(?:GSA)\/(\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:function(e){var t=!e.test(/like android/i),r=e.test(/android/i);return t&&r},describe:function(e){var t={name:"Android Browser"},r=i.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/playstation 4/i],describe:function(e){var t={name:"PlayStation 4"},r=i.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/safari|applewebkit/i],describe:function(e){var t={name:"Safari"},r=i.default.getFirstMatch(s,e);return r&&(t.version=r),t}},{test:[/.*/i],describe:function(e){var t=-1!==e.search("\\(")?/^(.*)\/(.*)[ \t]\((.*)/:/^(.*)\/(.*) /;return{name:i.default.getFirstMatch(t,e),version:i.default.getSecondMatch(t,e)}}}];t.default=a,e.exports=t.default},93:function(e,t,r){"use strict";t.__esModule=!0,t.default=void 0;var n,i=(n=r(17))&&n.__esModule?n:{default:n},s=r(18);var a=[{test:[/Roku\/DVP/],describe:function(e){var t=i.default.getFirstMatch(/Roku\/DVP-(\d+\.\d+)/i,e);return{name:s.OS_MAP.Roku,version:t}}},{test:[/windows phone/i],describe:function(e){var t=i.default.getFirstMatch(/windows phone (?:os)?\s?(\d+(\.\d+)*)/i,e);return{name:s.OS_MAP.WindowsPhone,version:t}}},{test:[/windows /i],describe:function(e){var t=i.default.getFirstMatch(/Windows ((NT|XP)( \d\d?.\d)?)/i,e),r=i.default.getWindowsVersionName(t);return{name:s.OS_MAP.Windows,version:t,versionName:r}}},{test:[/Macintosh(.*?) FxiOS(.*?)\//],describe:function(e){var t={name:s.OS_MAP.iOS},r=i.default.getSecondMatch(/(Version\/)(\d[\d.]+)/,e);return r&&(t.version=r),t}},{test:[/macintosh/i],describe:function(e){var t=i.default.getFirstMatch(/mac os x (\d+(\.?_?\d+)+)/i,e).replace(/[_\s]/g,"."),r=i.default.getMacOSVersionName(t),n={name:s.OS_MAP.MacOS,version:t};return r&&(n.versionName=r),n}},{test:[/(ipod|iphone|ipad)/i],describe:function(e){var t=i.default.getFirstMatch(/os (\d+([_\s]\d+)*) like mac os x/i,e).replace(/[_\s]/g,".");return{name:s.OS_MAP.iOS,version:t}}},{test:function(e){var t=!e.test(/like android/i),r=e.test(/android/i);return t&&r},describe:function(e){var t=i.default.getFirstMatch(/android[\s/-](\d+(\.\d+)*)/i,e),r=i.default.getAndroidVersionName(t),n={name:s.OS_MAP.Android,version:t};return r&&(n.versionName=r),n}},{test:[/(web|hpw)[o0]s/i],describe:function(e){var t=i.default.getFirstMatch(/(?:web|hpw)[o0]s\/(\d+(\.\d+)*)/i,e),r={name:s.OS_MAP.WebOS};return t&&t.length&&(r.version=t),r}},{test:[/blackberry|\bbb\d+/i,/rim\stablet/i],describe:function(e){var t=i.default.getFirstMatch(/rim\stablet\sos\s(\d+(\.\d+)*)/i,e)||i.default.getFirstMatch(/blackberry\d+\/(\d+([_\s]\d+)*)/i,e)||i.default.getFirstMatch(/\bbb(\d+)/i,e);return{name:s.OS_MAP.BlackBerry,version:t}}},{test:[/bada/i],describe:function(e){var t=i.default.getFirstMatch(/bada\/(\d+(\.\d+)*)/i,e);return{name:s.OS_MAP.Bada,version:t}}},{test:[/tizen/i],describe:function(e){var t=i.default.getFirstMatch(/tizen[/\s](\d+(\.\d+)*)/i,e);return{name:s.OS_MAP.Tizen,version:t}}},{test:[/linux/i],describe:function(){return{name:s.OS_MAP.Linux}}},{test:[/CrOS/],describe:function(){return{name:s.OS_MAP.ChromeOS}}},{test:[/PlayStation 4/],describe:function(e){var t=i.default.getFirstMatch(/PlayStation 4[/\s](\d+(\.\d+)*)/i,e);return{name:s.OS_MAP.PlayStation4,version:t}}}];t.default=a,e.exports=t.default},94:function(e,t,r){"use strict";t.__esModule=!0,t.default=void 0;var n,i=(n=r(17))&&n.__esModule?n:{default:n},s=r(18);var a=[{test:[/googlebot/i],describe:function(){return{type:"bot",vendor:"Google"}}},{test:[/huawei/i],describe:function(e){var t=i.default.getFirstMatch(/(can-l01)/i,e)&&"Nova",r={type:s.PLATFORMS_MAP.mobile,vendor:"Huawei"};return t&&(r.model=t),r}},{test:[/nexus\s*(?:7|8|9|10).*/i],describe:function(){return{type:s.PLATFORMS_MAP.tablet,vendor:"Nexus"}}},{test:[/ipad/i],describe:function(){return{type:s.PLATFORMS_MAP.tablet,vendor:"Apple",model:"iPad"}}},{test:[/Macintosh(.*?) FxiOS(.*?)\//],describe:function(){return{type:s.PLATFORMS_MAP.tablet,vendor:"Apple",model:"iPad"}}},{test:[/kftt build/i],describe:function(){return{type:s.PLATFORMS_MAP.tablet,vendor:"Amazon",model:"Kindle Fire HD 7"}}},{test:[/silk/i],describe:function(){return{type:s.PLATFORMS_MAP.tablet,vendor:"Amazon"}}},{test:[/tablet(?! pc)/i],describe:function(){return{type:s.PLATFORMS_MAP.tablet}}},{test:function(e){var t=e.test(/ipod|iphone/i),r=e.test(/like (ipod|iphone)/i);return t&&!r},describe:function(e){var t=i.default.getFirstMatch(/(ipod|iphone)/i,e);return{type:s.PLATFORMS_MAP.mobile,vendor:"Apple",model:t}}},{test:[/nexus\s*[0-6].*/i,/galaxy nexus/i],describe:function(){return{type:s.PLATFORMS_MAP.mobile,vendor:"Nexus"}}},{test:[/[^-]mobi/i],describe:function(){return{type:s.PLATFORMS_MAP.mobile}}},{test:function(e){return"blackberry"===e.getBrowserName(!0)},describe:function(){return{type:s.PLATFORMS_MAP.mobile,vendor:"BlackBerry"}}},{test:function(e){return"bada"===e.getBrowserName(!0)},describe:function(){return{type:s.PLATFORMS_MAP.mobile}}},{test:function(e){return"windows phone"===e.getBrowserName()},describe:function(){return{type:s.PLATFORMS_MAP.mobile,vendor:"Microsoft"}}},{test:function(e){var t=Number(String(e.getOSVersion()).split(".")[0]);return"android"===e.getOSName(!0)&&t>=3},describe:function(){return{type:s.PLATFORMS_MAP.tablet}}},{test:function(e){return"android"===e.getOSName(!0)},describe:function(){return{type:s.PLATFORMS_MAP.mobile}}},{test:function(e){return"macos"===e.getOSName(!0)},describe:function(){return{type:s.PLATFORMS_MAP.desktop,vendor:"Apple"}}},{test:function(e){return"windows"===e.getOSName(!0)},describe:function(){return{type:s.PLATFORMS_MAP.desktop}}},{test:function(e){return"linux"===e.getOSName(!0)},describe:function(){return{type:s.PLATFORMS_MAP.desktop}}},{test:function(e){return"playstation 4"===e.getOSName(!0)},describe:function(){return{type:s.PLATFORMS_MAP.tv}}},{test:function(e){return"roku"===e.getOSName(!0)},describe:function(){return{type:s.PLATFORMS_MAP.tv}}}];t.default=a,e.exports=t.default},95:function(e,t,r){"use strict";t.__esModule=!0,t.default=void 0;var n,i=(n=r(17))&&n.__esModule?n:{default:n},s=r(18);var a=[{test:function(e){return"microsoft edge"===e.getBrowserName(!0)},describe:function(e){if(/\sedg\//i.test(e))return{name:s.ENGINE_MAP.Blink};var t=i.default.getFirstMatch(/edge\/(\d+(\.?_?\d+)+)/i,e);return{name:s.ENGINE_MAP.EdgeHTML,version:t}}},{test:[/trident/i],describe:function(e){var t={name:s.ENGINE_MAP.Trident},r=i.default.getFirstMatch(/trident\/(\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:function(e){return e.test(/presto/i)},describe:function(e){var t={name:s.ENGINE_MAP.Presto},r=i.default.getFirstMatch(/presto\/(\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:function(e){var t=e.test(/gecko/i),r=e.test(/like gecko/i);return t&&!r},describe:function(e){var t={name:s.ENGINE_MAP.Gecko},r=i.default.getFirstMatch(/gecko\/(\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}},{test:[/(apple)?webkit\/537\.36/i],describe:function(){return{name:s.ENGINE_MAP.Blink}}},{test:[/(apple)?webkit/i],describe:function(e){var t={name:s.ENGINE_MAP.WebKit},r=i.default.getFirstMatch(/webkit\/(\d+(\.?_?\d+)+)/i,e);return r&&(t.version=r),t}}];t.default=a,e.exports=t.default}})})); },{}],8:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.Consumer = void 0; -const Logger_1 = require("./Logger"); -const EnhancedEventEmitter_1 = require("./EnhancedEventEmitter"); -const errors_1 = require("./errors"); -const logger = new Logger_1.Logger('Consumer'); -class Consumer extends EnhancedEventEmitter_1.EnhancedEventEmitter { - /** - * @emits transportclose - * @emits trackended - * @emits @getstats - * @emits @close - * @emits @pause - * @emits @resume - */ - constructor({ id, localId, producerId, rtpReceiver, track, rtpParameters, appData }) { - super(); - // Closed flag. - this._closed = false; - // Observer instance. - this._observer = new EnhancedEventEmitter_1.EnhancedEventEmitter(); - logger.debug('constructor()'); - this._id = id; - this._localId = localId; - this._producerId = producerId; - this._rtpReceiver = rtpReceiver; - this._track = track; - this._rtpParameters = rtpParameters; - this._paused = !track.enabled; - this._appData = appData; - this._onTrackEnded = this._onTrackEnded.bind(this); - this._handleTrack(); - } - /** - * Consumer id. - */ - get id() { - return this._id; - } - /** - * Local id. - */ - get localId() { - return this._localId; - } - /** - * Associated Producer id. - */ - get producerId() { - return this._producerId; - } - /** - * Whether the Consumer is closed. - */ - get closed() { - return this._closed; - } - /** - * Media kind. - */ - get kind() { - return this._track.kind; - } - /** - * Associated RTCRtpReceiver. - */ - get rtpReceiver() { - return this._rtpReceiver; - } - /** - * The associated track. - */ - get track() { - return this._track; - } - /** - * RTP parameters. - */ - get rtpParameters() { - return this._rtpParameters; - } - /** - * Whether the Consumer is paused. - */ - get paused() { - return this._paused; - } - /** - * App custom data. - */ - get appData() { - return this._appData; - } - /** - * Invalid setter. - */ - set appData(appData) { - throw new Error('cannot override appData object'); - } - /** - * Observer. - * - * @emits close - * @emits pause - * @emits resume - * @emits trackended - */ - get observer() { - return this._observer; - } - /** - * Closes the Consumer. - */ - close() { - if (this._closed) - return; - logger.debug('close()'); - this._closed = true; - this._destroyTrack(); - this.emit('@close'); - // Emit observer event. - this._observer.safeEmit('close'); - } - /** - * Transport was closed. - */ - transportClosed() { - if (this._closed) - return; - logger.debug('transportClosed()'); - this._closed = true; - this._destroyTrack(); - this.safeEmit('transportclose'); - // Emit observer event. - this._observer.safeEmit('close'); - } - /** - * Get associated RTCRtpReceiver stats. - */ - async getStats() { - if (this._closed) - throw new errors_1.InvalidStateError('closed'); - return this.safeEmitAsPromise('@getstats'); - } - /** - * Pauses receiving media. - */ - pause() { - logger.debug('pause()'); - if (this._closed) { - logger.error('pause() | Consumer closed'); - return; - } - this._paused = true; - this._track.enabled = false; - this.emit('@pause'); - // Emit observer event. - this._observer.safeEmit('pause'); - } - /** - * Resumes receiving media. - */ - resume() { - logger.debug('resume()'); - if (this._closed) { - logger.error('resume() | Consumer closed'); - return; - } - this._paused = false; - this._track.enabled = true; - this.emit('@resume'); - // Emit observer event. - this._observer.safeEmit('resume'); - } - _onTrackEnded() { - logger.debug('track "ended" event'); - this.safeEmit('trackended'); - // Emit observer event. - this._observer.safeEmit('trackended'); - } - _handleTrack() { - this._track.addEventListener('ended', this._onTrackEnded); - } - _destroyTrack() { - try { - this._track.removeEventListener('ended', this._onTrackEnded); - this._track.stop(); - } - catch (error) { } - } +const debug = require('debug')('h264-profile-level-id'); + +/* eslint-disable no-console */ +debug.log = console.info.bind(console); +/* eslint-enable no-console */ + +const ProfileConstrainedBaseline = 1; +const ProfileBaseline = 2; +const ProfileMain = 3; +const ProfileConstrainedHigh = 4; +const ProfileHigh = 5; + +exports.ProfileConstrainedBaseline = ProfileConstrainedBaseline; +exports.ProfileBaseline = ProfileBaseline; +exports.ProfileMain = ProfileMain; +exports.ProfileConstrainedHigh = ProfileConstrainedHigh; +exports.ProfileHigh = ProfileHigh; + +// All values are equal to ten times the level number, except level 1b which is +// special. +const Level1_b = 0; +const Level1 = 10; +const Level1_1 = 11; +const Level1_2 = 12; +const Level1_3 = 13; +const Level2 = 20; +const Level2_1 = 21; +const Level2_2 = 22; +const Level3 = 30; +const Level3_1 = 31; +const Level3_2 = 32; +const Level4 = 40; +const Level4_1 = 41; +const Level4_2 = 42; +const Level5 = 50; +const Level5_1 = 51; +const Level5_2 = 52; + +exports.Level1_b = Level1_b; +exports.Level1 = Level1; +exports.Level1_1 = Level1_1; +exports.Level1_2 = Level1_2; +exports.Level1_3 = Level1_3; +exports.Level2 = Level2; +exports.Level2_1 = Level2_1; +exports.Level2_2 = Level2_2; +exports.Level3 = Level3; +exports.Level3_1 = Level3_1; +exports.Level3_2 = Level3_2; +exports.Level4 = Level4; +exports.Level4_1 = Level4_1; +exports.Level4_2 = Level4_2; +exports.Level5 = Level5; +exports.Level5_1 = Level5_1; +exports.Level5_2 = Level5_2; + +class ProfileLevelId +{ + constructor(profile, level) + { + this.profile = profile; + this.level = level; + } } -exports.Consumer = Consumer; -},{"./EnhancedEventEmitter":12,"./Logger":13,"./errors":18}],9:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.DataConsumer = void 0; -const Logger_1 = require("./Logger"); -const EnhancedEventEmitter_1 = require("./EnhancedEventEmitter"); -const logger = new Logger_1.Logger('DataConsumer'); -class DataConsumer extends EnhancedEventEmitter_1.EnhancedEventEmitter { - /** - * @emits transportclose - * @emits open - * @emits error - (error: Error) - * @emits close - * @emits message - (message: any) - * @emits @close - */ - constructor({ id, dataProducerId, dataChannel, sctpStreamParameters, appData }) { - super(); - // Closed flag. - this._closed = false; - // Observer instance. - this._observer = new EnhancedEventEmitter_1.EnhancedEventEmitter(); - logger.debug('constructor()'); - this._id = id; - this._dataProducerId = dataProducerId; - this._dataChannel = dataChannel; - this._sctpStreamParameters = sctpStreamParameters; - this._appData = appData; - this._handleDataChannel(); - } - /** - * DataConsumer id. - */ - get id() { - return this._id; - } - /** - * Associated DataProducer id. - */ - get dataProducerId() { - return this._dataProducerId; - } - /** - * Whether the DataConsumer is closed. - */ - get closed() { - return this._closed; - } - /** - * SCTP stream parameters. - */ - get sctpStreamParameters() { - return this._sctpStreamParameters; - } - /** - * DataChannel readyState. - */ - get readyState() { - return this._dataChannel.readyState; - } - /** - * DataChannel label. - */ - get label() { - return this._dataChannel.label; - } - /** - * DataChannel protocol. - */ - get protocol() { - return this._dataChannel.protocol; - } - /** - * DataChannel binaryType. - */ - get binaryType() { - return this._dataChannel.binaryType; - } - /** - * Set DataChannel binaryType. - */ - set binaryType(binaryType) { - this._dataChannel.binaryType = binaryType; - } - /** - * App custom data. - */ - get appData() { - return this._appData; - } - /** - * Invalid setter. - */ - set appData(appData) { - throw new Error('cannot override appData object'); - } - /** - * Observer. - * - * @emits close - */ - get observer() { - return this._observer; - } - /** - * Closes the DataConsumer. - */ - close() { - if (this._closed) - return; - logger.debug('close()'); - this._closed = true; - this._dataChannel.close(); - this.emit('@close'); - // Emit observer event. - this._observer.safeEmit('close'); - } - /** - * Transport was closed. - */ - transportClosed() { - if (this._closed) - return; - logger.debug('transportClosed()'); - this._closed = true; - this._dataChannel.close(); - this.safeEmit('transportclose'); - // Emit observer event. - this._observer.safeEmit('close'); - } - _handleDataChannel() { - this._dataChannel.addEventListener('open', () => { - if (this._closed) - return; - logger.debug('DataChannel "open" event'); - this.safeEmit('open'); - }); - this._dataChannel.addEventListener('error', (event) => { - if (this._closed) - return; - let { error } = event; - if (!error) - error = new Error('unknown DataChannel error'); - if (error.errorDetail === 'sctp-failure') { - logger.error('DataChannel SCTP error [sctpCauseCode:%s]: %s', error.sctpCauseCode, error.message); - } - else { - logger.error('DataChannel "error" event: %o', error); - } - this.safeEmit('error', error); - }); - this._dataChannel.addEventListener('close', () => { - if (this._closed) - return; - logger.warn('DataChannel "close" event'); - this._closed = true; - this.emit('@close'); - this.safeEmit('close'); - }); - this._dataChannel.addEventListener('message', (event) => { - if (this._closed) - return; - this.safeEmit('message', event.data); - }); - } +exports.ProfileLevelId = ProfileLevelId; + +// Default ProfileLevelId. +// +// TODO: The default should really be profile Baseline and level 1 according to +// the spec: https://tools.ietf.org/html/rfc6184#section-8.1. In order to not +// break backwards compatibility with older versions of WebRTC where external +// codecs don't have any parameters, use profile ConstrainedBaseline level 3_1 +// instead. This workaround will only be done in an interim period to allow +// external clients to update their code. +// +// http://crbug/webrtc/6337. +const DefaultProfileLevelId = + new ProfileLevelId(ProfileConstrainedBaseline, Level3_1); + +// For level_idc=11 and profile_idc=0x42, 0x4D, or 0x58, the constraint set3 +// flag specifies if level 1b or level 1.1 is used. +const ConstraintSet3Flag = 0x10; + +// Class for matching bit patterns such as "x1xx0000" where 'x' is allowed to be +// either 0 or 1. +class BitPattern +{ + constructor(str) + { + this._mask = ~byteMaskString('x', str); + this._maskedValue = byteMaskString('1', str); + } + + isMatch(value) + { + return this._maskedValue === (value & this._mask); + } } -exports.DataConsumer = DataConsumer; -},{"./EnhancedEventEmitter":12,"./Logger":13}],10:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.DataProducer = void 0; -const Logger_1 = require("./Logger"); -const EnhancedEventEmitter_1 = require("./EnhancedEventEmitter"); -const errors_1 = require("./errors"); -const logger = new Logger_1.Logger('DataProducer'); -class DataProducer extends EnhancedEventEmitter_1.EnhancedEventEmitter { - /** - * @emits transportclose - * @emits open - * @emits error - (error: Error) - * @emits close - * @emits bufferedamountlow - * @emits @close - */ - constructor({ id, dataChannel, sctpStreamParameters, appData }) { - super(); - // Closed flag. - this._closed = false; - // Observer instance. - this._observer = new EnhancedEventEmitter_1.EnhancedEventEmitter(); - logger.debug('constructor()'); - this._id = id; - this._dataChannel = dataChannel; - this._sctpStreamParameters = sctpStreamParameters; - this._appData = appData; - this._handleDataChannel(); - } - /** - * DataProducer id. - */ - get id() { - return this._id; - } - /** - * Whether the DataProducer is closed. - */ - get closed() { - return this._closed; - } - /** - * SCTP stream parameters. - */ - get sctpStreamParameters() { - return this._sctpStreamParameters; - } - /** - * DataChannel readyState. - */ - get readyState() { - return this._dataChannel.readyState; - } - /** - * DataChannel label. - */ - get label() { - return this._dataChannel.label; - } - /** - * DataChannel protocol. - */ - get protocol() { - return this._dataChannel.protocol; - } - /** - * DataChannel bufferedAmount. - */ - get bufferedAmount() { - return this._dataChannel.bufferedAmount; - } - /** - * DataChannel bufferedAmountLowThreshold. - */ - get bufferedAmountLowThreshold() { - return this._dataChannel.bufferedAmountLowThreshold; - } - /** - * Set DataChannel bufferedAmountLowThreshold. - */ - set bufferedAmountLowThreshold(bufferedAmountLowThreshold) { - this._dataChannel.bufferedAmountLowThreshold = bufferedAmountLowThreshold; - } - /** - * App custom data. - */ - get appData() { - return this._appData; - } - /** - * Invalid setter. - */ - set appData(appData) { - throw new Error('cannot override appData object'); - } - /** - * Observer. - * - * @emits close - */ - get observer() { - return this._observer; - } - /** - * Closes the DataProducer. - */ - close() { - if (this._closed) - return; - logger.debug('close()'); - this._closed = true; - this._dataChannel.close(); - this.emit('@close'); - // Emit observer event. - this._observer.safeEmit('close'); - } - /** - * Transport was closed. - */ - transportClosed() { - if (this._closed) - return; - logger.debug('transportClosed()'); - this._closed = true; - this._dataChannel.close(); - this.safeEmit('transportclose'); - // Emit observer event. - this._observer.safeEmit('close'); - } - /** - * Send a message. - * - * @param {String|Blob|ArrayBuffer|ArrayBufferView} data. - */ - send(data) { - logger.debug('send()'); - if (this._closed) - throw new errors_1.InvalidStateError('closed'); - this._dataChannel.send(data); - } - _handleDataChannel() { - this._dataChannel.addEventListener('open', () => { - if (this._closed) - return; - logger.debug('DataChannel "open" event'); - this.safeEmit('open'); - }); - this._dataChannel.addEventListener('error', (event) => { - if (this._closed) - return; - let { error } = event; - if (!error) - error = new Error('unknown DataChannel error'); - if (error.errorDetail === 'sctp-failure') { - logger.error('DataChannel SCTP error [sctpCauseCode:%s]: %s', error.sctpCauseCode, error.message); - } - else { - logger.error('DataChannel "error" event: %o', error); - } - this.safeEmit('error', error); - }); - this._dataChannel.addEventListener('close', () => { - if (this._closed) - return; - logger.warn('DataChannel "close" event'); - this._closed = true; - this.emit('@close'); - this.safeEmit('close'); - }); - this._dataChannel.addEventListener('message', () => { - if (this._closed) - return; - logger.warn('DataChannel "message" event in a DataProducer, message discarded'); - }); - this._dataChannel.addEventListener('bufferedamountlow', () => { - if (this._closed) - return; - this.safeEmit('bufferedamountlow'); - }); - } +// Class for converting between profile_idc/profile_iop to Profile. +class ProfilePattern +{ + constructor(profile_idc, profile_iop, profile) + { + this.profile_idc = profile_idc; + this.profile_iop = profile_iop; + this.profile = profile; + } } -exports.DataProducer = DataProducer; -},{"./EnhancedEventEmitter":12,"./Logger":13,"./errors":18}],11:[function(require,module,exports){ -"use strict"; -/* global RTCRtpTransceiver */ -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; +// This is from https://tools.ietf.org/html/rfc6184#section-8.1. +const ProfilePatterns = +[ + new ProfilePattern(0x42, new BitPattern('x1xx0000'), ProfileConstrainedBaseline), + new ProfilePattern(0x4D, new BitPattern('1xxx0000'), ProfileConstrainedBaseline), + new ProfilePattern(0x58, new BitPattern('11xx0000'), ProfileConstrainedBaseline), + new ProfilePattern(0x42, new BitPattern('x0xx0000'), ProfileBaseline), + new ProfilePattern(0x58, new BitPattern('10xx0000'), ProfileBaseline), + new ProfilePattern(0x4D, new BitPattern('0x0x0000'), ProfileMain), + new ProfilePattern(0x64, new BitPattern('00000000'), ProfileHigh), + new ProfilePattern(0x64, new BitPattern('00001100'), ProfileConstrainedHigh) +]; + +/** + * Parse profile level id that is represented as a string of 3 hex bytes. + * Nothing will be returned if the string is not a recognized H264 profile + * level id. + * + * @param {String} str - profile-level-id value as a string of 3 hex bytes. + * + * @returns {ProfileLevelId} + */ +exports.parseProfileLevelId = function(str) +{ + // The string should consist of 3 bytes in hexadecimal format. + if (typeof str !== 'string' || str.length !== 6) + return null; + + const profile_level_id_numeric = parseInt(str, 16); + + if (profile_level_id_numeric === 0) + return null; + + // Separate into three bytes. + const level_idc = profile_level_id_numeric & 0xFF; + const profile_iop = (profile_level_id_numeric >> 8) & 0xFF; + const profile_idc = (profile_level_id_numeric >> 16) & 0xFF; + + // Parse level based on level_idc and constraint set 3 flag. + let level; + + switch (level_idc) + { + case Level1_1: + { + level = (profile_iop & ConstraintSet3Flag) !== 0 ? Level1_b : Level1_1; + break; + } + case Level1: + case Level1_2: + case Level1_3: + case Level2: + case Level2_1: + case Level2_2: + case Level3: + case Level3_1: + case Level3_2: + case Level4: + case Level4_1: + case Level4_2: + case Level5: + case Level5_1: + case Level5_2: + { + level = level_idc; + break; + } + // Unrecognized level_idc. + default: + { + debug('parseProfileLevelId() | unrecognized level_idc:%s', level_idc); + + return null; + } + } + + // Parse profile_idc/profile_iop into a Profile enum. + for (const pattern of ProfilePatterns) + { + if ( + profile_idc === pattern.profile_idc && + pattern.profile_iop.isMatch(profile_iop) + ) + { + return new ProfileLevelId(pattern.profile, level); + } + } + + debug('parseProfileLevelId() | unrecognized profile_idc/profile_iop combination'); + + return null; }; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; + +/** + * Returns canonical string representation as three hex bytes of the profile + * level id, or returns nothing for invalid profile level ids. + * + * @param {ProfileLevelId} profile_level_id + * + * @returns {String} + */ +exports.profileLevelIdToString = function(profile_level_id) +{ + // Handle special case level == 1b. + if (profile_level_id.level == Level1_b) + { + switch (profile_level_id.profile) + { + case ProfileConstrainedBaseline: + { + return '42f00b'; + } + case ProfileBaseline: + { + return '42100b'; + } + case ProfileMain: + { + return '4d100b'; + } + // Level 1_b is not allowed for other profiles. + default: + { + debug( + 'profileLevelIdToString() | Level 1_b not is allowed for profile:%s', + profile_level_id.profile); + + return null; + } + } + } + + let profile_idc_iop_string; + + switch (profile_level_id.profile) + { + case ProfileConstrainedBaseline: + { + profile_idc_iop_string = '42e0'; + break; + } + case ProfileBaseline: + { + profile_idc_iop_string = '4200'; + break; + } + case ProfileMain: + { + profile_idc_iop_string = '4d00'; + break; + } + case ProfileConstrainedHigh: + { + profile_idc_iop_string = '640c'; + break; + } + case ProfileHigh: + { + profile_idc_iop_string = '6400'; + break; + } + default: + { + debug( + 'profileLevelIdToString() | unrecognized profile:%s', + profile_level_id.profile); + + return null; + } + } + + let levelStr = (profile_level_id.level).toString(16); + + if (levelStr.length === 1) + levelStr = `0${levelStr}`; + + return `${profile_idc_iop_string}${levelStr}`; }; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.Device = exports.detectDevice = void 0; -const bowser_1 = __importDefault(require("bowser")); -const Logger_1 = require("./Logger"); -const EnhancedEventEmitter_1 = require("./EnhancedEventEmitter"); -const errors_1 = require("./errors"); -const utils = __importStar(require("./utils")); -const ortc = __importStar(require("./ortc")); -const Transport_1 = require("./Transport"); -const Chrome74_1 = require("./handlers/Chrome74"); -const Chrome70_1 = require("./handlers/Chrome70"); -const Chrome67_1 = require("./handlers/Chrome67"); -const Chrome55_1 = require("./handlers/Chrome55"); -const Firefox60_1 = require("./handlers/Firefox60"); -const Safari12_1 = require("./handlers/Safari12"); -const Safari11_1 = require("./handlers/Safari11"); -const Edge11_1 = require("./handlers/Edge11"); -const ReactNative_1 = require("./handlers/ReactNative"); -const logger = new Logger_1.Logger('Device'); -function detectDevice() { - // React-Native. - // NOTE: react-native-webrtc >= 1.75.0 is required. - if (typeof navigator === 'object' && navigator.product === 'ReactNative') { - if (typeof RTCPeerConnection === 'undefined') { - logger.warn('this._detectDevice() | unsupported ReactNative without RTCPeerConnection'); - return undefined; - } - logger.debug('this._detectDevice() | ReactNative handler chosen'); - return 'ReactNative'; - } - // Browser. - else if (typeof navigator === 'object' && typeof navigator.userAgent === 'string') { - const ua = navigator.userAgent; - const browser = bowser_1.default.getParser(ua); - const engine = browser.getEngine(); - // Chrome, Chromium, and Edge. - if (browser.satisfies({ chrome: '>=74', chromium: '>=74', 'microsoft edge': '>=88' })) { - return 'Chrome74'; - } - else if (browser.satisfies({ chrome: '>=70', chromium: '>=70' })) { - return 'Chrome70'; - } - else if (browser.satisfies({ chrome: '>=67', chromium: '>=67' })) { - return 'Chrome67'; - } - else if (browser.satisfies({ chrome: '>=55', chromium: '>=55' })) { - return 'Chrome55'; - } - // Firefox. - else if (browser.satisfies({ firefox: '>=60' })) { - return 'Firefox60'; - } - // Firefox on iOS. - else if (browser.satisfies({ ios: { OS: '>=14.3', firefox: '>=30.0' } })) { - return 'Safari12'; - } - // Safari with Unified-Plan support enabled. - else if (browser.satisfies({ safari: '>=12.0' }) && - typeof RTCRtpTransceiver !== 'undefined' && - RTCRtpTransceiver.prototype.hasOwnProperty('currentDirection')) { - return 'Safari12'; - } - // Safari with Plab-B support. - else if (browser.satisfies({ safari: '>=11' })) { - return 'Safari11'; - } - // Old Edge with ORTC support. - else if (browser.satisfies({ 'microsoft edge': '>=11' }) && - browser.satisfies({ 'microsoft edge': '<=18' })) { - return 'Edge11'; - } - // Best effort for Chromium based browsers. - else if (engine.name && engine.name.toLowerCase() === 'blink') { - const match = ua.match(/(?:(?:Chrome|Chromium))[ /](\w+)/i); - if (match) { - const version = Number(match[1]); - if (version >= 74) { - return 'Chrome74'; - } - else if (version >= 70) { - return 'Chrome70'; - } - else if (version >= 67) { - return 'Chrome67'; - } - else { - return 'Chrome55'; - } - } - else { - return 'Chrome74'; - } - } - // Unsupported browser. - else { - logger.warn('this._detectDevice() | browser not supported [name:%s, version:%s]', browser.getBrowserName(), browser.getBrowserVersion()); - return undefined; - } - } - // Unknown device. - else { - logger.warn('this._detectDevice() | unknown device'); - return undefined; - } + +/** + * Parse profile level id that is represented as a string of 3 hex bytes + * contained in an SDP key-value map. A default profile level id will be + * returned if the profile-level-id key is missing. Nothing will be returned if + * the key is present but the string is invalid. + * + * @param {Object} [params={}] - Codec parameters object. + * + * @returns {ProfileLevelId} + */ +exports.parseSdpProfileLevelId = function(params = {}) +{ + const profile_level_id = params['profile-level-id']; + + return !profile_level_id + ? DefaultProfileLevelId + : exports.parseProfileLevelId(profile_level_id); +}; + +/** + * Returns true if the parameters have the same H264 profile, i.e. the same + * H264 profile (Baseline, High, etc). + * + * @param {Object} [params1={}] - Codec parameters object. + * @param {Object} [params2={}] - Codec parameters object. + * + * @returns {Boolean} + */ +exports.isSameProfile = function(params1 = {}, params2 = {}) +{ + const profile_level_id_1 = exports.parseSdpProfileLevelId(params1); + const profile_level_id_2 = exports.parseSdpProfileLevelId(params2); + + // Compare H264 profiles, but not levels. + return Boolean( + profile_level_id_1 && + profile_level_id_2 && + profile_level_id_1.profile === profile_level_id_2.profile + ); +}; + +/** + * Generate codec parameters that will be used as answer in an SDP negotiation + * based on local supported parameters and remote offered parameters. Both + * local_supported_params and remote_offered_params represent sendrecv media + * descriptions, i.e they are a mix of both encode and decode capabilities. In + * theory, when the profile in local_supported_params represent a strict superset + * of the profile in remote_offered_params, we could limit the profile in the + * answer to the profile in remote_offered_params. + * + * However, to simplify the code, each supported H264 profile should be listed + * explicitly in the list of local supported codecs, even if they are redundant. + * Then each local codec in the list should be tested one at a time against the + * remote codec, and only when the profiles are equal should this function be + * called. Therefore, this function does not need to handle profile intersection, + * and the profile of local_supported_params and remote_offered_params must be + * equal before calling this function. The parameters that are used when + * negotiating are the level part of profile-level-id and level-asymmetry-allowed. + * + * @param {Object} [local_supported_params={}] + * @param {Object} [remote_offered_params={}] + * + * @returns {String} Canonical string representation as three hex bytes of the + * profile level id, or null if no one of the params have profile-level-id. + * + * @throws {TypeError} If Profile mismatch or invalid params. + */ +exports.generateProfileLevelIdForAnswer = function( + local_supported_params = {}, + remote_offered_params = {} +) +{ + // If both local and remote params do not contain profile-level-id, they are + // both using the default profile. In this case, don't return anything. + if ( + !local_supported_params['profile-level-id'] && + !remote_offered_params['profile-level-id'] + ) + { + debug( + 'generateProfileLevelIdForAnswer() | no profile-level-id in local and remote params'); + + return null; + } + + // Parse profile-level-ids. + const local_profile_level_id = + exports.parseSdpProfileLevelId(local_supported_params); + const remote_profile_level_id = + exports.parseSdpProfileLevelId(remote_offered_params); + + // The local and remote codec must have valid and equal H264 Profiles. + if (!local_profile_level_id) + throw new TypeError('invalid local_profile_level_id'); + + if (!remote_profile_level_id) + throw new TypeError('invalid remote_profile_level_id'); + + if (local_profile_level_id.profile !== remote_profile_level_id.profile) + throw new TypeError('H264 Profile mismatch'); + + // Parse level information. + const level_asymmetry_allowed = ( + isLevelAsymmetryAllowed(local_supported_params) && + isLevelAsymmetryAllowed(remote_offered_params) + ); + + const local_level = local_profile_level_id.level; + const remote_level = remote_profile_level_id.level; + const min_level = minLevel(local_level, remote_level); + + // Determine answer level. When level asymmetry is not allowed, level upgrade + // is not allowed, i.e., the level in the answer must be equal to or lower + // than the level in the offer. + const answer_level = level_asymmetry_allowed ? local_level : min_level; + + debug( + 'generateProfileLevelIdForAnswer() | result: [profile:%s, level:%s]', + local_profile_level_id.profile, answer_level); + + // Return the resulting profile-level-id for the answer parameters. + return exports.profileLevelIdToString( + new ProfileLevelId(local_profile_level_id.profile, answer_level)); +}; + +// Convert a string of 8 characters into a byte where the positions containing +// character c will have their bit set. For example, c = 'x', str = "x1xx0000" +// will return 0b10110000. +function byteMaskString(c, str) +{ + return ( + ((str[0] === c) << 7) | ((str[1] === c) << 6) | ((str[2] === c) << 5) | + ((str[3] === c) << 4) | ((str[4] === c) << 3) | ((str[5] === c) << 2) | + ((str[6] === c) << 1) | ((str[7] === c) << 0) + ); } -exports.detectDevice = detectDevice; -class Device { - /** - * Create a new Device to connect to mediasoup server. - * - * @throws {UnsupportedError} if device is not supported. - */ - constructor({ handlerName, handlerFactory, Handler } = {}) { - // Loaded flag. - this._loaded = false; - // Observer instance. - this._observer = new EnhancedEventEmitter_1.EnhancedEventEmitter(); - logger.debug('constructor()'); - // Handle deprecated option. - if (Handler) { - logger.warn('constructor() | Handler option is DEPRECATED, use handlerName or handlerFactory instead'); - if (typeof Handler === 'string') - handlerName = Handler; - else - throw new TypeError('non string Handler option no longer supported, use handlerFactory instead'); - } - if (handlerName && handlerFactory) { - throw new TypeError('just one of handlerName or handlerInterface can be given'); - } - if (handlerFactory) { - this._handlerFactory = handlerFactory; - } - else { - if (handlerName) { - logger.debug('constructor() | handler given: %s', handlerName); - } - else { - handlerName = detectDevice(); - if (handlerName) - logger.debug('constructor() | detected handler: %s', handlerName); - else - throw new errors_1.UnsupportedError('device not supported'); - } - switch (handlerName) { - case 'Chrome74': - this._handlerFactory = Chrome74_1.Chrome74.createFactory(); - break; - case 'Chrome70': - this._handlerFactory = Chrome70_1.Chrome70.createFactory(); - break; - case 'Chrome67': - this._handlerFactory = Chrome67_1.Chrome67.createFactory(); - break; - case 'Chrome55': - this._handlerFactory = Chrome55_1.Chrome55.createFactory(); - break; - case 'Firefox60': - this._handlerFactory = Firefox60_1.Firefox60.createFactory(); - break; - case 'Safari12': - this._handlerFactory = Safari12_1.Safari12.createFactory(); - break; - case 'Safari11': - this._handlerFactory = Safari11_1.Safari11.createFactory(); - break; - case 'Edge11': - this._handlerFactory = Edge11_1.Edge11.createFactory(); - break; - case 'ReactNative': - this._handlerFactory = ReactNative_1.ReactNative.createFactory(); - break; - default: - throw new TypeError(`unknown handlerName "${handlerName}"`); - } - } - // Create a temporal handler to get its name. - const handler = this._handlerFactory(); - this._handlerName = handler.name; - handler.close(); - this._extendedRtpCapabilities = undefined; - this._recvRtpCapabilities = undefined; - this._canProduceByKind = - { - audio: false, - video: false - }; - this._sctpCapabilities = undefined; + +// Compare H264 levels and handle the level 1b case. +function isLessLevel(a, b) +{ + if (a === Level1_b) + return b !== Level1 && b !== Level1_b; + + if (b === Level1_b) + return a !== Level1; + + return a < b; +} + +function minLevel(a, b) +{ + return isLessLevel(a, b) ? a : b; +} + +function isLevelAsymmetryAllowed(params = {}) +{ + const level_asymmetry_allowed = params['level-asymmetry-allowed']; + + return ( + level_asymmetry_allowed === 1 || + level_asymmetry_allowed === '1' + ); +} + +},{"debug":9}],9:[function(require,module,exports){ +arguments[4][4][0].apply(exports,arguments) +},{"./common":10,"_process":56,"dup":4}],10:[function(require,module,exports){ +arguments[4][5][0].apply(exports,arguments) +},{"dup":5,"ms":11}],11:[function(require,module,exports){ +arguments[4][6][0].apply(exports,arguments) +},{"dup":6}],12:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Consumer = void 0; +const Logger_1 = require("./Logger"); +const EnhancedEventEmitter_1 = require("./EnhancedEventEmitter"); +const errors_1 = require("./errors"); +const logger = new Logger_1.Logger('Consumer'); +class Consumer extends EnhancedEventEmitter_1.EnhancedEventEmitter { + constructor({ id, localId, producerId, rtpReceiver, track, rtpParameters, appData }) { + super(); + // Closed flag. + this._closed = false; + // Observer instance. + this._observer = new EnhancedEventEmitter_1.EnhancedEventEmitter(); + logger.debug('constructor()'); + this._id = id; + this._localId = localId; + this._producerId = producerId; + this._rtpReceiver = rtpReceiver; + this._track = track; + this._rtpParameters = rtpParameters; + this._paused = !track.enabled; + this._appData = appData || {}; + this.onTrackEnded = this.onTrackEnded.bind(this); + this.handleTrack(); } /** - * The RTC handler name. + * Consumer id. */ - get handlerName() { - return this._handlerName; + get id() { + return this._id; } /** - * Whether the Device is loaded. + * Local id. */ - get loaded() { - return this._loaded; + get localId() { + return this._localId; } /** - * RTP capabilities of the Device for receiving media. - * - * @throws {InvalidStateError} if not loaded. + * Associated Producer id. */ - get rtpCapabilities() { - if (!this._loaded) - throw new errors_1.InvalidStateError('not loaded'); - return this._recvRtpCapabilities; + get producerId() { + return this._producerId; } /** - * SCTP capabilities of the Device. - * - * @throws {InvalidStateError} if not loaded. + * Whether the Consumer is closed. */ - get sctpCapabilities() { - if (!this._loaded) - throw new errors_1.InvalidStateError('not loaded'); - return this._sctpCapabilities; + get closed() { + return this._closed; } /** - * Observer. - * - * @emits newtransport - (transport: Transport) + * Media kind. */ - get observer() { - return this._observer; + get kind() { + return this._track.kind; } /** - * Initialize the Device. + * Associated RTCRtpReceiver. */ - async load({ routerRtpCapabilities }) { - logger.debug('load() [routerRtpCapabilities:%o]', routerRtpCapabilities); - routerRtpCapabilities = utils.clone(routerRtpCapabilities, undefined); - // Temporal handler to get its capabilities. - let handler; - try { - if (this._loaded) - throw new errors_1.InvalidStateError('already loaded'); - // This may throw. - ortc.validateRtpCapabilities(routerRtpCapabilities); - handler = this._handlerFactory(); - const nativeRtpCapabilities = await handler.getNativeRtpCapabilities(); - logger.debug('load() | got native RTP capabilities:%o', nativeRtpCapabilities); - // This may throw. - ortc.validateRtpCapabilities(nativeRtpCapabilities); - // Get extended RTP capabilities. - this._extendedRtpCapabilities = ortc.getExtendedRtpCapabilities(nativeRtpCapabilities, routerRtpCapabilities); - logger.debug('load() | got extended RTP capabilities:%o', this._extendedRtpCapabilities); - // Check whether we can produce audio/video. - this._canProduceByKind.audio = - ortc.canSend('audio', this._extendedRtpCapabilities); - this._canProduceByKind.video = - ortc.canSend('video', this._extendedRtpCapabilities); - // Generate our receiving RTP capabilities for receiving media. - this._recvRtpCapabilities = - ortc.getRecvRtpCapabilities(this._extendedRtpCapabilities); - // This may throw. - ortc.validateRtpCapabilities(this._recvRtpCapabilities); - logger.debug('load() | got receiving RTP capabilities:%o', this._recvRtpCapabilities); - // Generate our SCTP capabilities. - this._sctpCapabilities = await handler.getNativeSctpCapabilities(); - logger.debug('load() | got native SCTP capabilities:%o', this._sctpCapabilities); - // This may throw. - ortc.validateSctpCapabilities(this._sctpCapabilities); - logger.debug('load() succeeded'); - this._loaded = true; - handler.close(); - } - catch (error) { - if (handler) - handler.close(); - throw error; - } + get rtpReceiver() { + return this._rtpReceiver; } /** - * Whether we can produce audio/video. - * - * @throws {InvalidStateError} if not loaded. - * @throws {TypeError} if wrong arguments. + * The associated track. */ - canProduce(kind) { - if (!this._loaded) - throw new errors_1.InvalidStateError('not loaded'); - else if (kind !== 'audio' && kind !== 'video') - throw new TypeError(`invalid kind "${kind}"`); - return this._canProduceByKind[kind]; + get track() { + return this._track; } /** - * Creates a Transport for sending media. - * - * @throws {InvalidStateError} if not loaded. - * @throws {TypeError} if wrong arguments. + * RTP parameters. */ - createSendTransport({ id, iceParameters, iceCandidates, dtlsParameters, sctpParameters, iceServers, iceTransportPolicy, additionalSettings, proprietaryConstraints, appData = {} }) { - logger.debug('createSendTransport()'); - return this._createTransport({ - direction: 'send', - id: id, - iceParameters: iceParameters, - iceCandidates: iceCandidates, - dtlsParameters: dtlsParameters, - sctpParameters: sctpParameters, - iceServers: iceServers, - iceTransportPolicy: iceTransportPolicy, - additionalSettings: additionalSettings, - proprietaryConstraints: proprietaryConstraints, - appData: appData - }); + get rtpParameters() { + return this._rtpParameters; } /** - * Creates a Transport for receiving media. - * - * @throws {InvalidStateError} if not loaded. - * @throws {TypeError} if wrong arguments. + * Whether the Consumer is paused. */ - createRecvTransport({ id, iceParameters, iceCandidates, dtlsParameters, sctpParameters, iceServers, iceTransportPolicy, additionalSettings, proprietaryConstraints, appData = {} }) { - logger.debug('createRecvTransport()'); - return this._createTransport({ - direction: 'recv', - id: id, - iceParameters: iceParameters, - iceCandidates: iceCandidates, - dtlsParameters: dtlsParameters, - sctpParameters: sctpParameters, - iceServers: iceServers, - iceTransportPolicy: iceTransportPolicy, - additionalSettings: additionalSettings, - proprietaryConstraints: proprietaryConstraints, - appData: appData - }); + get paused() { + return this._paused; } - _createTransport({ direction, id, iceParameters, iceCandidates, dtlsParameters, sctpParameters, iceServers, iceTransportPolicy, additionalSettings, proprietaryConstraints, appData = {} }) { - if (!this._loaded) - throw new errors_1.InvalidStateError('not loaded'); - else if (typeof id !== 'string') - throw new TypeError('missing id'); - else if (typeof iceParameters !== 'object') - throw new TypeError('missing iceParameters'); - else if (!Array.isArray(iceCandidates)) - throw new TypeError('missing iceCandidates'); - else if (typeof dtlsParameters !== 'object') - throw new TypeError('missing dtlsParameters'); - else if (sctpParameters && typeof sctpParameters !== 'object') - throw new TypeError('wrong sctpParameters'); - else if (appData && typeof appData !== 'object') - throw new TypeError('if given, appData must be an object'); - // Create a new Transport. - const transport = new Transport_1.Transport({ - direction, - id, - iceParameters, - iceCandidates, - dtlsParameters, - sctpParameters, - iceServers, - iceTransportPolicy, - additionalSettings, - proprietaryConstraints, - appData, - handlerFactory: this._handlerFactory, - extendedRtpCapabilities: this._extendedRtpCapabilities, - canProduceByKind: this._canProduceByKind - }); - // Emit observer event. - this._observer.safeEmit('newtransport', transport); - return transport; + /** + * App custom data. + */ + get appData() { + return this._appData; } -} -exports.Device = Device; - -},{"./EnhancedEventEmitter":12,"./Logger":13,"./Transport":17,"./errors":18,"./handlers/Chrome55":19,"./handlers/Chrome67":20,"./handlers/Chrome70":21,"./handlers/Chrome74":22,"./handlers/Edge11":23,"./handlers/Firefox60":24,"./handlers/ReactNative":26,"./handlers/Safari11":27,"./handlers/Safari12":28,"./ortc":36,"./utils":39,"bowser":3}],12:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.EnhancedEventEmitter = void 0; -const events_1 = require("events"); -const Logger_1 = require("./Logger"); -const logger = new Logger_1.Logger('EnhancedEventEmitter'); -class EnhancedEventEmitter extends events_1.EventEmitter { - constructor() { - super(); - this.setMaxListeners(Infinity); + /** + * Invalid setter. + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + set appData(appData) { + throw new Error('cannot override appData object'); } - safeEmit(event, ...args) { - const numListeners = this.listenerCount(event); - try { - return this.emit(event, ...args); + get observer() { + return this._observer; + } + /** + * Closes the Consumer. + */ + close() { + if (this._closed) { + return; } - catch (error) { - logger.error('safeEmit() | event listener threw an error [event:%s]:%o', event, error); - return Boolean(numListeners); + logger.debug('close()'); + this._closed = true; + this.destroyTrack(); + this.emit('@close'); + // Emit observer event. + this._observer.safeEmit('close'); + } + /** + * Transport was closed. + */ + transportClosed() { + if (this._closed) { + return; } + logger.debug('transportClosed()'); + this._closed = true; + this.destroyTrack(); + this.safeEmit('transportclose'); + // Emit observer event. + this._observer.safeEmit('close'); } - async safeEmitAsPromise(event, ...args) { + /** + * Get associated RTCRtpReceiver stats. + */ + async getStats() { + if (this._closed) { + throw new errors_1.InvalidStateError('closed'); + } return new Promise((resolve, reject) => { - try { - this.emit(event, ...args, resolve, reject); - } - catch (error) { - logger.error('safeEmitAsPromise() | event listener threw an error [event:%s]:%o', event, error); - reject(error); - } + this.safeEmit('@getstats', resolve, reject); }); } -} -exports.EnhancedEventEmitter = EnhancedEventEmitter; - -},{"./Logger":13,"events":47}],13:[function(require,module,exports){ -"use strict"; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.Logger = void 0; -const debug_1 = __importDefault(require("debug")); -const APP_NAME = 'mediasoup-client'; -class Logger { - constructor(prefix) { - if (prefix) { - this._debug = (0, debug_1.default)(`${APP_NAME}:${prefix}`); - this._warn = (0, debug_1.default)(`${APP_NAME}:WARN:${prefix}`); - this._error = (0, debug_1.default)(`${APP_NAME}:ERROR:${prefix}`); + /** + * Pauses receiving media. + */ + pause() { + logger.debug('pause()'); + if (this._closed) { + logger.error('pause() | Consumer closed'); + return; } - else { - this._debug = (0, debug_1.default)(APP_NAME); - this._warn = (0, debug_1.default)(`${APP_NAME}:WARN`); - this._error = (0, debug_1.default)(`${APP_NAME}:ERROR`); + if (this._paused) { + logger.debug('pause() | Consumer is already paused'); + return; } - /* eslint-disable no-console */ - this._debug.log = console.info.bind(console); - this._warn.log = console.warn.bind(console); - this._error.log = console.error.bind(console); - /* eslint-enable no-console */ + this._paused = true; + this._track.enabled = false; + this.emit('@pause'); + // Emit observer event. + this._observer.safeEmit('pause'); } - get debug() { - return this._debug; + /** + * Resumes receiving media. + */ + resume() { + logger.debug('resume()'); + if (this._closed) { + logger.error('resume() | Consumer closed'); + return; + } + if (!this._paused) { + logger.debug('resume() | Consumer is already resumed'); + return; + } + this._paused = false; + this._track.enabled = true; + this.emit('@resume'); + // Emit observer event. + this._observer.safeEmit('resume'); } - get warn() { - return this._warn; + onTrackEnded() { + logger.debug('track "ended" event'); + this.safeEmit('trackended'); + // Emit observer event. + this._observer.safeEmit('trackended'); } - get error() { - return this._error; + handleTrack() { + this._track.addEventListener('ended', this.onTrackEnded); + } + destroyTrack() { + try { + this._track.removeEventListener('ended', this.onTrackEnded); + this._track.stop(); + } + catch (error) { } } } -exports.Logger = Logger; +exports.Consumer = Consumer; -},{"debug":40}],14:[function(require,module,exports){ +},{"./EnhancedEventEmitter":16,"./Logger":17,"./errors":22}],13:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.Producer = void 0; +exports.DataConsumer = void 0; const Logger_1 = require("./Logger"); const EnhancedEventEmitter_1 = require("./EnhancedEventEmitter"); -const errors_1 = require("./errors"); -const logger = new Logger_1.Logger('Producer'); -class Producer extends EnhancedEventEmitter_1.EnhancedEventEmitter { - /** - * @emits transportclose - * @emits trackended - * @emits @replacetrack - (track: MediaStreamTrack | null) - * @emits @setmaxspatiallayer - (spatialLayer: string) - * @emits @setrtpencodingparameters - (params: any) - * @emits @getstats - * @emits @close - */ - constructor({ id, localId, rtpSender, track, rtpParameters, stopTracks, disableTrackOnPause, zeroRtpOnPause, appData }) { +const logger = new Logger_1.Logger('DataConsumer'); +class DataConsumer extends EnhancedEventEmitter_1.EnhancedEventEmitter { + constructor({ id, dataProducerId, dataChannel, sctpStreamParameters, appData }) { super(); // Closed flag. this._closed = false; @@ -2360,77 +1611,65 @@ class Producer extends EnhancedEventEmitter_1.EnhancedEventEmitter { this._observer = new EnhancedEventEmitter_1.EnhancedEventEmitter(); logger.debug('constructor()'); this._id = id; - this._localId = localId; - this._rtpSender = rtpSender; - this._track = track; - this._kind = track.kind; - this._rtpParameters = rtpParameters; - this._paused = disableTrackOnPause ? !track.enabled : false; - this._maxSpatialLayer = undefined; - this._stopTracks = stopTracks; - this._disableTrackOnPause = disableTrackOnPause; - this._zeroRtpOnPause = zeroRtpOnPause; - this._appData = appData; - this._onTrackEnded = this._onTrackEnded.bind(this); - // NOTE: Minor issue. If zeroRtpOnPause is true, we cannot emit the - // '@replacetrack' event here, so RTCRtpSender.track won't be null. - this._handleTrack(); + this._dataProducerId = dataProducerId; + this._dataChannel = dataChannel; + this._sctpStreamParameters = sctpStreamParameters; + this._appData = appData || {}; + this.handleDataChannel(); } /** - * Producer id. + * DataConsumer id. */ get id() { return this._id; } /** - * Local id. + * Associated DataProducer id. */ - get localId() { - return this._localId; + get dataProducerId() { + return this._dataProducerId; } /** - * Whether the Producer is closed. + * Whether the DataConsumer is closed. */ get closed() { return this._closed; } /** - * Media kind. + * SCTP stream parameters. */ - get kind() { - return this._kind; + get sctpStreamParameters() { + return this._sctpStreamParameters; } /** - * Associated RTCRtpSender. + * DataChannel readyState. */ - get rtpSender() { - return this._rtpSender; + get readyState() { + return this._dataChannel.readyState; } /** - * The associated track. + * DataChannel label. */ - get track() { - return this._track; + get label() { + return this._dataChannel.label; } /** - * RTP parameters. + * DataChannel protocol. */ - get rtpParameters() { - return this._rtpParameters; + get protocol() { + return this._dataChannel.protocol; } /** - * Whether the Producer is paused. + * DataChannel binaryType. */ - get paused() { - return this._paused; + get binaryType() { + return this._dataChannel.binaryType; } /** - * Max spatial layer. - * - * @type {Number | undefined} + * Set DataChannel binaryType. */ - get maxSpatialLayer() { - return this._maxSpatialLayer; + set binaryType(binaryType) { + this._dataChannel.binaryType = binaryType; } /** * App custom data. @@ -2441,29 +1680,23 @@ class Producer extends EnhancedEventEmitter_1.EnhancedEventEmitter { /** * Invalid setter. */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars set appData(appData) { throw new Error('cannot override appData object'); } - /** - * Observer. - * - * @emits close - * @emits pause - * @emits resume - * @emits trackended - */ get observer() { return this._observer; } /** - * Closes the Producer. + * Closes the DataConsumer. */ close() { - if (this._closed) + if (this._closed) { return; + } logger.debug('close()'); this._closed = true; - this._destroyTrack(); + this._dataChannel.close(); this.emit('@close'); // Emit observer event. this._observer.safeEmit('close'); @@ -2472,287 +1705,136 @@ class Producer extends EnhancedEventEmitter_1.EnhancedEventEmitter { * Transport was closed. */ transportClosed() { - if (this._closed) + if (this._closed) { return; + } logger.debug('transportClosed()'); this._closed = true; - this._destroyTrack(); + this._dataChannel.close(); this.safeEmit('transportclose'); // Emit observer event. this._observer.safeEmit('close'); } - /** - * Get associated RTCRtpSender stats. - */ - async getStats() { - if (this._closed) - throw new errors_1.InvalidStateError('closed'); - return this.safeEmitAsPromise('@getstats'); - } - /** - * Pauses sending media. - */ - pause() { - logger.debug('pause()'); - if (this._closed) { - logger.error('pause() | Producer closed'); - return; - } - this._paused = true; - if (this._track && this._disableTrackOnPause) { - this._track.enabled = false; - } - if (this._zeroRtpOnPause) { - this.safeEmitAsPromise('@replacetrack', null) - .catch(() => { }); - } - // Emit observer event. - this._observer.safeEmit('pause'); - } - /** - * Resumes sending media. - */ - resume() { - logger.debug('resume()'); - if (this._closed) { - logger.error('resume() | Producer closed'); - return; - } - this._paused = false; - if (this._track && this._disableTrackOnPause) { - this._track.enabled = true; - } - if (this._zeroRtpOnPause) { - this.safeEmitAsPromise('@replacetrack', this._track) - .catch(() => { }); - } - // Emit observer event. - this._observer.safeEmit('resume'); - } - /** - * Replaces the current track with a new one or null. - */ - async replaceTrack({ track }) { - logger.debug('replaceTrack() [track:%o]', track); - if (this._closed) { - // This must be done here. Otherwise there is no chance to stop the given - // track. - if (track && this._stopTracks) { - try { - track.stop(); - } - catch (error) { } + handleDataChannel() { + this._dataChannel.addEventListener('open', () => { + if (this._closed) { + return; } - throw new errors_1.InvalidStateError('closed'); - } - else if (track && track.readyState === 'ended') { - throw new errors_1.InvalidStateError('track ended'); - } - // Do nothing if this is the same track as the current handled one. - if (track === this._track) { - logger.debug('replaceTrack() | same track, ignored'); - return; - } - if (!this._zeroRtpOnPause || !this._paused) { - await this.safeEmitAsPromise('@replacetrack', track); - } - // Destroy the previous track. - this._destroyTrack(); - // Set the new track. - this._track = track; - // If this Producer was paused/resumed and the state of the new - // track does not match, fix it. - if (this._track && this._disableTrackOnPause) { - if (!this._paused) - this._track.enabled = true; - else if (this._paused) - this._track.enabled = false; - } - // Handle the effective track. - this._handleTrack(); - } - /** - * Sets the video max spatial layer to be sent. - */ - async setMaxSpatialLayer(spatialLayer) { - if (this._closed) - throw new errors_1.InvalidStateError('closed'); - else if (this._kind !== 'video') - throw new errors_1.UnsupportedError('not a video Producer'); - else if (typeof spatialLayer !== 'number') - throw new TypeError('invalid spatialLayer'); - if (spatialLayer === this._maxSpatialLayer) - return; - await this.safeEmitAsPromise('@setmaxspatiallayer', spatialLayer); - this._maxSpatialLayer = spatialLayer; - } - /** - * Sets the DSCP value. - */ - async setRtpEncodingParameters(params) { - if (this._closed) - throw new errors_1.InvalidStateError('closed'); - else if (typeof params !== 'object') - throw new TypeError('invalid params'); - await this.safeEmitAsPromise('@setrtpencodingparameters', params); - } - _onTrackEnded() { - logger.debug('track "ended" event'); - this.safeEmit('trackended'); - // Emit observer event. - this._observer.safeEmit('trackended'); - } - _handleTrack() { - if (!this._track) - return; - this._track.addEventListener('ended', this._onTrackEnded); - } - _destroyTrack() { - if (!this._track) - return; - try { - this._track.removeEventListener('ended', this._onTrackEnded); - // Just stop the track unless the app set stopTracks: false. - if (this._stopTracks) - this._track.stop(); - } - catch (error) { } + logger.debug('DataChannel "open" event'); + this.safeEmit('open'); + }); + this._dataChannel.addEventListener('error', (event) => { + if (this._closed) { + return; + } + let { error } = event; + if (!error) { + error = new Error('unknown DataChannel error'); + } + if (error.errorDetail === 'sctp-failure') { + logger.error('DataChannel SCTP error [sctpCauseCode:%s]: %s', error.sctpCauseCode, error.message); + } + else { + logger.error('DataChannel "error" event: %o', error); + } + this.safeEmit('error', error); + }); + this._dataChannel.addEventListener('close', () => { + if (this._closed) { + return; + } + logger.warn('DataChannel "close" event'); + this._closed = true; + this.emit('@close'); + this.safeEmit('close'); + // Emit observer event. + this._observer.safeEmit('close'); + }); + this._dataChannel.addEventListener('message', (event) => { + if (this._closed) { + return; + } + this.safeEmit('message', event.data); + }); } } -exports.Producer = Producer; - -},{"./EnhancedEventEmitter":12,"./Logger":13,"./errors":18}],15:[function(require,module,exports){ -"use strict"; -/** - * The RTP capabilities define what mediasoup or an endpoint can receive at - * media level. - */ -Object.defineProperty(exports, "__esModule", { value: true }); - -},{}],16:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); +exports.DataConsumer = DataConsumer; -},{}],17:[function(require,module,exports){ +},{"./EnhancedEventEmitter":16,"./Logger":17}],14:[function(require,module,exports){ "use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; Object.defineProperty(exports, "__esModule", { value: true }); -exports.Transport = void 0; -const awaitqueue_1 = require("awaitqueue"); +exports.DataProducer = void 0; const Logger_1 = require("./Logger"); const EnhancedEventEmitter_1 = require("./EnhancedEventEmitter"); const errors_1 = require("./errors"); -const utils = __importStar(require("./utils")); -const ortc = __importStar(require("./ortc")); -const Producer_1 = require("./Producer"); -const Consumer_1 = require("./Consumer"); -const DataProducer_1 = require("./DataProducer"); -const DataConsumer_1 = require("./DataConsumer"); -const logger = new Logger_1.Logger('Transport'); -class Transport extends EnhancedEventEmitter_1.EnhancedEventEmitter { - /** - * @emits connect - (transportLocalParameters: any, callback: Function, errback: Function) - * @emits connectionstatechange - (connectionState: ConnectionState) - * @emits produce - (producerLocalParameters: any, callback: Function, errback: Function) - * @emits producedata - (dataProducerLocalParameters: any, callback: Function, errback: Function) - */ - constructor({ direction, id, iceParameters, iceCandidates, dtlsParameters, sctpParameters, iceServers, iceTransportPolicy, additionalSettings, proprietaryConstraints, appData, handlerFactory, extendedRtpCapabilities, canProduceByKind }) { +const logger = new Logger_1.Logger('DataProducer'); +class DataProducer extends EnhancedEventEmitter_1.EnhancedEventEmitter { + constructor({ id, dataChannel, sctpStreamParameters, appData }) { super(); // Closed flag. this._closed = false; - // Transport connection state. - this._connectionState = 'new'; - // Map of Producers indexed by id. - this._producers = new Map(); - // Map of Consumers indexed by id. - this._consumers = new Map(); - // Map of DataProducers indexed by id. - this._dataProducers = new Map(); - // Map of DataConsumers indexed by id. - this._dataConsumers = new Map(); - // Whether the Consumer for RTP probation has been created. - this._probatorConsumerCreated = false; - // AwaitQueue instance to make async tasks happen sequentially. - this._awaitQueue = new awaitqueue_1.AwaitQueue({ ClosedErrorClass: errors_1.InvalidStateError }); // Observer instance. this._observer = new EnhancedEventEmitter_1.EnhancedEventEmitter(); - logger.debug('constructor() [id:%s, direction:%s]', id, direction); + logger.debug('constructor()'); this._id = id; - this._direction = direction; - this._extendedRtpCapabilities = extendedRtpCapabilities; - this._canProduceByKind = canProduceByKind; - this._maxSctpMessageSize = - sctpParameters ? sctpParameters.maxMessageSize : null; - // Clone and sanitize additionalSettings. - additionalSettings = utils.clone(additionalSettings, {}); - delete additionalSettings.iceServers; - delete additionalSettings.iceTransportPolicy; - delete additionalSettings.bundlePolicy; - delete additionalSettings.rtcpMuxPolicy; - delete additionalSettings.sdpSemantics; - this._handler = handlerFactory(); - this._handler.run({ - direction, - iceParameters, - iceCandidates, - dtlsParameters, - sctpParameters, - iceServers, - iceTransportPolicy, - additionalSettings, - proprietaryConstraints, - extendedRtpCapabilities - }); - this._appData = appData; - this._handleHandler(); + this._dataChannel = dataChannel; + this._sctpStreamParameters = sctpStreamParameters; + this._appData = appData || {}; + this.handleDataChannel(); } /** - * Transport id. + * DataProducer id. */ get id() { return this._id; } /** - * Whether the Transport is closed. + * Whether the DataProducer is closed. */ get closed() { return this._closed; } /** - * Transport direction. + * SCTP stream parameters. */ - get direction() { - return this._direction; + get sctpStreamParameters() { + return this._sctpStreamParameters; } /** - * RTC handler instance. + * DataChannel readyState. */ - get handler() { - return this._handler; + get readyState() { + return this._dataChannel.readyState; } /** - * Connection state. + * DataChannel label. */ - get connectionState() { - return this._connectionState; + get label() { + return this._dataChannel.label; + } + /** + * DataChannel protocol. + */ + get protocol() { + return this._dataChannel.protocol; + } + /** + * DataChannel bufferedAmount. + */ + get bufferedAmount() { + return this._dataChannel.bufferedAmount; + } + /** + * DataChannel bufferedAmountLowThreshold. + */ + get bufferedAmountLowThreshold() { + return this._dataChannel.bufferedAmountLowThreshold; + } + /** + * Set DataChannel bufferedAmountLowThreshold. + */ + set bufferedAmountLowThreshold(bufferedAmountLowThreshold) { + this._dataChannel.bufferedAmountLowThreshold = bufferedAmountLowThreshold; } /** * App custom data. @@ -2763,469 +1845,113 @@ class Transport extends EnhancedEventEmitter_1.EnhancedEventEmitter { /** * Invalid setter. */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars set appData(appData) { throw new Error('cannot override appData object'); } - /** - * Observer. - * - * @emits close - * @emits newproducer - (producer: Producer) - * @emits newconsumer - (producer: Producer) - * @emits newdataproducer - (dataProducer: DataProducer) - * @emits newdataconsumer - (dataProducer: DataProducer) - */ get observer() { return this._observer; } /** - * Close the Transport. + * Closes the DataProducer. */ close() { - if (this._closed) + if (this._closed) { return; + } logger.debug('close()'); this._closed = true; - // Close the AwaitQueue. - this._awaitQueue.close(); - // Close the handler. - this._handler.close(); - // Close all Producers. - for (const producer of this._producers.values()) { - producer.transportClosed(); - } - this._producers.clear(); - // Close all Consumers. - for (const consumer of this._consumers.values()) { - consumer.transportClosed(); - } - this._consumers.clear(); - // Close all DataProducers. - for (const dataProducer of this._dataProducers.values()) { - dataProducer.transportClosed(); - } - this._dataProducers.clear(); - // Close all DataConsumers. - for (const dataConsumer of this._dataConsumers.values()) { - dataConsumer.transportClosed(); - } - this._dataConsumers.clear(); + this._dataChannel.close(); + this.emit('@close'); // Emit observer event. this._observer.safeEmit('close'); } /** - * Get associated Transport (RTCPeerConnection) stats. - * - * @returns {RTCStatsReport} - */ - async getStats() { - if (this._closed) - throw new errors_1.InvalidStateError('closed'); - return this._handler.getTransportStats(); - } - /** - * Restart ICE connection. + * Transport was closed. */ - async restartIce({ iceParameters }) { - logger.debug('restartIce()'); - if (this._closed) - throw new errors_1.InvalidStateError('closed'); - else if (!iceParameters) - throw new TypeError('missing iceParameters'); - // Enqueue command. - return this._awaitQueue.push(async () => this._handler.restartIce(iceParameters), 'transport.restartIce()'); + transportClosed() { + if (this._closed) { + return; + } + logger.debug('transportClosed()'); + this._closed = true; + this._dataChannel.close(); + this.safeEmit('transportclose'); + // Emit observer event. + this._observer.safeEmit('close'); } /** - * Update ICE servers. + * Send a message. + * + * @param {String|Blob|ArrayBuffer|ArrayBufferView} data. */ - async updateIceServers({ iceServers } = {}) { - logger.debug('updateIceServers()'); - if (this._closed) + send(data) { + logger.debug('send()'); + if (this._closed) { throw new errors_1.InvalidStateError('closed'); - else if (!Array.isArray(iceServers)) - throw new TypeError('missing iceServers'); - // Enqueue command. - return this._awaitQueue.push(async () => this._handler.updateIceServers(iceServers), 'transport.updateIceServers()'); + } + this._dataChannel.send(data); } - /** - * Create a Producer. - */ - async produce({ track, encodings, codecOptions, codec, stopTracks = true, disableTrackOnPause = true, zeroRtpOnPause = false, appData = {} } = {}) { - logger.debug('produce() [track:%o]', track); - if (!track) - throw new TypeError('missing track'); - else if (this._direction !== 'send') - throw new errors_1.UnsupportedError('not a sending Transport'); - else if (!this._canProduceByKind[track.kind]) - throw new errors_1.UnsupportedError(`cannot produce ${track.kind}`); - else if (track.readyState === 'ended') - throw new errors_1.InvalidStateError('track ended'); - else if (this.listenerCount('connect') === 0 && this._connectionState === 'new') - throw new TypeError('no "connect" listener set into this transport'); - else if (this.listenerCount('produce') === 0) - throw new TypeError('no "produce" listener set into this transport'); - else if (appData && typeof appData !== 'object') - throw new TypeError('if given, appData must be an object'); - // Enqueue command. - return this._awaitQueue.push(async () => { - let normalizedEncodings; - if (encodings && !Array.isArray(encodings)) { - throw TypeError('encodings must be an array'); - } - else if (encodings && encodings.length === 0) { - normalizedEncodings = undefined; + handleDataChannel() { + this._dataChannel.addEventListener('open', () => { + if (this._closed) { + return; } - else if (encodings) { - normalizedEncodings = encodings - .map((encoding) => { - const normalizedEncoding = { active: true }; - if (encoding.active === false) - normalizedEncoding.active = false; - if (typeof encoding.dtx === 'boolean') - normalizedEncoding.dtx = encoding.dtx; - if (typeof encoding.scalabilityMode === 'string') - normalizedEncoding.scalabilityMode = encoding.scalabilityMode; - if (typeof encoding.scaleResolutionDownBy === 'number') - normalizedEncoding.scaleResolutionDownBy = encoding.scaleResolutionDownBy; - if (typeof encoding.maxBitrate === 'number') - normalizedEncoding.maxBitrate = encoding.maxBitrate; - if (typeof encoding.maxFramerate === 'number') - normalizedEncoding.maxFramerate = encoding.maxFramerate; - if (typeof encoding.adaptivePtime === 'boolean') - normalizedEncoding.adaptivePtime = encoding.adaptivePtime; - if (typeof encoding.priority === 'string') - normalizedEncoding.priority = encoding.priority; - if (typeof encoding.networkPriority === 'string') - normalizedEncoding.networkPriority = encoding.networkPriority; - return normalizedEncoding; - }); + logger.debug('DataChannel "open" event'); + this.safeEmit('open'); + }); + this._dataChannel.addEventListener('error', (event) => { + if (this._closed) { + return; } - const { localId, rtpParameters, rtpSender } = await this._handler.send({ - track, - encodings: normalizedEncodings, - codecOptions, - codec - }); - try { - // This will fill rtpParameters's missing fields with default values. - ortc.validateRtpParameters(rtpParameters); - const { id } = await this.safeEmitAsPromise('produce', { - kind: track.kind, - rtpParameters, - appData - }); - const producer = new Producer_1.Producer({ - id, - localId, - rtpSender, - track, - rtpParameters, - stopTracks, - disableTrackOnPause, - zeroRtpOnPause, - appData - }); - this._producers.set(producer.id, producer); - this._handleProducer(producer); - // Emit observer event. - this._observer.safeEmit('newproducer', producer); - return producer; + let { error } = event; + if (!error) { + error = new Error('unknown DataChannel error'); } - catch (error) { - this._handler.stopSending(localId) - .catch(() => { }); - throw error; + if (error.errorDetail === 'sctp-failure') { + logger.error('DataChannel SCTP error [sctpCauseCode:%s]: %s', error.sctpCauseCode, error.message); } - }, 'transport.produce()') - // This catch is needed to stop the given track if the command above - // failed due to closed Transport. - .catch((error) => { - if (stopTracks) { - try { - track.stop(); - } - catch (error2) { } + else { + logger.error('DataChannel "error" event: %o', error); } - throw error; + this.safeEmit('error', error); }); - } - /** - * Create a Consumer to consume a remote Producer. - */ - async consume({ id, producerId, kind, rtpParameters, appData = {} }) { - logger.debug('consume()'); - rtpParameters = utils.clone(rtpParameters, undefined); - if (this._closed) - throw new errors_1.InvalidStateError('closed'); - else if (this._direction !== 'recv') - throw new errors_1.UnsupportedError('not a receiving Transport'); - else if (typeof id !== 'string') - throw new TypeError('missing id'); - else if (typeof producerId !== 'string') - throw new TypeError('missing producerId'); - else if (kind !== 'audio' && kind !== 'video') - throw new TypeError(`invalid kind '${kind}'`); - else if (this.listenerCount('connect') === 0 && this._connectionState === 'new') - throw new TypeError('no "connect" listener set into this transport'); - else if (appData && typeof appData !== 'object') - throw new TypeError('if given, appData must be an object'); - // Enqueue command. - return this._awaitQueue.push(async () => { - // Ensure the device can consume it. - const canConsume = ortc.canReceive(rtpParameters, this._extendedRtpCapabilities); - if (!canConsume) - throw new errors_1.UnsupportedError('cannot consume this Producer'); - const { localId, rtpReceiver, track } = await this._handler.receive({ trackId: id, kind, rtpParameters }); - const consumer = new Consumer_1.Consumer({ - id, - localId, - producerId, - rtpReceiver, - track, - rtpParameters, - appData - }); - this._consumers.set(consumer.id, consumer); - this._handleConsumer(consumer); - // If this is the first video Consumer and the Consumer for RTP probation - // has not yet been created, create it now. - if (!this._probatorConsumerCreated && kind === 'video') { - try { - const probatorRtpParameters = ortc.generateProbatorRtpParameters(consumer.rtpParameters); - await this._handler.receive({ - trackId: 'probator', - kind: 'video', - rtpParameters: probatorRtpParameters - }); - logger.debug('consume() | Consumer for RTP probation created'); - this._probatorConsumerCreated = true; - } - catch (error) { - logger.error('consume() | failed to create Consumer for RTP probation:%o', error); - } + this._dataChannel.addEventListener('close', () => { + if (this._closed) { + return; } + logger.warn('DataChannel "close" event'); + this._closed = true; + this.emit('@close'); + this.safeEmit('close'); // Emit observer event. - this._observer.safeEmit('newconsumer', consumer); - return consumer; - }, 'transport.consume()'); + this._observer.safeEmit('close'); + }); + this._dataChannel.addEventListener('message', () => { + if (this._closed) { + return; + } + logger.warn('DataChannel "message" event in a DataProducer, message discarded'); + }); + this._dataChannel.addEventListener('bufferedamountlow', () => { + if (this._closed) { + return; + } + this.safeEmit('bufferedamountlow'); + }); } - /** - * Create a DataProducer - */ - async produceData({ ordered = true, maxPacketLifeTime, maxRetransmits, label = '', protocol = '', appData = {} } = {}) { - logger.debug('produceData()'); - if (this._direction !== 'send') - throw new errors_1.UnsupportedError('not a sending Transport'); - else if (!this._maxSctpMessageSize) - throw new errors_1.UnsupportedError('SCTP not enabled by remote Transport'); - else if (this.listenerCount('connect') === 0 && this._connectionState === 'new') - throw new TypeError('no "connect" listener set into this transport'); - else if (this.listenerCount('producedata') === 0) - throw new TypeError('no "producedata" listener set into this transport'); - else if (appData && typeof appData !== 'object') - throw new TypeError('if given, appData must be an object'); - if (maxPacketLifeTime || maxRetransmits) - ordered = false; - // Enqueue command. - return this._awaitQueue.push(async () => { - const { dataChannel, sctpStreamParameters } = await this._handler.sendDataChannel({ - ordered, - maxPacketLifeTime, - maxRetransmits, - label, - protocol - }); - // This will fill sctpStreamParameters's missing fields with default values. - ortc.validateSctpStreamParameters(sctpStreamParameters); - const { id } = await this.safeEmitAsPromise('producedata', { - sctpStreamParameters, - label, - protocol, - appData - }); - const dataProducer = new DataProducer_1.DataProducer({ id, dataChannel, sctpStreamParameters, appData }); - this._dataProducers.set(dataProducer.id, dataProducer); - this._handleDataProducer(dataProducer); - // Emit observer event. - this._observer.safeEmit('newdataproducer', dataProducer); - return dataProducer; - }, 'transport.produceData()'); +} +exports.DataProducer = DataProducer; + +},{"./EnhancedEventEmitter":16,"./Logger":17,"./errors":22}],15:[function(require,module,exports){ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; } - /** - * Create a DataConsumer - */ - async consumeData({ id, dataProducerId, sctpStreamParameters, label = '', protocol = '', appData = {} }) { - logger.debug('consumeData()'); - sctpStreamParameters = utils.clone(sctpStreamParameters, undefined); - if (this._closed) - throw new errors_1.InvalidStateError('closed'); - else if (this._direction !== 'recv') - throw new errors_1.UnsupportedError('not a receiving Transport'); - else if (!this._maxSctpMessageSize) - throw new errors_1.UnsupportedError('SCTP not enabled by remote Transport'); - else if (typeof id !== 'string') - throw new TypeError('missing id'); - else if (typeof dataProducerId !== 'string') - throw new TypeError('missing dataProducerId'); - else if (this.listenerCount('connect') === 0 && this._connectionState === 'new') - throw new TypeError('no "connect" listener set into this transport'); - else if (appData && typeof appData !== 'object') - throw new TypeError('if given, appData must be an object'); - // This may throw. - ortc.validateSctpStreamParameters(sctpStreamParameters); - // Enqueue command. - return this._awaitQueue.push(async () => { - const { dataChannel } = await this._handler.receiveDataChannel({ - sctpStreamParameters, - label, - protocol - }); - const dataConsumer = new DataConsumer_1.DataConsumer({ - id, - dataProducerId, - dataChannel, - sctpStreamParameters, - appData - }); - this._dataConsumers.set(dataConsumer.id, dataConsumer); - this._handleDataConsumer(dataConsumer); - // Emit observer event. - this._observer.safeEmit('newdataconsumer', dataConsumer); - return dataConsumer; - }, 'transport.consumeData()'); - } - _handleHandler() { - const handler = this._handler; - handler.on('@connect', ({ dtlsParameters }, callback, errback) => { - if (this._closed) { - errback(new errors_1.InvalidStateError('closed')); - return; - } - this.safeEmit('connect', { dtlsParameters }, callback, errback); - }); - handler.on('@connectionstatechange', (connectionState) => { - if (connectionState === this._connectionState) - return; - logger.debug('connection state changed to %s', connectionState); - this._connectionState = connectionState; - if (!this._closed) - this.safeEmit('connectionstatechange', connectionState); - }); - } - _handleProducer(producer) { - producer.on('@close', () => { - this._producers.delete(producer.id); - if (this._closed) - return; - this._awaitQueue.push(async () => this._handler.stopSending(producer.localId), 'producer @close event') - .catch((error) => logger.warn('producer.close() failed:%o', error)); - }); - producer.on('@replacetrack', (track, callback, errback) => { - this._awaitQueue.push(async () => this._handler.replaceTrack(producer.localId, track), 'producer @replacetrack event') - .then(callback) - .catch(errback); - }); - producer.on('@setmaxspatiallayer', (spatialLayer, callback, errback) => { - this._awaitQueue.push(async () => (this._handler.setMaxSpatialLayer(producer.localId, spatialLayer)), 'producer @setmaxspatiallayer event') - .then(callback) - .catch(errback); - }); - producer.on('@setrtpencodingparameters', (params, callback, errback) => { - this._awaitQueue.push(async () => (this._handler.setRtpEncodingParameters(producer.localId, params)), 'producer @setrtpencodingparameters event') - .then(callback) - .catch(errback); - }); - producer.on('@getstats', (callback, errback) => { - if (this._closed) - return errback(new errors_1.InvalidStateError('closed')); - this._handler.getSenderStats(producer.localId) - .then(callback) - .catch(errback); - }); - } - _handleConsumer(consumer) { - consumer.on('@close', () => { - this._consumers.delete(consumer.id); - if (this._closed) - return; - this._awaitQueue.push(async () => this._handler.stopReceiving(consumer.localId), 'consumer @close event') - .catch(() => { }); - }); - consumer.on('@pause', () => { - this._awaitQueue.push(async () => this._handler.pauseReceiving(consumer.localId), 'consumer @pause event') - .catch(() => { }); - }); - consumer.on('@resume', () => { - this._awaitQueue.push(async () => this._handler.resumeReceiving(consumer.localId), 'consumer @resume event') - .catch(() => { }); - }); - consumer.on('@getstats', (callback, errback) => { - if (this._closed) - return errback(new errors_1.InvalidStateError('closed')); - this._handler.getReceiverStats(consumer.localId) - .then(callback) - .catch(errback); - }); - } - _handleDataProducer(dataProducer) { - dataProducer.on('@close', () => { - this._dataProducers.delete(dataProducer.id); - }); - } - _handleDataConsumer(dataConsumer) { - dataConsumer.on('@close', () => { - this._dataConsumers.delete(dataConsumer.id); - }); - } -} -exports.Transport = Transport; - -},{"./Consumer":8,"./DataConsumer":9,"./DataProducer":10,"./EnhancedEventEmitter":12,"./Logger":13,"./Producer":14,"./errors":18,"./ortc":36,"./utils":39,"awaitqueue":2}],18:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.InvalidStateError = exports.UnsupportedError = void 0; -/** - * Error indicating not support for something. - */ -class UnsupportedError extends Error { - constructor(message) { - super(message); - this.name = 'UnsupportedError'; - if (Error.hasOwnProperty('captureStackTrace')) // Just in V8. - { - // @ts-ignore - Error.captureStackTrace(this, UnsupportedError); - } - else { - this.stack = (new Error(message)).stack; - } - } -} -exports.UnsupportedError = UnsupportedError; -/** - * Error produced when calling a method in an invalid state. - */ -class InvalidStateError extends Error { - constructor(message) { - super(message); - this.name = 'InvalidStateError'; - if (Error.hasOwnProperty('captureStackTrace')) // Just in V8. - { - // @ts-ignore - Error.captureStackTrace(this, InvalidStateError); - } - else { - this.stack = (new Error(message)).stack; - } - } -} -exports.InvalidStateError = InvalidStateError; - -},{}],19:[function(require,module,exports){ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); + Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; @@ -3242,1592 +1968,835 @@ var __importStar = (this && this.__importStar) || function (mod) { __setModuleDefault(result, mod); return result; }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); -exports.Chrome55 = void 0; -const sdpTransform = __importStar(require("sdp-transform")); -const Logger_1 = require("../Logger"); -const errors_1 = require("../errors"); -const utils = __importStar(require("../utils")); -const ortc = __importStar(require("../ortc")); -const sdpCommonUtils = __importStar(require("./sdp/commonUtils")); -const sdpPlanBUtils = __importStar(require("./sdp/planBUtils")); -const HandlerInterface_1 = require("./HandlerInterface"); -const RemoteSdp_1 = require("./sdp/RemoteSdp"); -const logger = new Logger_1.Logger('Chrome55'); -const SCTP_NUM_STREAMS = { OS: 1024, MIS: 1024 }; -class Chrome55 extends HandlerInterface_1.HandlerInterface { - constructor() { - super(); - // Local stream for sending. - this._sendStream = new MediaStream(); - // Map of sending MediaStreamTracks indexed by localId. - this._mapSendLocalIdTrack = new Map(); - // Next sending localId. - this._nextSendLocalId = 0; - // Map of MID, RTP parameters and RTCRtpReceiver indexed by local id. - // Value is an Object with mid, rtpParameters and rtpReceiver. - this._mapRecvLocalIdInfo = new Map(); - // Whether a DataChannel m=application section has been created. - this._hasDataChannelMediaSection = false; - // Sending DataChannel id value counter. Incremented for each new DataChannel. - this._nextSendSctpStreamId = 0; - // Got transport local and remote parameters. - this._transportReady = false; - } - /** - * Creates a factory function. - */ - static createFactory() { - return () => new Chrome55(); - } - get name() { - return 'Chrome55'; - } - close() { - logger.debug('close()'); - // Close RTCPeerConnection. - if (this._pc) { - try { - this._pc.close(); - } - catch (error) { } +exports.Device = exports.detectDevice = void 0; +const bowser_1 = __importDefault(require("bowser")); +const Logger_1 = require("./Logger"); +const EnhancedEventEmitter_1 = require("./EnhancedEventEmitter"); +const errors_1 = require("./errors"); +const utils = __importStar(require("./utils")); +const ortc = __importStar(require("./ortc")); +const Transport_1 = require("./Transport"); +const Chrome111_1 = require("./handlers/Chrome111"); +const Chrome74_1 = require("./handlers/Chrome74"); +const Chrome70_1 = require("./handlers/Chrome70"); +const Chrome67_1 = require("./handlers/Chrome67"); +const Chrome55_1 = require("./handlers/Chrome55"); +const Firefox60_1 = require("./handlers/Firefox60"); +const Safari12_1 = require("./handlers/Safari12"); +const Safari11_1 = require("./handlers/Safari11"); +const Edge11_1 = require("./handlers/Edge11"); +const ReactNativeUnifiedPlan_1 = require("./handlers/ReactNativeUnifiedPlan"); +const ReactNative_1 = require("./handlers/ReactNative"); +const logger = new Logger_1.Logger('Device'); +function detectDevice() { + // React-Native. + // NOTE: react-native-webrtc >= 1.75.0 is required. + // NOTE: react-native-webrtc with Unified Plan requires version >= 106.0.0. + if (typeof navigator === 'object' && navigator.product === 'ReactNative') { + if (typeof RTCPeerConnection === 'undefined') { + logger.warn('this._detectDevice() | unsupported react-native-webrtc without RTCPeerConnection, forgot to call registerGlobals()?'); + return undefined; } - } - async getNativeRtpCapabilities() { - logger.debug('getNativeRtpCapabilities()'); - const pc = new RTCPeerConnection({ - iceServers: [], - iceTransportPolicy: 'all', - bundlePolicy: 'max-bundle', - rtcpMuxPolicy: 'require', - sdpSemantics: 'plan-b' - }); - try { - const offer = await pc.createOffer({ - offerToReceiveAudio: true, - offerToReceiveVideo: true - }); - try { - pc.close(); - } - catch (error) { } - const sdpObject = sdpTransform.parse(offer.sdp); - const nativeRtpCapabilities = sdpCommonUtils.extractRtpCapabilities({ sdpObject }); - return nativeRtpCapabilities; + if (typeof RTCRtpTransceiver !== 'undefined') { + logger.debug('this._detectDevice() | ReactNative UnifiedPlan handler chosen'); + return 'ReactNativeUnifiedPlan'; } - catch (error) { - try { - pc.close(); - } - catch (error2) { } - throw error; + else { + logger.debug('this._detectDevice() | ReactNative PlanB handler chosen'); + return 'ReactNative'; } } - async getNativeSctpCapabilities() { - logger.debug('getNativeSctpCapabilities()'); - return { - numStreams: SCTP_NUM_STREAMS - }; - } - run({ direction, iceParameters, iceCandidates, dtlsParameters, sctpParameters, iceServers, iceTransportPolicy, additionalSettings, proprietaryConstraints, extendedRtpCapabilities }) { - logger.debug('run()'); - this._direction = direction; - this._remoteSdp = new RemoteSdp_1.RemoteSdp({ - iceParameters, - iceCandidates, - dtlsParameters, - sctpParameters, - planB: true - }); - this._sendingRtpParametersByKind = - { - audio: ortc.getSendingRtpParameters('audio', extendedRtpCapabilities), - video: ortc.getSendingRtpParameters('video', extendedRtpCapabilities) - }; - this._sendingRemoteRtpParametersByKind = - { - audio: ortc.getSendingRemoteRtpParameters('audio', extendedRtpCapabilities), - video: ortc.getSendingRemoteRtpParameters('video', extendedRtpCapabilities) - }; - if (dtlsParameters.role && dtlsParameters.role !== 'auto') { - this._forcedLocalDtlsRole = dtlsParameters.role === 'server' - ? 'client' - : 'server'; + // Browser. + else if (typeof navigator === 'object' && typeof navigator.userAgent === 'string') { + const ua = navigator.userAgent; + const browser = bowser_1.default.getParser(ua); + const engine = browser.getEngine(); + // Chrome, Chromium, and Edge. + if (browser.satisfies({ chrome: '>=111', chromium: '>=111', 'microsoft edge': '>=111' })) { + return 'Chrome111'; } - this._pc = new RTCPeerConnection({ - iceServers: iceServers || [], - iceTransportPolicy: iceTransportPolicy || 'all', - bundlePolicy: 'max-bundle', - rtcpMuxPolicy: 'require', - sdpSemantics: 'plan-b', - ...additionalSettings - }, proprietaryConstraints); - // Handle RTCPeerConnection connection status. - this._pc.addEventListener('iceconnectionstatechange', () => { - switch (this._pc.iceConnectionState) { - 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; - } - }); - } - async updateIceServers(iceServers) { - logger.debug('updateIceServers()'); - const configuration = this._pc.getConfiguration(); - configuration.iceServers = iceServers; - this._pc.setConfiguration(configuration); - } - async restartIce(iceParameters) { - logger.debug('restartIce()'); - // Provide the remote SDP handler with new remote ICE parameters. - this._remoteSdp.updateIceParameters(iceParameters); - if (!this._transportReady) - return; - if (this._direction === 'send') { - const offer = await this._pc.createOffer({ iceRestart: true }); - logger.debug('restartIce() | calling pc.setLocalDescription() [offer:%o]', offer); - await this._pc.setLocalDescription(offer); - const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; - logger.debug('restartIce() | calling pc.setRemoteDescription() [answer:%o]', answer); - await this._pc.setRemoteDescription(answer); + else if (browser.satisfies({ chrome: '>=74', chromium: '>=74', 'microsoft edge': '>=88' })) { + return 'Chrome74'; } - else { - const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; - logger.debug('restartIce() | calling pc.setRemoteDescription() [offer:%o]', offer); - await this._pc.setRemoteDescription(offer); - const answer = await this._pc.createAnswer(); - logger.debug('restartIce() | calling pc.setLocalDescription() [answer:%o]', answer); - await this._pc.setLocalDescription(answer); + else if (browser.satisfies({ chrome: '>=70', chromium: '>=70' })) { + return 'Chrome70'; } - } - async getTransportStats() { - return this._pc.getStats(); - } - async send({ track, encodings, codecOptions, codec }) { - var _a; - this._assertSendDirection(); - logger.debug('send() [kind:%s, track.id:%s]', track.kind, track.id); - if (codec) { - logger.warn('send() | codec selection is not available in %s handler', this.name); + else if (browser.satisfies({ chrome: '>=67', chromium: '>=67' })) { + return 'Chrome67'; } - this._sendStream.addTrack(track); - this._pc.addStream(this._sendStream); - let offer = await this._pc.createOffer(); - let localSdpObject = sdpTransform.parse(offer.sdp); - let offerMediaObject; - const sendingRtpParameters = utils.clone(this._sendingRtpParametersByKind[track.kind], {}); - sendingRtpParameters.codecs = - ortc.reduceCodecs(sendingRtpParameters.codecs); - const sendingRemoteRtpParameters = utils.clone(this._sendingRemoteRtpParametersByKind[track.kind], {}); - sendingRemoteRtpParameters.codecs = - ortc.reduceCodecs(sendingRemoteRtpParameters.codecs); - if (!this._transportReady) { - await this._setupTransport({ - localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', - localSdpObject - }); + else if (browser.satisfies({ chrome: '>=55', chromium: '>=55' })) { + return 'Chrome55'; } - if (track.kind === 'video' && encodings && encodings.length > 1) { - logger.debug('send() | enabling simulcast'); - localSdpObject = sdpTransform.parse(offer.sdp); - offerMediaObject = localSdpObject.media.find((m) => m.type === 'video'); - sdpPlanBUtils.addLegacySimulcast({ - offerMediaObject, - track, - numStreams: encodings.length - }); - offer = { type: 'offer', sdp: sdpTransform.write(localSdpObject) }; + // Firefox. + else if (browser.satisfies({ firefox: '>=60' })) { + return 'Firefox60'; } - logger.debug('send() | calling pc.setLocalDescription() [offer:%o]', offer); - await this._pc.setLocalDescription(offer); - localSdpObject = sdpTransform.parse(this._pc.localDescription.sdp); - offerMediaObject = localSdpObject.media - .find((m) => m.type === track.kind); - // Set RTCP CNAME. - sendingRtpParameters.rtcp.cname = - sdpCommonUtils.getCname({ offerMediaObject }); - // Set RTP encodings. - sendingRtpParameters.encodings = - sdpPlanBUtils.getRtpEncodings({ offerMediaObject, track }); - // Complete encodings with given values. - if (encodings) { - for (let idx = 0; idx < sendingRtpParameters.encodings.length; ++idx) { - if (encodings[idx]) - Object.assign(sendingRtpParameters.encodings[idx], encodings[idx]); - } + // Firefox on iOS. + else if (browser.satisfies({ ios: { OS: '>=14.3', firefox: '>=30.0' } })) { + return 'Safari12'; } - // If VP8 and there is effective simulcast, add scalabilityMode to each - // encoding. - if (sendingRtpParameters.encodings.length > 1 && - sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/vp8') { - for (const encoding of sendingRtpParameters.encodings) { - encoding.scalabilityMode = 'S1T3'; - } + // Safari with Unified-Plan support enabled. + else if (browser.satisfies({ safari: '>=12.0' }) && + typeof RTCRtpTransceiver !== 'undefined' && + RTCRtpTransceiver.prototype.hasOwnProperty('currentDirection')) { + return 'Safari12'; } - this._remoteSdp.send({ - offerMediaObject, - offerRtpParameters: sendingRtpParameters, - answerRtpParameters: sendingRemoteRtpParameters, - codecOptions - }); - const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; - logger.debug('send() | calling pc.setRemoteDescription() [answer:%o]', answer); - await this._pc.setRemoteDescription(answer); - const localId = String(this._nextSendLocalId); - this._nextSendLocalId++; - // Insert into the map. - this._mapSendLocalIdTrack.set(localId, track); - return { - localId: localId, - rtpParameters: sendingRtpParameters - }; - } - async stopSending(localId) { - this._assertSendDirection(); - logger.debug('stopSending() [localId:%s]', localId); - const track = this._mapSendLocalIdTrack.get(localId); - if (!track) - throw new Error('track not found'); - this._mapSendLocalIdTrack.delete(localId); - this._sendStream.removeTrack(track); - this._pc.addStream(this._sendStream); - const offer = await this._pc.createOffer(); - logger.debug('stopSending() | calling pc.setLocalDescription() [offer:%o]', offer); - try { - await this._pc.setLocalDescription(offer); + // Safari with Plab-B support. + else if (browser.satisfies({ safari: '>=11' })) { + return 'Safari11'; } - catch (error) { - // NOTE: If there are no sending tracks, setLocalDescription() will fail with - // "Failed to create channels". If so, ignore it. - if (this._sendStream.getTracks().length === 0) { - logger.warn('stopSending() | ignoring expected error due no sending tracks: %s', error.toString()); - return; + // Old Edge with ORTC support. + else if (browser.satisfies({ 'microsoft edge': '>=11' }) && + browser.satisfies({ 'microsoft edge': '<=18' })) { + return 'Edge11'; + } + // Best effort for Chromium based browsers. + else if (engine.name && engine.name.toLowerCase() === 'blink') { + const match = ua.match(/(?:(?:Chrome|Chromium))[ /](\w+)/i); + if (match) { + const version = Number(match[1]); + if (version >= 111) { + return 'Chrome111'; + } + else if (version >= 74) { + return 'Chrome74'; + } + else if (version >= 70) { + return 'Chrome70'; + } + else if (version >= 67) { + return 'Chrome67'; + } + else { + return 'Chrome55'; + } + } + else { + return 'Chrome111'; } - throw error; } - if (this._pc.signalingState === 'stable') - return; - const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; - logger.debug('stopSending() | calling pc.setRemoteDescription() [answer:%o]', answer); - await this._pc.setRemoteDescription(answer); - } - async replaceTrack( - // eslint-disable-next-line @typescript-eslint/no-unused-vars - localId, track) { - throw new errors_1.UnsupportedError('not implemented'); - } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - async setMaxSpatialLayer(localId, spatialLayer) { - throw new errors_1.UnsupportedError(' not implemented'); - } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - async setRtpEncodingParameters(localId, params) { - throw new errors_1.UnsupportedError('not supported'); + // Unsupported browser. + else { + logger.warn('this._detectDevice() | browser not supported [name:%s, version:%s]', browser.getBrowserName(), browser.getBrowserVersion()); + return undefined; + } } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - async getSenderStats(localId) { - throw new errors_1.UnsupportedError('not implemented'); + // Unknown device. + else { + logger.warn('this._detectDevice() | unknown device'); + return undefined; } - async sendDataChannel({ ordered, maxPacketLifeTime, maxRetransmits, label, protocol }) { - var _a; - this._assertSendDirection(); - const options = { - negotiated: true, - id: this._nextSendSctpStreamId, - ordered, - maxPacketLifeTime, - maxRetransmitTime: maxPacketLifeTime, - maxRetransmits, - protocol - }; - logger.debug('sendDataChannel() [options:%o]', options); - const dataChannel = this._pc.createDataChannel(label, options); - // Increase next id. - this._nextSendSctpStreamId = - ++this._nextSendSctpStreamId % SCTP_NUM_STREAMS.MIS; - // If this is the first DataChannel we need to create the SDP answer with - // m=application section. - if (!this._hasDataChannelMediaSection) { - const offer = await this._pc.createOffer(); - const localSdpObject = sdpTransform.parse(offer.sdp); - const offerMediaObject = localSdpObject.media - .find((m) => m.type === 'application'); - if (!this._transportReady) { - await this._setupTransport({ - localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', - localSdpObject - }); +} +exports.detectDevice = detectDevice; +class Device { + /** + * Create a new Device to connect to mediasoup server. + * + * @throws {UnsupportedError} if device is not supported. + */ + constructor({ handlerName, handlerFactory, Handler } = {}) { + // Loaded flag. + this._loaded = false; + // Observer instance. + this._observer = new EnhancedEventEmitter_1.EnhancedEventEmitter(); + logger.debug('constructor()'); + // Handle deprecated option. + if (Handler) { + logger.warn('constructor() | Handler option is DEPRECATED, use handlerName or handlerFactory instead'); + if (typeof Handler === 'string') { + handlerName = Handler; + } + else { + throw new TypeError('non string Handler option no longer supported, use handlerFactory instead'); } - logger.debug('sendDataChannel() | calling pc.setLocalDescription() [offer:%o]', offer); - await this._pc.setLocalDescription(offer); - this._remoteSdp.sendSctpAssociation({ offerMediaObject }); - const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; - logger.debug('sendDataChannel() | calling pc.setRemoteDescription() [answer:%o]', answer); - await this._pc.setRemoteDescription(answer); - this._hasDataChannelMediaSection = true; } - const sctpStreamParameters = { - streamId: options.id, - ordered: options.ordered, - maxPacketLifeTime: options.maxPacketLifeTime, - maxRetransmits: options.maxRetransmits - }; - return { dataChannel, sctpStreamParameters }; - } - async receive({ trackId, kind, rtpParameters }) { - var _a; - this._assertRecvDirection(); - logger.debug('receive() [trackId:%s, kind:%s]', trackId, kind); - const localId = trackId; - const mid = kind; - const streamId = rtpParameters.rtcp.cname; - this._remoteSdp.receive({ - mid, - kind, - offerRtpParameters: rtpParameters, - streamId, - trackId - }); - const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; - logger.debug('receive() | calling pc.setRemoteDescription() [offer:%o]', offer); - await this._pc.setRemoteDescription(offer); - let answer = await this._pc.createAnswer(); - const localSdpObject = sdpTransform.parse(answer.sdp); - const answerMediaObject = localSdpObject.media - .find((m) => String(m.mid) === mid); - // May need to modify codec parameters in the answer based on codec - // parameters in the offer. - sdpCommonUtils.applyCodecParameters({ - offerRtpParameters: rtpParameters, - answerMediaObject - }); - answer = { type: 'answer', sdp: sdpTransform.write(localSdpObject) }; - if (!this._transportReady) { - await this._setupTransport({ - localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', - localSdpObject - }); + if (handlerName && handlerFactory) { + throw new TypeError('just one of handlerName or handlerInterface can be given'); } - logger.debug('receive() | calling pc.setLocalDescription() [answer:%o]', answer); - await this._pc.setLocalDescription(answer); - const stream = this._pc.getRemoteStreams() - .find((s) => s.id === streamId); - const track = stream.getTrackById(localId); - if (!track) - throw new Error('remote track not found'); - // Insert into the map. - this._mapRecvLocalIdInfo.set(localId, { mid, rtpParameters }); - return { localId, track }; - } - async stopReceiving(localId) { - this._assertRecvDirection(); - logger.debug('stopReceiving() [localId:%s]', localId); - const { mid, rtpParameters } = this._mapRecvLocalIdInfo.get(localId) || {}; - // Remove from the map. - this._mapRecvLocalIdInfo.delete(localId); - this._remoteSdp.planBStopReceiving({ mid: mid, offerRtpParameters: rtpParameters }); - const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; - logger.debug('stopReceiving() | calling pc.setRemoteDescription() [offer:%o]', offer); - await this._pc.setRemoteDescription(offer); - const answer = await this._pc.createAnswer(); - logger.debug('stopReceiving() | calling pc.setLocalDescription() [answer:%o]', answer); - await this._pc.setLocalDescription(answer); + if (handlerFactory) { + this._handlerFactory = handlerFactory; + } + else { + if (handlerName) { + logger.debug('constructor() | handler given: %s', handlerName); + } + else { + handlerName = detectDevice(); + if (handlerName) { + logger.debug('constructor() | detected handler: %s', handlerName); + } + else { + throw new errors_1.UnsupportedError('device not supported'); + } + } + switch (handlerName) { + case 'Chrome111': + this._handlerFactory = Chrome111_1.Chrome111.createFactory(); + break; + case 'Chrome74': + this._handlerFactory = Chrome74_1.Chrome74.createFactory(); + break; + case 'Chrome70': + this._handlerFactory = Chrome70_1.Chrome70.createFactory(); + break; + case 'Chrome67': + this._handlerFactory = Chrome67_1.Chrome67.createFactory(); + break; + case 'Chrome55': + this._handlerFactory = Chrome55_1.Chrome55.createFactory(); + break; + case 'Firefox60': + this._handlerFactory = Firefox60_1.Firefox60.createFactory(); + break; + case 'Safari12': + this._handlerFactory = Safari12_1.Safari12.createFactory(); + break; + case 'Safari11': + this._handlerFactory = Safari11_1.Safari11.createFactory(); + break; + case 'Edge11': + this._handlerFactory = Edge11_1.Edge11.createFactory(); + break; + case 'ReactNativeUnifiedPlan': + this._handlerFactory = ReactNativeUnifiedPlan_1.ReactNativeUnifiedPlan.createFactory(); + break; + case 'ReactNative': + this._handlerFactory = ReactNative_1.ReactNative.createFactory(); + break; + default: + throw new TypeError(`unknown handlerName "${handlerName}"`); + } + } + // Create a temporal handler to get its name. + const handler = this._handlerFactory(); + this._handlerName = handler.name; + handler.close(); + this._extendedRtpCapabilities = undefined; + this._recvRtpCapabilities = undefined; + this._canProduceByKind = + { + audio: false, + video: false + }; + this._sctpCapabilities = undefined; } - async pauseReceiving( - // eslint-disable-next-line @typescript-eslint/no-unused-vars - localId) { - // Unimplemented. + /** + * The RTC handler name. + */ + get handlerName() { + return this._handlerName; } - async resumeReceiving( - // eslint-disable-next-line @typescript-eslint/no-unused-vars - localId) { - // Unimplemented. + /** + * Whether the Device is loaded. + */ + get loaded() { + return this._loaded; } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - async getReceiverStats(localId) { - throw new errors_1.UnsupportedError('not implemented'); + /** + * RTP capabilities of the Device for receiving media. + * + * @throws {InvalidStateError} if not loaded. + */ + get rtpCapabilities() { + if (!this._loaded) { + throw new errors_1.InvalidStateError('not loaded'); + } + return this._recvRtpCapabilities; } - async receiveDataChannel({ sctpStreamParameters, label, protocol }) { - var _a; - this._assertRecvDirection(); - const { streamId, ordered, maxPacketLifeTime, maxRetransmits } = sctpStreamParameters; - const options = { - negotiated: true, - id: streamId, - ordered, - maxPacketLifeTime, - maxRetransmitTime: maxPacketLifeTime, - maxRetransmits, - protocol - }; - logger.debug('receiveDataChannel() [options:%o]', options); - const dataChannel = this._pc.createDataChannel(label, options); - // If this is the first DataChannel we need to create the SDP offer with - // m=application section. - if (!this._hasDataChannelMediaSection) { - this._remoteSdp.receiveSctpAssociation({ oldDataChannelSpec: true }); - const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; - logger.debug('receiveDataChannel() | calling pc.setRemoteDescription() [offer:%o]', offer); - await this._pc.setRemoteDescription(offer); - const answer = await this._pc.createAnswer(); - if (!this._transportReady) { - const localSdpObject = sdpTransform.parse(answer.sdp); - await this._setupTransport({ - localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', - localSdpObject - }); - } - logger.debug('receiveDataChannel() | calling pc.setRemoteDescription() [answer:%o]', answer); - await this._pc.setLocalDescription(answer); - this._hasDataChannelMediaSection = true; + /** + * SCTP capabilities of the Device. + * + * @throws {InvalidStateError} if not loaded. + */ + get sctpCapabilities() { + if (!this._loaded) { + throw new errors_1.InvalidStateError('not loaded'); } - return { dataChannel }; + return this._sctpCapabilities; } - async _setupTransport({ localDtlsRole, localSdpObject }) { - if (!localSdpObject) - localSdpObject = sdpTransform.parse(this._pc.localDescription.sdp); - // Get our local DTLS parameters. - const dtlsParameters = sdpCommonUtils.extractDtlsParameters({ sdpObject: localSdpObject }); - // Set our DTLS role. - dtlsParameters.role = localDtlsRole; - // Update the remote DTLS role in the SDP. - this._remoteSdp.updateDtlsRole(localDtlsRole === 'client' ? 'server' : 'client'); - // Need to tell the remote transport about our parameters. - await this.safeEmitAsPromise('@connect', { dtlsParameters }); - this._transportReady = true; + get observer() { + return this._observer; } - _assertSendDirection() { - if (this._direction !== 'send') { - throw new Error('method can just be called for handlers with "send" direction'); + /** + * Initialize the Device. + */ + async load({ routerRtpCapabilities }) { + logger.debug('load() [routerRtpCapabilities:%o]', routerRtpCapabilities); + routerRtpCapabilities = utils.clone(routerRtpCapabilities, undefined); + // Temporal handler to get its capabilities. + let handler; + try { + if (this._loaded) { + throw new errors_1.InvalidStateError('already loaded'); + } + // This may throw. + ortc.validateRtpCapabilities(routerRtpCapabilities); + handler = this._handlerFactory(); + const nativeRtpCapabilities = await handler.getNativeRtpCapabilities(); + logger.debug('load() | got native RTP capabilities:%o', nativeRtpCapabilities); + // This may throw. + ortc.validateRtpCapabilities(nativeRtpCapabilities); + // Get extended RTP capabilities. + this._extendedRtpCapabilities = ortc.getExtendedRtpCapabilities(nativeRtpCapabilities, routerRtpCapabilities); + logger.debug('load() | got extended RTP capabilities:%o', this._extendedRtpCapabilities); + // Check whether we can produce audio/video. + this._canProduceByKind.audio = + ortc.canSend('audio', this._extendedRtpCapabilities); + this._canProduceByKind.video = + ortc.canSend('video', this._extendedRtpCapabilities); + // Generate our receiving RTP capabilities for receiving media. + this._recvRtpCapabilities = + ortc.getRecvRtpCapabilities(this._extendedRtpCapabilities); + // This may throw. + ortc.validateRtpCapabilities(this._recvRtpCapabilities); + logger.debug('load() | got receiving RTP capabilities:%o', this._recvRtpCapabilities); + // Generate our SCTP capabilities. + this._sctpCapabilities = await handler.getNativeSctpCapabilities(); + logger.debug('load() | got native SCTP capabilities:%o', this._sctpCapabilities); + // This may throw. + ortc.validateSctpCapabilities(this._sctpCapabilities); + logger.debug('load() succeeded'); + this._loaded = true; + handler.close(); + } + catch (error) { + if (handler) { + handler.close(); + } + throw error; } } - _assertRecvDirection() { - if (this._direction !== 'recv') { - throw new Error('method can just be called for handlers with "recv" direction'); + /** + * Whether we can produce audio/video. + * + * @throws {InvalidStateError} if not loaded. + * @throws {TypeError} if wrong arguments. + */ + canProduce(kind) { + if (!this._loaded) { + throw new errors_1.InvalidStateError('not loaded'); + } + else if (kind !== 'audio' && kind !== 'video') { + throw new TypeError(`invalid kind "${kind}"`); + } + return this._canProduceByKind[kind]; + } + /** + * Creates a Transport for sending media. + * + * @throws {InvalidStateError} if not loaded. + * @throws {TypeError} if wrong arguments. + */ + createSendTransport({ id, iceParameters, iceCandidates, dtlsParameters, sctpParameters, iceServers, iceTransportPolicy, additionalSettings, proprietaryConstraints, appData }) { + logger.debug('createSendTransport()'); + return this.createTransport({ + direction: 'send', + id: id, + iceParameters: iceParameters, + iceCandidates: iceCandidates, + dtlsParameters: dtlsParameters, + sctpParameters: sctpParameters, + iceServers: iceServers, + iceTransportPolicy: iceTransportPolicy, + additionalSettings: additionalSettings, + proprietaryConstraints: proprietaryConstraints, + appData: appData + }); + } + /** + * Creates a Transport for receiving media. + * + * @throws {InvalidStateError} if not loaded. + * @throws {TypeError} if wrong arguments. + */ + createRecvTransport({ id, iceParameters, iceCandidates, dtlsParameters, sctpParameters, iceServers, iceTransportPolicy, additionalSettings, proprietaryConstraints, appData }) { + logger.debug('createRecvTransport()'); + return this.createTransport({ + direction: 'recv', + id: id, + iceParameters: iceParameters, + iceCandidates: iceCandidates, + dtlsParameters: dtlsParameters, + sctpParameters: sctpParameters, + iceServers: iceServers, + iceTransportPolicy: iceTransportPolicy, + additionalSettings: additionalSettings, + proprietaryConstraints: proprietaryConstraints, + appData: appData + }); + } + createTransport({ direction, id, iceParameters, iceCandidates, dtlsParameters, sctpParameters, iceServers, iceTransportPolicy, additionalSettings, proprietaryConstraints, appData }) { + if (!this._loaded) { + throw new errors_1.InvalidStateError('not loaded'); + } + else if (typeof id !== 'string') { + throw new TypeError('missing id'); + } + else if (typeof iceParameters !== 'object') { + throw new TypeError('missing iceParameters'); + } + else if (!Array.isArray(iceCandidates)) { + throw new TypeError('missing iceCandidates'); + } + else if (typeof dtlsParameters !== 'object') { + throw new TypeError('missing dtlsParameters'); + } + else if (sctpParameters && typeof sctpParameters !== 'object') { + throw new TypeError('wrong sctpParameters'); + } + else if (appData && typeof appData !== 'object') { + throw new TypeError('if given, appData must be an object'); } + // Create a new Transport. + const transport = new Transport_1.Transport({ + direction, + id, + iceParameters, + iceCandidates, + dtlsParameters, + sctpParameters, + iceServers, + iceTransportPolicy, + additionalSettings, + proprietaryConstraints, + appData, + handlerFactory: this._handlerFactory, + extendedRtpCapabilities: this._extendedRtpCapabilities, + canProduceByKind: this._canProduceByKind + }); + // Emit observer event. + this._observer.safeEmit('newtransport', transport); + return transport; } } -exports.Chrome55 = Chrome55; +exports.Device = Device; -},{"../Logger":13,"../errors":18,"../ortc":36,"../utils":39,"./HandlerInterface":25,"./sdp/RemoteSdp":31,"./sdp/commonUtils":32,"./sdp/planBUtils":33,"sdp-transform":44}],20:[function(require,module,exports){ +},{"./EnhancedEventEmitter":16,"./Logger":17,"./Transport":21,"./errors":22,"./handlers/Chrome111":23,"./handlers/Chrome55":24,"./handlers/Chrome67":25,"./handlers/Chrome70":26,"./handlers/Chrome74":27,"./handlers/Edge11":28,"./handlers/Firefox60":29,"./handlers/ReactNative":31,"./handlers/ReactNativeUnifiedPlan":32,"./handlers/Safari11":33,"./handlers/Safari12":34,"./ortc":43,"./utils":46,"bowser":7}],16:[function(require,module,exports){ "use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; Object.defineProperty(exports, "__esModule", { value: true }); -exports.Chrome67 = void 0; -const sdpTransform = __importStar(require("sdp-transform")); -const Logger_1 = require("../Logger"); -const utils = __importStar(require("../utils")); -const ortc = __importStar(require("../ortc")); -const sdpCommonUtils = __importStar(require("./sdp/commonUtils")); -const sdpPlanBUtils = __importStar(require("./sdp/planBUtils")); -const HandlerInterface_1 = require("./HandlerInterface"); -const RemoteSdp_1 = require("./sdp/RemoteSdp"); -const logger = new Logger_1.Logger('Chrome67'); -const SCTP_NUM_STREAMS = { OS: 1024, MIS: 1024 }; -class Chrome67 extends HandlerInterface_1.HandlerInterface { +exports.EnhancedEventEmitter = void 0; +const events_1 = require("events"); +const Logger_1 = require("./Logger"); +const logger = new Logger_1.Logger('EnhancedEventEmitter'); +class EnhancedEventEmitter extends events_1.EventEmitter { constructor() { super(); - // Local stream for sending. - this._sendStream = new MediaStream(); - // Map of RTCRtpSender indexed by localId. - this._mapSendLocalIdRtpSender = new Map(); - // Next sending localId. - this._nextSendLocalId = 0; - // Map of MID, RTP parameters and RTCRtpReceiver indexed by local id. - // Value is an Object with mid, rtpParameters and rtpReceiver. - this._mapRecvLocalIdInfo = new Map(); - // Whether a DataChannel m=application section has been created. - this._hasDataChannelMediaSection = false; - // Sending DataChannel id value counter. Incremented for each new DataChannel. - this._nextSendSctpStreamId = 0; - // Got transport local and remote parameters. - this._transportReady = false; + this.setMaxListeners(Infinity); + } + emit(eventName, ...args) { + return super.emit(eventName, ...args); } /** - * Creates a factory function. + * Special addition to the EventEmitter API. */ - static createFactory() { - return () => new Chrome67(); - } - get name() { - return 'Chrome67'; - } - close() { - logger.debug('close()'); - // Close RTCPeerConnection. - if (this._pc) { - try { - this._pc.close(); - } - catch (error) { } - } - } - async getNativeRtpCapabilities() { - logger.debug('getNativeRtpCapabilities()'); - const pc = new RTCPeerConnection({ - iceServers: [], - iceTransportPolicy: 'all', - bundlePolicy: 'max-bundle', - rtcpMuxPolicy: 'require', - sdpSemantics: 'plan-b' - }); + safeEmit(eventName, ...args) { + const numListeners = super.listenerCount(eventName); try { - const offer = await pc.createOffer({ - offerToReceiveAudio: true, - offerToReceiveVideo: true - }); - try { - pc.close(); - } - catch (error) { } - const sdpObject = sdpTransform.parse(offer.sdp); - const nativeRtpCapabilities = sdpCommonUtils.extractRtpCapabilities({ sdpObject }); - return nativeRtpCapabilities; + return super.emit(eventName, ...args); } catch (error) { - try { - pc.close(); - } - catch (error2) { } - throw error; + logger.error('safeEmit() | event listener threw an error [eventName:%s]:%o', eventName, error); + return Boolean(numListeners); } } - async getNativeSctpCapabilities() { - logger.debug('getNativeSctpCapabilities()'); - return { - numStreams: SCTP_NUM_STREAMS - }; + on(eventName, listener) { + super.on(eventName, listener); + return this; } - run({ direction, iceParameters, iceCandidates, dtlsParameters, sctpParameters, iceServers, iceTransportPolicy, additionalSettings, proprietaryConstraints, extendedRtpCapabilities }) { - logger.debug('run()'); - this._direction = direction; - this._remoteSdp = new RemoteSdp_1.RemoteSdp({ - iceParameters, - iceCandidates, - dtlsParameters, - sctpParameters, - planB: true - }); - this._sendingRtpParametersByKind = - { - audio: ortc.getSendingRtpParameters('audio', extendedRtpCapabilities), - video: ortc.getSendingRtpParameters('video', extendedRtpCapabilities) - }; - this._sendingRemoteRtpParametersByKind = - { - audio: ortc.getSendingRemoteRtpParameters('audio', extendedRtpCapabilities), - video: ortc.getSendingRemoteRtpParameters('video', extendedRtpCapabilities) - }; - if (dtlsParameters.role && dtlsParameters.role !== 'auto') { - this._forcedLocalDtlsRole = dtlsParameters.role === 'server' - ? 'client' - : 'server'; - } - this._pc = new RTCPeerConnection({ - iceServers: iceServers || [], - iceTransportPolicy: iceTransportPolicy || 'all', - bundlePolicy: 'max-bundle', - rtcpMuxPolicy: 'require', - sdpSemantics: 'plan-b', - ...additionalSettings - }, proprietaryConstraints); - // Handle RTCPeerConnection connection status. - this._pc.addEventListener('iceconnectionstatechange', () => { - switch (this._pc.iceConnectionState) { - 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; - } - }); + off(eventName, listener) { + super.off(eventName, listener); + return this; } - async updateIceServers(iceServers) { - logger.debug('updateIceServers()'); - const configuration = this._pc.getConfiguration(); - configuration.iceServers = iceServers; - this._pc.setConfiguration(configuration); + addListener(eventName, listener) { + super.on(eventName, listener); + return this; } - async restartIce(iceParameters) { - logger.debug('restartIce()'); - // Provide the remote SDP handler with new remote ICE parameters. - this._remoteSdp.updateIceParameters(iceParameters); - if (!this._transportReady) - return; - if (this._direction === 'send') { - const offer = await this._pc.createOffer({ iceRestart: true }); - logger.debug('restartIce() | calling pc.setLocalDescription() [offer:%o]', offer); - await this._pc.setLocalDescription(offer); - const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; - logger.debug('restartIce() | calling pc.setRemoteDescription() [answer:%o]', answer); - await this._pc.setRemoteDescription(answer); - } - else { - const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; - logger.debug('restartIce() | calling pc.setRemoteDescription() [offer:%o]', offer); - await this._pc.setRemoteDescription(offer); - const answer = await this._pc.createAnswer(); - logger.debug('restartIce() | calling pc.setLocalDescription() [answer:%o]', answer); - await this._pc.setLocalDescription(answer); - } + prependListener(eventName, listener) { + super.prependListener(eventName, listener); + return this; } - async getTransportStats() { - return this._pc.getStats(); + once(eventName, listener) { + super.once(eventName, listener); + return this; } - async send({ track, encodings, codecOptions, codec }) { - var _a; - this._assertSendDirection(); - logger.debug('send() [kind:%s, track.id:%s]', track.kind, track.id); - if (codec) { - logger.warn('send() | codec selection is not available in %s handler', this.name); - } - this._sendStream.addTrack(track); - this._pc.addTrack(track, this._sendStream); - let offer = await this._pc.createOffer(); - let localSdpObject = sdpTransform.parse(offer.sdp); - let offerMediaObject; - const sendingRtpParameters = utils.clone(this._sendingRtpParametersByKind[track.kind], {}); - sendingRtpParameters.codecs = - ortc.reduceCodecs(sendingRtpParameters.codecs); - const sendingRemoteRtpParameters = utils.clone(this._sendingRemoteRtpParametersByKind[track.kind], {}); - sendingRemoteRtpParameters.codecs = - ortc.reduceCodecs(sendingRemoteRtpParameters.codecs); - if (!this._transportReady) { - await this._setupTransport({ - localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', - localSdpObject - }); - } - if (track.kind === 'video' && encodings && encodings.length > 1) { - logger.debug('send() | enabling simulcast'); - localSdpObject = sdpTransform.parse(offer.sdp); - offerMediaObject = localSdpObject.media - .find((m) => m.type === 'video'); - sdpPlanBUtils.addLegacySimulcast({ - offerMediaObject, - track, - numStreams: encodings.length - }); - offer = { type: 'offer', sdp: sdpTransform.write(localSdpObject) }; - } - logger.debug('send() | calling pc.setLocalDescription() [offer:%o]', offer); - await this._pc.setLocalDescription(offer); - localSdpObject = sdpTransform.parse(this._pc.localDescription.sdp); - offerMediaObject = localSdpObject.media - .find((m) => m.type === track.kind); - // Set RTCP CNAME. - sendingRtpParameters.rtcp.cname = - sdpCommonUtils.getCname({ offerMediaObject }); - // Set RTP encodings. - sendingRtpParameters.encodings = - sdpPlanBUtils.getRtpEncodings({ offerMediaObject, track }); - // Complete encodings with given values. - if (encodings) { - for (let idx = 0; idx < sendingRtpParameters.encodings.length; ++idx) { - if (encodings[idx]) - Object.assign(sendingRtpParameters.encodings[idx], encodings[idx]); - } - } - // If VP8 and there is effective simulcast, add scalabilityMode to each - // encoding. - if (sendingRtpParameters.encodings.length > 1 && - sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/vp8') { - for (const encoding of sendingRtpParameters.encodings) { - encoding.scalabilityMode = 'S1T3'; - } - } - this._remoteSdp.send({ - offerMediaObject, - offerRtpParameters: sendingRtpParameters, - answerRtpParameters: sendingRemoteRtpParameters, - codecOptions - }); - const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; - logger.debug('send() | calling pc.setRemoteDescription() [answer:%o]', answer); - await this._pc.setRemoteDescription(answer); - const localId = String(this._nextSendLocalId); - this._nextSendLocalId++; - const rtpSender = this._pc.getSenders() - .find((s) => s.track === track); - // Insert into the map. - this._mapSendLocalIdRtpSender.set(localId, rtpSender); - return { - localId: localId, - rtpParameters: sendingRtpParameters, - rtpSender - }; + prependOnceListener(eventName, listener) { + super.prependOnceListener(eventName, listener); + return this; } - async stopSending(localId) { - this._assertSendDirection(); - logger.debug('stopSending() [localId:%s]', localId); - const rtpSender = this._mapSendLocalIdRtpSender.get(localId); - if (!rtpSender) - throw new Error('associated RTCRtpSender not found'); - this._pc.removeTrack(rtpSender); - if (rtpSender.track) - this._sendStream.removeTrack(rtpSender.track); - this._mapSendLocalIdRtpSender.delete(localId); - const offer = await this._pc.createOffer(); - logger.debug('stopSending() | calling pc.setLocalDescription() [offer:%o]', offer); - try { - await this._pc.setLocalDescription(offer); - } - catch (error) { - // NOTE: If there are no sending tracks, setLocalDescription() will fail with - // "Failed to create channels". If so, ignore it. - if (this._sendStream.getTracks().length === 0) { - logger.warn('stopSending() | ignoring expected error due no sending tracks: %s', error.toString()); - return; - } - throw error; - } - if (this._pc.signalingState === 'stable') - return; - const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; - logger.debug('stopSending() | calling pc.setRemoteDescription() [answer:%o]', answer); - await this._pc.setRemoteDescription(answer); + removeListener(eventName, listener) { + super.off(eventName, listener); + return this; } - async replaceTrack(localId, track) { - this._assertSendDirection(); - 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._mapSendLocalIdRtpSender.get(localId); - if (!rtpSender) - throw new Error('associated RTCRtpSender not found'); - const oldTrack = rtpSender.track; - await rtpSender.replaceTrack(track); - // Remove the old track from the local stream. - if (oldTrack) - this._sendStream.removeTrack(oldTrack); - // Add the new track to the local stream. - if (track) - this._sendStream.addTrack(track); + removeAllListeners(eventName) { + super.removeAllListeners(eventName); + return this; } - async setMaxSpatialLayer(localId, spatialLayer) { - this._assertSendDirection(); - logger.debug('setMaxSpatialLayer() [localId:%s, spatialLayer:%s]', localId, spatialLayer); - const rtpSender = this._mapSendLocalIdRtpSender.get(localId); - if (!rtpSender) - throw new Error('associated 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); + listenerCount(eventName) { + return super.listenerCount(eventName); } - async setRtpEncodingParameters(localId, params) { - this._assertSendDirection(); - logger.debug('setRtpEncodingParameters() [localId:%s, params:%o]', localId, params); - const rtpSender = this._mapSendLocalIdRtpSender.get(localId); - if (!rtpSender) - throw new Error('associated RTCRtpSender not found'); - const parameters = rtpSender.getParameters(); - parameters.encodings.forEach((encoding, idx) => { - parameters.encodings[idx] = { ...encoding, ...params }; - }); - await rtpSender.setParameters(parameters); + listeners(eventName) { + return super.listeners(eventName); } - async getSenderStats(localId) { - this._assertSendDirection(); - const rtpSender = this._mapSendLocalIdRtpSender.get(localId); - if (!rtpSender) - throw new Error('associated RTCRtpSender not found'); - return rtpSender.getStats(); + rawListeners(eventName) { + return super.rawListeners(eventName); } - async sendDataChannel({ ordered, maxPacketLifeTime, maxRetransmits, label, protocol }) { - var _a; - this._assertSendDirection(); - const options = { - negotiated: true, - id: this._nextSendSctpStreamId, - ordered, - maxPacketLifeTime, - maxRetransmitTime: maxPacketLifeTime, - maxRetransmits, - protocol - }; - logger.debug('sendDataChannel() [options:%o]', options); - const dataChannel = this._pc.createDataChannel(label, options); - // Increase next id. - this._nextSendSctpStreamId = - ++this._nextSendSctpStreamId % SCTP_NUM_STREAMS.MIS; - // If this is the first DataChannel we need to create the SDP answer with - // m=application section. - if (!this._hasDataChannelMediaSection) { - const offer = await this._pc.createOffer(); - const localSdpObject = sdpTransform.parse(offer.sdp); - const offerMediaObject = localSdpObject.media - .find((m) => m.type === 'application'); - if (!this._transportReady) { - await this._setupTransport({ - localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', - localSdpObject - }); - } - logger.debug('sendDataChannel() | calling pc.setLocalDescription() [offer:%o]', offer); - await this._pc.setLocalDescription(offer); - this._remoteSdp.sendSctpAssociation({ offerMediaObject }); - const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; - logger.debug('sendDataChannel() | calling pc.setRemoteDescription() [answer:%o]', answer); - await this._pc.setRemoteDescription(answer); - this._hasDataChannelMediaSection = true; +} +exports.EnhancedEventEmitter = EnhancedEventEmitter; + +},{"./Logger":17,"events":55}],17:[function(require,module,exports){ +"use strict"; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Logger = void 0; +const debug_1 = __importDefault(require("debug")); +const APP_NAME = 'mediasoup-client'; +class Logger { + constructor(prefix) { + if (prefix) { + this._debug = (0, debug_1.default)(`${APP_NAME}:${prefix}`); + this._warn = (0, debug_1.default)(`${APP_NAME}:WARN:${prefix}`); + this._error = (0, debug_1.default)(`${APP_NAME}:ERROR:${prefix}`); } - const sctpStreamParameters = { - streamId: options.id, - ordered: options.ordered, - maxPacketLifeTime: options.maxPacketLifeTime, - maxRetransmits: options.maxRetransmits - }; - return { dataChannel, sctpStreamParameters }; - } - async receive({ trackId, kind, rtpParameters }) { - var _a; - this._assertRecvDirection(); - logger.debug('receive() [trackId:%s, kind:%s]', trackId, kind); - const localId = trackId; - const mid = kind; - this._remoteSdp.receive({ - mid, - kind, - offerRtpParameters: rtpParameters, - streamId: rtpParameters.rtcp.cname, - trackId - }); - const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; - logger.debug('receive() | calling pc.setRemoteDescription() [offer:%o]', offer); - await this._pc.setRemoteDescription(offer); - let answer = await this._pc.createAnswer(); - const localSdpObject = sdpTransform.parse(answer.sdp); - const answerMediaObject = localSdpObject.media - .find((m) => String(m.mid) === mid); - // May need to modify codec parameters in the answer based on codec - // parameters in the offer. - sdpCommonUtils.applyCodecParameters({ - offerRtpParameters: rtpParameters, - answerMediaObject - }); - answer = { type: 'answer', sdp: sdpTransform.write(localSdpObject) }; - if (!this._transportReady) { - await this._setupTransport({ - localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', - localSdpObject - }); + else { + this._debug = (0, debug_1.default)(APP_NAME); + this._warn = (0, debug_1.default)(`${APP_NAME}:WARN`); + this._error = (0, debug_1.default)(`${APP_NAME}:ERROR`); } - logger.debug('receive() | calling pc.setLocalDescription() [answer:%o]', answer); - await this._pc.setLocalDescription(answer); - const rtpReceiver = this._pc.getReceivers() - .find((r) => r.track && r.track.id === localId); - if (!rtpReceiver) - throw new Error('new RTCRtpReceiver not'); - // Insert into the map. - this._mapRecvLocalIdInfo.set(localId, { mid, rtpParameters, rtpReceiver }); - return { - localId, - track: rtpReceiver.track, - rtpReceiver - }; + /* eslint-disable no-console */ + this._debug.log = console.info.bind(console); + this._warn.log = console.warn.bind(console); + this._error.log = console.error.bind(console); + /* eslint-enable no-console */ } - async stopReceiving(localId) { - this._assertRecvDirection(); - logger.debug('stopReceiving() [localId:%s]', localId); - const { mid, rtpParameters } = this._mapRecvLocalIdInfo.get(localId) || {}; - // Remove from the map. - this._mapRecvLocalIdInfo.delete(localId); - this._remoteSdp.planBStopReceiving({ mid: mid, offerRtpParameters: rtpParameters }); - const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; - logger.debug('stopReceiving() | calling pc.setRemoteDescription() [offer:%o]', offer); - await this._pc.setRemoteDescription(offer); - const answer = await this._pc.createAnswer(); - logger.debug('stopReceiving() | calling pc.setLocalDescription() [answer:%o]', answer); - await this._pc.setLocalDescription(answer); - } - async pauseReceiving( - // eslint-disable-next-line @typescript-eslint/no-unused-vars - localId) { - // Unimplemented. - } - async resumeReceiving( - // eslint-disable-next-line @typescript-eslint/no-unused-vars - localId) { - // Unimplemented. - } - async getReceiverStats(localId) { - this._assertRecvDirection(); - const { rtpReceiver } = this._mapRecvLocalIdInfo.get(localId) || {}; - if (!rtpReceiver) - throw new Error('associated RTCRtpReceiver not found'); - return rtpReceiver.getStats(); - } - async receiveDataChannel({ sctpStreamParameters, label, protocol }) { - var _a; - this._assertRecvDirection(); - const { streamId, ordered, maxPacketLifeTime, maxRetransmits } = sctpStreamParameters; - const options = { - negotiated: true, - id: streamId, - ordered, - maxPacketLifeTime, - maxRetransmitTime: maxPacketLifeTime, - maxRetransmits, - protocol - }; - logger.debug('receiveDataChannel() [options:%o]', options); - const dataChannel = this._pc.createDataChannel(label, options); - // If this is the first DataChannel we need to create the SDP offer with - // m=application section. - if (!this._hasDataChannelMediaSection) { - this._remoteSdp.receiveSctpAssociation({ oldDataChannelSpec: true }); - const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; - logger.debug('receiveDataChannel() | calling pc.setRemoteDescription() [offer:%o]', offer); - await this._pc.setRemoteDescription(offer); - const answer = await this._pc.createAnswer(); - if (!this._transportReady) { - const localSdpObject = sdpTransform.parse(answer.sdp); - await this._setupTransport({ - localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', - localSdpObject - }); - } - logger.debug('receiveDataChannel() | calling pc.setRemoteDescription() [answer:%o]', answer); - await this._pc.setLocalDescription(answer); - this._hasDataChannelMediaSection = true; - } - return { dataChannel }; - } - async _setupTransport({ localDtlsRole, localSdpObject }) { - if (!localSdpObject) - localSdpObject = sdpTransform.parse(this._pc.localDescription.sdp); - // Get our local DTLS parameters. - const dtlsParameters = sdpCommonUtils.extractDtlsParameters({ sdpObject: localSdpObject }); - // Set our DTLS role. - dtlsParameters.role = localDtlsRole; - // Update the remote DTLS role in the SDP. - this._remoteSdp.updateDtlsRole(localDtlsRole === 'client' ? 'server' : 'client'); - // Need to tell the remote transport about our parameters. - await this.safeEmitAsPromise('@connect', { dtlsParameters }); - this._transportReady = true; + get debug() { + return this._debug; } - _assertSendDirection() { - if (this._direction !== 'send') { - throw new Error('method can just be called for handlers with "send" direction'); - } + get warn() { + return this._warn; } - _assertRecvDirection() { - if (this._direction !== 'recv') { - throw new Error('method can just be called for handlers with "recv" direction'); - } + get error() { + return this._error; } } -exports.Chrome67 = Chrome67; +exports.Logger = Logger; -},{"../Logger":13,"../ortc":36,"../utils":39,"./HandlerInterface":25,"./sdp/RemoteSdp":31,"./sdp/commonUtils":32,"./sdp/planBUtils":33,"sdp-transform":44}],21:[function(require,module,exports){ +},{"debug":47}],18:[function(require,module,exports){ "use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; Object.defineProperty(exports, "__esModule", { value: true }); -exports.Chrome70 = void 0; -const sdpTransform = __importStar(require("sdp-transform")); -const Logger_1 = require("../Logger"); -const utils = __importStar(require("../utils")); -const ortc = __importStar(require("../ortc")); -const sdpCommonUtils = __importStar(require("./sdp/commonUtils")); -const sdpUnifiedPlanUtils = __importStar(require("./sdp/unifiedPlanUtils")); -const HandlerInterface_1 = require("./HandlerInterface"); -const RemoteSdp_1 = require("./sdp/RemoteSdp"); -const scalabilityModes_1 = require("../scalabilityModes"); -const logger = new Logger_1.Logger('Chrome70'); -const SCTP_NUM_STREAMS = { OS: 1024, MIS: 1024 }; -class Chrome70 extends HandlerInterface_1.HandlerInterface { - constructor() { +exports.Producer = void 0; +const Logger_1 = require("./Logger"); +const EnhancedEventEmitter_1 = require("./EnhancedEventEmitter"); +const errors_1 = require("./errors"); +const logger = new Logger_1.Logger('Producer'); +class Producer extends EnhancedEventEmitter_1.EnhancedEventEmitter { + constructor({ id, localId, rtpSender, track, rtpParameters, stopTracks, disableTrackOnPause, zeroRtpOnPause, appData }) { super(); - // Map of RTCTransceivers indexed by MID. - this._mapMidTransceiver = new Map(); - // Local stream for sending. - this._sendStream = new MediaStream(); - // Whether a DataChannel m=application section has been created. - this._hasDataChannelMediaSection = false; - // Sending DataChannel id value counter. Incremented for each new DataChannel. - this._nextSendSctpStreamId = 0; - // Got transport local and remote parameters. - this._transportReady = false; + // Closed flag. + this._closed = false; + // Observer instance. + this._observer = new EnhancedEventEmitter_1.EnhancedEventEmitter(); + logger.debug('constructor()'); + this._id = id; + this._localId = localId; + this._rtpSender = rtpSender; + this._track = track; + this._kind = track.kind; + this._rtpParameters = rtpParameters; + this._paused = disableTrackOnPause ? !track.enabled : false; + this._maxSpatialLayer = undefined; + this._stopTracks = stopTracks; + this._disableTrackOnPause = disableTrackOnPause; + this._zeroRtpOnPause = zeroRtpOnPause; + this._appData = appData || {}; + this.onTrackEnded = this.onTrackEnded.bind(this); + // NOTE: Minor issue. If zeroRtpOnPause is true, we cannot emit the + // '@replacetrack' event here, so RTCRtpSender.track won't be null. + this.handleTrack(); } /** - * Creates a factory function. + * Producer id. */ - static createFactory() { - return () => new Chrome70(); + get id() { + return this._id; } - get name() { - return 'Chrome70'; + /** + * Local id. + */ + get localId() { + return this._localId; + } + /** + * Whether the Producer is closed. + */ + get closed() { + return this._closed; + } + /** + * Media kind. + */ + get kind() { + return this._kind; + } + /** + * Associated RTCRtpSender. + */ + get rtpSender() { + return this._rtpSender; + } + /** + * The associated track. + */ + get track() { + return this._track; + } + /** + * RTP parameters. + */ + get rtpParameters() { + return this._rtpParameters; + } + /** + * Whether the Producer is paused. + */ + get paused() { + return this._paused; + } + /** + * Max spatial layer. + * + * @type {Number | undefined} + */ + get maxSpatialLayer() { + return this._maxSpatialLayer; + } + /** + * App custom data. + */ + get appData() { + return this._appData; + } + /** + * Invalid setter. + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + set appData(appData) { + throw new Error('cannot override appData object'); } + get observer() { + return this._observer; + } + /** + * Closes the Producer. + */ close() { + if (this._closed) { + return; + } logger.debug('close()'); - // Close RTCPeerConnection. - if (this._pc) { - try { - this._pc.close(); - } - catch (error) { } + this._closed = true; + this.destroyTrack(); + this.emit('@close'); + // Emit observer event. + this._observer.safeEmit('close'); + } + /** + * Transport was closed. + */ + transportClosed() { + if (this._closed) { + return; } + logger.debug('transportClosed()'); + this._closed = true; + this.destroyTrack(); + this.safeEmit('transportclose'); + // Emit observer event. + this._observer.safeEmit('close'); } - async getNativeRtpCapabilities() { - logger.debug('getNativeRtpCapabilities()'); - const pc = new RTCPeerConnection({ - iceServers: [], - iceTransportPolicy: 'all', - bundlePolicy: 'max-bundle', - rtcpMuxPolicy: 'require', - sdpSemantics: 'unified-plan' + /** + * Get associated RTCRtpSender stats. + */ + async getStats() { + if (this._closed) { + throw new errors_1.InvalidStateError('closed'); + } + return new Promise((resolve, reject) => { + this.safeEmit('@getstats', resolve, reject); }); - try { - pc.addTransceiver('audio'); - pc.addTransceiver('video'); - const offer = await pc.createOffer(); - try { - pc.close(); - } - catch (error) { } - const sdpObject = sdpTransform.parse(offer.sdp); - const nativeRtpCapabilities = sdpCommonUtils.extractRtpCapabilities({ sdpObject }); - return nativeRtpCapabilities; + } + /** + * Pauses sending media. + */ + pause() { + logger.debug('pause()'); + if (this._closed) { + logger.error('pause() | Producer closed'); + return; } - catch (error) { - try { - pc.close(); - } - catch (error2) { } - throw error; + this._paused = true; + if (this._track && this._disableTrackOnPause) { + this._track.enabled = false; } - } - async getNativeSctpCapabilities() { - logger.debug('getNativeSctpCapabilities()'); - return { - numStreams: SCTP_NUM_STREAMS - }; - } - run({ direction, iceParameters, iceCandidates, dtlsParameters, sctpParameters, iceServers, iceTransportPolicy, additionalSettings, proprietaryConstraints, extendedRtpCapabilities }) { - logger.debug('run()'); - this._direction = direction; - this._remoteSdp = new RemoteSdp_1.RemoteSdp({ - iceParameters, - iceCandidates, - dtlsParameters, - sctpParameters - }); - this._sendingRtpParametersByKind = - { - audio: ortc.getSendingRtpParameters('audio', extendedRtpCapabilities), - video: ortc.getSendingRtpParameters('video', extendedRtpCapabilities) - }; - this._sendingRemoteRtpParametersByKind = - { - audio: ortc.getSendingRemoteRtpParameters('audio', extendedRtpCapabilities), - video: ortc.getSendingRemoteRtpParameters('video', extendedRtpCapabilities) - }; - if (dtlsParameters.role && dtlsParameters.role !== 'auto') { - this._forcedLocalDtlsRole = dtlsParameters.role === 'server' - ? 'client' - : 'server'; + if (this._zeroRtpOnPause) { + new Promise((resolve, reject) => { + this.safeEmit('@pause', resolve, reject); + }).catch(() => { }); } - this._pc = new RTCPeerConnection({ - iceServers: iceServers || [], - iceTransportPolicy: iceTransportPolicy || 'all', - bundlePolicy: 'max-bundle', - rtcpMuxPolicy: 'require', - sdpSemantics: 'unified-plan', - ...additionalSettings - }, proprietaryConstraints); - // Handle RTCPeerConnection connection status. - this._pc.addEventListener('iceconnectionstatechange', () => { - switch (this._pc.iceConnectionState) { - 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; - } - }); - } - async updateIceServers(iceServers) { - logger.debug('updateIceServers()'); - const configuration = this._pc.getConfiguration(); - configuration.iceServers = iceServers; - this._pc.setConfiguration(configuration); + // Emit observer event. + this._observer.safeEmit('pause'); } - async restartIce(iceParameters) { - logger.debug('restartIce()'); - // Provide the remote SDP handler with new remote ICE parameters. - this._remoteSdp.updateIceParameters(iceParameters); - if (!this._transportReady) + /** + * Resumes sending media. + */ + resume() { + logger.debug('resume()'); + if (this._closed) { + logger.error('resume() | Producer closed'); return; - if (this._direction === 'send') { - const offer = await this._pc.createOffer({ iceRestart: true }); - logger.debug('restartIce() | calling pc.setLocalDescription() [offer:%o]', offer); - await this._pc.setLocalDescription(offer); - const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; - logger.debug('restartIce() | calling pc.setRemoteDescription() [answer:%o]', answer); - await this._pc.setRemoteDescription(answer); } - else { - const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; - logger.debug('restartIce() | calling pc.setRemoteDescription() [offer:%o]', offer); - await this._pc.setRemoteDescription(offer); - const answer = await this._pc.createAnswer(); - logger.debug('restartIce() | calling pc.setLocalDescription() [answer:%o]', answer); - await this._pc.setLocalDescription(answer); + this._paused = false; + if (this._track && this._disableTrackOnPause) { + this._track.enabled = true; } + if (this._zeroRtpOnPause) { + new Promise((resolve, reject) => { + this.safeEmit('@resume', resolve, reject); + }).catch(() => { }); + } + // Emit observer event. + this._observer.safeEmit('resume'); } - async getTransportStats() { - return this._pc.getStats(); - } - async send({ track, encodings, codecOptions, codec }) { - var _a; - this._assertSendDirection(); - logger.debug('send() [kind:%s, track.id:%s]', track.kind, track.id); - const sendingRtpParameters = utils.clone(this._sendingRtpParametersByKind[track.kind], {}); - // This may throw. - sendingRtpParameters.codecs = - ortc.reduceCodecs(sendingRtpParameters.codecs, codec); - const sendingRemoteRtpParameters = utils.clone(this._sendingRemoteRtpParametersByKind[track.kind], {}); - // This may throw. - sendingRemoteRtpParameters.codecs = - ortc.reduceCodecs(sendingRemoteRtpParameters.codecs, codec); - const mediaSectionIdx = this._remoteSdp.getNextMediaSectionIdx(); - const transceiver = this._pc.addTransceiver(track, { direction: 'sendonly', streams: [this._sendStream] }); - let offer = await this._pc.createOffer(); - let localSdpObject = sdpTransform.parse(offer.sdp); - let offerMediaObject; - if (!this._transportReady) { - await this._setupTransport({ - localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', - localSdpObject - }); + /** + * Replaces the current track with a new one or null. + */ + async replaceTrack({ track }) { + logger.debug('replaceTrack() [track:%o]', track); + if (this._closed) { + // This must be done here. Otherwise there is no chance to stop the given + // track. + if (track && this._stopTracks) { + try { + track.stop(); + } + catch (error) { } + } + throw new errors_1.InvalidStateError('closed'); } - if (encodings && encodings.length > 1) { - logger.debug('send() | enabling legacy simulcast'); - localSdpObject = sdpTransform.parse(offer.sdp); - offerMediaObject = localSdpObject.media[mediaSectionIdx.idx]; - sdpUnifiedPlanUtils.addLegacySimulcast({ - offerMediaObject, - numStreams: encodings.length - }); - offer = { type: 'offer', sdp: sdpTransform.write(localSdpObject) }; + else if (track && track.readyState === 'ended') { + throw new errors_1.InvalidStateError('track ended'); } - // Special case for VP9 with SVC. - let hackVp9Svc = false; - const layers = (0, scalabilityModes_1.parse)((encodings || [{}])[0].scalabilityMode); - if (encodings && - encodings.length === 1 && - layers.spatialLayers > 1 && - sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/vp9') { - logger.debug('send() | enabling legacy simulcast for VP9 SVC'); - hackVp9Svc = true; - localSdpObject = sdpTransform.parse(offer.sdp); - offerMediaObject = localSdpObject.media[mediaSectionIdx.idx]; - sdpUnifiedPlanUtils.addLegacySimulcast({ - offerMediaObject, - numStreams: layers.spatialLayers - }); - offer = { type: 'offer', sdp: sdpTransform.write(localSdpObject) }; + // Do nothing if this is the same track as the current handled one. + if (track === this._track) { + logger.debug('replaceTrack() | same track, ignored'); + return; } - logger.debug('send() | calling pc.setLocalDescription() [offer:%o]', offer); - await this._pc.setLocalDescription(offer); - // If encodings are given, apply them now. - if (encodings) { - logger.debug('send() | applying given encodings'); - const parameters = transceiver.sender.getParameters(); - for (let idx = 0; idx < (parameters.encodings || []).length; ++idx) { - const encoding = parameters.encodings[idx]; - const desiredEncoding = encodings[idx]; - // Should not happen but just in case. - if (!desiredEncoding) - break; - parameters.encodings[idx] = Object.assign(encoding, desiredEncoding); + await new Promise((resolve, reject) => { + this.safeEmit('@replacetrack', track, resolve, reject); + }); + // Destroy the previous track. + this.destroyTrack(); + // Set the new track. + this._track = track; + // If this Producer was paused/resumed and the state of the new + // track does not match, fix it. + if (this._track && this._disableTrackOnPause) { + if (!this._paused) { + this._track.enabled = true; } - await transceiver.sender.setParameters(parameters); - } - // We can now get the transceiver.mid. - const localId = transceiver.mid; - // Set MID. - sendingRtpParameters.mid = localId; - localSdpObject = sdpTransform.parse(this._pc.localDescription.sdp); - offerMediaObject = localSdpObject.media[mediaSectionIdx.idx]; - // Set RTCP CNAME. - sendingRtpParameters.rtcp.cname = - sdpCommonUtils.getCname({ offerMediaObject }); - // Set RTP encodings. - sendingRtpParameters.encodings = - sdpUnifiedPlanUtils.getRtpEncodings({ offerMediaObject }); - // Complete encodings with given values. - if (encodings) { - for (let idx = 0; idx < sendingRtpParameters.encodings.length; ++idx) { - if (encodings[idx]) - Object.assign(sendingRtpParameters.encodings[idx], encodings[idx]); + else if (this._paused) { + this._track.enabled = false; } } - // Hack for VP9 SVC. - if (hackVp9Svc) { - sendingRtpParameters.encodings = [sendingRtpParameters.encodings[0]]; + // Handle the effective track. + this.handleTrack(); + } + /** + * Sets the video max spatial layer to be sent. + */ + async setMaxSpatialLayer(spatialLayer) { + if (this._closed) { + throw new errors_1.InvalidStateError('closed'); } - // If VP8 or H264 and there is effective simulcast, add scalabilityMode to - // each encoding. - if (sendingRtpParameters.encodings.length > 1 && - (sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/vp8' || - sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/h264')) { - for (const encoding of sendingRtpParameters.encodings) { - encoding.scalabilityMode = 'S1T3'; - } + else if (this._kind !== 'video') { + throw new errors_1.UnsupportedError('not a video Producer'); } - this._remoteSdp.send({ - offerMediaObject, - reuseMid: mediaSectionIdx.reuseMid, - offerRtpParameters: sendingRtpParameters, - answerRtpParameters: sendingRemoteRtpParameters, - codecOptions - }); - const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; - logger.debug('send() | calling pc.setRemoteDescription() [answer:%o]', answer); - await this._pc.setRemoteDescription(answer); - // Store in the map. - this._mapMidTransceiver.set(localId, transceiver); - return { - localId, - rtpParameters: sendingRtpParameters, - rtpSender: transceiver.sender - }; - } - async stopSending(localId) { - this._assertSendDirection(); - logger.debug('stopSending() [localId:%s]', localId); - const transceiver = this._mapMidTransceiver.get(localId); - if (!transceiver) - throw new Error('associated RTCRtpTransceiver not found'); - transceiver.sender.replaceTrack(null); - this._pc.removeTrack(transceiver.sender); - this._remoteSdp.closeMediaSection(transceiver.mid); - const offer = await this._pc.createOffer(); - logger.debug('stopSending() | calling pc.setLocalDescription() [offer:%o]', offer); - await this._pc.setLocalDescription(offer); - const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; - logger.debug('stopSending() | calling pc.setRemoteDescription() [answer:%o]', answer); - await this._pc.setRemoteDescription(answer); - this._mapMidTransceiver.delete(localId); - } - async replaceTrack(localId, track) { - this._assertSendDirection(); - if (track) { - logger.debug('replaceTrack() [localId:%s, track.id:%s]', localId, track.id); + else if (typeof spatialLayer !== 'number') { + throw new TypeError('invalid spatialLayer'); } - else { - logger.debug('replaceTrack() [localId:%s, no track]', localId); + if (spatialLayer === this._maxSpatialLayer) { + return; } - const transceiver = this._mapMidTransceiver.get(localId); - if (!transceiver) - throw new Error('associated RTCRtpTransceiver not found'); - await transceiver.sender.replaceTrack(track); - } - async setMaxSpatialLayer(localId, spatialLayer) { - this._assertSendDirection(); - logger.debug('setMaxSpatialLayer() [localId:%s, spatialLayer:%s]', localId, spatialLayer); - const transceiver = this._mapMidTransceiver.get(localId); - if (!transceiver) - throw new Error('associated RTCRtpTransceiver not found'); - const parameters = transceiver.sender.getParameters(); - parameters.encodings.forEach((encoding, idx) => { - if (idx <= spatialLayer) - encoding.active = true; - else - encoding.active = false; - }); - await transceiver.sender.setParameters(parameters); - } - async setRtpEncodingParameters(localId, params) { - this._assertSendDirection(); - logger.debug('setRtpEncodingParameters() [localId:%s, params:%o]', localId, params); - const transceiver = this._mapMidTransceiver.get(localId); - if (!transceiver) - throw new Error('associated RTCRtpTransceiver not found'); - const parameters = transceiver.sender.getParameters(); - parameters.encodings.forEach((encoding, idx) => { - parameters.encodings[idx] = { ...encoding, ...params }; - }); - await transceiver.sender.setParameters(parameters); - } - async getSenderStats(localId) { - this._assertSendDirection(); - const transceiver = this._mapMidTransceiver.get(localId); - if (!transceiver) - throw new Error('associated RTCRtpTransceiver not found'); - return transceiver.sender.getStats(); + await new Promise((resolve, reject) => { + this.safeEmit('@setmaxspatiallayer', spatialLayer, resolve, reject); + }).catch(() => { }); + this._maxSpatialLayer = spatialLayer; } - async sendDataChannel({ ordered, maxPacketLifeTime, maxRetransmits, label, protocol }) { - var _a; - this._assertSendDirection(); - const options = { - negotiated: true, - id: this._nextSendSctpStreamId, - ordered, - maxPacketLifeTime, - maxRetransmitTime: maxPacketLifeTime, - maxRetransmits, - protocol - }; - logger.debug('sendDataChannel() [options:%o]', options); - const dataChannel = this._pc.createDataChannel(label, options); - // Increase next id. - this._nextSendSctpStreamId = - ++this._nextSendSctpStreamId % SCTP_NUM_STREAMS.MIS; - // If this is the first DataChannel we need to create the SDP answer with - // m=application section. - if (!this._hasDataChannelMediaSection) { - const offer = await this._pc.createOffer(); - const localSdpObject = sdpTransform.parse(offer.sdp); - const offerMediaObject = localSdpObject.media - .find((m) => m.type === 'application'); - if (!this._transportReady) { - await this._setupTransport({ - localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', - localSdpObject - }); - } - logger.debug('sendDataChannel() | calling pc.setLocalDescription() [offer:%o]', offer); - await this._pc.setLocalDescription(offer); - this._remoteSdp.sendSctpAssociation({ offerMediaObject }); - const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; - logger.debug('sendDataChannel() | calling pc.setRemoteDescription() [answer:%o]', answer); - await this._pc.setRemoteDescription(answer); - this._hasDataChannelMediaSection = true; + async setRtpEncodingParameters(params) { + if (this._closed) { + throw new errors_1.InvalidStateError('closed'); } - const sctpStreamParameters = { - streamId: options.id, - ordered: options.ordered, - maxPacketLifeTime: options.maxPacketLifeTime, - maxRetransmits: options.maxRetransmits - }; - return { dataChannel, sctpStreamParameters }; - } - async receive({ trackId, kind, rtpParameters }) { - var _a; - this._assertRecvDirection(); - logger.debug('receive() [trackId:%s, kind:%s]', trackId, kind); - const localId = rtpParameters.mid || String(this._mapMidTransceiver.size); - this._remoteSdp.receive({ - mid: localId, - kind, - offerRtpParameters: rtpParameters, - streamId: rtpParameters.rtcp.cname, - trackId - }); - const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; - logger.debug('receive() | calling pc.setRemoteDescription() [offer:%o]', offer); - await this._pc.setRemoteDescription(offer); - let answer = await this._pc.createAnswer(); - const localSdpObject = sdpTransform.parse(answer.sdp); - const answerMediaObject = localSdpObject.media - .find((m) => String(m.mid) === localId); - // May need to modify codec parameters in the answer based on codec - // parameters in the offer. - sdpCommonUtils.applyCodecParameters({ - offerRtpParameters: rtpParameters, - answerMediaObject - }); - answer = { type: 'answer', sdp: sdpTransform.write(localSdpObject) }; - if (!this._transportReady) { - await this._setupTransport({ - localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', - localSdpObject - }); + else if (typeof params !== 'object') { + throw new TypeError('invalid params'); } - logger.debug('receive() | calling pc.setLocalDescription() [answer:%o]', answer); - await this._pc.setLocalDescription(answer); - const transceiver = this._pc.getTransceivers() - .find((t) => t.mid === localId); - if (!transceiver) - throw new Error('new RTCRtpTransceiver not found'); - // Store in the map. - this._mapMidTransceiver.set(localId, transceiver); - return { - localId, - track: transceiver.receiver.track, - rtpReceiver: transceiver.receiver - }; - } - async stopReceiving(localId) { - this._assertRecvDirection(); - logger.debug('stopReceiving() [localId:%s]', localId); - const transceiver = this._mapMidTransceiver.get(localId); - if (!transceiver) - throw new Error('associated RTCRtpTransceiver not found'); - this._remoteSdp.closeMediaSection(transceiver.mid); - const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; - logger.debug('stopReceiving() | calling pc.setRemoteDescription() [offer:%o]', offer); - await this._pc.setRemoteDescription(offer); - const answer = await this._pc.createAnswer(); - logger.debug('stopReceiving() | calling pc.setLocalDescription() [answer:%o]', answer); - await this._pc.setLocalDescription(answer); - this._mapMidTransceiver.delete(localId); - } - async pauseReceiving( - // eslint-disable-next-line @typescript-eslint/no-unused-vars - localId) { - // Unimplemented. - } - async resumeReceiving( - // eslint-disable-next-line @typescript-eslint/no-unused-vars - localId) { - // Unimplemented. + await new Promise((resolve, reject) => { + this.safeEmit('@setrtpencodingparameters', params, resolve, reject); + }); } - async getReceiverStats(localId) { - this._assertRecvDirection(); - const transceiver = this._mapMidTransceiver.get(localId); - if (!transceiver) - throw new Error('associated RTCRtpTransceiver not found'); - return transceiver.receiver.getStats(); + onTrackEnded() { + logger.debug('track "ended" event'); + this.safeEmit('trackended'); + // Emit observer event. + this._observer.safeEmit('trackended'); } - async receiveDataChannel({ sctpStreamParameters, label, protocol }) { - var _a; - this._assertRecvDirection(); - const { streamId, ordered, maxPacketLifeTime, maxRetransmits } = sctpStreamParameters; - const options = { - negotiated: true, - id: streamId, - ordered, - maxPacketLifeTime, - maxRetransmitTime: maxPacketLifeTime, - maxRetransmits, - protocol - }; - logger.debug('receiveDataChannel() [options:%o]', options); - const dataChannel = this._pc.createDataChannel(label, options); - // If this is the first DataChannel we need to create the SDP offer with - // m=application section. - if (!this._hasDataChannelMediaSection) { - this._remoteSdp.receiveSctpAssociation(); - const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; - logger.debug('receiveDataChannel() | calling pc.setRemoteDescription() [offer:%o]', offer); - await this._pc.setRemoteDescription(offer); - const answer = await this._pc.createAnswer(); - if (!this._transportReady) { - const localSdpObject = sdpTransform.parse(answer.sdp); - await this._setupTransport({ - localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', - localSdpObject - }); - } - logger.debug('receiveDataChannel() | calling pc.setRemoteDescription() [answer:%o]', answer); - await this._pc.setLocalDescription(answer); - this._hasDataChannelMediaSection = true; + handleTrack() { + if (!this._track) { + return; } - return { dataChannel }; - } - async _setupTransport({ localDtlsRole, localSdpObject }) { - if (!localSdpObject) - localSdpObject = sdpTransform.parse(this._pc.localDescription.sdp); - // Get our local DTLS parameters. - const dtlsParameters = sdpCommonUtils.extractDtlsParameters({ sdpObject: localSdpObject }); - // Set our DTLS role. - dtlsParameters.role = localDtlsRole; - // Update the remote DTLS role in the SDP. - this._remoteSdp.updateDtlsRole(localDtlsRole === 'client' ? 'server' : 'client'); - // Need to tell the remote transport about our parameters. - await this.safeEmitAsPromise('@connect', { dtlsParameters }); - this._transportReady = true; + this._track.addEventListener('ended', this.onTrackEnded); } - _assertSendDirection() { - if (this._direction !== 'send') { - throw new Error('method can just be called for handlers with "send" direction'); + destroyTrack() { + if (!this._track) { + return; } - } - _assertRecvDirection() { - if (this._direction !== 'recv') { - throw new Error('method can just be called for handlers with "recv" direction'); + try { + this._track.removeEventListener('ended', this.onTrackEnded); + // Just stop the track unless the app set stopTracks: false. + if (this._stopTracks) { + this._track.stop(); + } } + catch (error) { } } } -exports.Chrome70 = Chrome70; +exports.Producer = Producer; + +},{"./EnhancedEventEmitter":16,"./Logger":17,"./errors":22}],19:[function(require,module,exports){ +"use strict"; +/** + * The RTP capabilities define what mediasoup or an endpoint can receive at + * media level. + */ +Object.defineProperty(exports, "__esModule", { value: true }); + +},{}],20:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); -},{"../Logger":13,"../ortc":36,"../scalabilityModes":37,"../utils":39,"./HandlerInterface":25,"./sdp/RemoteSdp":31,"./sdp/commonUtils":32,"./sdp/unifiedPlanUtils":34,"sdp-transform":44}],22:[function(require,module,exports){ +},{}],21:[function(require,module,exports){ "use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; @@ -4844,960 +2813,853 @@ var __importStar = (this && this.__importStar) || function (mod) { __setModuleDefault(result, mod); return result; }; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; Object.defineProperty(exports, "__esModule", { value: true }); -exports.Chrome74 = void 0; -const sdpTransform = __importStar(require("sdp-transform")); -const Logger_1 = require("../Logger"); -const utils = __importStar(require("../utils")); -const ortc = __importStar(require("../ortc")); -const sdpCommonUtils = __importStar(require("./sdp/commonUtils")); -const sdpUnifiedPlanUtils = __importStar(require("./sdp/unifiedPlanUtils")); -const HandlerInterface_1 = require("./HandlerInterface"); -const RemoteSdp_1 = require("./sdp/RemoteSdp"); -const scalabilityModes_1 = require("../scalabilityModes"); -const logger = new Logger_1.Logger('Chrome74'); -const SCTP_NUM_STREAMS = { OS: 1024, MIS: 1024 }; -class Chrome74 extends HandlerInterface_1.HandlerInterface { - constructor() { - super(); - // Map of RTCTransceivers indexed by MID. - this._mapMidTransceiver = new Map(); - // Local stream for sending. - this._sendStream = new MediaStream(); - // Whether a DataChannel m=application section has been created. - this._hasDataChannelMediaSection = false; - // Sending DataChannel id value counter. Incremented for each new DataChannel. - this._nextSendSctpStreamId = 0; - // Got transport local and remote parameters. - this._transportReady = false; - } +exports.Transport = void 0; +const awaitqueue_1 = require("awaitqueue"); +const queue_microtask_1 = __importDefault(require("queue-microtask")); +const Logger_1 = require("./Logger"); +const EnhancedEventEmitter_1 = require("./EnhancedEventEmitter"); +const errors_1 = require("./errors"); +const utils = __importStar(require("./utils")); +const ortc = __importStar(require("./ortc")); +const Producer_1 = require("./Producer"); +const Consumer_1 = require("./Consumer"); +const DataProducer_1 = require("./DataProducer"); +const DataConsumer_1 = require("./DataConsumer"); +const logger = new Logger_1.Logger('Transport'); +class ConsumerCreationTask { + constructor(consumerOptions) { + this.consumerOptions = consumerOptions; + this.promise = new Promise((resolve, reject) => { + this.resolve = resolve; + this.reject = reject; + }); + } +} +class Transport extends EnhancedEventEmitter_1.EnhancedEventEmitter { + constructor({ direction, id, iceParameters, iceCandidates, dtlsParameters, sctpParameters, iceServers, iceTransportPolicy, additionalSettings, proprietaryConstraints, appData, handlerFactory, extendedRtpCapabilities, canProduceByKind }) { + super(); + // Closed flag. + this._closed = false; + // Transport connection state. + this._connectionState = 'new'; + // Map of Producers indexed by id. + this._producers = new Map(); + // Map of Consumers indexed by id. + this._consumers = new Map(); + // Map of DataProducers indexed by id. + this._dataProducers = new Map(); + // Map of DataConsumers indexed by id. + this._dataConsumers = new Map(); + // Whether the Consumer for RTP probation has been created. + this._probatorConsumerCreated = false; + // AwaitQueue instance to make async tasks happen sequentially. + this._awaitQueue = new awaitqueue_1.AwaitQueue(); + // Consumer creation tasks awaiting to be processed. + this._pendingConsumerTasks = []; + // Consumer creation in progress flag. + this._consumerCreationInProgress = false; + // Consumers pending to be paused. + this._pendingPauseConsumers = new Map(); + // Consumer pause in progress flag. + this._consumerPauseInProgress = false; + // Consumers pending to be resumed. + this._pendingResumeConsumers = new Map(); + // Consumer resume in progress flag. + this._consumerResumeInProgress = false; + // Consumers pending to be closed. + this._pendingCloseConsumers = new Map(); + // Consumer close in progress flag. + this._consumerCloseInProgress = false; + // Observer instance. + this._observer = new EnhancedEventEmitter_1.EnhancedEventEmitter(); + logger.debug('constructor() [id:%s, direction:%s]', id, direction); + this._id = id; + this._direction = direction; + this._extendedRtpCapabilities = extendedRtpCapabilities; + this._canProduceByKind = canProduceByKind; + this._maxSctpMessageSize = + sctpParameters ? sctpParameters.maxMessageSize : null; + // Clone and sanitize additionalSettings. + additionalSettings = utils.clone(additionalSettings, {}); + delete additionalSettings.iceServers; + delete additionalSettings.iceTransportPolicy; + delete additionalSettings.bundlePolicy; + delete additionalSettings.rtcpMuxPolicy; + delete additionalSettings.sdpSemantics; + this._handler = handlerFactory(); + this._handler.run({ + direction, + iceParameters, + iceCandidates, + dtlsParameters, + sctpParameters, + iceServers, + iceTransportPolicy, + additionalSettings, + proprietaryConstraints, + extendedRtpCapabilities + }); + this._appData = appData || {}; + this.handleHandler(); + } /** - * Creates a factory function. + * Transport id. */ - static createFactory() { - return () => new Chrome74(); + get id() { + return this._id; } - get name() { - return 'Chrome74'; + /** + * Whether the Transport is closed. + */ + get closed() { + return this._closed; + } + /** + * Transport direction. + */ + get direction() { + return this._direction; + } + /** + * RTC handler instance. + */ + get handler() { + return this._handler; + } + /** + * Connection state. + */ + get connectionState() { + return this._connectionState; + } + /** + * App custom data. + */ + get appData() { + return this._appData; + } + /** + * Invalid setter. + */ + // eslint-disable-next-line @typescript-eslint/no-unused-vars + set appData(appData) { + throw new Error('cannot override appData object'); + } + get observer() { + return this._observer; } + /** + * Close the Transport. + */ close() { + if (this._closed) { + return; + } logger.debug('close()'); - // Close RTCPeerConnection. - if (this._pc) { - try { - this._pc.close(); - } - catch (error) { } + this._closed = true; + // Stop the AwaitQueue. + this._awaitQueue.stop(); + // Close the handler. + this._handler.close(); + // Close all Producers. + for (const producer of this._producers.values()) { + producer.transportClosed(); } - } - async getNativeRtpCapabilities() { - logger.debug('getNativeRtpCapabilities()'); - const pc = new RTCPeerConnection({ - iceServers: [], - iceTransportPolicy: 'all', - bundlePolicy: 'max-bundle', - rtcpMuxPolicy: 'require', - sdpSemantics: 'unified-plan' - }); - try { - pc.addTransceiver('audio'); - pc.addTransceiver('video'); - const offer = await pc.createOffer(); - try { - pc.close(); - } - catch (error) { } - const sdpObject = sdpTransform.parse(offer.sdp); - const nativeRtpCapabilities = sdpCommonUtils.extractRtpCapabilities({ sdpObject }); - return nativeRtpCapabilities; + this._producers.clear(); + // Close all Consumers. + for (const consumer of this._consumers.values()) { + consumer.transportClosed(); } - catch (error) { - try { - pc.close(); - } - catch (error2) { } - throw error; + this._consumers.clear(); + // Close all DataProducers. + for (const dataProducer of this._dataProducers.values()) { + dataProducer.transportClosed(); + } + this._dataProducers.clear(); + // Close all DataConsumers. + for (const dataConsumer of this._dataConsumers.values()) { + dataConsumer.transportClosed(); } + this._dataConsumers.clear(); + // Emit observer event. + this._observer.safeEmit('close'); } - async getNativeSctpCapabilities() { - logger.debug('getNativeSctpCapabilities()'); - return { - numStreams: SCTP_NUM_STREAMS - }; + /** + * Get associated Transport (RTCPeerConnection) stats. + * + * @returns {RTCStatsReport} + */ + async getStats() { + if (this._closed) { + throw new errors_1.InvalidStateError('closed'); + } + return this._handler.getTransportStats(); } - run({ direction, iceParameters, iceCandidates, dtlsParameters, sctpParameters, iceServers, iceTransportPolicy, additionalSettings, proprietaryConstraints, extendedRtpCapabilities }) { - logger.debug('run()'); - this._direction = direction; - this._remoteSdp = new RemoteSdp_1.RemoteSdp({ - iceParameters, - iceCandidates, - dtlsParameters, - sctpParameters - }); - this._sendingRtpParametersByKind = - { - audio: ortc.getSendingRtpParameters('audio', extendedRtpCapabilities), - video: ortc.getSendingRtpParameters('video', extendedRtpCapabilities) - }; - this._sendingRemoteRtpParametersByKind = - { - audio: ortc.getSendingRemoteRtpParameters('audio', extendedRtpCapabilities), - video: ortc.getSendingRemoteRtpParameters('video', extendedRtpCapabilities) - }; - if (dtlsParameters.role && dtlsParameters.role !== 'auto') { - this._forcedLocalDtlsRole = dtlsParameters.role === 'server' - ? 'client' - : 'server'; + /** + * Restart ICE connection. + */ + async restartIce({ iceParameters }) { + logger.debug('restartIce()'); + if (this._closed) { + throw new errors_1.InvalidStateError('closed'); } - this._pc = new RTCPeerConnection({ - iceServers: iceServers || [], - iceTransportPolicy: iceTransportPolicy || 'all', - bundlePolicy: 'max-bundle', - rtcpMuxPolicy: 'require', - sdpSemantics: 'unified-plan', - ...additionalSettings - }, proprietaryConstraints); - // Handle RTCPeerConnection connection status. - this._pc.addEventListener('iceconnectionstatechange', () => { - switch (this._pc.iceConnectionState) { - 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; - } - }); + else if (!iceParameters) { + throw new TypeError('missing iceParameters'); + } + // Enqueue command. + return this._awaitQueue.push(async () => this._handler.restartIce(iceParameters), 'transport.restartIce()'); } - async updateIceServers(iceServers) { + /** + * Update ICE servers. + */ + async updateIceServers({ iceServers } = {}) { logger.debug('updateIceServers()'); - const configuration = this._pc.getConfiguration(); - configuration.iceServers = iceServers; - this._pc.setConfiguration(configuration); - } - async restartIce(iceParameters) { - logger.debug('restartIce()'); - // Provide the remote SDP handler with new remote ICE parameters. - this._remoteSdp.updateIceParameters(iceParameters); - if (!this._transportReady) - return; - if (this._direction === 'send') { - const offer = await this._pc.createOffer({ iceRestart: true }); - logger.debug('restartIce() | calling pc.setLocalDescription() [offer:%o]', offer); - await this._pc.setLocalDescription(offer); - const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; - logger.debug('restartIce() | calling pc.setRemoteDescription() [answer:%o]', answer); - await this._pc.setRemoteDescription(answer); + if (this._closed) { + throw new errors_1.InvalidStateError('closed'); } - else { - const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; - logger.debug('restartIce() | calling pc.setRemoteDescription() [offer:%o]', offer); - await this._pc.setRemoteDescription(offer); - const answer = await this._pc.createAnswer(); - logger.debug('restartIce() | calling pc.setLocalDescription() [answer:%o]', answer); - await this._pc.setLocalDescription(answer); + else if (!Array.isArray(iceServers)) { + throw new TypeError('missing iceServers'); } + // Enqueue command. + return this._awaitQueue.push(async () => this._handler.updateIceServers(iceServers), 'transport.updateIceServers()'); } - async getTransportStats() { - return this._pc.getStats(); - } - async send({ track, encodings, codecOptions, codec }) { - var _a; - this._assertSendDirection(); - logger.debug('send() [kind:%s, track.id:%s]', track.kind, track.id); - if (encodings && encodings.length > 1) { - encodings.forEach((encoding, idx) => { - encoding.rid = `r${idx}`; - }); + /** + * Create a Producer. + */ + async produce({ track, encodings, codecOptions, codec, stopTracks = true, disableTrackOnPause = true, zeroRtpOnPause = false, appData = {} } = {}) { + logger.debug('produce() [track:%o]', track); + if (this._closed) { + throw new errors_1.InvalidStateError('closed'); } - const sendingRtpParameters = utils.clone(this._sendingRtpParametersByKind[track.kind], {}); - // This may throw. - sendingRtpParameters.codecs = - ortc.reduceCodecs(sendingRtpParameters.codecs, codec); - const sendingRemoteRtpParameters = utils.clone(this._sendingRemoteRtpParametersByKind[track.kind], {}); - // This may throw. - sendingRemoteRtpParameters.codecs = - ortc.reduceCodecs(sendingRemoteRtpParameters.codecs, codec); - const mediaSectionIdx = this._remoteSdp.getNextMediaSectionIdx(); - const transceiver = this._pc.addTransceiver(track, { - direction: 'sendonly', - streams: [this._sendStream], - sendEncodings: encodings - }); - let offer = await this._pc.createOffer(); - let localSdpObject = sdpTransform.parse(offer.sdp); - let offerMediaObject; - if (!this._transportReady) { - await this._setupTransport({ - localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', - localSdpObject - }); + else if (!track) { + throw new TypeError('missing track'); } - // Special case for VP9 with SVC. - let hackVp9Svc = false; - const layers = (0, scalabilityModes_1.parse)((encodings || [{}])[0].scalabilityMode); - if (encodings && - encodings.length === 1 && - layers.spatialLayers > 1 && - sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/vp9') { - logger.debug('send() | enabling legacy simulcast for VP9 SVC'); - hackVp9Svc = true; - localSdpObject = sdpTransform.parse(offer.sdp); - offerMediaObject = localSdpObject.media[mediaSectionIdx.idx]; - sdpUnifiedPlanUtils.addLegacySimulcast({ - offerMediaObject, - numStreams: layers.spatialLayers - }); - offer = { type: 'offer', sdp: sdpTransform.write(localSdpObject) }; + else if (this._direction !== 'send') { + throw new errors_1.UnsupportedError('not a sending Transport'); } - logger.debug('send() | calling pc.setLocalDescription() [offer:%o]', offer); - await this._pc.setLocalDescription(offer); - // We can now get the transceiver.mid. - const localId = transceiver.mid; - // Set MID. - sendingRtpParameters.mid = localId; - localSdpObject = sdpTransform.parse(this._pc.localDescription.sdp); - offerMediaObject = localSdpObject.media[mediaSectionIdx.idx]; - // Set RTCP CNAME. - sendingRtpParameters.rtcp.cname = - sdpCommonUtils.getCname({ offerMediaObject }); - // Set RTP encodings by parsing the SDP offer if no encodings are given. - if (!encodings) { - sendingRtpParameters.encodings = - sdpUnifiedPlanUtils.getRtpEncodings({ offerMediaObject }); + else if (!this._canProduceByKind[track.kind]) { + throw new errors_1.UnsupportedError(`cannot produce ${track.kind}`); } - // Set RTP encodings by parsing the SDP offer and complete them with given - // one if just a single encoding has been given. - else if (encodings.length === 1) { - let newEncodings = sdpUnifiedPlanUtils.getRtpEncodings({ offerMediaObject }); - Object.assign(newEncodings[0], encodings[0]); - // Hack for VP9 SVC. - if (hackVp9Svc) - newEncodings = [newEncodings[0]]; - sendingRtpParameters.encodings = newEncodings; + else if (track.readyState === 'ended') { + throw new errors_1.InvalidStateError('track ended'); } - // Otherwise if more than 1 encoding are given use them verbatim. - else { - sendingRtpParameters.encodings = encodings; + else if (this.listenerCount('connect') === 0 && this._connectionState === 'new') { + throw new TypeError('no "connect" listener set into this transport'); } - // If VP8 or H264 and there is effective simulcast, add scalabilityMode to - // each encoding. - if (sendingRtpParameters.encodings.length > 1 && - (sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/vp8' || - sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/h264')) { - for (const encoding of sendingRtpParameters.encodings) { - encoding.scalabilityMode = 'S1T3'; - } + else if (this.listenerCount('produce') === 0) { + throw new TypeError('no "produce" listener set into this transport'); } - this._remoteSdp.send({ - offerMediaObject, - reuseMid: mediaSectionIdx.reuseMid, - offerRtpParameters: sendingRtpParameters, - answerRtpParameters: sendingRemoteRtpParameters, - codecOptions, - extmapAllowMixed: true + else if (appData && typeof appData !== 'object') { + throw new TypeError('if given, appData must be an object'); + } + // Enqueue command. + return this._awaitQueue.push(async () => { + let normalizedEncodings; + if (encodings && !Array.isArray(encodings)) { + throw TypeError('encodings must be an array'); + } + else if (encodings && encodings.length === 0) { + normalizedEncodings = undefined; + } + else if (encodings) { + normalizedEncodings = encodings + .map((encoding) => { + const normalizedEncoding = { active: true }; + if (encoding.active === false) { + normalizedEncoding.active = false; + } + if (typeof encoding.dtx === 'boolean') { + normalizedEncoding.dtx = encoding.dtx; + } + if (typeof encoding.scalabilityMode === 'string') { + normalizedEncoding.scalabilityMode = encoding.scalabilityMode; + } + if (typeof encoding.scaleResolutionDownBy === 'number') { + normalizedEncoding.scaleResolutionDownBy = encoding.scaleResolutionDownBy; + } + if (typeof encoding.maxBitrate === 'number') { + normalizedEncoding.maxBitrate = encoding.maxBitrate; + } + if (typeof encoding.maxFramerate === 'number') { + normalizedEncoding.maxFramerate = encoding.maxFramerate; + } + if (typeof encoding.adaptivePtime === 'boolean') { + normalizedEncoding.adaptivePtime = encoding.adaptivePtime; + } + if (typeof encoding.priority === 'string') { + normalizedEncoding.priority = encoding.priority; + } + if (typeof encoding.networkPriority === 'string') { + normalizedEncoding.networkPriority = encoding.networkPriority; + } + return normalizedEncoding; + }); + } + const { localId, rtpParameters, rtpSender } = await this._handler.send({ + track, + encodings: normalizedEncodings, + codecOptions, + codec + }); + try { + // This will fill rtpParameters's missing fields with default values. + ortc.validateRtpParameters(rtpParameters); + const { id } = await new Promise((resolve, reject) => { + this.safeEmit('produce', { + kind: track.kind, + rtpParameters, + appData + }, resolve, reject); + }); + const producer = new Producer_1.Producer({ + id, + localId, + rtpSender, + track, + rtpParameters, + stopTracks, + disableTrackOnPause, + zeroRtpOnPause, + appData + }); + this._producers.set(producer.id, producer); + this.handleProducer(producer); + // Emit observer event. + this._observer.safeEmit('newproducer', producer); + return producer; + } + catch (error) { + this._handler.stopSending(localId) + .catch(() => { }); + throw error; + } + }, 'transport.produce()') + // This catch is needed to stop the given track if the command above + // failed due to closed Transport. + .catch((error) => { + if (stopTracks) { + try { + track.stop(); + } + catch (error2) { } + } + throw error; }); - const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; - logger.debug('send() | calling pc.setRemoteDescription() [answer:%o]', answer); - await this._pc.setRemoteDescription(answer); - // Store in the map. - this._mapMidTransceiver.set(localId, transceiver); - return { - localId, - rtpParameters: sendingRtpParameters, - rtpSender: transceiver.sender - }; - } - async stopSending(localId) { - this._assertSendDirection(); - logger.debug('stopSending() [localId:%s]', localId); - const transceiver = this._mapMidTransceiver.get(localId); - if (!transceiver) - throw new Error('associated RTCRtpTransceiver not found'); - transceiver.sender.replaceTrack(null); - this._pc.removeTrack(transceiver.sender); - this._remoteSdp.closeMediaSection(transceiver.mid); - const offer = await this._pc.createOffer(); - logger.debug('stopSending() | calling pc.setLocalDescription() [offer:%o]', offer); - await this._pc.setLocalDescription(offer); - const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; - logger.debug('stopSending() | calling pc.setRemoteDescription() [answer:%o]', answer); - await this._pc.setRemoteDescription(answer); - this._mapMidTransceiver.delete(localId); } - async replaceTrack(localId, track) { - this._assertSendDirection(); - if (track) { - logger.debug('replaceTrack() [localId:%s, track.id:%s]', localId, track.id); + /** + * Create a Consumer to consume a remote Producer. + */ + async consume({ id, producerId, kind, rtpParameters, streamId, appData = {} }) { + logger.debug('consume()'); + rtpParameters = utils.clone(rtpParameters, undefined); + if (this._closed) { + throw new errors_1.InvalidStateError('closed'); } - else { - logger.debug('replaceTrack() [localId:%s, no track]', localId); + else if (this._direction !== 'recv') { + throw new errors_1.UnsupportedError('not a receiving Transport'); } - const transceiver = this._mapMidTransceiver.get(localId); - if (!transceiver) - throw new Error('associated RTCRtpTransceiver not found'); - await transceiver.sender.replaceTrack(track); - } - async setMaxSpatialLayer(localId, spatialLayer) { - this._assertSendDirection(); - logger.debug('setMaxSpatialLayer() [localId:%s, spatialLayer:%s]', localId, spatialLayer); - const transceiver = this._mapMidTransceiver.get(localId); - if (!transceiver) - throw new Error('associated RTCRtpTransceiver not found'); - const parameters = transceiver.sender.getParameters(); - parameters.encodings.forEach((encoding, idx) => { - if (idx <= spatialLayer) - encoding.active = true; - else - encoding.active = false; - }); - await transceiver.sender.setParameters(parameters); - } - async setRtpEncodingParameters(localId, params) { - this._assertSendDirection(); - logger.debug('setRtpEncodingParameters() [localId:%s, params:%o]', localId, params); - const transceiver = this._mapMidTransceiver.get(localId); - if (!transceiver) - throw new Error('associated RTCRtpTransceiver not found'); - const parameters = transceiver.sender.getParameters(); - parameters.encodings.forEach((encoding, idx) => { - parameters.encodings[idx] = { ...encoding, ...params }; - }); - await transceiver.sender.setParameters(parameters); - } - async getSenderStats(localId) { - this._assertSendDirection(); - const transceiver = this._mapMidTransceiver.get(localId); - if (!transceiver) - throw new Error('associated RTCRtpTransceiver not found'); - return transceiver.sender.getStats(); - } - async sendDataChannel({ ordered, maxPacketLifeTime, maxRetransmits, label, protocol }) { - var _a; - this._assertSendDirection(); - const options = { - negotiated: true, - id: this._nextSendSctpStreamId, - ordered, - maxPacketLifeTime, - maxRetransmits, - protocol - }; - logger.debug('sendDataChannel() [options:%o]', options); - const dataChannel = this._pc.createDataChannel(label, options); - // Increase next id. - this._nextSendSctpStreamId = - ++this._nextSendSctpStreamId % SCTP_NUM_STREAMS.MIS; - // If this is the first DataChannel we need to create the SDP answer with - // m=application section. - if (!this._hasDataChannelMediaSection) { - const offer = await this._pc.createOffer(); - const localSdpObject = sdpTransform.parse(offer.sdp); - const offerMediaObject = localSdpObject.media - .find((m) => m.type === 'application'); - if (!this._transportReady) { - await this._setupTransport({ - localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', - localSdpObject - }); - } - logger.debug('sendDataChannel() | calling pc.setLocalDescription() [offer:%o]', offer); - await this._pc.setLocalDescription(offer); - this._remoteSdp.sendSctpAssociation({ offerMediaObject }); - const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; - logger.debug('sendDataChannel() | calling pc.setRemoteDescription() [answer:%o]', answer); - await this._pc.setRemoteDescription(answer); - this._hasDataChannelMediaSection = true; + else if (typeof id !== 'string') { + throw new TypeError('missing id'); } - const sctpStreamParameters = { - streamId: options.id, - ordered: options.ordered, - maxPacketLifeTime: options.maxPacketLifeTime, - maxRetransmits: options.maxRetransmits - }; - return { dataChannel, sctpStreamParameters }; - } - async receive({ trackId, kind, rtpParameters }) { - var _a; - this._assertRecvDirection(); - logger.debug('receive() [trackId:%s, kind:%s]', trackId, kind); - const localId = rtpParameters.mid || String(this._mapMidTransceiver.size); - this._remoteSdp.receive({ - mid: localId, - kind, - offerRtpParameters: rtpParameters, - streamId: rtpParameters.rtcp.cname, - trackId - }); - const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; - logger.debug('receive() | calling pc.setRemoteDescription() [offer:%o]', offer); - await this._pc.setRemoteDescription(offer); - let answer = await this._pc.createAnswer(); - const localSdpObject = sdpTransform.parse(answer.sdp); - const answerMediaObject = localSdpObject.media - .find((m) => String(m.mid) === localId); - // May need to modify codec parameters in the answer based on codec - // parameters in the offer. - sdpCommonUtils.applyCodecParameters({ - offerRtpParameters: rtpParameters, - answerMediaObject - }); - answer = { type: 'answer', sdp: sdpTransform.write(localSdpObject) }; - if (!this._transportReady) { - await this._setupTransport({ - localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', - localSdpObject - }); + else if (typeof producerId !== 'string') { + throw new TypeError('missing producerId'); } - logger.debug('receive() | calling pc.setLocalDescription() [answer:%o]', answer); - await this._pc.setLocalDescription(answer); - const transceiver = this._pc.getTransceivers() - .find((t) => t.mid === localId); - if (!transceiver) - throw new Error('new RTCRtpTransceiver not found'); - // Store in the map. - this._mapMidTransceiver.set(localId, transceiver); - return { - localId, - track: transceiver.receiver.track, - rtpReceiver: transceiver.receiver - }; - } - async stopReceiving(localId) { - this._assertRecvDirection(); - logger.debug('stopReceiving() [localId:%s]', localId); - const transceiver = this._mapMidTransceiver.get(localId); - if (!transceiver) - throw new Error('associated RTCRtpTransceiver not found'); - this._remoteSdp.closeMediaSection(transceiver.mid); - const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; - logger.debug('stopReceiving() | calling pc.setRemoteDescription() [offer:%o]', offer); - await this._pc.setRemoteDescription(offer); - const answer = await this._pc.createAnswer(); - logger.debug('stopReceiving() | calling pc.setLocalDescription() [answer:%o]', answer); - await this._pc.setLocalDescription(answer); - this._mapMidTransceiver.delete(localId); - } - async pauseReceiving(localId) { - this._assertRecvDirection(); - logger.debug('pauseReceiving() [localId:%s]', localId); - const transceiver = this._mapMidTransceiver.get(localId); - if (!transceiver) - throw new Error('associated RTCRtpTransceiver not found'); - transceiver.direction = 'inactive'; - const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; - logger.debug('pauseReceiving() | calling pc.setRemoteDescription() [offer:%o]', offer); - await this._pc.setRemoteDescription(offer); - const answer = await this._pc.createAnswer(); - logger.debug('pauseReceiving() | calling pc.setLocalDescription() [answer:%o]', answer); - await this._pc.setLocalDescription(answer); - } - async resumeReceiving(localId) { - this._assertRecvDirection(); - logger.debug('resumeReceiving() [localId:%s]', localId); - const transceiver = this._mapMidTransceiver.get(localId); - if (!transceiver) - throw new Error('associated RTCRtpTransceiver not found'); - transceiver.direction = 'recvonly'; - const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; - logger.debug('resumeReceiving() | calling pc.setRemoteDescription() [offer:%o]', offer); - await this._pc.setRemoteDescription(offer); - const answer = await this._pc.createAnswer(); - logger.debug('resumeReceiving() | calling pc.setLocalDescription() [answer:%o]', answer); - await this._pc.setLocalDescription(answer); - } - async getReceiverStats(localId) { - this._assertRecvDirection(); - const transceiver = this._mapMidTransceiver.get(localId); - if (!transceiver) - throw new Error('associated RTCRtpTransceiver not found'); - return transceiver.receiver.getStats(); - } - async receiveDataChannel({ sctpStreamParameters, label, protocol }) { - var _a; - this._assertRecvDirection(); - const { streamId, ordered, maxPacketLifeTime, maxRetransmits } = sctpStreamParameters; - const options = { - negotiated: true, - id: streamId, - ordered, - maxPacketLifeTime, - maxRetransmits, - protocol - }; - logger.debug('receiveDataChannel() [options:%o]', options); - const dataChannel = this._pc.createDataChannel(label, options); - // If this is the first DataChannel we need to create the SDP offer with - // m=application section. - if (!this._hasDataChannelMediaSection) { - this._remoteSdp.receiveSctpAssociation(); - const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; - logger.debug('receiveDataChannel() | calling pc.setRemoteDescription() [offer:%o]', offer); - await this._pc.setRemoteDescription(offer); - const answer = await this._pc.createAnswer(); - if (!this._transportReady) { - const localSdpObject = sdpTransform.parse(answer.sdp); - await this._setupTransport({ - localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', - localSdpObject - }); - } - logger.debug('receiveDataChannel() | calling pc.setRemoteDescription() [answer:%o]', answer); - await this._pc.setLocalDescription(answer); - this._hasDataChannelMediaSection = true; + else if (kind !== 'audio' && kind !== 'video') { + throw new TypeError(`invalid kind '${kind}'`); } - return { dataChannel }; - } - async _setupTransport({ localDtlsRole, localSdpObject }) { - if (!localSdpObject) - localSdpObject = sdpTransform.parse(this._pc.localDescription.sdp); - // Get our local DTLS parameters. - const dtlsParameters = sdpCommonUtils.extractDtlsParameters({ sdpObject: localSdpObject }); - // Set our DTLS role. - dtlsParameters.role = localDtlsRole; - // Update the remote DTLS role in the SDP. - this._remoteSdp.updateDtlsRole(localDtlsRole === 'client' ? 'server' : 'client'); - // Need to tell the remote transport about our parameters. - await this.safeEmitAsPromise('@connect', { dtlsParameters }); - this._transportReady = true; - } - _assertSendDirection() { - if (this._direction !== 'send') { - throw new Error('method can just be called for handlers with "send" direction'); + else if (this.listenerCount('connect') === 0 && this._connectionState === 'new') { + throw new TypeError('no "connect" listener set into this transport'); } - } - _assertRecvDirection() { - if (this._direction !== 'recv') { - throw new Error('method can just be called for handlers with "recv" direction'); + else if (appData && typeof appData !== 'object') { + throw new TypeError('if given, appData must be an object'); } - } -} -exports.Chrome74 = Chrome74; - -},{"../Logger":13,"../ortc":36,"../scalabilityModes":37,"../utils":39,"./HandlerInterface":25,"./sdp/RemoteSdp":31,"./sdp/commonUtils":32,"./sdp/unifiedPlanUtils":34,"sdp-transform":44}],23:[function(require,module,exports){ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.Edge11 = void 0; -const Logger_1 = require("../Logger"); -const errors_1 = require("../errors"); -const utils = __importStar(require("../utils")); -const ortc = __importStar(require("../ortc")); -const edgeUtils = __importStar(require("./ortc/edgeUtils")); -const HandlerInterface_1 = require("./HandlerInterface"); -const logger = new Logger_1.Logger('Edge11'); -class Edge11 extends HandlerInterface_1.HandlerInterface { - constructor() { - super(); - // Map of RTCRtpSenders indexed by id. - this._rtpSenders = new Map(); - // Map of RTCRtpReceivers indexed by id. - this._rtpReceivers = new Map(); - // Next localId for sending tracks. - this._nextSendLocalId = 0; - // Got transport local and remote parameters. - this._transportReady = false; + // Ensure the device can consume it. + const canConsume = ortc.canReceive(rtpParameters, this._extendedRtpCapabilities); + if (!canConsume) { + throw new errors_1.UnsupportedError('cannot consume this Producer'); + } + const consumerCreationTask = new ConsumerCreationTask({ + id, + producerId, + kind, + rtpParameters, + streamId, + appData + }); + // Store the Consumer creation task. + this._pendingConsumerTasks.push(consumerCreationTask); + // There is no Consumer creation in progress, create it now. + (0, queue_microtask_1.default)(() => { + if (this._closed) { + throw new errors_1.InvalidStateError('closed'); + } + if (this._consumerCreationInProgress === false) { + this.createPendingConsumers(); + } + }); + return consumerCreationTask.promise; } /** - * Creates a factory function. + * Create a DataProducer */ - static createFactory() { - return () => new Edge11(); - } - get name() { - return 'Edge11'; - } - close() { - logger.debug('close()'); - // Close the ICE gatherer. - // NOTE: Not yet implemented by Edge. - try { - this._iceGatherer.close(); + async produceData({ ordered = true, maxPacketLifeTime, maxRetransmits, label = '', protocol = '', appData = {} } = {}) { + logger.debug('produceData()'); + if (this._closed) { + throw new errors_1.InvalidStateError('closed'); } - catch (error) { } - // Close the ICE transport. - try { - this._iceTransport.stop(); + else if (this._direction !== 'send') { + throw new errors_1.UnsupportedError('not a sending Transport'); } - catch (error) { } - // Close the DTLS transport. - try { - this._dtlsTransport.stop(); + else if (!this._maxSctpMessageSize) { + throw new errors_1.UnsupportedError('SCTP not enabled by remote Transport'); } - catch (error) { } - // Close RTCRtpSenders. - for (const rtpSender of this._rtpSenders.values()) { - try { - rtpSender.stop(); - } - catch (error) { } + else if (this.listenerCount('connect') === 0 && this._connectionState === 'new') { + throw new TypeError('no "connect" listener set into this transport'); } - // Close RTCRtpReceivers. - for (const rtpReceiver of this._rtpReceivers.values()) { - try { - rtpReceiver.stop(); - } - catch (error) { } + else if (this.listenerCount('producedata') === 0) { + throw new TypeError('no "producedata" listener set into this transport'); } - } - 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); + else if (appData && typeof appData !== 'object') { + throw new TypeError('if given, appData must be an object'); } - 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() }; + if (maxPacketLifeTime || maxRetransmits) { + ordered = false; } - 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 }; + // Enqueue command. + return this._awaitQueue.push(async () => { + const { dataChannel, sctpStreamParameters } = await this._handler.sendDataChannel({ + ordered, + maxPacketLifeTime, + maxRetransmits, + label, + protocol + }); + // This will fill sctpStreamParameters's missing fields with default values. + ortc.validateSctpStreamParameters(sctpStreamParameters); + const { id } = await new Promise((resolve, reject) => { + this.safeEmit('producedata', { + sctpStreamParameters, + label, + protocol, + appData + }, resolve, reject); + }); + const dataProducer = new DataProducer_1.DataProducer({ id, dataChannel, sctpStreamParameters, appData }); + this._dataProducers.set(dataProducer.id, dataProducer); + this.handleDataProducer(dataProducer); + // Emit observer event. + this._observer.safeEmit('newdataproducer', dataProducer); + return dataProducer; + }, 'transport.produceData()'); } - 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; + /** + * Create a DataConsumer + */ + async consumeData({ id, dataProducerId, sctpStreamParameters, label = '', protocol = '', appData = {} }) { + logger.debug('consumeData()'); + sctpStreamParameters = utils.clone(sctpStreamParameters, undefined); + if (this._closed) { + throw new errors_1.InvalidStateError('closed'); } - } - async replaceTrack(localId, track) { - if (track) { - logger.debug('replaceTrack() [localId:%s, track.id:%s]', localId, track.id); + else if (this._direction !== 'recv') { + throw new errors_1.UnsupportedError('not a receiving Transport'); } - else { - logger.debug('replaceTrack() [localId:%s, no track]', localId); + else if (!this._maxSctpMessageSize) { + throw new errors_1.UnsupportedError('SCTP not enabled by remote Transport'); } - 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({ trackId, kind, rtpParameters }) { - logger.debug('receive() [trackId:%s, kind:%s]', trackId, kind); - if (!this._transportReady) - await this._setupTransport({ localDtlsRole: 'server' }); - 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); - return { - localId, - track: rtpReceiver.track, - rtpReceiver - }; - } - async stopReceiving(localId) { - 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(); + else if (typeof id !== 'string') { + throw new TypeError('missing id'); } - catch (error) { - logger.warn('stopReceiving() | rtpReceiver.stop() failed:%o', error); + else if (typeof dataProducerId !== 'string') { + throw new TypeError('missing dataProducerId'); } - } - async pauseReceiving( - // eslint-disable-next-line @typescript-eslint/no-unused-vars - localId) { - // Unimplemented. - } - async resumeReceiving( - // eslint-disable-next-line @typescript-eslint/no-unused-vars - localId) { - // 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-ignore - 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(); + else if (this.listenerCount('connect') === 0 && this._connectionState === 'new') { + throw new TypeError('no "connect" listener set into this transport'); } - catch (error) { - logger.debug('_setIceGatherer() | iceGatherer.gather() failed: %s', error.toString()); + else if (appData && typeof appData !== 'object') { + throw new TypeError('if given, appData must be an object'); } - this._iceGatherer = iceGatherer; + // This may throw. + ortc.validateSctpStreamParameters(sctpStreamParameters); + // Enqueue command. + return this._awaitQueue.push(async () => { + const { dataChannel } = await this._handler.receiveDataChannel({ + sctpStreamParameters, + label, + protocol + }); + const dataConsumer = new DataConsumer_1.DataConsumer({ + id, + dataProducerId, + dataChannel, + sctpStreamParameters, + appData + }); + this._dataConsumers.set(dataConsumer.id, dataConsumer); + this.handleDataConsumer(dataConsumer); + // Emit observer event. + this._observer.safeEmit('newdataconsumer', dataConsumer); + return dataConsumer; + }, 'transport.consumeData()'); } - _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; + // This method is guaranteed to never throw. + async createPendingConsumers() { + this._consumerCreationInProgress = true; + this._awaitQueue.push(async () => { + if (this._pendingConsumerTasks.length === 0) { + logger.debug('createPendingConsumers() | there is no Consumer to be created'); + return; } - }); - // 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; + const pendingConsumerTasks = [...this._pendingConsumerTasks]; + // Clear pending Consumer tasks. + this._pendingConsumerTasks = []; + // Video Consumer in order to create the probator. + let videoConsumerForProbator = undefined; + // Fill options list. + const optionsList = []; + for (const task of pendingConsumerTasks) { + const { id, kind, rtpParameters, streamId } = task.consumerOptions; + optionsList.push({ + trackId: id, + kind: kind, + rtpParameters, + streamId + }); + } + try { + const results = await this._handler.receive(optionsList); + for (let idx = 0; idx < results.length; idx++) { + const task = pendingConsumerTasks[idx]; + const result = results[idx]; + const { id, producerId, kind, rtpParameters, appData } = task.consumerOptions; + const { localId, rtpReceiver, track } = result; + const consumer = new Consumer_1.Consumer({ + id: id, + localId, + producerId: producerId, + rtpReceiver, + track, + rtpParameters, + appData + }); + this._consumers.set(consumer.id, consumer); + this.handleConsumer(consumer); + // If this is the first video Consumer and the Consumer for RTP probation + // has not yet been created, it's time to create it. + if (!this._probatorConsumerCreated && + !videoConsumerForProbator && kind === 'video') { + videoConsumerForProbator = consumer; + } + // Emit observer event. + this._observer.safeEmit('newconsumer', consumer); + task.resolve(consumer); + } + } + catch (error) { + for (const task of pendingConsumerTasks) { + task.reject(error); + } + } + // If RTP probation must be handled, do it now. + if (videoConsumerForProbator) { + try { + const probatorRtpParameters = ortc.generateProbatorRtpParameters(videoConsumerForProbator.rtpParameters); + await this._handler.receive([{ + trackId: 'probator', + kind: 'video', + rtpParameters: probatorRtpParameters + }]); + logger.debug('createPendingConsumers() | Consumer for RTP probation created'); + this._probatorConsumerCreated = true; + } + catch (error) { + logger.error('createPendingConsumers() | failed to create Consumer for RTP probation:%o', error); + } + } + }, 'transport.createPendingConsumers()') + .then(() => { + this._consumerCreationInProgress = false; + // There are pending Consumer tasks, enqueue their creation. + if (this._pendingConsumerTasks.length > 0) { + this.createPendingConsumers(); + } + }) + // NOTE: We only get here when the await queue is closed. + .catch(() => { }); + } + pausePendingConsumers() { + this._consumerPauseInProgress = true; + this._awaitQueue.push(async () => { + if (this._pendingPauseConsumers.size === 0) { + logger.debug('pausePendingConsumers() | there is no Consumer to be paused'); + return; + } + const pendingPauseConsumers = Array.from(this._pendingPauseConsumers.values()); + // Clear pending pause Consumer map. + this._pendingPauseConsumers.clear(); + try { + const localIds = pendingPauseConsumers + .map((consumer) => consumer.localId); + await this._handler.pauseReceiving(localIds); + } + catch (error) { + logger.error('pausePendingConsumers() | failed to pause Consumers:', error); + } + }, 'transport.pausePendingConsumers') + .then(() => { + this._consumerPauseInProgress = false; + // There are pending Consumers to be paused, do it. + if (this._pendingPauseConsumers.size > 0) { + this.pausePendingConsumers(); + } + }) + // NOTE: We only get here when the await queue is closed. + .catch(() => { }); + } + resumePendingConsumers() { + this._consumerResumeInProgress = true; + this._awaitQueue.push(async () => { + if (this._pendingResumeConsumers.size === 0) { + logger.debug('resumePendingConsumers() | there is no Consumer to be resumed'); + return; + } + const pendingResumeConsumers = Array.from(this._pendingResumeConsumers.values()); + // Clear pending resume Consumer map. + this._pendingResumeConsumers.clear(); + try { + const localIds = pendingResumeConsumers + .map((consumer) => consumer.localId); + await this._handler.resumeReceiving(localIds); + } + catch (error) { + logger.error('resumePendingConsumers() | failed to resume Consumers:', error); } + }, 'transport.resumePendingConsumers') + .then(() => { + this._consumerResumeInProgress = false; + // There are pending Consumer to be resumed, do it. + if (this._pendingResumeConsumers.size > 0) { + this.resumePendingConsumers(); + } + }) + // NOTE: We only get here when the await queue is closed. + .catch(() => { }); + } + closePendingConsumers() { + this._consumerCloseInProgress = true; + this._awaitQueue.push(async () => { + if (this._pendingCloseConsumers.size === 0) { + logger.debug('closePendingConsumers() | there is no Consumer to be closed'); + return; + } + const pendingCloseConsumers = Array.from(this._pendingCloseConsumers.values()); + // Clear pending close Consumer map. + this._pendingCloseConsumers.clear(); + try { + await this._handler.stopReceiving(pendingCloseConsumers.map((consumer) => consumer.localId)); + } + catch (error) { + logger.error('closePendingConsumers() | failed to close Consumers:', error); + } + }, 'transport.closePendingConsumers') + .then(() => { + this._consumerCloseInProgress = false; + // There are pending Consumer to be resumed, do it. + if (this._pendingCloseConsumers.size > 0) { + this.closePendingConsumers(); + } + }) + // NOTE: We only get here when the await queue is closed. + .catch(() => { }); + } + handleHandler() { + const handler = this._handler; + handler.on('@connect', ({ dtlsParameters }, callback, errback) => { + if (this._closed) { + errback(new errors_1.InvalidStateError('closed')); + return; + } + this.safeEmit('connect', { dtlsParameters }, callback, errback); }); - iceTransport.addEventListener('candidatepairchange', (event) => { - logger.debug('iceTransport "candidatepairchange" event [pair:%o]', event.pair); + handler.on('@connectionstatechange', (connectionState) => { + if (connectionState === this._connectionState) { + return; + } + logger.debug('connection state changed to %s', connectionState); + this._connectionState = connectionState; + if (!this._closed) { + this.safeEmit('connectionstatechange', connectionState); + } }); - 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); + handleProducer(producer) { + producer.on('@close', () => { + this._producers.delete(producer.id); + if (this._closed) { + return; + } + this._awaitQueue.push(async () => this._handler.stopSending(producer.localId), 'producer @close event') + .catch((error) => logger.warn('producer.close() failed:%o', error)); }); - // 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'); + producer.on('@pause', (callback, errback) => { + this._awaitQueue.push(async () => this._handler.pauseSending(producer.localId), 'producer @pause event') + .then(callback) + .catch(errback); }); - dtlsTransport.addEventListener('error', (event) => { - logger.error('dtlsTransport "error" event [event:%o]', event); + producer.on('@resume', (callback, errback) => { + this._awaitQueue.push(async () => this._handler.resumeSending(producer.localId), 'producer @resume event') + .then(callback) + .catch(errback); + }); + producer.on('@replacetrack', (track, callback, errback) => { + this._awaitQueue.push(async () => this._handler.replaceTrack(producer.localId, track), 'producer @replacetrack event') + .then(callback) + .catch(errback); + }); + producer.on('@setmaxspatiallayer', (spatialLayer, callback, errback) => { + this._awaitQueue.push(async () => (this._handler.setMaxSpatialLayer(producer.localId, spatialLayer)), 'producer @setmaxspatiallayer event') + .then(callback) + .catch(errback); + }); + producer.on('@setrtpencodingparameters', (params, callback, errback) => { + this._awaitQueue.push(async () => (this._handler.setRtpEncodingParameters(producer.localId, params)), 'producer @setrtpencodingparameters event') + .then(callback) + .catch(errback); + }); + producer.on('@getstats', (callback, errback) => { + if (this._closed) { + return errback(new errors_1.InvalidStateError('closed')); + } + this._handler.getSenderStats(producer.localId) + .then(callback) + .catch(errback); }); - 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 this.safeEmitAsPromise('@connect', { dtlsParameters }); - // 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'); + handleConsumer(consumer) { + consumer.on('@close', () => { + this._consumers.delete(consumer.id); + this._pendingPauseConsumers.delete(consumer.id); + this._pendingResumeConsumers.delete(consumer.id); + if (this._closed) { + return; + } + // Store the Consumer into the close list. + this._pendingCloseConsumers.set(consumer.id, consumer); + // There is no Consumer close in progress, do it now. + if (this._consumerCloseInProgress === false) { + this.closePendingConsumers(); + } + }); + consumer.on('@pause', () => { + // If Consumer is pending to be resumed, remove from pending resume list. + if (this._pendingResumeConsumers.has(consumer.id)) { + this._pendingResumeConsumers.delete(consumer.id); + } + // Store the Consumer into the pending list. + this._pendingPauseConsumers.set(consumer.id, consumer); + // There is no Consumer pause in progress, do it now. + (0, queue_microtask_1.default)(() => { + if (this._closed) { + return; + } + if (this._consumerPauseInProgress === false) { + this.pausePendingConsumers(); + } + }); + }); + consumer.on('@resume', () => { + // If Consumer is pending to be paused, remove from pending pause list. + if (this._pendingPauseConsumers.has(consumer.id)) { + this._pendingPauseConsumers.delete(consumer.id); + } + // Store the Consumer into the pending list. + this._pendingResumeConsumers.set(consumer.id, consumer); + // There is no Consumer resume in progress, do it now. + (0, queue_microtask_1.default)(() => { + if (this._closed) { + return; + } + if (this._consumerResumeInProgress === false) { + this.resumePendingConsumers(); + } + }); + }); + consumer.on('@getstats', (callback, errback) => { + if (this._closed) { + return errback(new errors_1.InvalidStateError('closed')); + } + this._handler.getReceiverStats(consumer.localId) + .then(callback) + .catch(errback); + }); + } + handleDataProducer(dataProducer) { + dataProducer.on('@close', () => { + this._dataProducers.delete(dataProducer.id); + }); + } + handleDataConsumer(dataConsumer) { + dataConsumer.on('@close', () => { + this._dataConsumers.delete(dataConsumer.id); }); - // Start the RTCDtlsTransport. - this._dtlsTransport.start(this._remoteDtlsParameters); - this._transportReady = true; } } -exports.Edge11 = Edge11; +exports.Transport = Transport; + +},{"./Consumer":12,"./DataConsumer":13,"./DataProducer":14,"./EnhancedEventEmitter":16,"./Logger":17,"./Producer":18,"./errors":22,"./ortc":43,"./utils":46,"awaitqueue":3,"queue-microtask":50}],22:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.InvalidStateError = exports.UnsupportedError = void 0; +/** + * Error indicating not support for something. + */ +class UnsupportedError extends Error { + constructor(message) { + super(message); + this.name = 'UnsupportedError'; + if (Error.hasOwnProperty('captureStackTrace')) // Just in V8. + { + // @ts-ignore + Error.captureStackTrace(this, UnsupportedError); + } + else { + this.stack = (new Error(message)).stack; + } + } +} +exports.UnsupportedError = UnsupportedError; +/** + * Error produced when calling a method in an invalid state. + */ +class InvalidStateError extends Error { + constructor(message) { + super(message); + this.name = 'InvalidStateError'; + if (Error.hasOwnProperty('captureStackTrace')) // Just in V8. + { + // @ts-ignore + Error.captureStackTrace(this, InvalidStateError); + } + else { + this.stack = (new Error(message)).stack; + } + } +} +exports.InvalidStateError = InvalidStateError; -},{"../Logger":13,"../errors":18,"../ortc":36,"../utils":39,"./HandlerInterface":25,"./ortc/edgeUtils":29}],24:[function(require,module,exports){ +},{}],23:[function(require,module,exports){ "use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; @@ -5815,19 +3677,26 @@ var __importStar = (this && this.__importStar) || function (mod) { return result; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.Firefox60 = void 0; +exports.Chrome111 = void 0; const sdpTransform = __importStar(require("sdp-transform")); const Logger_1 = require("../Logger"); -const errors_1 = require("../errors"); const utils = __importStar(require("../utils")); const ortc = __importStar(require("../ortc")); const sdpCommonUtils = __importStar(require("./sdp/commonUtils")); const sdpUnifiedPlanUtils = __importStar(require("./sdp/unifiedPlanUtils")); +const ortcUtils = __importStar(require("./ortc/utils")); const HandlerInterface_1 = require("./HandlerInterface"); const RemoteSdp_1 = require("./sdp/RemoteSdp"); -const logger = new Logger_1.Logger('Firefox60'); -const SCTP_NUM_STREAMS = { OS: 16, MIS: 2048 }; -class Firefox60 extends HandlerInterface_1.HandlerInterface { +const scalabilityModes_1 = require("../scalabilityModes"); +const logger = new Logger_1.Logger('Chrome111'); +const SCTP_NUM_STREAMS = { OS: 1024, MIS: 1024 }; +class Chrome111 extends HandlerInterface_1.HandlerInterface { + /** + * Creates a factory function. + */ + static createFactory() { + return () => new Chrome111(); + } constructor() { super(); // Map of RTCTransceivers indexed by MID. @@ -5841,14 +3710,8 @@ class Firefox60 extends HandlerInterface_1.HandlerInterface { // Got transport local and remote parameters. this._transportReady = false; } - /** - * Creates a factory function. - */ - static createFactory() { - return () => new Firefox60(); - } get name() { - return 'Firefox60'; + return 'Chrome111'; } close() { logger.debug('close()'); @@ -5859,6 +3722,7 @@ class Firefox60 extends HandlerInterface_1.HandlerInterface { } catch (error) { } } + this.emit('@close'); } async getNativeRtpCapabilities() { logger.debug('getNativeRtpCapabilities()'); @@ -5866,50 +3730,24 @@ class Firefox60 extends HandlerInterface_1.HandlerInterface { iceServers: [], iceTransportPolicy: 'all', bundlePolicy: 'max-bundle', - rtcpMuxPolicy: 'require' + rtcpMuxPolicy: 'require', + sdpSemantics: 'unified-plan' }); - // NOTE: We need to add a real video track to get the RID extension mapping. - const canvas = document.createElement('canvas'); - // NOTE: Otherwise Firefox fails in next line. - canvas.getContext('2d'); - const fakeStream = canvas.captureStream(); - const fakeVideoTrack = fakeStream.getVideoTracks()[0]; try { - pc.addTransceiver('audio', { direction: 'sendrecv' }); - const videoTransceiver = pc.addTransceiver(fakeVideoTrack, { direction: 'sendrecv' }); - const parameters = videoTransceiver.sender.getParameters(); - const encodings = [ - { rid: 'r0', maxBitrate: 100000 }, - { rid: 'r1', maxBitrate: 500000 } - ]; - parameters.encodings = encodings; - await videoTransceiver.sender.setParameters(parameters); + pc.addTransceiver('audio'); + pc.addTransceiver('video'); const offer = await pc.createOffer(); - try { - canvas.remove(); - } - catch (error) { } - try { - fakeVideoTrack.stop(); - } - catch (error) { } try { pc.close(); } catch (error) { } const sdpObject = sdpTransform.parse(offer.sdp); const nativeRtpCapabilities = sdpCommonUtils.extractRtpCapabilities({ sdpObject }); + // libwebrtc supports NACK for OPUS but doesn't announce it. + ortcUtils.addNackSuppportForOpus(nativeRtpCapabilities); return nativeRtpCapabilities; } catch (error) { - try { - canvas.remove(); - } - catch (error2) { } - try { - fakeVideoTrack.stop(); - } - catch (error2) { } try { pc.close(); } @@ -5942,46 +3780,61 @@ class Firefox60 extends HandlerInterface_1.HandlerInterface { audio: ortc.getSendingRemoteRtpParameters('audio', extendedRtpCapabilities), video: ortc.getSendingRemoteRtpParameters('video', extendedRtpCapabilities) }; + if (dtlsParameters.role && dtlsParameters.role !== 'auto') { + this._forcedLocalDtlsRole = dtlsParameters.role === 'server' + ? 'client' + : 'server'; + } this._pc = new RTCPeerConnection({ iceServers: iceServers || [], iceTransportPolicy: iceTransportPolicy || 'all', bundlePolicy: 'max-bundle', rtcpMuxPolicy: 'require', + sdpSemantics: 'unified-plan', ...additionalSettings }, proprietaryConstraints); - // Handle RTCPeerConnection connection status. - this._pc.addEventListener('iceconnectionstatechange', () => { - switch (this._pc.iceConnectionState) { - 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; - } - }); + if (this._pc.connectionState) { + this._pc.addEventListener('connectionstatechange', () => { + this.emit('@connectionstatechange', this._pc.connectionState); + }); + } + else { + logger.warn('run() | pc.connectionState not supported, using pc.iceConnectionState'); + this._pc.addEventListener('iceconnectionstatechange', () => { + switch (this._pc.iceConnectionState) { + 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; + } + }); + } } - // eslint-disable-next-line @typescript-eslint/no-unused-vars async updateIceServers(iceServers) { - // NOTE: Firefox does not implement pc.setConfiguration(). - throw new errors_1.UnsupportedError('not supported'); + logger.debug('updateIceServers()'); + const configuration = this._pc.getConfiguration(); + configuration.iceServers = iceServers; + this._pc.setConfiguration(configuration); } async restartIce(iceParameters) { logger.debug('restartIce()'); // Provide the remote SDP handler with new remote ICE parameters. this._remoteSdp.updateIceParameters(iceParameters); - if (!this._transportReady) + if (!this._transportReady) { return; + } if (this._direction === 'send') { const offer = await this._pc.createOffer({ iceRestart: true }); logger.debug('restartIce() | calling pc.setLocalDescription() [offer:%o]', offer); @@ -6003,17 +3856,31 @@ class Firefox60 extends HandlerInterface_1.HandlerInterface { return this._pc.getStats(); } async send({ track, encodings, codecOptions, codec }) { - this._assertSendDirection(); + var _a; + this.assertSendDirection(); logger.debug('send() [kind:%s, track.id:%s]', track.kind, track.id); - if (encodings) { - encodings = utils.clone(encodings, []); - if (encodings.length > 1) { - encodings.forEach((encoding, idx) => { - encoding.rid = `r${idx}`; - }); - // Clone the encodings and reverse them because Firefox likes them - // from high to low. - encodings.reverse(); + if (encodings && encodings.length > 1) { + encodings.forEach((encoding, idx) => { + encoding.rid = `r${idx}`; + }); + // Set rid and verify scalabilityMode in each encoding. + // NOTE: Even if WebRTC allows different scalabilityMode (different number + // of temporal layers) per simulcast stream, we need that those are the + // same in all them, so let's pick up the highest value. + // NOTE: If scalabilityMode is not given, Chrome will use L1T3. + let nextRid = 1; + let maxTemporalLayers = 1; + for (const encoding of encodings) { + const temporalLayers = encoding.scalabilityMode + ? (0, scalabilityModes_1.parse)(encoding.scalabilityMode).temporalLayers + : 3; + if (temporalLayers > maxTemporalLayers) { + maxTemporalLayers = temporalLayers; + } + } + for (const encoding of encodings) { + encoding.rid = `r${nextRid++}`; + encoding.scalabilityMode = `L1T${maxTemporalLayers}`; } } const sendingRtpParameters = utils.clone(this._sendingRtpParametersByKind[track.kind], {}); @@ -6024,25 +3891,20 @@ class Firefox60 extends HandlerInterface_1.HandlerInterface { // This may throw. sendingRemoteRtpParameters.codecs = ortc.reduceCodecs(sendingRemoteRtpParameters.codecs, codec); - // NOTE: Firefox fails sometimes to properly anticipate the closed media - // section that it should use, so don't reuse closed media sections. - // https://github.com/versatica/mediasoup-client/issues/104 - // - // const mediaSectionIdx = this._remoteSdp!.getNextMediaSectionIdx(); - const transceiver = this._pc.addTransceiver(track, { direction: 'sendonly', streams: [this._sendStream] }); - // NOTE: This is not spec compliants. Encodings should be given in addTransceiver - // second argument, but Firefox does not support it. - if (encodings) { - const parameters = transceiver.sender.getParameters(); - parameters.encodings = encodings; - await transceiver.sender.setParameters(parameters); - } + const mediaSectionIdx = this._remoteSdp.getNextMediaSectionIdx(); + const transceiver = this._pc.addTransceiver(track, { + direction: 'sendonly', + streams: [this._sendStream], + sendEncodings: encodings + }); const offer = await this._pc.createOffer(); let localSdpObject = sdpTransform.parse(offer.sdp); - // In Firefox use DTLS role client even if we are the "offerer" since - // Firefox does not respect ICE-Lite. - if (!this._transportReady) - await this._setupTransport({ localDtlsRole: 'client', localSdpObject }); + if (!this._transportReady) { + await this.setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); + } logger.debug('send() | calling pc.setLocalDescription() [offer:%o]', offer); await this._pc.setLocalDescription(offer); // We can now get the transceiver.mid. @@ -6050,7 +3912,7 @@ class Firefox60 extends HandlerInterface_1.HandlerInterface { // Set MID. sendingRtpParameters.mid = localId; localSdpObject = sdpTransform.parse(this._pc.localDescription.sdp); - const offerMediaObject = localSdpObject.media[localSdpObject.media.length - 1]; + const offerMediaObject = localSdpObject.media[mediaSectionIdx.idx]; // Set RTCP CNAME. sendingRtpParameters.rtcp.cname = sdpCommonUtils.getCname({ offerMediaObject }); @@ -6066,22 +3928,13 @@ class Firefox60 extends HandlerInterface_1.HandlerInterface { Object.assign(newEncodings[0], encodings[0]); sendingRtpParameters.encodings = newEncodings; } - // Otherwise if more than 1 encoding are given use them verbatim (but - // reverse them back since we reversed them above to satisfy Firefox). + // Otherwise if more than 1 encoding are given use them verbatim. else { - sendingRtpParameters.encodings = encodings.reverse(); - } - // If VP8 or H264 and there is effective simulcast, add scalabilityMode to - // each encoding. - if (sendingRtpParameters.encodings.length > 1 && - (sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/vp8' || - sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/h264')) { - for (const encoding of sendingRtpParameters.encodings) { - encoding.scalabilityMode = 'S1T3'; - } + sendingRtpParameters.encodings = encodings; } this._remoteSdp.send({ offerMediaObject, + reuseMid: mediaSectionIdx.reuseMid, offerRtpParameters: sendingRtpParameters, answerRtpParameters: sendingRemoteRtpParameters, codecOptions, @@ -6099,16 +3952,21 @@ class Firefox60 extends HandlerInterface_1.HandlerInterface { }; } async stopSending(localId) { + this.assertSendDirection(); logger.debug('stopSending() [localId:%s]', localId); const transceiver = this._mapMidTransceiver.get(localId); - if (!transceiver) - throw new Error('associated transceiver not found'); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); + } transceiver.sender.replaceTrack(null); this._pc.removeTrack(transceiver.sender); - // NOTE: Cannot use closeMediaSection() due to the the note above in send() - // method. - // this._remoteSdp!.closeMediaSection(transceiver.mid); - this._remoteSdp.disableMediaSection(transceiver.mid); + const mediaSectionClosed = this._remoteSdp.closeMediaSection(transceiver.mid); + if (mediaSectionClosed) { + try { + transceiver.stop(); + } + catch (error) { } + } const offer = await this._pc.createOffer(); logger.debug('stopSending() | calling pc.setLocalDescription() [offer:%o]', offer); await this._pc.setLocalDescription(offer); @@ -6117,8 +3975,40 @@ class Firefox60 extends HandlerInterface_1.HandlerInterface { await this._pc.setRemoteDescription(answer); this._mapMidTransceiver.delete(localId); } + async pauseSending(localId) { + this.assertSendDirection(); + logger.debug('pauseSending() [localId:%s]', localId); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); + } + transceiver.direction = 'inactive'; + this._remoteSdp.pauseMediaSection(localId); + const offer = await this._pc.createOffer(); + logger.debug('pauseSending() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('pauseSending() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); + } + async resumeSending(localId) { + this.assertSendDirection(); + logger.debug('resumeSending() [localId:%s]', localId); + const transceiver = this._mapMidTransceiver.get(localId); + this._remoteSdp.resumeSendingMediaSection(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); + } + transceiver.direction = 'sendonly'; + const offer = await this._pc.createOffer(); + logger.debug('resumeSending() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('resumeSending() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); + } async replaceTrack(localId, track) { - this._assertSendDirection(); + this.assertSendDirection(); if (track) { logger.debug('replaceTrack() [localId:%s, track.id:%s]', localId, track.id); } @@ -6126,49 +4016,67 @@ class Firefox60 extends HandlerInterface_1.HandlerInterface { logger.debug('replaceTrack() [localId:%s, no track]', localId); } const transceiver = this._mapMidTransceiver.get(localId); - if (!transceiver) + if (!transceiver) { throw new Error('associated RTCRtpTransceiver not found'); + } await transceiver.sender.replaceTrack(track); } async setMaxSpatialLayer(localId, spatialLayer) { - this._assertSendDirection(); + this.assertSendDirection(); logger.debug('setMaxSpatialLayer() [localId:%s, spatialLayer:%s]', localId, spatialLayer); const transceiver = this._mapMidTransceiver.get(localId); - if (!transceiver) - throw new Error('associated transceiver not found'); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); + } const parameters = transceiver.sender.getParameters(); - // NOTE: We require encodings given from low to high, however Firefox - // requires them in reverse order, so do magic here. - spatialLayer = parameters.encodings.length - 1 - spatialLayer; parameters.encodings.forEach((encoding, idx) => { - if (idx >= spatialLayer) + if (idx <= spatialLayer) { encoding.active = true; - else + } + else { encoding.active = false; + } }); await transceiver.sender.setParameters(parameters); + this._remoteSdp.muxMediaSectionSimulcast(localId, parameters.encodings); + const offer = await this._pc.createOffer(); + logger.debug('setMaxSpatialLayer() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('setMaxSpatialLayer() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); } async setRtpEncodingParameters(localId, params) { - this._assertSendDirection(); + this.assertSendDirection(); logger.debug('setRtpEncodingParameters() [localId:%s, params:%o]', localId, params); const transceiver = this._mapMidTransceiver.get(localId); - if (!transceiver) + if (!transceiver) { throw new Error('associated RTCRtpTransceiver not found'); + } const parameters = transceiver.sender.getParameters(); parameters.encodings.forEach((encoding, idx) => { parameters.encodings[idx] = { ...encoding, ...params }; }); await transceiver.sender.setParameters(parameters); + this._remoteSdp.muxMediaSectionSimulcast(localId, parameters.encodings); + const offer = await this._pc.createOffer(); + logger.debug('setRtpEncodingParameters() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('setRtpEncodingParameters() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); } async getSenderStats(localId) { - this._assertSendDirection(); + this.assertSendDirection(); const transceiver = this._mapMidTransceiver.get(localId); - if (!transceiver) + if (!transceiver) { throw new Error('associated RTCRtpTransceiver not found'); + } return transceiver.sender.getStats(); } async sendDataChannel({ ordered, maxPacketLifeTime, maxRetransmits, label, protocol }) { - this._assertSendDirection(); + var _a; + this.assertSendDirection(); const options = { negotiated: true, id: this._nextSendSctpStreamId, @@ -6189,8 +4097,12 @@ class Firefox60 extends HandlerInterface_1.HandlerInterface { const localSdpObject = sdpTransform.parse(offer.sdp); const offerMediaObject = localSdpObject.media .find((m) => m.type === 'application'); - if (!this._transportReady) - await this._setupTransport({ localDtlsRole: 'client', localSdpObject }); + if (!this._transportReady) { + await this.setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); + } logger.debug('sendDataChannel() | calling pc.setLocalDescription() [offer:%o]', offer); await this._pc.setLocalDescription(offer); this._remoteSdp.sendSctpAssociation({ offerMediaObject }); @@ -6207,69 +4119,101 @@ class Firefox60 extends HandlerInterface_1.HandlerInterface { }; return { dataChannel, sctpStreamParameters }; } - async receive({ trackId, kind, rtpParameters }) { - this._assertRecvDirection(); - logger.debug('receive() [trackId:%s, kind:%s]', trackId, kind); - const localId = rtpParameters.mid || String(this._mapMidTransceiver.size); - this._remoteSdp.receive({ - mid: localId, - kind, - offerRtpParameters: rtpParameters, - streamId: rtpParameters.rtcp.cname, - trackId - }); + async receive(optionsList) { + var _a; + this.assertRecvDirection(); + const results = []; + const mapLocalId = new Map(); + for (const options of optionsList) { + const { trackId, kind, rtpParameters, streamId } = options; + logger.debug('receive() [trackId:%s, kind:%s]', trackId, kind); + const localId = rtpParameters.mid || String(this._mapMidTransceiver.size); + mapLocalId.set(trackId, localId); + this._remoteSdp.receive({ + mid: localId, + kind, + offerRtpParameters: rtpParameters, + streamId: streamId || rtpParameters.rtcp.cname, + trackId + }); + } const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; logger.debug('receive() | calling pc.setRemoteDescription() [offer:%o]', offer); await this._pc.setRemoteDescription(offer); let answer = await this._pc.createAnswer(); const localSdpObject = sdpTransform.parse(answer.sdp); - const answerMediaObject = localSdpObject.media - .find((m) => String(m.mid) === localId); - // May need to modify codec parameters in the answer based on codec - // parameters in the offer. - sdpCommonUtils.applyCodecParameters({ - offerRtpParameters: rtpParameters, - answerMediaObject - }); + for (const options of optionsList) { + const { trackId, rtpParameters } = options; + const localId = mapLocalId.get(trackId); + const answerMediaObject = localSdpObject.media + .find((m) => String(m.mid) === localId); + // May need to modify codec parameters in the answer based on codec + // parameters in the offer. + sdpCommonUtils.applyCodecParameters({ + offerRtpParameters: rtpParameters, + answerMediaObject + }); + } answer = { type: 'answer', sdp: sdpTransform.write(localSdpObject) }; - if (!this._transportReady) - await this._setupTransport({ localDtlsRole: 'client', localSdpObject }); + if (!this._transportReady) { + await this.setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); + } logger.debug('receive() | calling pc.setLocalDescription() [answer:%o]', answer); await this._pc.setLocalDescription(answer); - const transceiver = this._pc.getTransceivers() - .find((t) => t.mid === localId); - if (!transceiver) - throw new Error('new RTCRtpTransceiver not found'); - // Store in the map. - this._mapMidTransceiver.set(localId, transceiver); - return { - localId, - track: transceiver.receiver.track, - rtpReceiver: transceiver.receiver - }; + for (const options of optionsList) { + const { trackId } = options; + const localId = mapLocalId.get(trackId); + const transceiver = this._pc.getTransceivers() + .find((t) => t.mid === localId); + if (!transceiver) { + throw new Error('new RTCRtpTransceiver not found'); + } + else { + // Store in the map. + this._mapMidTransceiver.set(localId, transceiver); + results.push({ + localId, + track: transceiver.receiver.track, + rtpReceiver: transceiver.receiver + }); + } + } + return results; } - async stopReceiving(localId) { - this._assertRecvDirection(); - logger.debug('stopReceiving() [localId:%s]', localId); - const transceiver = this._mapMidTransceiver.get(localId); - if (!transceiver) - throw new Error('associated RTCRtpTransceiver not found'); - this._remoteSdp.closeMediaSection(transceiver.mid); + async stopReceiving(localIds) { + this.assertRecvDirection(); + for (const localId of localIds) { + logger.debug('stopReceiving() [localId:%s]', localId); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); + } + this._remoteSdp.closeMediaSection(transceiver.mid); + } const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; logger.debug('stopReceiving() | calling pc.setRemoteDescription() [offer:%o]', offer); await this._pc.setRemoteDescription(offer); const answer = await this._pc.createAnswer(); logger.debug('stopReceiving() | calling pc.setLocalDescription() [answer:%o]', answer); await this._pc.setLocalDescription(answer); - this._mapMidTransceiver.delete(localId); + for (const localId of localIds) { + this._mapMidTransceiver.delete(localId); + } } - async pauseReceiving(localId) { - this._assertRecvDirection(); - logger.debug('pauseReceiving() [localId:%s]', localId); - const transceiver = this._mapMidTransceiver.get(localId); - if (!transceiver) - throw new Error('associated RTCRtpTransceiver not found'); - transceiver.direction = 'inactive'; + async pauseReceiving(localIds) { + this.assertRecvDirection(); + for (const localId of localIds) { + logger.debug('pauseReceiving() [localId:%s]', localId); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); + } + transceiver.direction = 'inactive'; + this._remoteSdp.pauseMediaSection(localId); + } const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; logger.debug('pauseReceiving() | calling pc.setRemoteDescription() [offer:%o]', offer); await this._pc.setRemoteDescription(offer); @@ -6277,13 +4221,17 @@ class Firefox60 extends HandlerInterface_1.HandlerInterface { logger.debug('pauseReceiving() | calling pc.setLocalDescription() [answer:%o]', answer); await this._pc.setLocalDescription(answer); } - async resumeReceiving(localId) { - this._assertRecvDirection(); - logger.debug('resumeReceiving() [localId:%s]', localId); - const transceiver = this._mapMidTransceiver.get(localId); - if (!transceiver) - throw new Error('associated RTCRtpTransceiver not found'); - transceiver.direction = 'recvonly'; + async resumeReceiving(localIds) { + this.assertRecvDirection(); + for (const localId of localIds) { + logger.debug('resumeReceiving() [localId:%s]', localId); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); + } + transceiver.direction = 'recvonly'; + this._remoteSdp.resumeReceivingMediaSection(localId); + } const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; logger.debug('resumeReceiving() | calling pc.setRemoteDescription() [offer:%o]', offer); await this._pc.setRemoteDescription(offer); @@ -6292,14 +4240,16 @@ class Firefox60 extends HandlerInterface_1.HandlerInterface { await this._pc.setLocalDescription(answer); } async getReceiverStats(localId) { - this._assertRecvDirection(); + this.assertRecvDirection(); const transceiver = this._mapMidTransceiver.get(localId); - if (!transceiver) + if (!transceiver) { throw new Error('associated RTCRtpTransceiver not found'); + } return transceiver.receiver.getStats(); } async receiveDataChannel({ sctpStreamParameters, label, protocol }) { - this._assertRecvDirection(); + var _a; + this.assertRecvDirection(); const { streamId, ordered, maxPacketLifeTime, maxRetransmits } = sctpStreamParameters; const options = { negotiated: true, @@ -6321,7 +4271,10 @@ class Firefox60 extends HandlerInterface_1.HandlerInterface { const answer = await this._pc.createAnswer(); if (!this._transportReady) { const localSdpObject = sdpTransform.parse(answer.sdp); - await this._setupTransport({ localDtlsRole: 'client', localSdpObject }); + await this.setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); } logger.debug('receiveDataChannel() | calling pc.setRemoteDescription() [answer:%o]', answer); await this._pc.setLocalDescription(answer); @@ -6329,9 +4282,10 @@ class Firefox60 extends HandlerInterface_1.HandlerInterface { } return { dataChannel }; } - async _setupTransport({ localDtlsRole, localSdpObject }) { - if (!localSdpObject) + async setupTransport({ localDtlsRole, localSdpObject }) { + if (!localSdpObject) { localSdpObject = sdpTransform.parse(this._pc.localDescription.sdp); + } // Get our local DTLS parameters. const dtlsParameters = sdpCommonUtils.extractDtlsParameters({ sdpObject: localSdpObject }); // Set our DTLS role. @@ -6339,47 +4293,33 @@ class Firefox60 extends HandlerInterface_1.HandlerInterface { // Update the remote DTLS role in the SDP. this._remoteSdp.updateDtlsRole(localDtlsRole === 'client' ? 'server' : 'client'); // Need to tell the remote transport about our parameters. - await this.safeEmitAsPromise('@connect', { dtlsParameters }); + await new Promise((resolve, reject) => { + this.safeEmit('@connect', { dtlsParameters }, resolve, reject); + }); this._transportReady = true; } - _assertSendDirection() { + assertSendDirection() { if (this._direction !== 'send') { throw new Error('method can just be called for handlers with "send" direction'); } } - _assertRecvDirection() { + assertRecvDirection() { if (this._direction !== 'recv') { throw new Error('method can just be called for handlers with "recv" direction'); } } } -exports.Firefox60 = Firefox60; - -},{"../Logger":13,"../errors":18,"../ortc":36,"../utils":39,"./HandlerInterface":25,"./sdp/RemoteSdp":31,"./sdp/commonUtils":32,"./sdp/unifiedPlanUtils":34,"sdp-transform":44}],25:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.HandlerInterface = void 0; -const EnhancedEventEmitter_1 = require("../EnhancedEventEmitter"); -class HandlerInterface extends EnhancedEventEmitter_1.EnhancedEventEmitter { - /** - * @emits @connect - ( - * { dtlsParameters: DtlsParameters }, - * callback: Function, - * errback: Function - * ) - * @emits @connectionstatechange - (connectionState: ConnectionState) - */ - constructor() { - super(); - } -} -exports.HandlerInterface = HandlerInterface; +exports.Chrome111 = Chrome111; -},{"../EnhancedEventEmitter":12}],26:[function(require,module,exports){ +},{"../Logger":17,"../ortc":43,"../scalabilityModes":44,"../utils":46,"./HandlerInterface":30,"./ortc/utils":36,"./sdp/RemoteSdp":38,"./sdp/commonUtils":39,"./sdp/unifiedPlanUtils":41,"sdp-transform":52}],24:[function(require,module,exports){ "use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; @@ -6397,7 +4337,7 @@ var __importStar = (this && this.__importStar) || function (mod) { return result; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.ReactNative = void 0; +exports.Chrome55 = void 0; const sdpTransform = __importStar(require("sdp-transform")); const Logger_1 = require("../Logger"); const errors_1 = require("../errors"); @@ -6407,9 +4347,15 @@ const sdpCommonUtils = __importStar(require("./sdp/commonUtils")); const sdpPlanBUtils = __importStar(require("./sdp/planBUtils")); const HandlerInterface_1 = require("./HandlerInterface"); const RemoteSdp_1 = require("./sdp/RemoteSdp"); -const logger = new Logger_1.Logger('ReactNative'); +const logger = new Logger_1.Logger('Chrome55'); const SCTP_NUM_STREAMS = { OS: 1024, MIS: 1024 }; -class ReactNative extends HandlerInterface_1.HandlerInterface { +class Chrome55 extends HandlerInterface_1.HandlerInterface { + /** + * Creates a factory function. + */ + static createFactory() { + return () => new Chrome55(); + } constructor() { super(); // Local stream for sending. @@ -6428,21 +4374,11 @@ class ReactNative extends HandlerInterface_1.HandlerInterface { // Got transport local and remote parameters. this._transportReady = false; } - /** - * Creates a factory function. - */ - static createFactory() { - return () => new ReactNative(); - } get name() { - return 'ReactNative'; + return 'Chrome55'; } close() { logger.debug('close()'); - // Free/dispose native MediaStream but DO NOT free/dispose native - // MediaStreamTracks (that is parent's business). - // @ts-ignore (proprietary API in react-native-webrtc). - this._sendStream.release(/* releaseTracks */ false); // Close RTCPeerConnection. if (this._pc) { try { @@ -6450,6 +4386,7 @@ class ReactNative extends HandlerInterface_1.HandlerInterface { } catch (error) { } } + this.emit('@close'); } async getNativeRtpCapabilities() { logger.debug('getNativeRtpCapabilities()'); @@ -6520,27 +4457,34 @@ class ReactNative extends HandlerInterface_1.HandlerInterface { sdpSemantics: 'plan-b', ...additionalSettings }, proprietaryConstraints); - // Handle RTCPeerConnection connection status. - this._pc.addEventListener('iceconnectionstatechange', () => { - switch (this._pc.iceConnectionState) { - 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; - } - }); + if (this._pc.connectionState) { + this._pc.addEventListener('connectionstatechange', () => { + this.emit('@connectionstatechange', this._pc.connectionState); + }); + } + else { + this._pc.addEventListener('iceconnectionstatechange', () => { + logger.warn('run() | pc.connectionState not supported, using pc.iceConnectionState'); + switch (this._pc.iceConnectionState) { + 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; + } + }); + } } async updateIceServers(iceServers) { logger.debug('updateIceServers()'); @@ -6552,8 +4496,9 @@ class ReactNative extends HandlerInterface_1.HandlerInterface { logger.debug('restartIce()'); // Provide the remote SDP handler with new remote ICE parameters. this._remoteSdp.updateIceParameters(iceParameters); - if (!this._transportReady) + if (!this._transportReady) { return; + } if (this._direction === 'send') { const offer = await this._pc.createOffer({ iceRestart: true }); logger.debug('restartIce() | calling pc.setLocalDescription() [offer:%o]', offer); @@ -6576,7 +4521,7 @@ class ReactNative extends HandlerInterface_1.HandlerInterface { } async send({ track, encodings, codecOptions, codec }) { var _a; - this._assertSendDirection(); + this.assertSendDirection(); logger.debug('send() [kind:%s, track.id:%s]', track.kind, track.id); if (codec) { logger.warn('send() | codec selection is not available in %s handler', this.name); @@ -6593,7 +4538,7 @@ class ReactNative extends HandlerInterface_1.HandlerInterface { sendingRemoteRtpParameters.codecs = ortc.reduceCodecs(sendingRemoteRtpParameters.codecs); if (!this._transportReady) { - await this._setupTransport({ + await this.setupTransport({ localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', localSdpObject }); @@ -6601,8 +4546,7 @@ class ReactNative extends HandlerInterface_1.HandlerInterface { if (track.kind === 'video' && encodings && encodings.length > 1) { logger.debug('send() | enabling simulcast'); localSdpObject = sdpTransform.parse(offer.sdp); - offerMediaObject = localSdpObject.media - .find((m) => m.type === 'video'); + offerMediaObject = localSdpObject.media.find((m) => m.type === 'video'); sdpPlanBUtils.addLegacySimulcast({ offerMediaObject, track, @@ -6624,17 +4568,17 @@ class ReactNative extends HandlerInterface_1.HandlerInterface { // Complete encodings with given values. if (encodings) { for (let idx = 0; idx < sendingRtpParameters.encodings.length; ++idx) { - if (encodings[idx]) + if (encodings[idx]) { Object.assign(sendingRtpParameters.encodings[idx], encodings[idx]); + } } } - // If VP8 or H264 and there is effective simulcast, add scalabilityMode to - // each encoding. + // If VP8 and there is effective simulcast, add scalabilityMode to each + // encoding. if (sendingRtpParameters.encodings.length > 1 && - (sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/vp8' || - sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/h264')) { + sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/vp8') { for (const encoding of sendingRtpParameters.encodings) { - encoding.scalabilityMode = 'S1T3'; + encoding.scalabilityMode = 'L1T3'; } } this._remoteSdp.send({ @@ -6656,11 +4600,12 @@ class ReactNative extends HandlerInterface_1.HandlerInterface { }; } async stopSending(localId) { - this._assertSendDirection(); + this.assertSendDirection(); logger.debug('stopSending() [localId:%s]', localId); const track = this._mapSendLocalIdTrack.get(localId); - if (!track) + if (!track) { throw new Error('track not found'); + } this._mapSendLocalIdTrack.delete(localId); this._sendStream.removeTrack(track); this._pc.addStream(this._sendStream); @@ -6678,12 +4623,21 @@ class ReactNative extends HandlerInterface_1.HandlerInterface { } throw error; } - if (this._pc.signalingState === 'stable') + if (this._pc.signalingState === 'stable') { return; + } const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; logger.debug('stopSending() | calling pc.setRemoteDescription() [answer:%o]', answer); await this._pc.setRemoteDescription(answer); } + // 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( // eslint-disable-next-line @typescript-eslint/no-unused-vars localId, track) { @@ -6691,11 +4645,11 @@ class ReactNative extends HandlerInterface_1.HandlerInterface { } // eslint-disable-next-line @typescript-eslint/no-unused-vars async setMaxSpatialLayer(localId, spatialLayer) { - throw new errors_1.UnsupportedError('not implemented'); + throw new errors_1.UnsupportedError(' not implemented'); } // eslint-disable-next-line @typescript-eslint/no-unused-vars async setRtpEncodingParameters(localId, params) { - throw new errors_1.UnsupportedError('not implemented'); + throw new errors_1.UnsupportedError('not supported'); } // eslint-disable-next-line @typescript-eslint/no-unused-vars async getSenderStats(localId) { @@ -6703,7 +4657,7 @@ class ReactNative extends HandlerInterface_1.HandlerInterface { } async sendDataChannel({ ordered, maxPacketLifeTime, maxRetransmits, label, protocol }) { var _a; - this._assertSendDirection(); + this.assertSendDirection(); const options = { negotiated: true, id: this._nextSendSctpStreamId, @@ -6726,7 +4680,7 @@ class ReactNative extends HandlerInterface_1.HandlerInterface { const offerMediaObject = localSdpObject.media .find((m) => m.type === 'application'); if (!this._transportReady) { - await this._setupTransport({ + await this.setupTransport({ localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', localSdpObject }); @@ -6747,64 +4701,74 @@ class ReactNative extends HandlerInterface_1.HandlerInterface { }; return { dataChannel, sctpStreamParameters }; } - async receive({ trackId, kind, rtpParameters }) { + async receive(optionsList) { var _a; - this._assertRecvDirection(); - logger.debug('receive() [trackId:%s, kind:%s]', trackId, kind); - const localId = trackId; - const mid = kind; - let streamId = rtpParameters.rtcp.cname; - // NOTE: In React-Native we cannot reuse the same remote MediaStream for new - // remote tracks. This is because react-native-webrtc does not react on new - // tracks generated within already existing streams, so force the streamId - // to be different. - logger.debug('receive() | forcing a random remote streamId to avoid well known bug in react-native-webrtc'); - streamId += `-hack-${utils.generateRandomNumber()}`; - this._remoteSdp.receive({ - mid, - kind, - offerRtpParameters: rtpParameters, - streamId, - trackId - }); + this.assertRecvDirection(); + const results = []; + for (const options of optionsList) { + const { trackId, kind, rtpParameters, streamId } = options; + logger.debug('receive() [trackId:%s, kind:%s]', trackId, kind); + const mid = kind; + this._remoteSdp.receive({ + mid, + kind, + offerRtpParameters: rtpParameters, + streamId: streamId || rtpParameters.rtcp.cname, + trackId + }); + } const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; logger.debug('receive() | calling pc.setRemoteDescription() [offer:%o]', offer); await this._pc.setRemoteDescription(offer); let answer = await this._pc.createAnswer(); const localSdpObject = sdpTransform.parse(answer.sdp); - const answerMediaObject = localSdpObject.media - .find((m) => String(m.mid) === mid); - // May need to modify codec parameters in the answer based on codec - // parameters in the offer. - sdpCommonUtils.applyCodecParameters({ - offerRtpParameters: rtpParameters, - answerMediaObject - }); + for (const options of optionsList) { + const { kind, rtpParameters } = options; + const mid = kind; + const answerMediaObject = localSdpObject.media + .find((m) => String(m.mid) === mid); + // May need to modify codec parameters in the answer based on codec + // parameters in the offer. + sdpCommonUtils.applyCodecParameters({ + offerRtpParameters: rtpParameters, + answerMediaObject + }); + } answer = { type: 'answer', sdp: sdpTransform.write(localSdpObject) }; if (!this._transportReady) { - await this._setupTransport({ + await this.setupTransport({ localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', localSdpObject }); } logger.debug('receive() | calling pc.setLocalDescription() [answer:%o]', answer); await this._pc.setLocalDescription(answer); - const stream = this._pc.getRemoteStreams() - .find((s) => s.id === streamId); - const track = stream.getTrackById(localId); - if (!track) - throw new Error('remote track not found'); - // Insert into the map. - this._mapRecvLocalIdInfo.set(localId, { mid, rtpParameters }); - return { localId, track }; - } - async stopReceiving(localId) { - this._assertRecvDirection(); - logger.debug('stopReceiving() [localId:%s]', localId); - const { mid, rtpParameters } = this._mapRecvLocalIdInfo.get(localId) || {}; - // Remove from the map. - this._mapRecvLocalIdInfo.delete(localId); - this._remoteSdp.planBStopReceiving({ mid: mid, offerRtpParameters: rtpParameters }); + for (const options of optionsList) { + const { kind, trackId, rtpParameters } = options; + const mid = kind; + const localId = trackId; + const streamId = options.streamId || rtpParameters.rtcp.cname; + const stream = this._pc.getRemoteStreams() + .find((s) => s.id === streamId); + const track = stream.getTrackById(localId); + if (!track) { + throw new Error('remote track not found'); + } + // Insert into the map. + this._mapRecvLocalIdInfo.set(localId, { mid, rtpParameters }); + results.push({ localId, track }); + } + return results; + } + async stopReceiving(localIds) { + this.assertRecvDirection(); + for (const localId of localIds) { + logger.debug('stopReceiving() [localId:%s]', localId); + const { mid, rtpParameters } = this._mapRecvLocalIdInfo.get(localId) || {}; + // Remove from the map. + this._mapRecvLocalIdInfo.delete(localId); + this._remoteSdp.planBStopReceiving({ mid: mid, offerRtpParameters: rtpParameters }); + } const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; logger.debug('stopReceiving() | calling pc.setRemoteDescription() [offer:%o]', offer); await this._pc.setRemoteDescription(offer); @@ -6814,12 +4778,12 @@ class ReactNative extends HandlerInterface_1.HandlerInterface { } async pauseReceiving( // eslint-disable-next-line @typescript-eslint/no-unused-vars - localId) { + localIds) { // Unimplemented. } async resumeReceiving( // eslint-disable-next-line @typescript-eslint/no-unused-vars - localId) { + localIds) { // Unimplemented. } // eslint-disable-next-line @typescript-eslint/no-unused-vars @@ -6828,7 +4792,7 @@ class ReactNative extends HandlerInterface_1.HandlerInterface { } async receiveDataChannel({ sctpStreamParameters, label, protocol }) { var _a; - this._assertRecvDirection(); + this.assertRecvDirection(); const { streamId, ordered, maxPacketLifeTime, maxRetransmits } = sctpStreamParameters; const options = { negotiated: true, @@ -6851,7 +4815,7 @@ class ReactNative extends HandlerInterface_1.HandlerInterface { const answer = await this._pc.createAnswer(); if (!this._transportReady) { const localSdpObject = sdpTransform.parse(answer.sdp); - await this._setupTransport({ + await this.setupTransport({ localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', localSdpObject }); @@ -6862,9 +4826,10 @@ class ReactNative extends HandlerInterface_1.HandlerInterface { } return { dataChannel }; } - async _setupTransport({ localDtlsRole, localSdpObject }) { - if (!localSdpObject) + async setupTransport({ localDtlsRole, localSdpObject }) { + if (!localSdpObject) { localSdpObject = sdpTransform.parse(this._pc.localDescription.sdp); + } // Get our local DTLS parameters. const dtlsParameters = sdpCommonUtils.extractDtlsParameters({ sdpObject: localSdpObject }); // Set our DTLS role. @@ -6872,27 +4837,33 @@ class ReactNative extends HandlerInterface_1.HandlerInterface { // Update the remote DTLS role in the SDP. this._remoteSdp.updateDtlsRole(localDtlsRole === 'client' ? 'server' : 'client'); // Need to tell the remote transport about our parameters. - await this.safeEmitAsPromise('@connect', { dtlsParameters }); + await new Promise((resolve, reject) => { + this.safeEmit('@connect', { dtlsParameters }, resolve, reject); + }); this._transportReady = true; } - _assertSendDirection() { + assertSendDirection() { if (this._direction !== 'send') { throw new Error('method can just be called for handlers with "send" direction'); } } - _assertRecvDirection() { + assertRecvDirection() { if (this._direction !== 'recv') { throw new Error('method can just be called for handlers with "recv" direction'); } } } -exports.ReactNative = ReactNative; +exports.Chrome55 = Chrome55; -},{"../Logger":13,"../errors":18,"../ortc":36,"../utils":39,"./HandlerInterface":25,"./sdp/RemoteSdp":31,"./sdp/commonUtils":32,"./sdp/planBUtils":33,"sdp-transform":44}],27:[function(require,module,exports){ +},{"../Logger":17,"../errors":22,"../ortc":43,"../utils":46,"./HandlerInterface":30,"./sdp/RemoteSdp":38,"./sdp/commonUtils":39,"./sdp/planBUtils":40,"sdp-transform":52}],25:[function(require,module,exports){ "use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; @@ -6910,7 +4881,7 @@ var __importStar = (this && this.__importStar) || function (mod) { return result; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.Safari11 = void 0; +exports.Chrome67 = void 0; const sdpTransform = __importStar(require("sdp-transform")); const Logger_1 = require("../Logger"); const utils = __importStar(require("../utils")); @@ -6919,9 +4890,15 @@ const sdpCommonUtils = __importStar(require("./sdp/commonUtils")); const sdpPlanBUtils = __importStar(require("./sdp/planBUtils")); const HandlerInterface_1 = require("./HandlerInterface"); const RemoteSdp_1 = require("./sdp/RemoteSdp"); -const logger = new Logger_1.Logger('Safari11'); +const logger = new Logger_1.Logger('Chrome67'); const SCTP_NUM_STREAMS = { OS: 1024, MIS: 1024 }; -class Safari11 extends HandlerInterface_1.HandlerInterface { +class Chrome67 extends HandlerInterface_1.HandlerInterface { + /** + * Creates a factory function. + */ + static createFactory() { + return () => new Chrome67(); + } constructor() { super(); // Local stream for sending. @@ -6940,14 +4917,8 @@ class Safari11 extends HandlerInterface_1.HandlerInterface { // Got transport local and remote parameters. this._transportReady = false; } - /** - * Creates a factory function. - */ - static createFactory() { - return () => new Safari11(); - } get name() { - return 'Safari11'; + return 'Chrome67'; } close() { logger.debug('close()'); @@ -6958,6 +4929,7 @@ class Safari11 extends HandlerInterface_1.HandlerInterface { } catch (error) { } } + this.emit('@close'); } async getNativeRtpCapabilities() { logger.debug('getNativeRtpCapabilities()'); @@ -7025,29 +4997,37 @@ class Safari11 extends HandlerInterface_1.HandlerInterface { iceTransportPolicy: iceTransportPolicy || 'all', bundlePolicy: 'max-bundle', rtcpMuxPolicy: 'require', + sdpSemantics: 'plan-b', ...additionalSettings }, proprietaryConstraints); - // Handle RTCPeerConnection connection status. - this._pc.addEventListener('iceconnectionstatechange', () => { - switch (this._pc.iceConnectionState) { - 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; - } - }); + if (this._pc.connectionState) { + this._pc.addEventListener('connectionstatechange', () => { + this.emit('@connectionstatechange', this._pc.connectionState); + }); + } + else { + this._pc.addEventListener('iceconnectionstatechange', () => { + logger.warn('run() | pc.connectionState not supported, using pc.iceConnectionState'); + switch (this._pc.iceConnectionState) { + 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; + } + }); + } } async updateIceServers(iceServers) { logger.debug('updateIceServers()'); @@ -7059,8 +5039,9 @@ class Safari11 extends HandlerInterface_1.HandlerInterface { logger.debug('restartIce()'); // Provide the remote SDP handler with new remote ICE parameters. this._remoteSdp.updateIceParameters(iceParameters); - if (!this._transportReady) + if (!this._transportReady) { return; + } if (this._direction === 'send') { const offer = await this._pc.createOffer({ iceRestart: true }); logger.debug('restartIce() | calling pc.setLocalDescription() [offer:%o]', offer); @@ -7083,7 +5064,7 @@ class Safari11 extends HandlerInterface_1.HandlerInterface { } async send({ track, encodings, codecOptions, codec }) { var _a; - this._assertSendDirection(); + this.assertSendDirection(); logger.debug('send() [kind:%s, track.id:%s]', track.kind, track.id); if (codec) { logger.warn('send() | codec selection is not available in %s handler', this.name); @@ -7100,7 +5081,7 @@ class Safari11 extends HandlerInterface_1.HandlerInterface { sendingRemoteRtpParameters.codecs = ortc.reduceCodecs(sendingRemoteRtpParameters.codecs); if (!this._transportReady) { - await this._setupTransport({ + await this.setupTransport({ localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', localSdpObject }); @@ -7108,7 +5089,8 @@ class Safari11 extends HandlerInterface_1.HandlerInterface { if (track.kind === 'video' && encodings && encodings.length > 1) { logger.debug('send() | enabling simulcast'); localSdpObject = sdpTransform.parse(offer.sdp); - offerMediaObject = localSdpObject.media.find((m) => m.type === 'video'); + offerMediaObject = localSdpObject.media + .find((m) => m.type === 'video'); sdpPlanBUtils.addLegacySimulcast({ offerMediaObject, track, @@ -7130,8 +5112,9 @@ class Safari11 extends HandlerInterface_1.HandlerInterface { // Complete encodings with given values. if (encodings) { for (let idx = 0; idx < sendingRtpParameters.encodings.length; ++idx) { - if (encodings[idx]) + if (encodings[idx]) { Object.assign(sendingRtpParameters.encodings[idx], encodings[idx]); + } } } // If VP8 and there is effective simulcast, add scalabilityMode to each @@ -7139,7 +5122,7 @@ class Safari11 extends HandlerInterface_1.HandlerInterface { if (sendingRtpParameters.encodings.length > 1 && sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/vp8') { for (const encoding of sendingRtpParameters.encodings) { - encoding.scalabilityMode = 'S1T3'; + encoding.scalabilityMode = 'L1T3'; } } this._remoteSdp.send({ @@ -7164,12 +5147,16 @@ class Safari11 extends HandlerInterface_1.HandlerInterface { }; } async stopSending(localId) { - this._assertSendDirection(); + this.assertSendDirection(); + logger.debug('stopSending() [localId:%s]', localId); const rtpSender = this._mapSendLocalIdRtpSender.get(localId); - if (!rtpSender) + if (!rtpSender) { throw new Error('associated RTCRtpSender not found'); - if (rtpSender.track) + } + this._pc.removeTrack(rtpSender); + if (rtpSender.track) { this._sendStream.removeTrack(rtpSender.track); + } this._mapSendLocalIdRtpSender.delete(localId); const offer = await this._pc.createOffer(); logger.debug('stopSending() | calling pc.setLocalDescription() [offer:%o]', offer); @@ -7185,14 +5172,23 @@ class Safari11 extends HandlerInterface_1.HandlerInterface { } throw error; } - if (this._pc.signalingState === 'stable') + if (this._pc.signalingState === 'stable') { return; + } const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; logger.debug('stopSending() | calling pc.setRemoteDescription() [answer:%o]', answer); await this._pc.setRemoteDescription(answer); } + // 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) { - this._assertSendDirection(); + this.assertSendDirection(); if (track) { logger.debug('replaceTrack() [localId:%s, track.id:%s]', localId, track.id); } @@ -7200,38 +5196,45 @@ class Safari11 extends HandlerInterface_1.HandlerInterface { logger.debug('replaceTrack() [localId:%s, no track]', localId); } const rtpSender = this._mapSendLocalIdRtpSender.get(localId); - if (!rtpSender) + if (!rtpSender) { throw new Error('associated RTCRtpSender not found'); + } const oldTrack = rtpSender.track; await rtpSender.replaceTrack(track); // Remove the old track from the local stream. - if (oldTrack) + if (oldTrack) { this._sendStream.removeTrack(oldTrack); + } // Add the new track to the local stream. - if (track) + if (track) { this._sendStream.addTrack(track); + } } async setMaxSpatialLayer(localId, spatialLayer) { - this._assertSendDirection(); + this.assertSendDirection(); logger.debug('setMaxSpatialLayer() [localId:%s, spatialLayer:%s]', localId, spatialLayer); const rtpSender = this._mapSendLocalIdRtpSender.get(localId); - if (!rtpSender) + if (!rtpSender) { throw new Error('associated RTCRtpSender not found'); + } const parameters = rtpSender.getParameters(); parameters.encodings.forEach((encoding, idx) => { - if (idx <= spatialLayer) + if (idx <= spatialLayer) { encoding.active = true; - else + } + else { encoding.active = false; + } }); await rtpSender.setParameters(parameters); } async setRtpEncodingParameters(localId, params) { - this._assertSendDirection(); + this.assertSendDirection(); logger.debug('setRtpEncodingParameters() [localId:%s, params:%o]', localId, params); const rtpSender = this._mapSendLocalIdRtpSender.get(localId); - if (!rtpSender) + if (!rtpSender) { throw new Error('associated RTCRtpSender not found'); + } const parameters = rtpSender.getParameters(); parameters.encodings.forEach((encoding, idx) => { parameters.encodings[idx] = { ...encoding, ...params }; @@ -7239,20 +5242,22 @@ class Safari11 extends HandlerInterface_1.HandlerInterface { await rtpSender.setParameters(parameters); } async getSenderStats(localId) { - this._assertSendDirection(); + this.assertSendDirection(); const rtpSender = this._mapSendLocalIdRtpSender.get(localId); - if (!rtpSender) + if (!rtpSender) { throw new Error('associated RTCRtpSender not found'); + } return rtpSender.getStats(); } async sendDataChannel({ ordered, maxPacketLifeTime, maxRetransmits, label, protocol }) { var _a; - this._assertSendDirection(); + this.assertSendDirection(); const options = { negotiated: true, id: this._nextSendSctpStreamId, ordered, maxPacketLifeTime, + maxRetransmitTime: maxPacketLifeTime, maxRetransmits, protocol }; @@ -7269,7 +5274,7 @@ class Safari11 extends HandlerInterface_1.HandlerInterface { const offerMediaObject = localSdpObject.media .find((m) => m.type === 'application'); if (!this._transportReady) { - await this._setupTransport({ + await this.setupTransport({ localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', localSdpObject }); @@ -7290,60 +5295,76 @@ class Safari11 extends HandlerInterface_1.HandlerInterface { }; return { dataChannel, sctpStreamParameters }; } - async receive({ trackId, kind, rtpParameters }) { + async receive(optionsList) { var _a; - this._assertRecvDirection(); - logger.debug('receive() [trackId:%s, kind:%s]', trackId, kind); - const localId = trackId; - const mid = kind; - this._remoteSdp.receive({ - mid, - kind, - offerRtpParameters: rtpParameters, - streamId: rtpParameters.rtcp.cname, - trackId - }); + this.assertRecvDirection(); + const results = []; + for (const options of optionsList) { + const { trackId, kind, rtpParameters, streamId } = options; + logger.debug('receive() [trackId:%s, kind:%s]', trackId, kind); + const mid = kind; + this._remoteSdp.receive({ + mid, + kind, + offerRtpParameters: rtpParameters, + streamId: streamId || rtpParameters.rtcp.cname, + trackId + }); + } const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; logger.debug('receive() | calling pc.setRemoteDescription() [offer:%o]', offer); await this._pc.setRemoteDescription(offer); let answer = await this._pc.createAnswer(); const localSdpObject = sdpTransform.parse(answer.sdp); - const answerMediaObject = localSdpObject.media - .find((m) => String(m.mid) === mid); - // May need to modify codec parameters in the answer based on codec - // parameters in the offer. - sdpCommonUtils.applyCodecParameters({ - offerRtpParameters: rtpParameters, - answerMediaObject - }); + for (const options of optionsList) { + const { kind, rtpParameters } = options; + const mid = kind; + const answerMediaObject = localSdpObject.media + .find((m) => String(m.mid) === mid); + // May need to modify codec parameters in the answer based on codec + // parameters in the offer. + sdpCommonUtils.applyCodecParameters({ + offerRtpParameters: rtpParameters, + answerMediaObject + }); + } answer = { type: 'answer', sdp: sdpTransform.write(localSdpObject) }; if (!this._transportReady) { - await this._setupTransport({ + await this.setupTransport({ localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', localSdpObject }); } logger.debug('receive() | calling pc.setLocalDescription() [answer:%o]', answer); await this._pc.setLocalDescription(answer); - const rtpReceiver = this._pc.getReceivers() - .find((r) => r.track && r.track.id === localId); - if (!rtpReceiver) - throw new Error('new RTCRtpReceiver not'); - // Insert into the map. - this._mapRecvLocalIdInfo.set(localId, { mid, rtpParameters, rtpReceiver }); - return { - localId, - track: rtpReceiver.track, - rtpReceiver - }; + for (const options of optionsList) { + const { kind, trackId, rtpParameters } = options; + const localId = trackId; + const mid = kind; + const rtpReceiver = this._pc.getReceivers() + .find((r) => r.track && r.track.id === localId); + if (!rtpReceiver) { + throw new Error('new RTCRtpReceiver not'); + } + // Insert into the map. + this._mapRecvLocalIdInfo.set(localId, { mid, rtpParameters, rtpReceiver }); + results.push({ + localId, + track: rtpReceiver.track, + rtpReceiver + }); + } + return results; } - async stopReceiving(localId) { - this._assertRecvDirection(); - logger.debug('stopReceiving() [localId:%s]', localId); - const { mid, rtpParameters } = this._mapRecvLocalIdInfo.get(localId) || {}; - // Remove from the map. - this._mapRecvLocalIdInfo.delete(localId); - this._remoteSdp.planBStopReceiving({ mid: mid, offerRtpParameters: rtpParameters }); + async stopReceiving(localIds) { + this.assertRecvDirection(); + for (const localId of localIds) { + logger.debug('stopReceiving() [localId:%s]', localId); + const { mid, rtpParameters } = this._mapRecvLocalIdInfo.get(localId) || {}; + // Remove from the map. + this._mapRecvLocalIdInfo.delete(localId); + this._remoteSdp.planBStopReceiving({ mid: mid, offerRtpParameters: rtpParameters }); + } const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; logger.debug('stopReceiving() | calling pc.setRemoteDescription() [offer:%o]', offer); await this._pc.setRemoteDescription(offer); @@ -7351,32 +5372,34 @@ class Safari11 extends HandlerInterface_1.HandlerInterface { logger.debug('stopReceiving() | calling pc.setLocalDescription() [answer:%o]', answer); await this._pc.setLocalDescription(answer); } - async getReceiverStats(localId) { - this._assertRecvDirection(); - const { rtpReceiver } = this._mapRecvLocalIdInfo.get(localId) || {}; - if (!rtpReceiver) - throw new Error('associated RTCRtpReceiver not found'); - return rtpReceiver.getStats(); - } async pauseReceiving( // eslint-disable-next-line @typescript-eslint/no-unused-vars - localId) { + localIds) { // Unimplemented. } async resumeReceiving( // eslint-disable-next-line @typescript-eslint/no-unused-vars - localId) { + localIds) { // Unimplemented. } + async getReceiverStats(localId) { + this.assertRecvDirection(); + const { rtpReceiver } = this._mapRecvLocalIdInfo.get(localId) || {}; + if (!rtpReceiver) { + throw new Error('associated RTCRtpReceiver not found'); + } + return rtpReceiver.getStats(); + } async receiveDataChannel({ sctpStreamParameters, label, protocol }) { var _a; - this._assertRecvDirection(); + this.assertRecvDirection(); const { streamId, ordered, maxPacketLifeTime, maxRetransmits } = sctpStreamParameters; const options = { negotiated: true, id: streamId, ordered, maxPacketLifeTime, + maxRetransmitTime: maxPacketLifeTime, maxRetransmits, protocol }; @@ -7392,7 +5415,7 @@ class Safari11 extends HandlerInterface_1.HandlerInterface { const answer = await this._pc.createAnswer(); if (!this._transportReady) { const localSdpObject = sdpTransform.parse(answer.sdp); - await this._setupTransport({ + await this.setupTransport({ localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', localSdpObject }); @@ -7403,9 +5426,10 @@ class Safari11 extends HandlerInterface_1.HandlerInterface { } return { dataChannel }; } - async _setupTransport({ localDtlsRole, localSdpObject }) { - if (!localSdpObject) + async setupTransport({ localDtlsRole, localSdpObject }) { + if (!localSdpObject) { localSdpObject = sdpTransform.parse(this._pc.localDescription.sdp); + } // Get our local DTLS parameters. const dtlsParameters = sdpCommonUtils.extractDtlsParameters({ sdpObject: localSdpObject }); // Set our DTLS role. @@ -7413,27 +5437,33 @@ class Safari11 extends HandlerInterface_1.HandlerInterface { // Update the remote DTLS role in the SDP. this._remoteSdp.updateDtlsRole(localDtlsRole === 'client' ? 'server' : 'client'); // Need to tell the remote transport about our parameters. - await this.safeEmitAsPromise('@connect', { dtlsParameters }); + await new Promise((resolve, reject) => { + this.safeEmit('@connect', { dtlsParameters }, resolve, reject); + }); this._transportReady = true; } - _assertSendDirection() { + assertSendDirection() { if (this._direction !== 'send') { throw new Error('method can just be called for handlers with "send" direction'); } } - _assertRecvDirection() { + assertRecvDirection() { if (this._direction !== 'recv') { throw new Error('method can just be called for handlers with "recv" direction'); } } } -exports.Safari11 = Safari11; +exports.Chrome67 = Chrome67; -},{"../Logger":13,"../ortc":36,"../utils":39,"./HandlerInterface":25,"./sdp/RemoteSdp":31,"./sdp/commonUtils":32,"./sdp/planBUtils":33,"sdp-transform":44}],28:[function(require,module,exports){ +},{"../Logger":17,"../ortc":43,"../utils":46,"./HandlerInterface":30,"./sdp/RemoteSdp":38,"./sdp/commonUtils":39,"./sdp/planBUtils":40,"sdp-transform":52}],26:[function(require,module,exports){ "use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; @@ -7451,7 +5481,7 @@ var __importStar = (this && this.__importStar) || function (mod) { return result; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.Safari12 = void 0; +exports.Chrome70 = void 0; const sdpTransform = __importStar(require("sdp-transform")); const Logger_1 = require("../Logger"); const utils = __importStar(require("../utils")); @@ -7460,9 +5490,16 @@ const sdpCommonUtils = __importStar(require("./sdp/commonUtils")); const sdpUnifiedPlanUtils = __importStar(require("./sdp/unifiedPlanUtils")); const HandlerInterface_1 = require("./HandlerInterface"); const RemoteSdp_1 = require("./sdp/RemoteSdp"); -const logger = new Logger_1.Logger('Safari12'); +const scalabilityModes_1 = require("../scalabilityModes"); +const logger = new Logger_1.Logger('Chrome70'); const SCTP_NUM_STREAMS = { OS: 1024, MIS: 1024 }; -class Safari12 extends HandlerInterface_1.HandlerInterface { +class Chrome70 extends HandlerInterface_1.HandlerInterface { + /** + * Creates a factory function. + */ + static createFactory() { + return () => new Chrome70(); + } constructor() { super(); // Map of RTCTransceivers indexed by MID. @@ -7476,14 +5513,8 @@ class Safari12 extends HandlerInterface_1.HandlerInterface { // Got transport local and remote parameters. this._transportReady = false; } - /** - * Creates a factory function. - */ - static createFactory() { - return () => new Safari12(); - } get name() { - return 'Safari12'; + return 'Chrome70'; } close() { logger.debug('close()'); @@ -7494,6 +5525,7 @@ class Safari12 extends HandlerInterface_1.HandlerInterface { } catch (error) { } } + this.emit('@close'); } async getNativeRtpCapabilities() { logger.debug('getNativeRtpCapabilities()'); @@ -7501,7 +5533,8 @@ class Safari12 extends HandlerInterface_1.HandlerInterface { iceServers: [], iceTransportPolicy: 'all', bundlePolicy: 'max-bundle', - rtcpMuxPolicy: 'require' + rtcpMuxPolicy: 'require', + sdpSemantics: 'unified-plan' }); try { pc.addTransceiver('audio'); @@ -7558,29 +5591,37 @@ class Safari12 extends HandlerInterface_1.HandlerInterface { iceTransportPolicy: iceTransportPolicy || 'all', bundlePolicy: 'max-bundle', rtcpMuxPolicy: 'require', + sdpSemantics: 'unified-plan', ...additionalSettings }, proprietaryConstraints); - // Handle RTCPeerConnection connection status. - this._pc.addEventListener('iceconnectionstatechange', () => { - switch (this._pc.iceConnectionState) { - 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; - } - }); + if (this._pc.connectionState) { + this._pc.addEventListener('connectionstatechange', () => { + this.emit('@connectionstatechange', this._pc.connectionState); + }); + } + else { + this._pc.addEventListener('iceconnectionstatechange', () => { + logger.warn('run() | pc.connectionState not supported, using pc.iceConnectionState'); + switch (this._pc.iceConnectionState) { + 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; + } + }); + } } async updateIceServers(iceServers) { logger.debug('updateIceServers()'); @@ -7592,8 +5633,9 @@ class Safari12 extends HandlerInterface_1.HandlerInterface { logger.debug('restartIce()'); // Provide the remote SDP handler with new remote ICE parameters. this._remoteSdp.updateIceParameters(iceParameters); - if (!this._transportReady) + if (!this._transportReady) { return; + } if (this._direction === 'send') { const offer = await this._pc.createOffer({ iceRestart: true }); logger.debug('restartIce() | calling pc.setLocalDescription() [offer:%o]', offer); @@ -7616,7 +5658,7 @@ class Safari12 extends HandlerInterface_1.HandlerInterface { } async send({ track, encodings, codecOptions, codec }) { var _a; - this._assertSendDirection(); + this.assertSendDirection(); logger.debug('send() [kind:%s, track.id:%s]', track.kind, track.id); const sendingRtpParameters = utils.clone(this._sendingRtpParametersByKind[track.kind], {}); // This may throw. @@ -7632,7 +5674,7 @@ class Safari12 extends HandlerInterface_1.HandlerInterface { let localSdpObject = sdpTransform.parse(offer.sdp); let offerMediaObject; if (!this._transportReady) { - await this._setupTransport({ + await this.setupTransport({ localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', localSdpObject }); @@ -7647,11 +5689,43 @@ class Safari12 extends HandlerInterface_1.HandlerInterface { }); offer = { type: 'offer', sdp: sdpTransform.write(localSdpObject) }; } + // Special case for VP9 with SVC. + let hackVp9Svc = false; + const layers = (0, scalabilityModes_1.parse)((encodings || [{}])[0].scalabilityMode); + if (encodings && + encodings.length === 1 && + layers.spatialLayers > 1 && + sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/vp9') { + logger.debug('send() | enabling legacy simulcast for VP9 SVC'); + hackVp9Svc = true; + localSdpObject = sdpTransform.parse(offer.sdp); + offerMediaObject = localSdpObject.media[mediaSectionIdx.idx]; + sdpUnifiedPlanUtils.addLegacySimulcast({ + offerMediaObject, + numStreams: layers.spatialLayers + }); + offer = { type: 'offer', sdp: sdpTransform.write(localSdpObject) }; + } logger.debug('send() | calling pc.setLocalDescription() [offer:%o]', offer); await this._pc.setLocalDescription(offer); - // We can now get the transceiver.mid. - const localId = transceiver.mid; - // Set MID. + // If encodings are given, apply them now. + if (encodings) { + logger.debug('send() | applying given encodings'); + const parameters = transceiver.sender.getParameters(); + for (let idx = 0; idx < (parameters.encodings || []).length; ++idx) { + const encoding = parameters.encodings[idx]; + const desiredEncoding = encodings[idx]; + // Should not happen but just in case. + if (!desiredEncoding) { + break; + } + parameters.encodings[idx] = Object.assign(encoding, desiredEncoding); + } + await transceiver.sender.setParameters(parameters); + } + // We can now get the transceiver.mid. + const localId = transceiver.mid; + // Set MID. sendingRtpParameters.mid = localId; localSdpObject = sdpTransform.parse(this._pc.localDescription.sdp); offerMediaObject = localSdpObject.media[mediaSectionIdx.idx]; @@ -7664,17 +5738,22 @@ class Safari12 extends HandlerInterface_1.HandlerInterface { // Complete encodings with given values. if (encodings) { for (let idx = 0; idx < sendingRtpParameters.encodings.length; ++idx) { - if (encodings[idx]) + if (encodings[idx]) { Object.assign(sendingRtpParameters.encodings[idx], encodings[idx]); + } } } + // Hack for VP9 SVC. + if (hackVp9Svc) { + sendingRtpParameters.encodings = [sendingRtpParameters.encodings[0]]; + } // If VP8 or H264 and there is effective simulcast, add scalabilityMode to // each encoding. if (sendingRtpParameters.encodings.length > 1 && (sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/vp8' || sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/h264')) { for (const encoding of sendingRtpParameters.encodings) { - encoding.scalabilityMode = 'S1T3'; + encoding.scalabilityMode = 'L1T3'; } } this._remoteSdp.send({ @@ -7696,14 +5775,21 @@ class Safari12 extends HandlerInterface_1.HandlerInterface { }; } async stopSending(localId) { - this._assertSendDirection(); + this.assertSendDirection(); logger.debug('stopSending() [localId:%s]', localId); const transceiver = this._mapMidTransceiver.get(localId); - if (!transceiver) + if (!transceiver) { throw new Error('associated RTCRtpTransceiver not found'); + } transceiver.sender.replaceTrack(null); this._pc.removeTrack(transceiver.sender); - this._remoteSdp.closeMediaSection(transceiver.mid); + const mediaSectionClosed = this._remoteSdp.closeMediaSection(transceiver.mid); + if (mediaSectionClosed) { + try { + transceiver.stop(); + } + catch (error) { } + } const offer = await this._pc.createOffer(); logger.debug('stopSending() | calling pc.setLocalDescription() [offer:%o]', offer); await this._pc.setLocalDescription(offer); @@ -7712,8 +5798,16 @@ class Safari12 extends HandlerInterface_1.HandlerInterface { await this._pc.setRemoteDescription(answer); this._mapMidTransceiver.delete(localId); } + // 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) { - this._assertSendDirection(); + this.assertSendDirection(); if (track) { logger.debug('replaceTrack() [localId:%s, track.id:%s]', localId, track.id); } @@ -7721,52 +5815,73 @@ class Safari12 extends HandlerInterface_1.HandlerInterface { logger.debug('replaceTrack() [localId:%s, no track]', localId); } const transceiver = this._mapMidTransceiver.get(localId); - if (!transceiver) + if (!transceiver) { throw new Error('associated RTCRtpTransceiver not found'); + } await transceiver.sender.replaceTrack(track); } async setMaxSpatialLayer(localId, spatialLayer) { - this._assertSendDirection(); + this.assertSendDirection(); logger.debug('setMaxSpatialLayer() [localId:%s, spatialLayer:%s]', localId, spatialLayer); const transceiver = this._mapMidTransceiver.get(localId); - if (!transceiver) + if (!transceiver) { throw new Error('associated RTCRtpTransceiver not found'); + } const parameters = transceiver.sender.getParameters(); parameters.encodings.forEach((encoding, idx) => { - if (idx <= spatialLayer) + if (idx <= spatialLayer) { encoding.active = true; - else + } + else { encoding.active = false; + } }); await transceiver.sender.setParameters(parameters); + this._remoteSdp.muxMediaSectionSimulcast(localId, parameters.encodings); + const offer = await this._pc.createOffer(); + logger.debug('setMaxSpatialLayer() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('setMaxSpatialLayer() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); } async setRtpEncodingParameters(localId, params) { - this._assertSendDirection(); + this.assertSendDirection(); logger.debug('setRtpEncodingParameters() [localId:%s, params:%o]', localId, params); const transceiver = this._mapMidTransceiver.get(localId); - if (!transceiver) + if (!transceiver) { throw new Error('associated RTCRtpTransceiver not found'); + } const parameters = transceiver.sender.getParameters(); parameters.encodings.forEach((encoding, idx) => { parameters.encodings[idx] = { ...encoding, ...params }; }); await transceiver.sender.setParameters(parameters); + this._remoteSdp.muxMediaSectionSimulcast(localId, parameters.encodings); + const offer = await this._pc.createOffer(); + logger.debug('setRtpEncodingParameters() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('setRtpEncodingParameters() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); } async getSenderStats(localId) { - this._assertSendDirection(); + this.assertSendDirection(); const transceiver = this._mapMidTransceiver.get(localId); - if (!transceiver) + if (!transceiver) { throw new Error('associated RTCRtpTransceiver not found'); + } return transceiver.sender.getStats(); } async sendDataChannel({ ordered, maxPacketLifeTime, maxRetransmits, label, protocol }) { var _a; - this._assertSendDirection(); + this.assertSendDirection(); const options = { negotiated: true, id: this._nextSendSctpStreamId, ordered, maxPacketLifeTime, + maxRetransmitTime: maxPacketLifeTime, maxRetransmits, protocol }; @@ -7783,7 +5898,7 @@ class Safari12 extends HandlerInterface_1.HandlerInterface { const offerMediaObject = localSdpObject.media .find((m) => m.type === 'application'); if (!this._transportReady) { - await this._setupTransport({ + await this.setupTransport({ localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', localSdpObject }); @@ -7804,111 +5919,116 @@ class Safari12 extends HandlerInterface_1.HandlerInterface { }; return { dataChannel, sctpStreamParameters }; } - async receive({ trackId, kind, rtpParameters }) { + async receive(optionsList) { var _a; - this._assertRecvDirection(); - logger.debug('receive() [trackId:%s, kind:%s]', trackId, kind); - const localId = rtpParameters.mid || String(this._mapMidTransceiver.size); - this._remoteSdp.receive({ - mid: localId, - kind, - offerRtpParameters: rtpParameters, - streamId: rtpParameters.rtcp.cname, - trackId - }); + this.assertRecvDirection(); + const results = []; + const mapLocalId = new Map(); + for (const options of optionsList) { + const { trackId, kind, rtpParameters, streamId } = options; + logger.debug('receive() [trackId:%s, kind:%s]', trackId, kind); + const localId = rtpParameters.mid || String(this._mapMidTransceiver.size); + mapLocalId.set(trackId, localId); + this._remoteSdp.receive({ + mid: localId, + kind, + offerRtpParameters: rtpParameters, + streamId: streamId || rtpParameters.rtcp.cname, + trackId + }); + } const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; logger.debug('receive() | calling pc.setRemoteDescription() [offer:%o]', offer); await this._pc.setRemoteDescription(offer); let answer = await this._pc.createAnswer(); const localSdpObject = sdpTransform.parse(answer.sdp); - const answerMediaObject = localSdpObject.media - .find((m) => String(m.mid) === localId); - // May need to modify codec parameters in the answer based on codec - // parameters in the offer. - sdpCommonUtils.applyCodecParameters({ - offerRtpParameters: rtpParameters, - answerMediaObject - }); + for (const options of optionsList) { + const { trackId, rtpParameters } = options; + const localId = mapLocalId.get(trackId); + const answerMediaObject = localSdpObject.media + .find((m) => String(m.mid) === localId); + // May need to modify codec parameters in the answer based on codec + // parameters in the offer. + sdpCommonUtils.applyCodecParameters({ + offerRtpParameters: rtpParameters, + answerMediaObject + }); + } answer = { type: 'answer', sdp: sdpTransform.write(localSdpObject) }; if (!this._transportReady) { - await this._setupTransport({ + await this.setupTransport({ localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', localSdpObject }); } logger.debug('receive() | calling pc.setLocalDescription() [answer:%o]', answer); await this._pc.setLocalDescription(answer); - const transceiver = this._pc.getTransceivers() - .find((t) => t.mid === localId); - if (!transceiver) - throw new Error('new RTCRtpTransceiver not found'); - // Store in the map. - this._mapMidTransceiver.set(localId, transceiver); - return { - localId, - track: transceiver.receiver.track, - rtpReceiver: transceiver.receiver - }; + for (const options of optionsList) { + const { trackId } = options; + const localId = mapLocalId.get(trackId); + const transceiver = this._pc.getTransceivers() + .find((t) => t.mid === localId); + if (!transceiver) { + throw new Error('new RTCRtpTransceiver not found'); + } + // Store in the map. + this._mapMidTransceiver.set(localId, transceiver); + results.push({ + localId, + track: transceiver.receiver.track, + rtpReceiver: transceiver.receiver + }); + } + return results; } - async stopReceiving(localId) { - this._assertRecvDirection(); - logger.debug('stopReceiving() [localId:%s]', localId); - const transceiver = this._mapMidTransceiver.get(localId); - if (!transceiver) - throw new Error('associated RTCRtpTransceiver not found'); - this._remoteSdp.closeMediaSection(transceiver.mid); + async stopReceiving(localIds) { + this.assertRecvDirection(); + for (const localId of localIds) { + logger.debug('stopReceiving() [localId:%s]', localId); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); + } + this._remoteSdp.closeMediaSection(transceiver.mid); + } const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; logger.debug('stopReceiving() | calling pc.setRemoteDescription() [offer:%o]', offer); await this._pc.setRemoteDescription(offer); const answer = await this._pc.createAnswer(); logger.debug('stopReceiving() | calling pc.setLocalDescription() [answer:%o]', answer); await this._pc.setLocalDescription(answer); - this._mapMidTransceiver.delete(localId); + for (const localId of localIds) { + this._mapMidTransceiver.delete(localId); + } } - async pauseReceiving(localId) { - this._assertRecvDirection(); - logger.debug('pauseReceiving() [localId:%s]', localId); - const transceiver = this._mapMidTransceiver.get(localId); - if (!transceiver) - throw new Error('associated RTCRtpTransceiver not found'); - transceiver.direction = 'inactive'; - const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; - logger.debug('pauseReceiving() | calling pc.setRemoteDescription() [offer:%o]', offer); - await this._pc.setRemoteDescription(offer); - const answer = await this._pc.createAnswer(); - logger.debug('pauseReceiving() | calling pc.setLocalDescription() [answer:%o]', answer); - await this._pc.setLocalDescription(answer); + async pauseReceiving( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + localIds) { + // Unimplemented. } - async resumeReceiving(localId) { - this._assertRecvDirection(); - logger.debug('resumeReceiving() [localId:%s]', localId); - const transceiver = this._mapMidTransceiver.get(localId); - if (!transceiver) - throw new Error('associated RTCRtpTransceiver not found'); - transceiver.direction = 'recvonly'; - const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; - logger.debug('resumeReceiving() | calling pc.setRemoteDescription() [offer:%o]', offer); - await this._pc.setRemoteDescription(offer); - const answer = await this._pc.createAnswer(); - logger.debug('resumeReceiving() | calling pc.setLocalDescription() [answer:%o]', answer); - await this._pc.setLocalDescription(answer); + async resumeReceiving( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + localIds) { + // Unimplemented. } async getReceiverStats(localId) { - this._assertRecvDirection(); + this.assertRecvDirection(); const transceiver = this._mapMidTransceiver.get(localId); - if (!transceiver) + if (!transceiver) { throw new Error('associated RTCRtpTransceiver not found'); + } return transceiver.receiver.getStats(); } async receiveDataChannel({ sctpStreamParameters, label, protocol }) { var _a; - this._assertRecvDirection(); + this.assertRecvDirection(); const { streamId, ordered, maxPacketLifeTime, maxRetransmits } = sctpStreamParameters; const options = { negotiated: true, id: streamId, ordered, maxPacketLifeTime, + maxRetransmitTime: maxPacketLifeTime, maxRetransmits, protocol }; @@ -7924,7 +6044,7 @@ class Safari12 extends HandlerInterface_1.HandlerInterface { const answer = await this._pc.createAnswer(); if (!this._transportReady) { const localSdpObject = sdpTransform.parse(answer.sdp); - await this._setupTransport({ + await this.setupTransport({ localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', localSdpObject }); @@ -7935,9 +6055,10 @@ class Safari12 extends HandlerInterface_1.HandlerInterface { } return { dataChannel }; } - async _setupTransport({ localDtlsRole, localSdpObject }) { - if (!localSdpObject) + async setupTransport({ localDtlsRole, localSdpObject }) { + if (!localSdpObject) { localSdpObject = sdpTransform.parse(this._pc.localDescription.sdp); + } // Get our local DTLS parameters. const dtlsParameters = sdpCommonUtils.extractDtlsParameters({ sdpObject: localSdpObject }); // Set our DTLS role. @@ -7945,27 +6066,33 @@ class Safari12 extends HandlerInterface_1.HandlerInterface { // Update the remote DTLS role in the SDP. this._remoteSdp.updateDtlsRole(localDtlsRole === 'client' ? 'server' : 'client'); // Need to tell the remote transport about our parameters. - await this.safeEmitAsPromise('@connect', { dtlsParameters }); + await new Promise((resolve, reject) => { + this.safeEmit('@connect', { dtlsParameters }, resolve, reject); + }); this._transportReady = true; } - _assertSendDirection() { + assertSendDirection() { if (this._direction !== 'send') { throw new Error('method can just be called for handlers with "send" direction'); } } - _assertRecvDirection() { + assertRecvDirection() { if (this._direction !== 'recv') { throw new Error('method can just be called for handlers with "recv" direction'); } } } -exports.Safari12 = Safari12; +exports.Chrome70 = Chrome70; -},{"../Logger":13,"../ortc":36,"../utils":39,"./HandlerInterface":25,"./sdp/RemoteSdp":31,"./sdp/commonUtils":32,"./sdp/unifiedPlanUtils":34,"sdp-transform":44}],29:[function(require,module,exports){ +},{"../Logger":17,"../ortc":43,"../scalabilityModes":44,"../utils":46,"./HandlerInterface":30,"./sdp/RemoteSdp":38,"./sdp/commonUtils":39,"./sdp/unifiedPlanUtils":41,"sdp-transform":52}],27:[function(require,module,exports){ "use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; @@ -7983,876 +6110,666 @@ var __importStar = (this && this.__importStar) || function (mod) { return result; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.mangleRtpParameters = exports.getCapabilities = void 0; -const utils = __importStar(require("../../utils")); -/** - * Normalize ORTC based Edge's RTCRtpReceiver.getCapabilities() to produce a full - * compliant ORTC RTCRtpCapabilities. - */ -function getCapabilities() { - const nativeCaps = RTCRtpReceiver.getCapabilities(); - const caps = utils.clone(nativeCaps, {}); - for (const codec of caps.codecs) { - // Rename numChannels to channels. - codec.channels = codec.numChannels; - delete codec.numChannels; - // Add mimeType. - codec.mimeType = codec.mimeType || `${codec.kind}/${codec.name}`; - // NOTE: Edge sets some numeric parameters as string rather than number. Fix them. - if (codec.parameters) { - const parameters = codec.parameters; - if (parameters.apt) - parameters.apt = Number(parameters.apt); - if (parameters['packetization-mode']) - parameters['packetization-mode'] = Number(parameters['packetization-mode']); - } - // Delete emty parameter String in rtcpFeedback. - for (const feedback of codec.rtcpFeedback || []) { - if (!feedback.parameter) - feedback.parameter = ''; - } +exports.Chrome74 = void 0; +const sdpTransform = __importStar(require("sdp-transform")); +const Logger_1 = require("../Logger"); +const utils = __importStar(require("../utils")); +const ortc = __importStar(require("../ortc")); +const sdpCommonUtils = __importStar(require("./sdp/commonUtils")); +const sdpUnifiedPlanUtils = __importStar(require("./sdp/unifiedPlanUtils")); +const ortcUtils = __importStar(require("./ortc/utils")); +const HandlerInterface_1 = require("./HandlerInterface"); +const RemoteSdp_1 = require("./sdp/RemoteSdp"); +const scalabilityModes_1 = require("../scalabilityModes"); +const logger = new Logger_1.Logger('Chrome74'); +const SCTP_NUM_STREAMS = { OS: 1024, MIS: 1024 }; +class Chrome74 extends HandlerInterface_1.HandlerInterface { + /** + * Creates a factory function. + */ + static createFactory() { + return () => new Chrome74(); } - return caps; -} -exports.getCapabilities = getCapabilities; -/** - * Generate RTCRtpParameters as ORTC based Edge likes. - */ -function mangleRtpParameters(rtpParameters) { - const params = utils.clone(rtpParameters, {}); - // Rename mid to muxId. - if (params.mid) { - params.muxId = params.mid; - delete params.mid; + constructor() { + super(); + // Map of RTCTransceivers indexed by MID. + this._mapMidTransceiver = new Map(); + // Local stream for sending. + this._sendStream = new MediaStream(); + // Whether a DataChannel m=application section has been created. + this._hasDataChannelMediaSection = false; + // Sending DataChannel id value counter. Incremented for each new DataChannel. + this._nextSendSctpStreamId = 0; + // Got transport local and remote parameters. + this._transportReady = false; } - for (const codec of params.codecs) { - // Rename channels to numChannels. - if (codec.channels) { - codec.numChannels = codec.channels; - delete codec.channels; - } - // Add codec.name (requried by Edge). - if (codec.mimeType && !codec.name) - codec.name = codec.mimeType.split('/')[1]; - // Remove mimeType. - delete codec.mimeType; + get name() { + return 'Chrome74'; } - return params; -} -exports.mangleRtpParameters = mangleRtpParameters; - -},{"../../utils":39}],30:[function(require,module,exports){ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.OfferMediaSection = exports.AnswerMediaSection = exports.MediaSection = void 0; -const utils = __importStar(require("../../utils")); -class MediaSection { - constructor({ iceParameters, iceCandidates, dtlsParameters, planB = false }) { - this._mediaObject = {}; - this._planB = planB; - if (iceParameters) { - this.setIceParameters(iceParameters); - } - if (iceCandidates) { - this._mediaObject.candidates = []; - for (const candidate of iceCandidates) { - const candidateObject = {}; - // mediasoup does mandates rtcp-mux so candidates component is always - // RTP (1). - candidateObject.component = 1; - candidateObject.foundation = candidate.foundation; - candidateObject.ip = candidate.ip; - candidateObject.port = candidate.port; - candidateObject.priority = candidate.priority; - candidateObject.transport = candidate.protocol; - candidateObject.type = candidate.type; - if (candidate.tcpType) - candidateObject.tcptype = candidate.tcpType; - this._mediaObject.candidates.push(candidateObject); + close() { + logger.debug('close()'); + // Close RTCPeerConnection. + if (this._pc) { + try { + this._pc.close(); } - this._mediaObject.endOfCandidates = 'end-of-candidates'; - this._mediaObject.iceOptions = 'renomination'; - } - if (dtlsParameters) { - this.setDtlsRole(dtlsParameters.role); + catch (error) { } } + this.emit('@close'); } - get mid() { - return String(this._mediaObject.mid); - } - get closed() { - return this._mediaObject.port === 0; - } - getObject() { - return this._mediaObject; - } - setIceParameters(iceParameters) { - this._mediaObject.iceUfrag = iceParameters.usernameFragment; - this._mediaObject.icePwd = iceParameters.password; - } - disable() { - this._mediaObject.direction = 'inactive'; - delete this._mediaObject.ext; - delete this._mediaObject.ssrcs; - delete this._mediaObject.ssrcGroups; - delete this._mediaObject.simulcast; - delete this._mediaObject.simulcast_03; - delete this._mediaObject.rids; + async getNativeRtpCapabilities() { + logger.debug('getNativeRtpCapabilities()'); + const pc = new RTCPeerConnection({ + iceServers: [], + iceTransportPolicy: 'all', + bundlePolicy: 'max-bundle', + rtcpMuxPolicy: 'require', + sdpSemantics: 'unified-plan' + }); + try { + pc.addTransceiver('audio'); + pc.addTransceiver('video'); + const offer = await pc.createOffer(); + try { + pc.close(); + } + catch (error) { } + const sdpObject = sdpTransform.parse(offer.sdp); + const nativeRtpCapabilities = sdpCommonUtils.extractRtpCapabilities({ sdpObject }); + // libwebrtc supports NACK for OPUS but doesn't announce it. + ortcUtils.addNackSuppportForOpus(nativeRtpCapabilities); + return nativeRtpCapabilities; + } + catch (error) { + try { + pc.close(); + } + catch (error2) { } + throw error; + } } - close() { - this._mediaObject.direction = 'inactive'; - this._mediaObject.port = 0; - delete this._mediaObject.ext; - delete this._mediaObject.ssrcs; - delete this._mediaObject.ssrcGroups; - delete this._mediaObject.simulcast; - delete this._mediaObject.simulcast_03; - delete this._mediaObject.rids; - delete this._mediaObject.extmapAllowMixed; + async getNativeSctpCapabilities() { + logger.debug('getNativeSctpCapabilities()'); + return { + numStreams: SCTP_NUM_STREAMS + }; } -} -exports.MediaSection = MediaSection; -class AnswerMediaSection extends MediaSection { - constructor({ iceParameters, iceCandidates, dtlsParameters, sctpParameters, plainRtpParameters, planB = false, offerMediaObject, offerRtpParameters, answerRtpParameters, codecOptions, extmapAllowMixed = false }) { - super({ iceParameters, iceCandidates, dtlsParameters, planB }); - this._mediaObject.mid = String(offerMediaObject.mid); - this._mediaObject.type = offerMediaObject.type; - this._mediaObject.protocol = offerMediaObject.protocol; - if (!plainRtpParameters) { - this._mediaObject.connection = { ip: '127.0.0.1', version: 4 }; - this._mediaObject.port = 7; + run({ direction, iceParameters, iceCandidates, dtlsParameters, sctpParameters, iceServers, iceTransportPolicy, additionalSettings, proprietaryConstraints, extendedRtpCapabilities }) { + logger.debug('run()'); + this._direction = direction; + this._remoteSdp = new RemoteSdp_1.RemoteSdp({ + iceParameters, + iceCandidates, + dtlsParameters, + sctpParameters + }); + this._sendingRtpParametersByKind = + { + audio: ortc.getSendingRtpParameters('audio', extendedRtpCapabilities), + video: ortc.getSendingRtpParameters('video', extendedRtpCapabilities) + }; + this._sendingRemoteRtpParametersByKind = + { + audio: ortc.getSendingRemoteRtpParameters('audio', extendedRtpCapabilities), + video: ortc.getSendingRemoteRtpParameters('video', extendedRtpCapabilities) + }; + if (dtlsParameters.role && dtlsParameters.role !== 'auto') { + this._forcedLocalDtlsRole = dtlsParameters.role === 'server' + ? 'client' + : 'server'; } - else { - this._mediaObject.connection = - { - ip: plainRtpParameters.ip, - version: plainRtpParameters.ipVersion - }; - this._mediaObject.port = plainRtpParameters.port; + this._pc = new RTCPeerConnection({ + iceServers: iceServers || [], + iceTransportPolicy: iceTransportPolicy || 'all', + bundlePolicy: 'max-bundle', + rtcpMuxPolicy: 'require', + sdpSemantics: 'unified-plan', + ...additionalSettings + }, proprietaryConstraints); + if (this._pc.connectionState) { + this._pc.addEventListener('connectionstatechange', () => { + this.emit('@connectionstatechange', this._pc.connectionState); + }); } - switch (offerMediaObject.type) { - case 'audio': - case 'video': - { - this._mediaObject.direction = 'recvonly'; - this._mediaObject.rtp = []; - this._mediaObject.rtcpFb = []; - this._mediaObject.fmtp = []; - for (const codec of answerRtpParameters.codecs) { - const rtp = { - payload: codec.payloadType, - codec: getCodecName(codec), - rate: codec.clockRate - }; - if (codec.channels > 1) - rtp.encoding = codec.channels; - this._mediaObject.rtp.push(rtp); - const codecParameters = utils.clone(codec.parameters, {}); - if (codecOptions) { - const { opusStereo, opusFec, opusDtx, opusMaxPlaybackRate, opusMaxAverageBitrate, opusPtime, videoGoogleStartBitrate, videoGoogleMaxBitrate, videoGoogleMinBitrate } = codecOptions; - const offerCodec = offerRtpParameters.codecs - .find((c) => (c.payloadType === codec.payloadType)); - switch (codec.mimeType.toLowerCase()) { - case 'audio/opus': - { - if (opusStereo !== undefined) { - offerCodec.parameters['sprop-stereo'] = opusStereo ? 1 : 0; - codecParameters.stereo = opusStereo ? 1 : 0; - } - if (opusFec !== undefined) { - offerCodec.parameters.useinbandfec = opusFec ? 1 : 0; - codecParameters.useinbandfec = opusFec ? 1 : 0; - } - if (opusDtx !== undefined) { - offerCodec.parameters.usedtx = opusDtx ? 1 : 0; - codecParameters.usedtx = opusDtx ? 1 : 0; - } - if (opusMaxPlaybackRate !== undefined) { - codecParameters.maxplaybackrate = opusMaxPlaybackRate; - } - if (opusMaxAverageBitrate !== undefined) { - codecParameters.maxaveragebitrate = opusMaxAverageBitrate; - } - if (opusPtime !== undefined) { - offerCodec.parameters.ptime = opusPtime; - codecParameters.ptime = opusPtime; - } - break; - } - case 'video/vp8': - case 'video/vp9': - case 'video/h264': - case 'video/h265': - { - if (videoGoogleStartBitrate !== undefined) - codecParameters['x-google-start-bitrate'] = videoGoogleStartBitrate; - if (videoGoogleMaxBitrate !== undefined) - codecParameters['x-google-max-bitrate'] = videoGoogleMaxBitrate; - if (videoGoogleMinBitrate !== undefined) - codecParameters['x-google-min-bitrate'] = videoGoogleMinBitrate; - break; - } - } - } - const fmtp = { - payload: codec.payloadType, - config: '' - }; - for (const key of Object.keys(codecParameters)) { - if (fmtp.config) - fmtp.config += ';'; - fmtp.config += `${key}=${codecParameters[key]}`; - } - if (fmtp.config) - this._mediaObject.fmtp.push(fmtp); - for (const fb of codec.rtcpFeedback) { - this._mediaObject.rtcpFb.push({ - payload: codec.payloadType, - type: fb.type, - subtype: fb.parameter - }); - } - } - this._mediaObject.payloads = answerRtpParameters.codecs - .map((codec) => codec.payloadType) - .join(' '); - this._mediaObject.ext = []; - for (const ext of answerRtpParameters.headerExtensions) { - // Don't add a header extension if not present in the offer. - const found = (offerMediaObject.ext || []) - .some((localExt) => localExt.uri === ext.uri); - if (!found) - continue; - this._mediaObject.ext.push({ - uri: ext.uri, - value: ext.id - }); - } - // Allow both 1 byte and 2 bytes length header extensions. - if (extmapAllowMixed && - offerMediaObject.extmapAllowMixed === 'extmap-allow-mixed') { - this._mediaObject.extmapAllowMixed = 'extmap-allow-mixed'; - } - // Simulcast. - if (offerMediaObject.simulcast) { - this._mediaObject.simulcast = - { - dir1: 'recv', - list1: offerMediaObject.simulcast.list1 - }; - this._mediaObject.rids = []; - for (const rid of offerMediaObject.rids || []) { - if (rid.direction !== 'send') - continue; - this._mediaObject.rids.push({ - id: rid.id, - direction: 'recv' - }); - } - } - // Simulcast (draft version 03). - else if (offerMediaObject.simulcast_03) { - // eslint-disable-next-line camelcase - this._mediaObject.simulcast_03 = - { - value: offerMediaObject.simulcast_03.value.replace(/send/g, 'recv') - }; - this._mediaObject.rids = []; - for (const rid of offerMediaObject.rids || []) { - if (rid.direction !== 'send') - continue; - this._mediaObject.rids.push({ - id: rid.id, - direction: 'recv' - }); - } - } - this._mediaObject.rtcpMux = 'rtcp-mux'; - this._mediaObject.rtcpRsize = 'rtcp-rsize'; - if (this._planB && this._mediaObject.type === 'video') - this._mediaObject.xGoogleFlag = 'conference'; - break; - } - case 'application': - { - // New spec. - if (typeof offerMediaObject.sctpPort === 'number') { - this._mediaObject.payloads = 'webrtc-datachannel'; - this._mediaObject.sctpPort = sctpParameters.port; - this._mediaObject.maxMessageSize = sctpParameters.maxMessageSize; - } - // Old spec. - else if (offerMediaObject.sctpmap) { - this._mediaObject.payloads = sctpParameters.port; - this._mediaObject.sctpmap = - { - app: 'webrtc-datachannel', - sctpmapNumber: sctpParameters.port, - maxMessageSize: sctpParameters.maxMessageSize - }; - } - break; + else { + logger.warn('run() | pc.connectionState not supported, using pc.iceConnectionState'); + this._pc.addEventListener('iceconnectionstatechange', () => { + switch (this._pc.iceConnectionState) { + 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; } + }); } } - setDtlsRole(role) { - switch (role) { - case 'client': - this._mediaObject.setup = 'active'; - break; - case 'server': - this._mediaObject.setup = 'passive'; - break; - case 'auto': - this._mediaObject.setup = 'actpass'; - break; - } + async updateIceServers(iceServers) { + logger.debug('updateIceServers()'); + const configuration = this._pc.getConfiguration(); + configuration.iceServers = iceServers; + this._pc.setConfiguration(configuration); } -} -exports.AnswerMediaSection = AnswerMediaSection; -class OfferMediaSection extends MediaSection { - constructor({ iceParameters, iceCandidates, dtlsParameters, sctpParameters, plainRtpParameters, planB = false, mid, kind, offerRtpParameters, streamId, trackId, oldDataChannelSpec = false }) { - super({ iceParameters, iceCandidates, dtlsParameters, planB }); - this._mediaObject.mid = String(mid); - this._mediaObject.type = kind; - if (!plainRtpParameters) { - this._mediaObject.connection = { ip: '127.0.0.1', version: 4 }; - if (!sctpParameters) - this._mediaObject.protocol = 'UDP/TLS/RTP/SAVPF'; - else - this._mediaObject.protocol = 'UDP/DTLS/SCTP'; - this._mediaObject.port = 7; + async restartIce(iceParameters) { + logger.debug('restartIce()'); + // Provide the remote SDP handler with new remote ICE parameters. + this._remoteSdp.updateIceParameters(iceParameters); + if (!this._transportReady) { + return; } - else { - this._mediaObject.connection = - { - ip: plainRtpParameters.ip, - version: plainRtpParameters.ipVersion - }; - this._mediaObject.protocol = 'RTP/AVP'; - this._mediaObject.port = plainRtpParameters.port; + if (this._direction === 'send') { + const offer = await this._pc.createOffer({ iceRestart: true }); + logger.debug('restartIce() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('restartIce() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); } - switch (kind) { - case 'audio': - case 'video': - { - this._mediaObject.direction = 'sendonly'; - this._mediaObject.rtp = []; - this._mediaObject.rtcpFb = []; - this._mediaObject.fmtp = []; - if (!this._planB) - this._mediaObject.msid = `${streamId || '-'} ${trackId}`; - for (const codec of offerRtpParameters.codecs) { - const rtp = { - payload: codec.payloadType, - codec: getCodecName(codec), - rate: codec.clockRate - }; - if (codec.channels > 1) - rtp.encoding = codec.channels; - this._mediaObject.rtp.push(rtp); - const fmtp = { - payload: codec.payloadType, - config: '' - }; - for (const key of Object.keys(codec.parameters)) { - if (fmtp.config) - fmtp.config += ';'; - fmtp.config += `${key}=${codec.parameters[key]}`; - } - if (fmtp.config) - this._mediaObject.fmtp.push(fmtp); - for (const fb of codec.rtcpFeedback) { - this._mediaObject.rtcpFb.push({ - payload: codec.payloadType, - type: fb.type, - subtype: fb.parameter - }); - } - } - this._mediaObject.payloads = offerRtpParameters.codecs - .map((codec) => codec.payloadType) - .join(' '); - this._mediaObject.ext = []; - for (const ext of offerRtpParameters.headerExtensions) { - this._mediaObject.ext.push({ - uri: ext.uri, - value: ext.id - }); - } - this._mediaObject.rtcpMux = 'rtcp-mux'; - this._mediaObject.rtcpRsize = 'rtcp-rsize'; - const encoding = offerRtpParameters.encodings[0]; - const ssrc = encoding.ssrc; - const rtxSsrc = (encoding.rtx && encoding.rtx.ssrc) - ? encoding.rtx.ssrc - : undefined; - this._mediaObject.ssrcs = []; - this._mediaObject.ssrcGroups = []; - if (offerRtpParameters.rtcp.cname) { - this._mediaObject.ssrcs.push({ - id: ssrc, - attribute: 'cname', - value: offerRtpParameters.rtcp.cname - }); - } - if (this._planB) { - this._mediaObject.ssrcs.push({ - id: ssrc, - attribute: 'msid', - value: `${streamId || '-'} ${trackId}` - }); - } - if (rtxSsrc) { - if (offerRtpParameters.rtcp.cname) { - this._mediaObject.ssrcs.push({ - id: rtxSsrc, - attribute: 'cname', - value: offerRtpParameters.rtcp.cname - }); - } - if (this._planB) { - this._mediaObject.ssrcs.push({ - id: rtxSsrc, - attribute: 'msid', - value: `${streamId || '-'} ${trackId}` - }); - } - // Associate original and retransmission SSRCs. - this._mediaObject.ssrcGroups.push({ - semantics: 'FID', - ssrcs: `${ssrc} ${rtxSsrc}` - }); - } - break; - } - case 'application': - { - // New spec. - if (!oldDataChannelSpec) { - this._mediaObject.payloads = 'webrtc-datachannel'; - this._mediaObject.sctpPort = sctpParameters.port; - this._mediaObject.maxMessageSize = sctpParameters.maxMessageSize; - } - // Old spec. - else { - this._mediaObject.payloads = sctpParameters.port; - this._mediaObject.sctpmap = - { - app: 'webrtc-datachannel', - sctpmapNumber: sctpParameters.port, - maxMessageSize: sctpParameters.maxMessageSize - }; - } - break; - } + else { + const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; + logger.debug('restartIce() | calling pc.setRemoteDescription() [offer:%o]', offer); + await this._pc.setRemoteDescription(offer); + const answer = await this._pc.createAnswer(); + logger.debug('restartIce() | calling pc.setLocalDescription() [answer:%o]', answer); + await this._pc.setLocalDescription(answer); } } - // eslint-disable-next-line @typescript-eslint/no-unused-vars - setDtlsRole(role) { - // Always 'actpass'. - this._mediaObject.setup = 'actpass'; + async getTransportStats() { + return this._pc.getStats(); } - planBReceive({ offerRtpParameters, streamId, trackId }) { - const encoding = offerRtpParameters.encodings[0]; - const ssrc = encoding.ssrc; - const rtxSsrc = (encoding.rtx && encoding.rtx.ssrc) - ? encoding.rtx.ssrc - : undefined; - const payloads = this._mediaObject.payloads.split(' '); - for (const codec of offerRtpParameters.codecs) { - if (payloads.includes(String(codec.payloadType))) { - continue; - } - const rtp = { - payload: codec.payloadType, - codec: getCodecName(codec), - rate: codec.clockRate - }; - if (codec.channels > 1) - rtp.encoding = codec.channels; - this._mediaObject.rtp.push(rtp); - const fmtp = { - payload: codec.payloadType, - config: '' - }; - for (const key of Object.keys(codec.parameters)) { - if (fmtp.config) - fmtp.config += ';'; - fmtp.config += `${key}=${codec.parameters[key]}`; - } - if (fmtp.config) - this._mediaObject.fmtp.push(fmtp); - for (const fb of codec.rtcpFeedback) { - this._mediaObject.rtcpFb.push({ - payload: codec.payloadType, - type: fb.type, - subtype: fb.parameter - }); - } - } - this._mediaObject.payloads += ` ${offerRtpParameters - .codecs - .filter((codec) => !this._mediaObject.payloads.includes(codec.payloadType)) - .map((codec) => codec.payloadType) - .join(' ')}`; - this._mediaObject.payloads = this._mediaObject.payloads.trim(); - if (offerRtpParameters.rtcp.cname) { - this._mediaObject.ssrcs.push({ - id: ssrc, - attribute: 'cname', - value: offerRtpParameters.rtcp.cname + async send({ track, encodings, codecOptions, codec }) { + var _a; + this.assertSendDirection(); + logger.debug('send() [kind:%s, track.id:%s]', track.kind, track.id); + if (encodings && encodings.length > 1) { + encodings.forEach((encoding, idx) => { + encoding.rid = `r${idx}`; }); } - this._mediaObject.ssrcs.push({ - id: ssrc, - attribute: 'msid', - value: `${streamId || '-'} ${trackId}` + const sendingRtpParameters = utils.clone(this._sendingRtpParametersByKind[track.kind], {}); + // This may throw. + sendingRtpParameters.codecs = + ortc.reduceCodecs(sendingRtpParameters.codecs, codec); + const sendingRemoteRtpParameters = utils.clone(this._sendingRemoteRtpParametersByKind[track.kind], {}); + // This may throw. + sendingRemoteRtpParameters.codecs = + ortc.reduceCodecs(sendingRemoteRtpParameters.codecs, codec); + const mediaSectionIdx = this._remoteSdp.getNextMediaSectionIdx(); + const transceiver = this._pc.addTransceiver(track, { + direction: 'sendonly', + streams: [this._sendStream], + sendEncodings: encodings }); - if (rtxSsrc) { - if (offerRtpParameters.rtcp.cname) { - this._mediaObject.ssrcs.push({ - id: rtxSsrc, - attribute: 'cname', - value: offerRtpParameters.rtcp.cname - }); - } - this._mediaObject.ssrcs.push({ - id: rtxSsrc, - attribute: 'msid', - value: `${streamId || '-'} ${trackId}` - }); - // Associate original and retransmission SSRCs. - this._mediaObject.ssrcGroups.push({ - semantics: 'FID', - ssrcs: `${ssrc} ${rtxSsrc}` + let offer = await this._pc.createOffer(); + let localSdpObject = sdpTransform.parse(offer.sdp); + let offerMediaObject; + if (!this._transportReady) { + await this.setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject }); } - } - planBStopReceiving({ offerRtpParameters }) { - const encoding = offerRtpParameters.encodings[0]; - const ssrc = encoding.ssrc; - const rtxSsrc = (encoding.rtx && encoding.rtx.ssrc) - ? encoding.rtx.ssrc - : undefined; - this._mediaObject.ssrcs = this._mediaObject.ssrcs - .filter((s) => s.id !== ssrc && s.id !== rtxSsrc); - if (rtxSsrc) { - this._mediaObject.ssrcGroups = this._mediaObject.ssrcGroups - .filter((group) => group.ssrcs !== `${ssrc} ${rtxSsrc}`); + // Special case for VP9 with SVC. + let hackVp9Svc = false; + const layers = (0, scalabilityModes_1.parse)((encodings || [{}])[0].scalabilityMode); + if (encodings && + encodings.length === 1 && + layers.spatialLayers > 1 && + sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/vp9') { + logger.debug('send() | enabling legacy simulcast for VP9 SVC'); + hackVp9Svc = true; + localSdpObject = sdpTransform.parse(offer.sdp); + offerMediaObject = localSdpObject.media[mediaSectionIdx.idx]; + sdpUnifiedPlanUtils.addLegacySimulcast({ + offerMediaObject, + numStreams: layers.spatialLayers + }); + offer = { type: 'offer', sdp: sdpTransform.write(localSdpObject) }; } - } -} -exports.OfferMediaSection = OfferMediaSection; -function getCodecName(codec) { - const MimeTypeRegex = new RegExp('^(audio|video)/(.+)', 'i'); - const mimeTypeMatch = MimeTypeRegex.exec(codec.mimeType); - if (!mimeTypeMatch) - throw new TypeError('invalid codec.mimeType'); - return mimeTypeMatch[2]; -} - -},{"../../utils":39}],31:[function(require,module,exports){ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.RemoteSdp = void 0; -const sdpTransform = __importStar(require("sdp-transform")); -const Logger_1 = require("../../Logger"); -const MediaSection_1 = require("./MediaSection"); -const logger = new Logger_1.Logger('RemoteSdp'); -class RemoteSdp { - constructor({ iceParameters, iceCandidates, dtlsParameters, sctpParameters, plainRtpParameters, planB = false }) { - // MediaSection instances with same order as in the SDP. - this._mediaSections = []; - // MediaSection indices indexed by MID. - this._midToIndex = new Map(); - this._iceParameters = iceParameters; - this._iceCandidates = iceCandidates; - this._dtlsParameters = dtlsParameters; - this._sctpParameters = sctpParameters; - this._plainRtpParameters = plainRtpParameters; - this._planB = planB; - this._sdpObject = - { - version: 0, - origin: { - address: '0.0.0.0', - ipVer: 4, - netType: 'IN', - sessionId: 10000, - sessionVersion: 0, - username: 'mediasoup-client' - }, - name: '-', - timing: { start: 0, stop: 0 }, - media: [] - }; - // If ICE parameters are given, add ICE-Lite indicator. - if (iceParameters && iceParameters.iceLite) { - this._sdpObject.icelite = 'ice-lite'; + logger.debug('send() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + // We can now get the transceiver.mid. + const localId = transceiver.mid; + // Set MID. + sendingRtpParameters.mid = localId; + localSdpObject = sdpTransform.parse(this._pc.localDescription.sdp); + offerMediaObject = localSdpObject.media[mediaSectionIdx.idx]; + // Set RTCP CNAME. + sendingRtpParameters.rtcp.cname = + sdpCommonUtils.getCname({ offerMediaObject }); + // Set RTP encodings by parsing the SDP offer if no encodings are given. + if (!encodings) { + sendingRtpParameters.encodings = + sdpUnifiedPlanUtils.getRtpEncodings({ offerMediaObject }); } - // If DTLS parameters are given, assume WebRTC and BUNDLE. - if (dtlsParameters) { - this._sdpObject.msidSemantic = { semantic: 'WMS', token: '*' }; - // NOTE: We take the latest fingerprint. - const numFingerprints = this._dtlsParameters.fingerprints.length; - this._sdpObject.fingerprint = - { - type: dtlsParameters.fingerprints[numFingerprints - 1].algorithm, - hash: dtlsParameters.fingerprints[numFingerprints - 1].value - }; - this._sdpObject.groups = [{ type: 'BUNDLE', mids: '' }]; + // Set RTP encodings by parsing the SDP offer and complete them with given + // one if just a single encoding has been given. + else if (encodings.length === 1) { + let newEncodings = sdpUnifiedPlanUtils.getRtpEncodings({ offerMediaObject }); + Object.assign(newEncodings[0], encodings[0]); + // Hack for VP9 SVC. + if (hackVp9Svc) { + newEncodings = [newEncodings[0]]; + } + sendingRtpParameters.encodings = newEncodings; } - // If there are plain RPT parameters, override SDP origin. - if (plainRtpParameters) { - this._sdpObject.origin.address = plainRtpParameters.ip; - this._sdpObject.origin.ipVer = plainRtpParameters.ipVersion; + // Otherwise if more than 1 encoding are given use them verbatim. + else { + sendingRtpParameters.encodings = encodings; } - } - updateIceParameters(iceParameters) { - logger.debug('updateIceParameters() [iceParameters:%o]', iceParameters); - this._iceParameters = iceParameters; - this._sdpObject.icelite = iceParameters.iceLite ? 'ice-lite' : undefined; - for (const mediaSection of this._mediaSections) { - mediaSection.setIceParameters(iceParameters); + // If VP8 or H264 and there is effective simulcast, add scalabilityMode to + // each encoding. + if (sendingRtpParameters.encodings.length > 1 && + (sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/vp8' || + sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/h264')) { + for (const encoding of sendingRtpParameters.encodings) { + if (encoding.scalabilityMode) { + encoding.scalabilityMode = `L1T${layers.temporalLayers}`; + } + else { + encoding.scalabilityMode = 'L1T3'; + } + } } + this._remoteSdp.send({ + offerMediaObject, + reuseMid: mediaSectionIdx.reuseMid, + offerRtpParameters: sendingRtpParameters, + answerRtpParameters: sendingRemoteRtpParameters, + codecOptions, + extmapAllowMixed: true + }); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('send() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); + // Store in the map. + this._mapMidTransceiver.set(localId, transceiver); + return { + localId, + rtpParameters: sendingRtpParameters, + rtpSender: transceiver.sender + }; } - updateDtlsRole(role) { - logger.debug('updateDtlsRole() [role:%s]', role); - this._dtlsParameters.role = role; - for (const mediaSection of this._mediaSections) { - mediaSection.setDtlsRole(role); + async stopSending(localId) { + this.assertSendDirection(); + logger.debug('stopSending() [localId:%s]', localId); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); + } + transceiver.sender.replaceTrack(null); + this._pc.removeTrack(transceiver.sender); + const mediaSectionClosed = this._remoteSdp.closeMediaSection(transceiver.mid); + if (mediaSectionClosed) { + try { + transceiver.stop(); + } + catch (error) { } } + const offer = await this._pc.createOffer(); + logger.debug('stopSending() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('stopSending() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); + this._mapMidTransceiver.delete(localId); } - getNextMediaSectionIdx() { - // If a closed media section is found, return its index. - for (let idx = 0; idx < this._mediaSections.length; ++idx) { - const mediaSection = this._mediaSections[idx]; - if (mediaSection.closed) - return { idx, reuseMid: mediaSection.mid }; + async pauseSending(localId) { + this.assertSendDirection(); + logger.debug('pauseSending() [localId:%s]', localId); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); } - // If no closed media section is found, return next one. - return { idx: this._mediaSections.length }; + transceiver.direction = 'inactive'; + this._remoteSdp.pauseMediaSection(localId); + const offer = await this._pc.createOffer(); + logger.debug('pauseSending() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('pauseSending() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); } - send({ offerMediaObject, reuseMid, offerRtpParameters, answerRtpParameters, codecOptions, extmapAllowMixed = false }) { - const mediaSection = new MediaSection_1.AnswerMediaSection({ - iceParameters: this._iceParameters, - iceCandidates: this._iceCandidates, - dtlsParameters: this._dtlsParameters, - plainRtpParameters: this._plainRtpParameters, - planB: this._planB, - offerMediaObject, - offerRtpParameters, - answerRtpParameters, - codecOptions, - extmapAllowMixed - }); - // Unified-Plan with closed media section replacement. - if (reuseMid) { - this._replaceMediaSection(mediaSection, reuseMid); + async resumeSending(localId) { + this.assertSendDirection(); + logger.debug('resumeSending() [localId:%s]', localId); + const transceiver = this._mapMidTransceiver.get(localId); + this._remoteSdp.resumeSendingMediaSection(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); } - // Unified-Plan or Plan-B with different media kind. - else if (!this._midToIndex.has(mediaSection.mid)) { - this._addMediaSection(mediaSection); + transceiver.direction = 'sendonly'; + const offer = await this._pc.createOffer(); + logger.debug('resumeSending() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('resumeSending() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); + } + async replaceTrack(localId, track) { + this.assertSendDirection(); + if (track) { + logger.debug('replaceTrack() [localId:%s, track.id:%s]', localId, track.id); } - // Plan-B with same media kind. else { - this._replaceMediaSection(mediaSection); + logger.debug('replaceTrack() [localId:%s, no track]', localId); + } + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); } + await transceiver.sender.replaceTrack(track); } - receive({ mid, kind, offerRtpParameters, streamId, trackId }) { - const idx = this._midToIndex.get(mid); - let mediaSection; - if (idx !== undefined) - mediaSection = this._mediaSections[idx]; - // Unified-Plan or different media kind. - if (!mediaSection) { - mediaSection = new MediaSection_1.OfferMediaSection({ - iceParameters: this._iceParameters, - iceCandidates: this._iceCandidates, - dtlsParameters: this._dtlsParameters, - plainRtpParameters: this._plainRtpParameters, - planB: this._planB, - mid, + async setMaxSpatialLayer(localId, spatialLayer) { + this.assertSendDirection(); + logger.debug('setMaxSpatialLayer() [localId:%s, spatialLayer:%s]', localId, spatialLayer); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); + } + const parameters = transceiver.sender.getParameters(); + parameters.encodings.forEach((encoding, idx) => { + if (idx <= spatialLayer) { + encoding.active = true; + } + else { + encoding.active = false; + } + }); + await transceiver.sender.setParameters(parameters); + this._remoteSdp.muxMediaSectionSimulcast(localId, parameters.encodings); + const offer = await this._pc.createOffer(); + logger.debug('setMaxSpatialLayer() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('setMaxSpatialLayer() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); + } + async setRtpEncodingParameters(localId, params) { + this.assertSendDirection(); + logger.debug('setRtpEncodingParameters() [localId:%s, params:%o]', localId, params); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); + } + const parameters = transceiver.sender.getParameters(); + parameters.encodings.forEach((encoding, idx) => { + parameters.encodings[idx] = { ...encoding, ...params }; + }); + await transceiver.sender.setParameters(parameters); + this._remoteSdp.muxMediaSectionSimulcast(localId, parameters.encodings); + const offer = await this._pc.createOffer(); + logger.debug('setRtpEncodingParameters() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('setRtpEncodingParameters() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); + } + async getSenderStats(localId) { + this.assertSendDirection(); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); + } + return transceiver.sender.getStats(); + } + async sendDataChannel({ ordered, maxPacketLifeTime, maxRetransmits, label, protocol }) { + var _a; + this.assertSendDirection(); + const options = { + negotiated: true, + id: this._nextSendSctpStreamId, + ordered, + maxPacketLifeTime, + maxRetransmits, + protocol + }; + logger.debug('sendDataChannel() [options:%o]', options); + const dataChannel = this._pc.createDataChannel(label, options); + // Increase next id. + this._nextSendSctpStreamId = + ++this._nextSendSctpStreamId % SCTP_NUM_STREAMS.MIS; + // If this is the first DataChannel we need to create the SDP answer with + // m=application section. + if (!this._hasDataChannelMediaSection) { + const offer = await this._pc.createOffer(); + const localSdpObject = sdpTransform.parse(offer.sdp); + const offerMediaObject = localSdpObject.media + .find((m) => m.type === 'application'); + if (!this._transportReady) { + await this.setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); + } + logger.debug('sendDataChannel() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + this._remoteSdp.sendSctpAssociation({ offerMediaObject }); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('sendDataChannel() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); + this._hasDataChannelMediaSection = true; + } + const sctpStreamParameters = { + streamId: options.id, + ordered: options.ordered, + maxPacketLifeTime: options.maxPacketLifeTime, + maxRetransmits: options.maxRetransmits + }; + return { dataChannel, sctpStreamParameters }; + } + async receive(optionsList) { + var _a; + this.assertRecvDirection(); + const results = []; + const mapLocalId = new Map(); + for (const options of optionsList) { + const { trackId, kind, rtpParameters, streamId } = options; + logger.debug('receive() [trackId:%s, kind:%s]', trackId, kind); + const localId = rtpParameters.mid || String(this._mapMidTransceiver.size); + mapLocalId.set(trackId, localId); + this._remoteSdp.receive({ + mid: localId, kind, - offerRtpParameters, - streamId, + offerRtpParameters: rtpParameters, + streamId: streamId || rtpParameters.rtcp.cname, trackId }); - // Let's try to recycle a closed media section (if any). - // NOTE: Yes, we can recycle a closed m=audio section with a new m=video. - const oldMediaSection = this._mediaSections.find((m) => (m.closed)); - if (oldMediaSection) { - this._replaceMediaSection(mediaSection, oldMediaSection.mid); + } + const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; + logger.debug('receive() | calling pc.setRemoteDescription() [offer:%o]', offer); + await this._pc.setRemoteDescription(offer); + let answer = await this._pc.createAnswer(); + const localSdpObject = sdpTransform.parse(answer.sdp); + for (const options of optionsList) { + const { trackId, rtpParameters } = options; + const localId = mapLocalId.get(trackId); + const answerMediaObject = localSdpObject.media + .find((m) => String(m.mid) === localId); + // May need to modify codec parameters in the answer based on codec + // parameters in the offer. + sdpCommonUtils.applyCodecParameters({ + offerRtpParameters: rtpParameters, + answerMediaObject + }); + } + answer = { type: 'answer', sdp: sdpTransform.write(localSdpObject) }; + if (!this._transportReady) { + await this.setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); + } + logger.debug('receive() | calling pc.setLocalDescription() [answer:%o]', answer); + await this._pc.setLocalDescription(answer); + for (const options of optionsList) { + const { trackId } = options; + const localId = mapLocalId.get(trackId); + const transceiver = this._pc.getTransceivers() + .find((t) => t.mid === localId); + if (!transceiver) { + throw new Error('new RTCRtpTransceiver not found'); } else { - this._addMediaSection(mediaSection); + // Store in the map. + this._mapMidTransceiver.set(localId, transceiver); + results.push({ + localId, + track: transceiver.receiver.track, + rtpReceiver: transceiver.receiver + }); } } - // Plan-B. - else { - mediaSection.planBReceive({ offerRtpParameters, streamId, trackId }); - this._replaceMediaSection(mediaSection); - } + return results; } - disableMediaSection(mid) { - const idx = this._midToIndex.get(mid); - if (idx === undefined) { - throw new Error(`no media section found with mid '${mid}'`); + async stopReceiving(localIds) { + this.assertRecvDirection(); + for (const localId of localIds) { + logger.debug('stopReceiving() [localId:%s]', localId); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); + } + this._remoteSdp.closeMediaSection(transceiver.mid); + } + const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; + logger.debug('stopReceiving() | calling pc.setRemoteDescription() [offer:%o]', offer); + await this._pc.setRemoteDescription(offer); + const answer = await this._pc.createAnswer(); + logger.debug('stopReceiving() | calling pc.setLocalDescription() [answer:%o]', answer); + await this._pc.setLocalDescription(answer); + for (const localId of localIds) { + this._mapMidTransceiver.delete(localId); } - const mediaSection = this._mediaSections[idx]; - mediaSection.disable(); } - closeMediaSection(mid) { - const idx = this._midToIndex.get(mid); - if (idx === undefined) { - throw new Error(`no media section found with mid '${mid}'`); + async pauseReceiving(localIds) { + this.assertRecvDirection(); + for (const localId of localIds) { + logger.debug('pauseReceiving() [localId:%s]', localId); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); + } + transceiver.direction = 'inactive'; + this._remoteSdp.pauseMediaSection(localId); } - const mediaSection = this._mediaSections[idx]; - // NOTE: Closing the first m section is a pain since it invalidates the - // bundled transport, so let's avoid it. - if (mid === this._firstMid) { - logger.debug('closeMediaSection() | cannot close first media section, disabling it instead [mid:%s]', mid); - this.disableMediaSection(mid); - return; + const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; + logger.debug('pauseReceiving() | calling pc.setRemoteDescription() [offer:%o]', offer); + await this._pc.setRemoteDescription(offer); + const answer = await this._pc.createAnswer(); + logger.debug('pauseReceiving() | calling pc.setLocalDescription() [answer:%o]', answer); + await this._pc.setLocalDescription(answer); + } + async resumeReceiving(localIds) { + this.assertRecvDirection(); + for (const localId of localIds) { + logger.debug('resumeReceiving() [localId:%s]', localId); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); + } + transceiver.direction = 'recvonly'; + this._remoteSdp.resumeReceivingMediaSection(localId); } - mediaSection.close(); - // Regenerate BUNDLE mids. - this._regenerateBundleMids(); + const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; + logger.debug('resumeReceiving() | calling pc.setRemoteDescription() [offer:%o]', offer); + await this._pc.setRemoteDescription(offer); + const answer = await this._pc.createAnswer(); + logger.debug('resumeReceiving() | calling pc.setLocalDescription() [answer:%o]', answer); + await this._pc.setLocalDescription(answer); } - planBStopReceiving({ mid, offerRtpParameters }) { - const idx = this._midToIndex.get(mid); - if (idx === undefined) { - throw new Error(`no media section found with mid '${mid}'`); + async getReceiverStats(localId) { + this.assertRecvDirection(); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); } - const mediaSection = this._mediaSections[idx]; - mediaSection.planBStopReceiving({ offerRtpParameters }); - this._replaceMediaSection(mediaSection); + return transceiver.receiver.getStats(); } - sendSctpAssociation({ offerMediaObject }) { - const mediaSection = new MediaSection_1.AnswerMediaSection({ - iceParameters: this._iceParameters, - iceCandidates: this._iceCandidates, - dtlsParameters: this._dtlsParameters, - sctpParameters: this._sctpParameters, - plainRtpParameters: this._plainRtpParameters, - offerMediaObject - }); - this._addMediaSection(mediaSection); + async receiveDataChannel({ sctpStreamParameters, label, protocol }) { + var _a; + this.assertRecvDirection(); + const { streamId, ordered, maxPacketLifeTime, maxRetransmits } = sctpStreamParameters; + const options = { + negotiated: true, + id: streamId, + ordered, + maxPacketLifeTime, + maxRetransmits, + protocol + }; + logger.debug('receiveDataChannel() [options:%o]', options); + const dataChannel = this._pc.createDataChannel(label, options); + // If this is the first DataChannel we need to create the SDP offer with + // m=application section. + if (!this._hasDataChannelMediaSection) { + this._remoteSdp.receiveSctpAssociation(); + const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; + logger.debug('receiveDataChannel() | calling pc.setRemoteDescription() [offer:%o]', offer); + await this._pc.setRemoteDescription(offer); + const answer = await this._pc.createAnswer(); + if (!this._transportReady) { + const localSdpObject = sdpTransform.parse(answer.sdp); + await this.setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); + } + logger.debug('receiveDataChannel() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setLocalDescription(answer); + this._hasDataChannelMediaSection = true; + } + return { dataChannel }; } - receiveSctpAssociation({ oldDataChannelSpec = false } = {}) { - const mediaSection = new MediaSection_1.OfferMediaSection({ - iceParameters: this._iceParameters, - iceCandidates: this._iceCandidates, - dtlsParameters: this._dtlsParameters, - sctpParameters: this._sctpParameters, - plainRtpParameters: this._plainRtpParameters, - mid: 'datachannel', - kind: 'application', - oldDataChannelSpec + async setupTransport({ localDtlsRole, localSdpObject }) { + if (!localSdpObject) { + localSdpObject = sdpTransform.parse(this._pc.localDescription.sdp); + } + // Get our local DTLS parameters. + const dtlsParameters = sdpCommonUtils.extractDtlsParameters({ sdpObject: localSdpObject }); + // Set our DTLS role. + dtlsParameters.role = localDtlsRole; + // Update the remote DTLS role in the SDP. + this._remoteSdp.updateDtlsRole(localDtlsRole === 'client' ? 'server' : 'client'); + // Need to tell the remote transport about our parameters. + await new Promise((resolve, reject) => { + this.safeEmit('@connect', { dtlsParameters }, resolve, reject); }); - this._addMediaSection(mediaSection); + this._transportReady = true; } - getSdp() { - // Increase SDP version. - this._sdpObject.origin.sessionVersion++; - return sdpTransform.write(this._sdpObject); + assertSendDirection() { + if (this._direction !== 'send') { + throw new Error('method can just be called for handlers with "send" direction'); + } } - _addMediaSection(newMediaSection) { - if (!this._firstMid) - this._firstMid = newMediaSection.mid; - // Add to the vector. - this._mediaSections.push(newMediaSection); - // Add to the map. - this._midToIndex.set(newMediaSection.mid, this._mediaSections.length - 1); - // Add to the SDP object. - this._sdpObject.media.push(newMediaSection.getObject()); - // Regenerate BUNDLE mids. - this._regenerateBundleMids(); - } - _replaceMediaSection(newMediaSection, reuseMid) { - // Store it in the map. - if (typeof reuseMid === 'string') { - const idx = this._midToIndex.get(reuseMid); - if (idx === undefined) { - throw new Error(`no media section found for reuseMid '${reuseMid}'`); - } - const oldMediaSection = this._mediaSections[idx]; - // Replace the index in the vector with the new media section. - this._mediaSections[idx] = newMediaSection; - // Update the map. - this._midToIndex.delete(oldMediaSection.mid); - this._midToIndex.set(newMediaSection.mid, idx); - // Update the SDP object. - this._sdpObject.media[idx] = newMediaSection.getObject(); - // Regenerate BUNDLE mids. - this._regenerateBundleMids(); - } - else { - const idx = this._midToIndex.get(newMediaSection.mid); - if (idx === undefined) { - throw new Error(`no media section found with mid '${newMediaSection.mid}'`); - } - // Replace the index in the vector with the new media section. - this._mediaSections[idx] = newMediaSection; - // Update the SDP object. - this._sdpObject.media[idx] = newMediaSection.getObject(); - } - } - _regenerateBundleMids() { - if (!this._dtlsParameters) - return; - this._sdpObject.groups[0].mids = this._mediaSections - .filter((mediaSection) => !mediaSection.closed) - .map((mediaSection) => mediaSection.mid) - .join(' '); + assertRecvDirection() { + if (this._direction !== 'recv') { + throw new Error('method can just be called for handlers with "recv" direction'); + } } } -exports.RemoteSdp = RemoteSdp; +exports.Chrome74 = Chrome74; -},{"../../Logger":13,"./MediaSection":30,"sdp-transform":44}],32:[function(require,module,exports){ +},{"../Logger":17,"../ortc":43,"../scalabilityModes":44,"../utils":46,"./HandlerInterface":30,"./ortc/utils":36,"./sdp/RemoteSdp":38,"./sdp/commonUtils":39,"./sdp/unifiedPlanUtils":41,"sdp-transform":52}],28:[function(require,module,exports){ "use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; @@ -8870,495 +6787,438 @@ var __importStar = (this && this.__importStar) || function (mod) { return result; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.applyCodecParameters = exports.getCname = exports.extractDtlsParameters = exports.extractRtpCapabilities = void 0; -const sdpTransform = __importStar(require("sdp-transform")); -function extractRtpCapabilities({ sdpObject }) { - // Map of RtpCodecParameters indexed by payload type. - const codecsMap = new Map(); - // Array of RtpHeaderExtensions. - const headerExtensions = []; - // Whether a m=audio/video section has been already found. - let gotAudio = false; - let gotVideo = false; - for (const m of sdpObject.media) { - const kind = m.type; - switch (kind) { - case 'audio': - { - if (gotAudio) - continue; - gotAudio = true; - break; - } - case 'video': - { - if (gotVideo) - continue; - gotVideo = true; - break; - } - default: - { - continue; - } +exports.Edge11 = void 0; +const Logger_1 = require("../Logger"); +const errors_1 = require("../errors"); +const utils = __importStar(require("../utils")); +const ortc = __importStar(require("../ortc")); +const edgeUtils = __importStar(require("./ortc/edgeUtils")); +const HandlerInterface_1 = require("./HandlerInterface"); +const logger = new Logger_1.Logger('Edge11'); +class Edge11 extends HandlerInterface_1.HandlerInterface { + /** + * Creates a factory function. + */ + static createFactory() { + return () => new Edge11(); + } + constructor() { + super(); + // Map of RTCRtpSenders indexed by id. + this._rtpSenders = new Map(); + // Map of RTCRtpReceivers indexed by id. + this._rtpReceivers = new Map(); + // Next localId for sending tracks. + this._nextSendLocalId = 0; + // Got transport local and remote parameters. + this._transportReady = false; + } + get name() { + return 'Edge11'; + } + close() { + logger.debug('close()'); + // Close the ICE gatherer. + // NOTE: Not yet implemented by Edge. + try { + this._iceGatherer.close(); } - // Get codecs. - for (const rtp of m.rtp) { - const codec = { - kind: kind, - mimeType: `${kind}/${rtp.codec}`, - preferredPayloadType: rtp.payload, - clockRate: rtp.rate, - channels: rtp.encoding, - parameters: {}, - rtcpFeedback: [] - }; - codecsMap.set(codec.preferredPayloadType, codec); + catch (error) { } + // Close the ICE transport. + try { + this._iceTransport.stop(); } - // Get codec parameters. - for (const fmtp of m.fmtp || []) { - const parameters = sdpTransform.parseParams(fmtp.config); - const codec = codecsMap.get(fmtp.payload); - if (!codec) - continue; - // Specials case to convert parameter value to string. - if (parameters && parameters.hasOwnProperty('profile-level-id')) - parameters['profile-level-id'] = String(parameters['profile-level-id']); - codec.parameters = parameters; + catch (error) { } + // Close the DTLS transport. + try { + this._dtlsTransport.stop(); } - // Get RTCP feedback for each codec. - for (const fb of m.rtcpFb || []) { - const codec = codecsMap.get(fb.payload); - if (!codec) - continue; - const feedback = { - type: fb.type, - parameter: fb.subtype - }; - if (!feedback.parameter) - delete feedback.parameter; - codec.rtcpFeedback.push(feedback); + catch (error) { } + // Close RTCRtpSenders. + for (const rtpSender of this._rtpSenders.values()) { + try { + rtpSender.stop(); + } + catch (error) { } } - // Get RTP header extensions. - for (const ext of m.ext || []) { - // Ignore encrypted extensions (not yet supported in mediasoup). - if (ext['encrypt-uri']) - continue; - const headerExtension = { - kind: kind, - uri: ext.uri, - preferredId: ext.value - }; - headerExtensions.push(headerExtension); + // Close RTCRtpReceivers. + for (const rtpReceiver of this._rtpReceivers.values()) { + try { + rtpReceiver.stop(); + } + catch (error) { } } + this.emit('@close'); } - const rtpCapabilities = { - codecs: Array.from(codecsMap.values()), - headerExtensions: headerExtensions - }; - return rtpCapabilities; -} -exports.extractRtpCapabilities = extractRtpCapabilities; -function extractDtlsParameters({ sdpObject }) { - const mediaObject = (sdpObject.media || []) - .find((m) => (m.iceUfrag && m.port !== 0)); - if (!mediaObject) - throw new Error('no active media section found'); - const fingerprint = mediaObject.fingerprint || sdpObject.fingerprint; - let role; - switch (mediaObject.setup) { - case 'active': - role = 'client'; - break; - case 'passive': - role = 'server'; - break; - case 'actpass': - role = 'auto'; - break; + async getNativeRtpCapabilities() { + logger.debug('getNativeRtpCapabilities()'); + return edgeUtils.getCapabilities(); } - const dtlsParameters = { - role, - fingerprints: [ + 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 = { - algorithm: fingerprint.type, - value: fingerprint.hash - } - ] - }; - return dtlsParameters; -} -exports.extractDtlsParameters = extractDtlsParameters; -function getCname({ offerMediaObject }) { - const ssrcCnameLine = (offerMediaObject.ssrcs || []) - .find((line) => line.attribute === 'cname'); - if (!ssrcCnameLine) - return ''; - return ssrcCnameLine.value; -} -exports.getCname = getCname; -/** - * Apply codec parameters in the given SDP m= section answer based on the - * given RTP parameters of an offer. - */ -function applyCodecParameters({ offerRtpParameters, answerMediaObject }) { - for (const codec of offerRtpParameters.codecs) { - const mimeType = codec.mimeType.toLowerCase(); - // Avoid parsing codec parameters for unhandled codecs. - if (mimeType !== 'audio/opus') - continue; - const rtp = (answerMediaObject.rtp || []) - .find((r) => r.payload === codec.payloadType); - if (!rtp) - continue; - // Just in case. - answerMediaObject.fmtp = answerMediaObject.fmtp || []; - let fmtp = answerMediaObject.fmtp - .find((f) => f.payload === codec.payloadType); - if (!fmtp) { - fmtp = { payload: codec.payloadType, config: '' }; - answerMediaObject.fmtp.push(fmtp); + 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; } - const parameters = sdpTransform.parseParams(fmtp.config); - switch (mimeType) { - case 'audio/opus': - { - const spropStereo = codec.parameters['sprop-stereo']; - if (spropStereo !== undefined) - parameters.stereo = spropStereo ? 1 : 0; - break; - } - } - // Write the codec fmtp.config back. - fmtp.config = ''; - for (const key of Object.keys(parameters)) { - if (fmtp.config) - fmtp.config += ';'; - fmtp.config += `${key}=${parameters[key]}`; + 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({}); } -} -exports.applyCodecParameters = applyCodecParameters; - -},{"sdp-transform":44}],33:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.addLegacySimulcast = exports.getRtpEncodings = void 0; -function getRtpEncodings({ offerMediaObject, track }) { - // First media SSRC (or the only one). - let firstSsrc; - const ssrcs = new Set(); - for (const line of offerMediaObject.ssrcs || []) { - if (line.attribute !== 'msid') - continue; - const trackId = line.value.split(' ')[1]; - if (trackId === track.id) { - const ssrc = line.id; - ssrcs.add(ssrc); - if (!firstSsrc) - firstSsrc = ssrc; + 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 }; } - if (ssrcs.size === 0) - throw new Error(`a=ssrc line with msid information not found [track.id:${track.id}]`); - const ssrcToRtxSsrc = new Map(); - // First assume RTX is used. - for (const line of offerMediaObject.ssrcGroups || []) { - if (line.semantics !== 'FID') - continue; - let [ssrc, rtxSsrc] = line.ssrcs.split(/\s+/); - ssrc = Number(ssrc); - rtxSsrc = Number(rtxSsrc); - if (ssrcs.has(ssrc)) { - // Remove both the SSRC and RTX SSRC from the set so later we know that they - // are already handled. - ssrcs.delete(ssrc); - ssrcs.delete(rtxSsrc); - // Add to the map. - ssrcToRtxSsrc.set(ssrc, rtxSsrc); + 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; } } - // If the set of SSRCs is not empty it means that RTX is not being used, so take - // media SSRCs from there. - for (const ssrc of ssrcs) { - // Add to the map. - ssrcToRtxSsrc.set(ssrc, null); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async pauseSending(localId) { + // Unimplemented. } - const encodings = []; - for (const [ssrc, rtxSsrc] of ssrcToRtxSsrc) { - const encoding = { ssrc }; - if (rtxSsrc) - encoding.rtx = { ssrc: rtxSsrc }; - encodings.push(encoding); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async resumeSending(localId) { + // Unimplemented. } - return encodings; -} -exports.getRtpEncodings = getRtpEncodings; -/** - * Adds multi-ssrc based simulcast into the given SDP media section offer. - */ -function addLegacySimulcast({ offerMediaObject, track, numStreams }) { - if (numStreams <= 1) - throw new TypeError('numStreams must be greater than 1'); - let firstSsrc; - let firstRtxSsrc; - let streamId; - // Get the SSRC. - const ssrcMsidLine = (offerMediaObject.ssrcs || []) - .find((line) => { - if (line.attribute !== 'msid') - return false; - const trackId = line.value.split(' ')[1]; - if (trackId === track.id) { - firstSsrc = line.id; - streamId = line.value.split(' ')[0]; - return true; + async replaceTrack(localId, track) { + if (track) { + logger.debug('replaceTrack() [localId:%s, track.id:%s]', localId, track.id); } else { - return false; - } - }); - if (!ssrcMsidLine) - throw new Error(`a=ssrc line with msid information not found [track.id:${track.id}]`); - // Get the SSRC for RTX. - (offerMediaObject.ssrcGroups || []) - .some((line) => { - if (line.semantics !== 'FID') - return false; - const ssrcs = line.ssrcs.split(/\s+/); - if (Number(ssrcs[0]) === firstSsrc) { - firstRtxSsrc = Number(ssrcs[1]); - return true; + logger.debug('replaceTrack() [localId:%s, no track]', localId); } - else { - return false; + const rtpSender = this._rtpSenders.get(localId); + if (!rtpSender) { + throw new Error('RTCRtpSender not found'); } - }); - const ssrcCnameLine = offerMediaObject.ssrcs - .find((line) => (line.attribute === 'cname' && line.id === firstSsrc)); - if (!ssrcCnameLine) - throw new Error(`a=ssrc line with cname information not found [track.id:${track.id}]`); - const cname = ssrcCnameLine.value; - const ssrcs = []; - const rtxSsrcs = []; - for (let i = 0; i < numStreams; ++i) { - ssrcs.push(firstSsrc + i); - if (firstRtxSsrc) - rtxSsrcs.push(firstRtxSsrc + i); + rtpSender.setTrack(track); } - offerMediaObject.ssrcGroups = offerMediaObject.ssrcGroups || []; - offerMediaObject.ssrcs = offerMediaObject.ssrcs || []; - offerMediaObject.ssrcGroups.push({ - semantics: 'SIM', - ssrcs: ssrcs.join(' ') - }); - for (let i = 0; i < ssrcs.length; ++i) { - const ssrc = ssrcs[i]; - offerMediaObject.ssrcs.push({ - id: ssrc, - attribute: 'cname', - value: cname - }); - offerMediaObject.ssrcs.push({ - id: ssrc, - attribute: 'msid', - value: `${streamId} ${track.id}` + 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); } - for (let i = 0; i < rtxSsrcs.length; ++i) { - const ssrc = ssrcs[i]; - const rtxSsrc = rtxSsrcs[i]; - offerMediaObject.ssrcs.push({ - id: rtxSsrc, - attribute: 'cname', - value: cname - }); - offerMediaObject.ssrcs.push({ - id: rtxSsrc, - attribute: 'msid', - value: `${streamId} ${track.id}` - }); - offerMediaObject.ssrcGroups.push({ - semantics: 'FID', - ssrcs: `${ssrc} ${rtxSsrc}` + 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); } -} -exports.addLegacySimulcast = addLegacySimulcast; - -},{}],34:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.addLegacySimulcast = exports.getRtpEncodings = void 0; -function getRtpEncodings({ offerMediaObject }) { - const ssrcs = new Set(); - for (const line of offerMediaObject.ssrcs || []) { - const ssrc = line.id; - ssrcs.add(ssrc); - } - if (ssrcs.size === 0) - throw new Error('no a=ssrc lines found'); - const ssrcToRtxSsrc = new Map(); - // First assume RTX is used. - for (const line of offerMediaObject.ssrcGroups || []) { - if (line.semantics !== 'FID') - continue; - let [ssrc, rtxSsrc] = line.ssrcs.split(/\s+/); - ssrc = Number(ssrc); - rtxSsrc = Number(rtxSsrc); - if (ssrcs.has(ssrc)) { - // Remove both the SSRC and RTX SSRC from the set so later we know that they - // are already handled. - ssrcs.delete(ssrc); - ssrcs.delete(rtxSsrc); - // Add to the map. - ssrcToRtxSsrc.set(ssrc, rtxSsrc); + async getSenderStats(localId) { + const rtpSender = this._rtpSenders.get(localId); + if (!rtpSender) { + throw new Error('RTCRtpSender not found'); } + return rtpSender.getStats(); } - // If the set of SSRCs is not empty it means that RTX is not being used, so take - // media SSRCs from there. - for (const ssrc of ssrcs) { - // Add to the map. - ssrcToRtxSsrc.set(ssrc, null); + async sendDataChannel( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + options) { + throw new errors_1.UnsupportedError('not implemented'); } - const encodings = []; - for (const [ssrc, rtxSsrc] of ssrcToRtxSsrc) { - const encoding = { ssrc }; - if (rtxSsrc) - encoding.rtx = { ssrc: rtxSsrc }; - encodings.push(encoding); + 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; } - return encodings; -} -exports.getRtpEncodings = getRtpEncodings; -/** - * Adds multi-ssrc based simulcast into the given SDP media section offer. - */ -function addLegacySimulcast({ offerMediaObject, numStreams }) { - if (numStreams <= 1) - throw new TypeError('numStreams must be greater than 1'); - // Get the SSRC. - const ssrcMsidLine = (offerMediaObject.ssrcs || []) - .find((line) => line.attribute === 'msid'); - if (!ssrcMsidLine) - throw new Error('a=ssrc line with msid information not found'); - const [streamId, trackId] = ssrcMsidLine.value.split(' '); - const firstSsrc = ssrcMsidLine.id; - let firstRtxSsrc; - // Get the SSRC for RTX. - (offerMediaObject.ssrcGroups || []) - .some((line) => { - if (line.semantics !== 'FID') - return false; - const ssrcs = line.ssrcs.split(/\s+/); - if (Number(ssrcs[0]) === firstSsrc) { - firstRtxSsrc = Number(ssrcs[1]); - return true; + 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); + } } - else { - return false; + } + 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'); } - }); - const ssrcCnameLine = offerMediaObject.ssrcs - .find((line) => line.attribute === 'cname'); - if (!ssrcCnameLine) - throw new Error('a=ssrc line with cname information not found'); - const cname = ssrcCnameLine.value; - const ssrcs = []; - const rtxSsrcs = []; - for (let i = 0; i < numStreams; ++i) { - ssrcs.push(firstSsrc + i); - if (firstRtxSsrc) - rtxSsrcs.push(firstRtxSsrc + i); + return rtpReceiver.getStats(); } - offerMediaObject.ssrcGroups = []; - offerMediaObject.ssrcs = []; - offerMediaObject.ssrcGroups.push({ - semantics: 'SIM', - ssrcs: ssrcs.join(' ') - }); - for (let i = 0; i < ssrcs.length; ++i) { - const ssrc = ssrcs[i]; - offerMediaObject.ssrcs.push({ - id: ssrc, - attribute: 'cname', - value: cname + async receiveDataChannel( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + options) { + throw new errors_1.UnsupportedError('not implemented'); + } + setIceGatherer({ iceServers, iceTransportPolicy }) { + // @ts-ignore + const iceGatherer = new RTCIceGatherer({ + iceServers: iceServers || [], + gatherPolicy: iceTransportPolicy || 'all' }); - offerMediaObject.ssrcs.push({ - id: ssrc, - attribute: 'msid', - value: `${streamId} ${trackId}` + 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; } - for (let i = 0; i < rtxSsrcs.length; ++i) { - const ssrc = ssrcs[i]; - const rtxSsrc = rtxSsrcs[i]; - offerMediaObject.ssrcs.push({ - id: rtxSsrc, - attribute: 'cname', - value: cname + 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; + } }); - offerMediaObject.ssrcs.push({ - id: rtxSsrc, - attribute: 'msid', - value: `${streamId} ${trackId}` + // 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; + } }); - offerMediaObject.ssrcGroups.push({ - semantics: 'FID', - ssrcs: `${ssrc} ${rtxSsrc}` + 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.addLegacySimulcast = addLegacySimulcast; - -},{}],35:[function(require,module,exports){ -"use strict"; -var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); -}) : (function(o, m, k, k2) { - if (k2 === undefined) k2 = k; - o[k2] = m[k]; -})); -var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { - Object.defineProperty(o, "default", { enumerable: true, value: v }); -}) : function(o, v) { - o["default"] = v; -}); -var __importStar = (this && this.__importStar) || function (mod) { - if (mod && mod.__esModule) return mod; - var result = {}; - if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); - __setModuleDefault(result, mod); - return result; -}; -var __importDefault = (this && this.__importDefault) || function (mod) { - return (mod && mod.__esModule) ? mod : { "default": mod }; -}; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.debug = exports.parseScalabilityMode = exports.detectDevice = exports.Device = exports.version = exports.types = void 0; -const debug_1 = __importDefault(require("debug")); -exports.debug = debug_1.default; -const Device_1 = require("./Device"); -Object.defineProperty(exports, "Device", { enumerable: true, get: function () { return Device_1.Device; } }); -Object.defineProperty(exports, "detectDevice", { enumerable: true, get: function () { return Device_1.detectDevice; } }); -const types = __importStar(require("./types")); -exports.types = types; -/** - * Expose mediasoup-client version. - */ -exports.version = '3.6.47'; -/** - * Expose parseScalabilityMode() function. - */ -var scalabilityModes_1 = require("./scalabilityModes"); -Object.defineProperty(exports, "parseScalabilityMode", { enumerable: true, get: function () { return scalabilityModes_1.parse; } }); +exports.Edge11 = Edge11; -},{"./Device":11,"./scalabilityModes":37,"./types":38,"debug":40}],36:[function(require,module,exports){ +},{"../Logger":17,"../errors":22,"../ortc":43,"../utils":46,"./HandlerInterface":30,"./ortc/edgeUtils":35}],29:[function(require,module,exports){ "use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; @@ -9376,1448 +7236,5769 @@ var __importStar = (this && this.__importStar) || function (mod) { return result; }; Object.defineProperty(exports, "__esModule", { value: true }); -exports.canReceive = exports.canSend = exports.generateProbatorRtpParameters = exports.reduceCodecs = exports.getSendingRemoteRtpParameters = exports.getSendingRtpParameters = exports.getRecvRtpCapabilities = exports.getExtendedRtpCapabilities = exports.validateSctpStreamParameters = exports.validateSctpParameters = exports.validateNumSctpStreams = exports.validateSctpCapabilities = exports.validateRtcpParameters = exports.validateRtpEncodingParameters = exports.validateRtpHeaderExtensionParameters = exports.validateRtpCodecParameters = exports.validateRtpParameters = exports.validateRtpHeaderExtension = exports.validateRtcpFeedback = exports.validateRtpCodecCapability = exports.validateRtpCapabilities = void 0; -const h264 = __importStar(require("h264-profile-level-id")); -const utils = __importStar(require("./utils")); -const RTP_PROBATOR_MID = 'probator'; -const RTP_PROBATOR_SSRC = 1234; -const RTP_PROBATOR_CODEC_PAYLOAD_TYPE = 127; -/** - * Validates RtpCapabilities. It may modify given data by adding missing - * fields with default values. - * It throws if invalid. - */ -function validateRtpCapabilities(caps) { - if (typeof caps !== 'object') - throw new TypeError('caps is not an object'); - // codecs is optional. If unset, fill with an empty array. - if (caps.codecs && !Array.isArray(caps.codecs)) - throw new TypeError('caps.codecs is not an array'); - else if (!caps.codecs) - caps.codecs = []; - for (const codec of caps.codecs) { - validateRtpCodecCapability(codec); - } - // headerExtensions is optional. If unset, fill with an empty array. - if (caps.headerExtensions && !Array.isArray(caps.headerExtensions)) - throw new TypeError('caps.headerExtensions is not an array'); - else if (!caps.headerExtensions) - caps.headerExtensions = []; - for (const ext of caps.headerExtensions) { - validateRtpHeaderExtension(ext); +exports.Firefox60 = void 0; +const sdpTransform = __importStar(require("sdp-transform")); +const Logger_1 = require("../Logger"); +const errors_1 = require("../errors"); +const utils = __importStar(require("../utils")); +const ortc = __importStar(require("../ortc")); +const sdpCommonUtils = __importStar(require("./sdp/commonUtils")); +const sdpUnifiedPlanUtils = __importStar(require("./sdp/unifiedPlanUtils")); +const HandlerInterface_1 = require("./HandlerInterface"); +const RemoteSdp_1 = require("./sdp/RemoteSdp"); +const scalabilityModes_1 = require("../scalabilityModes"); +const logger = new Logger_1.Logger('Firefox60'); +const SCTP_NUM_STREAMS = { OS: 16, MIS: 2048 }; +class Firefox60 extends HandlerInterface_1.HandlerInterface { + /** + * Creates a factory function. + */ + static createFactory() { + return () => new Firefox60(); } -} -exports.validateRtpCapabilities = validateRtpCapabilities; -/** - * Validates RtpCodecCapability. It may modify given data by adding missing - * fields with default values. - * It throws if invalid. - */ -function validateRtpCodecCapability(codec) { - const MimeTypeRegex = new RegExp('^(audio|video)/(.+)', 'i'); - if (typeof codec !== 'object') - throw new TypeError('codec is not an object'); - // mimeType is mandatory. - if (!codec.mimeType || typeof codec.mimeType !== 'string') - throw new TypeError('missing codec.mimeType'); - const mimeTypeMatch = MimeTypeRegex.exec(codec.mimeType); - if (!mimeTypeMatch) - throw new TypeError('invalid codec.mimeType'); - // Just override kind with media component of mimeType. - codec.kind = mimeTypeMatch[1].toLowerCase(); - // preferredPayloadType is optional. - if (codec.preferredPayloadType && typeof codec.preferredPayloadType !== 'number') - throw new TypeError('invalid codec.preferredPayloadType'); - // clockRate is mandatory. - if (typeof codec.clockRate !== 'number') - throw new TypeError('missing codec.clockRate'); - // channels is optional. If unset, set it to 1 (just if audio). - if (codec.kind === 'audio') { - if (typeof codec.channels !== 'number') - codec.channels = 1; + constructor() { + super(); + // Map of RTCTransceivers indexed by MID. + this._mapMidTransceiver = new Map(); + // Local stream for sending. + this._sendStream = new MediaStream(); + // Whether a DataChannel m=application section has been created. + this._hasDataChannelMediaSection = false; + // Sending DataChannel id value counter. Incremented for each new DataChannel. + this._nextSendSctpStreamId = 0; + // Got transport local and remote parameters. + this._transportReady = false; } - else { - delete codec.channels; + get name() { + return 'Firefox60'; } - // parameters is optional. If unset, set it to an empty object. - if (!codec.parameters || typeof codec.parameters !== 'object') - codec.parameters = {}; - for (const key of Object.keys(codec.parameters)) { - let value = codec.parameters[key]; - if (value === undefined) { - codec.parameters[key] = ''; - value = ''; + close() { + logger.debug('close()'); + // Close RTCPeerConnection. + if (this._pc) { + try { + this._pc.close(); + } + catch (error) { } } - if (typeof value !== 'string' && typeof value !== 'number') { - throw new TypeError(`invalid codec parameter [key:${key}s, value:${value}]`); + this.emit('@close'); + } + async getNativeRtpCapabilities() { + logger.debug('getNativeRtpCapabilities()'); + const pc = new RTCPeerConnection({ + iceServers: [], + iceTransportPolicy: 'all', + bundlePolicy: 'max-bundle', + rtcpMuxPolicy: 'require' + }); + // NOTE: We need to add a real video track to get the RID extension mapping. + const canvas = document.createElement('canvas'); + // NOTE: Otherwise Firefox fails in next line. + canvas.getContext('2d'); + const fakeStream = canvas.captureStream(); + const fakeVideoTrack = fakeStream.getVideoTracks()[0]; + try { + pc.addTransceiver('audio', { direction: 'sendrecv' }); + const videoTransceiver = pc.addTransceiver(fakeVideoTrack, { direction: 'sendrecv' }); + const parameters = videoTransceiver.sender.getParameters(); + const encodings = [ + { rid: 'r0', maxBitrate: 100000 }, + { rid: 'r1', maxBitrate: 500000 } + ]; + parameters.encodings = encodings; + await videoTransceiver.sender.setParameters(parameters); + const offer = await pc.createOffer(); + try { + canvas.remove(); + } + catch (error) { } + try { + fakeVideoTrack.stop(); + } + catch (error) { } + try { + pc.close(); + } + catch (error) { } + const sdpObject = sdpTransform.parse(offer.sdp); + const nativeRtpCapabilities = sdpCommonUtils.extractRtpCapabilities({ sdpObject }); + return nativeRtpCapabilities; } - // Specific parameters validation. - if (key === 'apt') { - if (typeof value !== 'number') - throw new TypeError('invalid codec apt parameter'); + catch (error) { + try { + canvas.remove(); + } + catch (error2) { } + try { + fakeVideoTrack.stop(); + } + catch (error2) { } + try { + pc.close(); + } + catch (error2) { } + throw error; } } - // rtcpFeedback is optional. If unset, set it to an empty array. - if (!codec.rtcpFeedback || !Array.isArray(codec.rtcpFeedback)) - codec.rtcpFeedback = []; - for (const fb of codec.rtcpFeedback) { - validateRtcpFeedback(fb); + async getNativeSctpCapabilities() { + logger.debug('getNativeSctpCapabilities()'); + return { + numStreams: SCTP_NUM_STREAMS + }; } -} -exports.validateRtpCodecCapability = validateRtpCodecCapability; -/** - * Validates RtcpFeedback. It may modify given data by adding missing - * fields with default values. - * It throws if invalid. - */ -function validateRtcpFeedback(fb) { - if (typeof fb !== 'object') - throw new TypeError('fb is not an object'); - // type is mandatory. - if (!fb.type || typeof fb.type !== 'string') - throw new TypeError('missing fb.type'); - // parameter is optional. If unset set it to an empty string. - if (!fb.parameter || typeof fb.parameter !== 'string') - fb.parameter = ''; -} -exports.validateRtcpFeedback = validateRtcpFeedback; -/** - * Validates RtpHeaderExtension. It may modify given data by adding missing - * fields with default values. - * It throws if invalid. - */ -function validateRtpHeaderExtension(ext) { - if (typeof ext !== 'object') - throw new TypeError('ext is not an object'); - // kind is mandatory. - if (ext.kind !== 'audio' && ext.kind !== 'video') - throw new TypeError('invalid ext.kind'); - // uri is mandatory. - if (!ext.uri || typeof ext.uri !== 'string') - throw new TypeError('missing ext.uri'); - // preferredId is mandatory. - if (typeof ext.preferredId !== 'number') - throw new TypeError('missing ext.preferredId'); - // preferredEncrypt is optional. If unset set it to false. - if (ext.preferredEncrypt && typeof ext.preferredEncrypt !== 'boolean') - throw new TypeError('invalid ext.preferredEncrypt'); - else if (!ext.preferredEncrypt) - ext.preferredEncrypt = false; - // direction is optional. If unset set it to sendrecv. - if (ext.direction && typeof ext.direction !== 'string') - throw new TypeError('invalid ext.direction'); - else if (!ext.direction) - ext.direction = 'sendrecv'; -} -exports.validateRtpHeaderExtension = validateRtpHeaderExtension; -/** - * Validates RtpParameters. It may modify given data by adding missing - * fields with default values. - * It throws if invalid. - */ -function validateRtpParameters(params) { - if (typeof params !== 'object') - throw new TypeError('params is not an object'); - // mid is optional. - if (params.mid && typeof params.mid !== 'string') - throw new TypeError('params.mid is not a string'); - // codecs is mandatory. - if (!Array.isArray(params.codecs)) - throw new TypeError('missing params.codecs'); - for (const codec of params.codecs) { - validateRtpCodecParameters(codec); + run({ direction, iceParameters, iceCandidates, dtlsParameters, sctpParameters, iceServers, iceTransportPolicy, additionalSettings, proprietaryConstraints, extendedRtpCapabilities }) { + logger.debug('run()'); + this._direction = direction; + this._remoteSdp = new RemoteSdp_1.RemoteSdp({ + iceParameters, + iceCandidates, + dtlsParameters, + sctpParameters + }); + this._sendingRtpParametersByKind = + { + audio: ortc.getSendingRtpParameters('audio', extendedRtpCapabilities), + video: ortc.getSendingRtpParameters('video', extendedRtpCapabilities) + }; + this._sendingRemoteRtpParametersByKind = + { + audio: ortc.getSendingRemoteRtpParameters('audio', extendedRtpCapabilities), + video: ortc.getSendingRemoteRtpParameters('video', extendedRtpCapabilities) + }; + this._pc = new RTCPeerConnection({ + iceServers: iceServers || [], + iceTransportPolicy: iceTransportPolicy || 'all', + bundlePolicy: 'max-bundle', + rtcpMuxPolicy: 'require', + ...additionalSettings + }, proprietaryConstraints); + if (this._pc.connectionState) { + this._pc.addEventListener('connectionstatechange', () => { + this.emit('@connectionstatechange', this._pc.connectionState); + }); + } + else { + this._pc.addEventListener('iceconnectionstatechange', () => { + logger.warn('run() | pc.connectionState not supported, using pc.iceConnectionState'); + switch (this._pc.iceConnectionState) { + 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; + } + }); + } } - // headerExtensions is optional. If unset, fill with an empty array. - if (params.headerExtensions && !Array.isArray(params.headerExtensions)) - throw new TypeError('params.headerExtensions is not an array'); - else if (!params.headerExtensions) - params.headerExtensions = []; - for (const ext of params.headerExtensions) { - validateRtpHeaderExtensionParameters(ext); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async updateIceServers(iceServers) { + // NOTE: Firefox does not implement pc.setConfiguration(). + throw new errors_1.UnsupportedError('not supported'); } - // encodings is optional. If unset, fill with an empty array. - if (params.encodings && !Array.isArray(params.encodings)) - throw new TypeError('params.encodings is not an array'); - else if (!params.encodings) - params.encodings = []; - for (const encoding of params.encodings) { - validateRtpEncodingParameters(encoding); - } - // rtcp is optional. If unset, fill with an empty object. - if (params.rtcp && typeof params.rtcp !== 'object') - throw new TypeError('params.rtcp is not an object'); - else if (!params.rtcp) - params.rtcp = {}; - validateRtcpParameters(params.rtcp); -} -exports.validateRtpParameters = validateRtpParameters; -/** - * Validates RtpCodecParameters. It may modify given data by adding missing - * fields with default values. - * It throws if invalid. - */ -function validateRtpCodecParameters(codec) { - const MimeTypeRegex = new RegExp('^(audio|video)/(.+)', 'i'); - if (typeof codec !== 'object') - throw new TypeError('codec is not an object'); - // mimeType is mandatory. - if (!codec.mimeType || typeof codec.mimeType !== 'string') - throw new TypeError('missing codec.mimeType'); - const mimeTypeMatch = MimeTypeRegex.exec(codec.mimeType); - if (!mimeTypeMatch) - throw new TypeError('invalid codec.mimeType'); - // payloadType is mandatory. - if (typeof codec.payloadType !== 'number') - throw new TypeError('missing codec.payloadType'); - // clockRate is mandatory. - if (typeof codec.clockRate !== 'number') - throw new TypeError('missing codec.clockRate'); - const kind = mimeTypeMatch[1].toLowerCase(); - // channels is optional. If unset, set it to 1 (just if audio). - if (kind === 'audio') { - if (typeof codec.channels !== 'number') - codec.channels = 1; + async restartIce(iceParameters) { + logger.debug('restartIce()'); + // Provide the remote SDP handler with new remote ICE parameters. + this._remoteSdp.updateIceParameters(iceParameters); + if (!this._transportReady) { + return; + } + if (this._direction === 'send') { + const offer = await this._pc.createOffer({ iceRestart: true }); + logger.debug('restartIce() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('restartIce() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); + } + else { + const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; + logger.debug('restartIce() | calling pc.setRemoteDescription() [offer:%o]', offer); + await this._pc.setRemoteDescription(offer); + const answer = await this._pc.createAnswer(); + logger.debug('restartIce() | calling pc.setLocalDescription() [answer:%o]', answer); + await this._pc.setLocalDescription(answer); + } } - else { - delete codec.channels; + async getTransportStats() { + return this._pc.getStats(); } - // parameters is optional. If unset, set it to an empty object. - if (!codec.parameters || typeof codec.parameters !== 'object') - codec.parameters = {}; - for (const key of Object.keys(codec.parameters)) { - let value = codec.parameters[key]; - if (value === undefined) { - codec.parameters[key] = ''; - value = ''; + async send({ track, encodings, codecOptions, codec }) { + this.assertSendDirection(); + logger.debug('send() [kind:%s, track.id:%s]', track.kind, track.id); + if (encodings) { + encodings = utils.clone(encodings, []); + if (encodings.length > 1) { + encodings.forEach((encoding, idx) => { + encoding.rid = `r${idx}`; + }); + // Clone the encodings and reverse them because Firefox likes them + // from high to low. + encodings.reverse(); + } } - if (typeof value !== 'string' && typeof value !== 'number') { - throw new TypeError(`invalid codec parameter [key:${key}s, value:${value}]`); + const sendingRtpParameters = utils.clone(this._sendingRtpParametersByKind[track.kind], {}); + // This may throw. + sendingRtpParameters.codecs = + ortc.reduceCodecs(sendingRtpParameters.codecs, codec); + const sendingRemoteRtpParameters = utils.clone(this._sendingRemoteRtpParametersByKind[track.kind], {}); + // This may throw. + sendingRemoteRtpParameters.codecs = + ortc.reduceCodecs(sendingRemoteRtpParameters.codecs, codec); + // NOTE: Firefox fails sometimes to properly anticipate the closed media + // section that it should use, so don't reuse closed media sections. + // https://github.com/versatica/mediasoup-client/issues/104 + // + // const mediaSectionIdx = this._remoteSdp!.getNextMediaSectionIdx(); + const transceiver = this._pc.addTransceiver(track, { direction: 'sendonly', streams: [this._sendStream] }); + // NOTE: This is not spec compliants. Encodings should be given in addTransceiver + // second argument, but Firefox does not support it. + if (encodings) { + const parameters = transceiver.sender.getParameters(); + parameters.encodings = encodings; + await transceiver.sender.setParameters(parameters); } - // Specific parameters validation. - if (key === 'apt') { - if (typeof value !== 'number') - throw new TypeError('invalid codec apt parameter'); + const offer = await this._pc.createOffer(); + let localSdpObject = sdpTransform.parse(offer.sdp); + // In Firefox use DTLS role client even if we are the "offerer" since + // Firefox does not respect ICE-Lite. + if (!this._transportReady) { + await this.setupTransport({ localDtlsRole: 'client', localSdpObject }); + } + const layers = (0, scalabilityModes_1.parse)((encodings || [{}])[0].scalabilityMode); + logger.debug('send() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + // We can now get the transceiver.mid. + const localId = transceiver.mid; + // Set MID. + sendingRtpParameters.mid = localId; + localSdpObject = sdpTransform.parse(this._pc.localDescription.sdp); + const offerMediaObject = localSdpObject.media[localSdpObject.media.length - 1]; + // Set RTCP CNAME. + sendingRtpParameters.rtcp.cname = + sdpCommonUtils.getCname({ offerMediaObject }); + // Set RTP encodings by parsing the SDP offer if no encodings are given. + if (!encodings) { + sendingRtpParameters.encodings = + sdpUnifiedPlanUtils.getRtpEncodings({ offerMediaObject }); + } + // Set RTP encodings by parsing the SDP offer and complete them with given + // one if just a single encoding has been given. + else if (encodings.length === 1) { + const newEncodings = sdpUnifiedPlanUtils.getRtpEncodings({ offerMediaObject }); + Object.assign(newEncodings[0], encodings[0]); + sendingRtpParameters.encodings = newEncodings; + } + // Otherwise if more than 1 encoding are given use them verbatim (but + // reverse them back since we reversed them above to satisfy Firefox). + else { + sendingRtpParameters.encodings = encodings.reverse(); + } + // If VP8 or H264 and there is effective simulcast, add scalabilityMode to + // each encoding. + if (sendingRtpParameters.encodings.length > 1 && + (sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/vp8' || + sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/h264')) { + for (const encoding of sendingRtpParameters.encodings) { + if (encoding.scalabilityMode) { + encoding.scalabilityMode = `L1T${layers.temporalLayers}`; + } + else { + encoding.scalabilityMode = 'L1T3'; + } + } } + this._remoteSdp.send({ + offerMediaObject, + offerRtpParameters: sendingRtpParameters, + answerRtpParameters: sendingRemoteRtpParameters, + codecOptions, + extmapAllowMixed: true + }); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('send() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); + // Store in the map. + this._mapMidTransceiver.set(localId, transceiver); + return { + localId, + rtpParameters: sendingRtpParameters, + rtpSender: transceiver.sender + }; } - // rtcpFeedback is optional. If unset, set it to an empty array. - if (!codec.rtcpFeedback || !Array.isArray(codec.rtcpFeedback)) - codec.rtcpFeedback = []; - for (const fb of codec.rtcpFeedback) { - validateRtcpFeedback(fb); + async stopSending(localId) { + logger.debug('stopSending() [localId:%s]', localId); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated transceiver not found'); + } + transceiver.sender.replaceTrack(null); + // NOTE: Cannot use stop() the transceiver due to the the note above in + // send() method. + // try + // { + // transceiver.stop(); + // } + // catch (error) + // {} + this._pc.removeTrack(transceiver.sender); + // NOTE: Cannot use closeMediaSection() due to the the note above in send() + // method. + // this._remoteSdp!.closeMediaSection(transceiver.mid); + this._remoteSdp.disableMediaSection(transceiver.mid); + const offer = await this._pc.createOffer(); + logger.debug('stopSending() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('stopSending() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); + this._mapMidTransceiver.delete(localId); } -} -exports.validateRtpCodecParameters = validateRtpCodecParameters; -/** - * Validates RtpHeaderExtensionParameteters. It may modify given data by adding missing - * fields with default values. - * It throws if invalid. - */ -function validateRtpHeaderExtensionParameters(ext) { - if (typeof ext !== 'object') - throw new TypeError('ext is not an object'); - // uri is mandatory. - if (!ext.uri || typeof ext.uri !== 'string') - throw new TypeError('missing ext.uri'); - // id is mandatory. - if (typeof ext.id !== 'number') - throw new TypeError('missing ext.id'); - // encrypt is optional. If unset set it to false. - if (ext.encrypt && typeof ext.encrypt !== 'boolean') - throw new TypeError('invalid ext.encrypt'); - else if (!ext.encrypt) - ext.encrypt = false; - // parameters is optional. If unset, set it to an empty object. - if (!ext.parameters || typeof ext.parameters !== 'object') - ext.parameters = {}; - for (const key of Object.keys(ext.parameters)) { - let value = ext.parameters[key]; - if (value === undefined) { - ext.parameters[key] = ''; - value = ''; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async pauseSending(localId) { + this.assertSendDirection(); + logger.debug('pauseSending() [localId:%s]', localId); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); } - if (typeof value !== 'string' && typeof value !== 'number') - throw new TypeError('invalid header extension parameter'); + transceiver.direction = 'inactive'; + this._remoteSdp.pauseMediaSection(localId); + const offer = await this._pc.createOffer(); + logger.debug('pauseSending() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('pauseSending() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); } -} -exports.validateRtpHeaderExtensionParameters = validateRtpHeaderExtensionParameters; -/** - * Validates RtpEncodingParameters. It may modify given data by adding missing - * fields with default values. - * It throws if invalid. - */ -function validateRtpEncodingParameters(encoding) { - if (typeof encoding !== 'object') - throw new TypeError('encoding is not an object'); - // ssrc is optional. - if (encoding.ssrc && typeof encoding.ssrc !== 'number') - throw new TypeError('invalid encoding.ssrc'); - // rid is optional. - if (encoding.rid && typeof encoding.rid !== 'string') - throw new TypeError('invalid encoding.rid'); - // rtx is optional. - if (encoding.rtx && typeof encoding.rtx !== 'object') { - throw new TypeError('invalid encoding.rtx'); + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async resumeSending(localId) { + this.assertSendDirection(); + logger.debug('resumeSending() [localId:%s]', localId); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); + } + transceiver.direction = 'sendonly'; + this._remoteSdp.resumeSendingMediaSection(localId); + const offer = await this._pc.createOffer(); + logger.debug('resumeSending() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('resumeSending() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); } - else if (encoding.rtx) { - // RTX ssrc is mandatory if rtx is present. - if (typeof encoding.rtx.ssrc !== 'number') - throw new TypeError('missing encoding.rtx.ssrc'); + async replaceTrack(localId, track) { + this.assertSendDirection(); + if (track) { + logger.debug('replaceTrack() [localId:%s, track.id:%s]', localId, track.id); + } + else { + logger.debug('replaceTrack() [localId:%s, no track]', localId); + } + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); + } + await transceiver.sender.replaceTrack(track); } - // dtx is optional. If unset set it to false. - if (!encoding.dtx || typeof encoding.dtx !== 'boolean') - encoding.dtx = false; - // scalabilityMode is optional. - if (encoding.scalabilityMode && typeof encoding.scalabilityMode !== 'string') - throw new TypeError('invalid encoding.scalabilityMode'); -} -exports.validateRtpEncodingParameters = validateRtpEncodingParameters; -/** - * Validates RtcpParameters. It may modify given data by adding missing - * fields with default values. - * It throws if invalid. - */ -function validateRtcpParameters(rtcp) { - if (typeof rtcp !== 'object') - throw new TypeError('rtcp is not an object'); - // cname is optional. - if (rtcp.cname && typeof rtcp.cname !== 'string') - throw new TypeError('invalid rtcp.cname'); - // reducedSize is optional. If unset set it to true. - if (!rtcp.reducedSize || typeof rtcp.reducedSize !== 'boolean') - rtcp.reducedSize = true; -} -exports.validateRtcpParameters = validateRtcpParameters; -/** - * Validates SctpCapabilities. It may modify given data by adding missing - * fields with default values. - * It throws if invalid. - */ -function validateSctpCapabilities(caps) { - if (typeof caps !== 'object') - throw new TypeError('caps is not an object'); - // numStreams is mandatory. - if (!caps.numStreams || typeof caps.numStreams !== 'object') - throw new TypeError('missing caps.numStreams'); - validateNumSctpStreams(caps.numStreams); -} -exports.validateSctpCapabilities = validateSctpCapabilities; -/** - * Validates NumSctpStreams. It may modify given data by adding missing - * fields with default values. - * It throws if invalid. - */ -function validateNumSctpStreams(numStreams) { - if (typeof numStreams !== 'object') - throw new TypeError('numStreams is not an object'); - // OS is mandatory. - if (typeof numStreams.OS !== 'number') - throw new TypeError('missing numStreams.OS'); - // MIS is mandatory. - if (typeof numStreams.MIS !== 'number') - throw new TypeError('missing numStreams.MIS'); -} -exports.validateNumSctpStreams = validateNumSctpStreams; -/** - * Validates SctpParameters. It may modify given data by adding missing - * fields with default values. - * It throws if invalid. - */ -function validateSctpParameters(params) { - if (typeof params !== 'object') - throw new TypeError('params is not an object'); - // port is mandatory. - if (typeof params.port !== 'number') - throw new TypeError('missing params.port'); - // OS is mandatory. - if (typeof params.OS !== 'number') - throw new TypeError('missing params.OS'); - // MIS is mandatory. - if (typeof params.MIS !== 'number') - throw new TypeError('missing params.MIS'); - // maxMessageSize is mandatory. - if (typeof params.maxMessageSize !== 'number') - throw new TypeError('missing params.maxMessageSize'); -} -exports.validateSctpParameters = validateSctpParameters; -/** - * Validates SctpStreamParameters. It may modify given data by adding missing - * fields with default values. - * It throws if invalid. - */ -function validateSctpStreamParameters(params) { - if (typeof params !== 'object') - throw new TypeError('params is not an object'); - // streamId is mandatory. - if (typeof params.streamId !== 'number') - throw new TypeError('missing params.streamId'); - // ordered is optional. - let orderedGiven = false; - if (typeof params.ordered === 'boolean') - orderedGiven = true; - else - params.ordered = true; - // maxPacketLifeTime is optional. - if (params.maxPacketLifeTime && typeof params.maxPacketLifeTime !== 'number') - throw new TypeError('invalid params.maxPacketLifeTime'); - // maxRetransmits is optional. - if (params.maxRetransmits && typeof params.maxRetransmits !== 'number') - throw new TypeError('invalid params.maxRetransmits'); - if (params.maxPacketLifeTime && params.maxRetransmits) - throw new TypeError('cannot provide both maxPacketLifeTime and maxRetransmits'); - if (orderedGiven && - params.ordered && - (params.maxPacketLifeTime || params.maxRetransmits)) { - throw new TypeError('cannot be ordered with maxPacketLifeTime or maxRetransmits'); - } - else if (!orderedGiven && (params.maxPacketLifeTime || params.maxRetransmits)) { - params.ordered = false; + async setMaxSpatialLayer(localId, spatialLayer) { + this.assertSendDirection(); + logger.debug('setMaxSpatialLayer() [localId:%s, spatialLayer:%s]', localId, spatialLayer); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated transceiver not found'); + } + const parameters = transceiver.sender.getParameters(); + // NOTE: We require encodings given from low to high, however Firefox + // requires them in reverse order, so do magic here. + spatialLayer = parameters.encodings.length - 1 - spatialLayer; + parameters.encodings.forEach((encoding, idx) => { + if (idx >= spatialLayer) { + encoding.active = true; + } + else { + encoding.active = false; + } + }); + await transceiver.sender.setParameters(parameters); + this._remoteSdp.muxMediaSectionSimulcast(localId, parameters.encodings); + const offer = await this._pc.createOffer(); + logger.debug('setMaxSpatialLayer() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('setMaxSpatialLayer() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); } - // label is optional. - if (params.label && typeof params.label !== 'string') - throw new TypeError('invalid params.label'); - // protocol is optional. - if (params.protocol && typeof params.protocol !== 'string') - throw new TypeError('invalid params.protocol'); -} -exports.validateSctpStreamParameters = validateSctpStreamParameters; -/** - * Generate extended RTP capabilities for sending and receiving. - */ -function getExtendedRtpCapabilities(localCaps, remoteCaps) { - const extendedRtpCapabilities = { - codecs: [], - headerExtensions: [] - }; - // Match media codecs and keep the order preferred by remoteCaps. - for (const remoteCodec of remoteCaps.codecs || []) { - if (isRtxCodec(remoteCodec)) - continue; - const matchingLocalCodec = (localCaps.codecs || []) - .find((localCodec) => (matchCodecs(localCodec, remoteCodec, { strict: true, modify: true }))); - if (!matchingLocalCodec) - continue; - const extendedCodec = { - mimeType: matchingLocalCodec.mimeType, - kind: matchingLocalCodec.kind, - clockRate: matchingLocalCodec.clockRate, - channels: matchingLocalCodec.channels, - localPayloadType: matchingLocalCodec.preferredPayloadType, - localRtxPayloadType: undefined, - remotePayloadType: remoteCodec.preferredPayloadType, - remoteRtxPayloadType: undefined, - localParameters: matchingLocalCodec.parameters, - remoteParameters: remoteCodec.parameters, - rtcpFeedback: reduceRtcpFeedback(matchingLocalCodec, remoteCodec) - }; - extendedRtpCapabilities.codecs.push(extendedCodec); + async setRtpEncodingParameters(localId, params) { + this.assertSendDirection(); + logger.debug('setRtpEncodingParameters() [localId:%s, params:%o]', localId, params); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); + } + const parameters = transceiver.sender.getParameters(); + parameters.encodings.forEach((encoding, idx) => { + parameters.encodings[idx] = { ...encoding, ...params }; + }); + await transceiver.sender.setParameters(parameters); + this._remoteSdp.muxMediaSectionSimulcast(localId, parameters.encodings); + const offer = await this._pc.createOffer(); + logger.debug('setRtpEncodingParameters() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('setRtpEncodingParameters() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); } - // Match RTX codecs. - for (const extendedCodec of extendedRtpCapabilities.codecs) { - const matchingLocalRtxCodec = localCaps.codecs - .find((localCodec) => (isRtxCodec(localCodec) && - localCodec.parameters.apt === extendedCodec.localPayloadType)); - const matchingRemoteRtxCodec = remoteCaps.codecs - .find((remoteCodec) => (isRtxCodec(remoteCodec) && - remoteCodec.parameters.apt === extendedCodec.remotePayloadType)); - if (matchingLocalRtxCodec && matchingRemoteRtxCodec) { - extendedCodec.localRtxPayloadType = matchingLocalRtxCodec.preferredPayloadType; - extendedCodec.remoteRtxPayloadType = matchingRemoteRtxCodec.preferredPayloadType; + async getSenderStats(localId) { + this.assertSendDirection(); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); } + return transceiver.sender.getStats(); } - // Match header extensions. - for (const remoteExt of remoteCaps.headerExtensions) { - const matchingLocalExt = localCaps.headerExtensions - .find((localExt) => (matchHeaderExtensions(localExt, remoteExt))); - if (!matchingLocalExt) - continue; - const extendedExt = { - kind: remoteExt.kind, - uri: remoteExt.uri, - sendId: matchingLocalExt.preferredId, - recvId: remoteExt.preferredId, - encrypt: matchingLocalExt.preferredEncrypt, - direction: 'sendrecv' + async sendDataChannel({ ordered, maxPacketLifeTime, maxRetransmits, label, protocol }) { + this.assertSendDirection(); + const options = { + negotiated: true, + id: this._nextSendSctpStreamId, + ordered, + maxPacketLifeTime, + maxRetransmits, + protocol }; - switch (remoteExt.direction) { - case 'sendrecv': - extendedExt.direction = 'sendrecv'; - break; - case 'recvonly': - extendedExt.direction = 'sendonly'; - break; - case 'sendonly': - extendedExt.direction = 'recvonly'; - break; - case 'inactive': - extendedExt.direction = 'inactive'; - break; + logger.debug('sendDataChannel() [options:%o]', options); + const dataChannel = this._pc.createDataChannel(label, options); + // Increase next id. + this._nextSendSctpStreamId = + ++this._nextSendSctpStreamId % SCTP_NUM_STREAMS.MIS; + // If this is the first DataChannel we need to create the SDP answer with + // m=application section. + if (!this._hasDataChannelMediaSection) { + const offer = await this._pc.createOffer(); + const localSdpObject = sdpTransform.parse(offer.sdp); + const offerMediaObject = localSdpObject.media + .find((m) => m.type === 'application'); + if (!this._transportReady) { + await this.setupTransport({ localDtlsRole: 'client', localSdpObject }); + } + logger.debug('sendDataChannel() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + this._remoteSdp.sendSctpAssociation({ offerMediaObject }); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('sendDataChannel() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); + this._hasDataChannelMediaSection = true; } - extendedRtpCapabilities.headerExtensions.push(extendedExt); - } - return extendedRtpCapabilities; -} -exports.getExtendedRtpCapabilities = getExtendedRtpCapabilities; -/** - * Generate RTP capabilities for receiving media based on the given extended - * RTP capabilities. - */ -function getRecvRtpCapabilities(extendedRtpCapabilities) { - const rtpCapabilities = { - codecs: [], - headerExtensions: [] - }; - for (const extendedCodec of extendedRtpCapabilities.codecs) { - const codec = { - mimeType: extendedCodec.mimeType, - kind: extendedCodec.kind, - preferredPayloadType: extendedCodec.remotePayloadType, - clockRate: extendedCodec.clockRate, - channels: extendedCodec.channels, - parameters: extendedCodec.localParameters, - rtcpFeedback: extendedCodec.rtcpFeedback + const sctpStreamParameters = { + streamId: options.id, + ordered: options.ordered, + maxPacketLifeTime: options.maxPacketLifeTime, + maxRetransmits: options.maxRetransmits }; - rtpCapabilities.codecs.push(codec); - // Add RTX codec. - if (!extendedCodec.remoteRtxPayloadType) - continue; - const rtxCodec = { - mimeType: `${extendedCodec.kind}/rtx`, - kind: extendedCodec.kind, - preferredPayloadType: extendedCodec.remoteRtxPayloadType, - clockRate: extendedCodec.clockRate, - parameters: { - apt: extendedCodec.remotePayloadType - }, - rtcpFeedback: [] - }; - rtpCapabilities.codecs.push(rtxCodec); - // TODO: In the future, we need to add FEC, CN, etc, codecs. + return { dataChannel, sctpStreamParameters }; } - for (const extendedExtension of extendedRtpCapabilities.headerExtensions) { - // Ignore RTP extensions not valid for receiving. - if (extendedExtension.direction !== 'sendrecv' && - extendedExtension.direction !== 'recvonly') { - continue; + async receive( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + optionsList) { + this.assertRecvDirection(); + const results = []; + const mapLocalId = new Map(); + for (const options of optionsList) { + const { trackId, kind, rtpParameters, streamId } = options; + logger.debug('receive() [trackId:%s, kind:%s]', trackId, kind); + const localId = rtpParameters.mid || String(this._mapMidTransceiver.size); + mapLocalId.set(trackId, localId); + this._remoteSdp.receive({ + mid: localId, + kind, + offerRtpParameters: rtpParameters, + streamId: streamId || rtpParameters.rtcp.cname, + trackId + }); } - const ext = { - kind: extendedExtension.kind, - uri: extendedExtension.uri, - preferredId: extendedExtension.recvId, - preferredEncrypt: extendedExtension.encrypt, - direction: extendedExtension.direction - }; - rtpCapabilities.headerExtensions.push(ext); - } - return rtpCapabilities; -} -exports.getRecvRtpCapabilities = getRecvRtpCapabilities; -/** - * Generate RTP parameters of the given kind for sending media. - * NOTE: mid, encodings and rtcp fields are left empty. - */ -function getSendingRtpParameters(kind, extendedRtpCapabilities) { - const rtpParameters = { - mid: undefined, - codecs: [], - headerExtensions: [], - encodings: [], - rtcp: {} - }; - for (const extendedCodec of extendedRtpCapabilities.codecs) { - if (extendedCodec.kind !== kind) - continue; - const codec = { - mimeType: extendedCodec.mimeType, - payloadType: extendedCodec.localPayloadType, - clockRate: extendedCodec.clockRate, - channels: extendedCodec.channels, - parameters: extendedCodec.localParameters, - rtcpFeedback: extendedCodec.rtcpFeedback - }; - rtpParameters.codecs.push(codec); - // Add RTX codec. - if (extendedCodec.localRtxPayloadType) { - const rtxCodec = { - mimeType: `${extendedCodec.kind}/rtx`, - payloadType: extendedCodec.localRtxPayloadType, - clockRate: extendedCodec.clockRate, - parameters: { - apt: extendedCodec.localPayloadType - }, - rtcpFeedback: [] - }; - rtpParameters.codecs.push(rtxCodec); + const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; + logger.debug('receive() | calling pc.setRemoteDescription() [offer:%o]', offer); + await this._pc.setRemoteDescription(offer); + let answer = await this._pc.createAnswer(); + const localSdpObject = sdpTransform.parse(answer.sdp); + for (const options of optionsList) { + const { trackId, rtpParameters } = options; + const localId = mapLocalId.get(trackId); + const answerMediaObject = localSdpObject.media + .find((m) => String(m.mid) === localId); + // May need to modify codec parameters in the answer based on codec + // parameters in the offer. + sdpCommonUtils.applyCodecParameters({ + offerRtpParameters: rtpParameters, + answerMediaObject + }); + answer = { type: 'answer', sdp: sdpTransform.write(localSdpObject) }; + } + if (!this._transportReady) { + await this.setupTransport({ localDtlsRole: 'client', localSdpObject }); + } + logger.debug('receive() | calling pc.setLocalDescription() [answer:%o]', answer); + await this._pc.setLocalDescription(answer); + for (const options of optionsList) { + const { trackId } = options; + const localId = mapLocalId.get(trackId); + const transceiver = this._pc.getTransceivers() + .find((t) => t.mid === localId); + if (!transceiver) { + throw new Error('new RTCRtpTransceiver not found'); + } + // Store in the map. + this._mapMidTransceiver.set(localId, transceiver); + results.push({ + localId, + track: transceiver.receiver.track, + rtpReceiver: transceiver.receiver + }); } + return results; } - for (const extendedExtension of extendedRtpCapabilities.headerExtensions) { - // Ignore RTP extensions of a different kind and those not valid for sending. - if ((extendedExtension.kind && extendedExtension.kind !== kind) || - (extendedExtension.direction !== 'sendrecv' && - extendedExtension.direction !== 'sendonly')) { - continue; + async stopReceiving(localIds) { + this.assertRecvDirection(); + for (const localId of localIds) { + logger.debug('stopReceiving() [localId:%s]', localId); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); + } + this._remoteSdp.closeMediaSection(transceiver.mid); + } + const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; + logger.debug('stopReceiving() | calling pc.setRemoteDescription() [offer:%o]', offer); + await this._pc.setRemoteDescription(offer); + const answer = await this._pc.createAnswer(); + logger.debug('stopReceiving() | calling pc.setLocalDescription() [answer:%o]', answer); + await this._pc.setLocalDescription(answer); + for (const localId of localIds) { + this._mapMidTransceiver.delete(localId); } - const ext = { - uri: extendedExtension.uri, - id: extendedExtension.sendId, - encrypt: extendedExtension.encrypt, - parameters: {} - }; - rtpParameters.headerExtensions.push(ext); } - return rtpParameters; -} -exports.getSendingRtpParameters = getSendingRtpParameters; -/** - * Generate RTP parameters of the given kind suitable for the remote SDP answer. - */ -function getSendingRemoteRtpParameters(kind, extendedRtpCapabilities) { - const rtpParameters = { - mid: undefined, - codecs: [], - headerExtensions: [], - encodings: [], - rtcp: {} - }; - for (const extendedCodec of extendedRtpCapabilities.codecs) { - if (extendedCodec.kind !== kind) - continue; - const codec = { - mimeType: extendedCodec.mimeType, - payloadType: extendedCodec.localPayloadType, - clockRate: extendedCodec.clockRate, - channels: extendedCodec.channels, - parameters: extendedCodec.remoteParameters, - rtcpFeedback: extendedCodec.rtcpFeedback - }; - rtpParameters.codecs.push(codec); - // Add RTX codec. - if (extendedCodec.localRtxPayloadType) { - const rtxCodec = { - mimeType: `${extendedCodec.kind}/rtx`, - payloadType: extendedCodec.localRtxPayloadType, - clockRate: extendedCodec.clockRate, - parameters: { - apt: extendedCodec.localPayloadType - }, - rtcpFeedback: [] - }; - rtpParameters.codecs.push(rtxCodec); + async pauseReceiving(localIds) { + this.assertRecvDirection(); + for (const localId of localIds) { + logger.debug('pauseReceiving() [localId:%s]', localId); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); + } + transceiver.direction = 'inactive'; + this._remoteSdp.pauseMediaSection(localId); } + const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; + logger.debug('pauseReceiving() | calling pc.setRemoteDescription() [offer:%o]', offer); + await this._pc.setRemoteDescription(offer); + const answer = await this._pc.createAnswer(); + logger.debug('pauseReceiving() | calling pc.setLocalDescription() [answer:%o]', answer); + await this._pc.setLocalDescription(answer); } - for (const extendedExtension of extendedRtpCapabilities.headerExtensions) { - // Ignore RTP extensions of a different kind and those not valid for sending. - if ((extendedExtension.kind && extendedExtension.kind !== kind) || - (extendedExtension.direction !== 'sendrecv' && - extendedExtension.direction !== 'sendonly')) { - continue; + async resumeReceiving(localIds) { + this.assertRecvDirection(); + for (const localId of localIds) { + logger.debug('resumeReceiving() [localId:%s]', localId); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); + } + transceiver.direction = 'recvonly'; + this._remoteSdp.resumeReceivingMediaSection(localId); } - const ext = { - uri: extendedExtension.uri, - id: extendedExtension.sendId, - encrypt: extendedExtension.encrypt, - parameters: {} - }; - rtpParameters.headerExtensions.push(ext); + const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; + logger.debug('resumeReceiving() | calling pc.setRemoteDescription() [offer:%o]', offer); + await this._pc.setRemoteDescription(offer); + const answer = await this._pc.createAnswer(); + logger.debug('resumeReceiving() | calling pc.setLocalDescription() [answer:%o]', answer); + await this._pc.setLocalDescription(answer); } - // Reduce codecs' RTCP feedback. Use Transport-CC if available, REMB otherwise. - if (rtpParameters.headerExtensions.some((ext) => (ext.uri === 'http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01'))) { - for (const codec of rtpParameters.codecs) { - codec.rtcpFeedback = (codec.rtcpFeedback || []) - .filter((fb) => fb.type !== 'goog-remb'); + async getReceiverStats(localId) { + this.assertRecvDirection(); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); } + return transceiver.receiver.getStats(); } - else if (rtpParameters.headerExtensions.some((ext) => (ext.uri === 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time'))) { - for (const codec of rtpParameters.codecs) { - codec.rtcpFeedback = (codec.rtcpFeedback || []) - .filter((fb) => fb.type !== 'transport-cc'); + async receiveDataChannel({ sctpStreamParameters, label, protocol }) { + this.assertRecvDirection(); + const { streamId, ordered, maxPacketLifeTime, maxRetransmits } = sctpStreamParameters; + const options = { + negotiated: true, + id: streamId, + ordered, + maxPacketLifeTime, + maxRetransmits, + protocol + }; + logger.debug('receiveDataChannel() [options:%o]', options); + const dataChannel = this._pc.createDataChannel(label, options); + // If this is the first DataChannel we need to create the SDP offer with + // m=application section. + if (!this._hasDataChannelMediaSection) { + this._remoteSdp.receiveSctpAssociation(); + const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; + logger.debug('receiveDataChannel() | calling pc.setRemoteDescription() [offer:%o]', offer); + await this._pc.setRemoteDescription(offer); + const answer = await this._pc.createAnswer(); + if (!this._transportReady) { + const localSdpObject = sdpTransform.parse(answer.sdp); + await this.setupTransport({ localDtlsRole: 'client', localSdpObject }); + } + logger.debug('receiveDataChannel() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setLocalDescription(answer); + this._hasDataChannelMediaSection = true; } + return { dataChannel }; } - else { - for (const codec of rtpParameters.codecs) { - codec.rtcpFeedback = (codec.rtcpFeedback || []) - .filter((fb) => (fb.type !== 'transport-cc' && - fb.type !== 'goog-remb')); + async setupTransport({ localDtlsRole, localSdpObject }) { + if (!localSdpObject) { + localSdpObject = sdpTransform.parse(this._pc.localDescription.sdp); } + // Get our local DTLS parameters. + const dtlsParameters = sdpCommonUtils.extractDtlsParameters({ sdpObject: localSdpObject }); + // Set our DTLS role. + dtlsParameters.role = localDtlsRole; + // Update the remote DTLS role in the SDP. + this._remoteSdp.updateDtlsRole(localDtlsRole === 'client' ? 'server' : 'client'); + // Need to tell the remote transport about our parameters. + await new Promise((resolve, reject) => { + this.safeEmit('@connect', { dtlsParameters }, resolve, reject); + }); + this._transportReady = true; } - return rtpParameters; -} -exports.getSendingRemoteRtpParameters = getSendingRemoteRtpParameters; -/** - * Reduce given codecs by returning an array of codecs "compatible" with the - * given capability codec. If no capability codec is given, take the first - * one(s). - * - * Given codecs must be generated by ortc.getSendingRtpParameters() or - * ortc.getSendingRemoteRtpParameters(). - * - * The returned array of codecs also include a RTX codec if available. - */ -function reduceCodecs(codecs, capCodec) { - const filteredCodecs = []; - // If no capability codec is given, take the first one (and RTX). - if (!capCodec) { - filteredCodecs.push(codecs[0]); - if (isRtxCodec(codecs[1])) - filteredCodecs.push(codecs[1]); + assertSendDirection() { + if (this._direction !== 'send') { + throw new Error('method can just be called for handlers with "send" direction'); + } } - // Otherwise look for a compatible set of codecs. - else { - for (let idx = 0; idx < codecs.length; ++idx) { - if (matchCodecs(codecs[idx], capCodec)) { - filteredCodecs.push(codecs[idx]); - if (isRtxCodec(codecs[idx + 1])) - filteredCodecs.push(codecs[idx + 1]); - break; - } + assertRecvDirection() { + if (this._direction !== 'recv') { + throw new Error('method can just be called for handlers with "recv" direction'); } - if (filteredCodecs.length === 0) - throw new TypeError('no matching codec found'); } - return filteredCodecs; -} -exports.reduceCodecs = reduceCodecs; -/** - * Create RTP parameters for a Consumer for the RTP probator. - */ -function generateProbatorRtpParameters(videoRtpParameters) { - // Clone given reference video RTP parameters. - videoRtpParameters = utils.clone(videoRtpParameters, {}); - // This may throw. - validateRtpParameters(videoRtpParameters); - const rtpParameters = { - mid: RTP_PROBATOR_MID, - codecs: [], - headerExtensions: [], - encodings: [{ ssrc: RTP_PROBATOR_SSRC }], - rtcp: { cname: 'probator' } - }; - rtpParameters.codecs.push(videoRtpParameters.codecs[0]); - rtpParameters.codecs[0].payloadType = RTP_PROBATOR_CODEC_PAYLOAD_TYPE; - rtpParameters.headerExtensions = videoRtpParameters.headerExtensions; - return rtpParameters; -} -exports.generateProbatorRtpParameters = generateProbatorRtpParameters; -/** - * Whether media can be sent based on the given RTP capabilities. - */ -function canSend(kind, extendedRtpCapabilities) { - return extendedRtpCapabilities.codecs. - some((codec) => codec.kind === kind); -} -exports.canSend = canSend; -/** - * Whether the given RTP parameters can be received with the given RTP - * capabilities. - */ -function canReceive(rtpParameters, extendedRtpCapabilities) { - // This may throw. - validateRtpParameters(rtpParameters); - if (rtpParameters.codecs.length === 0) - return false; - const firstMediaCodec = rtpParameters.codecs[0]; - return extendedRtpCapabilities.codecs - .some((codec) => codec.remotePayloadType === firstMediaCodec.payloadType); -} -exports.canReceive = canReceive; -function isRtxCodec(codec) { - if (!codec) - return false; - return /.+\/rtx$/i.test(codec.mimeType); -} -function matchCodecs(aCodec, bCodec, { strict = false, modify = false } = {}) { - const aMimeType = aCodec.mimeType.toLowerCase(); - const bMimeType = bCodec.mimeType.toLowerCase(); - if (aMimeType !== bMimeType) - return false; - if (aCodec.clockRate !== bCodec.clockRate) - return false; - if (aCodec.channels !== bCodec.channels) - return false; - // Per codec special checks. - switch (aMimeType) { - case 'video/h264': - { - // If strict matching check profile-level-id. - if (strict) { - const aPacketizationMode = aCodec.parameters['packetization-mode'] || 0; - const bPacketizationMode = bCodec.parameters['packetization-mode'] || 0; - if (aPacketizationMode !== bPacketizationMode) - return false; - if (!h264.isSameProfile(aCodec.parameters, bCodec.parameters)) - return false; - let selectedProfileLevelId; - try { - selectedProfileLevelId = - h264.generateProfileLevelIdForAnswer(aCodec.parameters, bCodec.parameters); - } - catch (error) { - return false; - } - if (modify) { - if (selectedProfileLevelId) { - aCodec.parameters['profile-level-id'] = selectedProfileLevelId; - bCodec.parameters['profile-level-id'] = selectedProfileLevelId; - } - else { - delete aCodec.parameters['profile-level-id']; - delete bCodec.parameters['profile-level-id']; - } - } - } - break; - } - case 'video/vp9': - { - // If strict matching check profile-id. - if (strict) { - const aProfileId = aCodec.parameters['profile-id'] || 0; - const bProfileId = bCodec.parameters['profile-id'] || 0; - if (aProfileId !== bProfileId) - return false; - } - break; - } - } - return true; -} -function matchHeaderExtensions(aExt, bExt) { - if (aExt.kind && bExt.kind && aExt.kind !== bExt.kind) - return false; - if (aExt.uri !== bExt.uri) - return false; - return true; -} -function reduceRtcpFeedback(codecA, codecB) { - const reducedRtcpFeedback = []; - for (const aFb of codecA.rtcpFeedback || []) { - const matchingBFb = (codecB.rtcpFeedback || []) - .find((bFb) => (bFb.type === aFb.type && - (bFb.parameter === aFb.parameter || (!bFb.parameter && !aFb.parameter)))); - if (matchingBFb) - reducedRtcpFeedback.push(matchingBFb); - } - return reducedRtcpFeedback; } +exports.Firefox60 = Firefox60; -},{"./utils":39,"h264-profile-level-id":4}],37:[function(require,module,exports){ +},{"../Logger":17,"../errors":22,"../ortc":43,"../scalabilityModes":44,"../utils":46,"./HandlerInterface":30,"./sdp/RemoteSdp":38,"./sdp/commonUtils":39,"./sdp/unifiedPlanUtils":41,"sdp-transform":52}],30:[function(require,module,exports){ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); -exports.parse = void 0; -const ScalabilityModeRegex = new RegExp('^[LS]([1-9]\\d{0,1})T([1-9]\\d{0,1})'); -function parse(scalabilityMode) { - const match = ScalabilityModeRegex.exec(scalabilityMode || ''); - if (match) { - return { - spatialLayers: Number(match[1]), - temporalLayers: Number(match[2]) - }; - } - else { - return { - spatialLayers: 1, - temporalLayers: 1 - }; +exports.HandlerInterface = void 0; +const EnhancedEventEmitter_1 = require("../EnhancedEventEmitter"); +class HandlerInterface extends EnhancedEventEmitter_1.EnhancedEventEmitter { + constructor() { + super(); } } -exports.parse = parse; +exports.HandlerInterface = HandlerInterface; -},{}],38:[function(require,module,exports){ +},{"../EnhancedEventEmitter":16}],31:[function(require,module,exports){ "use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; - Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); -var __exportStar = (this && this.__exportStar) || function(m, exports) { - for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; }; Object.defineProperty(exports, "__esModule", { value: true }); -__exportStar(require("./Device"), exports); -__exportStar(require("./Transport"), exports); -__exportStar(require("./Producer"), exports); -__exportStar(require("./Consumer"), exports); -__exportStar(require("./DataProducer"), exports); -__exportStar(require("./DataConsumer"), exports); -__exportStar(require("./RtpParameters"), exports); -__exportStar(require("./SctpParameters"), exports); -__exportStar(require("./handlers/HandlerInterface"), exports); -__exportStar(require("./errors"), exports); - -},{"./Consumer":8,"./DataConsumer":9,"./DataProducer":10,"./Device":11,"./Producer":14,"./RtpParameters":15,"./SctpParameters":16,"./Transport":17,"./errors":18,"./handlers/HandlerInterface":25}],39:[function(require,module,exports){ -"use strict"; -Object.defineProperty(exports, "__esModule", { value: true }); -exports.generateRandomNumber = exports.clone = void 0; -/** - * Clones the given data. - */ -function clone(data, defaultValue) { - if (typeof data === 'undefined') - return defaultValue; - return JSON.parse(JSON.stringify(data)); -} -exports.clone = clone; -/** - * Generates a random positive integer. - */ -function generateRandomNumber() { - return Math.round(Math.random() * 10000000); -} -exports.generateRandomNumber = generateRandomNumber; - -},{}],40:[function(require,module,exports){ -(function (process){(function (){ -/* eslint-env browser */ - -/** - * This is the web browser implementation of `debug()`. - */ - -exports.formatArgs = formatArgs; -exports.save = save; -exports.load = load; -exports.useColors = useColors; -exports.storage = localstorage(); -exports.destroy = (() => { - let warned = false; - - return () => { - if (!warned) { - warned = true; - console.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.'); - } - }; -})(); - -/** - * Colors. - */ - -exports.colors = [ - '#0000CC', - '#0000FF', - '#0033CC', - '#0033FF', - '#0066CC', - '#0066FF', - '#0099CC', - '#0099FF', - '#00CC00', - '#00CC33', - '#00CC66', - '#00CC99', - '#00CCCC', - '#00CCFF', - '#3300CC', - '#3300FF', - '#3333CC', - '#3333FF', - '#3366CC', - '#3366FF', - '#3399CC', - '#3399FF', - '#33CC00', - '#33CC33', - '#33CC66', - '#33CC99', - '#33CCCC', - '#33CCFF', - '#6600CC', - '#6600FF', - '#6633CC', - '#6633FF', - '#66CC00', - '#66CC33', - '#9900CC', - '#9900FF', - '#9933CC', - '#9933FF', - '#99CC00', - '#99CC33', - '#CC0000', - '#CC0033', - '#CC0066', - '#CC0099', - '#CC00CC', - '#CC00FF', - '#CC3300', - '#CC3333', - '#CC3366', - '#CC3399', - '#CC33CC', - '#CC33FF', - '#CC6600', - '#CC6633', - '#CC9900', - '#CC9933', - '#CCCC00', - '#CCCC33', - '#FF0000', - '#FF0033', - '#FF0066', - '#FF0099', - '#FF00CC', - '#FF00FF', - '#FF3300', - '#FF3333', - '#FF3366', - '#FF3399', - '#FF33CC', - '#FF33FF', - '#FF6600', - '#FF6633', - '#FF9900', - '#FF9933', - '#FFCC00', - '#FFCC33' -]; +exports.ReactNative = void 0; +const sdpTransform = __importStar(require("sdp-transform")); +const Logger_1 = require("../Logger"); +const errors_1 = require("../errors"); +const utils = __importStar(require("../utils")); +const ortc = __importStar(require("../ortc")); +const sdpCommonUtils = __importStar(require("./sdp/commonUtils")); +const sdpPlanBUtils = __importStar(require("./sdp/planBUtils")); +const HandlerInterface_1 = require("./HandlerInterface"); +const RemoteSdp_1 = require("./sdp/RemoteSdp"); +const logger = new Logger_1.Logger('ReactNative'); +const SCTP_NUM_STREAMS = { OS: 1024, MIS: 1024 }; +class ReactNative extends HandlerInterface_1.HandlerInterface { + /** + * Creates a factory function. + */ + static createFactory() { + return () => new ReactNative(); + } + constructor() { + super(); + // Local stream for sending. + this._sendStream = new MediaStream(); + // Map of sending MediaStreamTracks indexed by localId. + this._mapSendLocalIdTrack = new Map(); + // Next sending localId. + this._nextSendLocalId = 0; + // Map of MID, RTP parameters and RTCRtpReceiver indexed by local id. + // Value is an Object with mid, rtpParameters and rtpReceiver. + this._mapRecvLocalIdInfo = new Map(); + // Whether a DataChannel m=application section has been created. + this._hasDataChannelMediaSection = false; + // Sending DataChannel id value counter. Incremented for each new DataChannel. + this._nextSendSctpStreamId = 0; + // Got transport local and remote parameters. + this._transportReady = false; + } + get name() { + return 'ReactNative'; + } + close() { + logger.debug('close()'); + // Free/dispose native MediaStream but DO NOT free/dispose native + // MediaStreamTracks (that is parent's business). + // @ts-ignore (proprietary API in react-native-webrtc). + this._sendStream.release(/* releaseTracks */ false); + // Close RTCPeerConnection. + if (this._pc) { + try { + this._pc.close(); + } + catch (error) { } + } + this.emit('@close'); + } + async getNativeRtpCapabilities() { + logger.debug('getNativeRtpCapabilities()'); + const pc = new RTCPeerConnection({ + iceServers: [], + iceTransportPolicy: 'all', + bundlePolicy: 'max-bundle', + rtcpMuxPolicy: 'require', + sdpSemantics: 'plan-b' + }); + try { + const offer = await pc.createOffer({ + offerToReceiveAudio: true, + offerToReceiveVideo: true + }); + try { + pc.close(); + } + catch (error) { } + const sdpObject = sdpTransform.parse(offer.sdp); + const nativeRtpCapabilities = sdpCommonUtils.extractRtpCapabilities({ sdpObject }); + return nativeRtpCapabilities; + } + catch (error) { + try { + pc.close(); + } + catch (error2) { } + throw error; + } + } + async getNativeSctpCapabilities() { + logger.debug('getNativeSctpCapabilities()'); + return { + numStreams: SCTP_NUM_STREAMS + }; + } + run({ direction, iceParameters, iceCandidates, dtlsParameters, sctpParameters, iceServers, iceTransportPolicy, additionalSettings, proprietaryConstraints, extendedRtpCapabilities }) { + logger.debug('run()'); + this._direction = direction; + this._remoteSdp = new RemoteSdp_1.RemoteSdp({ + iceParameters, + iceCandidates, + dtlsParameters, + sctpParameters, + planB: true + }); + this._sendingRtpParametersByKind = + { + audio: ortc.getSendingRtpParameters('audio', extendedRtpCapabilities), + video: ortc.getSendingRtpParameters('video', extendedRtpCapabilities) + }; + this._sendingRemoteRtpParametersByKind = + { + audio: ortc.getSendingRemoteRtpParameters('audio', extendedRtpCapabilities), + video: ortc.getSendingRemoteRtpParameters('video', extendedRtpCapabilities) + }; + if (dtlsParameters.role && dtlsParameters.role !== 'auto') { + this._forcedLocalDtlsRole = dtlsParameters.role === 'server' + ? 'client' + : 'server'; + } + this._pc = new RTCPeerConnection({ + iceServers: iceServers || [], + iceTransportPolicy: iceTransportPolicy || 'all', + bundlePolicy: 'max-bundle', + rtcpMuxPolicy: 'require', + sdpSemantics: 'plan-b', + ...additionalSettings + }, proprietaryConstraints); + if (this._pc.connectionState) { + this._pc.addEventListener('connectionstatechange', () => { + this.emit('@connectionstatechange', this._pc.connectionState); + }); + } + else { + this._pc.addEventListener('iceconnectionstatechange', () => { + logger.warn('run() | pc.connectionState not supported, using pc.iceConnectionState'); + switch (this._pc.iceConnectionState) { + 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; + } + }); + } + } + async updateIceServers(iceServers) { + logger.debug('updateIceServers()'); + const configuration = this._pc.getConfiguration(); + configuration.iceServers = iceServers; + this._pc.setConfiguration(configuration); + } + async restartIce(iceParameters) { + logger.debug('restartIce()'); + // Provide the remote SDP handler with new remote ICE parameters. + this._remoteSdp.updateIceParameters(iceParameters); + if (!this._transportReady) { + return; + } + if (this._direction === 'send') { + const offer = await this._pc.createOffer({ iceRestart: true }); + logger.debug('restartIce() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('restartIce() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); + } + else { + const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; + logger.debug('restartIce() | calling pc.setRemoteDescription() [offer:%o]', offer); + await this._pc.setRemoteDescription(offer); + const answer = await this._pc.createAnswer(); + logger.debug('restartIce() | calling pc.setLocalDescription() [answer:%o]', answer); + await this._pc.setLocalDescription(answer); + } + } + async getTransportStats() { + return this._pc.getStats(); + } + async send({ track, encodings, codecOptions, codec }) { + var _a; + this.assertSendDirection(); + logger.debug('send() [kind:%s, track.id:%s]', track.kind, track.id); + if (codec) { + logger.warn('send() | codec selection is not available in %s handler', this.name); + } + this._sendStream.addTrack(track); + this._pc.addStream(this._sendStream); + let offer = await this._pc.createOffer(); + let localSdpObject = sdpTransform.parse(offer.sdp); + let offerMediaObject; + const sendingRtpParameters = utils.clone(this._sendingRtpParametersByKind[track.kind], {}); + sendingRtpParameters.codecs = + ortc.reduceCodecs(sendingRtpParameters.codecs); + const sendingRemoteRtpParameters = utils.clone(this._sendingRemoteRtpParametersByKind[track.kind], {}); + sendingRemoteRtpParameters.codecs = + ortc.reduceCodecs(sendingRemoteRtpParameters.codecs); + if (!this._transportReady) { + await this.setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); + } + if (track.kind === 'video' && encodings && encodings.length > 1) { + logger.debug('send() | enabling simulcast'); + localSdpObject = sdpTransform.parse(offer.sdp); + offerMediaObject = localSdpObject.media + .find((m) => m.type === 'video'); + sdpPlanBUtils.addLegacySimulcast({ + offerMediaObject, + track, + numStreams: encodings.length + }); + offer = { type: 'offer', sdp: sdpTransform.write(localSdpObject) }; + } + logger.debug('send() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + localSdpObject = sdpTransform.parse(this._pc.localDescription.sdp); + offerMediaObject = localSdpObject.media + .find((m) => m.type === track.kind); + // Set RTCP CNAME. + sendingRtpParameters.rtcp.cname = + sdpCommonUtils.getCname({ offerMediaObject }); + // Set RTP encodings. + sendingRtpParameters.encodings = + sdpPlanBUtils.getRtpEncodings({ offerMediaObject, track }); + // Complete encodings with given values. + if (encodings) { + for (let idx = 0; idx < sendingRtpParameters.encodings.length; ++idx) { + if (encodings[idx]) { + Object.assign(sendingRtpParameters.encodings[idx], encodings[idx]); + } + } + } + // If VP8 or H264 and there is effective simulcast, add scalabilityMode to + // each encoding. + if (sendingRtpParameters.encodings.length > 1 && + (sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/vp8' || + sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/h264')) { + for (const encoding of sendingRtpParameters.encodings) { + encoding.scalabilityMode = 'L1T3'; + } + } + this._remoteSdp.send({ + offerMediaObject, + offerRtpParameters: sendingRtpParameters, + answerRtpParameters: sendingRemoteRtpParameters, + codecOptions + }); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('send() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); + const localId = String(this._nextSendLocalId); + this._nextSendLocalId++; + // Insert into the map. + this._mapSendLocalIdTrack.set(localId, track); + return { + localId: localId, + rtpParameters: sendingRtpParameters + }; + } + async stopSending(localId) { + this.assertSendDirection(); + logger.debug('stopSending() [localId:%s]', localId); + const track = this._mapSendLocalIdTrack.get(localId); + if (!track) { + throw new Error('track not found'); + } + this._mapSendLocalIdTrack.delete(localId); + this._sendStream.removeTrack(track); + this._pc.addStream(this._sendStream); + const offer = await this._pc.createOffer(); + logger.debug('stopSending() | calling pc.setLocalDescription() [offer:%o]', offer); + try { + await this._pc.setLocalDescription(offer); + } + catch (error) { + // NOTE: If there are no sending tracks, setLocalDescription() will fail with + // "Failed to create channels". If so, ignore it. + if (this._sendStream.getTracks().length === 0) { + logger.warn('stopSending() | ignoring expected error due no sending tracks: %s', error.toString()); + return; + } + throw error; + } + if (this._pc.signalingState === 'stable') { + return; + } + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('stopSending() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); + } + // 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( + // eslint-disable-next-line @typescript-eslint/no-unused-vars + localId, track) { + throw new errors_1.UnsupportedError('not implemented'); + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async setMaxSpatialLayer(localId, spatialLayer) { + throw new errors_1.UnsupportedError('not implemented'); + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async setRtpEncodingParameters(localId, params) { + throw new errors_1.UnsupportedError('not implemented'); + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async getSenderStats(localId) { + throw new errors_1.UnsupportedError('not implemented'); + } + async sendDataChannel({ ordered, maxPacketLifeTime, maxRetransmits, label, protocol }) { + var _a; + this.assertSendDirection(); + const options = { + negotiated: true, + id: this._nextSendSctpStreamId, + ordered, + maxPacketLifeTime, + maxRetransmitTime: maxPacketLifeTime, + maxRetransmits, + protocol + }; + logger.debug('sendDataChannel() [options:%o]', options); + const dataChannel = this._pc.createDataChannel(label, options); + // Increase next id. + this._nextSendSctpStreamId = + ++this._nextSendSctpStreamId % SCTP_NUM_STREAMS.MIS; + // If this is the first DataChannel we need to create the SDP answer with + // m=application section. + if (!this._hasDataChannelMediaSection) { + const offer = await this._pc.createOffer(); + const localSdpObject = sdpTransform.parse(offer.sdp); + const offerMediaObject = localSdpObject.media + .find((m) => m.type === 'application'); + if (!this._transportReady) { + await this.setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); + } + logger.debug('sendDataChannel() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + this._remoteSdp.sendSctpAssociation({ offerMediaObject }); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('sendDataChannel() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); + this._hasDataChannelMediaSection = true; + } + const sctpStreamParameters = { + streamId: options.id, + ordered: options.ordered, + maxPacketLifeTime: options.maxPacketLifeTime, + maxRetransmits: options.maxRetransmits + }; + return { dataChannel, sctpStreamParameters }; + } + async receive(optionsList) { + var _a; + this.assertRecvDirection(); + const results = []; + const mapStreamId = new Map(); + for (const options of optionsList) { + const { trackId, kind, rtpParameters } = options; + logger.debug('receive() [trackId:%s, kind:%s]', trackId, kind); + const mid = kind; + let streamId = options.streamId || rtpParameters.rtcp.cname; + // NOTE: In React-Native we cannot reuse the same remote MediaStream for new + // remote tracks. This is because react-native-webrtc does not react on new + // tracks generated within already existing streams, so force the streamId + // to be different. See: + // https://github.com/react-native-webrtc/react-native-webrtc/issues/401 + logger.debug('receive() | forcing a random remote streamId to avoid well known bug in react-native-webrtc'); + streamId += `-hack-${utils.generateRandomNumber()}`; + mapStreamId.set(trackId, streamId); + this._remoteSdp.receive({ + mid, + kind, + offerRtpParameters: rtpParameters, + streamId, + trackId + }); + } + const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; + logger.debug('receive() | calling pc.setRemoteDescription() [offer:%o]', offer); + await this._pc.setRemoteDescription(offer); + let answer = await this._pc.createAnswer(); + const localSdpObject = sdpTransform.parse(answer.sdp); + for (const options of optionsList) { + const { kind, rtpParameters } = options; + const mid = kind; + const answerMediaObject = localSdpObject.media + .find((m) => String(m.mid) === mid); + // May need to modify codec parameters in the answer based on codec + // parameters in the offer. + sdpCommonUtils.applyCodecParameters({ + offerRtpParameters: rtpParameters, + answerMediaObject + }); + } + answer = { type: 'answer', sdp: sdpTransform.write(localSdpObject) }; + if (!this._transportReady) { + await this.setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); + } + logger.debug('receive() | calling pc.setLocalDescription() [answer:%o]', answer); + await this._pc.setLocalDescription(answer); + for (const options of optionsList) { + const { kind, trackId, rtpParameters } = options; + const localId = trackId; + const mid = kind; + const streamId = mapStreamId.get(trackId); + const stream = this._pc.getRemoteStreams() + .find((s) => s.id === streamId); + const track = stream.getTrackById(localId); + if (!track) { + throw new Error('remote track not found'); + } + // Insert into the map. + this._mapRecvLocalIdInfo.set(localId, { mid, rtpParameters }); + results.push({ localId, track }); + } + return results; + } + async stopReceiving(localIds) { + this.assertRecvDirection(); + for (const localId of localIds) { + logger.debug('stopReceiving() [localId:%s]', localId); + const { mid, rtpParameters } = this._mapRecvLocalIdInfo.get(localId) || {}; + // Remove from the map. + this._mapRecvLocalIdInfo.delete(localId); + this._remoteSdp.planBStopReceiving({ mid: mid, offerRtpParameters: rtpParameters }); + } + const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; + logger.debug('stopReceiving() | calling pc.setRemoteDescription() [offer:%o]', offer); + await this._pc.setRemoteDescription(offer); + const answer = await this._pc.createAnswer(); + logger.debug('stopReceiving() | calling pc.setLocalDescription() [answer:%o]', answer); + await this._pc.setLocalDescription(answer); + } + 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. + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async getReceiverStats(localId) { + throw new errors_1.UnsupportedError('not implemented'); + } + async receiveDataChannel({ sctpStreamParameters, label, protocol }) { + var _a; + this.assertRecvDirection(); + const { streamId, ordered, maxPacketLifeTime, maxRetransmits } = sctpStreamParameters; + const options = { + negotiated: true, + id: streamId, + ordered, + maxPacketLifeTime, + maxRetransmitTime: maxPacketLifeTime, + maxRetransmits, + protocol + }; + logger.debug('receiveDataChannel() [options:%o]', options); + const dataChannel = this._pc.createDataChannel(label, options); + // If this is the first DataChannel we need to create the SDP offer with + // m=application section. + if (!this._hasDataChannelMediaSection) { + this._remoteSdp.receiveSctpAssociation({ oldDataChannelSpec: true }); + const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; + logger.debug('receiveDataChannel() | calling pc.setRemoteDescription() [offer:%o]', offer); + await this._pc.setRemoteDescription(offer); + const answer = await this._pc.createAnswer(); + if (!this._transportReady) { + const localSdpObject = sdpTransform.parse(answer.sdp); + await this.setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); + } + logger.debug('receiveDataChannel() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setLocalDescription(answer); + this._hasDataChannelMediaSection = true; + } + return { dataChannel }; + } + async setupTransport({ localDtlsRole, localSdpObject }) { + if (!localSdpObject) { + localSdpObject = sdpTransform.parse(this._pc.localDescription.sdp); + } + // Get our local DTLS parameters. + const dtlsParameters = sdpCommonUtils.extractDtlsParameters({ sdpObject: localSdpObject }); + // Set our DTLS role. + dtlsParameters.role = localDtlsRole; + // Update the remote DTLS role in the SDP. + this._remoteSdp.updateDtlsRole(localDtlsRole === 'client' ? 'server' : 'client'); + // Need to tell the remote transport about our parameters. + await new Promise((resolve, reject) => { + this.safeEmit('@connect', { dtlsParameters }, resolve, reject); + }); + this._transportReady = true; + } + assertSendDirection() { + if (this._direction !== 'send') { + throw new Error('method can just be called for handlers with "send" direction'); + } + } + assertRecvDirection() { + if (this._direction !== 'recv') { + throw new Error('method can just be called for handlers with "recv" direction'); + } + } +} +exports.ReactNative = ReactNative; + +},{"../Logger":17,"../errors":22,"../ortc":43,"../utils":46,"./HandlerInterface":30,"./sdp/RemoteSdp":38,"./sdp/commonUtils":39,"./sdp/planBUtils":40,"sdp-transform":52}],32:[function(require,module,exports){ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.ReactNativeUnifiedPlan = void 0; +const sdpTransform = __importStar(require("sdp-transform")); +const Logger_1 = require("../Logger"); +const utils = __importStar(require("../utils")); +const ortc = __importStar(require("../ortc")); +const sdpCommonUtils = __importStar(require("./sdp/commonUtils")); +const sdpUnifiedPlanUtils = __importStar(require("./sdp/unifiedPlanUtils")); +const ortcUtils = __importStar(require("./ortc/utils")); +const HandlerInterface_1 = require("./HandlerInterface"); +const RemoteSdp_1 = require("./sdp/RemoteSdp"); +const scalabilityModes_1 = require("../scalabilityModes"); +const logger = new Logger_1.Logger('ReactNativeUnifiedPlan'); +const SCTP_NUM_STREAMS = { OS: 1024, MIS: 1024 }; +class ReactNativeUnifiedPlan extends HandlerInterface_1.HandlerInterface { + /** + * Creates a factory function. + */ + static createFactory() { + return () => new ReactNativeUnifiedPlan(); + } + constructor() { + super(); + // Map of RTCTransceivers indexed by MID. + this._mapMidTransceiver = new Map(); + // Local stream for sending. + this._sendStream = new MediaStream(); + // Whether a DataChannel m=application section has been created. + this._hasDataChannelMediaSection = false; + // Sending DataChannel id value counter. Incremented for each new DataChannel. + this._nextSendSctpStreamId = 0; + // Got transport local and remote parameters. + this._transportReady = false; + } + get name() { + return 'ReactNativeUnifiedPlan'; + } + close() { + logger.debug('close()'); + // Free/dispose native MediaStream but DO NOT free/dispose native + // MediaStreamTracks (that is parent's business). + // @ts-ignore (proprietary API in react-native-webrtc). + this._sendStream.release(/* releaseTracks */ false); + // Close RTCPeerConnection. + if (this._pc) { + try { + this._pc.close(); + } + catch (error) { } + } + this.emit('@close'); + } + async getNativeRtpCapabilities() { + logger.debug('getNativeRtpCapabilities()'); + const pc = new RTCPeerConnection({ + iceServers: [], + iceTransportPolicy: 'all', + bundlePolicy: 'max-bundle', + rtcpMuxPolicy: 'require', + sdpSemantics: 'unified-plan' + }); + try { + pc.addTransceiver('audio'); + pc.addTransceiver('video'); + const offer = await pc.createOffer(); + try { + pc.close(); + } + catch (error) { } + const sdpObject = sdpTransform.parse(offer.sdp); + const nativeRtpCapabilities = sdpCommonUtils.extractRtpCapabilities({ sdpObject }); + // libwebrtc supports NACK for OPUS but doesn't announce it. + ortcUtils.addNackSuppportForOpus(nativeRtpCapabilities); + return nativeRtpCapabilities; + } + catch (error) { + try { + pc.close(); + } + catch (error2) { } + throw error; + } + } + async getNativeSctpCapabilities() { + logger.debug('getNativeSctpCapabilities()'); + return { + numStreams: SCTP_NUM_STREAMS + }; + } + run({ direction, iceParameters, iceCandidates, dtlsParameters, sctpParameters, iceServers, iceTransportPolicy, additionalSettings, proprietaryConstraints, extendedRtpCapabilities }) { + logger.debug('run()'); + this._direction = direction; + this._remoteSdp = new RemoteSdp_1.RemoteSdp({ + iceParameters, + iceCandidates, + dtlsParameters, + sctpParameters + }); + this._sendingRtpParametersByKind = + { + audio: ortc.getSendingRtpParameters('audio', extendedRtpCapabilities), + video: ortc.getSendingRtpParameters('video', extendedRtpCapabilities) + }; + this._sendingRemoteRtpParametersByKind = + { + audio: ortc.getSendingRemoteRtpParameters('audio', extendedRtpCapabilities), + video: ortc.getSendingRemoteRtpParameters('video', extendedRtpCapabilities) + }; + if (dtlsParameters.role && dtlsParameters.role !== 'auto') { + this._forcedLocalDtlsRole = dtlsParameters.role === 'server' + ? 'client' + : 'server'; + } + this._pc = new RTCPeerConnection({ + iceServers: iceServers || [], + iceTransportPolicy: iceTransportPolicy || 'all', + bundlePolicy: 'max-bundle', + rtcpMuxPolicy: 'require', + sdpSemantics: 'unified-plan', + ...additionalSettings + }, proprietaryConstraints); + if (this._pc.connectionState) { + this._pc.addEventListener('connectionstatechange', () => { + this.emit('@connectionstatechange', this._pc.connectionState); + }); + } + else { + this._pc.addEventListener('iceconnectionstatechange', () => { + logger.warn('run() | pc.connectionState not supported, using pc.iceConnectionState'); + switch (this._pc.iceConnectionState) { + 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; + } + }); + } + } + async updateIceServers(iceServers) { + logger.debug('updateIceServers()'); + const configuration = this._pc.getConfiguration(); + configuration.iceServers = iceServers; + this._pc.setConfiguration(configuration); + } + async restartIce(iceParameters) { + logger.debug('restartIce()'); + // Provide the remote SDP handler with new remote ICE parameters. + this._remoteSdp.updateIceParameters(iceParameters); + if (!this._transportReady) { + return; + } + if (this._direction === 'send') { + const offer = await this._pc.createOffer({ iceRestart: true }); + logger.debug('restartIce() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('restartIce() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); + } + else { + const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; + logger.debug('restartIce() | calling pc.setRemoteDescription() [offer:%o]', offer); + await this._pc.setRemoteDescription(offer); + const answer = await this._pc.createAnswer(); + logger.debug('restartIce() | calling pc.setLocalDescription() [answer:%o]', answer); + await this._pc.setLocalDescription(answer); + } + } + async getTransportStats() { + return this._pc.getStats(); + } + async send({ track, encodings, codecOptions, codec }) { + var _a; + this.assertSendDirection(); + logger.debug('send() [kind:%s, track.id:%s]', track.kind, track.id); + if (encodings && encodings.length > 1) { + encodings.forEach((encoding, idx) => { + encoding.rid = `r${idx}`; + }); + } + const sendingRtpParameters = utils.clone(this._sendingRtpParametersByKind[track.kind], {}); + // This may throw. + sendingRtpParameters.codecs = + ortc.reduceCodecs(sendingRtpParameters.codecs, codec); + const sendingRemoteRtpParameters = utils.clone(this._sendingRemoteRtpParametersByKind[track.kind], {}); + // This may throw. + sendingRemoteRtpParameters.codecs = + ortc.reduceCodecs(sendingRemoteRtpParameters.codecs, codec); + const mediaSectionIdx = this._remoteSdp.getNextMediaSectionIdx(); + const transceiver = this._pc.addTransceiver(track, { + direction: 'sendonly', + streams: [this._sendStream], + sendEncodings: encodings + }); + let offer = await this._pc.createOffer(); + let localSdpObject = sdpTransform.parse(offer.sdp); + let offerMediaObject; + if (!this._transportReady) { + await this.setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); + } + // Special case for VP9 with SVC. + let hackVp9Svc = false; + const layers = (0, scalabilityModes_1.parse)((encodings || [{}])[0].scalabilityMode); + if (encodings && + encodings.length === 1 && + layers.spatialLayers > 1 && + sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/vp9') { + logger.debug('send() | enabling legacy simulcast for VP9 SVC'); + hackVp9Svc = true; + localSdpObject = sdpTransform.parse(offer.sdp); + offerMediaObject = localSdpObject.media[mediaSectionIdx.idx]; + sdpUnifiedPlanUtils.addLegacySimulcast({ + offerMediaObject, + numStreams: layers.spatialLayers + }); + offer = { type: 'offer', sdp: sdpTransform.write(localSdpObject) }; + } + logger.debug('send() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + // We can now get the transceiver.mid. + const localId = transceiver.mid; + // Set MID. + sendingRtpParameters.mid = localId; + localSdpObject = sdpTransform.parse(this._pc.localDescription.sdp); + offerMediaObject = localSdpObject.media[mediaSectionIdx.idx]; + // Set RTCP CNAME. + sendingRtpParameters.rtcp.cname = + sdpCommonUtils.getCname({ offerMediaObject }); + // Set RTP encodings by parsing the SDP offer if no encodings are given. + if (!encodings) { + sendingRtpParameters.encodings = + sdpUnifiedPlanUtils.getRtpEncodings({ offerMediaObject }); + } + // Set RTP encodings by parsing the SDP offer and complete them with given + // one if just a single encoding has been given. + else if (encodings.length === 1) { + let newEncodings = sdpUnifiedPlanUtils.getRtpEncodings({ offerMediaObject }); + Object.assign(newEncodings[0], encodings[0]); + // Hack for VP9 SVC. + if (hackVp9Svc) { + newEncodings = [newEncodings[0]]; + } + sendingRtpParameters.encodings = newEncodings; + } + // Otherwise if more than 1 encoding are given use them verbatim. + else { + sendingRtpParameters.encodings = encodings; + } + // If VP8 or H264 and there is effective simulcast, add scalabilityMode to + // each encoding. + if (sendingRtpParameters.encodings.length > 1 && + (sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/vp8' || + sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/h264')) { + for (const encoding of sendingRtpParameters.encodings) { + if (encoding.scalabilityMode) { + encoding.scalabilityMode = `L1T${layers.temporalLayers}`; + } + else { + encoding.scalabilityMode = 'L1T3'; + } + } + } + this._remoteSdp.send({ + offerMediaObject, + reuseMid: mediaSectionIdx.reuseMid, + offerRtpParameters: sendingRtpParameters, + answerRtpParameters: sendingRemoteRtpParameters, + codecOptions, + extmapAllowMixed: true + }); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('send() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); + // Store in the map. + this._mapMidTransceiver.set(localId, transceiver); + return { + localId, + rtpParameters: sendingRtpParameters, + rtpSender: transceiver.sender + }; + } + async stopSending(localId) { + this.assertSendDirection(); + logger.debug('stopSending() [localId:%s]', localId); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); + } + transceiver.sender.replaceTrack(null); + this._pc.removeTrack(transceiver.sender); + const mediaSectionClosed = this._remoteSdp.closeMediaSection(transceiver.mid); + if (mediaSectionClosed) { + try { + transceiver.stop(); + } + catch (error) { } + } + const offer = await this._pc.createOffer(); + logger.debug('stopSending() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('stopSending() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); + this._mapMidTransceiver.delete(localId); + } + async pauseSending(localId) { + this.assertSendDirection(); + logger.debug('pauseSending() [localId:%s]', localId); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); + } + transceiver.direction = 'inactive'; + this._remoteSdp.pauseMediaSection(localId); + const offer = await this._pc.createOffer(); + logger.debug('pauseSending() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('pauseSending() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); + } + async resumeSending(localId) { + this.assertSendDirection(); + logger.debug('resumeSending() [localId:%s]', localId); + const transceiver = this._mapMidTransceiver.get(localId); + this._remoteSdp.resumeSendingMediaSection(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); + } + transceiver.direction = 'sendonly'; + const offer = await this._pc.createOffer(); + logger.debug('resumeSending() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('resumeSending() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); + } + async replaceTrack(localId, track) { + this.assertSendDirection(); + if (track) { + logger.debug('replaceTrack() [localId:%s, track.id:%s]', localId, track.id); + } + else { + logger.debug('replaceTrack() [localId:%s, no track]', localId); + } + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); + } + await transceiver.sender.replaceTrack(track); + } + async setMaxSpatialLayer(localId, spatialLayer) { + this.assertSendDirection(); + logger.debug('setMaxSpatialLayer() [localId:%s, spatialLayer:%s]', localId, spatialLayer); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); + } + const parameters = transceiver.sender.getParameters(); + parameters.encodings.forEach((encoding, idx) => { + if (idx <= spatialLayer) { + encoding.active = true; + } + else { + encoding.active = false; + } + }); + await transceiver.sender.setParameters(parameters); + this._remoteSdp.muxMediaSectionSimulcast(localId, parameters.encodings); + const offer = await this._pc.createOffer(); + logger.debug('setMaxSpatialLayer() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('setMaxSpatialLayer() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); + } + async setRtpEncodingParameters(localId, params) { + this.assertSendDirection(); + logger.debug('setRtpEncodingParameters() [localId:%s, params:%o]', localId, params); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); + } + const parameters = transceiver.sender.getParameters(); + parameters.encodings.forEach((encoding, idx) => { + parameters.encodings[idx] = { ...encoding, ...params }; + }); + await transceiver.sender.setParameters(parameters); + this._remoteSdp.muxMediaSectionSimulcast(localId, parameters.encodings); + const offer = await this._pc.createOffer(); + logger.debug('setRtpEncodingParameters() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('setRtpEncodingParameters() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); + } + async getSenderStats(localId) { + this.assertSendDirection(); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); + } + return transceiver.sender.getStats(); + } + async sendDataChannel({ ordered, maxPacketLifeTime, maxRetransmits, label, protocol }) { + var _a; + this.assertSendDirection(); + const options = { + negotiated: true, + id: this._nextSendSctpStreamId, + ordered, + maxPacketLifeTime, + maxRetransmits, + protocol + }; + logger.debug('sendDataChannel() [options:%o]', options); + const dataChannel = this._pc.createDataChannel(label, options); + // Increase next id. + this._nextSendSctpStreamId = + ++this._nextSendSctpStreamId % SCTP_NUM_STREAMS.MIS; + // If this is the first DataChannel we need to create the SDP answer with + // m=application section. + if (!this._hasDataChannelMediaSection) { + const offer = await this._pc.createOffer(); + const localSdpObject = sdpTransform.parse(offer.sdp); + const offerMediaObject = localSdpObject.media + .find((m) => m.type === 'application'); + if (!this._transportReady) { + await this.setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); + } + logger.debug('sendDataChannel() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + this._remoteSdp.sendSctpAssociation({ offerMediaObject }); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('sendDataChannel() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); + this._hasDataChannelMediaSection = true; + } + const sctpStreamParameters = { + streamId: options.id, + ordered: options.ordered, + maxPacketLifeTime: options.maxPacketLifeTime, + maxRetransmits: options.maxRetransmits + }; + return { dataChannel, sctpStreamParameters }; + } + async receive(optionsList) { + var _a; + this.assertRecvDirection(); + const results = []; + const mapLocalId = new Map(); + for (const options of optionsList) { + const { trackId, kind, rtpParameters, streamId } = options; + logger.debug('receive() [trackId:%s, kind:%s]', trackId, kind); + const localId = rtpParameters.mid || String(this._mapMidTransceiver.size); + mapLocalId.set(trackId, localId); + this._remoteSdp.receive({ + mid: localId, + kind, + offerRtpParameters: rtpParameters, + streamId: streamId || rtpParameters.rtcp.cname, + trackId + }); + } + const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; + logger.debug('receive() | calling pc.setRemoteDescription() [offer:%o]', offer); + await this._pc.setRemoteDescription(offer); + let answer = await this._pc.createAnswer(); + const localSdpObject = sdpTransform.parse(answer.sdp); + for (const options of optionsList) { + const { trackId, rtpParameters } = options; + const localId = mapLocalId.get(trackId); + const answerMediaObject = localSdpObject.media + .find((m) => String(m.mid) === localId); + // May need to modify codec parameters in the answer based on codec + // parameters in the offer. + sdpCommonUtils.applyCodecParameters({ + offerRtpParameters: rtpParameters, + answerMediaObject + }); + } + answer = { type: 'answer', sdp: sdpTransform.write(localSdpObject) }; + if (!this._transportReady) { + await this.setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); + } + logger.debug('receive() | calling pc.setLocalDescription() [answer:%o]', answer); + await this._pc.setLocalDescription(answer); + for (const options of optionsList) { + const { trackId } = options; + const localId = mapLocalId.get(trackId); + const transceiver = this._pc.getTransceivers() + .find((t) => t.mid === localId); + if (!transceiver) { + throw new Error('new RTCRtpTransceiver not found'); + } + else { + // Store in the map. + this._mapMidTransceiver.set(localId, transceiver); + results.push({ + localId, + track: transceiver.receiver.track, + rtpReceiver: transceiver.receiver + }); + } + } + return results; + } + async stopReceiving(localIds) { + this.assertRecvDirection(); + for (const localId of localIds) { + logger.debug('stopReceiving() [localId:%s]', localId); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); + } + this._remoteSdp.closeMediaSection(transceiver.mid); + } + const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; + logger.debug('stopReceiving() | calling pc.setRemoteDescription() [offer:%o]', offer); + await this._pc.setRemoteDescription(offer); + const answer = await this._pc.createAnswer(); + logger.debug('stopReceiving() | calling pc.setLocalDescription() [answer:%o]', answer); + await this._pc.setLocalDescription(answer); + for (const localId of localIds) { + this._mapMidTransceiver.delete(localId); + } + } + async pauseReceiving(localIds) { + this.assertRecvDirection(); + for (const localId of localIds) { + logger.debug('pauseReceiving() [localId:%s]', localId); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); + } + transceiver.direction = 'inactive'; + this._remoteSdp.pauseMediaSection(localId); + } + const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; + logger.debug('pauseReceiving() | calling pc.setRemoteDescription() [offer:%o]', offer); + await this._pc.setRemoteDescription(offer); + const answer = await this._pc.createAnswer(); + logger.debug('pauseReceiving() | calling pc.setLocalDescription() [answer:%o]', answer); + await this._pc.setLocalDescription(answer); + } + async resumeReceiving(localIds) { + this.assertRecvDirection(); + for (const localId of localIds) { + logger.debug('resumeReceiving() [localId:%s]', localId); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); + } + transceiver.direction = 'recvonly'; + this._remoteSdp.resumeReceivingMediaSection(localId); + } + const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; + logger.debug('resumeReceiving() | calling pc.setRemoteDescription() [offer:%o]', offer); + await this._pc.setRemoteDescription(offer); + const answer = await this._pc.createAnswer(); + logger.debug('resumeReceiving() | calling pc.setLocalDescription() [answer:%o]', answer); + await this._pc.setLocalDescription(answer); + } + async getReceiverStats(localId) { + this.assertRecvDirection(); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); + } + return transceiver.receiver.getStats(); + } + async receiveDataChannel({ sctpStreamParameters, label, protocol }) { + var _a; + this.assertRecvDirection(); + const { streamId, ordered, maxPacketLifeTime, maxRetransmits } = sctpStreamParameters; + const options = { + negotiated: true, + id: streamId, + ordered, + maxPacketLifeTime, + maxRetransmits, + protocol + }; + logger.debug('receiveDataChannel() [options:%o]', options); + const dataChannel = this._pc.createDataChannel(label, options); + // If this is the first DataChannel we need to create the SDP offer with + // m=application section. + if (!this._hasDataChannelMediaSection) { + this._remoteSdp.receiveSctpAssociation(); + const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; + logger.debug('receiveDataChannel() | calling pc.setRemoteDescription() [offer:%o]', offer); + await this._pc.setRemoteDescription(offer); + const answer = await this._pc.createAnswer(); + if (!this._transportReady) { + const localSdpObject = sdpTransform.parse(answer.sdp); + await this.setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); + } + logger.debug('receiveDataChannel() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setLocalDescription(answer); + this._hasDataChannelMediaSection = true; + } + return { dataChannel }; + } + async setupTransport({ localDtlsRole, localSdpObject }) { + if (!localSdpObject) { + localSdpObject = sdpTransform.parse(this._pc.localDescription.sdp); + } + // Get our local DTLS parameters. + const dtlsParameters = sdpCommonUtils.extractDtlsParameters({ sdpObject: localSdpObject }); + // Set our DTLS role. + dtlsParameters.role = localDtlsRole; + // Update the remote DTLS role in the SDP. + this._remoteSdp.updateDtlsRole(localDtlsRole === 'client' ? 'server' : 'client'); + // Need to tell the remote transport about our parameters. + await new Promise((resolve, reject) => { + this.safeEmit('@connect', { dtlsParameters }, resolve, reject); + }); + this._transportReady = true; + } + assertSendDirection() { + if (this._direction !== 'send') { + throw new Error('method can just be called for handlers with "send" direction'); + } + } + assertRecvDirection() { + if (this._direction !== 'recv') { + throw new Error('method can just be called for handlers with "recv" direction'); + } + } +} +exports.ReactNativeUnifiedPlan = ReactNativeUnifiedPlan; + +},{"../Logger":17,"../ortc":43,"../scalabilityModes":44,"../utils":46,"./HandlerInterface":30,"./ortc/utils":36,"./sdp/RemoteSdp":38,"./sdp/commonUtils":39,"./sdp/unifiedPlanUtils":41,"sdp-transform":52}],33:[function(require,module,exports){ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Safari11 = void 0; +const sdpTransform = __importStar(require("sdp-transform")); +const Logger_1 = require("../Logger"); +const utils = __importStar(require("../utils")); +const ortc = __importStar(require("../ortc")); +const sdpCommonUtils = __importStar(require("./sdp/commonUtils")); +const sdpPlanBUtils = __importStar(require("./sdp/planBUtils")); +const HandlerInterface_1 = require("./HandlerInterface"); +const RemoteSdp_1 = require("./sdp/RemoteSdp"); +const logger = new Logger_1.Logger('Safari11'); +const SCTP_NUM_STREAMS = { OS: 1024, MIS: 1024 }; +class Safari11 extends HandlerInterface_1.HandlerInterface { + /** + * Creates a factory function. + */ + static createFactory() { + return () => new Safari11(); + } + constructor() { + super(); + // Local stream for sending. + this._sendStream = new MediaStream(); + // Map of RTCRtpSender indexed by localId. + this._mapSendLocalIdRtpSender = new Map(); + // Next sending localId. + this._nextSendLocalId = 0; + // Map of MID, RTP parameters and RTCRtpReceiver indexed by local id. + // Value is an Object with mid, rtpParameters and rtpReceiver. + this._mapRecvLocalIdInfo = new Map(); + // Whether a DataChannel m=application section has been created. + this._hasDataChannelMediaSection = false; + // Sending DataChannel id value counter. Incremented for each new DataChannel. + this._nextSendSctpStreamId = 0; + // Got transport local and remote parameters. + this._transportReady = false; + } + get name() { + return 'Safari11'; + } + close() { + logger.debug('close()'); + // Close RTCPeerConnection. + if (this._pc) { + try { + this._pc.close(); + } + catch (error) { } + } + this.emit('@close'); + } + async getNativeRtpCapabilities() { + logger.debug('getNativeRtpCapabilities()'); + const pc = new RTCPeerConnection({ + iceServers: [], + iceTransportPolicy: 'all', + bundlePolicy: 'max-bundle', + rtcpMuxPolicy: 'require', + sdpSemantics: 'plan-b' + }); + try { + const offer = await pc.createOffer({ + offerToReceiveAudio: true, + offerToReceiveVideo: true + }); + try { + pc.close(); + } + catch (error) { } + const sdpObject = sdpTransform.parse(offer.sdp); + const nativeRtpCapabilities = sdpCommonUtils.extractRtpCapabilities({ sdpObject }); + return nativeRtpCapabilities; + } + catch (error) { + try { + pc.close(); + } + catch (error2) { } + throw error; + } + } + async getNativeSctpCapabilities() { + logger.debug('getNativeSctpCapabilities()'); + return { + numStreams: SCTP_NUM_STREAMS + }; + } + run({ direction, iceParameters, iceCandidates, dtlsParameters, sctpParameters, iceServers, iceTransportPolicy, additionalSettings, proprietaryConstraints, extendedRtpCapabilities }) { + logger.debug('run()'); + this._direction = direction; + this._remoteSdp = new RemoteSdp_1.RemoteSdp({ + iceParameters, + iceCandidates, + dtlsParameters, + sctpParameters, + planB: true + }); + this._sendingRtpParametersByKind = + { + audio: ortc.getSendingRtpParameters('audio', extendedRtpCapabilities), + video: ortc.getSendingRtpParameters('video', extendedRtpCapabilities) + }; + this._sendingRemoteRtpParametersByKind = + { + audio: ortc.getSendingRemoteRtpParameters('audio', extendedRtpCapabilities), + video: ortc.getSendingRemoteRtpParameters('video', extendedRtpCapabilities) + }; + if (dtlsParameters.role && dtlsParameters.role !== 'auto') { + this._forcedLocalDtlsRole = dtlsParameters.role === 'server' + ? 'client' + : 'server'; + } + this._pc = new RTCPeerConnection({ + iceServers: iceServers || [], + iceTransportPolicy: iceTransportPolicy || 'all', + bundlePolicy: 'max-bundle', + rtcpMuxPolicy: 'require', + ...additionalSettings + }, proprietaryConstraints); + if (this._pc.connectionState) { + this._pc.addEventListener('connectionstatechange', () => { + this.emit('@connectionstatechange', this._pc.connectionState); + }); + } + else { + this._pc.addEventListener('iceconnectionstatechange', () => { + logger.warn('run() | pc.connectionState not supported, using pc.iceConnectionState'); + switch (this._pc.iceConnectionState) { + 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; + } + }); + } + } + async updateIceServers(iceServers) { + logger.debug('updateIceServers()'); + const configuration = this._pc.getConfiguration(); + configuration.iceServers = iceServers; + this._pc.setConfiguration(configuration); + } + async restartIce(iceParameters) { + logger.debug('restartIce()'); + // Provide the remote SDP handler with new remote ICE parameters. + this._remoteSdp.updateIceParameters(iceParameters); + if (!this._transportReady) { + return; + } + if (this._direction === 'send') { + const offer = await this._pc.createOffer({ iceRestart: true }); + logger.debug('restartIce() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('restartIce() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); + } + else { + const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; + logger.debug('restartIce() | calling pc.setRemoteDescription() [offer:%o]', offer); + await this._pc.setRemoteDescription(offer); + const answer = await this._pc.createAnswer(); + logger.debug('restartIce() | calling pc.setLocalDescription() [answer:%o]', answer); + await this._pc.setLocalDescription(answer); + } + } + async getTransportStats() { + return this._pc.getStats(); + } + async send({ track, encodings, codecOptions, codec }) { + var _a; + this.assertSendDirection(); + logger.debug('send() [kind:%s, track.id:%s]', track.kind, track.id); + if (codec) { + logger.warn('send() | codec selection is not available in %s handler', this.name); + } + this._sendStream.addTrack(track); + this._pc.addTrack(track, this._sendStream); + let offer = await this._pc.createOffer(); + let localSdpObject = sdpTransform.parse(offer.sdp); + let offerMediaObject; + const sendingRtpParameters = utils.clone(this._sendingRtpParametersByKind[track.kind], {}); + sendingRtpParameters.codecs = + ortc.reduceCodecs(sendingRtpParameters.codecs); + const sendingRemoteRtpParameters = utils.clone(this._sendingRemoteRtpParametersByKind[track.kind], {}); + sendingRemoteRtpParameters.codecs = + ortc.reduceCodecs(sendingRemoteRtpParameters.codecs); + if (!this._transportReady) { + await this.setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); + } + if (track.kind === 'video' && encodings && encodings.length > 1) { + logger.debug('send() | enabling simulcast'); + localSdpObject = sdpTransform.parse(offer.sdp); + offerMediaObject = localSdpObject.media.find((m) => m.type === 'video'); + sdpPlanBUtils.addLegacySimulcast({ + offerMediaObject, + track, + numStreams: encodings.length + }); + offer = { type: 'offer', sdp: sdpTransform.write(localSdpObject) }; + } + logger.debug('send() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + localSdpObject = sdpTransform.parse(this._pc.localDescription.sdp); + offerMediaObject = localSdpObject.media + .find((m) => m.type === track.kind); + // Set RTCP CNAME. + sendingRtpParameters.rtcp.cname = + sdpCommonUtils.getCname({ offerMediaObject }); + // Set RTP encodings. + sendingRtpParameters.encodings = + sdpPlanBUtils.getRtpEncodings({ offerMediaObject, track }); + // Complete encodings with given values. + if (encodings) { + for (let idx = 0; idx < sendingRtpParameters.encodings.length; ++idx) { + if (encodings[idx]) { + Object.assign(sendingRtpParameters.encodings[idx], encodings[idx]); + } + } + } + // If VP8 and there is effective simulcast, add scalabilityMode to each + // encoding. + if (sendingRtpParameters.encodings.length > 1 && + sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/vp8') { + for (const encoding of sendingRtpParameters.encodings) { + encoding.scalabilityMode = 'L1T3'; + } + } + this._remoteSdp.send({ + offerMediaObject, + offerRtpParameters: sendingRtpParameters, + answerRtpParameters: sendingRemoteRtpParameters, + codecOptions + }); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('send() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); + const localId = String(this._nextSendLocalId); + this._nextSendLocalId++; + const rtpSender = this._pc.getSenders() + .find((s) => s.track === track); + // Insert into the map. + this._mapSendLocalIdRtpSender.set(localId, rtpSender); + return { + localId: localId, + rtpParameters: sendingRtpParameters, + rtpSender + }; + } + async stopSending(localId) { + this.assertSendDirection(); + const rtpSender = this._mapSendLocalIdRtpSender.get(localId); + if (!rtpSender) { + throw new Error('associated RTCRtpSender not found'); + } + if (rtpSender.track) { + this._sendStream.removeTrack(rtpSender.track); + } + this._mapSendLocalIdRtpSender.delete(localId); + const offer = await this._pc.createOffer(); + logger.debug('stopSending() | calling pc.setLocalDescription() [offer:%o]', offer); + try { + await this._pc.setLocalDescription(offer); + } + catch (error) { + // NOTE: If there are no sending tracks, setLocalDescription() will fail with + // "Failed to create channels". If so, ignore it. + if (this._sendStream.getTracks().length === 0) { + logger.warn('stopSending() | ignoring expected error due no sending tracks: %s', error.toString()); + return; + } + throw error; + } + if (this._pc.signalingState === 'stable') { + return; + } + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('stopSending() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); + } + // 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) { + this.assertSendDirection(); + 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._mapSendLocalIdRtpSender.get(localId); + if (!rtpSender) { + throw new Error('associated RTCRtpSender not found'); + } + const oldTrack = rtpSender.track; + await rtpSender.replaceTrack(track); + // Remove the old track from the local stream. + if (oldTrack) { + this._sendStream.removeTrack(oldTrack); + } + // Add the new track to the local stream. + if (track) { + this._sendStream.addTrack(track); + } + } + async setMaxSpatialLayer(localId, spatialLayer) { + this.assertSendDirection(); + logger.debug('setMaxSpatialLayer() [localId:%s, spatialLayer:%s]', localId, spatialLayer); + const rtpSender = this._mapSendLocalIdRtpSender.get(localId); + if (!rtpSender) { + throw new Error('associated 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) { + this.assertSendDirection(); + logger.debug('setRtpEncodingParameters() [localId:%s, params:%o]', localId, params); + const rtpSender = this._mapSendLocalIdRtpSender.get(localId); + if (!rtpSender) { + throw new Error('associated RTCRtpSender not found'); + } + const parameters = rtpSender.getParameters(); + parameters.encodings.forEach((encoding, idx) => { + parameters.encodings[idx] = { ...encoding, ...params }; + }); + await rtpSender.setParameters(parameters); + } + async getSenderStats(localId) { + this.assertSendDirection(); + const rtpSender = this._mapSendLocalIdRtpSender.get(localId); + if (!rtpSender) { + throw new Error('associated RTCRtpSender not found'); + } + return rtpSender.getStats(); + } + async sendDataChannel({ ordered, maxPacketLifeTime, maxRetransmits, label, protocol }) { + var _a; + this.assertSendDirection(); + const options = { + negotiated: true, + id: this._nextSendSctpStreamId, + ordered, + maxPacketLifeTime, + maxRetransmits, + protocol + }; + logger.debug('sendDataChannel() [options:%o]', options); + const dataChannel = this._pc.createDataChannel(label, options); + // Increase next id. + this._nextSendSctpStreamId = + ++this._nextSendSctpStreamId % SCTP_NUM_STREAMS.MIS; + // If this is the first DataChannel we need to create the SDP answer with + // m=application section. + if (!this._hasDataChannelMediaSection) { + const offer = await this._pc.createOffer(); + const localSdpObject = sdpTransform.parse(offer.sdp); + const offerMediaObject = localSdpObject.media + .find((m) => m.type === 'application'); + if (!this._transportReady) { + await this.setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); + } + logger.debug('sendDataChannel() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + this._remoteSdp.sendSctpAssociation({ offerMediaObject }); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('sendDataChannel() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); + this._hasDataChannelMediaSection = true; + } + const sctpStreamParameters = { + streamId: options.id, + ordered: options.ordered, + maxPacketLifeTime: options.maxPacketLifeTime, + maxRetransmits: options.maxRetransmits + }; + return { dataChannel, sctpStreamParameters }; + } + async receive(optionsList) { + var _a; + this.assertRecvDirection(); + const results = []; + for (const options of optionsList) { + const { trackId, kind, rtpParameters, streamId } = options; + logger.debug('receive() [trackId:%s, kind:%s]', trackId, kind); + const mid = kind; + this._remoteSdp.receive({ + mid, + kind, + offerRtpParameters: rtpParameters, + streamId: streamId || rtpParameters.rtcp.cname, + trackId + }); + } + const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; + logger.debug('receive() | calling pc.setRemoteDescription() [offer:%o]', offer); + await this._pc.setRemoteDescription(offer); + let answer = await this._pc.createAnswer(); + const localSdpObject = sdpTransform.parse(answer.sdp); + for (const options of optionsList) { + const { kind, rtpParameters } = options; + const mid = kind; + const answerMediaObject = localSdpObject.media + .find((m) => String(m.mid) === mid); + // May need to modify codec parameters in the answer based on codec + // parameters in the offer. + sdpCommonUtils.applyCodecParameters({ + offerRtpParameters: rtpParameters, + answerMediaObject + }); + } + answer = { type: 'answer', sdp: sdpTransform.write(localSdpObject) }; + if (!this._transportReady) { + await this.setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); + } + logger.debug('receive() | calling pc.setLocalDescription() [answer:%o]', answer); + await this._pc.setLocalDescription(answer); + for (const options of optionsList) { + const { kind, trackId, rtpParameters } = options; + const mid = kind; + const localId = trackId; + const rtpReceiver = this._pc.getReceivers() + .find((r) => r.track && r.track.id === localId); + if (!rtpReceiver) { + throw new Error('new RTCRtpReceiver not'); + } + // Insert into the map. + this._mapRecvLocalIdInfo.set(localId, { mid, rtpParameters, rtpReceiver }); + results.push({ + localId, + track: rtpReceiver.track, + rtpReceiver + }); + } + return results; + } + async stopReceiving(localIds) { + this.assertRecvDirection(); + for (const localId of localIds) { + logger.debug('stopReceiving() [localId:%s]', localId); + const { mid, rtpParameters } = this._mapRecvLocalIdInfo.get(localId) || {}; + // Remove from the map. + this._mapRecvLocalIdInfo.delete(localId); + this._remoteSdp.planBStopReceiving({ mid: mid, offerRtpParameters: rtpParameters }); + } + const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; + logger.debug('stopReceiving() | calling pc.setRemoteDescription() [offer:%o]', offer); + await this._pc.setRemoteDescription(offer); + const answer = await this._pc.createAnswer(); + logger.debug('stopReceiving() | calling pc.setLocalDescription() [answer:%o]', answer); + await this._pc.setLocalDescription(answer); + } + async getReceiverStats(localId) { + this.assertRecvDirection(); + const { rtpReceiver } = this._mapRecvLocalIdInfo.get(localId) || {}; + if (!rtpReceiver) { + throw new Error('associated RTCRtpReceiver not found'); + } + return rtpReceiver.getStats(); + } + 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 receiveDataChannel({ sctpStreamParameters, label, protocol }) { + var _a; + this.assertRecvDirection(); + const { streamId, ordered, maxPacketLifeTime, maxRetransmits } = sctpStreamParameters; + const options = { + negotiated: true, + id: streamId, + ordered, + maxPacketLifeTime, + maxRetransmits, + protocol + }; + logger.debug('receiveDataChannel() [options:%o]', options); + const dataChannel = this._pc.createDataChannel(label, options); + // If this is the first DataChannel we need to create the SDP offer with + // m=application section. + if (!this._hasDataChannelMediaSection) { + this._remoteSdp.receiveSctpAssociation({ oldDataChannelSpec: true }); + const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; + logger.debug('receiveDataChannel() | calling pc.setRemoteDescription() [offer:%o]', offer); + await this._pc.setRemoteDescription(offer); + const answer = await this._pc.createAnswer(); + if (!this._transportReady) { + const localSdpObject = sdpTransform.parse(answer.sdp); + await this.setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); + } + logger.debug('receiveDataChannel() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setLocalDescription(answer); + this._hasDataChannelMediaSection = true; + } + return { dataChannel }; + } + async setupTransport({ localDtlsRole, localSdpObject }) { + if (!localSdpObject) { + localSdpObject = sdpTransform.parse(this._pc.localDescription.sdp); + } + // Get our local DTLS parameters. + const dtlsParameters = sdpCommonUtils.extractDtlsParameters({ sdpObject: localSdpObject }); + // Set our DTLS role. + dtlsParameters.role = localDtlsRole; + // Update the remote DTLS role in the SDP. + this._remoteSdp.updateDtlsRole(localDtlsRole === 'client' ? 'server' : 'client'); + // Need to tell the remote transport about our parameters. + await new Promise((resolve, reject) => { + this.safeEmit('@connect', { dtlsParameters }, resolve, reject); + }); + this._transportReady = true; + } + assertSendDirection() { + if (this._direction !== 'send') { + throw new Error('method can just be called for handlers with "send" direction'); + } + } + assertRecvDirection() { + if (this._direction !== 'recv') { + throw new Error('method can just be called for handlers with "recv" direction'); + } + } +} +exports.Safari11 = Safari11; + +},{"../Logger":17,"../ortc":43,"../utils":46,"./HandlerInterface":30,"./sdp/RemoteSdp":38,"./sdp/commonUtils":39,"./sdp/planBUtils":40,"sdp-transform":52}],34:[function(require,module,exports){ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.Safari12 = void 0; +const sdpTransform = __importStar(require("sdp-transform")); +const Logger_1 = require("../Logger"); +const utils = __importStar(require("../utils")); +const ortc = __importStar(require("../ortc")); +const sdpCommonUtils = __importStar(require("./sdp/commonUtils")); +const sdpUnifiedPlanUtils = __importStar(require("./sdp/unifiedPlanUtils")); +const ortcUtils = __importStar(require("./ortc/utils")); +const HandlerInterface_1 = require("./HandlerInterface"); +const RemoteSdp_1 = require("./sdp/RemoteSdp"); +const scalabilityModes_1 = require("../scalabilityModes"); +const logger = new Logger_1.Logger('Safari12'); +const SCTP_NUM_STREAMS = { OS: 1024, MIS: 1024 }; +class Safari12 extends HandlerInterface_1.HandlerInterface { + /** + * Creates a factory function. + */ + static createFactory() { + return () => new Safari12(); + } + constructor() { + super(); + // Map of RTCTransceivers indexed by MID. + this._mapMidTransceiver = new Map(); + // Local stream for sending. + this._sendStream = new MediaStream(); + // Whether a DataChannel m=application section has been created. + this._hasDataChannelMediaSection = false; + // Sending DataChannel id value counter. Incremented for each new DataChannel. + this._nextSendSctpStreamId = 0; + // Got transport local and remote parameters. + this._transportReady = false; + } + get name() { + return 'Safari12'; + } + close() { + logger.debug('close()'); + // Close RTCPeerConnection. + if (this._pc) { + try { + this._pc.close(); + } + catch (error) { } + } + this.emit('@close'); + } + async getNativeRtpCapabilities() { + logger.debug('getNativeRtpCapabilities()'); + const pc = new RTCPeerConnection({ + iceServers: [], + iceTransportPolicy: 'all', + bundlePolicy: 'max-bundle', + rtcpMuxPolicy: 'require' + }); + try { + pc.addTransceiver('audio'); + pc.addTransceiver('video'); + const offer = await pc.createOffer(); + try { + pc.close(); + } + catch (error) { } + const sdpObject = sdpTransform.parse(offer.sdp); + const nativeRtpCapabilities = sdpCommonUtils.extractRtpCapabilities({ sdpObject }); + // libwebrtc supports NACK for OPUS but doesn't announce it. + ortcUtils.addNackSuppportForOpus(nativeRtpCapabilities); + return nativeRtpCapabilities; + } + catch (error) { + try { + pc.close(); + } + catch (error2) { } + throw error; + } + } + async getNativeSctpCapabilities() { + logger.debug('getNativeSctpCapabilities()'); + return { + numStreams: SCTP_NUM_STREAMS + }; + } + run({ direction, iceParameters, iceCandidates, dtlsParameters, sctpParameters, iceServers, iceTransportPolicy, additionalSettings, proprietaryConstraints, extendedRtpCapabilities }) { + logger.debug('run()'); + this._direction = direction; + this._remoteSdp = new RemoteSdp_1.RemoteSdp({ + iceParameters, + iceCandidates, + dtlsParameters, + sctpParameters + }); + this._sendingRtpParametersByKind = + { + audio: ortc.getSendingRtpParameters('audio', extendedRtpCapabilities), + video: ortc.getSendingRtpParameters('video', extendedRtpCapabilities) + }; + this._sendingRemoteRtpParametersByKind = + { + audio: ortc.getSendingRemoteRtpParameters('audio', extendedRtpCapabilities), + video: ortc.getSendingRemoteRtpParameters('video', extendedRtpCapabilities) + }; + if (dtlsParameters.role && dtlsParameters.role !== 'auto') { + this._forcedLocalDtlsRole = dtlsParameters.role === 'server' + ? 'client' + : 'server'; + } + this._pc = new RTCPeerConnection({ + iceServers: iceServers || [], + iceTransportPolicy: iceTransportPolicy || 'all', + bundlePolicy: 'max-bundle', + rtcpMuxPolicy: 'require', + ...additionalSettings + }, proprietaryConstraints); + if (this._pc.connectionState) { + this._pc.addEventListener('connectionstatechange', () => { + this.emit('@connectionstatechange', this._pc.connectionState); + }); + } + else { + this._pc.addEventListener('iceconnectionstatechange', () => { + logger.warn('run() | pc.connectionState not supported, using pc.iceConnectionState'); + switch (this._pc.iceConnectionState) { + 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; + } + }); + } + } + async updateIceServers(iceServers) { + logger.debug('updateIceServers()'); + const configuration = this._pc.getConfiguration(); + configuration.iceServers = iceServers; + this._pc.setConfiguration(configuration); + } + async restartIce(iceParameters) { + logger.debug('restartIce()'); + // Provide the remote SDP handler with new remote ICE parameters. + this._remoteSdp.updateIceParameters(iceParameters); + if (!this._transportReady) { + return; + } + if (this._direction === 'send') { + const offer = await this._pc.createOffer({ iceRestart: true }); + logger.debug('restartIce() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('restartIce() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); + } + else { + const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; + logger.debug('restartIce() | calling pc.setRemoteDescription() [offer:%o]', offer); + await this._pc.setRemoteDescription(offer); + const answer = await this._pc.createAnswer(); + logger.debug('restartIce() | calling pc.setLocalDescription() [answer:%o]', answer); + await this._pc.setLocalDescription(answer); + } + } + async getTransportStats() { + return this._pc.getStats(); + } + async send({ track, encodings, codecOptions, codec }) { + var _a; + this.assertSendDirection(); + logger.debug('send() [kind:%s, track.id:%s]', track.kind, track.id); + const sendingRtpParameters = utils.clone(this._sendingRtpParametersByKind[track.kind], {}); + // This may throw. + sendingRtpParameters.codecs = + ortc.reduceCodecs(sendingRtpParameters.codecs, codec); + const sendingRemoteRtpParameters = utils.clone(this._sendingRemoteRtpParametersByKind[track.kind], {}); + // This may throw. + sendingRemoteRtpParameters.codecs = + ortc.reduceCodecs(sendingRemoteRtpParameters.codecs, codec); + const mediaSectionIdx = this._remoteSdp.getNextMediaSectionIdx(); + const transceiver = this._pc.addTransceiver(track, { direction: 'sendonly', streams: [this._sendStream] }); + let offer = await this._pc.createOffer(); + let localSdpObject = sdpTransform.parse(offer.sdp); + let offerMediaObject; + if (!this._transportReady) { + await this.setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); + } + const layers = (0, scalabilityModes_1.parse)((encodings || [{}])[0].scalabilityMode); + if (encodings && encodings.length > 1) { + logger.debug('send() | enabling legacy simulcast'); + localSdpObject = sdpTransform.parse(offer.sdp); + offerMediaObject = localSdpObject.media[mediaSectionIdx.idx]; + sdpUnifiedPlanUtils.addLegacySimulcast({ + offerMediaObject, + numStreams: encodings.length + }); + offer = { type: 'offer', sdp: sdpTransform.write(localSdpObject) }; + } + logger.debug('send() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + // We can now get the transceiver.mid. + const localId = transceiver.mid; + // Set MID. + sendingRtpParameters.mid = localId; + localSdpObject = sdpTransform.parse(this._pc.localDescription.sdp); + offerMediaObject = localSdpObject.media[mediaSectionIdx.idx]; + // Set RTCP CNAME. + sendingRtpParameters.rtcp.cname = + sdpCommonUtils.getCname({ offerMediaObject }); + // Set RTP encodings. + sendingRtpParameters.encodings = + sdpUnifiedPlanUtils.getRtpEncodings({ offerMediaObject }); + // Complete encodings with given values. + if (encodings) { + for (let idx = 0; idx < sendingRtpParameters.encodings.length; ++idx) { + if (encodings[idx]) { + Object.assign(sendingRtpParameters.encodings[idx], encodings[idx]); + } + } + } + // If VP8 or H264 and there is effective simulcast, add scalabilityMode to + // each encoding. + if (sendingRtpParameters.encodings.length > 1 && + (sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/vp8' || + sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/h264')) { + for (const encoding of sendingRtpParameters.encodings) { + if (encoding.scalabilityMode) { + encoding.scalabilityMode = `L1T${layers.temporalLayers}`; + } + else { + encoding.scalabilityMode = 'L1T3'; + } + } + } + this._remoteSdp.send({ + offerMediaObject, + reuseMid: mediaSectionIdx.reuseMid, + offerRtpParameters: sendingRtpParameters, + answerRtpParameters: sendingRemoteRtpParameters, + codecOptions + }); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('send() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); + // Store in the map. + this._mapMidTransceiver.set(localId, transceiver); + return { + localId, + rtpParameters: sendingRtpParameters, + rtpSender: transceiver.sender + }; + } + async stopSending(localId) { + this.assertSendDirection(); + logger.debug('stopSending() [localId:%s]', localId); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); + } + transceiver.sender.replaceTrack(null); + this._pc.removeTrack(transceiver.sender); + const mediaSectionClosed = this._remoteSdp.closeMediaSection(transceiver.mid); + if (mediaSectionClosed) { + try { + transceiver.stop(); + } + catch (error) { } + } + const offer = await this._pc.createOffer(); + logger.debug('stopSending() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('stopSending() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); + this._mapMidTransceiver.delete(localId); + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async pauseSending(localId) { + this.assertSendDirection(); + logger.debug('pauseSending() [localId:%s]', localId); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); + } + transceiver.direction = 'inactive'; + this._remoteSdp.pauseMediaSection(localId); + const offer = await this._pc.createOffer(); + logger.debug('pauseSending() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('pauseSending() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + async resumeSending(localId) { + this.assertSendDirection(); + logger.debug('resumeSending() [localId:%s]', localId); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); + } + transceiver.direction = 'sendonly'; + this._remoteSdp.resumeSendingMediaSection(localId); + const offer = await this._pc.createOffer(); + logger.debug('resumeSending() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('resumeSending() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); + } + async replaceTrack(localId, track) { + this.assertSendDirection(); + if (track) { + logger.debug('replaceTrack() [localId:%s, track.id:%s]', localId, track.id); + } + else { + logger.debug('replaceTrack() [localId:%s, no track]', localId); + } + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); + } + await transceiver.sender.replaceTrack(track); + } + async setMaxSpatialLayer(localId, spatialLayer) { + this.assertSendDirection(); + logger.debug('setMaxSpatialLayer() [localId:%s, spatialLayer:%s]', localId, spatialLayer); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); + } + const parameters = transceiver.sender.getParameters(); + parameters.encodings.forEach((encoding, idx) => { + if (idx <= spatialLayer) { + encoding.active = true; + } + else { + encoding.active = false; + } + }); + await transceiver.sender.setParameters(parameters); + this._remoteSdp.muxMediaSectionSimulcast(localId, parameters.encodings); + const offer = await this._pc.createOffer(); + logger.debug('setMaxSpatialLayer() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('setMaxSpatialLayer() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); + } + async setRtpEncodingParameters(localId, params) { + this.assertSendDirection(); + logger.debug('setRtpEncodingParameters() [localId:%s, params:%o]', localId, params); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); + } + const parameters = transceiver.sender.getParameters(); + parameters.encodings.forEach((encoding, idx) => { + parameters.encodings[idx] = { ...encoding, ...params }; + }); + await transceiver.sender.setParameters(parameters); + this._remoteSdp.muxMediaSectionSimulcast(localId, parameters.encodings); + const offer = await this._pc.createOffer(); + logger.debug('setRtpEncodingParameters() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('setRtpEncodingParameters() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); + } + async getSenderStats(localId) { + this.assertSendDirection(); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); + } + return transceiver.sender.getStats(); + } + async sendDataChannel({ ordered, maxPacketLifeTime, maxRetransmits, label, protocol }) { + var _a; + this.assertSendDirection(); + const options = { + negotiated: true, + id: this._nextSendSctpStreamId, + ordered, + maxPacketLifeTime, + maxRetransmits, + protocol + }; + logger.debug('sendDataChannel() [options:%o]', options); + const dataChannel = this._pc.createDataChannel(label, options); + // Increase next id. + this._nextSendSctpStreamId = + ++this._nextSendSctpStreamId % SCTP_NUM_STREAMS.MIS; + // If this is the first DataChannel we need to create the SDP answer with + // m=application section. + if (!this._hasDataChannelMediaSection) { + const offer = await this._pc.createOffer(); + const localSdpObject = sdpTransform.parse(offer.sdp); + const offerMediaObject = localSdpObject.media + .find((m) => m.type === 'application'); + if (!this._transportReady) { + await this.setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); + } + logger.debug('sendDataChannel() | calling pc.setLocalDescription() [offer:%o]', offer); + await this._pc.setLocalDescription(offer); + this._remoteSdp.sendSctpAssociation({ offerMediaObject }); + const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() }; + logger.debug('sendDataChannel() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setRemoteDescription(answer); + this._hasDataChannelMediaSection = true; + } + const sctpStreamParameters = { + streamId: options.id, + ordered: options.ordered, + maxPacketLifeTime: options.maxPacketLifeTime, + maxRetransmits: options.maxRetransmits + }; + return { dataChannel, sctpStreamParameters }; + } + async receive(optionsList) { + var _a; + this.assertRecvDirection(); + const results = []; + const mapLocalId = new Map(); + for (const options of optionsList) { + const { trackId, kind, rtpParameters, streamId } = options; + logger.debug('receive() [trackId:%s, kind:%s]', trackId, kind); + const localId = rtpParameters.mid || String(this._mapMidTransceiver.size); + mapLocalId.set(trackId, localId); + this._remoteSdp.receive({ + mid: localId, + kind, + offerRtpParameters: rtpParameters, + streamId: streamId || rtpParameters.rtcp.cname, + trackId + }); + } + const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; + logger.debug('receive() | calling pc.setRemoteDescription() [offer:%o]', offer); + await this._pc.setRemoteDescription(offer); + let answer = await this._pc.createAnswer(); + const localSdpObject = sdpTransform.parse(answer.sdp); + for (const options of optionsList) { + const { trackId, rtpParameters } = options; + const localId = mapLocalId.get(trackId); + const answerMediaObject = localSdpObject.media + .find((m) => String(m.mid) === localId); + // May need to modify codec parameters in the answer based on codec + // parameters in the offer. + sdpCommonUtils.applyCodecParameters({ + offerRtpParameters: rtpParameters, + answerMediaObject + }); + } + answer = { type: 'answer', sdp: sdpTransform.write(localSdpObject) }; + if (!this._transportReady) { + await this.setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); + } + logger.debug('receive() | calling pc.setLocalDescription() [answer:%o]', answer); + await this._pc.setLocalDescription(answer); + for (const options of optionsList) { + const { trackId } = options; + const localId = mapLocalId.get(trackId); + const transceiver = this._pc.getTransceivers() + .find((t) => t.mid === localId); + if (!transceiver) { + throw new Error('new RTCRtpTransceiver not found'); + } + // Store in the map. + this._mapMidTransceiver.set(localId, transceiver); + results.push({ + localId, + track: transceiver.receiver.track, + rtpReceiver: transceiver.receiver + }); + } + return results; + } + async stopReceiving(localIds) { + this.assertRecvDirection(); + for (const localId of localIds) { + logger.debug('stopReceiving() [localId:%s]', localId); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); + } + this._remoteSdp.closeMediaSection(transceiver.mid); + } + const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; + logger.debug('stopReceiving() | calling pc.setRemoteDescription() [offer:%o]', offer); + await this._pc.setRemoteDescription(offer); + const answer = await this._pc.createAnswer(); + logger.debug('stopReceiving() | calling pc.setLocalDescription() [answer:%o]', answer); + await this._pc.setLocalDescription(answer); + for (const localId of localIds) { + this._mapMidTransceiver.delete(localId); + } + } + async pauseReceiving(localIds) { + this.assertRecvDirection(); + for (const localId of localIds) { + logger.debug('pauseReceiving() [localId:%s]', localId); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); + } + transceiver.direction = 'inactive'; + this._remoteSdp.pauseMediaSection(localId); + } + const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; + logger.debug('pauseReceiving() | calling pc.setRemoteDescription() [offer:%o]', offer); + await this._pc.setRemoteDescription(offer); + const answer = await this._pc.createAnswer(); + logger.debug('pauseReceiving() | calling pc.setLocalDescription() [answer:%o]', answer); + await this._pc.setLocalDescription(answer); + } + async resumeReceiving(localIds) { + this.assertRecvDirection(); + for (const localId of localIds) { + logger.debug('resumeReceiving() [localId:%s]', localId); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); + } + transceiver.direction = 'recvonly'; + this._remoteSdp.resumeReceivingMediaSection(localId); + } + const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; + logger.debug('resumeReceiving() | calling pc.setRemoteDescription() [offer:%o]', offer); + await this._pc.setRemoteDescription(offer); + const answer = await this._pc.createAnswer(); + logger.debug('resumeReceiving() | calling pc.setLocalDescription() [answer:%o]', answer); + await this._pc.setLocalDescription(answer); + } + async getReceiverStats(localId) { + this.assertRecvDirection(); + const transceiver = this._mapMidTransceiver.get(localId); + if (!transceiver) { + throw new Error('associated RTCRtpTransceiver not found'); + } + return transceiver.receiver.getStats(); + } + async receiveDataChannel({ sctpStreamParameters, label, protocol }) { + var _a; + this.assertRecvDirection(); + const { streamId, ordered, maxPacketLifeTime, maxRetransmits } = sctpStreamParameters; + const options = { + negotiated: true, + id: streamId, + ordered, + maxPacketLifeTime, + maxRetransmits, + protocol + }; + logger.debug('receiveDataChannel() [options:%o]', options); + const dataChannel = this._pc.createDataChannel(label, options); + // If this is the first DataChannel we need to create the SDP offer with + // m=application section. + if (!this._hasDataChannelMediaSection) { + this._remoteSdp.receiveSctpAssociation(); + const offer = { type: 'offer', sdp: this._remoteSdp.getSdp() }; + logger.debug('receiveDataChannel() | calling pc.setRemoteDescription() [offer:%o]', offer); + await this._pc.setRemoteDescription(offer); + const answer = await this._pc.createAnswer(); + if (!this._transportReady) { + const localSdpObject = sdpTransform.parse(answer.sdp); + await this.setupTransport({ + localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client', + localSdpObject + }); + } + logger.debug('receiveDataChannel() | calling pc.setRemoteDescription() [answer:%o]', answer); + await this._pc.setLocalDescription(answer); + this._hasDataChannelMediaSection = true; + } + return { dataChannel }; + } + async setupTransport({ localDtlsRole, localSdpObject }) { + if (!localSdpObject) { + localSdpObject = sdpTransform.parse(this._pc.localDescription.sdp); + } + // Get our local DTLS parameters. + const dtlsParameters = sdpCommonUtils.extractDtlsParameters({ sdpObject: localSdpObject }); + // Set our DTLS role. + dtlsParameters.role = localDtlsRole; + // Update the remote DTLS role in the SDP. + this._remoteSdp.updateDtlsRole(localDtlsRole === 'client' ? 'server' : 'client'); + // Need to tell the remote transport about our parameters. + await new Promise((resolve, reject) => { + this.safeEmit('@connect', { dtlsParameters }, resolve, reject); + }); + this._transportReady = true; + } + assertSendDirection() { + if (this._direction !== 'send') { + throw new Error('method can just be called for handlers with "send" direction'); + } + } + assertRecvDirection() { + if (this._direction !== 'recv') { + throw new Error('method can just be called for handlers with "recv" direction'); + } + } +} +exports.Safari12 = Safari12; +},{"../Logger":17,"../ortc":43,"../scalabilityModes":44,"../utils":46,"./HandlerInterface":30,"./ortc/utils":36,"./sdp/RemoteSdp":38,"./sdp/commonUtils":39,"./sdp/unifiedPlanUtils":41,"sdp-transform":52}],35:[function(require,module,exports){ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.mangleRtpParameters = exports.getCapabilities = void 0; +const utils = __importStar(require("../../utils")); /** - * Currently only WebKit-based Web Inspectors, Firefox >= v31, - * and the Firebug extension (any Firefox version) are known - * to support "%c" CSS customizations. - * - * TODO: add a `localStorage` variable to explicitly enable/disable colors + * Normalize ORTC based Edge's RTCRtpReceiver.getCapabilities() to produce a full + * compliant ORTC RTCRtpCapabilities. */ +function getCapabilities() { + const nativeCaps = RTCRtpReceiver.getCapabilities(); + const caps = utils.clone(nativeCaps, {}); + for (const codec of caps.codecs) { + // Rename numChannels to channels. + codec.channels = codec.numChannels; + delete codec.numChannels; + // Add mimeType. + codec.mimeType = codec.mimeType || `${codec.kind}/${codec.name}`; + // NOTE: Edge sets some numeric parameters as string rather than number. Fix them. + if (codec.parameters) { + const parameters = codec.parameters; + if (parameters.apt) { + parameters.apt = Number(parameters.apt); + } + if (parameters['packetization-mode']) { + parameters['packetization-mode'] = Number(parameters['packetization-mode']); + } + } + // Delete emty parameter String in rtcpFeedback. + for (const feedback of codec.rtcpFeedback || []) { + if (!feedback.parameter) { + feedback.parameter = ''; + } + } + } + return caps; +} +exports.getCapabilities = getCapabilities; +/** + * Generate RTCRtpParameters as ORTC based Edge likes. + */ +function mangleRtpParameters(rtpParameters) { + const params = utils.clone(rtpParameters, {}); + // Rename mid to muxId. + if (params.mid) { + params.muxId = params.mid; + delete params.mid; + } + for (const codec of params.codecs) { + // Rename channels to numChannels. + if (codec.channels) { + codec.numChannels = codec.channels; + delete codec.channels; + } + // Add codec.name (requried by Edge). + if (codec.mimeType && !codec.name) { + codec.name = codec.mimeType.split('/')[1]; + } + // Remove mimeType. + delete codec.mimeType; + } + return params; +} +exports.mangleRtpParameters = mangleRtpParameters; -// eslint-disable-next-line complexity -function useColors() { - // NB: In an Electron preload script, document will be defined but not fully - // initialized. Since we know we're in Chrome, we'll just detect this case - // explicitly - if (typeof window !== 'undefined' && window.process && (window.process.type === 'renderer' || window.process.__nwjs)) { - return true; - } - - // Internet Explorer and Edge do not support colors. - if (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)) { - return false; - } +},{"../../utils":46}],36:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.addNackSuppportForOpus = void 0; +/** + * This function adds RTCP NACK support for OPUS codec in given capabilities. + */ +function addNackSuppportForOpus(rtpCapabilities) { + var _a; + for (const codec of (rtpCapabilities.codecs || [])) { + if ((codec.mimeType.toLowerCase() === 'audio/opus' || + codec.mimeType.toLowerCase() === 'audio/multiopus') && + !((_a = codec.rtcpFeedback) === null || _a === void 0 ? void 0 : _a.some((fb) => fb.type === 'nack' && !fb.parameter))) { + if (!codec.rtcpFeedback) { + codec.rtcpFeedback = []; + } + codec.rtcpFeedback.push({ type: 'nack' }); + } + } +} +exports.addNackSuppportForOpus = addNackSuppportForOpus; - // Is webkit? http://stackoverflow.com/a/16459606/376773 - // document is undefined in react-native: https://github.com/facebook/react-native/pull/1632 - return (typeof document !== 'undefined' && document.documentElement && document.documentElement.style && document.documentElement.style.WebkitAppearance) || - // Is firebug? http://stackoverflow.com/a/398120/376773 - (typeof window !== 'undefined' && window.console && (window.console.firebug || (window.console.exception && window.console.table))) || - // Is firefox >= v31? - // https://developer.mozilla.org/en-US/docs/Tools/Web_Console#Styling_messages - (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/firefox\/(\d+)/) && parseInt(RegExp.$1, 10) >= 31) || - // Double check webkit in userAgent just in case we are in a worker - (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/applewebkit\/(\d+)/)); +},{}],37:[function(require,module,exports){ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.OfferMediaSection = exports.AnswerMediaSection = exports.MediaSection = void 0; +const sdpTransform = __importStar(require("sdp-transform")); +const utils = __importStar(require("../../utils")); +class MediaSection { + constructor({ iceParameters, iceCandidates, dtlsParameters, planB = false }) { + this._mediaObject = {}; + this._planB = planB; + if (iceParameters) { + this.setIceParameters(iceParameters); + } + if (iceCandidates) { + this._mediaObject.candidates = []; + for (const candidate of iceCandidates) { + const candidateObject = {}; + // mediasoup does mandates rtcp-mux so candidates component is always + // RTP (1). + candidateObject.component = 1; + candidateObject.foundation = candidate.foundation; + candidateObject.ip = candidate.ip; + candidateObject.port = candidate.port; + candidateObject.priority = candidate.priority; + candidateObject.transport = candidate.protocol; + candidateObject.type = candidate.type; + if (candidate.tcpType) { + candidateObject.tcptype = candidate.tcpType; + } + this._mediaObject.candidates.push(candidateObject); + } + this._mediaObject.endOfCandidates = 'end-of-candidates'; + this._mediaObject.iceOptions = 'renomination'; + } + if (dtlsParameters) { + this.setDtlsRole(dtlsParameters.role); + } + } + get mid() { + return String(this._mediaObject.mid); + } + get closed() { + return this._mediaObject.port === 0; + } + getObject() { + return this._mediaObject; + } + setIceParameters(iceParameters) { + this._mediaObject.iceUfrag = iceParameters.usernameFragment; + this._mediaObject.icePwd = iceParameters.password; + } + pause() { + this._mediaObject.direction = 'inactive'; + } + disable() { + this.pause(); + delete this._mediaObject.ext; + delete this._mediaObject.ssrcs; + delete this._mediaObject.ssrcGroups; + delete this._mediaObject.simulcast; + delete this._mediaObject.simulcast_03; + delete this._mediaObject.rids; + delete this._mediaObject.extmapAllowMixed; + } + close() { + this.disable(); + this._mediaObject.port = 0; + } +} +exports.MediaSection = MediaSection; +class AnswerMediaSection extends MediaSection { + constructor({ iceParameters, iceCandidates, dtlsParameters, sctpParameters, plainRtpParameters, planB = false, offerMediaObject, offerRtpParameters, answerRtpParameters, codecOptions, extmapAllowMixed = false }) { + super({ iceParameters, iceCandidates, dtlsParameters, planB }); + this._mediaObject.mid = String(offerMediaObject.mid); + this._mediaObject.type = offerMediaObject.type; + this._mediaObject.protocol = offerMediaObject.protocol; + if (!plainRtpParameters) { + this._mediaObject.connection = { ip: '127.0.0.1', version: 4 }; + this._mediaObject.port = 7; + } + else { + this._mediaObject.connection = + { + ip: plainRtpParameters.ip, + version: plainRtpParameters.ipVersion + }; + this._mediaObject.port = plainRtpParameters.port; + } + switch (offerMediaObject.type) { + case 'audio': + case 'video': + { + this._mediaObject.direction = 'recvonly'; + this._mediaObject.rtp = []; + this._mediaObject.rtcpFb = []; + this._mediaObject.fmtp = []; + for (const codec of answerRtpParameters.codecs) { + const rtp = { + payload: codec.payloadType, + codec: getCodecName(codec), + rate: codec.clockRate + }; + if (codec.channels > 1) { + rtp.encoding = codec.channels; + } + this._mediaObject.rtp.push(rtp); + const codecParameters = utils.clone(codec.parameters, {}); + let codecRtcpFeedback = utils.clone(codec.rtcpFeedback, []); + if (codecOptions) { + const { opusStereo, opusFec, opusDtx, opusMaxPlaybackRate, opusMaxAverageBitrate, opusPtime, opusNack, videoGoogleStartBitrate, videoGoogleMaxBitrate, videoGoogleMinBitrate } = codecOptions; + const offerCodec = offerRtpParameters.codecs + .find((c) => (c.payloadType === codec.payloadType)); + switch (codec.mimeType.toLowerCase()) { + case 'audio/opus': + case 'audio/multiopus': + { + if (opusStereo !== undefined) { + offerCodec.parameters['sprop-stereo'] = opusStereo ? 1 : 0; + codecParameters.stereo = opusStereo ? 1 : 0; + } + if (opusFec !== undefined) { + offerCodec.parameters.useinbandfec = opusFec ? 1 : 0; + codecParameters.useinbandfec = opusFec ? 1 : 0; + } + if (opusDtx !== undefined) { + offerCodec.parameters.usedtx = opusDtx ? 1 : 0; + codecParameters.usedtx = opusDtx ? 1 : 0; + } + if (opusMaxPlaybackRate !== undefined) { + codecParameters.maxplaybackrate = opusMaxPlaybackRate; + } + if (opusMaxAverageBitrate !== undefined) { + codecParameters.maxaveragebitrate = opusMaxAverageBitrate; + } + if (opusPtime !== undefined) { + offerCodec.parameters.ptime = opusPtime; + codecParameters.ptime = opusPtime; + } + // If opusNack is not set, we must remove NACK support for OPUS. + // Otherwise it would be enabled for those handlers that artificially + // announce it in their RTP capabilities. + if (!opusNack) { + offerCodec.rtcpFeedback = offerCodec + .rtcpFeedback + .filter((fb) => fb.type !== 'nack' || fb.parameter); + codecRtcpFeedback = codecRtcpFeedback + .filter((fb) => fb.type !== 'nack' || fb.parameter); + } + break; + } + case 'video/vp8': + case 'video/vp9': + case 'video/h264': + case 'video/h265': + { + if (videoGoogleStartBitrate !== undefined) { + codecParameters['x-google-start-bitrate'] = videoGoogleStartBitrate; + } + if (videoGoogleMaxBitrate !== undefined) { + codecParameters['x-google-max-bitrate'] = videoGoogleMaxBitrate; + } + if (videoGoogleMinBitrate !== undefined) { + codecParameters['x-google-min-bitrate'] = videoGoogleMinBitrate; + } + break; + } + } + } + const fmtp = { + payload: codec.payloadType, + config: '' + }; + for (const key of Object.keys(codecParameters)) { + if (fmtp.config) { + fmtp.config += ';'; + } + fmtp.config += `${key}=${codecParameters[key]}`; + } + if (fmtp.config) { + this._mediaObject.fmtp.push(fmtp); + } + for (const fb of codecRtcpFeedback) { + this._mediaObject.rtcpFb.push({ + payload: codec.payloadType, + type: fb.type, + subtype: fb.parameter + }); + } + } + this._mediaObject.payloads = answerRtpParameters.codecs + .map((codec) => codec.payloadType) + .join(' '); + this._mediaObject.ext = []; + for (const ext of answerRtpParameters.headerExtensions) { + // Don't add a header extension if not present in the offer. + const found = (offerMediaObject.ext || []) + .some((localExt) => localExt.uri === ext.uri); + if (!found) { + continue; + } + this._mediaObject.ext.push({ + uri: ext.uri, + value: ext.id + }); + } + // Allow both 1 byte and 2 bytes length header extensions. + if (extmapAllowMixed && + offerMediaObject.extmapAllowMixed === 'extmap-allow-mixed') { + this._mediaObject.extmapAllowMixed = 'extmap-allow-mixed'; + } + // Simulcast. + if (offerMediaObject.simulcast) { + this._mediaObject.simulcast = + { + dir1: 'recv', + list1: offerMediaObject.simulcast.list1 + }; + this._mediaObject.rids = []; + for (const rid of offerMediaObject.rids || []) { + if (rid.direction !== 'send') { + continue; + } + this._mediaObject.rids.push({ + id: rid.id, + direction: 'recv' + }); + } + } + // Simulcast (draft version 03). + else if (offerMediaObject.simulcast_03) { + // eslint-disable-next-line camelcase + this._mediaObject.simulcast_03 = + { + value: offerMediaObject.simulcast_03.value.replace(/send/g, 'recv') + }; + this._mediaObject.rids = []; + for (const rid of offerMediaObject.rids || []) { + if (rid.direction !== 'send') { + continue; + } + this._mediaObject.rids.push({ + id: rid.id, + direction: 'recv' + }); + } + } + this._mediaObject.rtcpMux = 'rtcp-mux'; + this._mediaObject.rtcpRsize = 'rtcp-rsize'; + if (this._planB && this._mediaObject.type === 'video') { + this._mediaObject.xGoogleFlag = 'conference'; + } + break; + } + case 'application': + { + // New spec. + if (typeof offerMediaObject.sctpPort === 'number') { + this._mediaObject.payloads = 'webrtc-datachannel'; + this._mediaObject.sctpPort = sctpParameters.port; + this._mediaObject.maxMessageSize = sctpParameters.maxMessageSize; + } + // Old spec. + else if (offerMediaObject.sctpmap) { + this._mediaObject.payloads = sctpParameters.port; + this._mediaObject.sctpmap = + { + app: 'webrtc-datachannel', + sctpmapNumber: sctpParameters.port, + maxMessageSize: sctpParameters.maxMessageSize + }; + } + break; + } + } + } + setDtlsRole(role) { + switch (role) { + case 'client': + this._mediaObject.setup = 'active'; + break; + case 'server': + this._mediaObject.setup = 'passive'; + break; + case 'auto': + this._mediaObject.setup = 'actpass'; + break; + } + } + resume() { + this._mediaObject.direction = 'recvonly'; + } + muxSimulcastStreams(encodings) { + var _a; + if (!this._mediaObject.simulcast || !this._mediaObject.simulcast.list1) { + return; + } + const layers = {}; + for (const encoding of encodings) { + if (encoding.rid) { + layers[encoding.rid] = encoding; + } + } + const raw = this._mediaObject.simulcast.list1; + // NOTE: Ignore bug in @types/sdp-transform. + // Ongoing PR: https://github.com/DefinitelyTyped/DefinitelyTyped/pull/64119 + // @ts-ignore + const simulcastStreams = sdpTransform.parseSimulcastStreamList(raw); + for (const simulcastStream of simulcastStreams) { + for (const simulcastFormat of simulcastStream) { + simulcastFormat.paused = !((_a = layers[simulcastFormat.scid]) === null || _a === void 0 ? void 0 : _a.active); + } + } + this._mediaObject.simulcast.list1 = simulcastStreams.map((simulcastFormats) => simulcastFormats.map((f) => `${f.paused ? '~' : ''}${f.scid}`).join(',')).join(';'); + } +} +exports.AnswerMediaSection = AnswerMediaSection; +class OfferMediaSection extends MediaSection { + constructor({ iceParameters, iceCandidates, dtlsParameters, sctpParameters, plainRtpParameters, planB = false, mid, kind, offerRtpParameters, streamId, trackId, oldDataChannelSpec = false }) { + super({ iceParameters, iceCandidates, dtlsParameters, planB }); + this._mediaObject.mid = String(mid); + this._mediaObject.type = kind; + if (!plainRtpParameters) { + this._mediaObject.connection = { ip: '127.0.0.1', version: 4 }; + if (!sctpParameters) { + this._mediaObject.protocol = 'UDP/TLS/RTP/SAVPF'; + } + else { + this._mediaObject.protocol = 'UDP/DTLS/SCTP'; + } + this._mediaObject.port = 7; + } + else { + this._mediaObject.connection = + { + ip: plainRtpParameters.ip, + version: plainRtpParameters.ipVersion + }; + this._mediaObject.protocol = 'RTP/AVP'; + this._mediaObject.port = plainRtpParameters.port; + } + switch (kind) { + case 'audio': + case 'video': + { + this._mediaObject.direction = 'sendonly'; + this._mediaObject.rtp = []; + this._mediaObject.rtcpFb = []; + this._mediaObject.fmtp = []; + if (!this._planB) { + this._mediaObject.msid = `${streamId || '-'} ${trackId}`; + } + for (const codec of offerRtpParameters.codecs) { + const rtp = { + payload: codec.payloadType, + codec: getCodecName(codec), + rate: codec.clockRate + }; + if (codec.channels > 1) { + rtp.encoding = codec.channels; + } + this._mediaObject.rtp.push(rtp); + const fmtp = { + payload: codec.payloadType, + config: '' + }; + for (const key of Object.keys(codec.parameters)) { + if (fmtp.config) { + fmtp.config += ';'; + } + fmtp.config += `${key}=${codec.parameters[key]}`; + } + if (fmtp.config) { + this._mediaObject.fmtp.push(fmtp); + } + for (const fb of codec.rtcpFeedback) { + this._mediaObject.rtcpFb.push({ + payload: codec.payloadType, + type: fb.type, + subtype: fb.parameter + }); + } + } + this._mediaObject.payloads = offerRtpParameters.codecs + .map((codec) => codec.payloadType) + .join(' '); + this._mediaObject.ext = []; + for (const ext of offerRtpParameters.headerExtensions) { + this._mediaObject.ext.push({ + uri: ext.uri, + value: ext.id + }); + } + this._mediaObject.rtcpMux = 'rtcp-mux'; + this._mediaObject.rtcpRsize = 'rtcp-rsize'; + const encoding = offerRtpParameters.encodings[0]; + const ssrc = encoding.ssrc; + const rtxSsrc = (encoding.rtx && encoding.rtx.ssrc) + ? encoding.rtx.ssrc + : undefined; + this._mediaObject.ssrcs = []; + this._mediaObject.ssrcGroups = []; + if (offerRtpParameters.rtcp.cname) { + this._mediaObject.ssrcs.push({ + id: ssrc, + attribute: 'cname', + value: offerRtpParameters.rtcp.cname + }); + } + if (this._planB) { + this._mediaObject.ssrcs.push({ + id: ssrc, + attribute: 'msid', + value: `${streamId || '-'} ${trackId}` + }); + } + if (rtxSsrc) { + if (offerRtpParameters.rtcp.cname) { + this._mediaObject.ssrcs.push({ + id: rtxSsrc, + attribute: 'cname', + value: offerRtpParameters.rtcp.cname + }); + } + if (this._planB) { + this._mediaObject.ssrcs.push({ + id: rtxSsrc, + attribute: 'msid', + value: `${streamId || '-'} ${trackId}` + }); + } + // Associate original and retransmission SSRCs. + this._mediaObject.ssrcGroups.push({ + semantics: 'FID', + ssrcs: `${ssrc} ${rtxSsrc}` + }); + } + break; + } + case 'application': + { + // New spec. + if (!oldDataChannelSpec) { + this._mediaObject.payloads = 'webrtc-datachannel'; + this._mediaObject.sctpPort = sctpParameters.port; + this._mediaObject.maxMessageSize = sctpParameters.maxMessageSize; + } + // Old spec. + else { + this._mediaObject.payloads = sctpParameters.port; + this._mediaObject.sctpmap = + { + app: 'webrtc-datachannel', + sctpmapNumber: sctpParameters.port, + maxMessageSize: sctpParameters.maxMessageSize + }; + } + break; + } + } + } + // eslint-disable-next-line @typescript-eslint/no-unused-vars + setDtlsRole(role) { + // Always 'actpass'. + this._mediaObject.setup = 'actpass'; + } + resume() { + this._mediaObject.direction = 'sendonly'; + } + planBReceive({ offerRtpParameters, streamId, trackId }) { + const encoding = offerRtpParameters.encodings[0]; + const ssrc = encoding.ssrc; + const rtxSsrc = (encoding.rtx && encoding.rtx.ssrc) + ? encoding.rtx.ssrc + : undefined; + const payloads = this._mediaObject.payloads.split(' '); + for (const codec of offerRtpParameters.codecs) { + if (payloads.includes(String(codec.payloadType))) { + continue; + } + const rtp = { + payload: codec.payloadType, + codec: getCodecName(codec), + rate: codec.clockRate + }; + if (codec.channels > 1) { + rtp.encoding = codec.channels; + } + this._mediaObject.rtp.push(rtp); + const fmtp = { + payload: codec.payloadType, + config: '' + }; + for (const key of Object.keys(codec.parameters)) { + if (fmtp.config) { + fmtp.config += ';'; + } + fmtp.config += `${key}=${codec.parameters[key]}`; + } + if (fmtp.config) { + this._mediaObject.fmtp.push(fmtp); + } + for (const fb of codec.rtcpFeedback) { + this._mediaObject.rtcpFb.push({ + payload: codec.payloadType, + type: fb.type, + subtype: fb.parameter + }); + } + } + this._mediaObject.payloads += ` ${offerRtpParameters + .codecs + .filter((codec) => !this._mediaObject.payloads.includes(codec.payloadType)) + .map((codec) => codec.payloadType) + .join(' ')}`; + this._mediaObject.payloads = this._mediaObject.payloads.trim(); + if (offerRtpParameters.rtcp.cname) { + this._mediaObject.ssrcs.push({ + id: ssrc, + attribute: 'cname', + value: offerRtpParameters.rtcp.cname + }); + } + this._mediaObject.ssrcs.push({ + id: ssrc, + attribute: 'msid', + value: `${streamId || '-'} ${trackId}` + }); + if (rtxSsrc) { + if (offerRtpParameters.rtcp.cname) { + this._mediaObject.ssrcs.push({ + id: rtxSsrc, + attribute: 'cname', + value: offerRtpParameters.rtcp.cname + }); + } + this._mediaObject.ssrcs.push({ + id: rtxSsrc, + attribute: 'msid', + value: `${streamId || '-'} ${trackId}` + }); + // Associate original and retransmission SSRCs. + this._mediaObject.ssrcGroups.push({ + semantics: 'FID', + ssrcs: `${ssrc} ${rtxSsrc}` + }); + } + } + planBStopReceiving({ offerRtpParameters }) { + const encoding = offerRtpParameters.encodings[0]; + const ssrc = encoding.ssrc; + const rtxSsrc = (encoding.rtx && encoding.rtx.ssrc) + ? encoding.rtx.ssrc + : undefined; + this._mediaObject.ssrcs = this._mediaObject.ssrcs + .filter((s) => s.id !== ssrc && s.id !== rtxSsrc); + if (rtxSsrc) { + this._mediaObject.ssrcGroups = this._mediaObject.ssrcGroups + .filter((group) => group.ssrcs !== `${ssrc} ${rtxSsrc}`); + } + } +} +exports.OfferMediaSection = OfferMediaSection; +function getCodecName(codec) { + const MimeTypeRegex = new RegExp('^(audio|video)/(.+)', 'i'); + const mimeTypeMatch = MimeTypeRegex.exec(codec.mimeType); + if (!mimeTypeMatch) { + throw new TypeError('invalid codec.mimeType'); + } + return mimeTypeMatch[2]; } -/** - * Colorize log arguments if enabled. - * - * @api public - */ - -function formatArgs(args) { - args[0] = (this.useColors ? '%c' : '') + - this.namespace + - (this.useColors ? ' %c' : ' ') + - args[0] + - (this.useColors ? '%c ' : ' ') + - '+' + module.exports.humanize(this.diff); - - if (!this.useColors) { - return; - } - - const c = 'color: ' + this.color; - args.splice(1, 0, c, 'color: inherit'); - - // The final "%c" is somewhat tricky, because there could be other - // arguments passed either before or after the %c, so we need to - // figure out the correct index to insert the CSS into - let index = 0; - let lastC = 0; - args[0].replace(/%[a-zA-Z%]/g, match => { - if (match === '%%') { - return; - } - index++; - if (match === '%c') { - // We only are interested in the *last* %c - // (the user may have provided their own) - lastC = index; - } - }); - - args.splice(lastC, 0, c); +},{"../../utils":46,"sdp-transform":52}],38:[function(require,module,exports){ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.RemoteSdp = void 0; +const sdpTransform = __importStar(require("sdp-transform")); +const Logger_1 = require("../../Logger"); +const MediaSection_1 = require("./MediaSection"); +const logger = new Logger_1.Logger('RemoteSdp'); +class RemoteSdp { + constructor({ iceParameters, iceCandidates, dtlsParameters, sctpParameters, plainRtpParameters, planB = false }) { + // MediaSection instances with same order as in the SDP. + this._mediaSections = []; + // MediaSection indices indexed by MID. + this._midToIndex = new Map(); + this._iceParameters = iceParameters; + this._iceCandidates = iceCandidates; + this._dtlsParameters = dtlsParameters; + this._sctpParameters = sctpParameters; + this._plainRtpParameters = plainRtpParameters; + this._planB = planB; + this._sdpObject = + { + version: 0, + origin: { + address: '0.0.0.0', + ipVer: 4, + netType: 'IN', + sessionId: 10000, + sessionVersion: 0, + username: 'mediasoup-client' + }, + name: '-', + timing: { start: 0, stop: 0 }, + media: [] + }; + // If ICE parameters are given, add ICE-Lite indicator. + if (iceParameters && iceParameters.iceLite) { + this._sdpObject.icelite = 'ice-lite'; + } + // If DTLS parameters are given, assume WebRTC and BUNDLE. + if (dtlsParameters) { + this._sdpObject.msidSemantic = { semantic: 'WMS', token: '*' }; + // NOTE: We take the latest fingerprint. + const numFingerprints = this._dtlsParameters.fingerprints.length; + this._sdpObject.fingerprint = + { + type: dtlsParameters.fingerprints[numFingerprints - 1].algorithm, + hash: dtlsParameters.fingerprints[numFingerprints - 1].value + }; + this._sdpObject.groups = [{ type: 'BUNDLE', mids: '' }]; + } + // If there are plain RPT parameters, override SDP origin. + if (plainRtpParameters) { + this._sdpObject.origin.address = plainRtpParameters.ip; + this._sdpObject.origin.ipVer = plainRtpParameters.ipVersion; + } + } + updateIceParameters(iceParameters) { + logger.debug('updateIceParameters() [iceParameters:%o]', iceParameters); + this._iceParameters = iceParameters; + this._sdpObject.icelite = iceParameters.iceLite ? 'ice-lite' : undefined; + for (const mediaSection of this._mediaSections) { + mediaSection.setIceParameters(iceParameters); + } + } + updateDtlsRole(role) { + logger.debug('updateDtlsRole() [role:%s]', role); + this._dtlsParameters.role = role; + for (const mediaSection of this._mediaSections) { + mediaSection.setDtlsRole(role); + } + } + getNextMediaSectionIdx() { + // If a closed media section is found, return its index. + for (let idx = 0; idx < this._mediaSections.length; ++idx) { + const mediaSection = this._mediaSections[idx]; + if (mediaSection.closed) { + return { idx, reuseMid: mediaSection.mid }; + } + } + // If no closed media section is found, return next one. + return { idx: this._mediaSections.length }; + } + send({ offerMediaObject, reuseMid, offerRtpParameters, answerRtpParameters, codecOptions, extmapAllowMixed = false }) { + const mediaSection = new MediaSection_1.AnswerMediaSection({ + iceParameters: this._iceParameters, + iceCandidates: this._iceCandidates, + dtlsParameters: this._dtlsParameters, + plainRtpParameters: this._plainRtpParameters, + planB: this._planB, + offerMediaObject, + offerRtpParameters, + answerRtpParameters, + codecOptions, + extmapAllowMixed + }); + // Unified-Plan with closed media section replacement. + if (reuseMid) { + this._replaceMediaSection(mediaSection, reuseMid); + } + // Unified-Plan or Plan-B with different media kind. + else if (!this._midToIndex.has(mediaSection.mid)) { + this._addMediaSection(mediaSection); + } + // Plan-B with same media kind. + else { + this._replaceMediaSection(mediaSection); + } + } + receive({ mid, kind, offerRtpParameters, streamId, trackId }) { + const idx = this._midToIndex.get(mid); + let mediaSection; + if (idx !== undefined) { + mediaSection = this._mediaSections[idx]; + } + // Unified-Plan or different media kind. + if (!mediaSection) { + mediaSection = new MediaSection_1.OfferMediaSection({ + iceParameters: this._iceParameters, + iceCandidates: this._iceCandidates, + dtlsParameters: this._dtlsParameters, + plainRtpParameters: this._plainRtpParameters, + planB: this._planB, + mid, + kind, + offerRtpParameters, + streamId, + trackId + }); + // Let's try to recycle a closed media section (if any). + // NOTE: Yes, we can recycle a closed m=audio section with a new m=video. + const oldMediaSection = this._mediaSections.find((m) => (m.closed)); + if (oldMediaSection) { + this._replaceMediaSection(mediaSection, oldMediaSection.mid); + } + else { + this._addMediaSection(mediaSection); + } + } + // Plan-B. + else { + mediaSection.planBReceive({ offerRtpParameters, streamId, trackId }); + this._replaceMediaSection(mediaSection); + } + } + pauseMediaSection(mid) { + const mediaSection = this._findMediaSection(mid); + mediaSection.pause(); + } + resumeSendingMediaSection(mid) { + const mediaSection = this._findMediaSection(mid); + mediaSection.resume(); + } + resumeReceivingMediaSection(mid) { + const mediaSection = this._findMediaSection(mid); + mediaSection.resume(); + } + disableMediaSection(mid) { + const mediaSection = this._findMediaSection(mid); + mediaSection.disable(); + } + /** + * Closes media section. Returns true if the given MID corresponds to a m + * section that has been indeed closed. False otherwise. + * + * NOTE: Closing the first m section is a pain since it invalidates the bundled + * transport, so instead closing it we just disable it. + */ + closeMediaSection(mid) { + const mediaSection = this._findMediaSection(mid); + // NOTE: Closing the first m section is a pain since it invalidates the + // bundled transport, so let's avoid it. + if (mid === this._firstMid) { + logger.debug('closeMediaSection() | cannot close first media section, disabling it instead [mid:%s]', mid); + this.disableMediaSection(mid); + return false; + } + mediaSection.close(); + // Regenerate BUNDLE mids. + this._regenerateBundleMids(); + return true; + } + muxMediaSectionSimulcast(mid, encodings) { + const mediaSection = this._findMediaSection(mid); + mediaSection.muxSimulcastStreams(encodings); + this._replaceMediaSection(mediaSection); + } + planBStopReceiving({ mid, offerRtpParameters }) { + const mediaSection = this._findMediaSection(mid); + mediaSection.planBStopReceiving({ offerRtpParameters }); + this._replaceMediaSection(mediaSection); + } + sendSctpAssociation({ offerMediaObject }) { + const mediaSection = new MediaSection_1.AnswerMediaSection({ + iceParameters: this._iceParameters, + iceCandidates: this._iceCandidates, + dtlsParameters: this._dtlsParameters, + sctpParameters: this._sctpParameters, + plainRtpParameters: this._plainRtpParameters, + offerMediaObject + }); + this._addMediaSection(mediaSection); + } + receiveSctpAssociation({ oldDataChannelSpec = false } = {}) { + const mediaSection = new MediaSection_1.OfferMediaSection({ + iceParameters: this._iceParameters, + iceCandidates: this._iceCandidates, + dtlsParameters: this._dtlsParameters, + sctpParameters: this._sctpParameters, + plainRtpParameters: this._plainRtpParameters, + mid: 'datachannel', + kind: 'application', + oldDataChannelSpec + }); + this._addMediaSection(mediaSection); + } + getSdp() { + // Increase SDP version. + this._sdpObject.origin.sessionVersion++; + return sdpTransform.write(this._sdpObject); + } + _addMediaSection(newMediaSection) { + if (!this._firstMid) { + this._firstMid = newMediaSection.mid; + } + // Add to the vector. + this._mediaSections.push(newMediaSection); + // Add to the map. + this._midToIndex.set(newMediaSection.mid, this._mediaSections.length - 1); + // Add to the SDP object. + this._sdpObject.media.push(newMediaSection.getObject()); + // Regenerate BUNDLE mids. + this._regenerateBundleMids(); + } + _replaceMediaSection(newMediaSection, reuseMid) { + // Store it in the map. + if (typeof reuseMid === 'string') { + const idx = this._midToIndex.get(reuseMid); + if (idx === undefined) { + throw new Error(`no media section found for reuseMid '${reuseMid}'`); + } + const oldMediaSection = this._mediaSections[idx]; + // Replace the index in the vector with the new media section. + this._mediaSections[idx] = newMediaSection; + // Update the map. + this._midToIndex.delete(oldMediaSection.mid); + this._midToIndex.set(newMediaSection.mid, idx); + // Update the SDP object. + this._sdpObject.media[idx] = newMediaSection.getObject(); + // Regenerate BUNDLE mids. + this._regenerateBundleMids(); + } + else { + const idx = this._midToIndex.get(newMediaSection.mid); + if (idx === undefined) { + throw new Error(`no media section found with mid '${newMediaSection.mid}'`); + } + // Replace the index in the vector with the new media section. + this._mediaSections[idx] = newMediaSection; + // Update the SDP object. + this._sdpObject.media[idx] = newMediaSection.getObject(); + } + } + _findMediaSection(mid) { + const idx = this._midToIndex.get(mid); + if (idx === undefined) { + throw new Error(`no media section found with mid '${mid}'`); + } + return this._mediaSections[idx]; + } + _regenerateBundleMids() { + if (!this._dtlsParameters) { + return; + } + this._sdpObject.groups[0].mids = this._mediaSections + .filter((mediaSection) => !mediaSection.closed) + .map((mediaSection) => mediaSection.mid) + .join(' '); + } } +exports.RemoteSdp = RemoteSdp; +},{"../../Logger":17,"./MediaSection":37,"sdp-transform":52}],39:[function(require,module,exports){ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.applyCodecParameters = exports.getCname = exports.extractDtlsParameters = exports.extractRtpCapabilities = void 0; +const sdpTransform = __importStar(require("sdp-transform")); /** - * Invokes `console.debug()` when available. - * No-op when `console.debug` is not a "function". - * If `console.debug` is not available, falls back - * to `console.log`. - * - * @api public - */ -exports.log = console.debug || console.log || (() => {}); - -/** - * Save `namespaces`. - * - * @param {String} namespaces - * @api private + * This function must be called with an SDP with 1 m=audio and 1 m=video + * sections. */ -function save(namespaces) { - try { - if (namespaces) { - exports.storage.setItem('debug', namespaces); - } else { - exports.storage.removeItem('debug'); - } - } catch (error) { - // Swallow - // XXX (@Qix-) should we be logging these? - } +function extractRtpCapabilities({ sdpObject }) { + // Map of RtpCodecParameters indexed by payload type. + const codecsMap = new Map(); + // Array of RtpHeaderExtensions. + const headerExtensions = []; + // Whether a m=audio/video section has been already found. + let gotAudio = false; + let gotVideo = false; + for (const m of sdpObject.media) { + const kind = m.type; + switch (kind) { + case 'audio': + { + if (gotAudio) { + continue; + } + gotAudio = true; + break; + } + case 'video': + { + if (gotVideo) { + continue; + } + gotVideo = true; + break; + } + default: + { + continue; + } + } + // Get codecs. + for (const rtp of m.rtp) { + const codec = { + kind: kind, + mimeType: `${kind}/${rtp.codec}`, + preferredPayloadType: rtp.payload, + clockRate: rtp.rate, + channels: rtp.encoding, + parameters: {}, + rtcpFeedback: [] + }; + codecsMap.set(codec.preferredPayloadType, codec); + } + // Get codec parameters. + for (const fmtp of m.fmtp || []) { + const parameters = sdpTransform.parseParams(fmtp.config); + const codec = codecsMap.get(fmtp.payload); + if (!codec) { + continue; + } + // Specials case to convert parameter value to string. + if (parameters && parameters.hasOwnProperty('profile-level-id')) { + parameters['profile-level-id'] = String(parameters['profile-level-id']); + } + codec.parameters = parameters; + } + // Get RTCP feedback for each codec. + for (const fb of m.rtcpFb || []) { + const feedback = { + type: fb.type, + parameter: fb.subtype + }; + if (!feedback.parameter) { + delete feedback.parameter; + } + // rtcp-fb payload is not '*', so just apply it to its corresponding + // codec. + if (fb.payload !== '*') { + const codec = codecsMap.get(fb.payload); + if (!codec) { + continue; + } + codec.rtcpFeedback.push(feedback); + } + // If rtcp-fb payload is '*' it must be applied to all codecs with same + // kind (with some exceptions such as RTX codec). + else { + for (const codec of codecsMap.values()) { + if (codec.kind === kind && !/.+\/rtx$/i.test(codec.mimeType)) { + codec.rtcpFeedback.push(feedback); + } + } + } + } + // Get RTP header extensions. + for (const ext of m.ext || []) { + // Ignore encrypted extensions (not yet supported in mediasoup). + if (ext['encrypt-uri']) { + continue; + } + const headerExtension = { + kind: kind, + uri: ext.uri, + preferredId: ext.value + }; + headerExtensions.push(headerExtension); + } + } + const rtpCapabilities = { + codecs: Array.from(codecsMap.values()), + headerExtensions: headerExtensions + }; + return rtpCapabilities; } - -/** - * Load `namespaces`. - * - * @return {String} returns the previously persisted debug modes - * @api private - */ -function load() { - let r; - try { - r = exports.storage.getItem('debug'); - } catch (error) { - // Swallow - // XXX (@Qix-) should we be logging these? - } - - // If debug isn't set in LS, and we're in Electron, try to load $DEBUG - if (!r && typeof process !== 'undefined' && 'env' in process) { - r = process.env.DEBUG; - } - - return r; +exports.extractRtpCapabilities = extractRtpCapabilities; +function extractDtlsParameters({ sdpObject }) { + const mediaObject = (sdpObject.media || []) + .find((m) => (m.iceUfrag && m.port !== 0)); + if (!mediaObject) { + throw new Error('no active media section found'); + } + const fingerprint = mediaObject.fingerprint || sdpObject.fingerprint; + let role; + switch (mediaObject.setup) { + case 'active': + role = 'client'; + break; + case 'passive': + role = 'server'; + break; + case 'actpass': + role = 'auto'; + break; + } + const dtlsParameters = { + role, + fingerprints: [ + { + algorithm: fingerprint.type, + value: fingerprint.hash + } + ] + }; + return dtlsParameters; } - -/** - * Localstorage attempts to return the localstorage. - * - * This is necessary because safari throws - * when a user disables cookies/localstorage - * and you attempt to access it. - * - * @return {LocalStorage} - * @api private - */ - -function localstorage() { - try { - // TVMLKit (Apple TV JS Runtime) does not have a window object, just localStorage in the global context - // The Browser also has localStorage in the global context. - return localStorage; - } catch (error) { - // Swallow - // XXX (@Qix-) should we be logging these? - } +exports.extractDtlsParameters = extractDtlsParameters; +function getCname({ offerMediaObject }) { + const ssrcCnameLine = (offerMediaObject.ssrcs || []) + .find((line) => line.attribute === 'cname'); + if (!ssrcCnameLine) { + return ''; + } + return ssrcCnameLine.value; } - -module.exports = require('./common')(exports); - -const {formatters} = module.exports; - -/** - * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default. - */ - -formatters.j = function (v) { - try { - return JSON.stringify(v); - } catch (error) { - return '[UnexpectedJSONParseError]: ' + error.message; - } -}; - -}).call(this)}).call(this,require('_process')) -},{"./common":41,"_process":48}],41:[function(require,module,exports){ - +exports.getCname = getCname; /** - * This is the common logic for both the Node.js and web browser - * implementations of `debug()`. + * Apply codec parameters in the given SDP m= section answer based on the + * given RTP parameters of an offer. */ +function applyCodecParameters({ offerRtpParameters, answerMediaObject }) { + for (const codec of offerRtpParameters.codecs) { + const mimeType = codec.mimeType.toLowerCase(); + // Avoid parsing codec parameters for unhandled codecs. + if (mimeType !== 'audio/opus') { + continue; + } + const rtp = (answerMediaObject.rtp || []) + .find((r) => r.payload === codec.payloadType); + if (!rtp) { + continue; + } + // Just in case. + answerMediaObject.fmtp = answerMediaObject.fmtp || []; + let fmtp = answerMediaObject.fmtp + .find((f) => f.payload === codec.payloadType); + if (!fmtp) { + fmtp = { payload: codec.payloadType, config: '' }; + answerMediaObject.fmtp.push(fmtp); + } + const parameters = sdpTransform.parseParams(fmtp.config); + switch (mimeType) { + case 'audio/opus': + { + const spropStereo = codec.parameters['sprop-stereo']; + if (spropStereo !== undefined) { + parameters.stereo = spropStereo ? 1 : 0; + } + break; + } + } + // Write the codec fmtp.config back. + fmtp.config = ''; + for (const key of Object.keys(parameters)) { + if (fmtp.config) { + fmtp.config += ';'; + } + fmtp.config += `${key}=${parameters[key]}`; + } + } +} +exports.applyCodecParameters = applyCodecParameters; -function setup(env) { - createDebug.debug = createDebug; - createDebug.default = createDebug; - createDebug.coerce = coerce; - createDebug.disable = disable; - createDebug.enable = enable; - createDebug.enabled = enabled; - createDebug.humanize = require('ms'); - createDebug.destroy = destroy; - - Object.keys(env).forEach(key => { - createDebug[key] = env[key]; - }); - - /** - * The currently active debug mode names, and names to skip. - */ - - createDebug.names = []; - createDebug.skips = []; - - /** - * Map of special "%n" handling functions, for the debug "format" argument. - * - * Valid key names are a single, lower or upper-case letter, i.e. "n" and "N". - */ - createDebug.formatters = {}; - - /** - * Selects a color for a debug namespace - * @param {String} namespace The namespace string for the for the debug instance to be colored - * @return {Number|String} An ANSI color code for the given namespace - * @api private - */ - function selectColor(namespace) { - let hash = 0; - - for (let i = 0; i < namespace.length; i++) { - hash = ((hash << 5) - hash) + namespace.charCodeAt(i); - hash |= 0; // Convert to 32bit integer - } - - return createDebug.colors[Math.abs(hash) % createDebug.colors.length]; - } - createDebug.selectColor = selectColor; - - /** - * Create a debugger with the given `namespace`. - * - * @param {String} namespace - * @return {Function} - * @api public - */ - function createDebug(namespace) { - let prevTime; - let enableOverride = null; - let namespacesCache; - let enabledCache; - - function debug(...args) { - // Disabled? - if (!debug.enabled) { - return; - } - - const self = debug; - - // Set `diff` timestamp - const curr = Number(new Date()); - const ms = curr - (prevTime || curr); - self.diff = ms; - self.prev = prevTime; - self.curr = curr; - prevTime = curr; - - args[0] = createDebug.coerce(args[0]); - - if (typeof args[0] !== 'string') { - // Anything else let's inspect with %O - args.unshift('%O'); - } - - // Apply any `formatters` transformations - let index = 0; - args[0] = args[0].replace(/%([a-zA-Z%])/g, (match, format) => { - // If we encounter an escaped % then don't increase the array index - if (match === '%%') { - return '%'; - } - index++; - const formatter = createDebug.formatters[format]; - if (typeof formatter === 'function') { - const val = args[index]; - match = formatter.call(self, val); - - // Now we need to remove `args[index]` since it's inlined in the `format` - args.splice(index, 1); - index--; - } - return match; - }); - - // Apply env-specific formatting (colors, etc.) - createDebug.formatArgs.call(self, args); - - const logFn = self.log || createDebug.log; - logFn.apply(self, args); - } - - debug.namespace = namespace; - debug.useColors = createDebug.useColors(); - debug.color = createDebug.selectColor(namespace); - debug.extend = extend; - debug.destroy = createDebug.destroy; // XXX Temporary. Will be removed in the next major release. - - Object.defineProperty(debug, 'enabled', { - enumerable: true, - configurable: false, - get: () => { - if (enableOverride !== null) { - return enableOverride; - } - if (namespacesCache !== createDebug.namespaces) { - namespacesCache = createDebug.namespaces; - enabledCache = createDebug.enabled(namespace); - } - - return enabledCache; - }, - set: v => { - enableOverride = v; - } - }); - - // Env-specific initialization logic for debug instances - if (typeof createDebug.init === 'function') { - createDebug.init(debug); - } - - return debug; - } - - function extend(namespace, delimiter) { - const newDebug = createDebug(this.namespace + (typeof delimiter === 'undefined' ? ':' : delimiter) + namespace); - newDebug.log = this.log; - return newDebug; - } - - /** - * Enables a debug mode by namespaces. This can include modes - * separated by a colon and wildcards. - * - * @param {String} namespaces - * @api public - */ - function enable(namespaces) { - createDebug.save(namespaces); - createDebug.namespaces = namespaces; - - createDebug.names = []; - createDebug.skips = []; - - let i; - const split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/); - const len = split.length; - - for (i = 0; i < len; i++) { - if (!split[i]) { - // ignore empty strings - continue; - } - - namespaces = split[i].replace(/\*/g, '.*?'); - - if (namespaces[0] === '-') { - createDebug.skips.push(new RegExp('^' + namespaces.substr(1) + '$')); - } else { - createDebug.names.push(new RegExp('^' + namespaces + '$')); - } - } - } - - /** - * Disable debug output. - * - * @return {String} namespaces - * @api public - */ - function disable() { - const namespaces = [ - ...createDebug.names.map(toNamespace), - ...createDebug.skips.map(toNamespace).map(namespace => '-' + namespace) - ].join(','); - createDebug.enable(''); - return namespaces; - } - - /** - * Returns true if the given mode name is enabled, false otherwise. - * - * @param {String} name - * @return {Boolean} - * @api public - */ - function enabled(name) { - if (name[name.length - 1] === '*') { - return true; - } - - let i; - let len; - - for (i = 0, len = createDebug.skips.length; i < len; i++) { - if (createDebug.skips[i].test(name)) { - return false; - } - } - - for (i = 0, len = createDebug.names.length; i < len; i++) { - if (createDebug.names[i].test(name)) { - return true; - } - } +},{"sdp-transform":52}],40:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.addLegacySimulcast = exports.getRtpEncodings = void 0; +function getRtpEncodings({ offerMediaObject, track }) { + // First media SSRC (or the only one). + let firstSsrc; + const ssrcs = new Set(); + for (const line of offerMediaObject.ssrcs || []) { + if (line.attribute !== 'msid') { + continue; + } + const trackId = line.value.split(' ')[1]; + if (trackId === track.id) { + const ssrc = line.id; + ssrcs.add(ssrc); + if (!firstSsrc) { + firstSsrc = ssrc; + } + } + } + if (ssrcs.size === 0) { + throw new Error(`a=ssrc line with msid information not found [track.id:${track.id}]`); + } + const ssrcToRtxSsrc = new Map(); + // First assume RTX is used. + for (const line of offerMediaObject.ssrcGroups || []) { + if (line.semantics !== 'FID') { + continue; + } + let [ssrc, rtxSsrc] = line.ssrcs.split(/\s+/); + ssrc = Number(ssrc); + rtxSsrc = Number(rtxSsrc); + if (ssrcs.has(ssrc)) { + // Remove both the SSRC and RTX SSRC from the set so later we know that they + // are already handled. + ssrcs.delete(ssrc); + ssrcs.delete(rtxSsrc); + // Add to the map. + ssrcToRtxSsrc.set(ssrc, rtxSsrc); + } + } + // If the set of SSRCs is not empty it means that RTX is not being used, so take + // media SSRCs from there. + for (const ssrc of ssrcs) { + // Add to the map. + ssrcToRtxSsrc.set(ssrc, null); + } + const encodings = []; + for (const [ssrc, rtxSsrc] of ssrcToRtxSsrc) { + const encoding = { ssrc }; + if (rtxSsrc) { + encoding.rtx = { ssrc: rtxSsrc }; + } + encodings.push(encoding); + } + return encodings; +} +exports.getRtpEncodings = getRtpEncodings; +/** + * Adds multi-ssrc based simulcast into the given SDP media section offer. + */ +function addLegacySimulcast({ offerMediaObject, track, numStreams }) { + if (numStreams <= 1) { + throw new TypeError('numStreams must be greater than 1'); + } + let firstSsrc; + let firstRtxSsrc; + let streamId; + // Get the SSRC. + const ssrcMsidLine = (offerMediaObject.ssrcs || []) + .find((line) => { + if (line.attribute !== 'msid') { + return false; + } + const trackId = line.value.split(' ')[1]; + if (trackId === track.id) { + firstSsrc = line.id; + streamId = line.value.split(' ')[0]; + return true; + } + else { + return false; + } + }); + if (!ssrcMsidLine) { + throw new Error(`a=ssrc line with msid information not found [track.id:${track.id}]`); + } + // Get the SSRC for RTX. + (offerMediaObject.ssrcGroups || []) + .some((line) => { + if (line.semantics !== 'FID') { + return false; + } + const ssrcs = line.ssrcs.split(/\s+/); + if (Number(ssrcs[0]) === firstSsrc) { + firstRtxSsrc = Number(ssrcs[1]); + return true; + } + else { + return false; + } + }); + const ssrcCnameLine = offerMediaObject.ssrcs + .find((line) => (line.attribute === 'cname' && line.id === firstSsrc)); + if (!ssrcCnameLine) { + throw new Error(`a=ssrc line with cname information not found [track.id:${track.id}]`); + } + const cname = ssrcCnameLine.value; + const ssrcs = []; + const rtxSsrcs = []; + for (let i = 0; i < numStreams; ++i) { + ssrcs.push(firstSsrc + i); + if (firstRtxSsrc) { + rtxSsrcs.push(firstRtxSsrc + i); + } + } + offerMediaObject.ssrcGroups = offerMediaObject.ssrcGroups || []; + offerMediaObject.ssrcs = offerMediaObject.ssrcs || []; + offerMediaObject.ssrcGroups.push({ + semantics: 'SIM', + ssrcs: ssrcs.join(' ') + }); + for (let i = 0; i < ssrcs.length; ++i) { + const ssrc = ssrcs[i]; + offerMediaObject.ssrcs.push({ + id: ssrc, + attribute: 'cname', + value: cname + }); + offerMediaObject.ssrcs.push({ + id: ssrc, + attribute: 'msid', + value: `${streamId} ${track.id}` + }); + } + for (let i = 0; i < rtxSsrcs.length; ++i) { + const ssrc = ssrcs[i]; + const rtxSsrc = rtxSsrcs[i]; + offerMediaObject.ssrcs.push({ + id: rtxSsrc, + attribute: 'cname', + value: cname + }); + offerMediaObject.ssrcs.push({ + id: rtxSsrc, + attribute: 'msid', + value: `${streamId} ${track.id}` + }); + offerMediaObject.ssrcGroups.push({ + semantics: 'FID', + ssrcs: `${ssrc} ${rtxSsrc}` + }); + } +} +exports.addLegacySimulcast = addLegacySimulcast; - return false; - } +},{}],41:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.addLegacySimulcast = exports.getRtpEncodings = void 0; +function getRtpEncodings({ offerMediaObject }) { + const ssrcs = new Set(); + for (const line of offerMediaObject.ssrcs || []) { + const ssrc = line.id; + ssrcs.add(ssrc); + } + if (ssrcs.size === 0) { + throw new Error('no a=ssrc lines found'); + } + const ssrcToRtxSsrc = new Map(); + // First assume RTX is used. + for (const line of offerMediaObject.ssrcGroups || []) { + if (line.semantics !== 'FID') { + continue; + } + let [ssrc, rtxSsrc] = line.ssrcs.split(/\s+/); + ssrc = Number(ssrc); + rtxSsrc = Number(rtxSsrc); + if (ssrcs.has(ssrc)) { + // Remove both the SSRC and RTX SSRC from the set so later we know + // that they are already handled. + ssrcs.delete(ssrc); + ssrcs.delete(rtxSsrc); + // Add to the map. + ssrcToRtxSsrc.set(ssrc, rtxSsrc); + } + } + // If the set of SSRCs is not empty it means that RTX is not being used, so + // take media SSRCs from there. + for (const ssrc of ssrcs) { + // Add to the map. + ssrcToRtxSsrc.set(ssrc, null); + } + const encodings = []; + for (const [ssrc, rtxSsrc] of ssrcToRtxSsrc) { + const encoding = { ssrc }; + if (rtxSsrc) { + encoding.rtx = { ssrc: rtxSsrc }; + } + encodings.push(encoding); + } + return encodings; +} +exports.getRtpEncodings = getRtpEncodings; +/** + * Adds multi-ssrc based simulcast into the given SDP media section offer. + */ +function addLegacySimulcast({ offerMediaObject, numStreams }) { + if (numStreams <= 1) { + throw new TypeError('numStreams must be greater than 1'); + } + // Get the SSRC. + const ssrcMsidLine = (offerMediaObject.ssrcs || []) + .find((line) => line.attribute === 'msid'); + if (!ssrcMsidLine) { + throw new Error('a=ssrc line with msid information not found'); + } + const [streamId, trackId] = ssrcMsidLine.value.split(' '); + const firstSsrc = ssrcMsidLine.id; + let firstRtxSsrc; + // Get the SSRC for RTX. + (offerMediaObject.ssrcGroups || []) + .some((line) => { + if (line.semantics !== 'FID') { + return false; + } + const ssrcs = line.ssrcs.split(/\s+/); + if (Number(ssrcs[0]) === firstSsrc) { + firstRtxSsrc = Number(ssrcs[1]); + return true; + } + else { + return false; + } + }); + const ssrcCnameLine = offerMediaObject.ssrcs + .find((line) => line.attribute === 'cname'); + if (!ssrcCnameLine) { + throw new Error('a=ssrc line with cname information not found'); + } + const cname = ssrcCnameLine.value; + const ssrcs = []; + const rtxSsrcs = []; + for (let i = 0; i < numStreams; ++i) { + ssrcs.push(firstSsrc + i); + if (firstRtxSsrc) { + rtxSsrcs.push(firstRtxSsrc + i); + } + } + offerMediaObject.ssrcGroups = []; + offerMediaObject.ssrcs = []; + offerMediaObject.ssrcGroups.push({ + semantics: 'SIM', + ssrcs: ssrcs.join(' ') + }); + for (let i = 0; i < ssrcs.length; ++i) { + const ssrc = ssrcs[i]; + offerMediaObject.ssrcs.push({ + id: ssrc, + attribute: 'cname', + value: cname + }); + offerMediaObject.ssrcs.push({ + id: ssrc, + attribute: 'msid', + value: `${streamId} ${trackId}` + }); + } + for (let i = 0; i < rtxSsrcs.length; ++i) { + const ssrc = ssrcs[i]; + const rtxSsrc = rtxSsrcs[i]; + offerMediaObject.ssrcs.push({ + id: rtxSsrc, + attribute: 'cname', + value: cname + }); + offerMediaObject.ssrcs.push({ + id: rtxSsrc, + attribute: 'msid', + value: `${streamId} ${trackId}` + }); + offerMediaObject.ssrcGroups.push({ + semantics: 'FID', + ssrcs: `${ssrc} ${rtxSsrc}` + }); + } +} +exports.addLegacySimulcast = addLegacySimulcast; - /** - * Convert regexp to namespace - * - * @param {RegExp} regxep - * @return {String} namespace - * @api private - */ - function toNamespace(regexp) { - return regexp.toString() - .substring(2, regexp.toString().length - 2) - .replace(/\.\*\?$/, '*'); - } +},{}],42:[function(require,module,exports){ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +var __importDefault = (this && this.__importDefault) || function (mod) { + return (mod && mod.__esModule) ? mod : { "default": mod }; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.debug = exports.parseScalabilityMode = exports.detectDevice = exports.Device = exports.version = exports.types = void 0; +const debug_1 = __importDefault(require("debug")); +exports.debug = debug_1.default; +const Device_1 = require("./Device"); +Object.defineProperty(exports, "Device", { enumerable: true, get: function () { return Device_1.Device; } }); +Object.defineProperty(exports, "detectDevice", { enumerable: true, get: function () { return Device_1.detectDevice; } }); +const types = __importStar(require("./types")); +exports.types = types; +/** + * Expose mediasoup-client version. + */ +exports.version = '3.6.82'; +/** + * Expose parseScalabilityMode() function. + */ +var scalabilityModes_1 = require("./scalabilityModes"); +Object.defineProperty(exports, "parseScalabilityMode", { enumerable: true, get: function () { return scalabilityModes_1.parse; } }); - /** - * Coerce `val`. - * - * @param {Mixed} val - * @return {Mixed} - * @api private - */ - function coerce(val) { - if (val instanceof Error) { - return val.stack || val.message; - } - return val; - } +},{"./Device":15,"./scalabilityModes":44,"./types":45,"debug":47}],43:[function(require,module,exports){ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { + Object.defineProperty(o, "default", { enumerable: true, value: v }); +}) : function(o, v) { + o["default"] = v; +}); +var __importStar = (this && this.__importStar) || function (mod) { + if (mod && mod.__esModule) return mod; + var result = {}; + if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); + __setModuleDefault(result, mod); + return result; +}; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.canReceive = exports.canSend = exports.generateProbatorRtpParameters = exports.reduceCodecs = exports.getSendingRemoteRtpParameters = exports.getSendingRtpParameters = exports.getRecvRtpCapabilities = exports.getExtendedRtpCapabilities = exports.validateSctpStreamParameters = exports.validateSctpParameters = exports.validateNumSctpStreams = exports.validateSctpCapabilities = exports.validateRtcpParameters = exports.validateRtpEncodingParameters = exports.validateRtpHeaderExtensionParameters = exports.validateRtpCodecParameters = exports.validateRtpParameters = exports.validateRtpHeaderExtension = exports.validateRtcpFeedback = exports.validateRtpCodecCapability = exports.validateRtpCapabilities = void 0; +const h264 = __importStar(require("h264-profile-level-id")); +const utils = __importStar(require("./utils")); +const RTP_PROBATOR_MID = 'probator'; +const RTP_PROBATOR_SSRC = 1234; +const RTP_PROBATOR_CODEC_PAYLOAD_TYPE = 127; +/** + * Validates RtpCapabilities. It may modify given data by adding missing + * fields with default values. + * It throws if invalid. + */ +function validateRtpCapabilities(caps) { + if (typeof caps !== 'object') { + throw new TypeError('caps is not an object'); + } + // codecs is optional. If unset, fill with an empty array. + if (caps.codecs && !Array.isArray(caps.codecs)) { + throw new TypeError('caps.codecs is not an array'); + } + else if (!caps.codecs) { + caps.codecs = []; + } + for (const codec of caps.codecs) { + validateRtpCodecCapability(codec); + } + // headerExtensions is optional. If unset, fill with an empty array. + if (caps.headerExtensions && !Array.isArray(caps.headerExtensions)) { + throw new TypeError('caps.headerExtensions is not an array'); + } + else if (!caps.headerExtensions) { + caps.headerExtensions = []; + } + for (const ext of caps.headerExtensions) { + validateRtpHeaderExtension(ext); + } +} +exports.validateRtpCapabilities = validateRtpCapabilities; +/** + * Validates RtpCodecCapability. It may modify given data by adding missing + * fields with default values. + * It throws if invalid. + */ +function validateRtpCodecCapability(codec) { + const MimeTypeRegex = new RegExp('^(audio|video)/(.+)', 'i'); + if (typeof codec !== 'object') { + throw new TypeError('codec is not an object'); + } + // mimeType is mandatory. + if (!codec.mimeType || typeof codec.mimeType !== 'string') { + throw new TypeError('missing codec.mimeType'); + } + const mimeTypeMatch = MimeTypeRegex.exec(codec.mimeType); + if (!mimeTypeMatch) { + throw new TypeError('invalid codec.mimeType'); + } + // Just override kind with media component of mimeType. + codec.kind = mimeTypeMatch[1].toLowerCase(); + // preferredPayloadType is optional. + if (codec.preferredPayloadType && typeof codec.preferredPayloadType !== 'number') { + throw new TypeError('invalid codec.preferredPayloadType'); + } + // clockRate is mandatory. + if (typeof codec.clockRate !== 'number') { + throw new TypeError('missing codec.clockRate'); + } + // channels is optional. If unset, set it to 1 (just if audio). + if (codec.kind === 'audio') { + if (typeof codec.channels !== 'number') { + codec.channels = 1; + } + } + else { + delete codec.channels; + } + // parameters is optional. If unset, set it to an empty object. + if (!codec.parameters || typeof codec.parameters !== 'object') { + codec.parameters = {}; + } + for (const key of Object.keys(codec.parameters)) { + let value = codec.parameters[key]; + if (value === undefined) { + codec.parameters[key] = ''; + value = ''; + } + if (typeof value !== 'string' && typeof value !== 'number') { + throw new TypeError(`invalid codec parameter [key:${key}s, value:${value}]`); + } + // Specific parameters validation. + if (key === 'apt') { + if (typeof value !== 'number') { + throw new TypeError('invalid codec apt parameter'); + } + } + } + // rtcpFeedback is optional. If unset, set it to an empty array. + if (!codec.rtcpFeedback || !Array.isArray(codec.rtcpFeedback)) { + codec.rtcpFeedback = []; + } + for (const fb of codec.rtcpFeedback) { + validateRtcpFeedback(fb); + } +} +exports.validateRtpCodecCapability = validateRtpCodecCapability; +/** + * Validates RtcpFeedback. It may modify given data by adding missing + * fields with default values. + * It throws if invalid. + */ +function validateRtcpFeedback(fb) { + if (typeof fb !== 'object') { + throw new TypeError('fb is not an object'); + } + // type is mandatory. + if (!fb.type || typeof fb.type !== 'string') { + throw new TypeError('missing fb.type'); + } + // parameter is optional. If unset set it to an empty string. + if (!fb.parameter || typeof fb.parameter !== 'string') { + fb.parameter = ''; + } +} +exports.validateRtcpFeedback = validateRtcpFeedback; +/** + * Validates RtpHeaderExtension. It may modify given data by adding missing + * fields with default values. + * It throws if invalid. + */ +function validateRtpHeaderExtension(ext) { + if (typeof ext !== 'object') { + throw new TypeError('ext is not an object'); + } + // kind is mandatory. + if (ext.kind !== 'audio' && ext.kind !== 'video') { + throw new TypeError('invalid ext.kind'); + } + // uri is mandatory. + if (!ext.uri || typeof ext.uri !== 'string') { + throw new TypeError('missing ext.uri'); + } + // preferredId is mandatory. + if (typeof ext.preferredId !== 'number') { + throw new TypeError('missing ext.preferredId'); + } + // preferredEncrypt is optional. If unset set it to false. + if (ext.preferredEncrypt && typeof ext.preferredEncrypt !== 'boolean') { + throw new TypeError('invalid ext.preferredEncrypt'); + } + else if (!ext.preferredEncrypt) { + ext.preferredEncrypt = false; + } + // direction is optional. If unset set it to sendrecv. + if (ext.direction && typeof ext.direction !== 'string') { + throw new TypeError('invalid ext.direction'); + } + else if (!ext.direction) { + ext.direction = 'sendrecv'; + } +} +exports.validateRtpHeaderExtension = validateRtpHeaderExtension; +/** + * Validates RtpParameters. It may modify given data by adding missing + * fields with default values. + * It throws if invalid. + */ +function validateRtpParameters(params) { + if (typeof params !== 'object') { + throw new TypeError('params is not an object'); + } + // mid is optional. + if (params.mid && typeof params.mid !== 'string') { + throw new TypeError('params.mid is not a string'); + } + // codecs is mandatory. + if (!Array.isArray(params.codecs)) { + throw new TypeError('missing params.codecs'); + } + for (const codec of params.codecs) { + validateRtpCodecParameters(codec); + } + // headerExtensions is optional. If unset, fill with an empty array. + if (params.headerExtensions && !Array.isArray(params.headerExtensions)) { + throw new TypeError('params.headerExtensions is not an array'); + } + else if (!params.headerExtensions) { + params.headerExtensions = []; + } + for (const ext of params.headerExtensions) { + validateRtpHeaderExtensionParameters(ext); + } + // encodings is optional. If unset, fill with an empty array. + if (params.encodings && !Array.isArray(params.encodings)) { + throw new TypeError('params.encodings is not an array'); + } + else if (!params.encodings) { + params.encodings = []; + } + for (const encoding of params.encodings) { + validateRtpEncodingParameters(encoding); + } + // rtcp is optional. If unset, fill with an empty object. + if (params.rtcp && typeof params.rtcp !== 'object') { + throw new TypeError('params.rtcp is not an object'); + } + else if (!params.rtcp) { + params.rtcp = {}; + } + validateRtcpParameters(params.rtcp); +} +exports.validateRtpParameters = validateRtpParameters; +/** + * Validates RtpCodecParameters. It may modify given data by adding missing + * fields with default values. + * It throws if invalid. + */ +function validateRtpCodecParameters(codec) { + const MimeTypeRegex = new RegExp('^(audio|video)/(.+)', 'i'); + if (typeof codec !== 'object') { + throw new TypeError('codec is not an object'); + } + // mimeType is mandatory. + if (!codec.mimeType || typeof codec.mimeType !== 'string') { + throw new TypeError('missing codec.mimeType'); + } + const mimeTypeMatch = MimeTypeRegex.exec(codec.mimeType); + if (!mimeTypeMatch) { + throw new TypeError('invalid codec.mimeType'); + } + // payloadType is mandatory. + if (typeof codec.payloadType !== 'number') { + throw new TypeError('missing codec.payloadType'); + } + // clockRate is mandatory. + if (typeof codec.clockRate !== 'number') { + throw new TypeError('missing codec.clockRate'); + } + const kind = mimeTypeMatch[1].toLowerCase(); + // channels is optional. If unset, set it to 1 (just if audio). + if (kind === 'audio') { + if (typeof codec.channels !== 'number') { + codec.channels = 1; + } + } + else { + delete codec.channels; + } + // parameters is optional. If unset, set it to an empty object. + if (!codec.parameters || typeof codec.parameters !== 'object') { + codec.parameters = {}; + } + for (const key of Object.keys(codec.parameters)) { + let value = codec.parameters[key]; + if (value === undefined) { + codec.parameters[key] = ''; + value = ''; + } + if (typeof value !== 'string' && typeof value !== 'number') { + throw new TypeError(`invalid codec parameter [key:${key}s, value:${value}]`); + } + // Specific parameters validation. + if (key === 'apt') { + if (typeof value !== 'number') { + throw new TypeError('invalid codec apt parameter'); + } + } + } + // rtcpFeedback is optional. If unset, set it to an empty array. + if (!codec.rtcpFeedback || !Array.isArray(codec.rtcpFeedback)) { + codec.rtcpFeedback = []; + } + for (const fb of codec.rtcpFeedback) { + validateRtcpFeedback(fb); + } +} +exports.validateRtpCodecParameters = validateRtpCodecParameters; +/** + * Validates RtpHeaderExtensionParameteters. It may modify given data by adding missing + * fields with default values. + * It throws if invalid. + */ +function validateRtpHeaderExtensionParameters(ext) { + if (typeof ext !== 'object') { + throw new TypeError('ext is not an object'); + } + // uri is mandatory. + if (!ext.uri || typeof ext.uri !== 'string') { + throw new TypeError('missing ext.uri'); + } + // id is mandatory. + if (typeof ext.id !== 'number') { + throw new TypeError('missing ext.id'); + } + // encrypt is optional. If unset set it to false. + if (ext.encrypt && typeof ext.encrypt !== 'boolean') { + throw new TypeError('invalid ext.encrypt'); + } + else if (!ext.encrypt) { + ext.encrypt = false; + } + // parameters is optional. If unset, set it to an empty object. + if (!ext.parameters || typeof ext.parameters !== 'object') { + ext.parameters = {}; + } + for (const key of Object.keys(ext.parameters)) { + let value = ext.parameters[key]; + if (value === undefined) { + ext.parameters[key] = ''; + value = ''; + } + if (typeof value !== 'string' && typeof value !== 'number') { + throw new TypeError('invalid header extension parameter'); + } + } +} +exports.validateRtpHeaderExtensionParameters = validateRtpHeaderExtensionParameters; +/** + * Validates RtpEncodingParameters. It may modify given data by adding missing + * fields with default values. + * It throws if invalid. + */ +function validateRtpEncodingParameters(encoding) { + if (typeof encoding !== 'object') { + throw new TypeError('encoding is not an object'); + } + // ssrc is optional. + if (encoding.ssrc && typeof encoding.ssrc !== 'number') { + throw new TypeError('invalid encoding.ssrc'); + } + // rid is optional. + if (encoding.rid && typeof encoding.rid !== 'string') { + throw new TypeError('invalid encoding.rid'); + } + // rtx is optional. + if (encoding.rtx && typeof encoding.rtx !== 'object') { + throw new TypeError('invalid encoding.rtx'); + } + else if (encoding.rtx) { + // RTX ssrc is mandatory if rtx is present. + if (typeof encoding.rtx.ssrc !== 'number') { + throw new TypeError('missing encoding.rtx.ssrc'); + } + } + // dtx is optional. If unset set it to false. + if (!encoding.dtx || typeof encoding.dtx !== 'boolean') { + encoding.dtx = false; + } + // scalabilityMode is optional. + if (encoding.scalabilityMode && typeof encoding.scalabilityMode !== 'string') { + throw new TypeError('invalid encoding.scalabilityMode'); + } +} +exports.validateRtpEncodingParameters = validateRtpEncodingParameters; +/** + * Validates RtcpParameters. It may modify given data by adding missing + * fields with default values. + * It throws if invalid. + */ +function validateRtcpParameters(rtcp) { + if (typeof rtcp !== 'object') { + throw new TypeError('rtcp is not an object'); + } + // cname is optional. + if (rtcp.cname && typeof rtcp.cname !== 'string') { + throw new TypeError('invalid rtcp.cname'); + } + // reducedSize is optional. If unset set it to true. + if (!rtcp.reducedSize || typeof rtcp.reducedSize !== 'boolean') { + rtcp.reducedSize = true; + } +} +exports.validateRtcpParameters = validateRtcpParameters; +/** + * Validates SctpCapabilities. It may modify given data by adding missing + * fields with default values. + * It throws if invalid. + */ +function validateSctpCapabilities(caps) { + if (typeof caps !== 'object') { + throw new TypeError('caps is not an object'); + } + // numStreams is mandatory. + if (!caps.numStreams || typeof caps.numStreams !== 'object') { + throw new TypeError('missing caps.numStreams'); + } + validateNumSctpStreams(caps.numStreams); +} +exports.validateSctpCapabilities = validateSctpCapabilities; +/** + * Validates NumSctpStreams. It may modify given data by adding missing + * fields with default values. + * It throws if invalid. + */ +function validateNumSctpStreams(numStreams) { + if (typeof numStreams !== 'object') { + throw new TypeError('numStreams is not an object'); + } + // OS is mandatory. + if (typeof numStreams.OS !== 'number') { + throw new TypeError('missing numStreams.OS'); + } + // MIS is mandatory. + if (typeof numStreams.MIS !== 'number') { + throw new TypeError('missing numStreams.MIS'); + } +} +exports.validateNumSctpStreams = validateNumSctpStreams; +/** + * Validates SctpParameters. It may modify given data by adding missing + * fields with default values. + * It throws if invalid. + */ +function validateSctpParameters(params) { + if (typeof params !== 'object') { + throw new TypeError('params is not an object'); + } + // port is mandatory. + if (typeof params.port !== 'number') { + throw new TypeError('missing params.port'); + } + // OS is mandatory. + if (typeof params.OS !== 'number') { + throw new TypeError('missing params.OS'); + } + // MIS is mandatory. + if (typeof params.MIS !== 'number') { + throw new TypeError('missing params.MIS'); + } + // maxMessageSize is mandatory. + if (typeof params.maxMessageSize !== 'number') { + throw new TypeError('missing params.maxMessageSize'); + } +} +exports.validateSctpParameters = validateSctpParameters; +/** + * Validates SctpStreamParameters. It may modify given data by adding missing + * fields with default values. + * It throws if invalid. + */ +function validateSctpStreamParameters(params) { + if (typeof params !== 'object') { + throw new TypeError('params is not an object'); + } + // streamId is mandatory. + if (typeof params.streamId !== 'number') { + throw new TypeError('missing params.streamId'); + } + // ordered is optional. + let orderedGiven = false; + if (typeof params.ordered === 'boolean') { + orderedGiven = true; + } + else { + params.ordered = true; + } + // maxPacketLifeTime is optional. + if (params.maxPacketLifeTime && typeof params.maxPacketLifeTime !== 'number') { + throw new TypeError('invalid params.maxPacketLifeTime'); + } + // maxRetransmits is optional. + if (params.maxRetransmits && typeof params.maxRetransmits !== 'number') { + throw new TypeError('invalid params.maxRetransmits'); + } + if (params.maxPacketLifeTime && params.maxRetransmits) { + throw new TypeError('cannot provide both maxPacketLifeTime and maxRetransmits'); + } + if (orderedGiven && + params.ordered && + (params.maxPacketLifeTime || params.maxRetransmits)) { + throw new TypeError('cannot be ordered with maxPacketLifeTime or maxRetransmits'); + } + else if (!orderedGiven && (params.maxPacketLifeTime || params.maxRetransmits)) { + params.ordered = false; + } + // label is optional. + if (params.label && typeof params.label !== 'string') { + throw new TypeError('invalid params.label'); + } + // protocol is optional. + if (params.protocol && typeof params.protocol !== 'string') { + throw new TypeError('invalid params.protocol'); + } +} +exports.validateSctpStreamParameters = validateSctpStreamParameters; +/** + * Generate extended RTP capabilities for sending and receiving. + */ +function getExtendedRtpCapabilities(localCaps, remoteCaps) { + const extendedRtpCapabilities = { + codecs: [], + headerExtensions: [] + }; + // Match media codecs and keep the order preferred by remoteCaps. + for (const remoteCodec of remoteCaps.codecs || []) { + if (isRtxCodec(remoteCodec)) { + continue; + } + const matchingLocalCodec = (localCaps.codecs || []) + .find((localCodec) => (matchCodecs(localCodec, remoteCodec, { strict: true, modify: true }))); + if (!matchingLocalCodec) { + continue; + } + const extendedCodec = { + mimeType: matchingLocalCodec.mimeType, + kind: matchingLocalCodec.kind, + clockRate: matchingLocalCodec.clockRate, + channels: matchingLocalCodec.channels, + localPayloadType: matchingLocalCodec.preferredPayloadType, + localRtxPayloadType: undefined, + remotePayloadType: remoteCodec.preferredPayloadType, + remoteRtxPayloadType: undefined, + localParameters: matchingLocalCodec.parameters, + remoteParameters: remoteCodec.parameters, + rtcpFeedback: reduceRtcpFeedback(matchingLocalCodec, remoteCodec) + }; + extendedRtpCapabilities.codecs.push(extendedCodec); + } + // Match RTX codecs. + for (const extendedCodec of extendedRtpCapabilities.codecs) { + const matchingLocalRtxCodec = localCaps.codecs + .find((localCodec) => (isRtxCodec(localCodec) && + localCodec.parameters.apt === extendedCodec.localPayloadType)); + const matchingRemoteRtxCodec = remoteCaps.codecs + .find((remoteCodec) => (isRtxCodec(remoteCodec) && + remoteCodec.parameters.apt === extendedCodec.remotePayloadType)); + if (matchingLocalRtxCodec && matchingRemoteRtxCodec) { + extendedCodec.localRtxPayloadType = matchingLocalRtxCodec.preferredPayloadType; + extendedCodec.remoteRtxPayloadType = matchingRemoteRtxCodec.preferredPayloadType; + } + } + // Match header extensions. + for (const remoteExt of remoteCaps.headerExtensions) { + const matchingLocalExt = localCaps.headerExtensions + .find((localExt) => (matchHeaderExtensions(localExt, remoteExt))); + if (!matchingLocalExt) { + continue; + } + const extendedExt = { + kind: remoteExt.kind, + uri: remoteExt.uri, + sendId: matchingLocalExt.preferredId, + recvId: remoteExt.preferredId, + encrypt: matchingLocalExt.preferredEncrypt, + direction: 'sendrecv' + }; + switch (remoteExt.direction) { + case 'sendrecv': + extendedExt.direction = 'sendrecv'; + break; + case 'recvonly': + extendedExt.direction = 'sendonly'; + break; + case 'sendonly': + extendedExt.direction = 'recvonly'; + break; + case 'inactive': + extendedExt.direction = 'inactive'; + break; + } + extendedRtpCapabilities.headerExtensions.push(extendedExt); + } + return extendedRtpCapabilities; +} +exports.getExtendedRtpCapabilities = getExtendedRtpCapabilities; +/** + * Generate RTP capabilities for receiving media based on the given extended + * RTP capabilities. + */ +function getRecvRtpCapabilities(extendedRtpCapabilities) { + const rtpCapabilities = { + codecs: [], + headerExtensions: [] + }; + for (const extendedCodec of extendedRtpCapabilities.codecs) { + const codec = { + mimeType: extendedCodec.mimeType, + kind: extendedCodec.kind, + preferredPayloadType: extendedCodec.remotePayloadType, + clockRate: extendedCodec.clockRate, + channels: extendedCodec.channels, + parameters: extendedCodec.localParameters, + rtcpFeedback: extendedCodec.rtcpFeedback + }; + rtpCapabilities.codecs.push(codec); + // Add RTX codec. + if (!extendedCodec.remoteRtxPayloadType) { + continue; + } + const rtxCodec = { + mimeType: `${extendedCodec.kind}/rtx`, + kind: extendedCodec.kind, + preferredPayloadType: extendedCodec.remoteRtxPayloadType, + clockRate: extendedCodec.clockRate, + parameters: { + apt: extendedCodec.remotePayloadType + }, + rtcpFeedback: [] + }; + rtpCapabilities.codecs.push(rtxCodec); + // TODO: In the future, we need to add FEC, CN, etc, codecs. + } + for (const extendedExtension of extendedRtpCapabilities.headerExtensions) { + // Ignore RTP extensions not valid for receiving. + if (extendedExtension.direction !== 'sendrecv' && + extendedExtension.direction !== 'recvonly') { + continue; + } + const ext = { + kind: extendedExtension.kind, + uri: extendedExtension.uri, + preferredId: extendedExtension.recvId, + preferredEncrypt: extendedExtension.encrypt, + direction: extendedExtension.direction + }; + rtpCapabilities.headerExtensions.push(ext); + } + return rtpCapabilities; +} +exports.getRecvRtpCapabilities = getRecvRtpCapabilities; +/** + * Generate RTP parameters of the given kind for sending media. + * NOTE: mid, encodings and rtcp fields are left empty. + */ +function getSendingRtpParameters(kind, extendedRtpCapabilities) { + const rtpParameters = { + mid: undefined, + codecs: [], + headerExtensions: [], + encodings: [], + rtcp: {} + }; + for (const extendedCodec of extendedRtpCapabilities.codecs) { + if (extendedCodec.kind !== kind) { + continue; + } + const codec = { + mimeType: extendedCodec.mimeType, + payloadType: extendedCodec.localPayloadType, + clockRate: extendedCodec.clockRate, + channels: extendedCodec.channels, + parameters: extendedCodec.localParameters, + rtcpFeedback: extendedCodec.rtcpFeedback + }; + rtpParameters.codecs.push(codec); + // Add RTX codec. + if (extendedCodec.localRtxPayloadType) { + const rtxCodec = { + mimeType: `${extendedCodec.kind}/rtx`, + payloadType: extendedCodec.localRtxPayloadType, + clockRate: extendedCodec.clockRate, + parameters: { + apt: extendedCodec.localPayloadType + }, + rtcpFeedback: [] + }; + rtpParameters.codecs.push(rtxCodec); + } + } + for (const extendedExtension of extendedRtpCapabilities.headerExtensions) { + // Ignore RTP extensions of a different kind and those not valid for sending. + if ((extendedExtension.kind && extendedExtension.kind !== kind) || + (extendedExtension.direction !== 'sendrecv' && + extendedExtension.direction !== 'sendonly')) { + continue; + } + const ext = { + uri: extendedExtension.uri, + id: extendedExtension.sendId, + encrypt: extendedExtension.encrypt, + parameters: {} + }; + rtpParameters.headerExtensions.push(ext); + } + return rtpParameters; +} +exports.getSendingRtpParameters = getSendingRtpParameters; +/** + * Generate RTP parameters of the given kind suitable for the remote SDP answer. + */ +function getSendingRemoteRtpParameters(kind, extendedRtpCapabilities) { + const rtpParameters = { + mid: undefined, + codecs: [], + headerExtensions: [], + encodings: [], + rtcp: {} + }; + for (const extendedCodec of extendedRtpCapabilities.codecs) { + if (extendedCodec.kind !== kind) { + continue; + } + const codec = { + mimeType: extendedCodec.mimeType, + payloadType: extendedCodec.localPayloadType, + clockRate: extendedCodec.clockRate, + channels: extendedCodec.channels, + parameters: extendedCodec.remoteParameters, + rtcpFeedback: extendedCodec.rtcpFeedback + }; + rtpParameters.codecs.push(codec); + // Add RTX codec. + if (extendedCodec.localRtxPayloadType) { + const rtxCodec = { + mimeType: `${extendedCodec.kind}/rtx`, + payloadType: extendedCodec.localRtxPayloadType, + clockRate: extendedCodec.clockRate, + parameters: { + apt: extendedCodec.localPayloadType + }, + rtcpFeedback: [] + }; + rtpParameters.codecs.push(rtxCodec); + } + } + for (const extendedExtension of extendedRtpCapabilities.headerExtensions) { + // Ignore RTP extensions of a different kind and those not valid for sending. + if ((extendedExtension.kind && extendedExtension.kind !== kind) || + (extendedExtension.direction !== 'sendrecv' && + extendedExtension.direction !== 'sendonly')) { + continue; + } + const ext = { + uri: extendedExtension.uri, + id: extendedExtension.sendId, + encrypt: extendedExtension.encrypt, + parameters: {} + }; + rtpParameters.headerExtensions.push(ext); + } + // Reduce codecs' RTCP feedback. Use Transport-CC if available, REMB otherwise. + if (rtpParameters.headerExtensions.some((ext) => (ext.uri === 'http://www.ietf.org/id/draft-holmer-rmcat-transport-wide-cc-extensions-01'))) { + for (const codec of rtpParameters.codecs) { + codec.rtcpFeedback = (codec.rtcpFeedback || []) + .filter((fb) => fb.type !== 'goog-remb'); + } + } + else if (rtpParameters.headerExtensions.some((ext) => (ext.uri === 'http://www.webrtc.org/experiments/rtp-hdrext/abs-send-time'))) { + for (const codec of rtpParameters.codecs) { + codec.rtcpFeedback = (codec.rtcpFeedback || []) + .filter((fb) => fb.type !== 'transport-cc'); + } + } + else { + for (const codec of rtpParameters.codecs) { + codec.rtcpFeedback = (codec.rtcpFeedback || []) + .filter((fb) => (fb.type !== 'transport-cc' && + fb.type !== 'goog-remb')); + } + } + return rtpParameters; +} +exports.getSendingRemoteRtpParameters = getSendingRemoteRtpParameters; +/** + * Reduce given codecs by returning an array of codecs "compatible" with the + * given capability codec. If no capability codec is given, take the first + * one(s). + * + * Given codecs must be generated by ortc.getSendingRtpParameters() or + * ortc.getSendingRemoteRtpParameters(). + * + * The returned array of codecs also include a RTX codec if available. + */ +function reduceCodecs(codecs, capCodec) { + const filteredCodecs = []; + // If no capability codec is given, take the first one (and RTX). + if (!capCodec) { + filteredCodecs.push(codecs[0]); + if (isRtxCodec(codecs[1])) { + filteredCodecs.push(codecs[1]); + } + } + // Otherwise look for a compatible set of codecs. + else { + for (let idx = 0; idx < codecs.length; ++idx) { + if (matchCodecs(codecs[idx], capCodec)) { + filteredCodecs.push(codecs[idx]); + if (isRtxCodec(codecs[idx + 1])) { + filteredCodecs.push(codecs[idx + 1]); + } + break; + } + } + if (filteredCodecs.length === 0) { + throw new TypeError('no matching codec found'); + } + } + return filteredCodecs; +} +exports.reduceCodecs = reduceCodecs; +/** + * Create RTP parameters for a Consumer for the RTP probator. + */ +function generateProbatorRtpParameters(videoRtpParameters) { + // Clone given reference video RTP parameters. + videoRtpParameters = utils.clone(videoRtpParameters, {}); + // This may throw. + validateRtpParameters(videoRtpParameters); + const rtpParameters = { + mid: RTP_PROBATOR_MID, + codecs: [], + headerExtensions: [], + encodings: [{ ssrc: RTP_PROBATOR_SSRC }], + rtcp: { cname: 'probator' } + }; + rtpParameters.codecs.push(videoRtpParameters.codecs[0]); + rtpParameters.codecs[0].payloadType = RTP_PROBATOR_CODEC_PAYLOAD_TYPE; + rtpParameters.headerExtensions = videoRtpParameters.headerExtensions; + return rtpParameters; +} +exports.generateProbatorRtpParameters = generateProbatorRtpParameters; +/** + * Whether media can be sent based on the given RTP capabilities. + */ +function canSend(kind, extendedRtpCapabilities) { + return extendedRtpCapabilities.codecs. + some((codec) => codec.kind === kind); +} +exports.canSend = canSend; +/** + * Whether the given RTP parameters can be received with the given RTP + * capabilities. + */ +function canReceive(rtpParameters, extendedRtpCapabilities) { + // This may throw. + validateRtpParameters(rtpParameters); + if (rtpParameters.codecs.length === 0) { + return false; + } + const firstMediaCodec = rtpParameters.codecs[0]; + return extendedRtpCapabilities.codecs + .some((codec) => codec.remotePayloadType === firstMediaCodec.payloadType); +} +exports.canReceive = canReceive; +function isRtxCodec(codec) { + if (!codec) { + return false; + } + return /.+\/rtx$/i.test(codec.mimeType); +} +function matchCodecs(aCodec, bCodec, { strict = false, modify = false } = {}) { + const aMimeType = aCodec.mimeType.toLowerCase(); + const bMimeType = bCodec.mimeType.toLowerCase(); + if (aMimeType !== bMimeType) { + return false; + } + if (aCodec.clockRate !== bCodec.clockRate) { + return false; + } + if (aCodec.channels !== bCodec.channels) { + return false; + } + // Per codec special checks. + switch (aMimeType) { + case 'video/h264': + { + if (strict) { + const aPacketizationMode = aCodec.parameters['packetization-mode'] || 0; + const bPacketizationMode = bCodec.parameters['packetization-mode'] || 0; + if (aPacketizationMode !== bPacketizationMode) { + return false; + } + if (!h264.isSameProfile(aCodec.parameters, bCodec.parameters)) { + return false; + } + let selectedProfileLevelId; + try { + selectedProfileLevelId = + h264.generateProfileLevelIdForAnswer(aCodec.parameters, bCodec.parameters); + } + catch (error) { + return false; + } + if (modify) { + if (selectedProfileLevelId) { + aCodec.parameters['profile-level-id'] = selectedProfileLevelId; + bCodec.parameters['profile-level-id'] = selectedProfileLevelId; + } + else { + delete aCodec.parameters['profile-level-id']; + delete bCodec.parameters['profile-level-id']; + } + } + } + break; + } + case 'video/vp9': + { + if (strict) { + const aProfileId = aCodec.parameters['profile-id'] || 0; + const bProfileId = bCodec.parameters['profile-id'] || 0; + if (aProfileId !== bProfileId) { + return false; + } + } + break; + } + } + return true; +} +function matchHeaderExtensions(aExt, bExt) { + if (aExt.kind && bExt.kind && aExt.kind !== bExt.kind) { + return false; + } + if (aExt.uri !== bExt.uri) { + return false; + } + return true; +} +function reduceRtcpFeedback(codecA, codecB) { + const reducedRtcpFeedback = []; + for (const aFb of codecA.rtcpFeedback || []) { + const matchingBFb = (codecB.rtcpFeedback || []) + .find((bFb) => (bFb.type === aFb.type && + (bFb.parameter === aFb.parameter || (!bFb.parameter && !aFb.parameter)))); + if (matchingBFb) { + reducedRtcpFeedback.push(matchingBFb); + } + } + return reducedRtcpFeedback; +} - /** - * XXX DO NOT USE. This is a temporary stub function. - * XXX It WILL be removed in the next major release. - */ - function destroy() { - console.warn('Instance method `debug.destroy()` is deprecated and no longer does anything. It will be removed in the next major version of `debug`.'); - } +},{"./utils":46,"h264-profile-level-id":8}],44:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.parse = void 0; +const ScalabilityModeRegex = new RegExp('^[LS]([1-9]\\d{0,1})T([1-9]\\d{0,1})'); +function parse(scalabilityMode) { + const match = ScalabilityModeRegex.exec(scalabilityMode || ''); + if (match) { + return { + spatialLayers: Number(match[1]), + temporalLayers: Number(match[2]) + }; + } + else { + return { + spatialLayers: 1, + temporalLayers: 1 + }; + } +} +exports.parse = parse; - createDebug.enable(createDebug.load()); +},{}],45:[function(require,module,exports){ +"use strict"; +var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + var desc = Object.getOwnPropertyDescriptor(m, k); + if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { + desc = { enumerable: true, get: function() { return m[k]; } }; + } + Object.defineProperty(o, k2, desc); +}) : (function(o, m, k, k2) { + if (k2 === undefined) k2 = k; + o[k2] = m[k]; +})); +var __exportStar = (this && this.__exportStar) || function(m, exports) { + for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); +}; +Object.defineProperty(exports, "__esModule", { value: true }); +__exportStar(require("./Device"), exports); +__exportStar(require("./Transport"), exports); +__exportStar(require("./Producer"), exports); +__exportStar(require("./Consumer"), exports); +__exportStar(require("./DataProducer"), exports); +__exportStar(require("./DataConsumer"), exports); +__exportStar(require("./RtpParameters"), exports); +__exportStar(require("./SctpParameters"), exports); +__exportStar(require("./handlers/HandlerInterface"), exports); +__exportStar(require("./errors"), exports); - return createDebug; +},{"./Consumer":12,"./DataConsumer":13,"./DataProducer":14,"./Device":15,"./Producer":18,"./RtpParameters":19,"./SctpParameters":20,"./Transport":21,"./errors":22,"./handlers/HandlerInterface":30}],46:[function(require,module,exports){ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.generateRandomNumber = exports.clone = void 0; +/** + * Clones the given data. + */ +function clone(data, defaultValue) { + if (typeof data === 'undefined') { + return defaultValue; + } + return JSON.parse(JSON.stringify(data)); } +exports.clone = clone; +/** + * Generates a random positive integer. + */ +function generateRandomNumber() { + return Math.round(Math.random() * 10000000); +} +exports.generateRandomNumber = generateRandomNumber; -module.exports = setup; - -},{"ms":42}],42:[function(require,module,exports){ -arguments[4][7][0].apply(exports,arguments) -},{"dup":7}],43:[function(require,module,exports){ +},{}],47:[function(require,module,exports){ +arguments[4][4][0].apply(exports,arguments) +},{"./common":48,"_process":56,"dup":4}],48:[function(require,module,exports){ +arguments[4][5][0].apply(exports,arguments) +},{"dup":5,"ms":49}],49:[function(require,module,exports){ +arguments[4][6][0].apply(exports,arguments) +},{"dup":6}],50:[function(require,module,exports){ +(function (global){(function (){ +/*! queue-microtask. MIT License. Feross Aboukhadijeh */ +let promise + +module.exports = typeof queueMicrotask === 'function' + ? queueMicrotask.bind(typeof window !== 'undefined' ? window : global) + // reuse resolved promise, and allocate it lazily + : cb => (promise || (promise = Promise.resolve())) + .then(cb) + .catch(err => setTimeout(() => { throw err }, 0)) + +}).call(this)}).call(this,typeof global !== "undefined" ? global : typeof self !== "undefined" ? self : typeof window !== "undefined" ? window : {}) +},{}],51:[function(require,module,exports){ var grammar = module.exports = { v: [{ name: 'version', @@ -11313,7 +13494,7 @@ Object.keys(grammar).forEach(function (key) { }); }); -},{}],44:[function(require,module,exports){ +},{}],52:[function(require,module,exports){ var parser = require('./parser'); var writer = require('./writer'); @@ -11326,7 +13507,7 @@ exports.parseRemoteCandidates = parser.parseRemoteCandidates; exports.parseImageAttributes = parser.parseImageAttributes; exports.parseSimulcastStreamList = parser.parseSimulcastStreamList; -},{"./parser":45,"./writer":46}],45:[function(require,module,exports){ +},{"./parser":53,"./writer":54}],53:[function(require,module,exports){ var toIntIfInt = function (v) { return String(Number(v)) === v ? Number(v) : v; }; @@ -11452,7 +13633,7 @@ exports.parseSimulcastStreamList = function (str) { }); }; -},{"./grammar":43}],46:[function(require,module,exports){ +},{"./grammar":51}],54:[function(require,module,exports){ var grammar = require('./grammar'); // customized util.format - discards excess arguments and can void middle ones @@ -11568,7 +13749,7 @@ module.exports = function (session, opts) { return sdp.join('\r\n') + '\r\n'; }; -},{"./grammar":43}],47:[function(require,module,exports){ +},{"./grammar":51}],55:[function(require,module,exports){ // Copyright Joyent, Inc. and other Node contributors. // // Permission is hereby granted, free of charge, to any person obtaining a @@ -12067,7 +14248,7 @@ function eventTargetAgnosticAddListener(emitter, name, listener, flags) { } } -},{}],48:[function(require,module,exports){ +},{}],56:[function(require,module,exports){ // shim for using process in browser var process = module.exports = {}; diff --git a/src/Peer.js b/src/Peer.js index 10c0199..50d4e12 100644 --- a/src/Peer.js +++ b/src/Peer.js @@ -57,10 +57,10 @@ module.exports = class Peer { if (consumer.type === 'simulcast') { await consumer.setPreferredLayers({ - spatialLayer: 2, - temporalLayer: 2 + spatialLayer: 3, + temporalLayer: 3 }) - } + } //https://www.w3.org/TR/webrtc-svc/ this.consumers.set(consumer.id, consumer) diff --git a/src/app.js b/src/app.js index 0c3023e..a5ec340 100644 --- a/src/app.js +++ b/src/app.js @@ -4,6 +4,7 @@ const app = express() const https = require('httpolyglot') const fs = require('fs') const mediasoup = require('mediasoup') +const mediasoupClient = require('mediasoup-client') const config = require('./config') const path = require('path') const Room = require('./Room') @@ -20,7 +21,12 @@ const io = require('socket.io')(httpsServer) app.use(express.static(path.join(__dirname, '..', 'public'))) httpsServer.listen(config.listenPort, () => { - console.log('Listening on https://' + 'localhost' + ':' + config.listenPort) + console.log('Server', { + listening: 'https://' + 'localhost' + ':' + config.listenPort, + mediasoup_server: mediasoup.version, + mediasoup_client: mediasoupClient.version, + node_version: process.versions.node, + }) }) // all mediasoup workers From c4a848790f90b2e55fea8decd997339db50f2950 Mon Sep 17 00:00:00 2001 From: Miroslav Pejic Date: Wed, 20 Mar 2024 18:32:29 +0100 Subject: [PATCH 8/8] fix scalabilityMode --- public/RoomClient.js | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/public/RoomClient.js b/public/RoomClient.js index ab9a88a..fab976f 100644 --- a/public/RoomClient.js +++ b/public/RoomClient.js @@ -331,17 +331,17 @@ class RoomClient { rid: 'r0', maxBitrate: 100000, //scaleResolutionDownBy: 10.0, - scalabilityMode: 'S1T3' + scalabilityMode: 'S3T3' }, { rid: 'r1', maxBitrate: 300000, - scalabilityMode: 'S1T3' + scalabilityMode: 'S3T3' }, { rid: 'r2', maxBitrate: 900000, - scalabilityMode: 'S1T3' + scalabilityMode: 'S3T3' } ] params.codecOptions = {