msc-node
Version:
mediasoup client side Node.js library
426 lines (425 loc) • 17.2 kB
JavaScript
"use strict";
/* global RTCRtpTransceiver */
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 __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.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 werift_1 = require("./handlers/werift");
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';
}
else if (version >= 70) {
return 'Chrome70';
}
else if (version >= 67) {
return 'Chrome67';
}
else {
return 'Chrome55';
}
}
else {
return 'Chrome74';
}
}
// Unsupported browser.
else {
logger.warn('this._detectDevice() | browser not supported [name:%s, version:%s]', browser.getBrowserName(), browser.getBrowserVersion());
return undefined;
}
}
// Unknown device.
else {
return 'werift';
}
}
exports.detectDevice = detectDevice;
class Device {
/**
* Create a new Device to connect to mediasoup server.
*
* @throws {UnsupportedError} if device is not supported.
*/
constructor(weriftRtpCapabilities, { handlerName, handlerFactory, Handler } = {}) {
// Loaded flag.
this._loaded = false;
// Observer instance.
this._observer = new EnhancedEventEmitter_1.EnhancedEventEmitter();
logger.debug('constructor()');
// Handle deprecated option.
if (Handler) {
logger.warn('constructor() | Handler option is DEPRECATED, use handlerName or handlerFactory instead');
if (typeof Handler === 'string') {
handlerName = Handler;
}
else {
throw new TypeError('non string Handler option no longer supported, use handlerFactory instead');
}
}
if (handlerName && handlerFactory) {
throw new TypeError('just one of handlerName or handlerInterface can be given');
}
if (handlerFactory) {
this._handlerFactory = handlerFactory;
}
else {
if (handlerName) {
logger.debug('constructor() | handler given: %s', handlerName);
}
else {
handlerName = detectDevice();
if (handlerName) {
logger.debug('constructor() | detected handler: %s', handlerName);
}
else {
throw new errors_1.UnsupportedError('device not supported');
}
}
switch (handlerName) {
case 'Chrome74':
this._handlerFactory = Chrome74_1.Chrome74.createFactory();
break;
case 'Chrome70':
this._handlerFactory = Chrome70_1.Chrome70.createFactory();
break;
case 'Chrome67':
this._handlerFactory = Chrome67_1.Chrome67.createFactory();
break;
case 'Chrome55':
this._handlerFactory = Chrome55_1.Chrome55.createFactory();
break;
case 'Firefox60':
this._handlerFactory = Firefox60_1.Firefox60.createFactory();
break;
case 'Safari12':
this._handlerFactory = Safari12_1.Safari12.createFactory();
break;
case 'Safari11':
this._handlerFactory = Safari11_1.Safari11.createFactory();
break;
case 'Edge11':
this._handlerFactory = Edge11_1.Edge11.createFactory();
break;
case 'ReactNative':
this._handlerFactory = ReactNative_1.ReactNative.createFactory();
break;
case 'werift':
this._handlerFactory = werift_1.Werift.createFactory(weriftRtpCapabilities);
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;
}
/**
* Observer.
*
* @emits newtransport - (transport: Transport)
*/
get observer() {
return this._observer;
}
/**
* Initialize the Device.
*/
load({ routerRtpCapabilities }) {
return __awaiter(this, void 0, void 0, function* () {
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 = yield 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 = yield 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;