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/.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
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 0c15b88..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.1",
+ "express": "^4.18.2",
"httpolyglot": "^0.1.2",
- "mediasoup": "^3.8.2",
- "mediasoup-client": "^3.6.37",
- "socket.io": "^4.1.3"
+ "mediasoup": "^3.11.11",
+ "mediasoup-client": "^3.6.82",
+ "socket.io": "^4.6.1"
},
"devDependencies": {
- "prettier": "2.3.2"
+ "prettier": "2.8.4"
}
}
diff --git a/public/RoomClient.js b/public/RoomClient.js
index 29d11cd..9a69a9e 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) => {
@@ -290,9 +292,6 @@ class RoomClient {
ideal: 1080
},
deviceId: deviceId
- /*aspectRatio: {
- ideal: 1.7777777778
- }*/
}
}
break
@@ -328,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 = {
@@ -521,6 +519,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)
@@ -616,9 +619,10 @@ class RoomClient {
document.body.appendChild(tmpInput)
tmpInput.value = window.location.href
tmpInput.select()
- document.execCommand('copy')
+ tmpInput.setSelectionRange(0, 99999) // For mobile devices
+ navigator.clipboard.writeText(tmpInput.value)
document.body.removeChild(tmpInput)
- console.log('URL copied to clipboard 👍')
+ alert('ROOM URL copied to clipboard 👍')
}
showDevices() {
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 @@
Audio:
-
+
Video:
-
+
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
diff --git a/public/modules/mediasoupclient.min.js b/public/modules/mediasoupclient.min.js
index 7b8efd0..02ad4e7 100644
--- a/public/modules/mediasoupclient.min.js
+++ b/public/modules/mediasoupclient.min.js
@@ -2,1179 +2,790 @@
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.formatArgs = formatArgs;
-exports.save = save;
-exports.load = load;
-exports.useColors = useColors;
-exports.storage = localstorage();
-exports.destroy = (() => {
- let warned = false;
+ args[0] = createDebug.coerce(args[0]);
- 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`.');
+ 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);
}
- };
-})();
-/**
- * Colors.
- */
+ 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.
-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'
-];
+ 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);
+ }
-/**
- * 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 enabledCache;
+ },
+ set: v => {
+ enableOverride = v;
+ }
+ });
-// 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;
+ // Env-specific initialization logic for debug instances
+ if (typeof createDebug.init === 'function') {
+ createDebug.init(debug);
+ }
+
+ return debug;
}
- // Internet Explorer and Edge do not support colors.
- if (typeof navigator !== 'undefined' && navigator.userAgent && navigator.userAgent.toLowerCase().match(/(edge|trident)\/(\d+)/)) {
- return false;
+ function extend(namespace, delimiter) {
+ const newDebug = createDebug(this.namespace + (typeof delimiter === 'undefined' ? ':' : delimiter) + namespace);
+ newDebug.log = this.log;
+ return newDebug;
}
- // 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+)/));
-}
+ /**
+ * 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;
-/**
- * Colorize log arguments if enabled.
- *
- * @api public
- */
+ createDebug.names = [];
+ createDebug.skips = [];
-function formatArgs(args) {
- args[0] = (this.useColors ? '%c' : '') +
- this.namespace +
- (this.useColors ? ' %c' : ' ') +
- args[0] +
- (this.useColors ? '%c ' : ' ') +
- '+' + module.exports.humanize(this.diff);
+ let i;
+ const split = (typeof namespaces === 'string' ? namespaces : '').split(/[\s,]+/);
+ const len = split.length;
- if (!this.useColors) {
- return;
- }
+ for (i = 0; i < len; i++) {
+ if (!split[i]) {
+ // ignore empty strings
+ continue;
+ }
- const c = 'color: ' + this.color;
- args.splice(1, 0, c, 'color: inherit');
+ namespaces = split[i].replace(/\*/g, '.*?');
- // 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;
+ if (namespaces[0] === '-') {
+ createDebug.skips.push(new RegExp('^' + namespaces.slice(1) + '$'));
+ } else {
+ createDebug.names.push(new RegExp('^' + namespaces + '$'));
+ }
}
- 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;
+ /**
+ * 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;
}
- 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?
- }
-}
+ /**
+ * 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;
+ }
-module.exports = require('./common')(exports);
+ let i;
+ let len;
-const {formatters} = module.exports;
+ for (i = 0, len = createDebug.skips.length; i < len; i++) {
+ if (createDebug.skips[i].test(name)) {
+ return false;
+ }
+ }
-/**
- * Map %j to `JSON.stringify()`, since no Web Inspectors do that by default.
- */
+ for (i = 0, len = createDebug.names.length; i < len; i++) {
+ if (createDebug.names[i].test(name)) {
+ return true;
+ }
+ }
-formatters.j = function (v) {
- try {
- return JSON.stringify(v);
- } catch (error) {
- return '[UnexpectedJSONParseError]: ' + error.message;
+ return false;
}
-};
-
-}).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');
- 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.
+ * Convert regexp to namespace
*
- * Valid key names are a single, lower or upper-case letter, i.e. "n" and "N".
+ * @param {RegExp} regxep
+ * @return {String} namespace
+ * @api private
*/
- createDebug.formatters = {};
+ function toNamespace(regexp) {
+ return regexp.toString()
+ .substring(2, regexp.toString().length - 2)
+ .replace(/\.\*\?$/, '*');
+ }
/**
- * 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
+ * Coerce `val`.
+ *
+ * @param {Mixed} val
+ * @return {Mixed}
* @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
+ function coerce(val) {
+ if (val instanceof Error) {
+ return val.stack || val.message;
}
-
- return createDebug.colors[Math.abs(hash) % createDebug.colors.length];
+ return val;
}
- createDebug.selectColor = selectColor;
/**
- * Create a debugger with the given `namespace`.
- *
- * @param {String} namespace
- * @return {Function}
- * @api public
+ * XXX DO NOT USE. This is a temporary stub function.
+ * XXX It WILL be removed in the next major release.
*/
- 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;
+ 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`.');
+ }
- args[0] = createDebug.coerce(args[0]);
+ createDebug.enable(createDebug.load());
- if (typeof args[0] !== 'string') {
- // Anything else let's inspect with %O
- args.unshift('%O');
- }
+ return createDebug;
+}
- // 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);
+module.exports = setup;
- // Now we need to remove `args[index]` since it's inlined in the `format`
- args.splice(index, 1);
- index--;
- }
- return match;
- });
+},{"ms":6}],6:[function(require,module,exports){
+/**
+ * Helpers.
+ */
- // 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":7}],7:[function(require,module,exports){
-/**
- * Helpers.
- */
-
-var s = 1000;
-var m = s * 60;
-var h = m * 60;
-var d = h * 24;
-var w = d * 7;
-var y = d * 365.25;
+var s = 1000;
+var m = s * 60;
+var h = m * 60;
+var d = h * 24;
+var w = d * 7;
+var y = d * 365.25;
/**
* Parse or format the given `val`.
@@ -1328,95 +939,1644 @@ 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
- */
- 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.
+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);
+ }
+
+ isMatch(value)
+ {
+ return this._maskedValue === (value & this._mask);
+ }
+}
+
+// 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;
+ }
+}
+
+// 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;
+};
+
+/**
+ * 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}`;
+};
+
+/**
+ * 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)
+ );
+}
+
+// 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();
+ }
+ /**
+ * 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.
+ */
+ // 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 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 new Promise((resolve, reject) => {
+ this.safeEmit('@getstats', resolve, reject);
+ });
+ }
+ /**
+ * Pauses receiving media.
+ */
+ pause() {
+ logger.debug('pause()');
+ if (this._closed) {
+ logger.error('pause() | Consumer closed');
+ return;
+ }
+ if (this._paused) {
+ logger.debug('pause() | Consumer is already paused');
+ 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;
+ }
+ 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');
+ }
+ 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) { }
+ }
+}
+exports.Consumer = Consumer;
+
+},{"./EnhancedEventEmitter":16,"./Logger":17,"./errors":22}],13:[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 {
+ 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.
+ */
+ // 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 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');
+ // Emit observer event.
+ this._observer.safeEmit('close');
+ });
+ this._dataChannel.addEventListener('message', (event) => {
+ if (this._closed) {
+ return;
+ }
+ this.safeEmit('message', event.data);
+ });
+ }
+}
+exports.DataConsumer = DataConsumer;
+
+},{"./EnhancedEventEmitter":16,"./Logger":17}],14:[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 {
+ 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.
+ */
+ // 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 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');
+ // Emit observer event.
+ 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');
+ });
+ }
+}
+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]; } };
+ }
+ 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.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;
+ }
+ if (typeof RTCRtpTransceiver !== 'undefined') {
+ logger.debug('this._detectDevice() | ReactNative UnifiedPlan handler chosen');
+ return 'ReactNativeUnifiedPlan';
+ }
+ else {
+ logger.debug('this._detectDevice() | ReactNative PlanB 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: '>=111', chromium: '>=111', 'microsoft edge': '>=111' })) {
+ return 'Chrome111';
+ }
+ else 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 >= 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';
+ }
+ }
+ // 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;
+ }
+}
+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 '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;
+ }
+ /**
+ * The RTC handler name.
+ */
+ get handlerName() {
+ return this._handlerName;
+ }
+ /**
+ * Whether the Device is loaded.
+ */
+ get loaded() {
+ return this._loaded;
+ }
+ /**
+ * 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;
+ }
+ /**
+ * SCTP capabilities of the Device.
+ *
+ * @throws {InvalidStateError} if not loaded.
+ */
+ get sctpCapabilities() {
+ if (!this._loaded) {
+ throw new errors_1.InvalidStateError('not loaded');
+ }
+ return this._sctpCapabilities;
+ }
+ get observer() {
+ return this._observer;
+ }
+ /**
+ * 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;
+ }
+ }
+ /**
+ * 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.Device = Device;
+
+},{"./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";
+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);
+ }
+ emit(eventName, ...args) {
+ return super.emit(eventName, ...args);
+ }
+ /**
+ * Special addition to the EventEmitter API.
+ */
+ safeEmit(eventName, ...args) {
+ const numListeners = super.listenerCount(eventName);
+ try {
+ return super.emit(eventName, ...args);
+ }
+ catch (error) {
+ logger.error('safeEmit() | event listener threw an error [eventName:%s]:%o', eventName, error);
+ return Boolean(numListeners);
+ }
+ }
+ on(eventName, listener) {
+ super.on(eventName, listener);
+ return this;
+ }
+ off(eventName, listener) {
+ super.off(eventName, listener);
+ return this;
+ }
+ addListener(eventName, listener) {
+ super.on(eventName, listener);
+ return this;
+ }
+ prependListener(eventName, listener) {
+ super.prependListener(eventName, listener);
+ return this;
+ }
+ once(eventName, listener) {
+ super.once(eventName, listener);
+ return this;
+ }
+ prependOnceListener(eventName, listener) {
+ super.prependOnceListener(eventName, listener);
+ return this;
+ }
+ removeListener(eventName, listener) {
+ super.off(eventName, listener);
+ return this;
+ }
+ removeAllListeners(eventName) {
+ super.removeAllListeners(eventName);
+ return this;
+ }
+ listenerCount(eventName) {
+ return super.listenerCount(eventName);
+ }
+ listeners(eventName) {
+ return super.listeners(eventName);
+ }
+ rawListeners(eventName) {
+ return super.rawListeners(eventName);
+ }
+}
+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}`);
+ }
+ 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`);
+ }
+ /* 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":47}],18:[function(require,module,exports){
+"use strict";
+Object.defineProperty(exports, "__esModule", { value: true });
+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();
+ // 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();
+ }
+ /**
+ * Producer id.
+ */
+ get id() {
+ return this._id;
+ }
+ /**
+ * 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;
@@ -1424,29 +2584,23 @@ class Consumer 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 Consumer.
+ * Closes the Producer.
*/
close() {
- if (this._closed)
+ if (this._closed) {
return;
+ }
logger.debug('close()');
this._closed = true;
- this._destroyTrack();
+ this.destroyTrack();
this.emit('@close');
// Emit observer event.
this._observer.safeEmit('close');
@@ -1455,153 +2609,332 @@ class Consumer extends EnhancedEventEmitter_1.EnhancedEventEmitter {
* Transport was closed.
*/
transportClosed() {
- if (this._closed)
+ if (this._closed) {
return;
+ }
logger.debug('transportClosed()');
this._closed = true;
- this._destroyTrack();
+ this.destroyTrack();
this.safeEmit('transportclose');
// Emit observer event.
this._observer.safeEmit('close');
}
/**
- * Get associated RTCRtpReceiver stats.
+ * Get associated RTCRtpSender stats.
*/
async getStats() {
- if (this._closed)
+ if (this._closed) {
throw new errors_1.InvalidStateError('closed');
- return this.safeEmitAsPromise('@getstats');
+ }
+ return new Promise((resolve, reject) => {
+ this.safeEmit('@getstats', resolve, reject);
+ });
}
/**
- * Pauses receiving media.
+ * Pauses sending media.
*/
pause() {
logger.debug('pause()');
if (this._closed) {
- logger.error('pause() | Consumer closed');
+ logger.error('pause() | Producer closed');
return;
}
this._paused = true;
- this._track.enabled = false;
+ if (this._track && this._disableTrackOnPause) {
+ this._track.enabled = false;
+ }
+ if (this._zeroRtpOnPause) {
+ new Promise((resolve, reject) => {
+ this.safeEmit('@pause', resolve, reject);
+ }).catch(() => { });
+ }
// Emit observer event.
this._observer.safeEmit('pause');
}
/**
- * Resumes receiving media.
+ * Resumes sending media.
*/
resume() {
logger.debug('resume()');
if (this._closed) {
- logger.error('resume() | Consumer closed');
+ logger.error('resume() | Producer closed');
return;
}
this._paused = false;
- this._track.enabled = true;
+ 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');
}
- _onTrackEnded() {
+ /**
+ * 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');
+ }
+ 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;
+ }
+ 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;
+ }
+ 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 new Promise((resolve, reject) => {
+ this.safeEmit('@setmaxspatiallayer', spatialLayer, resolve, reject);
+ }).catch(() => { });
+ this._maxSpatialLayer = spatialLayer;
+ }
+ async setRtpEncodingParameters(params) {
+ if (this._closed) {
+ throw new errors_1.InvalidStateError('closed');
+ }
+ else if (typeof params !== 'object') {
+ throw new TypeError('invalid params');
+ }
+ await new Promise((resolve, reject) => {
+ this.safeEmit('@setrtpencodingparameters', params, resolve, reject);
+ });
+ }
+ onTrackEnded() {
logger.debug('track "ended" event');
this.safeEmit('trackended');
// Emit observer event.
this._observer.safeEmit('trackended');
}
- _handleTrack() {
- this._track.addEventListener('ended', this._onTrackEnded);
+ handleTrack() {
+ if (!this._track) {
+ return;
+ }
+ this._track.addEventListener('ended', this.onTrackEnded);
}
- _destroyTrack() {
+ destroyTrack() {
+ if (!this._track) {
+ return;
+ }
try {
- this._track.removeEventListener('ended', this._onTrackEnded);
- this._track.stop();
+ 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.Consumer = Consumer;
+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 });
-},{"./EnhancedEventEmitter":12,"./Logger":13,"./errors":18}],9:[function(require,module,exports){
+},{}],20:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
-exports.DataConsumer = void 0;
+
+},{}],21:[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.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 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 }) {
+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()');
+ logger.debug('constructor() [id:%s, direction:%s]', id, direction);
this._id = id;
- this._dataProducerId = dataProducerId;
- this._dataChannel = dataChannel;
- this._sctpStreamParameters = sctpStreamParameters;
- this._appData = appData;
- this._handleDataChannel();
+ 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();
}
/**
- * DataConsumer id.
+ * Transport id.
*/
get id() {
return this._id;
}
/**
- * Associated DataProducer id.
- */
- get dataProducerId() {
- return this._dataProducerId;
- }
- /**
- * Whether the DataConsumer is closed.
+ * Whether the Transport 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.
+ * Transport direction.
*/
- get protocol() {
- return this._dataChannel.protocol;
+ get direction() {
+ return this._direction;
}
/**
- * DataChannel binaryType.
+ * RTC handler instance.
*/
- get binaryType() {
- return this._dataChannel.binaryType;
+ get handler() {
+ return this._handler;
}
/**
- * Set DataChannel binaryType.
+ * Connection state.
*/
- set binaryType(binaryType) {
- this._dataChannel.binaryType = binaryType;
+ get connectionState() {
+ return this._connectionState;
}
/**
* App custom data.
@@ -1612,1616 +2945,1925 @@ class DataConsumer 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
- */
get observer() {
return this._observer;
}
/**
- * Closes the DataConsumer.
+ * Close the Transport.
*/
close() {
- if (this._closed)
+ 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.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;
+ // Stop the AwaitQueue.
+ this._awaitQueue.stop();
+ // 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();
+ // Emit observer event.
+ this._observer.safeEmit('close');
}
/**
- * DataChannel bufferedAmountLowThreshold.
+ * Get associated Transport (RTCPeerConnection) stats.
+ *
+ * @returns {RTCStatsReport}
*/
- get bufferedAmountLowThreshold() {
- return this._dataChannel.bufferedAmountLowThreshold;
+ async getStats() {
+ if (this._closed) {
+ throw new errors_1.InvalidStateError('closed');
+ }
+ return this._handler.getTransportStats();
}
/**
- * Set DataChannel bufferedAmountLowThreshold.
+ * Restart ICE connection.
*/
- set bufferedAmountLowThreshold(bufferedAmountLowThreshold) {
- this._dataChannel.bufferedAmountLowThreshold = bufferedAmountLowThreshold;
+ 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()');
}
/**
- * App custom data.
+ * Update ICE servers.
*/
- get appData() {
- return this._appData;
+ async updateIceServers({ iceServers } = {}) {
+ logger.debug('updateIceServers()');
+ 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()');
}
/**
- * Invalid setter.
+ * Create a Producer.
*/
- set appData(appData) {
- throw new Error('cannot override appData object');
+ 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');
+ }
+ else 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;
+ }
+ 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;
+ });
}
/**
- * Observer.
- *
- * @emits close
+ * Create a Consumer to consume a remote Producer.
*/
- get observer() {
- return this._observer;
+ 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 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');
+ }
+ // 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;
}
/**
- * Closes the DataProducer.
+ * Create a 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');
+ async produceData({ ordered = true, maxPacketLifeTime, maxRetransmits, label = '', protocol = '', appData = {} } = {}) {
+ logger.debug('produceData()');
+ if (this._closed) {
+ throw new errors_1.InvalidStateError('closed');
+ }
+ else 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 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()');
}
/**
- * Transport was closed.
+ * Create a DataConsumer
*/
- transportClosed() {
- if (this._closed)
- return;
- logger.debug('transportClosed()');
- this._closed = true;
- this._dataChannel.close();
- this.safeEmit('transportclose');
- // Emit observer event.
- this._observer.safeEmit('close');
+ 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()');
}
- /**
- * 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);
+ // 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;
+ }
+ 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(() => { });
}
- _handleDataChannel() {
- this._dataChannel.addEventListener('open', () => {
- if (this._closed)
+ handleHandler() {
+ const handler = this._handler;
+ handler.on('@connect', ({ dtlsParameters }, callback, errback) => {
+ if (this._closed) {
+ errback(new errors_1.InvalidStateError('closed'));
return;
- logger.debug('DataChannel "open" event');
- this.safeEmit('open');
+ }
+ this.safeEmit('connect', { dtlsParameters }, callback, errback);
});
- this._dataChannel.addEventListener('error', (event) => {
- if (this._closed)
+ handler.on('@connectionstatechange', (connectionState) => {
+ if (connectionState === this._connectionState) {
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);
+ logger.debug('connection state changed to %s', connectionState);
+ this._connectionState = connectionState;
+ if (!this._closed) {
+ this.safeEmit('connectionstatechange', connectionState);
}
- this.safeEmit('error', error);
});
- this._dataChannel.addEventListener('close', () => {
- if (this._closed)
+ }
+ handleProducer(producer) {
+ producer.on('@close', () => {
+ this._producers.delete(producer.id);
+ if (this._closed) {
return;
- logger.warn('DataChannel "close" event');
- this._closed = true;
- this.emit('@close');
- this.safeEmit('close');
+ }
+ this._awaitQueue.push(async () => this._handler.stopSending(producer.localId), 'producer @close event')
+ .catch((error) => logger.warn('producer.close() failed:%o', error));
});
- this._dataChannel.addEventListener('message', () => {
- if (this._closed)
- return;
- logger.warn('DataChannel "message" event in a DataProducer, message discarded');
+ producer.on('@pause', (callback, errback) => {
+ this._awaitQueue.push(async () => this._handler.pauseSending(producer.localId), 'producer @pause event')
+ .then(callback)
+ .catch(errback);
});
- this._dataChannel.addEventListener('bufferedamountlow', () => {
- if (this._closed)
- return;
- this.safeEmit('bufferedamountlow');
+ 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);
});
}
-}
-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.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.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 and Chromium.
- if (browser.satisfies({ chrome: '>=74', chromium: '>=74' })) {
- 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';
- }
- // 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';
+ 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;
}
- else if (version >= 70) {
- return 'Chrome70';
+ if (this._consumerPauseInProgress === false) {
+ this.pausePendingConsumers();
}
- else if (version >= 67) {
- return 'Chrome67';
+ });
+ });
+ 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;
}
- else {
- return 'Chrome55';
+ if (this._consumerResumeInProgress === false) {
+ this.resumePendingConsumers();
}
- }
- else {
- return 'Chrome74';
- }
- }
- // Unsupported browser.
- else {
- logger.warn('this._detectDevice() | browser not supported [name:%s, version:%s]', browser.getBrowserName(), browser.getBrowserVersion());
- return undefined;
- }
+ });
+ });
+ consumer.on('@getstats', (callback, errback) => {
+ if (this._closed) {
+ return errback(new errors_1.InvalidStateError('closed'));
+ }
+ this._handler.getReceiverStats(consumer.localId)
+ .then(callback)
+ .catch(errback);
+ });
}
- // Unknown device.
- else {
- logger.warn('this._detectDevice() | unknown device');
- return undefined;
+ handleDataProducer(dataProducer) {
+ dataProducer.on('@close', () => {
+ this._dataProducers.delete(dataProducer.id);
+ });
+ }
+ handleDataConsumer(dataConsumer) {
+ dataConsumer.on('@close', () => {
+ this._dataConsumers.delete(dataConsumer.id);
+ });
}
}
-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');
+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);
}
- if (handlerName && handlerFactory) {
- throw new TypeError('just one of handlerName or handlerInterface can be given');
+ else {
+ this.stack = (new Error(message)).stack;
}
- if (handlerFactory) {
- this._handlerFactory = handlerFactory;
+ }
+}
+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 {
- 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}"`);
- }
+ this.stack = (new Error(message)).stack;
}
- // 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;
}
- /**
- * The RTC handler name.
- */
- get handlerName() {
- return this._handlerName;
+}
+exports.InvalidStateError = InvalidStateError;
+
+},{}],23:[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.Chrome111 = 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('Chrome111');
+const SCTP_NUM_STREAMS = { OS: 1024, MIS: 1024 };
+class Chrome111 extends HandlerInterface_1.HandlerInterface {
/**
- * Whether the Device is loaded.
+ * Creates a factory function.
*/
- get loaded() {
- return this._loaded;
+ static createFactory() {
+ return () => new Chrome111();
}
- /**
- * 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;
+ 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;
}
- /**
- * SCTP capabilities of the Device.
- *
- * @throws {InvalidStateError} if not loaded.
- */
- get sctpCapabilities() {
- if (!this._loaded)
- throw new errors_1.InvalidStateError('not loaded');
- return this._sctpCapabilities;
+ get name() {
+ return 'Chrome111';
}
- /**
- * Observer.
- *
- * @emits newtransport - (transport: Transport)
- */
- get observer() {
- return this._observer;
+ close() {
+ logger.debug('close()');
+ // Close RTCPeerConnection.
+ if (this._pc) {
+ try {
+ this._pc.close();
+ }
+ catch (error) { }
+ }
+ this.emit('@close');
}
- /**
- * 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;
+ async getNativeRtpCapabilities() {
+ logger.debug('getNativeRtpCapabilities()');
+ const pc = new RTCPeerConnection({
+ iceServers: [],
+ iceTransportPolicy: 'all',
+ bundlePolicy: 'max-bundle',
+ rtcpMuxPolicy: 'require',
+ sdpSemantics: 'unified-plan'
+ });
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();
+ 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) {
- if (handler)
- handler.close();
+ try {
+ pc.close();
+ }
+ catch (error2) { }
throw error;
}
}
- /**
- * 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
- });
+ async getNativeSctpCapabilities() {
+ logger.debug('getNativeSctpCapabilities()');
+ return {
+ numStreams: SCTP_NUM_STREAMS
+ };
}
- _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,
+ 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,
- iceServers,
- iceTransportPolicy,
- additionalSettings,
- proprietaryConstraints,
- appData,
- handlerFactory: this._handlerFactory,
- extendedRtpCapabilities: this._extendedRtpCapabilities,
- canProduceByKind: this._canProduceByKind
+ sctpParameters
});
- // Emit observer event.
- this._observer.safeEmit('newtransport', transport);
- return transport;
+ 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 {
+ 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;
+ }
+ });
+ }
}
-}
-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);
+ 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}`;
+ });
+ // 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], {});
+ // 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
+ });
+ const offer = await this._pc.createOffer();
+ let localSdpObject = sdpTransform.parse(offer.sdp);
+ 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.
+ const localId = transceiver.mid;
+ // Set MID.
+ sendingRtpParameters.mid = localId;
+ localSdpObject = sdpTransform.parse(this._pc.localDescription.sdp);
+ const 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) {
+ 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.
+ else {
+ sendingRtpParameters.encodings = encodings;
+ }
+ 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);
}
- safeEmit(event, ...args) {
- const numListeners = this.listenerCount(event);
- try {
- return this.emit(event, ...args);
+ 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');
}
- catch (error) {
- logger.error('safeEmit() | event listener threw an error [event:%s]:%o', event, error);
- return Boolean(numListeners);
+ 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 safeEmitAsPromise(event, ...args) {
- return new Promise((resolve, reject) => {
- try {
- this.emit(event, ...args, resolve, reject);
+ 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;
}
- catch (error) {
- logger.error('safeEmitAsPromise() | event listener threw an error [event:%s]:%o', event, error);
- reject(error);
+ 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);
}
-}
-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 = debug_1.default(`${APP_NAME}:${prefix}`);
- this._warn = debug_1.default(`${APP_NAME}:WARN:${prefix}`);
- this._error = 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`);
+ 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');
}
- /* 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":40}],14:[function(require,module,exports){
-"use strict";
-Object.defineProperty(exports, "__esModule", { value: true });
-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 {
- /**
- * @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 }) {
- super();
- // 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();
- }
- /**
- * Producer id.
- */
- get id() {
- return this._id;
- }
- /**
- * 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.
- */
- 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.
- */
- close() {
- if (this._closed)
- return;
- logger.debug('close()');
- this._closed = true;
- this._destroyTrack();
- this.emit('@close');
- // Emit observer event.
- this._observer.safeEmit('close');
+ 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);
}
- /**
- * 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 getSenderStats(localId) {
+ this.assertSendDirection();
+ const transceiver = this._mapMidTransceiver.get(localId);
+ if (!transceiver) {
+ throw new Error('associated RTCRtpTransceiver not found');
+ }
+ return transceiver.sender.getStats();
}
- /**
- * Get associated RTCRtpSender stats.
- */
- async getStats() {
- if (this._closed)
- throw new errors_1.InvalidStateError('closed');
- return this.safeEmitAsPromise('@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 };
}
- /**
- * Pauses sending media.
- */
- pause() {
- logger.debug('pause()');
- if (this._closed) {
- logger.error('pause() | Producer closed');
- return;
+ 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
+ });
}
- this._paused = true;
- if (this._track && this._disableTrackOnPause) {
- this._track.enabled = false;
+ 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
+ });
}
- if (this._zeroRtpOnPause) {
- this.safeEmitAsPromise('@replacetrack', null)
- .catch(() => { });
+ answer = { type: 'answer', sdp: sdpTransform.write(localSdpObject) };
+ if (!this._transportReady) {
+ await this.setupTransport({
+ localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client',
+ localSdpObject
+ });
}
- // Emit observer event.
- this._observer.safeEmit('pause');
+ 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;
}
- /**
- * Resumes sending media.
- */
- resume() {
- logger.debug('resume()');
- if (this._closed) {
- logger.error('resume() | Producer closed');
- return;
+ 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);
}
- this._paused = false;
- if (this._track && this._disableTrackOnPause) {
- this._track.enabled = true;
+ 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);
}
- if (this._zeroRtpOnPause) {
- this.safeEmitAsPromise('@replacetrack', this._track)
- .catch(() => { });
+ }
+ 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);
}
- // Emit observer event.
- this._observer.safeEmit('resume');
+ 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);
}
- /**
- * 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) { }
+ 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');
}
- throw new errors_1.InvalidStateError('closed');
+ transceiver.direction = 'recvonly';
+ this._remoteSdp.resumeReceivingMediaSection(localId);
}
- else if (track && track.readyState === 'ended') {
- throw new errors_1.InvalidStateError('track ended');
+ 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');
}
- // Do nothing if this is the same track as the current handled one.
- if (track === this._track) {
- logger.debug('replaceTrack() | same track, ignored');
- return;
+ 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;
}
- if (!this._zeroRtpOnPause || !this._paused) {
- await this.safeEmitAsPromise('@replacetrack', track);
+ return { dataChannel };
+ }
+ async setupTransport({ localDtlsRole, localSdpObject }) {
+ if (!localSdpObject) {
+ localSdpObject = sdpTransform.parse(this._pc.localDescription.sdp);
}
- // 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;
+ // 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');
}
- // 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;
+ assertRecvDirection() {
+ if (this._direction !== 'recv') {
+ throw new Error('method can just be called for handlers with "recv" direction');
+ }
}
+}
+exports.Chrome111 = Chrome111;
+
+},{"../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;
+ 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.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 {
/**
- * Sets the DSCP value.
+ * Creates a factory function.
*/
- 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);
+ static createFactory() {
+ return () => new Chrome55();
}
- _onTrackEnded() {
- logger.debug('track "ended" event');
- this.safeEmit('trackended');
- // Emit observer event.
- this._observer.safeEmit('trackended');
+ 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;
}
- _handleTrack() {
- if (!this._track)
- return;
- this._track.addEventListener('ended', this._onTrackEnded);
+ get name() {
+ return 'Chrome55';
}
- _destroyTrack() {
- if (!this._track)
- return;
+ 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 {
- this._track.removeEventListener('ended', this._onTrackEnded);
- // Just stop the track unless the app set stopTracks: false.
- if (this._stopTracks)
- this._track.stop();
+ 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;
}
- catch (error) { }
}
-}
-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 });
-
-},{}],17:[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.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");
-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 }) {
- 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);
- this._id = id;
+ 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._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,
+ this._remoteSdp = new RemoteSdp_1.RemoteSdp({
iceParameters,
iceCandidates,
dtlsParameters,
sctpParameters,
- iceServers,
- iceTransportPolicy,
- additionalSettings,
- proprietaryConstraints,
- extendedRtpCapabilities
+ planB: true
});
- this._appData = appData;
- this._handleHandler();
- }
- /**
- * Transport id.
- */
- get id() {
- return this._id;
- }
- /**
- * 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;
+ 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;
+ }
+ });
+ }
}
- /**
- * Connection state.
- */
- get connectionState() {
- return this._connectionState;
+ async updateIceServers(iceServers) {
+ logger.debug('updateIceServers()');
+ const configuration = this._pc.getConfiguration();
+ configuration.iceServers = iceServers;
+ this._pc.setConfiguration(configuration);
}
- /**
- * App custom data.
- */
- get appData() {
- return this._appData;
+ 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);
+ }
}
- /**
- * Invalid setter.
- */
- set appData(appData) {
- throw new Error('cannot override appData object');
+ async getTransportStats() {
+ return this._pc.getStats();
}
- /**
- * Observer.
- *
- * @emits close
- * @emits newproducer - (producer: Producer)
- * @emits newconsumer - (producer: Producer)
- * @emits newdataproducer - (dataProducer: DataProducer)
- * @emits newdataconsumer - (dataProducer: DataProducer)
- */
- get observer() {
- return this._observer;
+ 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 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++;
+ // Insert into the map.
+ this._mapSendLocalIdTrack.set(localId, track);
+ return {
+ localId: localId,
+ rtpParameters: sendingRtpParameters
+ };
}
- /**
- * Close the Transport.
- */
- close() {
- 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();
+ 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._producers.clear();
- // Close all Consumers.
- for (const consumer of this._consumers.values()) {
- consumer.transportClosed();
+ 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);
}
- this._consumers.clear();
- // Close all DataProducers.
- for (const dataProducer of this._dataProducers.values()) {
- dataProducer.transportClosed();
+ 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;
}
- this._dataProducers.clear();
- // Close all DataConsumers.
- for (const dataConsumer of this._dataConsumers.values()) {
- dataConsumer.transportClosed();
+ if (this._pc.signalingState === 'stable') {
+ return;
}
- this._dataConsumers.clear();
- // Emit observer event.
- this._observer.safeEmit('close');
+ const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() };
+ logger.debug('stopSending() | calling pc.setRemoteDescription() [answer:%o]', answer);
+ await this._pc.setRemoteDescription(answer);
}
- /**
- * Get associated Transport (RTCPeerConnection) stats.
- *
- * @returns {RTCStatsReport}
- */
- async getStats() {
- if (this._closed)
- throw new errors_1.InvalidStateError('closed');
- return this._handler.getTransportStats();
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ async pauseSending(localId) {
+ // Unimplemented.
}
- /**
- * Restart ICE connection.
- */
- 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()');
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ async resumeSending(localId) {
+ // Unimplemented.
}
- /**
- * Update ICE servers.
- */
- async updateIceServers({ iceServers } = {}) {
- logger.debug('updateIceServers()');
- 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()');
+ async replaceTrack(
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ localId, track) {
+ throw new errors_1.UnsupportedError('not implemented');
}
- /**
- * 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;
- }
- 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 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;
- }
- 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;
- });
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ async setMaxSpatialLayer(localId, spatialLayer) {
+ throw new errors_1.UnsupportedError(' not implemented');
}
- /**
- * 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);
- }
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ async setRtpEncodingParameters(localId, params) {
+ throw new errors_1.UnsupportedError('not supported');
+ }
+ // 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
+ });
}
- // Emit observer event.
- this._observer.safeEmit('newconsumer', consumer);
- return consumer;
- }, 'transport.consume()');
+ 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 };
}
- /**
- * 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
+ 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 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()');
- }
- /**
- * 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 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
});
- const dataConsumer = new DataConsumer_1.DataConsumer({
- id,
- dataProducerId,
- dataChannel,
- sctpStreamParameters,
- appData
+ }
+ answer = { type: 'answer', sdp: sdpTransform.write(localSdpObject) };
+ if (!this._transportReady) {
+ await this.setupTransport({
+ localDtlsRole: (_a = this._forcedLocalDtlsRole) !== null && _a !== void 0 ? _a : 'client',
+ localSdpObject
});
- 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;
+ }
+ 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 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');
}
- 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);
- });
+ // Insert into the map.
+ this._mapRecvLocalIdInfo.set(localId, { mid, rtpParameters });
+ results.push({ localId, track });
+ }
+ return results;
}
- _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);
- });
+ 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);
}
- _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('@getstats', (callback, errback) => {
- if (this._closed)
- return errback(new errors_1.InvalidStateError('closed'));
- this._handler.getReceiverStats(consumer.localId)
- .then(callback)
- .catch(errback);
- });
+ async pauseReceiving(
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ localIds) {
+ // Unimplemented.
}
- _handleDataProducer(dataProducer) {
- dataProducer.on('@close', () => {
- this._dataProducers.delete(dataProducer.id);
- });
+ async resumeReceiving(
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ localIds) {
+ // Unimplemented.
}
- _handleDataConsumer(dataConsumer) {
- dataConsumer.on('@close', () => {
- this._dataConsumers.delete(dataConsumer.id);
- });
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ async getReceiverStats(localId) {
+ throw new errors_1.UnsupportedError('not implemented');
}
-}
-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);
+ 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;
}
- else {
- this.stack = (new Error(message)).stack;
+ 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;
}
-}
-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);
+ assertSendDirection() {
+ if (this._direction !== 'send') {
+ throw new Error('method can just be called for handlers with "send" direction');
}
- else {
- this.stack = (new Error(message)).stack;
+ }
+ assertRecvDirection() {
+ if (this._direction !== 'recv') {
+ throw new Error('method can just be called for handlers with "recv" direction');
}
}
}
-exports.InvalidStateError = InvalidStateError;
+exports.Chrome55 = Chrome55;
-},{}],19:[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];
@@ -3234,30 +4876,35 @@ 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;
};
Object.defineProperty(exports, "__esModule", { value: true });
-exports.Chrome55 = void 0;
+exports.Chrome67 = 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 logger = new Logger_1.Logger('Chrome67');
const SCTP_NUM_STREAMS = { OS: 1024, MIS: 1024 };
-class Chrome55 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.
this._sendStream = new MediaStream();
- // Map of sending MediaStreamTracks indexed by localId.
- this._mapSendLocalIdTrack = new Map();
+ // 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.
@@ -3270,14 +4917,8 @@ class Chrome55 extends HandlerInterface_1.HandlerInterface {
// Got transport local and remote parameters.
this._transportReady = false;
}
- /**
- * Creates a factory function.
- */
- static createFactory() {
- return () => new Chrome55();
- }
get name() {
- return 'Chrome55';
+ return 'Chrome67';
}
close() {
logger.debug('close()');
@@ -3288,6 +4929,7 @@ class Chrome55 extends HandlerInterface_1.HandlerInterface {
}
catch (error) { }
}
+ this.emit('@close');
}
async getNativeRtpCapabilities() {
logger.debug('getNativeRtpCapabilities()');
@@ -3345,28 +4987,47 @@ 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);
- // 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 (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()');
@@ -3378,8 +5039,9 @@ class Chrome55 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);
@@ -3401,13 +5063,14 @@ class Chrome55 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 (codec) {
logger.warn('send() | codec selection is not available in %s handler', this.name);
}
this._sendStream.addTrack(track);
- this._pc.addStream(this._sendStream);
+ this._pc.addTrack(track, this._sendStream);
let offer = await this._pc.createOffer();
let localSdpObject = sdpTransform.parse(offer.sdp);
let offerMediaObject;
@@ -3417,12 +5080,17 @@ 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);
- offerMediaObject = localSdpObject.media.find((m) => m.type === 'video');
+ offerMediaObject = localSdpObject.media
+ .find((m) => m.type === 'video');
sdpPlanBUtils.addLegacySimulcast({
offerMediaObject,
track,
@@ -3444,8 +5112,9 @@ class Chrome55 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
@@ -3453,7 +5122,7 @@ class Chrome55 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({
@@ -3467,22 +5136,28 @@ class Chrome55 extends HandlerInterface_1.HandlerInterface {
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._mapSendLocalIdTrack.set(localId, track);
+ this._mapSendLocalIdRtpSender.set(localId, rtpSender);
return {
localId: localId,
- rtpParameters: sendingRtpParameters
+ rtpParameters: sendingRtpParameters,
+ rtpSender
};
}
async stopSending(localId) {
- this._assertSendDirection();
+ 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 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 {
@@ -3497,31 +5172,86 @@ class Chrome55 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);
}
- async replaceTrack(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
- localId, track) {
- throw new errors_1.UnsupportedError('not implemented');
+ 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) {
- throw new errors_1.UnsupportedError(' not implemented');
+ 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);
}
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
async setRtpEncodingParameters(localId, params) {
- throw new errors_1.UnsupportedError('not supported');
+ 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);
}
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
async getSenderStats(localId) {
- throw new errors_1.UnsupportedError('not implemented');
+ 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 }) {
- this._assertSendDirection();
+ var _a;
+ this.assertSendDirection();
const options = {
negotiated: true,
id: this._nextSendSctpStreamId,
@@ -3543,8 +5273,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 });
@@ -3561,53 +5295,76 @@ class Chrome55 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 = trackId;
- const mid = kind;
- const streamId = rtpParameters.rtcp.cname;
- this._remoteSdp.receive({
- mid,
- kind,
- offerRtpParameters: rtpParameters,
- streamId,
- trackId
- });
+ 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);
- 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({ 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()
- .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 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(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);
@@ -3615,12 +5372,27 @@ 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
+ localIds) {
+ // Unimplemented.
+ }
+ async resumeReceiving(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
+ localIds) {
+ // Unimplemented.
+ }
async getReceiverStats(localId) {
- throw new errors_1.UnsupportedError('not implemented');
+ 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 }) {
- this._assertRecvDirection();
+ var _a;
+ this.assertRecvDirection();
const { streamId, ordered, maxPacketLifeTime, maxRetransmits } = sctpStreamParameters;
const options = {
negotiated: true,
@@ -3643,7 +5415,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);
@@ -3651,9 +5426,10 @@ class Chrome55 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.
@@ -3661,27 +5437,33 @@ class Chrome55 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.Chrome55 = Chrome55;
+exports.Chrome67 = Chrome67;
-},{"../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){
+},{"../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];
@@ -3694,34 +5476,36 @@ 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;
};
Object.defineProperty(exports, "__esModule", { value: true });
-exports.Chrome67 = void 0;
+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 sdpPlanBUtils = __importStar(require("./sdp/planBUtils"));
+const sdpUnifiedPlanUtils = __importStar(require("./sdp/unifiedPlanUtils"));
const HandlerInterface_1 = require("./HandlerInterface");
const RemoteSdp_1 = require("./sdp/RemoteSdp");
-const logger = new Logger_1.Logger('Chrome67');
+const scalabilityModes_1 = require("../scalabilityModes");
+const logger = new Logger_1.Logger('Chrome70');
const SCTP_NUM_STREAMS = { OS: 1024, MIS: 1024 };
-class Chrome67 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.
+ this._mapMidTransceiver = new Map();
// 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.
@@ -3729,14 +5513,8 @@ class Chrome67 extends HandlerInterface_1.HandlerInterface {
// Got transport local and remote parameters.
this._transportReady = false;
}
- /**
- * Creates a factory function.
- */
- static createFactory() {
- return () => new Chrome67();
- }
get name() {
- return 'Chrome67';
+ return 'Chrome70';
}
close() {
logger.debug('close()');
@@ -3747,6 +5525,7 @@ class Chrome67 extends HandlerInterface_1.HandlerInterface {
}
catch (error) { }
}
+ this.emit('@close');
}
async getNativeRtpCapabilities() {
logger.debug('getNativeRtpCapabilities()');
@@ -3755,13 +5534,12 @@ class Chrome67 extends HandlerInterface_1.HandlerInterface {
iceTransportPolicy: 'all',
bundlePolicy: 'max-bundle',
rtcpMuxPolicy: 'require',
- sdpSemantics: 'plan-b'
+ sdpSemantics: 'unified-plan'
});
try {
- const offer = await pc.createOffer({
- offerToReceiveAudio: true,
- offerToReceiveVideo: true
- });
+ pc.addTransceiver('audio');
+ pc.addTransceiver('video');
+ const offer = await pc.createOffer();
try {
pc.close();
}
@@ -3791,8 +5569,7 @@ class Chrome67 extends HandlerInterface_1.HandlerInterface {
iceParameters,
iceCandidates,
dtlsParameters,
- sctpParameters,
- planB: true
+ sctpParameters
});
this._sendingRtpParametersByKind =
{
@@ -3804,28 +5581,47 @@ 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);
- // 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 (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()');
@@ -3837,8 +5633,9 @@ class Chrome67 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);
@@ -3860,64 +5657,108 @@ class Chrome67 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 (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], {});
+ // This may throw.
sendingRtpParameters.codecs =
- ortc.reduceCodecs(sendingRtpParameters.codecs);
+ ortc.reduceCodecs(sendingRtpParameters.codecs, codec);
const sendingRemoteRtpParameters = utils.clone(this._sendingRemoteRtpParametersByKind[track.kind], {});
+ // This may throw.
sendingRemoteRtpParameters.codecs =
- ortc.reduceCodecs(sendingRemoteRtpParameters.codecs);
- if (!this._transportReady)
- await this._setupTransport({ localDtlsRole: 'server', localSdpObject });
- if (track.kind === 'video' && encodings && encodings.length > 1) {
- logger.debug('send() | enabling simulcast');
+ 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
+ });
+ }
+ 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) };
+ }
+ // 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
- .find((m) => m.type === 'video');
- sdpPlanBUtils.addLegacySimulcast({
+ offerMediaObject = localSdpObject.media[mediaSectionIdx.idx];
+ sdpUnifiedPlanUtils.addLegacySimulcast({
offerMediaObject,
- track,
- numStreams: encodings.length
+ numStreams: layers.spatialLayers
});
offer = { type: 'offer', sdp: sdpTransform.write(localSdpObject) };
}
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 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
- .find((m) => m.type === track.kind);
+ offerMediaObject = localSdpObject.media[mediaSectionIdx.idx];
// Set RTCP CNAME.
sendingRtpParameters.rtcp.cname =
sdpCommonUtils.getCname({ offerMediaObject });
// Set RTP encodings.
sendingRtpParameters.encodings =
- sdpPlanBUtils.getRtpEncodings({ offerMediaObject, track });
+ sdpUnifiedPlanUtils.getRtpEncodings({ offerMediaObject });
// 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
- // encoding.
+ // 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/vp8' ||
+ sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/h264')) {
for (const encoding of sendingRtpParameters.encodings) {
- encoding.scalabilityMode = 'S1T3';
+ encoding.scalabilityMode = 'L1T3';
}
}
this._remoteSdp.send({
offerMediaObject,
+ reuseMid: mediaSectionIdx.reuseMid,
offerRtpParameters: sendingRtpParameters,
answerRtpParameters: sendingRemoteRtpParameters,
codecOptions
@@ -3925,104 +5766,116 @@ class Chrome67 extends HandlerInterface_1.HandlerInterface {
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);
+ // Store in the map.
+ this._mapMidTransceiver.set(localId, transceiver);
return {
- localId: localId,
+ localId,
rtpParameters: sendingRtpParameters,
- rtpSender
+ rtpSender: transceiver.sender
};
}
async stopSending(localId) {
- this._assertSendDirection();
+ 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);
+ const transceiver = this._mapMidTransceiver.get(localId);
+ if (!transceiver) {
+ throw new Error('associated RTCRtpTransceiver not found');
}
- 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;
+ transceiver.sender.replaceTrack(null);
+ this._pc.removeTrack(transceiver.sender);
+ const mediaSectionClosed = this._remoteSdp.closeMediaSection(transceiver.mid);
+ if (mediaSectionClosed) {
+ try {
+ transceiver.stop();
}
- throw error;
+ catch (error) { }
}
- if (this._pc.signalingState === 'stable')
- return;
+ 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) {
+ // 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);
}
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);
+ 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();
+ 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();
+ 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)
+ if (idx <= spatialLayer) {
encoding.active = true;
- else
+ }
+ else {
encoding.active = false;
+ }
});
- await rtpSender.setParameters(parameters);
+ 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 rtpSender = this._mapSendLocalIdRtpSender.get(localId);
- if (!rtpSender)
- throw new Error('associated RTCRtpSender not found');
- const parameters = rtpSender.getParameters();
+ 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] = Object.assign(Object.assign({}, encoding), params);
+ parameters.encodings[idx] = { ...encoding, ...params };
});
- await rtpSender.setParameters(parameters);
+ 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 rtpSender = this._mapSendLocalIdRtpSender.get(localId);
- if (!rtpSender)
- throw new Error('associated RTCRtpSender not found');
- return rtpSender.getStats();
+ 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 }) {
- this._assertSendDirection();
+ var _a;
+ this.assertSendDirection();
const options = {
negotiated: true,
id: this._nextSendSctpStreamId,
@@ -4044,8 +5897,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 });
@@ -4062,71 +5919,109 @@ class Chrome67 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 = trackId;
- const mid = kind;
- this._remoteSdp.receive({
- mid,
- 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) === 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 { 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 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 { 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 { 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 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(
+ // 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) {
- this._assertRecvDirection();
- const { rtpReceiver } = this._mapRecvLocalIdInfo.get(localId) || {};
- if (!rtpReceiver)
- throw new Error('associated RTCRtpReceiver not found');
- return rtpReceiver.getStats();
+ 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 }) {
- this._assertRecvDirection();
+ var _a;
+ this.assertRecvDirection();
const { streamId, ordered, maxPacketLifeTime, maxRetransmits } = sctpStreamParameters;
const options = {
negotiated: true,
@@ -4142,14 +6037,17 @@ class Chrome67 extends HandlerInterface_1.HandlerInterface {
// 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 });
+ 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 });
+ 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);
@@ -4157,9 +6055,10 @@ class Chrome67 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.
@@ -4167,27 +6066,33 @@ class Chrome67 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.Chrome67 = Chrome67;
+exports.Chrome70 = Chrome70;
-},{"../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){
+},{"../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];
@@ -4200,24 +6105,31 @@ 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;
};
Object.defineProperty(exports, "__esModule", { value: true });
-exports.Chrome70 = void 0;
+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('Chrome70');
+const logger = new Logger_1.Logger('Chrome74');
const SCTP_NUM_STREAMS = { OS: 1024, MIS: 1024 };
-class Chrome70 extends HandlerInterface_1.HandlerInterface {
+class Chrome74 extends HandlerInterface_1.HandlerInterface {
+ /**
+ * Creates a factory function.
+ */
+ static createFactory() {
+ return () => new Chrome74();
+ }
constructor() {
super();
// Map of RTCTransceivers indexed by MID.
@@ -4231,14 +6143,8 @@ class Chrome70 extends HandlerInterface_1.HandlerInterface {
// Got transport local and remote parameters.
this._transportReady = false;
}
- /**
- * Creates a factory function.
- */
- static createFactory() {
- return () => new Chrome70();
- }
get name() {
- return 'Chrome70';
+ return 'Chrome74';
}
close() {
logger.debug('close()');
@@ -4249,6 +6155,7 @@ class Chrome70 extends HandlerInterface_1.HandlerInterface {
}
catch (error) { }
}
+ this.emit('@close');
}
async getNativeRtpCapabilities() {
logger.debug('getNativeRtpCapabilities()');
@@ -4269,6 +6176,8 @@ class Chrome70 extends HandlerInterface_1.HandlerInterface {
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) {
@@ -4304,28 +6213,47 @@ 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);
- // 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 (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 {
+ 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;
+ }
+ });
+ }
}
async updateIceServers(iceServers) {
logger.debug('updateIceServers()');
@@ -4337,8 +6265,9 @@ class Chrome70 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);
@@ -4360,8 +6289,14 @@ class Chrome70 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.length > 1) {
+ encodings.forEach((encoding, idx) => {
+ encoding.rid = `r${idx}`;
+ });
+ }
const sendingRtpParameters = utils.clone(this._sendingRtpParametersByKind[track.kind], {});
// This may throw.
sendingRtpParameters.codecs =
@@ -4371,25 +6306,23 @@ class Chrome70 extends HandlerInterface_1.HandlerInterface {
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: 'server', localSdpObject });
- 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
+ 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
});
- offer = { type: 'offer', sdp: sdpTransform.write(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 &&
@@ -4406,20 +6339,6 @@ class Chrome70 extends HandlerInterface_1.HandlerInterface {
}
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 transceiver.sender.setParameters(parameters);
- }
// We can now get the transceiver.mid.
const localId = transceiver.mid;
// Set MID.
@@ -4429,19 +6348,25 @@ class Chrome70 extends HandlerInterface_1.HandlerInterface {
// 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]);
+ // 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;
}
- // Hack for VP9 SVC.
- if (hackVp9Svc) {
- sendingRtpParameters.encodings = [sendingRtpParameters.encodings[0]];
+ // 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.
@@ -4449,7 +6374,12 @@ class Chrome70 extends HandlerInterface_1.HandlerInterface {
(sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/vp8' ||
sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/h264')) {
for (const encoding of sendingRtpParameters.encodings) {
- encoding.scalabilityMode = 'S1T3';
+ if (encoding.scalabilityMode) {
+ encoding.scalabilityMode = `L1T${layers.temporalLayers}`;
+ }
+ else {
+ encoding.scalabilityMode = 'L1T3';
+ }
}
}
this._remoteSdp.send({
@@ -4457,7 +6387,8 @@ class Chrome70 extends HandlerInterface_1.HandlerInterface {
reuseMid: mediaSectionIdx.reuseMid,
offerRtpParameters: sendingRtpParameters,
answerRtpParameters: sendingRemoteRtpParameters,
- codecOptions
+ codecOptions,
+ extmapAllowMixed: true
});
const answer = { type: 'answer', sdp: this._remoteSdp.getSdp() };
logger.debug('send() | calling pc.setRemoteDescription() [answer:%o]', answer);
@@ -4471,23 +6402,63 @@ class Chrome70 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);
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();
+ this.assertSendDirection();
if (track) {
logger.debug('replaceTrack() [localId:%s, track.id:%s]', localId, track.id);
}
@@ -4495,52 +6466,72 @@ class Chrome70 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] = Object.assign(Object.assign({}, encoding), params);
+ 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,
ordered,
maxPacketLifeTime,
- maxRetransmitTime: maxPacketLifeTime,
maxRetransmits,
protocol
};
@@ -4556,8 +6547,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 });
@@ -4574,77 +6569,143 @@ class Chrome70 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);
+ 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();
+ 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,
id: streamId,
ordered,
maxPacketLifeTime,
- maxRetransmitTime: maxPacketLifeTime,
maxRetransmits,
protocol
};
@@ -4660,7 +6721,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);
@@ -4668,9 +6732,10 @@ class Chrome70 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.
@@ -4678,27 +6743,33 @@ class Chrome70 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.Chrome70 = Chrome70;
+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}],22:[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];
@@ -4711,494 +6782,443 @@ 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;
};
Object.defineProperty(exports, "__esModule", { value: true });
-exports.Chrome74 = void 0;
-const sdpTransform = __importStar(require("sdp-transform"));
+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 sdpCommonUtils = __importStar(require("./sdp/commonUtils"));
-const sdpUnifiedPlanUtils = __importStar(require("./sdp/unifiedPlanUtils"));
+const edgeUtils = __importStar(require("./ortc/edgeUtils"));
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;
- }
+const logger = new Logger_1.Logger('Edge11');
+class Edge11 extends HandlerInterface_1.HandlerInterface {
/**
* Creates a factory function.
*/
static createFactory() {
- return () => new Chrome74();
+ 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 'Chrome74';
+ return 'Edge11';
}
close() {
logger.debug('close()');
- // Close RTCPeerConnection.
- if (this._pc) {
- try {
- this._pc.close();
- }
- catch (error) { }
+ // Close the ICE gatherer.
+ // NOTE: Not yet implemented by Edge.
+ try {
+ this._iceGatherer.close();
}
- }
- async getNativeRtpCapabilities() {
- logger.debug('getNativeRtpCapabilities()');
- const pc = new RTCPeerConnection({
- iceServers: [],
- iceTransportPolicy: 'all',
- bundlePolicy: 'max-bundle',
- rtcpMuxPolicy: 'require',
- sdpSemantics: 'unified-plan'
- });
+ catch (error) { }
+ // Close the ICE transport.
try {
- pc.addTransceiver('audio');
- pc.addTransceiver('video');
- const offer = await pc.createOffer();
+ this._iceTransport.stop();
+ }
+ catch (error) { }
+ // Close the DTLS transport.
+ try {
+ this._dtlsTransport.stop();
+ }
+ catch (error) { }
+ // Close RTCRtpSenders.
+ for (const rtpSender of this._rtpSenders.values()) {
try {
- pc.close();
+ rtpSender.stop();
}
catch (error) { }
- const sdpObject = sdpTransform.parse(offer.sdp);
- const nativeRtpCapabilities = sdpCommonUtils.extractRtpCapabilities({ sdpObject });
- return nativeRtpCapabilities;
}
- catch (error) {
+ // Close RTCRtpReceivers.
+ for (const rtpReceiver of this._rtpReceivers.values()) {
try {
- pc.close();
+ rtpReceiver.stop();
}
- catch (error2) { }
- throw error;
+ catch (error) { }
}
+ this.emit('@close');
+ }
+ async getNativeRtpCapabilities() {
+ logger.debug('getNativeRtpCapabilities()');
+ return edgeUtils.getCapabilities();
}
async getNativeSctpCapabilities() {
logger.debug('getNativeSctpCapabilities()');
return {
- numStreams: SCTP_NUM_STREAMS
+ numStreams: { OS: 0, MIS: 0 }
};
}
- run({ direction, iceParameters, iceCandidates, dtlsParameters, sctpParameters, iceServers, iceTransportPolicy, additionalSettings, proprietaryConstraints, extendedRtpCapabilities }) {
+ 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._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)
+ {
+ audio: ortc.getSendingRtpParameters('audio', extendedRtpCapabilities),
+ video: ortc.getSendingRtpParameters('video', extendedRtpCapabilities)
};
- this._pc = new RTCPeerConnection(Object.assign({ 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;
- }
- });
+ 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) {
- logger.debug('updateIceServers()');
- const configuration = this._pc.getConfiguration();
- configuration.iceServers = iceServers;
- this._pc.setConfiguration(configuration);
+ // NOTE: Edge 11 does not implement iceGatherer.gater().
+ throw new errors_1.UnsupportedError('not supported');
}
async restartIce(iceParameters) {
logger.debug('restartIce()');
- // Provide the remote SDP handler with new remote ICE parameters.
- this._remoteSdp.updateIceParameters(iceParameters);
- if (!this._transportReady)
+ this._remoteIceParameters = 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);
+ logger.debug('restartIce() | calling iceTransport.start()');
+ this._iceTransport.start(this._iceGatherer, iceParameters, 'controlling');
+ for (const candidate of this._remoteIceCandidates) {
+ this._iceTransport.addRemoteCandidate(candidate);
}
+ this._iceTransport.addRemoteCandidate({});
}
async getTransportStats() {
- return this._pc.getStats();
+ return this._iceTransport.getStats();
}
- async send({ track, encodings, codecOptions, codec }) {
- this._assertSendDirection();
+ 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 (encodings && encodings.length > 1) {
- encodings.forEach((encoding, idx) => {
- encoding.rid = `r${idx}`;
- });
+ if (!this._transportReady) {
+ await this.setupTransport({ localDtlsRole: 'server' });
}
- 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: 'server', localSdpObject });
- // Special case for VP9 with SVC.
- let hackVp9Svc = false;
- const layers = 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.
+ 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) {
- 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;
+ 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) {
- encoding.scalabilityMode = 'S1T3';
+ for (const encoding of encodings) {
+ encoding.ssrc = utils.generateRandomNumber();
+ if (useRtx) {
+ encoding.rtx = { ssrc: utils.generateRandomNumber() };
}
}
- 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
- };
+ rtpParameters.encodings = encodings;
+ // Fill RTCRtpParameters.rtcp.
+ rtpParameters.rtcp =
+ {
+ cname: this._cname,
+ reducedSize: true,
+ mux: true
+ };
+ // NOTE: Convert our standard RTCRtpParameters into those that Edge
+ // expects.
+ const edgeRtpParameters = edgeUtils.mangleRtpParameters(rtpParameters);
+ logger.debug('send() | calling rtpSender.send() [params:%o]', edgeRtpParameters);
+ await rtpSender.send(edgeRtpParameters);
+ const localId = String(this._nextSendLocalId);
+ this._nextSendLocalId++;
+ // Store it.
+ this._rtpSenders.set(localId, rtpSender);
+ return { localId, rtpParameters, rtpSender };
}
async stopSending(localId) {
- 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);
+ const rtpSender = this._rtpSenders.get(localId);
+ if (!rtpSender) {
+ throw new Error('RTCRtpSender not found');
+ }
+ this._rtpSenders.delete(localId);
+ try {
+ logger.debug('stopSending() | calling rtpSender.stop()');
+ rtpSender.stop();
+ }
+ catch (error) {
+ logger.warn('stopSending() | rtpSender.stop() failed:%o', error);
+ throw error;
+ }
+ }
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ async pauseSending(localId) {
+ // Unimplemented.
+ }
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ async resumeSending(localId) {
+ // Unimplemented.
}
async replaceTrack(localId, track) {
- 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);
+ const rtpSender = this._rtpSenders.get(localId);
+ if (!rtpSender) {
+ throw new Error('RTCRtpSender not found');
+ }
+ rtpSender.setTrack(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)
+ 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
+ }
+ else {
encoding.active = false;
+ }
});
- await transceiver.sender.setParameters(parameters);
+ await rtpSender.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();
+ 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] = Object.assign(Object.assign({}, encoding), params);
+ parameters.encodings[idx] = { ...encoding, ...params };
});
- await transceiver.sender.setParameters(parameters);
+ await rtpSender.setParameters(parameters);
+ }
+ async getSenderStats(localId) {
+ const rtpSender = this._rtpSenders.get(localId);
+ if (!rtpSender) {
+ throw new Error('RTCRtpSender not found');
+ }
+ return rtpSender.getStats();
+ }
+ async sendDataChannel(
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ options) {
+ throw new errors_1.UnsupportedError('not implemented');
+ }
+ async receive(optionsList) {
+ const results = [];
+ for (const options of optionsList) {
+ const { trackId, kind } = options;
+ logger.debug('receive() [trackId:%s, kind:%s]', trackId, kind);
+ }
+ if (!this._transportReady) {
+ await this.setupTransport({ localDtlsRole: 'server' });
+ }
+ for (const options of optionsList) {
+ const { trackId, kind, rtpParameters } = options;
+ logger.debug('receive() | calling new RTCRtpReceiver()');
+ const rtpReceiver = new RTCRtpReceiver(this._dtlsTransport, kind);
+ rtpReceiver.addEventListener('error', (event) => {
+ logger.error('rtpReceiver "error" event [event:%o]', event);
+ });
+ // NOTE: Convert our standard RTCRtpParameters into those that Edge
+ // expects.
+ const edgeRtpParameters = edgeUtils.mangleRtpParameters(rtpParameters);
+ logger.debug('receive() | calling rtpReceiver.receive() [params:%o]', edgeRtpParameters);
+ await rtpReceiver.receive(edgeRtpParameters);
+ const localId = trackId;
+ // Store it.
+ this._rtpReceivers.set(localId, rtpReceiver);
+ results.push({
+ localId,
+ track: rtpReceiver.track,
+ rtpReceiver
+ });
+ }
+ return results;
+ }
+ async stopReceiving(localIds) {
+ for (const localId of localIds) {
+ logger.debug('stopReceiving() [localId:%s]', localId);
+ const rtpReceiver = this._rtpReceivers.get(localId);
+ if (!rtpReceiver) {
+ throw new Error('RTCRtpReceiver not found');
+ }
+ this._rtpReceivers.delete(localId);
+ try {
+ logger.debug('stopReceiving() | calling rtpReceiver.stop()');
+ rtpReceiver.stop();
+ }
+ catch (error) {
+ logger.warn('stopReceiving() | rtpReceiver.stop() failed:%o', error);
+ }
+ }
}
- async getSenderStats(localId) {
- this._assertSendDirection();
- const transceiver = this._mapMidTransceiver.get(localId);
- if (!transceiver)
- throw new Error('associated RTCRtpTransceiver not found');
- return transceiver.sender.getStats();
+ async pauseReceiving(
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
+ localIds) {
+ // Unimplemented.
}
- async sendDataChannel({ ordered, maxPacketLifeTime, maxRetransmits, label, protocol }) {
- 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: 'server', 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 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 sctpStreamParameters = {
- streamId: options.id,
- ordered: options.ordered,
- maxPacketLifeTime: options.maxPacketLifeTime,
- maxRetransmits: options.maxRetransmits
- };
- return { dataChannel, sctpStreamParameters };
+ return rtpReceiver.getStats();
}
- 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 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'
});
- 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
+ iceGatherer.addEventListener('error', (event) => {
+ logger.error('iceGatherer "error" event [event:%o]', event);
});
- 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);
- 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);
+ // 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;
}
- async getReceiverStats(localId) {
- this._assertRecvDirection();
- const transceiver = this._mapMidTransceiver.get(localId);
- if (!transceiver)
- throw new Error('associated RTCRtpTransceiver not found');
- return transceiver.receiver.getStats();
+ setIceTransport() {
+ const iceTransport = new RTCIceTransport(this._iceGatherer);
+ // NOTE: Not yet implemented by Edge.
+ iceTransport.addEventListener('statechange', () => {
+ switch (iceTransport.state) {
+ case 'checking':
+ this.emit('@connectionstatechange', 'connecting');
+ break;
+ case 'connected':
+ case 'completed':
+ this.emit('@connectionstatechange', 'connected');
+ break;
+ case 'failed':
+ this.emit('@connectionstatechange', 'failed');
+ break;
+ case 'disconnected':
+ this.emit('@connectionstatechange', 'disconnected');
+ break;
+ case 'closed':
+ this.emit('@connectionstatechange', 'closed');
+ break;
+ }
+ });
+ // NOTE: Not standard, but implemented by Edge.
+ iceTransport.addEventListener('icestatechange', () => {
+ switch (iceTransport.state) {
+ case 'checking':
+ this.emit('@connectionstatechange', 'connecting');
+ break;
+ case 'connected':
+ case 'completed':
+ this.emit('@connectionstatechange', 'connected');
+ break;
+ case 'failed':
+ this.emit('@connectionstatechange', 'failed');
+ break;
+ case 'disconnected':
+ this.emit('@connectionstatechange', 'disconnected');
+ break;
+ case 'closed':
+ this.emit('@connectionstatechange', 'closed');
+ break;
+ }
+ });
+ iceTransport.addEventListener('candidatepairchange', (event) => {
+ logger.debug('iceTransport "candidatepairchange" event [pair:%o]', event.pair);
+ });
+ this._iceTransport = iceTransport;
}
- 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 });
+ 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');
}
- logger.debug('receiveDataChannel() | calling pc.setRemoteDescription() [answer:%o]', answer);
- await this._pc.setLocalDescription(answer);
- this._hasDataChannelMediaSection = true;
- }
- return { dataChannel };
+ });
+ dtlsTransport.addEventListener('error', (event) => {
+ logger.error('dtlsTransport "error" event [event:%o]', event);
+ });
+ this._dtlsTransport = dtlsTransport;
}
- async _setupTransport({ localDtlsRole, localSdpObject }) {
- if (!localSdpObject)
- localSdpObject = sdpTransform.parse(this._pc.localDescription.sdp);
+ async setupTransport({ localDtlsRole }) {
+ logger.debug('setupTransport()');
// Get our local DTLS parameters.
- const dtlsParameters = sdpCommonUtils.extractDtlsParameters({ sdpObject: localSdpObject });
- // Set our DTLS role.
+ const dtlsParameters = this._dtlsTransport.getLocalParameters();
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');
- }
- }
- _assertRecvDirection() {
- if (this._direction !== 'recv') {
- throw new Error('method can just be called for handlers with "recv" direction');
+ 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.Chrome74 = Chrome74;
+exports.Edge11 = Edge11;
-},{"../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){
+},{"../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];
@@ -5211,392 +7231,689 @@ 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;
};
Object.defineProperty(exports, "__esModule", { value: true });
-exports.Edge11 = void 0;
+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 edgeUtils = __importStar(require("./ortc/edgeUtils"));
+const sdpCommonUtils = __importStar(require("./sdp/commonUtils"));
+const sdpUnifiedPlanUtils = __importStar(require("./sdp/unifiedPlanUtils"));
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;
- }
+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 Edge11();
+ return () => new Firefox60();
+ }
+ 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 'Edge11';
+ return 'Firefox60';
}
close() {
logger.debug('close()');
- // Close the ICE gatherer.
- // NOTE: Not yet implemented by Edge.
- try {
- this._iceGatherer.close();
- }
- catch (error) { }
- // Close the ICE transport.
- try {
- this._iceTransport.stop();
+ // Close RTCPeerConnection.
+ if (this._pc) {
+ try {
+ this._pc.close();
+ }
+ catch (error) { }
}
- catch (error) { }
- // Close the DTLS transport.
+ 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 {
- this._dtlsTransport.stop();
- }
- catch (error) { }
- // Close RTCRtpSenders.
- for (const rtpSender of this._rtpSenders.values()) {
+ 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 {
- rtpSender.stop();
+ canvas.remove();
}
catch (error) { }
- }
- // Close RTCRtpReceivers.
- for (const rtpReceiver of this._rtpReceivers.values()) {
try {
- rtpReceiver.stop();
+ fakeVideoTrack.stop();
+ }
+ catch (error) { }
+ try {
+ pc.close();
}
catch (error) { }
+ const sdpObject = sdpTransform.parse(offer.sdp);
+ const nativeRtpCapabilities = sdpCommonUtils.extractRtpCapabilities({ sdpObject });
+ return nativeRtpCapabilities;
+ }
+ catch (error) {
+ try {
+ canvas.remove();
+ }
+ catch (error2) { }
+ try {
+ fakeVideoTrack.stop();
+ }
+ catch (error2) { }
+ try {
+ pc.close();
+ }
+ catch (error2) { }
+ throw error;
}
- }
- async getNativeRtpCapabilities() {
- logger.debug('getNativeRtpCapabilities()');
- return edgeUtils.getCapabilities();
}
async getNativeSctpCapabilities() {
logger.debug('getNativeSctpCapabilities()');
return {
- numStreams: { OS: 0, MIS: 0 }
+ numStreams: SCTP_NUM_STREAMS
};
}
- 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 }) {
+ 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._remoteIceParameters = iceParameters;
- this._remoteIceCandidates = iceCandidates;
- this._remoteDtlsParameters = dtlsParameters;
- this._cname = `CNAME-${utils.generateRandomNumber()}`;
- this._setIceGatherer({ iceServers, iceTransportPolicy });
- this._setIceTransport();
- this._setDtlsTransport();
+ 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;
+ }
+ });
+ }
}
// eslint-disable-next-line @typescript-eslint/no-unused-vars
async updateIceServers(iceServers) {
- // NOTE: Edge 11 does not implement iceGatherer.gater().
+ // NOTE: Firefox does not implement pc.setConfiguration().
throw new errors_1.UnsupportedError('not supported');
}
async restartIce(iceParameters) {
logger.debug('restartIce()');
- this._remoteIceParameters = iceParameters;
- if (!this._transportReady)
+ // Provide the remote SDP handler with new remote ICE parameters.
+ this._remoteSdp.updateIceParameters(iceParameters);
+ if (!this._transportReady) {
return;
- logger.debug('restartIce() | calling iceTransport.start()');
- this._iceTransport.start(this._iceGatherer, iceParameters, 'controlling');
- for (const candidate of this._remoteIceCandidates) {
- this._iceTransport.addRemoteCandidate(candidate);
}
- this._iceTransport.addRemoteCandidate({});
+ 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._iceTransport.getStats();
+ return this._pc.getStats();
}
- async send(
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- { track, encodings, codecOptions, codec }) {
+ async send({ track, encodings, codecOptions, codec }) {
+ this.assertSendDirection();
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 (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();
+ }
}
- 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 };
+ 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);
+ }
+ 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
+ };
}
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();
+ const transceiver = this._mapMidTransceiver.get(localId);
+ if (!transceiver) {
+ throw new Error('associated transceiver not found');
}
- catch (error) {
- logger.warn('stopSending() | rtpSender.stop() failed:%o', error);
- throw error;
+ 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);
+ }
+ // 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 rtpSender = this._rtpSenders.get(localId);
- if (!rtpSender)
- throw new Error('RTCRtpSender not found');
- rtpSender.setTrack(track);
+ 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 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)
+ 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
+ }
+ else {
encoding.active = false;
+ }
});
- await rtpSender.setParameters(parameters);
+ 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 rtpSender = this._rtpSenders.get(localId);
- if (!rtpSender)
- throw new Error('RTCRtpSender not found');
- const parameters = rtpSender.getParameters();
+ 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] = Object.assign(Object.assign({}, encoding), params);
+ parameters.encodings[idx] = { ...encoding, ...params };
});
- await rtpSender.setParameters(parameters);
+ 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) {
- 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');
+ this.assertSendDirection();
+ const transceiver = this._mapMidTransceiver.get(localId);
+ if (!transceiver) {
+ throw new Error('associated RTCRtpTransceiver not found');
+ }
+ return transceiver.sender.getStats();
}
- 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 sendDataChannel({ ordered, maxPacketLifeTime, maxRetransmits, label, protocol }) {
+ this.assertSendDirection();
+ const options = {
+ negotiated: true,
+ id: this._nextSendSctpStreamId,
+ ordered,
+ maxPacketLifeTime,
+ maxRetransmits,
+ protocol
};
- }
- 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();
- }
- catch (error) {
- logger.warn('stopReceiving() | rtpReceiver.stop() failed:%o', error);
+ 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;
}
+ const sctpStreamParameters = {
+ streamId: options.id,
+ ordered: options.ordered,
+ maxPacketLifeTime: options.maxPacketLifeTime,
+ maxRetransmits: options.maxRetransmits
+ };
+ return { dataChannel, sctpStreamParameters };
}
- async getReceiverStats(localId) {
- const rtpReceiver = this._rtpReceivers.get(localId);
- if (!rtpReceiver)
- throw new Error('RTCRtpReceiver not found');
- return rtpReceiver.getStats();
- }
- async receiveDataChannel(
+ async receive(
// eslint-disable-next-line @typescript-eslint/no-unused-vars
- options) {
- throw new errors_1.UnsupportedError('not implemented');
+ 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 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;
}
- _setIceGatherer({ iceServers, iceTransportPolicy }) {
- 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();
+ 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);
}
- catch (error) {
- logger.debug('_setIceGatherer() | iceGatherer.gather() failed: %s', error.toString());
+ 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);
}
- this._iceGatherer = iceGatherer;
}
- _setIceTransport() {
- const iceTransport = new RTCIceTransport(this._iceGatherer);
- // NOTE: Not yet implemented by Edge.
- iceTransport.addEventListener('statechange', () => {
- switch (iceTransport.state) {
- case 'checking':
- this.emit('@connectionstatechange', 'connecting');
- break;
- case 'connected':
- case 'completed':
- this.emit('@connectionstatechange', 'connected');
- break;
- case 'failed':
- this.emit('@connectionstatechange', 'failed');
- break;
- case 'disconnected':
- this.emit('@connectionstatechange', 'disconnected');
- break;
- case 'closed':
- this.emit('@connectionstatechange', 'closed');
- break;
+ 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');
}
- });
- // 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;
+ 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');
}
- });
- iceTransport.addEventListener('candidatepairchange', (event) => {
- logger.debug('iceTransport "candidatepairchange" event [pair:%o]', event.pair);
- });
- this._iceTransport = iceTransport;
+ 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);
}
- _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 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 }) {
+ 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 };
}
- async _setupTransport({ localDtlsRole }) {
- logger.debug('_setupTransport()');
+ async setupTransport({ localDtlsRole, localSdpObject }) {
+ if (!localSdpObject) {
+ localSdpObject = sdpTransform.parse(this._pc.localDescription.sdp);
+ }
// Get our local DTLS parameters.
- const dtlsParameters = this._dtlsTransport.getLocalParameters();
+ 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 });
- // 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');
+ await new Promise((resolve, reject) => {
+ this.safeEmit('@connect', { dtlsParameters }, resolve, reject);
});
- // Start the RTCDtlsTransport.
- this._dtlsTransport.start(this._remoteDtlsParameters);
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.Edge11 = Edge11;
+exports.Firefox60 = Firefox60;
+
+},{"../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.HandlerInterface = void 0;
+const EnhancedEventEmitter_1 = require("../EnhancedEventEmitter");
+class HandlerInterface extends EnhancedEventEmitter_1.EnhancedEventEmitter {
+ constructor() {
+ super();
+ }
+}
+exports.HandlerInterface = HandlerInterface;
-},{"../Logger":13,"../errors":18,"../ortc":36,"../utils":39,"./HandlerInterface":25,"./ortc/edgeUtils":29}],24:[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];
@@ -5609,30 +7926,41 @@ 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;
};
Object.defineProperty(exports, "__esModule", { value: true });
-exports.Firefox60 = void 0;
+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 sdpUnifiedPlanUtils = __importStar(require("./sdp/unifiedPlanUtils"));
+const sdpPlanBUtils = __importStar(require("./sdp/planBUtils"));
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 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();
- // Map of RTCTransceivers indexed by MID.
- this._mapMidTransceiver = new Map();
// 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.
@@ -5640,17 +7968,15 @@ 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 '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 {
@@ -5658,6 +7984,7 @@ class Firefox60 extends HandlerInterface_1.HandlerInterface {
}
catch (error) { }
}
+ this.emit('@close');
}
async getNativeRtpCapabilities() {
logger.debug('getNativeRtpCapabilities()');
@@ -5665,33 +7992,14 @@ class Firefox60 extends HandlerInterface_1.HandlerInterface {
iceServers: [],
iceTransportPolicy: 'all',
bundlePolicy: 'max-bundle',
- rtcpMuxPolicy: 'require'
+ rtcpMuxPolicy: 'require',
+ sdpSemantics: 'plan-b'
});
- // 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) { }
+ const offer = await pc.createOffer({
+ offerToReceiveAudio: true,
+ offerToReceiveVideo: true
+ });
try {
pc.close();
}
@@ -5701,14 +8009,6 @@ class Firefox60 extends HandlerInterface_1.HandlerInterface {
return nativeRtpCapabilities;
}
catch (error) {
- try {
- canvas.remove();
- }
- catch (error2) { }
- try {
- fakeVideoTrack.stop();
- }
- catch (error2) { }
try {
pc.close();
}
@@ -5729,7 +8029,8 @@ class Firefox60 extends HandlerInterface_1.HandlerInterface {
iceParameters,
iceCandidates,
dtlsParameters,
- sctpParameters
+ sctpParameters,
+ planB: true
});
this._sendingRtpParametersByKind =
{
@@ -5741,40 +8042,61 @@ 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);
- // 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 (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;
+ }
+ });
+ }
}
- // 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);
@@ -5796,73 +8118,59 @@ 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 (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], {});
- // This may throw.
sendingRtpParameters.codecs =
- ortc.reduceCodecs(sendingRtpParameters.codecs, codec);
+ ortc.reduceCodecs(sendingRtpParameters.codecs);
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);
+ 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) };
}
- 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 });
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];
+ offerMediaObject = localSdpObject.media
+ .find((m) => m.type === track.kind);
// 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();
+ // 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.
@@ -5870,102 +8178,92 @@ class Firefox60 extends HandlerInterface_1.HandlerInterface {
(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({
offerMediaObject,
offerRtpParameters: sendingRtpParameters,
answerRtpParameters: sendingRemoteRtpParameters,
- codecOptions,
- extmapAllowMixed: true
+ 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);
+ const localId = String(this._nextSendLocalId);
+ this._nextSendLocalId++;
+ // Insert into the map.
+ this._mapSendLocalIdTrack.set(localId, track);
return {
- localId,
- rtpParameters: sendingRtpParameters,
- rtpSender: transceiver.sender
+ localId: localId,
+ rtpParameters: sendingRtpParameters
};
}
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');
- 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 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);
- await this._pc.setLocalDescription(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);
}
- 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);
+ // 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) {
- 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);
+ throw new errors_1.UnsupportedError('not implemented');
}
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
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] = Object.assign(Object.assign({}, encoding), params);
- });
- await transceiver.sender.setParameters(parameters);
+ throw new errors_1.UnsupportedError('not implemented');
}
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
async getSenderStats(localId) {
- this._assertSendDirection();
- const transceiver = this._mapMidTransceiver.get(localId);
- if (!transceiver)
- throw new Error('associated RTCRtpTransceiver not found');
- return transceiver.sender.getStats();
+ throw new errors_1.UnsupportedError('not implemented');
}
async sendDataChannel({ ordered, maxPacketLifeTime, maxRetransmits, label, protocol }) {
- this._assertSendDirection();
+ var _a;
+ this.assertSendDirection();
const options = {
negotiated: true,
id: this._nextSendSctpStreamId,
ordered,
maxPacketLifeTime,
+ maxRetransmitTime: maxPacketLifeTime,
maxRetransmits,
protocol
};
@@ -5981,8 +8279,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: '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 });
@@ -5999,54 +8301,84 @@ 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 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);
- 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 { 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: '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 { 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(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 { 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);
@@ -6054,21 +8386,30 @@ class Firefox60 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
+ 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) {
- this._assertRecvDirection();
- const transceiver = this._mapMidTransceiver.get(localId);
- if (!transceiver)
- throw new Error('associated RTCRtpTransceiver not found');
- return transceiver.receiver.getStats();
+ throw new errors_1.UnsupportedError('not implemented');
}
async receiveDataChannel({ sctpStreamParameters, label, protocol }) {
- this._assertRecvDirection();
+ var _a;
+ this.assertRecvDirection();
const { streamId, ordered, maxPacketLifeTime, maxRetransmits } = sctpStreamParameters;
const options = {
negotiated: true,
id: streamId,
ordered,
maxPacketLifeTime,
+ maxRetransmitTime: maxPacketLifeTime,
maxRetransmits,
protocol
};
@@ -6077,14 +8418,17 @@ class Firefox60 extends HandlerInterface_1.HandlerInterface {
// If this is the first DataChannel we need to create the SDP offer with
// m=application section.
if (!this._hasDataChannelMediaSection) {
- this._remoteSdp.receiveSctpAssociation();
+ 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: '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);
@@ -6092,9 +8436,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.
@@ -6102,47 +8447,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.ReactNative = ReactNative;
-},{"../EnhancedEventEmitter":12}],26:[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}],32:[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];
@@ -6155,35 +8486,37 @@ 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;
};
Object.defineProperty(exports, "__esModule", { value: true });
-exports.ReactNative = void 0;
+exports.ReactNativeUnifiedPlan = 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 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('ReactNative');
+const scalabilityModes_1 = require("../scalabilityModes");
+const logger = new Logger_1.Logger('ReactNativeUnifiedPlan');
const SCTP_NUM_STREAMS = { OS: 1024, MIS: 1024 };
-class ReactNative extends HandlerInterface_1.HandlerInterface {
+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();
- // 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.
@@ -6191,20 +8524,15 @@ 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 'ReactNativeUnifiedPlan';
}
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 {
@@ -6212,6 +8540,7 @@ class ReactNative extends HandlerInterface_1.HandlerInterface {
}
catch (error) { }
}
+ this.emit('@close');
}
async getNativeRtpCapabilities() {
logger.debug('getNativeRtpCapabilities()');
@@ -6220,19 +8549,20 @@ class ReactNative extends HandlerInterface_1.HandlerInterface {
iceTransportPolicy: 'all',
bundlePolicy: 'max-bundle',
rtcpMuxPolicy: 'require',
- sdpSemantics: 'plan-b'
+ sdpSemantics: 'unified-plan'
});
try {
- const offer = await pc.createOffer({
- offerToReceiveAudio: true,
- offerToReceiveVideo: true
- });
+ 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) {
@@ -6252,45 +8582,63 @@ class ReactNative extends HandlerInterface_1.HandlerInterface {
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)
- };
- this._pc = new RTCPeerConnection(Object.assign({ 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;
- }
+ 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()');
@@ -6302,8 +8650,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);
@@ -6325,53 +8674,84 @@ class ReactNative 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 (codec) {
- logger.warn('send() | codec selection is not available in %s handler', this.name);
+ if (encodings && encodings.length > 1) {
+ encodings.forEach((encoding, idx) => {
+ encoding.rid = `r${idx}`;
+ });
}
- 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], {});
+ // This may throw.
sendingRtpParameters.codecs =
- ortc.reduceCodecs(sendingRtpParameters.codecs);
+ ortc.reduceCodecs(sendingRtpParameters.codecs, codec);
const sendingRemoteRtpParameters = utils.clone(this._sendingRemoteRtpParametersByKind[track.kind], {});
+ // This may throw.
sendingRemoteRtpParameters.codecs =
- ortc.reduceCodecs(sendingRemoteRtpParameters.codecs);
- if (!this._transportReady)
- await this._setupTransport({ localDtlsRole: 'server', localSdpObject });
- if (track.kind === 'video' && encodings && encodings.length > 1) {
- logger.debug('send() | enabling simulcast');
+ 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
- .find((m) => m.type === 'video');
- sdpPlanBUtils.addLegacySimulcast({
+ offerMediaObject = localSdpObject.media[mediaSectionIdx.idx];
+ sdpUnifiedPlanUtils.addLegacySimulcast({
offerMediaObject,
- track,
- numStreams: encodings.length
+ 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
- .find((m) => m.type === track.kind);
+ offerMediaObject = localSdpObject.media[mediaSectionIdx.idx];
// 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]);
+ // 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.
@@ -6379,81 +8759,164 @@ class ReactNative extends HandlerInterface_1.HandlerInterface {
(sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/vp8' ||
sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/h264')) {
for (const encoding of sendingRtpParameters.encodings) {
- encoding.scalabilityMode = 'S1T3';
+ if (encoding.scalabilityMode) {
+ encoding.scalabilityMode = `L1T${layers.temporalLayers}`;
+ }
+ else {
+ encoding.scalabilityMode = 'L1T3';
+ }
}
}
this._remoteSdp.send({
offerMediaObject,
+ reuseMid: mediaSectionIdx.reuseMid,
offerRtpParameters: sendingRtpParameters,
answerRtpParameters: sendingRemoteRtpParameters,
- codecOptions
+ 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);
- const localId = String(this._nextSendLocalId);
- this._nextSendLocalId++;
- // Insert into the map.
- this._mapSendLocalIdTrack.set(localId, track);
+ // Store in the map.
+ this._mapMidTransceiver.set(localId, transceiver);
return {
- localId: localId,
- rtpParameters: sendingRtpParameters
+ localId,
+ rtpParameters: sendingRtpParameters,
+ rtpSender: transceiver.sender
};
}
async stopSending(localId) {
- this._assertSendDirection();
+ 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);
+ const transceiver = this._mapMidTransceiver.get(localId);
+ if (!transceiver) {
+ throw new Error('associated RTCRtpTransceiver not found');
}
- 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;
+ transceiver.sender.replaceTrack(null);
+ this._pc.removeTrack(transceiver.sender);
+ const mediaSectionClosed = this._remoteSdp.closeMediaSection(transceiver.mid);
+ if (mediaSectionClosed) {
+ try {
+ transceiver.stop();
}
- throw error;
+ catch (error) { }
}
- if (this._pc.signalingState === 'stable')
- return;
+ 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(
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
- localId, track) {
- throw new errors_1.UnsupportedError('not implemented');
+ 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);
}
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
async setMaxSpatialLayer(localId, spatialLayer) {
- throw new errors_1.UnsupportedError('not implemented');
+ 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);
}
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
async setRtpEncodingParameters(localId, params) {
- throw new errors_1.UnsupportedError('not implemented');
+ 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);
}
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
async getSenderStats(localId) {
- throw new errors_1.UnsupportedError('not implemented');
+ 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 }) {
- this._assertSendDirection();
+ var _a;
+ this.assertSendDirection();
const options = {
negotiated: true,
id: this._nextSendSctpStreamId,
ordered,
maxPacketLifeTime,
- maxRetransmitTime: maxPacketLifeTime,
maxRetransmits,
protocol
};
@@ -6469,8 +8932,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 });
@@ -6487,79 +8954,143 @@ class ReactNative 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 = 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
- });
+ 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('receive() | calling pc.setRemoteDescription() [offer:%o]', offer);
+ logger.debug('pauseReceiving() | 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: 'client', localSdpObject });
- logger.debug('receive() | calling pc.setLocalDescription() [answer:%o]', answer);
+ const answer = await this._pc.createAnswer();
+ logger.debug('pauseReceiving() | 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 });
+ }
+ 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('stopReceiving() | calling pc.setRemoteDescription() [offer:%o]', offer);
+ logger.debug('resumeReceiving() | 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);
+ logger.debug('resumeReceiving() | calling pc.setLocalDescription() [answer:%o]', answer);
await this._pc.setLocalDescription(answer);
}
- // eslint-disable-next-line @typescript-eslint/no-unused-vars
async getReceiverStats(localId) {
- throw new errors_1.UnsupportedError('not implemented');
+ 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 }) {
- this._assertRecvDirection();
+ var _a;
+ this.assertRecvDirection();
const { streamId, ordered, maxPacketLifeTime, maxRetransmits } = sctpStreamParameters;
const options = {
negotiated: true,
id: streamId,
ordered,
maxPacketLifeTime,
- maxRetransmitTime: maxPacketLifeTime,
maxRetransmits,
protocol
};
@@ -6568,14 +9099,17 @@ class ReactNative extends HandlerInterface_1.HandlerInterface {
// 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 });
+ 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 });
+ 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);
@@ -6583,9 +9117,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.
@@ -6593,27 +9128,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.ReactNativeUnifiedPlan = ReactNativeUnifiedPlan;
-},{"../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,"../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;
- 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];
@@ -6626,7 +9167,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;
};
@@ -6643,6 +9184,12 @@ 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.
@@ -6661,12 +9208,6 @@ 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';
}
@@ -6679,6 +9220,7 @@ class Safari11 extends HandlerInterface_1.HandlerInterface {
}
catch (error) { }
}
+ this.emit('@close');
}
async getNativeRtpCapabilities() {
logger.debug('getNativeRtpCapabilities()');
@@ -6736,28 +9278,46 @@ 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);
- // 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 (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()');
@@ -6769,8 +9329,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);
@@ -6792,7 +9353,8 @@ class Safari11 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 (codec) {
logger.warn('send() | codec selection is not available in %s handler', this.name);
@@ -6808,8 +9370,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);
@@ -6835,8 +9401,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
@@ -6844,7 +9411,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({
@@ -6869,12 +9436,14 @@ class Safari11 extends HandlerInterface_1.HandlerInterface {
};
}
async stopSending(localId) {
- this._assertSendDirection();
+ this.assertSendDirection();
const rtpSender = this._mapSendLocalIdRtpSender.get(localId);
- if (!rtpSender)
+ if (!rtpSender) {
throw new Error('associated RTCRtpSender not found');
- if (rtpSender.track)
+ }
+ 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);
@@ -6890,14 +9459,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);
}
@@ -6905,53 +9483,62 @@ 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] = Object.assign(Object.assign({}, encoding), params);
+ parameters.encodings[idx] = { ...encoding, ...params };
});
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 }) {
- this._assertSendDirection();
+ var _a;
+ this.assertSendDirection();
const options = {
negotiated: true,
id: this._nextSendSctpStreamId,
@@ -6972,8 +9559,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 });
@@ -6990,55 +9581,76 @@ class Safari11 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 = trackId;
- const mid = kind;
- this._remoteSdp.receive({
- mid,
- kind,
- offerRtpParameters: rtpParameters,
- streamId: rtpParameters.rtcp.cname,
- trackId
- });
+ 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);
- 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({ 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()
- .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 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(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);
@@ -7047,14 +9659,26 @@ class Safari11 extends HandlerInterface_1.HandlerInterface {
await this._pc.setLocalDescription(answer);
}
async getReceiverStats(localId) {
- this._assertRecvDirection();
+ this.assertRecvDirection();
const { rtpReceiver } = this._mapRecvLocalIdInfo.get(localId) || {};
- if (!rtpReceiver)
+ 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 }) {
- this._assertRecvDirection();
+ var _a;
+ this.assertRecvDirection();
const { streamId, ordered, maxPacketLifeTime, maxRetransmits } = sctpStreamParameters;
const options = {
negotiated: true,
@@ -7076,7 +9700,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);
@@ -7084,9 +9711,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.
@@ -7094,15 +9722,17 @@ 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');
}
@@ -7110,11 +9740,15 @@ class Safari11 extends HandlerInterface_1.HandlerInterface {
}
exports.Safari11 = Safari11;
-},{"../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}],34:[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];
@@ -7127,7 +9761,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;
};
@@ -7139,11 +9773,19 @@ 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.
@@ -7157,12 +9799,6 @@ 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';
}
@@ -7175,6 +9811,7 @@ class Safari12 extends HandlerInterface_1.HandlerInterface {
}
catch (error) { }
}
+ this.emit('@close');
}
async getNativeRtpCapabilities() {
logger.debug('getNativeRtpCapabilities()');
@@ -7194,6 +9831,8 @@ class Safari12 extends HandlerInterface_1.HandlerInterface {
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) {
@@ -7229,28 +9868,46 @@ 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);
- // 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 (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()');
@@ -7262,8 +9919,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);
@@ -7285,7 +9943,8 @@ class Safari12 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);
const sendingRtpParameters = utils.clone(this._sendingRtpParametersByKind[track.kind], {});
// This may throw.
@@ -7300,8 +9959,13 @@ 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
+ });
+ }
+ 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);
@@ -7329,8 +9993,9 @@ 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]);
+ }
}
}
// If VP8 or H264 and there is effective simulcast, add scalabilityMode to
@@ -7339,7 +10004,12 @@ class Safari12 extends HandlerInterface_1.HandlerInterface {
(sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/vp8' ||
sendingRtpParameters.codecs[0].mimeType.toLowerCase() === 'video/h264')) {
for (const encoding of sendingRtpParameters.encodings) {
- encoding.scalabilityMode = 'S1T3';
+ if (encoding.scalabilityMode) {
+ encoding.scalabilityMode = `L1T${layers.temporalLayers}`;
+ }
+ else {
+ encoding.scalabilityMode = 'L1T3';
+ }
}
}
this._remoteSdp.send({
@@ -7361,23 +10031,65 @@ 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);
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();
+ this.assertSendDirection();
if (track) {
logger.debug('replaceTrack() [localId:%s, track.id:%s]', localId, track.id);
}
@@ -7385,46 +10097,67 @@ 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] = Object.assign(Object.assign({}, encoding), params);
+ 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,
@@ -7445,8 +10178,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 });
@@ -7463,70 +10200,135 @@ class Safari12 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');
+ }
+ // 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);
+ 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('stopReceiving() | calling pc.setRemoteDescription() [offer:%o]', offer);
+ logger.debug('resumeReceiving() | 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);
+ logger.debug('resumeReceiving() | calling pc.setLocalDescription() [answer:%o]', answer);
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,
@@ -7548,7 +10350,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);
@@ -7556,9 +10361,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.
@@ -7566,15 +10372,17 @@ 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');
}
@@ -7582,11 +10390,15 @@ class Safari12 extends HandlerInterface_1.HandlerInterface {
}
exports.Safari12 = Safari12;
-},{"../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,"./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;
- 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];
@@ -7599,7 +10411,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;
};
@@ -7622,15 +10434,18 @@ function getCapabilities() {
// NOTE: Edge sets some numeric parameters as string rather than number. Fix them.
if (codec.parameters) {
const parameters = codec.parameters;
- if (parameters.apt)
+ if (parameters.apt) {
parameters.apt = Number(parameters.apt);
- if (parameters['packetization-mode'])
+ }
+ 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)
+ if (!feedback.parameter) {
feedback.parameter = '';
+ }
}
}
return caps;
@@ -7653,8 +10468,9 @@ function mangleRtpParameters(rtpParameters) {
delete codec.channels;
}
// Add codec.name (requried by Edge).
- if (codec.mimeType && !codec.name)
+ if (codec.mimeType && !codec.name) {
codec.name = codec.mimeType.split('/')[1];
+ }
// Remove mimeType.
delete codec.mimeType;
}
@@ -7662,11 +10478,37 @@ function mangleRtpParameters(rtpParameters) {
}
exports.mangleRtpParameters = mangleRtpParameters;
-},{"../../utils":39}],30:[function(require,module,exports){
+},{"../../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;
+
+},{}],37:[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];
@@ -7679,12 +10521,13 @@ 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;
};
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 }) {
@@ -7706,8 +10549,9 @@ class MediaSection {
candidateObject.priority = candidate.priority;
candidateObject.transport = candidate.protocol;
candidateObject.type = candidate.type;
- if (candidate.tcpType)
+ if (candidate.tcpType) {
candidateObject.tcptype = candidate.tcpType;
+ }
this._mediaObject.candidates.push(candidateObject);
}
this._mediaObject.endOfCandidates = 'end-of-candidates';
@@ -7730,25 +10574,22 @@ class MediaSection {
this._mediaObject.iceUfrag = iceParameters.usernameFragment;
this._mediaObject.icePwd = iceParameters.password;
}
- disable() {
+ 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._mediaObject.direction = 'inactive';
+ this.disable();
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;
}
}
exports.MediaSection = MediaSection;
@@ -7784,16 +10625,19 @@ class AnswerMediaSection extends MediaSection {
codec: getCodecName(codec),
rate: codec.clockRate
};
- if (codec.channels > 1)
+ 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, videoGoogleStartBitrate, videoGoogleMaxBitrate, videoGoogleMinBitrate } = 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;
@@ -7817,6 +10661,16 @@ class AnswerMediaSection extends MediaSection {
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':
@@ -7824,12 +10678,15 @@ class AnswerMediaSection extends MediaSection {
case 'video/h264':
case 'video/h265':
{
- if (videoGoogleStartBitrate !== undefined)
+ if (videoGoogleStartBitrate !== undefined) {
codecParameters['x-google-start-bitrate'] = videoGoogleStartBitrate;
- if (videoGoogleMaxBitrate !== undefined)
+ }
+ if (videoGoogleMaxBitrate !== undefined) {
codecParameters['x-google-max-bitrate'] = videoGoogleMaxBitrate;
- if (videoGoogleMinBitrate !== undefined)
+ }
+ if (videoGoogleMinBitrate !== undefined) {
codecParameters['x-google-min-bitrate'] = videoGoogleMinBitrate;
+ }
break;
}
}
@@ -7839,13 +10696,15 @@ class AnswerMediaSection extends MediaSection {
config: ''
};
for (const key of Object.keys(codecParameters)) {
- if (fmtp.config)
+ if (fmtp.config) {
fmtp.config += ';';
+ }
fmtp.config += `${key}=${codecParameters[key]}`;
}
- if (fmtp.config)
+ if (fmtp.config) {
this._mediaObject.fmtp.push(fmtp);
- for (const fb of codec.rtcpFeedback) {
+ }
+ for (const fb of codecRtcpFeedback) {
this._mediaObject.rtcpFb.push({
payload: codec.payloadType,
type: fb.type,
@@ -7861,8 +10720,9 @@ class AnswerMediaSection extends MediaSection {
// Don't add a header extension if not present in the offer.
const found = (offerMediaObject.ext || [])
.some((localExt) => localExt.uri === ext.uri);
- if (!found)
+ if (!found) {
continue;
+ }
this._mediaObject.ext.push({
uri: ext.uri,
value: ext.id
@@ -7882,8 +10742,9 @@ class AnswerMediaSection extends MediaSection {
};
this._mediaObject.rids = [];
for (const rid of offerMediaObject.rids || []) {
- if (rid.direction !== 'send')
+ if (rid.direction !== 'send') {
continue;
+ }
this._mediaObject.rids.push({
id: rid.id,
direction: 'recv'
@@ -7899,8 +10760,9 @@ class AnswerMediaSection extends MediaSection {
};
this._mediaObject.rids = [];
for (const rid of offerMediaObject.rids || []) {
- if (rid.direction !== 'send')
+ if (rid.direction !== 'send') {
continue;
+ }
this._mediaObject.rids.push({
id: rid.id,
direction: 'recv'
@@ -7909,8 +10771,9 @@ class AnswerMediaSection extends MediaSection {
}
this._mediaObject.rtcpMux = 'rtcp-mux';
this._mediaObject.rtcpRsize = 'rtcp-rsize';
- if (this._planB && this._mediaObject.type === 'video')
+ if (this._planB && this._mediaObject.type === 'video') {
this._mediaObject.xGoogleFlag = 'conference';
+ }
break;
}
case 'application':
@@ -7948,6 +10811,32 @@ class AnswerMediaSection extends MediaSection {
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 {
@@ -7957,10 +10846,12 @@ class OfferMediaSection extends MediaSection {
this._mediaObject.type = kind;
if (!plainRtpParameters) {
this._mediaObject.connection = { ip: '127.0.0.1', version: 4 };
- if (!sctpParameters)
+ if (!sctpParameters) {
this._mediaObject.protocol = 'UDP/TLS/RTP/SAVPF';
- else
+ }
+ else {
this._mediaObject.protocol = 'UDP/DTLS/SCTP';
+ }
this._mediaObject.port = 7;
}
else {
@@ -7980,28 +10871,32 @@ class OfferMediaSection extends MediaSection {
this._mediaObject.rtp = [];
this._mediaObject.rtcpFb = [];
this._mediaObject.fmtp = [];
- if (!this._planB)
+ 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)
+ 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)
+ if (fmtp.config) {
fmtp.config += ';';
+ }
fmtp.config += `${key}=${codec.parameters[key]}`;
}
- if (fmtp.config)
+ if (fmtp.config) {
this._mediaObject.fmtp.push(fmtp);
+ }
for (const fb of codec.rtcpFeedback) {
this._mediaObject.rtcpFb.push({
payload: codec.payloadType,
@@ -8093,6 +10988,9 @@ class OfferMediaSection extends MediaSection {
// Always 'actpass'.
this._mediaObject.setup = 'actpass';
}
+ resume() {
+ this._mediaObject.direction = 'sendonly';
+ }
planBReceive({ offerRtpParameters, streamId, trackId }) {
const encoding = offerRtpParameters.encodings[0];
const ssrc = encoding.ssrc;
@@ -8109,20 +11007,23 @@ class OfferMediaSection extends MediaSection {
codec: getCodecName(codec),
rate: codec.clockRate
};
- if (codec.channels > 1)
+ 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)
+ if (fmtp.config) {
fmtp.config += ';';
+ }
fmtp.config += `${key}=${codec.parameters[key]}`;
}
- if (fmtp.config)
+ if (fmtp.config) {
this._mediaObject.fmtp.push(fmtp);
+ }
for (const fb of codec.rtcpFeedback) {
this._mediaObject.rtcpFb.push({
payload: codec.payloadType,
@@ -8187,16 +11088,21 @@ exports.OfferMediaSection = OfferMediaSection;
function getCodecName(codec) {
const MimeTypeRegex = new RegExp('^(audio|video)/(.+)', 'i');
const mimeTypeMatch = MimeTypeRegex.exec(codec.mimeType);
- if (!mimeTypeMatch)
+ if (!mimeTypeMatch) {
throw new TypeError('invalid codec.mimeType');
+ }
return mimeTypeMatch[2];
}
-},{"../../utils":39}],31:[function(require,module,exports){
+},{"../../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;
- 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];
@@ -8209,7 +11115,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;
};
@@ -8287,8 +11193,9 @@ class RemoteSdp {
// 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)
+ if (mediaSection.closed) {
return { idx, reuseMid: mediaSection.mid };
+ }
}
// If no closed media section is found, return next one.
return { idx: this._mediaSections.length };
@@ -8322,8 +11229,9 @@ class RemoteSdp {
receive({ mid, kind, offerRtpParameters, streamId, trackId }) {
const idx = this._midToIndex.get(mid);
let mediaSection;
- if (idx !== undefined)
+ if (idx !== undefined) {
mediaSection = this._mediaSections[idx];
+ }
// Unified-Plan or different media kind.
if (!mediaSection) {
mediaSection = new MediaSection_1.OfferMediaSection({
@@ -8354,37 +11262,50 @@ class RemoteSdp {
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 idx = this._midToIndex.get(mid);
- if (idx === undefined) {
- throw new Error(`no media section found with mid '${mid}'`);
- }
- const mediaSection = this._mediaSections[idx];
+ 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 idx = this._midToIndex.get(mid);
- if (idx === undefined) {
- throw new Error(`no media section found with mid '${mid}'`);
- }
- const mediaSection = this._mediaSections[idx];
+ 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;
+ 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 idx = this._midToIndex.get(mid);
- if (idx === undefined) {
- throw new Error(`no media section found with mid '${mid}'`);
- }
- const mediaSection = this._mediaSections[idx];
+ const mediaSection = this._findMediaSection(mid);
mediaSection.planBStopReceiving({ offerRtpParameters });
this._replaceMediaSection(mediaSection);
}
@@ -8418,8 +11339,9 @@ class RemoteSdp {
return sdpTransform.write(this._sdpObject);
}
_addMediaSection(newMediaSection) {
- if (!this._firstMid)
+ if (!this._firstMid) {
this._firstMid = newMediaSection.mid;
+ }
// Add to the vector.
this._mediaSections.push(newMediaSection);
// Add to the map.
@@ -8458,9 +11380,17 @@ class RemoteSdp {
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)
+ if (!this._dtlsParameters) {
return;
+ }
this._sdpObject.groups[0].mids = this._mediaSections
.filter((mediaSection) => !mediaSection.closed)
.map((mediaSection) => mediaSection.mid)
@@ -8469,11 +11399,15 @@ class RemoteSdp {
}
exports.RemoteSdp = RemoteSdp;
-},{"../../Logger":13,"./MediaSection":30,"sdp-transform":44}],32:[function(require,module,exports){
+},{"../../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;
- 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];
@@ -8486,13 +11420,17 @@ 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;
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.applyCodecParameters = exports.getCname = exports.extractDtlsParameters = exports.extractRtpCapabilities = void 0;
const sdpTransform = __importStar(require("sdp-transform"));
+/**
+ * This function must be called with an SDP with 1 m=audio and 1 m=video
+ * sections.
+ */
function extractRtpCapabilities({ sdpObject }) {
// Map of RtpCodecParameters indexed by payload type.
const codecsMap = new Map();
@@ -8506,15 +11444,17 @@ function extractRtpCapabilities({ sdpObject }) {
switch (kind) {
case 'audio':
{
- if (gotAudio)
+ if (gotAudio) {
continue;
+ }
gotAudio = true;
break;
}
case 'video':
{
- if (gotVideo)
+ if (gotVideo) {
continue;
+ }
gotVideo = true;
break;
}
@@ -8540,31 +11480,49 @@ function extractRtpCapabilities({ sdpObject }) {
for (const fmtp of m.fmtp || []) {
const parameters = sdpTransform.parseParams(fmtp.config);
const codec = codecsMap.get(fmtp.payload);
- if (!codec)
+ if (!codec) {
continue;
+ }
// Specials case to convert parameter value to string.
- if (parameters && parameters.hasOwnProperty('profile-level-id'))
+ 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 codec = codecsMap.get(fb.payload);
- if (!codec)
- continue;
const feedback = {
type: fb.type,
parameter: fb.subtype
};
- if (!feedback.parameter)
+ if (!feedback.parameter) {
delete feedback.parameter;
- codec.rtcpFeedback.push(feedback);
+ }
+ // 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'])
+ if (ext['encrypt-uri']) {
continue;
+ }
const headerExtension = {
kind: kind,
uri: ext.uri,
@@ -8583,8 +11541,9 @@ exports.extractRtpCapabilities = extractRtpCapabilities;
function extractDtlsParameters({ sdpObject }) {
const mediaObject = (sdpObject.media || [])
.find((m) => (m.iceUfrag && m.port !== 0));
- if (!mediaObject)
+ if (!mediaObject) {
throw new Error('no active media section found');
+ }
const fingerprint = mediaObject.fingerprint || sdpObject.fingerprint;
let role;
switch (mediaObject.setup) {
@@ -8613,8 +11572,9 @@ exports.extractDtlsParameters = extractDtlsParameters;
function getCname({ offerMediaObject }) {
const ssrcCnameLine = (offerMediaObject.ssrcs || [])
.find((line) => line.attribute === 'cname');
- if (!ssrcCnameLine)
+ if (!ssrcCnameLine) {
return '';
+ }
return ssrcCnameLine.value;
}
exports.getCname = getCname;
@@ -8626,12 +11586,14 @@ 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')
+ if (mimeType !== 'audio/opus') {
continue;
+ }
const rtp = (answerMediaObject.rtp || [])
.find((r) => r.payload === codec.payloadType);
- if (!rtp)
+ if (!rtp) {
continue;
+ }
// Just in case.
answerMediaObject.fmtp = answerMediaObject.fmtp || [];
let fmtp = answerMediaObject.fmtp
@@ -8645,23 +11607,25 @@ function applyCodecParameters({ offerRtpParameters, answerMediaObject }) {
case 'audio/opus':
{
const spropStereo = codec.parameters['sprop-stereo'];
- if (spropStereo !== undefined)
+ 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)
+ if (fmtp.config) {
fmtp.config += ';';
+ }
fmtp.config += `${key}=${parameters[key]}`;
}
}
}
exports.applyCodecParameters = applyCodecParameters;
-},{"sdp-transform":44}],33:[function(require,module,exports){
+},{"sdp-transform":52}],40:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.addLegacySimulcast = exports.getRtpEncodings = void 0;
@@ -8670,23 +11634,27 @@ function getRtpEncodings({ offerMediaObject, track }) {
let firstSsrc;
const ssrcs = new Set();
for (const line of offerMediaObject.ssrcs || []) {
- if (line.attribute !== 'msid')
+ if (line.attribute !== 'msid') {
continue;
+ }
const trackId = line.value.split(' ')[1];
if (trackId === track.id) {
const ssrc = line.id;
ssrcs.add(ssrc);
- if (!firstSsrc)
+ if (!firstSsrc) {
firstSsrc = ssrc;
+ }
}
}
- if (ssrcs.size === 0)
+ 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')
+ if (line.semantics !== 'FID') {
continue;
+ }
let [ssrc, rtxSsrc] = line.ssrcs.split(/\s+/);
ssrc = Number(ssrc);
rtxSsrc = Number(rtxSsrc);
@@ -8708,8 +11676,9 @@ function getRtpEncodings({ offerMediaObject, track }) {
const encodings = [];
for (const [ssrc, rtxSsrc] of ssrcToRtxSsrc) {
const encoding = { ssrc };
- if (rtxSsrc)
+ if (rtxSsrc) {
encoding.rtx = { ssrc: rtxSsrc };
+ }
encodings.push(encoding);
}
return encodings;
@@ -8719,16 +11688,18 @@ exports.getRtpEncodings = getRtpEncodings;
* Adds multi-ssrc based simulcast into the given SDP media section offer.
*/
function addLegacySimulcast({ offerMediaObject, track, numStreams }) {
- if (numStreams <= 1)
+ 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')
+ if (line.attribute !== 'msid') {
return false;
+ }
const trackId = line.value.split(' ')[1];
if (trackId === track.id) {
firstSsrc = line.id;
@@ -8739,13 +11710,15 @@ function addLegacySimulcast({ offerMediaObject, track, numStreams }) {
return false;
}
});
- if (!ssrcMsidLine)
+ 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')
+ if (line.semantics !== 'FID') {
return false;
+ }
const ssrcs = line.ssrcs.split(/\s+/);
if (Number(ssrcs[0]) === firstSsrc) {
firstRtxSsrc = Number(ssrcs[1]);
@@ -8757,15 +11730,17 @@ function addLegacySimulcast({ offerMediaObject, track, numStreams }) {
});
const ssrcCnameLine = offerMediaObject.ssrcs
.find((line) => (line.attribute === 'cname' && line.id === firstSsrc));
- if (!ssrcCnameLine)
+ 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)
+ if (firstRtxSsrc) {
rtxSsrcs.push(firstRtxSsrc + i);
+ }
}
offerMediaObject.ssrcGroups = offerMediaObject.ssrcGroups || [];
offerMediaObject.ssrcs = offerMediaObject.ssrcs || [];
@@ -8807,7 +11782,7 @@ function addLegacySimulcast({ offerMediaObject, track, numStreams }) {
}
exports.addLegacySimulcast = addLegacySimulcast;
-},{}],34:[function(require,module,exports){
+},{}],41:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.addLegacySimulcast = exports.getRtpEncodings = void 0;
@@ -8817,27 +11792,29 @@ function getRtpEncodings({ offerMediaObject }) {
const ssrc = line.id;
ssrcs.add(ssrc);
}
- if (ssrcs.size === 0)
+ 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')
+ 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.
+ // 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.
+ // 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);
@@ -8845,8 +11822,9 @@ function getRtpEncodings({ offerMediaObject }) {
const encodings = [];
for (const [ssrc, rtxSsrc] of ssrcToRtxSsrc) {
const encoding = { ssrc };
- if (rtxSsrc)
+ if (rtxSsrc) {
encoding.rtx = { ssrc: rtxSsrc };
+ }
encodings.push(encoding);
}
return encodings;
@@ -8856,21 +11834,24 @@ exports.getRtpEncodings = getRtpEncodings;
* Adds multi-ssrc based simulcast into the given SDP media section offer.
*/
function addLegacySimulcast({ offerMediaObject, numStreams }) {
- if (numStreams <= 1)
+ 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)
+ 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')
+ if (line.semantics !== 'FID') {
return false;
+ }
const ssrcs = line.ssrcs.split(/\s+/);
if (Number(ssrcs[0]) === firstSsrc) {
firstRtxSsrc = Number(ssrcs[1]);
@@ -8882,15 +11863,17 @@ function addLegacySimulcast({ offerMediaObject, numStreams }) {
});
const ssrcCnameLine = offerMediaObject.ssrcs
.find((line) => line.attribute === 'cname');
- if (!ssrcCnameLine)
+ 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)
+ if (firstRtxSsrc) {
rtxSsrcs.push(firstRtxSsrc + i);
+ }
}
offerMediaObject.ssrcGroups = [];
offerMediaObject.ssrcs = [];
@@ -8932,11 +11915,15 @@ function addLegacySimulcast({ offerMediaObject, numStreams }) {
}
exports.addLegacySimulcast = addLegacySimulcast;
-},{}],35:[function(require,module,exports){
+},{}],42:[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];
@@ -8949,7 +11936,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 +11944,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,18 +11955,22 @@ exports.types = types;
/**
* Expose mediasoup-client version.
*/
-exports.version = '3.6.37';
+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; } });
-},{"./Device":11,"./scalabilityModes":37,"./types":38,"debug":40}],36:[function(require,module,exports){
+},{"./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;
- 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];
@@ -8992,7 +11983,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;
};
@@ -9009,21 +12000,26 @@ const RTP_PROBATOR_CODEC_PAYLOAD_TYPE = 127;
* It throws if invalid.
*/
function validateRtpCapabilities(caps) {
- if (typeof caps !== 'object')
+ 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))
+ if (caps.codecs && !Array.isArray(caps.codecs)) {
throw new TypeError('caps.codecs is not an array');
- else if (!caps.codecs)
+ }
+ 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))
+ if (caps.headerExtensions && !Array.isArray(caps.headerExtensions)) {
throw new TypeError('caps.headerExtensions is not an array');
- else if (!caps.headerExtensions)
+ }
+ else if (!caps.headerExtensions) {
caps.headerExtensions = [];
+ }
for (const ext of caps.headerExtensions) {
validateRtpHeaderExtension(ext);
}
@@ -9036,33 +12032,40 @@ exports.validateRtpCapabilities = validateRtpCapabilities;
*/
function validateRtpCodecCapability(codec) {
const MimeTypeRegex = new RegExp('^(audio|video)/(.+)', 'i');
- if (typeof codec !== 'object')
+ if (typeof codec !== 'object') {
throw new TypeError('codec is not an object');
+ }
// mimeType is mandatory.
- if (!codec.mimeType || typeof codec.mimeType !== 'string')
+ if (!codec.mimeType || typeof codec.mimeType !== 'string') {
throw new TypeError('missing codec.mimeType');
+ }
const mimeTypeMatch = MimeTypeRegex.exec(codec.mimeType);
- if (!mimeTypeMatch)
+ 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')
+ if (codec.preferredPayloadType && typeof codec.preferredPayloadType !== 'number') {
throw new TypeError('invalid codec.preferredPayloadType');
+ }
// clockRate is mandatory.
- if (typeof codec.clockRate !== 'number')
+ 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')
+ 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')
+ 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) {
@@ -9074,13 +12077,15 @@ function validateRtpCodecCapability(codec) {
}
// Specific parameters validation.
if (key === 'apt') {
- if (typeof value !== 'number')
+ 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))
+ if (!codec.rtcpFeedback || !Array.isArray(codec.rtcpFeedback)) {
codec.rtcpFeedback = [];
+ }
for (const fb of codec.rtcpFeedback) {
validateRtcpFeedback(fb);
}
@@ -9092,14 +12097,17 @@ exports.validateRtpCodecCapability = validateRtpCodecCapability;
* It throws if invalid.
*/
function validateRtcpFeedback(fb) {
- if (typeof fb !== 'object')
+ if (typeof fb !== 'object') {
throw new TypeError('fb is not an object');
+ }
// type is mandatory.
- if (!fb.type || typeof fb.type !== 'string')
+ 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')
+ if (!fb.parameter || typeof fb.parameter !== 'string') {
fb.parameter = '';
+ }
}
exports.validateRtcpFeedback = validateRtcpFeedback;
/**
@@ -9108,27 +12116,35 @@ exports.validateRtcpFeedback = validateRtcpFeedback;
* It throws if invalid.
*/
function validateRtpHeaderExtension(ext) {
- if (typeof ext !== 'object')
+ if (typeof ext !== 'object') {
throw new TypeError('ext is not an object');
+ }
// kind is mandatory.
- if (ext.kind !== 'audio' && ext.kind !== 'video')
+ if (ext.kind !== 'audio' && ext.kind !== 'video') {
throw new TypeError('invalid ext.kind');
+ }
// uri is mandatory.
- if (!ext.uri || typeof ext.uri !== 'string')
+ if (!ext.uri || typeof ext.uri !== 'string') {
throw new TypeError('missing ext.uri');
+ }
// preferredId is mandatory.
- if (typeof ext.preferredId !== 'number')
+ 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')
+ if (ext.preferredEncrypt && typeof ext.preferredEncrypt !== 'boolean') {
throw new TypeError('invalid ext.preferredEncrypt');
- else if (!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')
+ if (ext.direction && typeof ext.direction !== 'string') {
throw new TypeError('invalid ext.direction');
- else if (!ext.direction)
+ }
+ else if (!ext.direction) {
ext.direction = 'sendrecv';
+ }
}
exports.validateRtpHeaderExtension = validateRtpHeaderExtension;
/**
@@ -9137,38 +12153,47 @@ exports.validateRtpHeaderExtension = validateRtpHeaderExtension;
* It throws if invalid.
*/
function validateRtpParameters(params) {
- if (typeof params !== 'object')
+ if (typeof params !== 'object') {
throw new TypeError('params is not an object');
+ }
// mid is optional.
- if (params.mid && typeof params.mid !== 'string')
+ if (params.mid && typeof params.mid !== 'string') {
throw new TypeError('params.mid is not a string');
+ }
// codecs is mandatory.
- if (!Array.isArray(params.codecs))
+ 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))
+ if (params.headerExtensions && !Array.isArray(params.headerExtensions)) {
throw new TypeError('params.headerExtensions is not an array');
- else if (!params.headerExtensions)
+ }
+ 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))
+ if (params.encodings && !Array.isArray(params.encodings)) {
throw new TypeError('params.encodings is not an array');
- else if (!params.encodings)
+ }
+ 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')
+ if (params.rtcp && typeof params.rtcp !== 'object') {
throw new TypeError('params.rtcp is not an object');
- else if (!params.rtcp)
+ }
+ else if (!params.rtcp) {
params.rtcp = {};
+ }
validateRtcpParameters(params.rtcp);
}
exports.validateRtpParameters = validateRtpParameters;
@@ -9179,32 +12204,39 @@ exports.validateRtpParameters = validateRtpParameters;
*/
function validateRtpCodecParameters(codec) {
const MimeTypeRegex = new RegExp('^(audio|video)/(.+)', 'i');
- if (typeof codec !== 'object')
+ if (typeof codec !== 'object') {
throw new TypeError('codec is not an object');
+ }
// mimeType is mandatory.
- if (!codec.mimeType || typeof codec.mimeType !== 'string')
+ if (!codec.mimeType || typeof codec.mimeType !== 'string') {
throw new TypeError('missing codec.mimeType');
+ }
const mimeTypeMatch = MimeTypeRegex.exec(codec.mimeType);
- if (!mimeTypeMatch)
+ if (!mimeTypeMatch) {
throw new TypeError('invalid codec.mimeType');
+ }
// payloadType is mandatory.
- if (typeof codec.payloadType !== 'number')
+ if (typeof codec.payloadType !== 'number') {
throw new TypeError('missing codec.payloadType');
+ }
// clockRate is mandatory.
- if (typeof codec.clockRate !== 'number')
+ 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')
+ 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')
+ 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) {
@@ -9216,13 +12248,15 @@ function validateRtpCodecParameters(codec) {
}
// Specific parameters validation.
if (key === 'apt') {
- if (typeof value !== 'number')
+ 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))
+ if (!codec.rtcpFeedback || !Array.isArray(codec.rtcpFeedback)) {
codec.rtcpFeedback = [];
+ }
for (const fb of codec.rtcpFeedback) {
validateRtcpFeedback(fb);
}
@@ -9234,30 +12268,37 @@ exports.validateRtpCodecParameters = validateRtpCodecParameters;
* It throws if invalid.
*/
function validateRtpHeaderExtensionParameters(ext) {
- if (typeof ext !== 'object')
+ if (typeof ext !== 'object') {
throw new TypeError('ext is not an object');
+ }
// uri is mandatory.
- if (!ext.uri || typeof ext.uri !== 'string')
+ if (!ext.uri || typeof ext.uri !== 'string') {
throw new TypeError('missing ext.uri');
+ }
// id is mandatory.
- if (typeof ext.id !== 'number')
+ 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')
+ if (ext.encrypt && typeof ext.encrypt !== 'boolean') {
throw new TypeError('invalid ext.encrypt');
- else if (!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')
+ 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')
+ if (typeof value !== 'string' && typeof value !== 'number') {
throw new TypeError('invalid header extension parameter');
+ }
}
}
exports.validateRtpHeaderExtensionParameters = validateRtpHeaderExtensionParameters;
@@ -9267,29 +12308,35 @@ exports.validateRtpHeaderExtensionParameters = validateRtpHeaderExtensionParamet
* It throws if invalid.
*/
function validateRtpEncodingParameters(encoding) {
- if (typeof encoding !== 'object')
+ if (typeof encoding !== 'object') {
throw new TypeError('encoding is not an object');
+ }
// ssrc is optional.
- if (encoding.ssrc && typeof encoding.ssrc !== 'number')
+ if (encoding.ssrc && typeof encoding.ssrc !== 'number') {
throw new TypeError('invalid encoding.ssrc');
+ }
// rid is optional.
- if (encoding.rid && typeof encoding.rid !== 'string')
+ 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')
+ 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')
+ if (!encoding.dtx || typeof encoding.dtx !== 'boolean') {
encoding.dtx = false;
+ }
// scalabilityMode is optional.
- if (encoding.scalabilityMode && typeof encoding.scalabilityMode !== 'string')
+ if (encoding.scalabilityMode && typeof encoding.scalabilityMode !== 'string') {
throw new TypeError('invalid encoding.scalabilityMode');
+ }
}
exports.validateRtpEncodingParameters = validateRtpEncodingParameters;
/**
@@ -9298,14 +12345,17 @@ exports.validateRtpEncodingParameters = validateRtpEncodingParameters;
* It throws if invalid.
*/
function validateRtcpParameters(rtcp) {
- if (typeof rtcp !== 'object')
+ if (typeof rtcp !== 'object') {
throw new TypeError('rtcp is not an object');
+ }
// cname is optional.
- if (rtcp.cname && typeof rtcp.cname !== 'string')
+ 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')
+ if (!rtcp.reducedSize || typeof rtcp.reducedSize !== 'boolean') {
rtcp.reducedSize = true;
+ }
}
exports.validateRtcpParameters = validateRtcpParameters;
/**
@@ -9314,11 +12364,13 @@ exports.validateRtcpParameters = validateRtcpParameters;
* It throws if invalid.
*/
function validateSctpCapabilities(caps) {
- if (typeof caps !== 'object')
+ if (typeof caps !== 'object') {
throw new TypeError('caps is not an object');
+ }
// numStreams is mandatory.
- if (!caps.numStreams || typeof caps.numStreams !== 'object')
+ if (!caps.numStreams || typeof caps.numStreams !== 'object') {
throw new TypeError('missing caps.numStreams');
+ }
validateNumSctpStreams(caps.numStreams);
}
exports.validateSctpCapabilities = validateSctpCapabilities;
@@ -9328,14 +12380,17 @@ exports.validateSctpCapabilities = validateSctpCapabilities;
* It throws if invalid.
*/
function validateNumSctpStreams(numStreams) {
- if (typeof numStreams !== 'object')
+ if (typeof numStreams !== 'object') {
throw new TypeError('numStreams is not an object');
+ }
// OS is mandatory.
- if (typeof numStreams.OS !== 'number')
+ if (typeof numStreams.OS !== 'number') {
throw new TypeError('missing numStreams.OS');
+ }
// MIS is mandatory.
- if (typeof numStreams.MIS !== 'number')
+ if (typeof numStreams.MIS !== 'number') {
throw new TypeError('missing numStreams.MIS');
+ }
}
exports.validateNumSctpStreams = validateNumSctpStreams;
/**
@@ -9344,20 +12399,25 @@ exports.validateNumSctpStreams = validateNumSctpStreams;
* It throws if invalid.
*/
function validateSctpParameters(params) {
- if (typeof params !== 'object')
+ if (typeof params !== 'object') {
throw new TypeError('params is not an object');
+ }
// port is mandatory.
- if (typeof params.port !== 'number')
+ if (typeof params.port !== 'number') {
throw new TypeError('missing params.port');
+ }
// OS is mandatory.
- if (typeof params.OS !== 'number')
+ if (typeof params.OS !== 'number') {
throw new TypeError('missing params.OS');
+ }
// MIS is mandatory.
- if (typeof params.MIS !== 'number')
+ if (typeof params.MIS !== 'number') {
throw new TypeError('missing params.MIS');
+ }
// maxMessageSize is mandatory.
- if (typeof params.maxMessageSize !== 'number')
+ if (typeof params.maxMessageSize !== 'number') {
throw new TypeError('missing params.maxMessageSize');
+ }
}
exports.validateSctpParameters = validateSctpParameters;
/**
@@ -9366,25 +12426,32 @@ exports.validateSctpParameters = validateSctpParameters;
* It throws if invalid.
*/
function validateSctpStreamParameters(params) {
- if (typeof params !== 'object')
+ if (typeof params !== 'object') {
throw new TypeError('params is not an object');
+ }
// streamId is mandatory.
- if (typeof params.streamId !== 'number')
+ if (typeof params.streamId !== 'number') {
throw new TypeError('missing params.streamId');
+ }
// ordered is optional.
let orderedGiven = false;
- if (typeof params.ordered === 'boolean')
+ if (typeof params.ordered === 'boolean') {
orderedGiven = true;
- else
+ }
+ else {
params.ordered = true;
+ }
// maxPacketLifeTime is optional.
- if (params.maxPacketLifeTime && typeof params.maxPacketLifeTime !== 'number')
+ if (params.maxPacketLifeTime && typeof params.maxPacketLifeTime !== 'number') {
throw new TypeError('invalid params.maxPacketLifeTime');
+ }
// maxRetransmits is optional.
- if (params.maxRetransmits && typeof params.maxRetransmits !== 'number')
+ if (params.maxRetransmits && typeof params.maxRetransmits !== 'number') {
throw new TypeError('invalid params.maxRetransmits');
- if (params.maxPacketLifeTime && params.maxRetransmits)
+ }
+ if (params.maxPacketLifeTime && params.maxRetransmits) {
throw new TypeError('cannot provide both maxPacketLifeTime and maxRetransmits');
+ }
if (orderedGiven &&
params.ordered &&
(params.maxPacketLifeTime || params.maxRetransmits)) {
@@ -9394,11 +12461,13 @@ function validateSctpStreamParameters(params) {
params.ordered = false;
}
// label is optional.
- if (params.label && typeof params.label !== 'string')
+ if (params.label && typeof params.label !== 'string') {
throw new TypeError('invalid params.label');
+ }
// protocol is optional.
- if (params.protocol && typeof params.protocol !== 'string')
+ if (params.protocol && typeof params.protocol !== 'string') {
throw new TypeError('invalid params.protocol');
+ }
}
exports.validateSctpStreamParameters = validateSctpStreamParameters;
/**
@@ -9411,12 +12480,14 @@ function getExtendedRtpCapabilities(localCaps, remoteCaps) {
};
// Match media codecs and keep the order preferred by remoteCaps.
for (const remoteCodec of remoteCaps.codecs || []) {
- if (isRtxCodec(remoteCodec))
+ if (isRtxCodec(remoteCodec)) {
continue;
+ }
const matchingLocalCodec = (localCaps.codecs || [])
.find((localCodec) => (matchCodecs(localCodec, remoteCodec, { strict: true, modify: true })));
- if (!matchingLocalCodec)
+ if (!matchingLocalCodec) {
continue;
+ }
const extendedCodec = {
mimeType: matchingLocalCodec.mimeType,
kind: matchingLocalCodec.kind,
@@ -9449,8 +12520,9 @@ function getExtendedRtpCapabilities(localCaps, remoteCaps) {
for (const remoteExt of remoteCaps.headerExtensions) {
const matchingLocalExt = localCaps.headerExtensions
.find((localExt) => (matchHeaderExtensions(localExt, remoteExt)));
- if (!matchingLocalExt)
+ if (!matchingLocalExt) {
continue;
+ }
const extendedExt = {
kind: remoteExt.kind,
uri: remoteExt.uri,
@@ -9499,8 +12571,9 @@ function getRecvRtpCapabilities(extendedRtpCapabilities) {
};
rtpCapabilities.codecs.push(codec);
// Add RTX codec.
- if (!extendedCodec.remoteRtxPayloadType)
+ if (!extendedCodec.remoteRtxPayloadType) {
continue;
+ }
const rtxCodec = {
mimeType: `${extendedCodec.kind}/rtx`,
kind: extendedCodec.kind,
@@ -9545,8 +12618,9 @@ function getSendingRtpParameters(kind, extendedRtpCapabilities) {
rtcp: {}
};
for (const extendedCodec of extendedRtpCapabilities.codecs) {
- if (extendedCodec.kind !== kind)
+ if (extendedCodec.kind !== kind) {
continue;
+ }
const codec = {
mimeType: extendedCodec.mimeType,
payloadType: extendedCodec.localPayloadType,
@@ -9600,8 +12674,9 @@ function getSendingRemoteRtpParameters(kind, extendedRtpCapabilities) {
rtcp: {}
};
for (const extendedCodec of extendedRtpCapabilities.codecs) {
- if (extendedCodec.kind !== kind)
+ if (extendedCodec.kind !== kind) {
continue;
+ }
const codec = {
mimeType: extendedCodec.mimeType,
payloadType: extendedCodec.localPayloadType,
@@ -9678,21 +12753,24 @@ function reduceCodecs(codecs, capCodec) {
// If no capability codec is given, take the first one (and RTX).
if (!capCodec) {
filteredCodecs.push(codecs[0]);
- if (isRtxCodec(codecs[1]))
+ 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]))
+ if (isRtxCodec(codecs[idx + 1])) {
filteredCodecs.push(codecs[idx + 1]);
+ }
break;
}
}
- if (filteredCodecs.length === 0)
+ if (filteredCodecs.length === 0) {
throw new TypeError('no matching codec found');
+ }
}
return filteredCodecs;
}
@@ -9733,39 +12811,45 @@ exports.canSend = canSend;
function canReceive(rtpParameters, extendedRtpCapabilities) {
// This may throw.
validateRtpParameters(rtpParameters);
- if (rtpParameters.codecs.length === 0)
+ 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)
+ 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)
+ if (aMimeType !== bMimeType) {
return false;
- if (aCodec.clockRate !== bCodec.clockRate)
+ }
+ if (aCodec.clockRate !== bCodec.clockRate) {
return false;
- if (aCodec.channels !== bCodec.channels)
+ }
+ if (aCodec.channels !== bCodec.channels) {
return false;
+ }
// Per codec special checks.
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) {
- if (!h264.isSameProfile(aCodec.parameters, bCodec.parameters))
+ 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 =
@@ -9789,12 +12873,12 @@ function matchCodecs(aCodec, bCodec, { strict = false, modify = false } = {}) {
}
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)
+ if (aProfileId !== bProfileId) {
return false;
+ }
}
break;
}
@@ -9802,10 +12886,12 @@ function matchCodecs(aCodec, bCodec, { strict = false, modify = false } = {}) {
return true;
}
function matchHeaderExtensions(aExt, bExt) {
- if (aExt.kind && bExt.kind && aExt.kind !== bExt.kind)
+ if (aExt.kind && bExt.kind && aExt.kind !== bExt.kind) {
return false;
- if (aExt.uri !== bExt.uri)
+ }
+ if (aExt.uri !== bExt.uri) {
return false;
+ }
return true;
}
function reduceRtcpFeedback(codecA, codecB) {
@@ -9814,13 +12900,14 @@ function reduceRtcpFeedback(codecA, codecB) {
const matchingBFb = (codecB.rtcpFeedback || [])
.find((bFb) => (bFb.type === aFb.type &&
(bFb.parameter === aFb.parameter || (!bFb.parameter && !aFb.parameter))));
- if (matchingBFb)
+ if (matchingBFb) {
reducedRtcpFeedback.push(matchingBFb);
+ }
}
return reducedRtcpFeedback;
}
-},{"./utils":39,"h264-profile-level-id":4}],37:[function(require,module,exports){
+},{"./utils":46,"h264-profile-level-id":8}],44:[function(require,module,exports){
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.parse = void 0;
@@ -9842,17 +12929,21 @@ function parse(scalabilityMode) {
}
exports.parse = parse;
-},{}],38:[function(require,module,exports){
+},{}],45:[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" && !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);
@@ -9866,7 +12957,7 @@ __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){
+},{"./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;
@@ -9874,8 +12965,9 @@ exports.generateRandomNumber = exports.clone = void 0;
* Clones the given data.
*/
function clone(data, defaultValue) {
- if (typeof data === 'undefined')
+ if (typeof data === 'undefined') {
return defaultValue;
+ }
return JSON.parse(JSON.stringify(data));
}
exports.clone = clone;
@@ -9887,13 +12979,26 @@ function generateRandomNumber() {
}
exports.generateRandomNumber = generateRandomNumber;
-},{}],40:[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)
-},{"./common":41,"_process":48,"dup":5}],41:[function(require,module,exports){
+},{"dup":5,"ms":49}],49:[function(require,module,exports){
arguments[4][6][0].apply(exports,arguments)
-},{"dup":6,"ms":42}],42:[function(require,module,exports){
-arguments[4][7][0].apply(exports,arguments)
-},{"dup":7}],43:[function(require,module,exports){
+},{"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',
@@ -10389,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');
@@ -10402,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;
};
@@ -10528,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
@@ -10644,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
@@ -11143,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 9efd546..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://' + config.listenIp + ':' + 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
@@ -167,7 +173,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 +243,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.
*/