UNPKG

msc-node

Version:

mediasoup client side Node.js library

426 lines (425 loc) 17.2 kB
"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;