From ac8d3c9c45794815f895c5d7cd0182b0afd79109 Mon Sep 17 00:00:00 2001 From: mofeng <3285338098@qq.com> Date: Sat, 16 Dec 2023 16:48:16 +0800 Subject: [PATCH] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=97=A0=E7=94=A8=E7=9A=84js?= =?UTF-8?q?=E6=96=87=E4=BB=B6=EF=BC=88=E7=8E=A9=E5=AE=A2=E4=BA=91=E6=97=A0?= =?UTF-8?q?=E6=B3=95=E4=BD=BF=E7=94=A8h.264/webrtc=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- web/adapter.js | 3364 -------------------------------------------- web/janus.js | 3651 ------------------------------------------------ 2 files changed, 7015 deletions(-) delete mode 100644 web/adapter.js delete mode 100644 web/janus.js diff --git a/web/adapter.js b/web/adapter.js deleted file mode 100644 index 51d0f276..00000000 --- a/web/adapter.js +++ /dev/null @@ -1,3364 +0,0 @@ -(function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.adapter = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i 0 && arguments[0] !== undefined ? arguments[0] : {}, - window = _ref.window; - var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : { - shimChrome: true, - shimFirefox: true, - shimSafari: true - }; - // Utils. - var logging = utils.log; - var browserDetails = utils.detectBrowser(window); - var adapter = { - browserDetails: browserDetails, - commonShim: commonShim, - extractVersion: utils.extractVersion, - disableLog: utils.disableLog, - disableWarnings: utils.disableWarnings, - // Expose sdp as a convenience. For production apps include directly. - sdp: sdp - }; - - // Shim browser if found. - switch (browserDetails.browser) { - case 'chrome': - if (!chromeShim || !chromeShim.shimPeerConnection || !options.shimChrome) { - logging('Chrome shim is not included in this adapter release.'); - return adapter; - } - if (browserDetails.version === null) { - logging('Chrome shim can not determine version, not shimming.'); - return adapter; - } - logging('adapter.js shimming chrome.'); - // Export to the adapter global object visible in the browser. - adapter.browserShim = chromeShim; - - // Must be called before shimPeerConnection. - commonShim.shimAddIceCandidateNullOrEmpty(window, browserDetails); - commonShim.shimParameterlessSetLocalDescription(window, browserDetails); - chromeShim.shimGetUserMedia(window, browserDetails); - chromeShim.shimMediaStream(window, browserDetails); - chromeShim.shimPeerConnection(window, browserDetails); - chromeShim.shimOnTrack(window, browserDetails); - chromeShim.shimAddTrackRemoveTrack(window, browserDetails); - chromeShim.shimGetSendersWithDtmf(window, browserDetails); - chromeShim.shimGetStats(window, browserDetails); - chromeShim.shimSenderReceiverGetStats(window, browserDetails); - chromeShim.fixNegotiationNeeded(window, browserDetails); - commonShim.shimRTCIceCandidate(window, browserDetails); - commonShim.shimRTCIceCandidateRelayProtocol(window, browserDetails); - commonShim.shimConnectionState(window, browserDetails); - commonShim.shimMaxMessageSize(window, browserDetails); - commonShim.shimSendThrowTypeError(window, browserDetails); - commonShim.removeExtmapAllowMixed(window, browserDetails); - break; - case 'firefox': - if (!firefoxShim || !firefoxShim.shimPeerConnection || !options.shimFirefox) { - logging('Firefox shim is not included in this adapter release.'); - return adapter; - } - logging('adapter.js shimming firefox.'); - // Export to the adapter global object visible in the browser. - adapter.browserShim = firefoxShim; - - // Must be called before shimPeerConnection. - commonShim.shimAddIceCandidateNullOrEmpty(window, browserDetails); - commonShim.shimParameterlessSetLocalDescription(window, browserDetails); - firefoxShim.shimGetUserMedia(window, browserDetails); - firefoxShim.shimPeerConnection(window, browserDetails); - firefoxShim.shimOnTrack(window, browserDetails); - firefoxShim.shimRemoveStream(window, browserDetails); - firefoxShim.shimSenderGetStats(window, browserDetails); - firefoxShim.shimReceiverGetStats(window, browserDetails); - firefoxShim.shimRTCDataChannel(window, browserDetails); - firefoxShim.shimAddTransceiver(window, browserDetails); - firefoxShim.shimGetParameters(window, browserDetails); - firefoxShim.shimCreateOffer(window, browserDetails); - firefoxShim.shimCreateAnswer(window, browserDetails); - commonShim.shimRTCIceCandidate(window, browserDetails); - commonShim.shimConnectionState(window, browserDetails); - commonShim.shimMaxMessageSize(window, browserDetails); - commonShim.shimSendThrowTypeError(window, browserDetails); - break; - case 'safari': - if (!safariShim || !options.shimSafari) { - logging('Safari shim is not included in this adapter release.'); - return adapter; - } - logging('adapter.js shimming safari.'); - // Export to the adapter global object visible in the browser. - adapter.browserShim = safariShim; - - // Must be called before shimCallbackAPI. - commonShim.shimAddIceCandidateNullOrEmpty(window, browserDetails); - commonShim.shimParameterlessSetLocalDescription(window, browserDetails); - safariShim.shimRTCIceServerUrls(window, browserDetails); - safariShim.shimCreateOfferLegacy(window, browserDetails); - safariShim.shimCallbacksAPI(window, browserDetails); - safariShim.shimLocalStreamsAPI(window, browserDetails); - safariShim.shimRemoteStreamsAPI(window, browserDetails); - safariShim.shimTrackEventTransceiver(window, browserDetails); - safariShim.shimGetUserMedia(window, browserDetails); - safariShim.shimAudioContext(window, browserDetails); - commonShim.shimRTCIceCandidate(window, browserDetails); - commonShim.shimRTCIceCandidateRelayProtocol(window, browserDetails); - commonShim.shimMaxMessageSize(window, browserDetails); - commonShim.shimSendThrowTypeError(window, browserDetails); - commonShim.removeExtmapAllowMixed(window, browserDetails); - break; - default: - logging('Unsupported browser!'); - break; - } - return adapter; -} - -},{"./chrome/chrome_shim":3,"./common_shim":6,"./firefox/firefox_shim":7,"./safari/safari_shim":10,"./utils":11,"sdp":12}],3:[function(require,module,exports){ -/* - * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. - */ -/* eslint-env node */ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.fixNegotiationNeeded = fixNegotiationNeeded; -exports.shimAddTrackRemoveTrack = shimAddTrackRemoveTrack; -exports.shimAddTrackRemoveTrackWithNative = shimAddTrackRemoveTrackWithNative; -Object.defineProperty(exports, "shimGetDisplayMedia", { - enumerable: true, - get: function get() { - return _getdisplaymedia.shimGetDisplayMedia; - } -}); -exports.shimGetSendersWithDtmf = shimGetSendersWithDtmf; -exports.shimGetStats = shimGetStats; -Object.defineProperty(exports, "shimGetUserMedia", { - enumerable: true, - get: function get() { - return _getusermedia.shimGetUserMedia; - } -}); -exports.shimMediaStream = shimMediaStream; -exports.shimOnTrack = shimOnTrack; -exports.shimPeerConnection = shimPeerConnection; -exports.shimSenderReceiverGetStats = shimSenderReceiverGetStats; -var utils = _interopRequireWildcard(require("../utils.js")); -var _getusermedia = require("./getusermedia"); -var _getdisplaymedia = require("./getdisplaymedia"); -function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } -function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; } -function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } -function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); } -function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); } -function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); } -function shimMediaStream(window) { - window.MediaStream = window.MediaStream || window.webkitMediaStream; -} -function shimOnTrack(window) { - if (_typeof(window) === 'object' && window.RTCPeerConnection && !('ontrack' in window.RTCPeerConnection.prototype)) { - Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', { - get: function get() { - return this._ontrack; - }, - set: function set(f) { - if (this._ontrack) { - this.removeEventListener('track', this._ontrack); - } - this.addEventListener('track', this._ontrack = f); - }, - enumerable: true, - configurable: true - }); - var origSetRemoteDescription = window.RTCPeerConnection.prototype.setRemoteDescription; - window.RTCPeerConnection.prototype.setRemoteDescription = function setRemoteDescription() { - var _this = this; - if (!this._ontrackpoly) { - this._ontrackpoly = function (e) { - // onaddstream does not fire when a track is added to an existing - // stream. But stream.onaddtrack is implemented so we use that. - e.stream.addEventListener('addtrack', function (te) { - var receiver; - if (window.RTCPeerConnection.prototype.getReceivers) { - receiver = _this.getReceivers().find(function (r) { - return r.track && r.track.id === te.track.id; - }); - } else { - receiver = { - track: te.track - }; - } - var event = new Event('track'); - event.track = te.track; - event.receiver = receiver; - event.transceiver = { - receiver: receiver - }; - event.streams = [e.stream]; - _this.dispatchEvent(event); - }); - e.stream.getTracks().forEach(function (track) { - var receiver; - if (window.RTCPeerConnection.prototype.getReceivers) { - receiver = _this.getReceivers().find(function (r) { - return r.track && r.track.id === track.id; - }); - } else { - receiver = { - track: track - }; - } - var event = new Event('track'); - event.track = track; - event.receiver = receiver; - event.transceiver = { - receiver: receiver - }; - event.streams = [e.stream]; - _this.dispatchEvent(event); - }); - }; - this.addEventListener('addstream', this._ontrackpoly); - } - return origSetRemoteDescription.apply(this, arguments); - }; - } else { - // even if RTCRtpTransceiver is in window, it is only used and - // emitted in unified-plan. Unfortunately this means we need - // to unconditionally wrap the event. - utils.wrapPeerConnectionEvent(window, 'track', function (e) { - if (!e.transceiver) { - Object.defineProperty(e, 'transceiver', { - value: { - receiver: e.receiver - } - }); - } - return e; - }); - } -} -function shimGetSendersWithDtmf(window) { - // Overrides addTrack/removeTrack, depends on shimAddTrackRemoveTrack. - if (_typeof(window) === 'object' && window.RTCPeerConnection && !('getSenders' in window.RTCPeerConnection.prototype) && 'createDTMFSender' in window.RTCPeerConnection.prototype) { - var shimSenderWithDtmf = function shimSenderWithDtmf(pc, track) { - return { - track: track, - get dtmf() { - if (this._dtmf === undefined) { - if (track.kind === 'audio') { - this._dtmf = pc.createDTMFSender(track); - } else { - this._dtmf = null; - } - } - return this._dtmf; - }, - _pc: pc - }; - }; - - // augment addTrack when getSenders is not available. - if (!window.RTCPeerConnection.prototype.getSenders) { - window.RTCPeerConnection.prototype.getSenders = function getSenders() { - this._senders = this._senders || []; - return this._senders.slice(); // return a copy of the internal state. - }; - - var origAddTrack = window.RTCPeerConnection.prototype.addTrack; - window.RTCPeerConnection.prototype.addTrack = function addTrack(track, stream) { - var sender = origAddTrack.apply(this, arguments); - if (!sender) { - sender = shimSenderWithDtmf(this, track); - this._senders.push(sender); - } - return sender; - }; - var origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack; - window.RTCPeerConnection.prototype.removeTrack = function removeTrack(sender) { - origRemoveTrack.apply(this, arguments); - var idx = this._senders.indexOf(sender); - if (idx !== -1) { - this._senders.splice(idx, 1); - } - }; - } - var origAddStream = window.RTCPeerConnection.prototype.addStream; - window.RTCPeerConnection.prototype.addStream = function addStream(stream) { - var _this2 = this; - this._senders = this._senders || []; - origAddStream.apply(this, [stream]); - stream.getTracks().forEach(function (track) { - _this2._senders.push(shimSenderWithDtmf(_this2, track)); - }); - }; - var origRemoveStream = window.RTCPeerConnection.prototype.removeStream; - window.RTCPeerConnection.prototype.removeStream = function removeStream(stream) { - var _this3 = this; - this._senders = this._senders || []; - origRemoveStream.apply(this, [stream]); - stream.getTracks().forEach(function (track) { - var sender = _this3._senders.find(function (s) { - return s.track === track; - }); - if (sender) { - // remove sender - _this3._senders.splice(_this3._senders.indexOf(sender), 1); - } - }); - }; - } else if (_typeof(window) === 'object' && window.RTCPeerConnection && 'getSenders' in window.RTCPeerConnection.prototype && 'createDTMFSender' in window.RTCPeerConnection.prototype && window.RTCRtpSender && !('dtmf' in window.RTCRtpSender.prototype)) { - var origGetSenders = window.RTCPeerConnection.prototype.getSenders; - window.RTCPeerConnection.prototype.getSenders = function getSenders() { - var _this4 = this; - var senders = origGetSenders.apply(this, []); - senders.forEach(function (sender) { - return sender._pc = _this4; - }); - return senders; - }; - Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', { - get: function get() { - if (this._dtmf === undefined) { - if (this.track.kind === 'audio') { - this._dtmf = this._pc.createDTMFSender(this.track); - } else { - this._dtmf = null; - } - } - return this._dtmf; - } - }); - } -} -function shimGetStats(window) { - if (!window.RTCPeerConnection) { - return; - } - var origGetStats = window.RTCPeerConnection.prototype.getStats; - window.RTCPeerConnection.prototype.getStats = function getStats() { - var _this5 = this; - var _arguments = Array.prototype.slice.call(arguments), - selector = _arguments[0], - onSucc = _arguments[1], - onErr = _arguments[2]; - - // If selector is a function then we are in the old style stats so just - // pass back the original getStats format to avoid breaking old users. - if (arguments.length > 0 && typeof selector === 'function') { - return origGetStats.apply(this, arguments); - } - - // When spec-style getStats is supported, return those when called with - // either no arguments or the selector argument is null. - if (origGetStats.length === 0 && (arguments.length === 0 || typeof selector !== 'function')) { - return origGetStats.apply(this, []); - } - var fixChromeStats_ = function fixChromeStats_(response) { - var standardReport = {}; - var reports = response.result(); - reports.forEach(function (report) { - var standardStats = { - id: report.id, - timestamp: report.timestamp, - type: { - localcandidate: 'local-candidate', - remotecandidate: 'remote-candidate' - }[report.type] || report.type - }; - report.names().forEach(function (name) { - standardStats[name] = report.stat(name); - }); - standardReport[standardStats.id] = standardStats; - }); - return standardReport; - }; - - // shim getStats with maplike support - var makeMapStats = function makeMapStats(stats) { - return new Map(Object.keys(stats).map(function (key) { - return [key, stats[key]]; - })); - }; - if (arguments.length >= 2) { - var successCallbackWrapper_ = function successCallbackWrapper_(response) { - onSucc(makeMapStats(fixChromeStats_(response))); - }; - return origGetStats.apply(this, [successCallbackWrapper_, selector]); - } - - // promise-support - return new Promise(function (resolve, reject) { - origGetStats.apply(_this5, [function (response) { - resolve(makeMapStats(fixChromeStats_(response))); - }, reject]); - }).then(onSucc, onErr); - }; -} -function shimSenderReceiverGetStats(window) { - if (!(_typeof(window) === 'object' && window.RTCPeerConnection && window.RTCRtpSender && window.RTCRtpReceiver)) { - return; - } - - // shim sender stats. - if (!('getStats' in window.RTCRtpSender.prototype)) { - var origGetSenders = window.RTCPeerConnection.prototype.getSenders; - if (origGetSenders) { - window.RTCPeerConnection.prototype.getSenders = function getSenders() { - var _this6 = this; - var senders = origGetSenders.apply(this, []); - senders.forEach(function (sender) { - return sender._pc = _this6; - }); - return senders; - }; - } - var origAddTrack = window.RTCPeerConnection.prototype.addTrack; - if (origAddTrack) { - window.RTCPeerConnection.prototype.addTrack = function addTrack() { - var sender = origAddTrack.apply(this, arguments); - sender._pc = this; - return sender; - }; - } - window.RTCRtpSender.prototype.getStats = function getStats() { - var sender = this; - return this._pc.getStats().then(function (result) { - return ( - /* Note: this will include stats of all senders that - * send a track with the same id as sender.track as - * it is not possible to identify the RTCRtpSender. - */ - utils.filterStats(result, sender.track, true) - ); - }); - }; - } - - // shim receiver stats. - if (!('getStats' in window.RTCRtpReceiver.prototype)) { - var origGetReceivers = window.RTCPeerConnection.prototype.getReceivers; - if (origGetReceivers) { - window.RTCPeerConnection.prototype.getReceivers = function getReceivers() { - var _this7 = this; - var receivers = origGetReceivers.apply(this, []); - receivers.forEach(function (receiver) { - return receiver._pc = _this7; - }); - return receivers; - }; - } - utils.wrapPeerConnectionEvent(window, 'track', function (e) { - e.receiver._pc = e.srcElement; - return e; - }); - window.RTCRtpReceiver.prototype.getStats = function getStats() { - var receiver = this; - return this._pc.getStats().then(function (result) { - return utils.filterStats(result, receiver.track, false); - }); - }; - } - if (!('getStats' in window.RTCRtpSender.prototype && 'getStats' in window.RTCRtpReceiver.prototype)) { - return; - } - - // shim RTCPeerConnection.getStats(track). - var origGetStats = window.RTCPeerConnection.prototype.getStats; - window.RTCPeerConnection.prototype.getStats = function getStats() { - if (arguments.length > 0 && arguments[0] instanceof window.MediaStreamTrack) { - var track = arguments[0]; - var sender; - var receiver; - var err; - this.getSenders().forEach(function (s) { - if (s.track === track) { - if (sender) { - err = true; - } else { - sender = s; - } - } - }); - this.getReceivers().forEach(function (r) { - if (r.track === track) { - if (receiver) { - err = true; - } else { - receiver = r; - } - } - return r.track === track; - }); - if (err || sender && receiver) { - return Promise.reject(new DOMException('There are more than one sender or receiver for the track.', 'InvalidAccessError')); - } else if (sender) { - return sender.getStats(); - } else if (receiver) { - return receiver.getStats(); - } - return Promise.reject(new DOMException('There is no sender or receiver for the track.', 'InvalidAccessError')); - } - return origGetStats.apply(this, arguments); - }; -} -function shimAddTrackRemoveTrackWithNative(window) { - // shim addTrack/removeTrack with native variants in order to make - // the interactions with legacy getLocalStreams behave as in other browsers. - // Keeps a mapping stream.id => [stream, rtpsenders...] - window.RTCPeerConnection.prototype.getLocalStreams = function getLocalStreams() { - var _this8 = this; - this._shimmedLocalStreams = this._shimmedLocalStreams || {}; - return Object.keys(this._shimmedLocalStreams).map(function (streamId) { - return _this8._shimmedLocalStreams[streamId][0]; - }); - }; - var origAddTrack = window.RTCPeerConnection.prototype.addTrack; - window.RTCPeerConnection.prototype.addTrack = function addTrack(track, stream) { - if (!stream) { - return origAddTrack.apply(this, arguments); - } - this._shimmedLocalStreams = this._shimmedLocalStreams || {}; - var sender = origAddTrack.apply(this, arguments); - if (!this._shimmedLocalStreams[stream.id]) { - this._shimmedLocalStreams[stream.id] = [stream, sender]; - } else if (this._shimmedLocalStreams[stream.id].indexOf(sender) === -1) { - this._shimmedLocalStreams[stream.id].push(sender); - } - return sender; - }; - var origAddStream = window.RTCPeerConnection.prototype.addStream; - window.RTCPeerConnection.prototype.addStream = function addStream(stream) { - var _this9 = this; - this._shimmedLocalStreams = this._shimmedLocalStreams || {}; - stream.getTracks().forEach(function (track) { - var alreadyExists = _this9.getSenders().find(function (s) { - return s.track === track; - }); - if (alreadyExists) { - throw new DOMException('Track already exists.', 'InvalidAccessError'); - } - }); - var existingSenders = this.getSenders(); - origAddStream.apply(this, arguments); - var newSenders = this.getSenders().filter(function (newSender) { - return existingSenders.indexOf(newSender) === -1; - }); - this._shimmedLocalStreams[stream.id] = [stream].concat(newSenders); - }; - var origRemoveStream = window.RTCPeerConnection.prototype.removeStream; - window.RTCPeerConnection.prototype.removeStream = function removeStream(stream) { - this._shimmedLocalStreams = this._shimmedLocalStreams || {}; - delete this._shimmedLocalStreams[stream.id]; - return origRemoveStream.apply(this, arguments); - }; - var origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack; - window.RTCPeerConnection.prototype.removeTrack = function removeTrack(sender) { - var _this10 = this; - this._shimmedLocalStreams = this._shimmedLocalStreams || {}; - if (sender) { - Object.keys(this._shimmedLocalStreams).forEach(function (streamId) { - var idx = _this10._shimmedLocalStreams[streamId].indexOf(sender); - if (idx !== -1) { - _this10._shimmedLocalStreams[streamId].splice(idx, 1); - } - if (_this10._shimmedLocalStreams[streamId].length === 1) { - delete _this10._shimmedLocalStreams[streamId]; - } - }); - } - return origRemoveTrack.apply(this, arguments); - }; -} -function shimAddTrackRemoveTrack(window, browserDetails) { - if (!window.RTCPeerConnection) { - return; - } - // shim addTrack and removeTrack. - if (window.RTCPeerConnection.prototype.addTrack && browserDetails.version >= 65) { - return shimAddTrackRemoveTrackWithNative(window); - } - - // also shim pc.getLocalStreams when addTrack is shimmed - // to return the original streams. - var origGetLocalStreams = window.RTCPeerConnection.prototype.getLocalStreams; - window.RTCPeerConnection.prototype.getLocalStreams = function getLocalStreams() { - var _this11 = this; - var nativeStreams = origGetLocalStreams.apply(this); - this._reverseStreams = this._reverseStreams || {}; - return nativeStreams.map(function (stream) { - return _this11._reverseStreams[stream.id]; - }); - }; - var origAddStream = window.RTCPeerConnection.prototype.addStream; - window.RTCPeerConnection.prototype.addStream = function addStream(stream) { - var _this12 = this; - this._streams = this._streams || {}; - this._reverseStreams = this._reverseStreams || {}; - stream.getTracks().forEach(function (track) { - var alreadyExists = _this12.getSenders().find(function (s) { - return s.track === track; - }); - if (alreadyExists) { - throw new DOMException('Track already exists.', 'InvalidAccessError'); - } - }); - // Add identity mapping for consistency with addTrack. - // Unless this is being used with a stream from addTrack. - if (!this._reverseStreams[stream.id]) { - var newStream = new window.MediaStream(stream.getTracks()); - this._streams[stream.id] = newStream; - this._reverseStreams[newStream.id] = stream; - stream = newStream; - } - origAddStream.apply(this, [stream]); - }; - var origRemoveStream = window.RTCPeerConnection.prototype.removeStream; - window.RTCPeerConnection.prototype.removeStream = function removeStream(stream) { - this._streams = this._streams || {}; - this._reverseStreams = this._reverseStreams || {}; - origRemoveStream.apply(this, [this._streams[stream.id] || stream]); - delete this._reverseStreams[this._streams[stream.id] ? this._streams[stream.id].id : stream.id]; - delete this._streams[stream.id]; - }; - window.RTCPeerConnection.prototype.addTrack = function addTrack(track, stream) { - var _this13 = this; - if (this.signalingState === 'closed') { - throw new DOMException('The RTCPeerConnection\'s signalingState is \'closed\'.', 'InvalidStateError'); - } - var streams = [].slice.call(arguments, 1); - if (streams.length !== 1 || !streams[0].getTracks().find(function (t) { - return t === track; - })) { - // this is not fully correct but all we can manage without - // [[associated MediaStreams]] internal slot. - throw new DOMException('The adapter.js addTrack polyfill only supports a single ' + ' stream which is associated with the specified track.', 'NotSupportedError'); - } - var alreadyExists = this.getSenders().find(function (s) { - return s.track === track; - }); - if (alreadyExists) { - throw new DOMException('Track already exists.', 'InvalidAccessError'); - } - this._streams = this._streams || {}; - this._reverseStreams = this._reverseStreams || {}; - var oldStream = this._streams[stream.id]; - if (oldStream) { - // this is using odd Chrome behaviour, use with caution: - // https://bugs.chromium.org/p/webrtc/issues/detail?id=7815 - // Note: we rely on the high-level addTrack/dtmf shim to - // create the sender with a dtmf sender. - oldStream.addTrack(track); - - // Trigger ONN async. - Promise.resolve().then(function () { - _this13.dispatchEvent(new Event('negotiationneeded')); - }); - } else { - var newStream = new window.MediaStream([track]); - this._streams[stream.id] = newStream; - this._reverseStreams[newStream.id] = stream; - this.addStream(newStream); - } - return this.getSenders().find(function (s) { - return s.track === track; - }); - }; - - // replace the internal stream id with the external one and - // vice versa. - function replaceInternalStreamId(pc, description) { - var sdp = description.sdp; - Object.keys(pc._reverseStreams || []).forEach(function (internalId) { - var externalStream = pc._reverseStreams[internalId]; - var internalStream = pc._streams[externalStream.id]; - sdp = sdp.replace(new RegExp(internalStream.id, 'g'), externalStream.id); - }); - return new RTCSessionDescription({ - type: description.type, - sdp: sdp - }); - } - function replaceExternalStreamId(pc, description) { - var sdp = description.sdp; - Object.keys(pc._reverseStreams || []).forEach(function (internalId) { - var externalStream = pc._reverseStreams[internalId]; - var internalStream = pc._streams[externalStream.id]; - sdp = sdp.replace(new RegExp(externalStream.id, 'g'), internalStream.id); - }); - return new RTCSessionDescription({ - type: description.type, - sdp: sdp - }); - } - ['createOffer', 'createAnswer'].forEach(function (method) { - var nativeMethod = window.RTCPeerConnection.prototype[method]; - var methodObj = _defineProperty({}, method, function () { - var _this14 = this; - var args = arguments; - var isLegacyCall = arguments.length && typeof arguments[0] === 'function'; - if (isLegacyCall) { - return nativeMethod.apply(this, [function (description) { - var desc = replaceInternalStreamId(_this14, description); - args[0].apply(null, [desc]); - }, function (err) { - if (args[1]) { - args[1].apply(null, err); - } - }, arguments[2]]); - } - return nativeMethod.apply(this, arguments).then(function (description) { - return replaceInternalStreamId(_this14, description); - }); - }); - window.RTCPeerConnection.prototype[method] = methodObj[method]; - }); - var origSetLocalDescription = window.RTCPeerConnection.prototype.setLocalDescription; - window.RTCPeerConnection.prototype.setLocalDescription = function setLocalDescription() { - if (!arguments.length || !arguments[0].type) { - return origSetLocalDescription.apply(this, arguments); - } - arguments[0] = replaceExternalStreamId(this, arguments[0]); - return origSetLocalDescription.apply(this, arguments); - }; - - // TODO: mangle getStats: https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamstats-streamidentifier - - var origLocalDescription = Object.getOwnPropertyDescriptor(window.RTCPeerConnection.prototype, 'localDescription'); - Object.defineProperty(window.RTCPeerConnection.prototype, 'localDescription', { - get: function get() { - var description = origLocalDescription.get.apply(this); - if (description.type === '') { - return description; - } - return replaceInternalStreamId(this, description); - } - }); - window.RTCPeerConnection.prototype.removeTrack = function removeTrack(sender) { - var _this15 = this; - if (this.signalingState === 'closed') { - throw new DOMException('The RTCPeerConnection\'s signalingState is \'closed\'.', 'InvalidStateError'); - } - // We can not yet check for sender instanceof RTCRtpSender - // since we shim RTPSender. So we check if sender._pc is set. - if (!sender._pc) { - throw new DOMException('Argument 1 of RTCPeerConnection.removeTrack ' + 'does not implement interface RTCRtpSender.', 'TypeError'); - } - var isLocal = sender._pc === this; - if (!isLocal) { - throw new DOMException('Sender was not created by this connection.', 'InvalidAccessError'); - } - - // Search for the native stream the senders track belongs to. - this._streams = this._streams || {}; - var stream; - Object.keys(this._streams).forEach(function (streamid) { - var hasTrack = _this15._streams[streamid].getTracks().find(function (track) { - return sender.track === track; - }); - if (hasTrack) { - stream = _this15._streams[streamid]; - } - }); - if (stream) { - if (stream.getTracks().length === 1) { - // if this is the last track of the stream, remove the stream. This - // takes care of any shimmed _senders. - this.removeStream(this._reverseStreams[stream.id]); - } else { - // relying on the same odd chrome behaviour as above. - stream.removeTrack(sender.track); - } - this.dispatchEvent(new Event('negotiationneeded')); - } - }; -} -function shimPeerConnection(window, browserDetails) { - if (!window.RTCPeerConnection && window.webkitRTCPeerConnection) { - // very basic support for old versions. - window.RTCPeerConnection = window.webkitRTCPeerConnection; - } - if (!window.RTCPeerConnection) { - return; - } - - // shim implicit creation of RTCSessionDescription/RTCIceCandidate - if (browserDetails.version < 53) { - ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'].forEach(function (method) { - var nativeMethod = window.RTCPeerConnection.prototype[method]; - var methodObj = _defineProperty({}, method, function () { - arguments[0] = new (method === 'addIceCandidate' ? window.RTCIceCandidate : window.RTCSessionDescription)(arguments[0]); - return nativeMethod.apply(this, arguments); - }); - window.RTCPeerConnection.prototype[method] = methodObj[method]; - }); - } -} - -// Attempt to fix ONN in plan-b mode. -function fixNegotiationNeeded(window, browserDetails) { - utils.wrapPeerConnectionEvent(window, 'negotiationneeded', function (e) { - var pc = e.target; - if (browserDetails.version < 72 || pc.getConfiguration && pc.getConfiguration().sdpSemantics === 'plan-b') { - if (pc.signalingState !== 'stable') { - return; - } - } - return e; - }); -} - -},{"../utils.js":11,"./getdisplaymedia":4,"./getusermedia":5}],4:[function(require,module,exports){ -/* - * Copyright (c) 2018 The adapter.js project authors. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. - */ -/* eslint-env node */ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.shimGetDisplayMedia = shimGetDisplayMedia; -function shimGetDisplayMedia(window, getSourceId) { - if (window.navigator.mediaDevices && 'getDisplayMedia' in window.navigator.mediaDevices) { - return; - } - if (!window.navigator.mediaDevices) { - return; - } - // getSourceId is a function that returns a promise resolving with - // the sourceId of the screen/window/tab to be shared. - if (typeof getSourceId !== 'function') { - console.error('shimGetDisplayMedia: getSourceId argument is not ' + 'a function'); - return; - } - window.navigator.mediaDevices.getDisplayMedia = function getDisplayMedia(constraints) { - return getSourceId(constraints).then(function (sourceId) { - var widthSpecified = constraints.video && constraints.video.width; - var heightSpecified = constraints.video && constraints.video.height; - var frameRateSpecified = constraints.video && constraints.video.frameRate; - constraints.video = { - mandatory: { - chromeMediaSource: 'desktop', - chromeMediaSourceId: sourceId, - maxFrameRate: frameRateSpecified || 3 - } - }; - if (widthSpecified) { - constraints.video.mandatory.maxWidth = widthSpecified; - } - if (heightSpecified) { - constraints.video.mandatory.maxHeight = heightSpecified; - } - return window.navigator.mediaDevices.getUserMedia(constraints); - }); - }; -} - -},{}],5:[function(require,module,exports){ -/* - * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. - */ -/* eslint-env node */ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.shimGetUserMedia = shimGetUserMedia; -var utils = _interopRequireWildcard(require("../utils.js")); -function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } -function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; } -function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); } -var logging = utils.log; -function shimGetUserMedia(window, browserDetails) { - var navigator = window && window.navigator; - if (!navigator.mediaDevices) { - return; - } - var constraintsToChrome_ = function constraintsToChrome_(c) { - if (_typeof(c) !== 'object' || c.mandatory || c.optional) { - return c; - } - var cc = {}; - Object.keys(c).forEach(function (key) { - if (key === 'require' || key === 'advanced' || key === 'mediaSource') { - return; - } - var r = _typeof(c[key]) === 'object' ? c[key] : { - ideal: c[key] - }; - if (r.exact !== undefined && typeof r.exact === 'number') { - r.min = r.max = r.exact; - } - var oldname_ = function oldname_(prefix, name) { - if (prefix) { - return prefix + name.charAt(0).toUpperCase() + name.slice(1); - } - return name === 'deviceId' ? 'sourceId' : name; - }; - if (r.ideal !== undefined) { - cc.optional = cc.optional || []; - var oc = {}; - if (typeof r.ideal === 'number') { - oc[oldname_('min', key)] = r.ideal; - cc.optional.push(oc); - oc = {}; - oc[oldname_('max', key)] = r.ideal; - cc.optional.push(oc); - } else { - oc[oldname_('', key)] = r.ideal; - cc.optional.push(oc); - } - } - if (r.exact !== undefined && typeof r.exact !== 'number') { - cc.mandatory = cc.mandatory || {}; - cc.mandatory[oldname_('', key)] = r.exact; - } else { - ['min', 'max'].forEach(function (mix) { - if (r[mix] !== undefined) { - cc.mandatory = cc.mandatory || {}; - cc.mandatory[oldname_(mix, key)] = r[mix]; - } - }); - } - }); - if (c.advanced) { - cc.optional = (cc.optional || []).concat(c.advanced); - } - return cc; - }; - var shimConstraints_ = function shimConstraints_(constraints, func) { - if (browserDetails.version >= 61) { - return func(constraints); - } - constraints = JSON.parse(JSON.stringify(constraints)); - if (constraints && _typeof(constraints.audio) === 'object') { - var remap = function remap(obj, a, b) { - if (a in obj && !(b in obj)) { - obj[b] = obj[a]; - delete obj[a]; - } - }; - constraints = JSON.parse(JSON.stringify(constraints)); - remap(constraints.audio, 'autoGainControl', 'googAutoGainControl'); - remap(constraints.audio, 'noiseSuppression', 'googNoiseSuppression'); - constraints.audio = constraintsToChrome_(constraints.audio); - } - if (constraints && _typeof(constraints.video) === 'object') { - // Shim facingMode for mobile & surface pro. - var face = constraints.video.facingMode; - face = face && (_typeof(face) === 'object' ? face : { - ideal: face - }); - var getSupportedFacingModeLies = browserDetails.version < 66; - if (face && (face.exact === 'user' || face.exact === 'environment' || face.ideal === 'user' || face.ideal === 'environment') && !(navigator.mediaDevices.getSupportedConstraints && navigator.mediaDevices.getSupportedConstraints().facingMode && !getSupportedFacingModeLies)) { - delete constraints.video.facingMode; - var matches; - if (face.exact === 'environment' || face.ideal === 'environment') { - matches = ['back', 'rear']; - } else if (face.exact === 'user' || face.ideal === 'user') { - matches = ['front']; - } - if (matches) { - // Look for matches in label, or use last cam for back (typical). - return navigator.mediaDevices.enumerateDevices().then(function (devices) { - devices = devices.filter(function (d) { - return d.kind === 'videoinput'; - }); - var dev = devices.find(function (d) { - return matches.some(function (match) { - return d.label.toLowerCase().includes(match); - }); - }); - if (!dev && devices.length && matches.includes('back')) { - dev = devices[devices.length - 1]; // more likely the back cam - } - - if (dev) { - constraints.video.deviceId = face.exact ? { - exact: dev.deviceId - } : { - ideal: dev.deviceId - }; - } - constraints.video = constraintsToChrome_(constraints.video); - logging('chrome: ' + JSON.stringify(constraints)); - return func(constraints); - }); - } - } - constraints.video = constraintsToChrome_(constraints.video); - } - logging('chrome: ' + JSON.stringify(constraints)); - return func(constraints); - }; - var shimError_ = function shimError_(e) { - if (browserDetails.version >= 64) { - return e; - } - return { - name: { - PermissionDeniedError: 'NotAllowedError', - PermissionDismissedError: 'NotAllowedError', - InvalidStateError: 'NotAllowedError', - DevicesNotFoundError: 'NotFoundError', - ConstraintNotSatisfiedError: 'OverconstrainedError', - TrackStartError: 'NotReadableError', - MediaDeviceFailedDueToShutdown: 'NotAllowedError', - MediaDeviceKillSwitchOn: 'NotAllowedError', - TabCaptureError: 'AbortError', - ScreenCaptureError: 'AbortError', - DeviceCaptureError: 'AbortError' - }[e.name] || e.name, - message: e.message, - constraint: e.constraint || e.constraintName, - toString: function toString() { - return this.name + (this.message && ': ') + this.message; - } - }; - }; - var getUserMedia_ = function getUserMedia_(constraints, onSuccess, onError) { - shimConstraints_(constraints, function (c) { - navigator.webkitGetUserMedia(c, onSuccess, function (e) { - if (onError) { - onError(shimError_(e)); - } - }); - }); - }; - navigator.getUserMedia = getUserMedia_.bind(navigator); - - // Even though Chrome 45 has navigator.mediaDevices and a getUserMedia - // function which returns a Promise, it does not accept spec-style - // constraints. - if (navigator.mediaDevices.getUserMedia) { - var origGetUserMedia = navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices); - navigator.mediaDevices.getUserMedia = function (cs) { - return shimConstraints_(cs, function (c) { - return origGetUserMedia(c).then(function (stream) { - if (c.audio && !stream.getAudioTracks().length || c.video && !stream.getVideoTracks().length) { - stream.getTracks().forEach(function (track) { - track.stop(); - }); - throw new DOMException('', 'NotFoundError'); - } - return stream; - }, function (e) { - return Promise.reject(shimError_(e)); - }); - }); - }; - } -} - -},{"../utils.js":11}],6:[function(require,module,exports){ -/* - * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. - */ -/* eslint-env node */ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.removeExtmapAllowMixed = removeExtmapAllowMixed; -exports.shimAddIceCandidateNullOrEmpty = shimAddIceCandidateNullOrEmpty; -exports.shimConnectionState = shimConnectionState; -exports.shimMaxMessageSize = shimMaxMessageSize; -exports.shimParameterlessSetLocalDescription = shimParameterlessSetLocalDescription; -exports.shimRTCIceCandidate = shimRTCIceCandidate; -exports.shimRTCIceCandidateRelayProtocol = shimRTCIceCandidateRelayProtocol; -exports.shimSendThrowTypeError = shimSendThrowTypeError; -var _sdp = _interopRequireDefault(require("sdp")); -var utils = _interopRequireWildcard(require("./utils")); -function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } -function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; } -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { "default": obj }; } -function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); } -function shimRTCIceCandidate(window) { - // foundation is arbitrarily chosen as an indicator for full support for - // https://w3c.github.io/webrtc-pc/#rtcicecandidate-interface - if (!window.RTCIceCandidate || window.RTCIceCandidate && 'foundation' in window.RTCIceCandidate.prototype) { - return; - } - var NativeRTCIceCandidate = window.RTCIceCandidate; - window.RTCIceCandidate = function RTCIceCandidate(args) { - // Remove the a= which shouldn't be part of the candidate string. - if (_typeof(args) === 'object' && args.candidate && args.candidate.indexOf('a=') === 0) { - args = JSON.parse(JSON.stringify(args)); - args.candidate = args.candidate.substring(2); - } - if (args.candidate && args.candidate.length) { - // Augment the native candidate with the parsed fields. - var nativeCandidate = new NativeRTCIceCandidate(args); - var parsedCandidate = _sdp["default"].parseCandidate(args.candidate); - for (var key in parsedCandidate) { - if (!(key in nativeCandidate)) { - Object.defineProperty(nativeCandidate, key, { - value: parsedCandidate[key] - }); - } - } - - // Override serializer to not serialize the extra attributes. - nativeCandidate.toJSON = function toJSON() { - return { - candidate: nativeCandidate.candidate, - sdpMid: nativeCandidate.sdpMid, - sdpMLineIndex: nativeCandidate.sdpMLineIndex, - usernameFragment: nativeCandidate.usernameFragment - }; - }; - return nativeCandidate; - } - return new NativeRTCIceCandidate(args); - }; - window.RTCIceCandidate.prototype = NativeRTCIceCandidate.prototype; - - // Hook up the augmented candidate in onicecandidate and - // addEventListener('icecandidate', ...) - utils.wrapPeerConnectionEvent(window, 'icecandidate', function (e) { - if (e.candidate) { - Object.defineProperty(e, 'candidate', { - value: new window.RTCIceCandidate(e.candidate), - writable: 'false' - }); - } - return e; - }); -} -function shimRTCIceCandidateRelayProtocol(window) { - if (!window.RTCIceCandidate || window.RTCIceCandidate && 'relayProtocol' in window.RTCIceCandidate.prototype) { - return; - } - - // Hook up the augmented candidate in onicecandidate and - // addEventListener('icecandidate', ...) - utils.wrapPeerConnectionEvent(window, 'icecandidate', function (e) { - if (e.candidate) { - var parsedCandidate = _sdp["default"].parseCandidate(e.candidate.candidate); - if (parsedCandidate.type === 'relay') { - // This is a libwebrtc-specific mapping of local type preference - // to relayProtocol. - e.candidate.relayProtocol = { - 0: 'tls', - 1: 'tcp', - 2: 'udp' - }[parsedCandidate.priority >> 24]; - } - } - return e; - }); -} -function shimMaxMessageSize(window, browserDetails) { - if (!window.RTCPeerConnection) { - return; - } - if (!('sctp' in window.RTCPeerConnection.prototype)) { - Object.defineProperty(window.RTCPeerConnection.prototype, 'sctp', { - get: function get() { - return typeof this._sctp === 'undefined' ? null : this._sctp; - } - }); - } - var sctpInDescription = function sctpInDescription(description) { - if (!description || !description.sdp) { - return false; - } - var sections = _sdp["default"].splitSections(description.sdp); - sections.shift(); - return sections.some(function (mediaSection) { - var mLine = _sdp["default"].parseMLine(mediaSection); - return mLine && mLine.kind === 'application' && mLine.protocol.indexOf('SCTP') !== -1; - }); - }; - var getRemoteFirefoxVersion = function getRemoteFirefoxVersion(description) { - // TODO: Is there a better solution for detecting Firefox? - var match = description.sdp.match(/mozilla...THIS_IS_SDPARTA-(\d+)/); - if (match === null || match.length < 2) { - return -1; - } - var version = parseInt(match[1], 10); - // Test for NaN (yes, this is ugly) - return version !== version ? -1 : version; - }; - var getCanSendMaxMessageSize = function getCanSendMaxMessageSize(remoteIsFirefox) { - // Every implementation we know can send at least 64 KiB. - // Note: Although Chrome is technically able to send up to 256 KiB, the - // data does not reach the other peer reliably. - // See: https://bugs.chromium.org/p/webrtc/issues/detail?id=8419 - var canSendMaxMessageSize = 65536; - if (browserDetails.browser === 'firefox') { - if (browserDetails.version < 57) { - if (remoteIsFirefox === -1) { - // FF < 57 will send in 16 KiB chunks using the deprecated PPID - // fragmentation. - canSendMaxMessageSize = 16384; - } else { - // However, other FF (and RAWRTC) can reassemble PPID-fragmented - // messages. Thus, supporting ~2 GiB when sending. - canSendMaxMessageSize = 2147483637; - } - } else if (browserDetails.version < 60) { - // Currently, all FF >= 57 will reset the remote maximum message size - // to the default value when a data channel is created at a later - // stage. :( - // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831 - canSendMaxMessageSize = browserDetails.version === 57 ? 65535 : 65536; - } else { - // FF >= 60 supports sending ~2 GiB - canSendMaxMessageSize = 2147483637; - } - } - return canSendMaxMessageSize; - }; - var getMaxMessageSize = function getMaxMessageSize(description, remoteIsFirefox) { - // Note: 65536 bytes is the default value from the SDP spec. Also, - // every implementation we know supports receiving 65536 bytes. - var maxMessageSize = 65536; - - // FF 57 has a slightly incorrect default remote max message size, so - // we need to adjust it here to avoid a failure when sending. - // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1425697 - if (browserDetails.browser === 'firefox' && browserDetails.version === 57) { - maxMessageSize = 65535; - } - var match = _sdp["default"].matchPrefix(description.sdp, 'a=max-message-size:'); - if (match.length > 0) { - maxMessageSize = parseInt(match[0].substring(19), 10); - } else if (browserDetails.browser === 'firefox' && remoteIsFirefox !== -1) { - // If the maximum message size is not present in the remote SDP and - // both local and remote are Firefox, the remote peer can receive - // ~2 GiB. - maxMessageSize = 2147483637; - } - return maxMessageSize; - }; - var origSetRemoteDescription = window.RTCPeerConnection.prototype.setRemoteDescription; - window.RTCPeerConnection.prototype.setRemoteDescription = function setRemoteDescription() { - this._sctp = null; - // Chrome decided to not expose .sctp in plan-b mode. - // As usual, adapter.js has to do an 'ugly worakaround' - // to cover up the mess. - if (browserDetails.browser === 'chrome' && browserDetails.version >= 76) { - var _this$getConfiguratio = this.getConfiguration(), - sdpSemantics = _this$getConfiguratio.sdpSemantics; - if (sdpSemantics === 'plan-b') { - Object.defineProperty(this, 'sctp', { - get: function get() { - return typeof this._sctp === 'undefined' ? null : this._sctp; - }, - enumerable: true, - configurable: true - }); - } - } - if (sctpInDescription(arguments[0])) { - // Check if the remote is FF. - var isFirefox = getRemoteFirefoxVersion(arguments[0]); - - // Get the maximum message size the local peer is capable of sending - var canSendMMS = getCanSendMaxMessageSize(isFirefox); - - // Get the maximum message size of the remote peer. - var remoteMMS = getMaxMessageSize(arguments[0], isFirefox); - - // Determine final maximum message size - var maxMessageSize; - if (canSendMMS === 0 && remoteMMS === 0) { - maxMessageSize = Number.POSITIVE_INFINITY; - } else if (canSendMMS === 0 || remoteMMS === 0) { - maxMessageSize = Math.max(canSendMMS, remoteMMS); - } else { - maxMessageSize = Math.min(canSendMMS, remoteMMS); - } - - // Create a dummy RTCSctpTransport object and the 'maxMessageSize' - // attribute. - var sctp = {}; - Object.defineProperty(sctp, 'maxMessageSize', { - get: function get() { - return maxMessageSize; - } - }); - this._sctp = sctp; - } - return origSetRemoteDescription.apply(this, arguments); - }; -} -function shimSendThrowTypeError(window) { - if (!(window.RTCPeerConnection && 'createDataChannel' in window.RTCPeerConnection.prototype)) { - return; - } - - // Note: Although Firefox >= 57 has a native implementation, the maximum - // message size can be reset for all data channels at a later stage. - // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831 - - function wrapDcSend(dc, pc) { - var origDataChannelSend = dc.send; - dc.send = function send() { - var data = arguments[0]; - var length = data.length || data.size || data.byteLength; - if (dc.readyState === 'open' && pc.sctp && length > pc.sctp.maxMessageSize) { - throw new TypeError('Message too large (can send a maximum of ' + pc.sctp.maxMessageSize + ' bytes)'); - } - return origDataChannelSend.apply(dc, arguments); - }; - } - var origCreateDataChannel = window.RTCPeerConnection.prototype.createDataChannel; - window.RTCPeerConnection.prototype.createDataChannel = function createDataChannel() { - var dataChannel = origCreateDataChannel.apply(this, arguments); - wrapDcSend(dataChannel, this); - return dataChannel; - }; - utils.wrapPeerConnectionEvent(window, 'datachannel', function (e) { - wrapDcSend(e.channel, e.target); - return e; - }); -} - -/* shims RTCConnectionState by pretending it is the same as iceConnectionState. - * See https://bugs.chromium.org/p/webrtc/issues/detail?id=6145#c12 - * for why this is a valid hack in Chrome. In Firefox it is slightly incorrect - * since DTLS failures would be hidden. See - * https://bugzilla.mozilla.org/show_bug.cgi?id=1265827 - * for the Firefox tracking bug. - */ -function shimConnectionState(window) { - if (!window.RTCPeerConnection || 'connectionState' in window.RTCPeerConnection.prototype) { - return; - } - var proto = window.RTCPeerConnection.prototype; - Object.defineProperty(proto, 'connectionState', { - get: function get() { - return { - completed: 'connected', - checking: 'connecting' - }[this.iceConnectionState] || this.iceConnectionState; - }, - enumerable: true, - configurable: true - }); - Object.defineProperty(proto, 'onconnectionstatechange', { - get: function get() { - return this._onconnectionstatechange || null; - }, - set: function set(cb) { - if (this._onconnectionstatechange) { - this.removeEventListener('connectionstatechange', this._onconnectionstatechange); - delete this._onconnectionstatechange; - } - if (cb) { - this.addEventListener('connectionstatechange', this._onconnectionstatechange = cb); - } - }, - enumerable: true, - configurable: true - }); - ['setLocalDescription', 'setRemoteDescription'].forEach(function (method) { - var origMethod = proto[method]; - proto[method] = function () { - if (!this._connectionstatechangepoly) { - this._connectionstatechangepoly = function (e) { - var pc = e.target; - if (pc._lastConnectionState !== pc.connectionState) { - pc._lastConnectionState = pc.connectionState; - var newEvent = new Event('connectionstatechange', e); - pc.dispatchEvent(newEvent); - } - return e; - }; - this.addEventListener('iceconnectionstatechange', this._connectionstatechangepoly); - } - return origMethod.apply(this, arguments); - }; - }); -} -function removeExtmapAllowMixed(window, browserDetails) { - /* remove a=extmap-allow-mixed for webrtc.org < M71 */ - if (!window.RTCPeerConnection) { - return; - } - if (browserDetails.browser === 'chrome' && browserDetails.version >= 71) { - return; - } - if (browserDetails.browser === 'safari' && browserDetails.version >= 605) { - return; - } - var nativeSRD = window.RTCPeerConnection.prototype.setRemoteDescription; - window.RTCPeerConnection.prototype.setRemoteDescription = function setRemoteDescription(desc) { - if (desc && desc.sdp && desc.sdp.indexOf('\na=extmap-allow-mixed') !== -1) { - var sdp = desc.sdp.split('\n').filter(function (line) { - return line.trim() !== 'a=extmap-allow-mixed'; - }).join('\n'); - // Safari enforces read-only-ness of RTCSessionDescription fields. - if (window.RTCSessionDescription && desc instanceof window.RTCSessionDescription) { - arguments[0] = new window.RTCSessionDescription({ - type: desc.type, - sdp: sdp - }); - } else { - desc.sdp = sdp; - } - } - return nativeSRD.apply(this, arguments); - }; -} -function shimAddIceCandidateNullOrEmpty(window, browserDetails) { - // Support for addIceCandidate(null or undefined) - // as well as addIceCandidate({candidate: "", ...}) - // https://bugs.chromium.org/p/chromium/issues/detail?id=978582 - // Note: must be called before other polyfills which change the signature. - if (!(window.RTCPeerConnection && window.RTCPeerConnection.prototype)) { - return; - } - var nativeAddIceCandidate = window.RTCPeerConnection.prototype.addIceCandidate; - if (!nativeAddIceCandidate || nativeAddIceCandidate.length === 0) { - return; - } - window.RTCPeerConnection.prototype.addIceCandidate = function addIceCandidate() { - if (!arguments[0]) { - if (arguments[1]) { - arguments[1].apply(null); - } - return Promise.resolve(); - } - // Firefox 68+ emits and processes {candidate: "", ...}, ignore - // in older versions. - // Native support for ignoring exists for Chrome M77+. - // Safari ignores as well, exact version unknown but works in the same - // version that also ignores addIceCandidate(null). - if ((browserDetails.browser === 'chrome' && browserDetails.version < 78 || browserDetails.browser === 'firefox' && browserDetails.version < 68 || browserDetails.browser === 'safari') && arguments[0] && arguments[0].candidate === '') { - return Promise.resolve(); - } - return nativeAddIceCandidate.apply(this, arguments); - }; -} - -// Note: Make sure to call this ahead of APIs that modify -// setLocalDescription.length -function shimParameterlessSetLocalDescription(window, browserDetails) { - if (!(window.RTCPeerConnection && window.RTCPeerConnection.prototype)) { - return; - } - var nativeSetLocalDescription = window.RTCPeerConnection.prototype.setLocalDescription; - if (!nativeSetLocalDescription || nativeSetLocalDescription.length === 0) { - return; - } - window.RTCPeerConnection.prototype.setLocalDescription = function setLocalDescription() { - var _this = this; - var desc = arguments[0] || {}; - if (_typeof(desc) !== 'object' || desc.type && desc.sdp) { - return nativeSetLocalDescription.apply(this, arguments); - } - // The remaining steps should technically happen when SLD comes off the - // RTCPeerConnection's operations chain (not ahead of going on it), but - // this is too difficult to shim. Instead, this shim only covers the - // common case where the operations chain is empty. This is imperfect, but - // should cover many cases. Rationale: Even if we can't reduce the glare - // window to zero on imperfect implementations, there's value in tapping - // into the perfect negotiation pattern that several browsers support. - desc = { - type: desc.type, - sdp: desc.sdp - }; - if (!desc.type) { - switch (this.signalingState) { - case 'stable': - case 'have-local-offer': - case 'have-remote-pranswer': - desc.type = 'offer'; - break; - default: - desc.type = 'answer'; - break; - } - } - if (desc.sdp || desc.type !== 'offer' && desc.type !== 'answer') { - return nativeSetLocalDescription.apply(this, [desc]); - } - var func = desc.type === 'offer' ? this.createOffer : this.createAnswer; - return func.apply(this).then(function (d) { - return nativeSetLocalDescription.apply(_this, [d]); - }); - }; -} - -},{"./utils":11,"sdp":12}],7:[function(require,module,exports){ -/* - * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. - */ -/* eslint-env node */ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.shimAddTransceiver = shimAddTransceiver; -exports.shimCreateAnswer = shimCreateAnswer; -exports.shimCreateOffer = shimCreateOffer; -Object.defineProperty(exports, "shimGetDisplayMedia", { - enumerable: true, - get: function get() { - return _getdisplaymedia.shimGetDisplayMedia; - } -}); -exports.shimGetParameters = shimGetParameters; -Object.defineProperty(exports, "shimGetUserMedia", { - enumerable: true, - get: function get() { - return _getusermedia.shimGetUserMedia; - } -}); -exports.shimOnTrack = shimOnTrack; -exports.shimPeerConnection = shimPeerConnection; -exports.shimRTCDataChannel = shimRTCDataChannel; -exports.shimReceiverGetStats = shimReceiverGetStats; -exports.shimRemoveStream = shimRemoveStream; -exports.shimSenderGetStats = shimSenderGetStats; -var utils = _interopRequireWildcard(require("../utils")); -var _getusermedia = require("./getusermedia"); -var _getdisplaymedia = require("./getdisplaymedia"); -function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } -function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; } -function _toConsumableArray(arr) { return _arrayWithoutHoles(arr) || _iterableToArray(arr) || _unsupportedIterableToArray(arr) || _nonIterableSpread(); } -function _nonIterableSpread() { throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method."); } -function _unsupportedIterableToArray(o, minLen) { if (!o) return; if (typeof o === "string") return _arrayLikeToArray(o, minLen); var n = Object.prototype.toString.call(o).slice(8, -1); if (n === "Object" && o.constructor) n = o.constructor.name; if (n === "Map" || n === "Set") return Array.from(o); if (n === "Arguments" || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)) return _arrayLikeToArray(o, minLen); } -function _iterableToArray(iter) { if (typeof Symbol !== "undefined" && iter[Symbol.iterator] != null || iter["@@iterator"] != null) return Array.from(iter); } -function _arrayWithoutHoles(arr) { if (Array.isArray(arr)) return _arrayLikeToArray(arr); } -function _arrayLikeToArray(arr, len) { if (len == null || len > arr.length) len = arr.length; for (var i = 0, arr2 = new Array(len); i < len; i++) arr2[i] = arr[i]; return arr2; } -function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } -function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); } -function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); } -function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); } -function shimOnTrack(window) { - if (_typeof(window) === 'object' && window.RTCTrackEvent && 'receiver' in window.RTCTrackEvent.prototype && !('transceiver' in window.RTCTrackEvent.prototype)) { - Object.defineProperty(window.RTCTrackEvent.prototype, 'transceiver', { - get: function get() { - return { - receiver: this.receiver - }; - } - }); - } -} -function shimPeerConnection(window, browserDetails) { - if (_typeof(window) !== 'object' || !(window.RTCPeerConnection || window.mozRTCPeerConnection)) { - return; // probably media.peerconnection.enabled=false in about:config - } - - if (!window.RTCPeerConnection && window.mozRTCPeerConnection) { - // very basic support for old versions. - window.RTCPeerConnection = window.mozRTCPeerConnection; - } - if (browserDetails.version < 53) { - // shim away need for obsolete RTCIceCandidate/RTCSessionDescription. - ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'].forEach(function (method) { - var nativeMethod = window.RTCPeerConnection.prototype[method]; - var methodObj = _defineProperty({}, method, function () { - arguments[0] = new (method === 'addIceCandidate' ? window.RTCIceCandidate : window.RTCSessionDescription)(arguments[0]); - return nativeMethod.apply(this, arguments); - }); - window.RTCPeerConnection.prototype[method] = methodObj[method]; - }); - } - var modernStatsTypes = { - inboundrtp: 'inbound-rtp', - outboundrtp: 'outbound-rtp', - candidatepair: 'candidate-pair', - localcandidate: 'local-candidate', - remotecandidate: 'remote-candidate' - }; - var nativeGetStats = window.RTCPeerConnection.prototype.getStats; - window.RTCPeerConnection.prototype.getStats = function getStats() { - var _arguments = Array.prototype.slice.call(arguments), - selector = _arguments[0], - onSucc = _arguments[1], - onErr = _arguments[2]; - return nativeGetStats.apply(this, [selector || null]).then(function (stats) { - if (browserDetails.version < 53 && !onSucc) { - // Shim only promise getStats with spec-hyphens in type names - // Leave callback version alone; misc old uses of forEach before Map - try { - stats.forEach(function (stat) { - stat.type = modernStatsTypes[stat.type] || stat.type; - }); - } catch (e) { - if (e.name !== 'TypeError') { - throw e; - } - // Avoid TypeError: "type" is read-only, in old versions. 34-43ish - stats.forEach(function (stat, i) { - stats.set(i, Object.assign({}, stat, { - type: modernStatsTypes[stat.type] || stat.type - })); - }); - } - } - return stats; - }).then(onSucc, onErr); - }; -} -function shimSenderGetStats(window) { - if (!(_typeof(window) === 'object' && window.RTCPeerConnection && window.RTCRtpSender)) { - return; - } - if (window.RTCRtpSender && 'getStats' in window.RTCRtpSender.prototype) { - return; - } - var origGetSenders = window.RTCPeerConnection.prototype.getSenders; - if (origGetSenders) { - window.RTCPeerConnection.prototype.getSenders = function getSenders() { - var _this = this; - var senders = origGetSenders.apply(this, []); - senders.forEach(function (sender) { - return sender._pc = _this; - }); - return senders; - }; - } - var origAddTrack = window.RTCPeerConnection.prototype.addTrack; - if (origAddTrack) { - window.RTCPeerConnection.prototype.addTrack = function addTrack() { - var sender = origAddTrack.apply(this, arguments); - sender._pc = this; - return sender; - }; - } - window.RTCRtpSender.prototype.getStats = function getStats() { - return this.track ? this._pc.getStats(this.track) : Promise.resolve(new Map()); - }; -} -function shimReceiverGetStats(window) { - if (!(_typeof(window) === 'object' && window.RTCPeerConnection && window.RTCRtpSender)) { - return; - } - if (window.RTCRtpSender && 'getStats' in window.RTCRtpReceiver.prototype) { - return; - } - var origGetReceivers = window.RTCPeerConnection.prototype.getReceivers; - if (origGetReceivers) { - window.RTCPeerConnection.prototype.getReceivers = function getReceivers() { - var _this2 = this; - var receivers = origGetReceivers.apply(this, []); - receivers.forEach(function (receiver) { - return receiver._pc = _this2; - }); - return receivers; - }; - } - utils.wrapPeerConnectionEvent(window, 'track', function (e) { - e.receiver._pc = e.srcElement; - return e; - }); - window.RTCRtpReceiver.prototype.getStats = function getStats() { - return this._pc.getStats(this.track); - }; -} -function shimRemoveStream(window) { - if (!window.RTCPeerConnection || 'removeStream' in window.RTCPeerConnection.prototype) { - return; - } - window.RTCPeerConnection.prototype.removeStream = function removeStream(stream) { - var _this3 = this; - utils.deprecated('removeStream', 'removeTrack'); - this.getSenders().forEach(function (sender) { - if (sender.track && stream.getTracks().includes(sender.track)) { - _this3.removeTrack(sender); - } - }); - }; -} -function shimRTCDataChannel(window) { - // rename DataChannel to RTCDataChannel (native fix in FF60): - // https://bugzilla.mozilla.org/show_bug.cgi?id=1173851 - if (window.DataChannel && !window.RTCDataChannel) { - window.RTCDataChannel = window.DataChannel; - } -} -function shimAddTransceiver(window) { - // https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647 - // Firefox ignores the init sendEncodings options passed to addTransceiver - // https://bugzilla.mozilla.org/show_bug.cgi?id=1396918 - if (!(_typeof(window) === 'object' && window.RTCPeerConnection)) { - return; - } - var origAddTransceiver = window.RTCPeerConnection.prototype.addTransceiver; - if (origAddTransceiver) { - window.RTCPeerConnection.prototype.addTransceiver = function addTransceiver() { - this.setParametersPromises = []; - // WebIDL input coercion and validation - var sendEncodings = arguments[1] && arguments[1].sendEncodings; - if (sendEncodings === undefined) { - sendEncodings = []; - } - sendEncodings = _toConsumableArray(sendEncodings); - var shouldPerformCheck = sendEncodings.length > 0; - if (shouldPerformCheck) { - // If sendEncodings params are provided, validate grammar - sendEncodings.forEach(function (encodingParam) { - if ('rid' in encodingParam) { - var ridRegex = /^[a-z0-9]{0,16}$/i; - if (!ridRegex.test(encodingParam.rid)) { - throw new TypeError('Invalid RID value provided.'); - } - } - if ('scaleResolutionDownBy' in encodingParam) { - if (!(parseFloat(encodingParam.scaleResolutionDownBy) >= 1.0)) { - throw new RangeError('scale_resolution_down_by must be >= 1.0'); - } - } - if ('maxFramerate' in encodingParam) { - if (!(parseFloat(encodingParam.maxFramerate) >= 0)) { - throw new RangeError('max_framerate must be >= 0.0'); - } - } - }); - } - var transceiver = origAddTransceiver.apply(this, arguments); - if (shouldPerformCheck) { - // Check if the init options were applied. If not we do this in an - // asynchronous way and save the promise reference in a global object. - // This is an ugly hack, but at the same time is way more robust than - // checking the sender parameters before and after the createOffer - // Also note that after the createoffer we are not 100% sure that - // the params were asynchronously applied so we might miss the - // opportunity to recreate offer. - var sender = transceiver.sender; - var params = sender.getParameters(); - if (!('encodings' in params) || - // Avoid being fooled by patched getParameters() below. - params.encodings.length === 1 && Object.keys(params.encodings[0]).length === 0) { - params.encodings = sendEncodings; - sender.sendEncodings = sendEncodings; - this.setParametersPromises.push(sender.setParameters(params).then(function () { - delete sender.sendEncodings; - })["catch"](function () { - delete sender.sendEncodings; - })); - } - } - return transceiver; - }; - } -} -function shimGetParameters(window) { - if (!(_typeof(window) === 'object' && window.RTCRtpSender)) { - return; - } - var origGetParameters = window.RTCRtpSender.prototype.getParameters; - if (origGetParameters) { - window.RTCRtpSender.prototype.getParameters = function getParameters() { - var params = origGetParameters.apply(this, arguments); - if (!('encodings' in params)) { - params.encodings = [].concat(this.sendEncodings || [{}]); - } - return params; - }; - } -} -function shimCreateOffer(window) { - // https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647 - // Firefox ignores the init sendEncodings options passed to addTransceiver - // https://bugzilla.mozilla.org/show_bug.cgi?id=1396918 - if (!(_typeof(window) === 'object' && window.RTCPeerConnection)) { - return; - } - var origCreateOffer = window.RTCPeerConnection.prototype.createOffer; - window.RTCPeerConnection.prototype.createOffer = function createOffer() { - var _arguments2 = arguments, - _this4 = this; - if (this.setParametersPromises && this.setParametersPromises.length) { - return Promise.all(this.setParametersPromises).then(function () { - return origCreateOffer.apply(_this4, _arguments2); - })["finally"](function () { - _this4.setParametersPromises = []; - }); - } - return origCreateOffer.apply(this, arguments); - }; -} -function shimCreateAnswer(window) { - // https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647 - // Firefox ignores the init sendEncodings options passed to addTransceiver - // https://bugzilla.mozilla.org/show_bug.cgi?id=1396918 - if (!(_typeof(window) === 'object' && window.RTCPeerConnection)) { - return; - } - var origCreateAnswer = window.RTCPeerConnection.prototype.createAnswer; - window.RTCPeerConnection.prototype.createAnswer = function createAnswer() { - var _arguments3 = arguments, - _this5 = this; - if (this.setParametersPromises && this.setParametersPromises.length) { - return Promise.all(this.setParametersPromises).then(function () { - return origCreateAnswer.apply(_this5, _arguments3); - })["finally"](function () { - _this5.setParametersPromises = []; - }); - } - return origCreateAnswer.apply(this, arguments); - }; -} - -},{"../utils":11,"./getdisplaymedia":8,"./getusermedia":9}],8:[function(require,module,exports){ -/* - * Copyright (c) 2018 The adapter.js project authors. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. - */ -/* eslint-env node */ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.shimGetDisplayMedia = shimGetDisplayMedia; -function shimGetDisplayMedia(window, preferredMediaSource) { - if (window.navigator.mediaDevices && 'getDisplayMedia' in window.navigator.mediaDevices) { - return; - } - if (!window.navigator.mediaDevices) { - return; - } - window.navigator.mediaDevices.getDisplayMedia = function getDisplayMedia(constraints) { - if (!(constraints && constraints.video)) { - var err = new DOMException('getDisplayMedia without video ' + 'constraints is undefined'); - err.name = 'NotFoundError'; - // from https://heycam.github.io/webidl/#idl-DOMException-error-names - err.code = 8; - return Promise.reject(err); - } - if (constraints.video === true) { - constraints.video = { - mediaSource: preferredMediaSource - }; - } else { - constraints.video.mediaSource = preferredMediaSource; - } - return window.navigator.mediaDevices.getUserMedia(constraints); - }; -} - -},{}],9:[function(require,module,exports){ -/* - * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. - */ -/* eslint-env node */ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.shimGetUserMedia = shimGetUserMedia; -var utils = _interopRequireWildcard(require("../utils")); -function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } -function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; } -function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); } -function shimGetUserMedia(window, browserDetails) { - var navigator = window && window.navigator; - var MediaStreamTrack = window && window.MediaStreamTrack; - navigator.getUserMedia = function (constraints, onSuccess, onError) { - // Replace Firefox 44+'s deprecation warning with unprefixed version. - utils.deprecated('navigator.getUserMedia', 'navigator.mediaDevices.getUserMedia'); - navigator.mediaDevices.getUserMedia(constraints).then(onSuccess, onError); - }; - if (!(browserDetails.version > 55 && 'autoGainControl' in navigator.mediaDevices.getSupportedConstraints())) { - var remap = function remap(obj, a, b) { - if (a in obj && !(b in obj)) { - obj[b] = obj[a]; - delete obj[a]; - } - }; - var nativeGetUserMedia = navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices); - navigator.mediaDevices.getUserMedia = function (c) { - if (_typeof(c) === 'object' && _typeof(c.audio) === 'object') { - c = JSON.parse(JSON.stringify(c)); - remap(c.audio, 'autoGainControl', 'mozAutoGainControl'); - remap(c.audio, 'noiseSuppression', 'mozNoiseSuppression'); - } - return nativeGetUserMedia(c); - }; - if (MediaStreamTrack && MediaStreamTrack.prototype.getSettings) { - var nativeGetSettings = MediaStreamTrack.prototype.getSettings; - MediaStreamTrack.prototype.getSettings = function () { - var obj = nativeGetSettings.apply(this, arguments); - remap(obj, 'mozAutoGainControl', 'autoGainControl'); - remap(obj, 'mozNoiseSuppression', 'noiseSuppression'); - return obj; - }; - } - if (MediaStreamTrack && MediaStreamTrack.prototype.applyConstraints) { - var nativeApplyConstraints = MediaStreamTrack.prototype.applyConstraints; - MediaStreamTrack.prototype.applyConstraints = function (c) { - if (this.kind === 'audio' && _typeof(c) === 'object') { - c = JSON.parse(JSON.stringify(c)); - remap(c, 'autoGainControl', 'mozAutoGainControl'); - remap(c, 'noiseSuppression', 'mozNoiseSuppression'); - } - return nativeApplyConstraints.apply(this, [c]); - }; - } - } -} - -},{"../utils":11}],10:[function(require,module,exports){ -/* - * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. - */ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.shimAudioContext = shimAudioContext; -exports.shimCallbacksAPI = shimCallbacksAPI; -exports.shimConstraints = shimConstraints; -exports.shimCreateOfferLegacy = shimCreateOfferLegacy; -exports.shimGetUserMedia = shimGetUserMedia; -exports.shimLocalStreamsAPI = shimLocalStreamsAPI; -exports.shimRTCIceServerUrls = shimRTCIceServerUrls; -exports.shimRemoteStreamsAPI = shimRemoteStreamsAPI; -exports.shimTrackEventTransceiver = shimTrackEventTransceiver; -var utils = _interopRequireWildcard(require("../utils")); -function _getRequireWildcardCache(nodeInterop) { if (typeof WeakMap !== "function") return null; var cacheBabelInterop = new WeakMap(); var cacheNodeInterop = new WeakMap(); return (_getRequireWildcardCache = function _getRequireWildcardCache(nodeInterop) { return nodeInterop ? cacheNodeInterop : cacheBabelInterop; })(nodeInterop); } -function _interopRequireWildcard(obj, nodeInterop) { if (!nodeInterop && obj && obj.__esModule) { return obj; } if (obj === null || _typeof(obj) !== "object" && typeof obj !== "function") { return { "default": obj }; } var cache = _getRequireWildcardCache(nodeInterop); if (cache && cache.has(obj)) { return cache.get(obj); } var newObj = {}; var hasPropertyDescriptor = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var key in obj) { if (key !== "default" && Object.prototype.hasOwnProperty.call(obj, key)) { var desc = hasPropertyDescriptor ? Object.getOwnPropertyDescriptor(obj, key) : null; if (desc && (desc.get || desc.set)) { Object.defineProperty(newObj, key, desc); } else { newObj[key] = obj[key]; } } } newObj["default"] = obj; if (cache) { cache.set(obj, newObj); } return newObj; } -function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); } -function shimLocalStreamsAPI(window) { - if (_typeof(window) !== 'object' || !window.RTCPeerConnection) { - return; - } - if (!('getLocalStreams' in window.RTCPeerConnection.prototype)) { - window.RTCPeerConnection.prototype.getLocalStreams = function getLocalStreams() { - if (!this._localStreams) { - this._localStreams = []; - } - return this._localStreams; - }; - } - if (!('addStream' in window.RTCPeerConnection.prototype)) { - var _addTrack = window.RTCPeerConnection.prototype.addTrack; - window.RTCPeerConnection.prototype.addStream = function addStream(stream) { - var _this = this; - if (!this._localStreams) { - this._localStreams = []; - } - if (!this._localStreams.includes(stream)) { - this._localStreams.push(stream); - } - // Try to emulate Chrome's behaviour of adding in audio-video order. - // Safari orders by track id. - stream.getAudioTracks().forEach(function (track) { - return _addTrack.call(_this, track, stream); - }); - stream.getVideoTracks().forEach(function (track) { - return _addTrack.call(_this, track, stream); - }); - }; - window.RTCPeerConnection.prototype.addTrack = function addTrack(track) { - var _this2 = this; - for (var _len = arguments.length, streams = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { - streams[_key - 1] = arguments[_key]; - } - if (streams) { - streams.forEach(function (stream) { - if (!_this2._localStreams) { - _this2._localStreams = [stream]; - } else if (!_this2._localStreams.includes(stream)) { - _this2._localStreams.push(stream); - } - }); - } - return _addTrack.apply(this, arguments); - }; - } - if (!('removeStream' in window.RTCPeerConnection.prototype)) { - window.RTCPeerConnection.prototype.removeStream = function removeStream(stream) { - var _this3 = this; - if (!this._localStreams) { - this._localStreams = []; - } - var index = this._localStreams.indexOf(stream); - if (index === -1) { - return; - } - this._localStreams.splice(index, 1); - var tracks = stream.getTracks(); - this.getSenders().forEach(function (sender) { - if (tracks.includes(sender.track)) { - _this3.removeTrack(sender); - } - }); - }; - } -} -function shimRemoteStreamsAPI(window) { - if (_typeof(window) !== 'object' || !window.RTCPeerConnection) { - return; - } - if (!('getRemoteStreams' in window.RTCPeerConnection.prototype)) { - window.RTCPeerConnection.prototype.getRemoteStreams = function getRemoteStreams() { - return this._remoteStreams ? this._remoteStreams : []; - }; - } - if (!('onaddstream' in window.RTCPeerConnection.prototype)) { - Object.defineProperty(window.RTCPeerConnection.prototype, 'onaddstream', { - get: function get() { - return this._onaddstream; - }, - set: function set(f) { - var _this4 = this; - if (this._onaddstream) { - this.removeEventListener('addstream', this._onaddstream); - this.removeEventListener('track', this._onaddstreampoly); - } - this.addEventListener('addstream', this._onaddstream = f); - this.addEventListener('track', this._onaddstreampoly = function (e) { - e.streams.forEach(function (stream) { - if (!_this4._remoteStreams) { - _this4._remoteStreams = []; - } - if (_this4._remoteStreams.includes(stream)) { - return; - } - _this4._remoteStreams.push(stream); - var event = new Event('addstream'); - event.stream = stream; - _this4.dispatchEvent(event); - }); - }); - } - }); - var origSetRemoteDescription = window.RTCPeerConnection.prototype.setRemoteDescription; - window.RTCPeerConnection.prototype.setRemoteDescription = function setRemoteDescription() { - var pc = this; - if (!this._onaddstreampoly) { - this.addEventListener('track', this._onaddstreampoly = function (e) { - e.streams.forEach(function (stream) { - if (!pc._remoteStreams) { - pc._remoteStreams = []; - } - if (pc._remoteStreams.indexOf(stream) >= 0) { - return; - } - pc._remoteStreams.push(stream); - var event = new Event('addstream'); - event.stream = stream; - pc.dispatchEvent(event); - }); - }); - } - return origSetRemoteDescription.apply(pc, arguments); - }; - } -} -function shimCallbacksAPI(window) { - if (_typeof(window) !== 'object' || !window.RTCPeerConnection) { - return; - } - var prototype = window.RTCPeerConnection.prototype; - var origCreateOffer = prototype.createOffer; - var origCreateAnswer = prototype.createAnswer; - var setLocalDescription = prototype.setLocalDescription; - var setRemoteDescription = prototype.setRemoteDescription; - var addIceCandidate = prototype.addIceCandidate; - prototype.createOffer = function createOffer(successCallback, failureCallback) { - var options = arguments.length >= 2 ? arguments[2] : arguments[0]; - var promise = origCreateOffer.apply(this, [options]); - if (!failureCallback) { - return promise; - } - promise.then(successCallback, failureCallback); - return Promise.resolve(); - }; - prototype.createAnswer = function createAnswer(successCallback, failureCallback) { - var options = arguments.length >= 2 ? arguments[2] : arguments[0]; - var promise = origCreateAnswer.apply(this, [options]); - if (!failureCallback) { - return promise; - } - promise.then(successCallback, failureCallback); - return Promise.resolve(); - }; - var withCallback = function withCallback(description, successCallback, failureCallback) { - var promise = setLocalDescription.apply(this, [description]); - if (!failureCallback) { - return promise; - } - promise.then(successCallback, failureCallback); - return Promise.resolve(); - }; - prototype.setLocalDescription = withCallback; - withCallback = function withCallback(description, successCallback, failureCallback) { - var promise = setRemoteDescription.apply(this, [description]); - if (!failureCallback) { - return promise; - } - promise.then(successCallback, failureCallback); - return Promise.resolve(); - }; - prototype.setRemoteDescription = withCallback; - withCallback = function withCallback(candidate, successCallback, failureCallback) { - var promise = addIceCandidate.apply(this, [candidate]); - if (!failureCallback) { - return promise; - } - promise.then(successCallback, failureCallback); - return Promise.resolve(); - }; - prototype.addIceCandidate = withCallback; -} -function shimGetUserMedia(window) { - var navigator = window && window.navigator; - if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { - // shim not needed in Safari 12.1 - var mediaDevices = navigator.mediaDevices; - var _getUserMedia = mediaDevices.getUserMedia.bind(mediaDevices); - navigator.mediaDevices.getUserMedia = function (constraints) { - return _getUserMedia(shimConstraints(constraints)); - }; - } - if (!navigator.getUserMedia && navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { - navigator.getUserMedia = function getUserMedia(constraints, cb, errcb) { - navigator.mediaDevices.getUserMedia(constraints).then(cb, errcb); - }.bind(navigator); - } -} -function shimConstraints(constraints) { - if (constraints && constraints.video !== undefined) { - return Object.assign({}, constraints, { - video: utils.compactObject(constraints.video) - }); - } - return constraints; -} -function shimRTCIceServerUrls(window) { - if (!window.RTCPeerConnection) { - return; - } - // migrate from non-spec RTCIceServer.url to RTCIceServer.urls - var OrigPeerConnection = window.RTCPeerConnection; - window.RTCPeerConnection = function RTCPeerConnection(pcConfig, pcConstraints) { - if (pcConfig && pcConfig.iceServers) { - var newIceServers = []; - for (var i = 0; i < pcConfig.iceServers.length; i++) { - var server = pcConfig.iceServers[i]; - if (server.urls === undefined && server.url) { - utils.deprecated('RTCIceServer.url', 'RTCIceServer.urls'); - server = JSON.parse(JSON.stringify(server)); - server.urls = server.url; - delete server.url; - newIceServers.push(server); - } else { - newIceServers.push(pcConfig.iceServers[i]); - } - } - pcConfig.iceServers = newIceServers; - } - return new OrigPeerConnection(pcConfig, pcConstraints); - }; - window.RTCPeerConnection.prototype = OrigPeerConnection.prototype; - // wrap static methods. Currently just generateCertificate. - if ('generateCertificate' in OrigPeerConnection) { - Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', { - get: function get() { - return OrigPeerConnection.generateCertificate; - } - }); - } -} -function shimTrackEventTransceiver(window) { - // Add event.transceiver member over deprecated event.receiver - if (_typeof(window) === 'object' && window.RTCTrackEvent && 'receiver' in window.RTCTrackEvent.prototype && !('transceiver' in window.RTCTrackEvent.prototype)) { - Object.defineProperty(window.RTCTrackEvent.prototype, 'transceiver', { - get: function get() { - return { - receiver: this.receiver - }; - } - }); - } -} -function shimCreateOfferLegacy(window) { - var origCreateOffer = window.RTCPeerConnection.prototype.createOffer; - window.RTCPeerConnection.prototype.createOffer = function createOffer(offerOptions) { - if (offerOptions) { - if (typeof offerOptions.offerToReceiveAudio !== 'undefined') { - // support bit values - offerOptions.offerToReceiveAudio = !!offerOptions.offerToReceiveAudio; - } - var audioTransceiver = this.getTransceivers().find(function (transceiver) { - return transceiver.receiver.track.kind === 'audio'; - }); - if (offerOptions.offerToReceiveAudio === false && audioTransceiver) { - if (audioTransceiver.direction === 'sendrecv') { - if (audioTransceiver.setDirection) { - audioTransceiver.setDirection('sendonly'); - } else { - audioTransceiver.direction = 'sendonly'; - } - } else if (audioTransceiver.direction === 'recvonly') { - if (audioTransceiver.setDirection) { - audioTransceiver.setDirection('inactive'); - } else { - audioTransceiver.direction = 'inactive'; - } - } - } else if (offerOptions.offerToReceiveAudio === true && !audioTransceiver) { - this.addTransceiver('audio', { - direction: 'recvonly' - }); - } - if (typeof offerOptions.offerToReceiveVideo !== 'undefined') { - // support bit values - offerOptions.offerToReceiveVideo = !!offerOptions.offerToReceiveVideo; - } - var videoTransceiver = this.getTransceivers().find(function (transceiver) { - return transceiver.receiver.track.kind === 'video'; - }); - if (offerOptions.offerToReceiveVideo === false && videoTransceiver) { - if (videoTransceiver.direction === 'sendrecv') { - if (videoTransceiver.setDirection) { - videoTransceiver.setDirection('sendonly'); - } else { - videoTransceiver.direction = 'sendonly'; - } - } else if (videoTransceiver.direction === 'recvonly') { - if (videoTransceiver.setDirection) { - videoTransceiver.setDirection('inactive'); - } else { - videoTransceiver.direction = 'inactive'; - } - } - } else if (offerOptions.offerToReceiveVideo === true && !videoTransceiver) { - this.addTransceiver('video', { - direction: 'recvonly' - }); - } - } - return origCreateOffer.apply(this, arguments); - }; -} -function shimAudioContext(window) { - if (_typeof(window) !== 'object' || window.AudioContext) { - return; - } - window.AudioContext = window.webkitAudioContext; -} - -},{"../utils":11}],11:[function(require,module,exports){ -/* - * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. - * - * Use of this source code is governed by a BSD-style license - * that can be found in the LICENSE file in the root of the source - * tree. - */ -/* eslint-env node */ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.compactObject = compactObject; -exports.deprecated = deprecated; -exports.detectBrowser = detectBrowser; -exports.disableLog = disableLog; -exports.disableWarnings = disableWarnings; -exports.extractVersion = extractVersion; -exports.filterStats = filterStats; -exports.log = log; -exports.walkStats = walkStats; -exports.wrapPeerConnectionEvent = wrapPeerConnectionEvent; -function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } -function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); } -function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); } -function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); } -var logDisabled_ = true; -var deprecationWarnings_ = true; - -/** - * Extract browser version out of the provided user agent string. - * - * @param {!string} uastring userAgent string. - * @param {!string} expr Regular expression used as match criteria. - * @param {!number} pos position in the version string to be returned. - * @return {!number} browser version. - */ -function extractVersion(uastring, expr, pos) { - var match = uastring.match(expr); - return match && match.length >= pos && parseInt(match[pos], 10); -} - -// Wraps the peerconnection event eventNameToWrap in a function -// which returns the modified event object (or false to prevent -// the event). -function wrapPeerConnectionEvent(window, eventNameToWrap, wrapper) { - if (!window.RTCPeerConnection) { - return; - } - var proto = window.RTCPeerConnection.prototype; - var nativeAddEventListener = proto.addEventListener; - proto.addEventListener = function (nativeEventName, cb) { - if (nativeEventName !== eventNameToWrap) { - return nativeAddEventListener.apply(this, arguments); - } - var wrappedCallback = function wrappedCallback(e) { - var modifiedEvent = wrapper(e); - if (modifiedEvent) { - if (cb.handleEvent) { - cb.handleEvent(modifiedEvent); - } else { - cb(modifiedEvent); - } - } - }; - this._eventMap = this._eventMap || {}; - if (!this._eventMap[eventNameToWrap]) { - this._eventMap[eventNameToWrap] = new Map(); - } - this._eventMap[eventNameToWrap].set(cb, wrappedCallback); - return nativeAddEventListener.apply(this, [nativeEventName, wrappedCallback]); - }; - var nativeRemoveEventListener = proto.removeEventListener; - proto.removeEventListener = function (nativeEventName, cb) { - if (nativeEventName !== eventNameToWrap || !this._eventMap || !this._eventMap[eventNameToWrap]) { - return nativeRemoveEventListener.apply(this, arguments); - } - if (!this._eventMap[eventNameToWrap].has(cb)) { - return nativeRemoveEventListener.apply(this, arguments); - } - var unwrappedCb = this._eventMap[eventNameToWrap].get(cb); - this._eventMap[eventNameToWrap]["delete"](cb); - if (this._eventMap[eventNameToWrap].size === 0) { - delete this._eventMap[eventNameToWrap]; - } - if (Object.keys(this._eventMap).length === 0) { - delete this._eventMap; - } - return nativeRemoveEventListener.apply(this, [nativeEventName, unwrappedCb]); - }; - Object.defineProperty(proto, 'on' + eventNameToWrap, { - get: function get() { - return this['_on' + eventNameToWrap]; - }, - set: function set(cb) { - if (this['_on' + eventNameToWrap]) { - this.removeEventListener(eventNameToWrap, this['_on' + eventNameToWrap]); - delete this['_on' + eventNameToWrap]; - } - if (cb) { - this.addEventListener(eventNameToWrap, this['_on' + eventNameToWrap] = cb); - } - }, - enumerable: true, - configurable: true - }); -} -function disableLog(bool) { - if (typeof bool !== 'boolean') { - return new Error('Argument type: ' + _typeof(bool) + '. Please use a boolean.'); - } - logDisabled_ = bool; - return bool ? 'adapter.js logging disabled' : 'adapter.js logging enabled'; -} - -/** - * Disable or enable deprecation warnings - * @param {!boolean} bool set to true to disable warnings. - */ -function disableWarnings(bool) { - if (typeof bool !== 'boolean') { - return new Error('Argument type: ' + _typeof(bool) + '. Please use a boolean.'); - } - deprecationWarnings_ = !bool; - return 'adapter.js deprecation warnings ' + (bool ? 'disabled' : 'enabled'); -} -function log() { - if ((typeof window === "undefined" ? "undefined" : _typeof(window)) === 'object') { - if (logDisabled_) { - return; - } - if (typeof console !== 'undefined' && typeof console.log === 'function') { - console.log.apply(console, arguments); - } - } -} - -/** - * Shows a deprecation warning suggesting the modern and spec-compatible API. - */ -function deprecated(oldMethod, newMethod) { - if (!deprecationWarnings_) { - return; - } - console.warn(oldMethod + ' is deprecated, please use ' + newMethod + ' instead.'); -} - -/** - * Browser detector. - * - * @return {object} result containing browser and version - * properties. - */ -function detectBrowser(window) { - // Returned result object. - var result = { - browser: null, - version: null - }; - - // Fail early if it's not a browser - if (typeof window === 'undefined' || !window.navigator) { - result.browser = 'Not a browser.'; - return result; - } - var navigator = window.navigator; - if (navigator.mozGetUserMedia) { - // Firefox. - result.browser = 'firefox'; - result.version = extractVersion(navigator.userAgent, /Firefox\/(\d+)\./, 1); - } else if (navigator.webkitGetUserMedia || window.isSecureContext === false && window.webkitRTCPeerConnection) { - // Chrome, Chromium, Webview, Opera. - // Version matches Chrome/WebRTC version. - // Chrome 74 removed webkitGetUserMedia on http as well so we need the - // more complicated fallback to webkitRTCPeerConnection. - result.browser = 'chrome'; - result.version = extractVersion(navigator.userAgent, /Chrom(e|ium)\/(\d+)\./, 2); - } else if (window.RTCPeerConnection && navigator.userAgent.match(/AppleWebKit\/(\d+)\./)) { - // Safari. - result.browser = 'safari'; - result.version = extractVersion(navigator.userAgent, /AppleWebKit\/(\d+)\./, 1); - result.supportsUnifiedPlan = window.RTCRtpTransceiver && 'currentDirection' in window.RTCRtpTransceiver.prototype; - } else { - // Default fallthrough: not supported. - result.browser = 'Not a supported browser.'; - return result; - } - return result; -} - -/** - * Checks if something is an object. - * - * @param {*} val The something you want to check. - * @return true if val is an object, false otherwise. - */ -function isObject(val) { - return Object.prototype.toString.call(val) === '[object Object]'; -} - -/** - * Remove all empty objects and undefined values - * from a nested object -- an enhanced and vanilla version - * of Lodash's `compact`. - */ -function compactObject(data) { - if (!isObject(data)) { - return data; - } - return Object.keys(data).reduce(function (accumulator, key) { - var isObj = isObject(data[key]); - var value = isObj ? compactObject(data[key]) : data[key]; - var isEmptyObject = isObj && !Object.keys(value).length; - if (value === undefined || isEmptyObject) { - return accumulator; - } - return Object.assign(accumulator, _defineProperty({}, key, value)); - }, {}); -} - -/* iterates the stats graph recursively. */ -function walkStats(stats, base, resultSet) { - if (!base || resultSet.has(base.id)) { - return; - } - resultSet.set(base.id, base); - Object.keys(base).forEach(function (name) { - if (name.endsWith('Id')) { - walkStats(stats, stats.get(base[name]), resultSet); - } else if (name.endsWith('Ids')) { - base[name].forEach(function (id) { - walkStats(stats, stats.get(id), resultSet); - }); - } - }); -} - -/* filter getStats for a sender/receiver track. */ -function filterStats(result, track, outbound) { - var streamStatsType = outbound ? 'outbound-rtp' : 'inbound-rtp'; - var filteredResult = new Map(); - if (track === null) { - return filteredResult; - } - var trackStats = []; - result.forEach(function (value) { - if (value.type === 'track' && value.trackIdentifier === track.id) { - trackStats.push(value); - } - }); - trackStats.forEach(function (trackStat) { - result.forEach(function (stats) { - if (stats.type === streamStatsType && stats.trackId === trackStat.id) { - walkStats(result, stats, filteredResult); - } - }); - }); - return filteredResult; -} - -},{}],12:[function(require,module,exports){ -/* eslint-env node */ -'use strict'; - -// SDP helpers. - -var _typeof = typeof Symbol === "function" && typeof Symbol.iterator === "symbol" ? function (obj) { return typeof obj; } : function (obj) { return obj && typeof Symbol === "function" && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }; - -var SDPUtils = {}; - -// Generate an alphanumeric identifier for cname or mids. -// TODO: use UUIDs instead? https://gist.github.com/jed/982883 -SDPUtils.generateIdentifier = function () { - return Math.random().toString(36).substring(2, 12); -}; - -// The RTCP CNAME used by all peerconnections from the same JS. -SDPUtils.localCName = SDPUtils.generateIdentifier(); - -// Splits SDP into lines, dealing with both CRLF and LF. -SDPUtils.splitLines = function (blob) { - return blob.trim().split('\n').map(function (line) { - return line.trim(); - }); -}; -// Splits SDP into sessionpart and mediasections. Ensures CRLF. -SDPUtils.splitSections = function (blob) { - var parts = blob.split('\nm='); - return parts.map(function (part, index) { - return (index > 0 ? 'm=' + part : part).trim() + '\r\n'; - }); -}; - -// Returns the session description. -SDPUtils.getDescription = function (blob) { - var sections = SDPUtils.splitSections(blob); - return sections && sections[0]; -}; - -// Returns the individual media sections. -SDPUtils.getMediaSections = function (blob) { - var sections = SDPUtils.splitSections(blob); - sections.shift(); - return sections; -}; - -// Returns lines that start with a certain prefix. -SDPUtils.matchPrefix = function (blob, prefix) { - return SDPUtils.splitLines(blob).filter(function (line) { - return line.indexOf(prefix) === 0; - }); -}; - -// Parses an ICE candidate line. Sample input: -// candidate:702786350 2 udp 41819902 8.8.8.8 60769 typ relay raddr 8.8.8.8 -// rport 55996" -// Input can be prefixed with a=. -SDPUtils.parseCandidate = function (line) { - var parts = void 0; - // Parse both variants. - if (line.indexOf('a=candidate:') === 0) { - parts = line.substring(12).split(' '); - } else { - parts = line.substring(10).split(' '); - } - - var candidate = { - foundation: parts[0], - component: { 1: 'rtp', 2: 'rtcp' }[parts[1]] || parts[1], - protocol: parts[2].toLowerCase(), - priority: parseInt(parts[3], 10), - ip: parts[4], - address: parts[4], // address is an alias for ip. - port: parseInt(parts[5], 10), - // skip parts[6] == 'typ' - type: parts[7] - }; - - for (var i = 8; i < parts.length; i += 2) { - switch (parts[i]) { - case 'raddr': - candidate.relatedAddress = parts[i + 1]; - break; - case 'rport': - candidate.relatedPort = parseInt(parts[i + 1], 10); - break; - case 'tcptype': - candidate.tcpType = parts[i + 1]; - break; - case 'ufrag': - candidate.ufrag = parts[i + 1]; // for backward compatibility. - candidate.usernameFragment = parts[i + 1]; - break; - default: - // extension handling, in particular ufrag. Don't overwrite. - if (candidate[parts[i]] === undefined) { - candidate[parts[i]] = parts[i + 1]; - } - break; - } - } - return candidate; -}; - -// Translates a candidate object into SDP candidate attribute. -// This does not include the a= prefix! -SDPUtils.writeCandidate = function (candidate) { - var sdp = []; - sdp.push(candidate.foundation); - - var component = candidate.component; - if (component === 'rtp') { - sdp.push(1); - } else if (component === 'rtcp') { - sdp.push(2); - } else { - sdp.push(component); - } - sdp.push(candidate.protocol.toUpperCase()); - sdp.push(candidate.priority); - sdp.push(candidate.address || candidate.ip); - sdp.push(candidate.port); - - var type = candidate.type; - sdp.push('typ'); - sdp.push(type); - if (type !== 'host' && candidate.relatedAddress && candidate.relatedPort) { - sdp.push('raddr'); - sdp.push(candidate.relatedAddress); - sdp.push('rport'); - sdp.push(candidate.relatedPort); - } - if (candidate.tcpType && candidate.protocol.toLowerCase() === 'tcp') { - sdp.push('tcptype'); - sdp.push(candidate.tcpType); - } - if (candidate.usernameFragment || candidate.ufrag) { - sdp.push('ufrag'); - sdp.push(candidate.usernameFragment || candidate.ufrag); - } - return 'candidate:' + sdp.join(' '); -}; - -// Parses an ice-options line, returns an array of option tags. -// Sample input: -// a=ice-options:foo bar -SDPUtils.parseIceOptions = function (line) { - return line.substring(14).split(' '); -}; - -// Parses a rtpmap line, returns RTCRtpCoddecParameters. Sample input: -// a=rtpmap:111 opus/48000/2 -SDPUtils.parseRtpMap = function (line) { - var parts = line.substring(9).split(' '); - var parsed = { - payloadType: parseInt(parts.shift(), 10) // was: id - }; - - parts = parts[0].split('/'); - - parsed.name = parts[0]; - parsed.clockRate = parseInt(parts[1], 10); // was: clockrate - parsed.channels = parts.length === 3 ? parseInt(parts[2], 10) : 1; - // legacy alias, got renamed back to channels in ORTC. - parsed.numChannels = parsed.channels; - return parsed; -}; - -// Generates a rtpmap line from RTCRtpCodecCapability or -// RTCRtpCodecParameters. -SDPUtils.writeRtpMap = function (codec) { - var pt = codec.payloadType; - if (codec.preferredPayloadType !== undefined) { - pt = codec.preferredPayloadType; - } - var channels = codec.channels || codec.numChannels || 1; - return 'a=rtpmap:' + pt + ' ' + codec.name + '/' + codec.clockRate + (channels !== 1 ? '/' + channels : '') + '\r\n'; -}; - -// Parses a extmap line (headerextension from RFC 5285). Sample input: -// a=extmap:2 urn:ietf:params:rtp-hdrext:toffset -// a=extmap:2/sendonly urn:ietf:params:rtp-hdrext:toffset -SDPUtils.parseExtmap = function (line) { - var parts = line.substring(9).split(' '); - return { - id: parseInt(parts[0], 10), - direction: parts[0].indexOf('/') > 0 ? parts[0].split('/')[1] : 'sendrecv', - uri: parts[1], - attributes: parts.slice(2).join(' ') - }; -}; - -// Generates an extmap line from RTCRtpHeaderExtensionParameters or -// RTCRtpHeaderExtension. -SDPUtils.writeExtmap = function (headerExtension) { - return 'a=extmap:' + (headerExtension.id || headerExtension.preferredId) + (headerExtension.direction && headerExtension.direction !== 'sendrecv' ? '/' + headerExtension.direction : '') + ' ' + headerExtension.uri + (headerExtension.attributes ? ' ' + headerExtension.attributes : '') + '\r\n'; -}; - -// Parses a fmtp line, returns dictionary. Sample input: -// a=fmtp:96 vbr=on;cng=on -// Also deals with vbr=on; cng=on -SDPUtils.parseFmtp = function (line) { - var parsed = {}; - var kv = void 0; - var parts = line.substring(line.indexOf(' ') + 1).split(';'); - for (var j = 0; j < parts.length; j++) { - kv = parts[j].trim().split('='); - parsed[kv[0].trim()] = kv[1]; - } - return parsed; -}; - -// Generates a fmtp line from RTCRtpCodecCapability or RTCRtpCodecParameters. -SDPUtils.writeFmtp = function (codec) { - var line = ''; - var pt = codec.payloadType; - if (codec.preferredPayloadType !== undefined) { - pt = codec.preferredPayloadType; - } - if (codec.parameters && Object.keys(codec.parameters).length) { - var params = []; - Object.keys(codec.parameters).forEach(function (param) { - if (codec.parameters[param] !== undefined) { - params.push(param + '=' + codec.parameters[param]); - } else { - params.push(param); - } - }); - line += 'a=fmtp:' + pt + ' ' + params.join(';') + '\r\n'; - } - return line; -}; - -// Parses a rtcp-fb line, returns RTCPRtcpFeedback object. Sample input: -// a=rtcp-fb:98 nack rpsi -SDPUtils.parseRtcpFb = function (line) { - var parts = line.substring(line.indexOf(' ') + 1).split(' '); - return { - type: parts.shift(), - parameter: parts.join(' ') - }; -}; - -// Generate a=rtcp-fb lines from RTCRtpCodecCapability or RTCRtpCodecParameters. -SDPUtils.writeRtcpFb = function (codec) { - var lines = ''; - var pt = codec.payloadType; - if (codec.preferredPayloadType !== undefined) { - pt = codec.preferredPayloadType; - } - if (codec.rtcpFeedback && codec.rtcpFeedback.length) { - // FIXME: special handling for trr-int? - codec.rtcpFeedback.forEach(function (fb) { - lines += 'a=rtcp-fb:' + pt + ' ' + fb.type + (fb.parameter && fb.parameter.length ? ' ' + fb.parameter : '') + '\r\n'; - }); - } - return lines; -}; - -// Parses a RFC 5576 ssrc media attribute. Sample input: -// a=ssrc:3735928559 cname:something -SDPUtils.parseSsrcMedia = function (line) { - var sp = line.indexOf(' '); - var parts = { - ssrc: parseInt(line.substring(7, sp), 10) - }; - var colon = line.indexOf(':', sp); - if (colon > -1) { - parts.attribute = line.substring(sp + 1, colon); - parts.value = line.substring(colon + 1); - } else { - parts.attribute = line.substring(sp + 1); - } - return parts; -}; - -// Parse a ssrc-group line (see RFC 5576). Sample input: -// a=ssrc-group:semantics 12 34 -SDPUtils.parseSsrcGroup = function (line) { - var parts = line.substring(13).split(' '); - return { - semantics: parts.shift(), - ssrcs: parts.map(function (ssrc) { - return parseInt(ssrc, 10); - }) - }; -}; - -// Extracts the MID (RFC 5888) from a media section. -// Returns the MID or undefined if no mid line was found. -SDPUtils.getMid = function (mediaSection) { - var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:')[0]; - if (mid) { - return mid.substring(6); - } -}; - -// Parses a fingerprint line for DTLS-SRTP. -SDPUtils.parseFingerprint = function (line) { - var parts = line.substring(14).split(' '); - return { - algorithm: parts[0].toLowerCase(), // algorithm is case-sensitive in Edge. - value: parts[1].toUpperCase() // the definition is upper-case in RFC 4572. - }; -}; - -// Extracts DTLS parameters from SDP media section or sessionpart. -// FIXME: for consistency with other functions this should only -// get the fingerprint line as input. See also getIceParameters. -SDPUtils.getDtlsParameters = function (mediaSection, sessionpart) { - var lines = SDPUtils.matchPrefix(mediaSection + sessionpart, 'a=fingerprint:'); - // Note: a=setup line is ignored since we use the 'auto' role in Edge. - return { - role: 'auto', - fingerprints: lines.map(SDPUtils.parseFingerprint) - }; -}; - -// Serializes DTLS parameters to SDP. -SDPUtils.writeDtlsParameters = function (params, setupType) { - var sdp = 'a=setup:' + setupType + '\r\n'; - params.fingerprints.forEach(function (fp) { - sdp += 'a=fingerprint:' + fp.algorithm + ' ' + fp.value + '\r\n'; - }); - return sdp; -}; - -// Parses a=crypto lines into -// https://rawgit.com/aboba/edgertc/master/msortc-rs4.html#dictionary-rtcsrtpsdesparameters-members -SDPUtils.parseCryptoLine = function (line) { - var parts = line.substring(9).split(' '); - return { - tag: parseInt(parts[0], 10), - cryptoSuite: parts[1], - keyParams: parts[2], - sessionParams: parts.slice(3) - }; -}; - -SDPUtils.writeCryptoLine = function (parameters) { - return 'a=crypto:' + parameters.tag + ' ' + parameters.cryptoSuite + ' ' + (_typeof(parameters.keyParams) === 'object' ? SDPUtils.writeCryptoKeyParams(parameters.keyParams) : parameters.keyParams) + (parameters.sessionParams ? ' ' + parameters.sessionParams.join(' ') : '') + '\r\n'; -}; - -// Parses the crypto key parameters into -// https://rawgit.com/aboba/edgertc/master/msortc-rs4.html#rtcsrtpkeyparam* -SDPUtils.parseCryptoKeyParams = function (keyParams) { - if (keyParams.indexOf('inline:') !== 0) { - return null; - } - var parts = keyParams.substring(7).split('|'); - return { - keyMethod: 'inline', - keySalt: parts[0], - lifeTime: parts[1], - mkiValue: parts[2] ? parts[2].split(':')[0] : undefined, - mkiLength: parts[2] ? parts[2].split(':')[1] : undefined - }; -}; - -SDPUtils.writeCryptoKeyParams = function (keyParams) { - return keyParams.keyMethod + ':' + keyParams.keySalt + (keyParams.lifeTime ? '|' + keyParams.lifeTime : '') + (keyParams.mkiValue && keyParams.mkiLength ? '|' + keyParams.mkiValue + ':' + keyParams.mkiLength : ''); -}; - -// Extracts all SDES parameters. -SDPUtils.getCryptoParameters = function (mediaSection, sessionpart) { - var lines = SDPUtils.matchPrefix(mediaSection + sessionpart, 'a=crypto:'); - return lines.map(SDPUtils.parseCryptoLine); -}; - -// Parses ICE information from SDP media section or sessionpart. -// FIXME: for consistency with other functions this should only -// get the ice-ufrag and ice-pwd lines as input. -SDPUtils.getIceParameters = function (mediaSection, sessionpart) { - var ufrag = SDPUtils.matchPrefix(mediaSection + sessionpart, 'a=ice-ufrag:')[0]; - var pwd = SDPUtils.matchPrefix(mediaSection + sessionpart, 'a=ice-pwd:')[0]; - if (!(ufrag && pwd)) { - return null; - } - return { - usernameFragment: ufrag.substring(12), - password: pwd.substring(10) - }; -}; - -// Serializes ICE parameters to SDP. -SDPUtils.writeIceParameters = function (params) { - var sdp = 'a=ice-ufrag:' + params.usernameFragment + '\r\n' + 'a=ice-pwd:' + params.password + '\r\n'; - if (params.iceLite) { - sdp += 'a=ice-lite\r\n'; - } - return sdp; -}; - -// Parses the SDP media section and returns RTCRtpParameters. -SDPUtils.parseRtpParameters = function (mediaSection) { - var description = { - codecs: [], - headerExtensions: [], - fecMechanisms: [], - rtcp: [] - }; - var lines = SDPUtils.splitLines(mediaSection); - var mline = lines[0].split(' '); - description.profile = mline[2]; - for (var i = 3; i < mline.length; i++) { - // find all codecs from mline[3..] - var pt = mline[i]; - var rtpmapline = SDPUtils.matchPrefix(mediaSection, 'a=rtpmap:' + pt + ' ')[0]; - if (rtpmapline) { - var codec = SDPUtils.parseRtpMap(rtpmapline); - var fmtps = SDPUtils.matchPrefix(mediaSection, 'a=fmtp:' + pt + ' '); - // Only the first a=fmtp: is considered. - codec.parameters = fmtps.length ? SDPUtils.parseFmtp(fmtps[0]) : {}; - codec.rtcpFeedback = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-fb:' + pt + ' ').map(SDPUtils.parseRtcpFb); - description.codecs.push(codec); - // parse FEC mechanisms from rtpmap lines. - switch (codec.name.toUpperCase()) { - case 'RED': - case 'ULPFEC': - description.fecMechanisms.push(codec.name.toUpperCase()); - break; - default: - // only RED and ULPFEC are recognized as FEC mechanisms. - break; - } - } - } - SDPUtils.matchPrefix(mediaSection, 'a=extmap:').forEach(function (line) { - description.headerExtensions.push(SDPUtils.parseExtmap(line)); - }); - var wildcardRtcpFb = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-fb:* ').map(SDPUtils.parseRtcpFb); - description.codecs.forEach(function (codec) { - wildcardRtcpFb.forEach(function (fb) { - var duplicate = codec.rtcpFeedback.find(function (existingFeedback) { - return existingFeedback.type === fb.type && existingFeedback.parameter === fb.parameter; - }); - if (!duplicate) { - codec.rtcpFeedback.push(fb); - } - }); - }); - // FIXME: parse rtcp. - return description; -}; - -// Generates parts of the SDP media section describing the capabilities / -// parameters. -SDPUtils.writeRtpDescription = function (kind, caps) { - var sdp = ''; - - // Build the mline. - sdp += 'm=' + kind + ' '; - sdp += caps.codecs.length > 0 ? '9' : '0'; // reject if no codecs. - sdp += ' ' + (caps.profile || 'UDP/TLS/RTP/SAVPF') + ' '; - sdp += caps.codecs.map(function (codec) { - if (codec.preferredPayloadType !== undefined) { - return codec.preferredPayloadType; - } - return codec.payloadType; - }).join(' ') + '\r\n'; - - sdp += 'c=IN IP4 0.0.0.0\r\n'; - sdp += 'a=rtcp:9 IN IP4 0.0.0.0\r\n'; - - // Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb. - caps.codecs.forEach(function (codec) { - sdp += SDPUtils.writeRtpMap(codec); - sdp += SDPUtils.writeFmtp(codec); - sdp += SDPUtils.writeRtcpFb(codec); - }); - var maxptime = 0; - caps.codecs.forEach(function (codec) { - if (codec.maxptime > maxptime) { - maxptime = codec.maxptime; - } - }); - if (maxptime > 0) { - sdp += 'a=maxptime:' + maxptime + '\r\n'; - } - - if (caps.headerExtensions) { - caps.headerExtensions.forEach(function (extension) { - sdp += SDPUtils.writeExtmap(extension); - }); - } - // FIXME: write fecMechanisms. - return sdp; -}; - -// Parses the SDP media section and returns an array of -// RTCRtpEncodingParameters. -SDPUtils.parseRtpEncodingParameters = function (mediaSection) { - var encodingParameters = []; - var description = SDPUtils.parseRtpParameters(mediaSection); - var hasRed = description.fecMechanisms.indexOf('RED') !== -1; - var hasUlpfec = description.fecMechanisms.indexOf('ULPFEC') !== -1; - - // filter a=ssrc:... cname:, ignore PlanB-msid - var ssrcs = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:').map(function (line) { - return SDPUtils.parseSsrcMedia(line); - }).filter(function (parts) { - return parts.attribute === 'cname'; - }); - var primarySsrc = ssrcs.length > 0 && ssrcs[0].ssrc; - var secondarySsrc = void 0; - - var flows = SDPUtils.matchPrefix(mediaSection, 'a=ssrc-group:FID').map(function (line) { - var parts = line.substring(17).split(' '); - return parts.map(function (part) { - return parseInt(part, 10); - }); - }); - if (flows.length > 0 && flows[0].length > 1 && flows[0][0] === primarySsrc) { - secondarySsrc = flows[0][1]; - } - - description.codecs.forEach(function (codec) { - if (codec.name.toUpperCase() === 'RTX' && codec.parameters.apt) { - var encParam = { - ssrc: primarySsrc, - codecPayloadType: parseInt(codec.parameters.apt, 10) - }; - if (primarySsrc && secondarySsrc) { - encParam.rtx = { ssrc: secondarySsrc }; - } - encodingParameters.push(encParam); - if (hasRed) { - encParam = JSON.parse(JSON.stringify(encParam)); - encParam.fec = { - ssrc: primarySsrc, - mechanism: hasUlpfec ? 'red+ulpfec' : 'red' - }; - encodingParameters.push(encParam); - } - } - }); - if (encodingParameters.length === 0 && primarySsrc) { - encodingParameters.push({ - ssrc: primarySsrc - }); - } - - // we support both b=AS and b=TIAS but interpret AS as TIAS. - var bandwidth = SDPUtils.matchPrefix(mediaSection, 'b='); - if (bandwidth.length) { - if (bandwidth[0].indexOf('b=TIAS:') === 0) { - bandwidth = parseInt(bandwidth[0].substring(7), 10); - } else if (bandwidth[0].indexOf('b=AS:') === 0) { - // use formula from JSEP to convert b=AS to TIAS value. - bandwidth = parseInt(bandwidth[0].substring(5), 10) * 1000 * 0.95 - 50 * 40 * 8; - } else { - bandwidth = undefined; - } - encodingParameters.forEach(function (params) { - params.maxBitrate = bandwidth; - }); - } - return encodingParameters; -}; - -// parses http://draft.ortc.org/#rtcrtcpparameters* -SDPUtils.parseRtcpParameters = function (mediaSection) { - var rtcpParameters = {}; - - // Gets the first SSRC. Note that with RTX there might be multiple - // SSRCs. - var remoteSsrc = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:').map(function (line) { - return SDPUtils.parseSsrcMedia(line); - }).filter(function (obj) { - return obj.attribute === 'cname'; - })[0]; - if (remoteSsrc) { - rtcpParameters.cname = remoteSsrc.value; - rtcpParameters.ssrc = remoteSsrc.ssrc; - } - - // Edge uses the compound attribute instead of reducedSize - // compound is !reducedSize - var rsize = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-rsize'); - rtcpParameters.reducedSize = rsize.length > 0; - rtcpParameters.compound = rsize.length === 0; - - // parses the rtcp-mux attrÑ–bute. - // Note that Edge does not support unmuxed RTCP. - var mux = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-mux'); - rtcpParameters.mux = mux.length > 0; - - return rtcpParameters; -}; - -SDPUtils.writeRtcpParameters = function (rtcpParameters) { - var sdp = ''; - if (rtcpParameters.reducedSize) { - sdp += 'a=rtcp-rsize\r\n'; - } - if (rtcpParameters.mux) { - sdp += 'a=rtcp-mux\r\n'; - } - if (rtcpParameters.ssrc !== undefined && rtcpParameters.cname) { - sdp += 'a=ssrc:' + rtcpParameters.ssrc + ' cname:' + rtcpParameters.cname + '\r\n'; - } - return sdp; -}; - -// parses either a=msid: or a=ssrc:... msid lines and returns -// the id of the MediaStream and MediaStreamTrack. -SDPUtils.parseMsid = function (mediaSection) { - var parts = void 0; - var spec = SDPUtils.matchPrefix(mediaSection, 'a=msid:'); - if (spec.length === 1) { - parts = spec[0].substring(7).split(' '); - return { stream: parts[0], track: parts[1] }; - } - var planB = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:').map(function (line) { - return SDPUtils.parseSsrcMedia(line); - }).filter(function (msidParts) { - return msidParts.attribute === 'msid'; - }); - if (planB.length > 0) { - parts = planB[0].value.split(' '); - return { stream: parts[0], track: parts[1] }; - } -}; - -// SCTP -// parses draft-ietf-mmusic-sctp-sdp-26 first and falls back -// to draft-ietf-mmusic-sctp-sdp-05 -SDPUtils.parseSctpDescription = function (mediaSection) { - var mline = SDPUtils.parseMLine(mediaSection); - var maxSizeLine = SDPUtils.matchPrefix(mediaSection, 'a=max-message-size:'); - var maxMessageSize = void 0; - if (maxSizeLine.length > 0) { - maxMessageSize = parseInt(maxSizeLine[0].substring(19), 10); - } - if (isNaN(maxMessageSize)) { - maxMessageSize = 65536; - } - var sctpPort = SDPUtils.matchPrefix(mediaSection, 'a=sctp-port:'); - if (sctpPort.length > 0) { - return { - port: parseInt(sctpPort[0].substring(12), 10), - protocol: mline.fmt, - maxMessageSize: maxMessageSize - }; - } - var sctpMapLines = SDPUtils.matchPrefix(mediaSection, 'a=sctpmap:'); - if (sctpMapLines.length > 0) { - var parts = sctpMapLines[0].substring(10).split(' '); - return { - port: parseInt(parts[0], 10), - protocol: parts[1], - maxMessageSize: maxMessageSize - }; - } -}; - -// SCTP -// outputs the draft-ietf-mmusic-sctp-sdp-26 version that all browsers -// support by now receiving in this format, unless we originally parsed -// as the draft-ietf-mmusic-sctp-sdp-05 format (indicated by the m-line -// protocol of DTLS/SCTP -- without UDP/ or TCP/) -SDPUtils.writeSctpDescription = function (media, sctp) { - var output = []; - if (media.protocol !== 'DTLS/SCTP') { - output = ['m=' + media.kind + ' 9 ' + media.protocol + ' ' + sctp.protocol + '\r\n', 'c=IN IP4 0.0.0.0\r\n', 'a=sctp-port:' + sctp.port + '\r\n']; - } else { - output = ['m=' + media.kind + ' 9 ' + media.protocol + ' ' + sctp.port + '\r\n', 'c=IN IP4 0.0.0.0\r\n', 'a=sctpmap:' + sctp.port + ' ' + sctp.protocol + ' 65535\r\n']; - } - if (sctp.maxMessageSize !== undefined) { - output.push('a=max-message-size:' + sctp.maxMessageSize + '\r\n'); - } - return output.join(''); -}; - -// Generate a session ID for SDP. -// https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-20#section-5.2.1 -// recommends using a cryptographically random +ve 64-bit value -// but right now this should be acceptable and within the right range -SDPUtils.generateSessionId = function () { - return Math.random().toString().substr(2, 22); -}; - -// Write boiler plate for start of SDP -// sessId argument is optional - if not supplied it will -// be generated randomly -// sessVersion is optional and defaults to 2 -// sessUser is optional and defaults to 'thisisadapterortc' -SDPUtils.writeSessionBoilerplate = function (sessId, sessVer, sessUser) { - var sessionId = void 0; - var version = sessVer !== undefined ? sessVer : 2; - if (sessId) { - sessionId = sessId; - } else { - sessionId = SDPUtils.generateSessionId(); - } - var user = sessUser || 'thisisadapterortc'; - // FIXME: sess-id should be an NTP timestamp. - return 'v=0\r\n' + 'o=' + user + ' ' + sessionId + ' ' + version + ' IN IP4 127.0.0.1\r\n' + 's=-\r\n' + 't=0 0\r\n'; -}; - -// Gets the direction from the mediaSection or the sessionpart. -SDPUtils.getDirection = function (mediaSection, sessionpart) { - // Look for sendrecv, sendonly, recvonly, inactive, default to sendrecv. - var lines = SDPUtils.splitLines(mediaSection); - for (var i = 0; i < lines.length; i++) { - switch (lines[i]) { - case 'a=sendrecv': - case 'a=sendonly': - case 'a=recvonly': - case 'a=inactive': - return lines[i].substring(2); - default: - // FIXME: What should happen here? - } - } - if (sessionpart) { - return SDPUtils.getDirection(sessionpart); - } - return 'sendrecv'; -}; - -SDPUtils.getKind = function (mediaSection) { - var lines = SDPUtils.splitLines(mediaSection); - var mline = lines[0].split(' '); - return mline[0].substring(2); -}; - -SDPUtils.isRejected = function (mediaSection) { - return mediaSection.split(' ', 2)[1] === '0'; -}; - -SDPUtils.parseMLine = function (mediaSection) { - var lines = SDPUtils.splitLines(mediaSection); - var parts = lines[0].substring(2).split(' '); - return { - kind: parts[0], - port: parseInt(parts[1], 10), - protocol: parts[2], - fmt: parts.slice(3).join(' ') - }; -}; - -SDPUtils.parseOLine = function (mediaSection) { - var line = SDPUtils.matchPrefix(mediaSection, 'o=')[0]; - var parts = line.substring(2).split(' '); - return { - username: parts[0], - sessionId: parts[1], - sessionVersion: parseInt(parts[2], 10), - netType: parts[3], - addressType: parts[4], - address: parts[5] - }; -}; - -// a very naive interpretation of a valid SDP. -SDPUtils.isValidSDP = function (blob) { - if (typeof blob !== 'string' || blob.length === 0) { - return false; - } - var lines = SDPUtils.splitLines(blob); - for (var i = 0; i < lines.length; i++) { - if (lines[i].length < 2 || lines[i].charAt(1) !== '=') { - return false; - } - // TODO: check the modifier a bit more. - } - return true; -}; - -// Expose public methods. -if ((typeof module === 'undefined' ? 'undefined' : _typeof(module)) === 'object') { - module.exports = SDPUtils; -} -},{}]},{},[1])(1) -}); diff --git a/web/janus.js b/web/janus.js deleted file mode 100644 index 4544ccfc..00000000 --- a/web/janus.js +++ /dev/null @@ -1,3651 +0,0 @@ -import "./adapter.js" -"use strict"; - -/* - The MIT License (MIT) - - Copyright (c) 2016 Meetecho - - Permission is hereby granted, free of charge, to any person obtaining - a copy of this software and associated documentation files (the "Software"), - to deal in the Software without restriction, including without limitation - the rights to use, copy, modify, merge, publish, distribute, sublicense, - and/or sell copies of the Software, and to permit persons to whom the - Software is furnished to do so, subject to the following conditions: - - The above copyright notice and this permission notice shall be included - in all copies or substantial portions of the Software. - - THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS - OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL - THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR - OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, - ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR - OTHER DEALINGS IN THE SOFTWARE. - */ - -// List of sessions -Janus.sessions = {}; - -Janus.isExtensionEnabled = function() { - if(navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia) { - // No need for the extension, getDisplayMedia is supported - return true; - } - if(window.navigator.userAgent.match('Chrome')) { - var chromever = parseInt(window.navigator.userAgent.match(/Chrome\/(.*) /)[1], 10); - var maxver = 33; - if(window.navigator.userAgent.match('Linux')) - maxver = 35; // "known" crash in chrome 34 and 35 on linux - if(chromever >= 26 && chromever <= maxver) { - // Older versions of Chrome don't support this extension-based approach, so lie - return true; - } - return Janus.extension.isInstalled(); - } else { - // Firefox and others, no need for the extension (but this doesn't mean it will work) - return true; - } -}; - -var defaultExtension = { - // Screensharing Chrome Extension ID - extensionId: 'hapfgfdkleiggjjpfpenajgdnfckjpaj', - isInstalled: function() { return document.querySelector('#janus-extension-installed') !== null; }, - getScreen: function (callback) { - var pending = window.setTimeout(function () { - var error = new Error('NavigatorUserMediaError'); - error.name = 'The required Chrome extension is not installed: click here to install it. (NOTE: this will need you to refresh the page)'; - return callback(error); - }, 1000); - this.cache[pending] = callback; - window.postMessage({ type: 'janusGetScreen', id: pending }, '*'); - }, - init: function () { - var cache = {}; - this.cache = cache; - // Wait for events from the Chrome Extension - window.addEventListener('message', function (event) { - if(event.origin != window.location.origin) - return; - if(event.data.type == 'janusGotScreen' && cache[event.data.id]) { - var callback = cache[event.data.id]; - delete cache[event.data.id]; - - if (event.data.sourceId === '') { - // user canceled - var error = new Error('NavigatorUserMediaError'); - error.name = 'You cancelled the request for permission, giving up...'; - callback(error); - } else { - callback(null, event.data.sourceId); - } - } else if (event.data.type == 'janusGetScreenPending') { - console.log('clearing ', event.data.id); - window.clearTimeout(event.data.id); - } - }); - } -}; - -Janus.useDefaultDependencies = function (deps) { - var f = (deps && deps.fetch) || fetch; - var p = (deps && deps.Promise) || Promise; - var socketCls = (deps && deps.WebSocket) || WebSocket; - - return { - newWebSocket: function(server, proto) { return new socketCls(server, proto); }, - extension: (deps && deps.extension) || defaultExtension, - isArray: function(arr) { return Array.isArray(arr); }, - webRTCAdapter: (deps && deps.adapter) || adapter, - httpAPICall: function(url, options) { - var fetchOptions = { - method: options.verb, - headers: { - 'Accept': 'application/json, text/plain, */*' - }, - cache: 'no-cache' - }; - if(options.verb === "POST") { - fetchOptions.headers['Content-Type'] = 'application/json'; - } - if(options.withCredentials !== undefined) { - fetchOptions.credentials = options.withCredentials === true ? 'include' : (options.withCredentials ? options.withCredentials : 'omit'); - } - if(options.body) { - fetchOptions.body = JSON.stringify(options.body); - } - - var fetching = f(url, fetchOptions).catch(function(error) { - return p.reject({message: 'Probably a network error, is the server down?', error: error}); - }); - - /* - * fetch() does not natively support timeouts. - * Work around this by starting a timeout manually, and racing it agains the fetch() to see which thing resolves first. - */ - - if(options.timeout) { - var timeout = new p(function(resolve, reject) { - var timerId = setTimeout(function() { - clearTimeout(timerId); - return reject({message: 'Request timed out', timeout: options.timeout}); - }, options.timeout); - }); - fetching = p.race([fetching, timeout]); - } - - fetching.then(function(response) { - if(response.ok) { - if(typeof(options.success) === typeof(Janus.noop)) { - return response.json().then(function(parsed) { - try { - options.success(parsed); - } catch(error) { - Janus.error('Unhandled httpAPICall success callback error', error); - } - }, function(error) { - return p.reject({message: 'Failed to parse response body', error: error, response: response}); - }); - } - } - else { - return p.reject({message: 'API call failed', response: response}); - } - }).catch(function(error) { - if(typeof(options.error) === typeof(Janus.noop)) { - options.error(error.message || '<< internal error >>', error); - } - }); - - return fetching; - } - } -}; - -Janus.useOldDependencies = function (deps) { - var jq = (deps && deps.jQuery) || jQuery; - var socketCls = (deps && deps.WebSocket) || WebSocket; - return { - newWebSocket: function(server, proto) { return new socketCls(server, proto); }, - isArray: function(arr) { return jq.isArray(arr); }, - extension: (deps && deps.extension) || defaultExtension, - webRTCAdapter: (deps && deps.adapter) || adapter, - httpAPICall: function(url, options) { - var payload = options.body !== undefined ? { - contentType: 'application/json', - data: JSON.stringify(options.body) - } : {}; - var credentials = options.withCredentials !== undefined ? {xhrFields: {withCredentials: options.withCredentials}} : {}; - - return jq.ajax(jq.extend(payload, credentials, { - url: url, - type: options.verb, - cache: false, - dataType: 'json', - async: options.async, - timeout: options.timeout, - success: function(result) { - if(typeof(options.success) === typeof(Janus.noop)) { - options.success(result); - } - }, - error: function(xhr, status, err) { - if(typeof(options.error) === typeof(Janus.noop)) { - options.error(status, err); - } - } - })); - } - }; -}; - -Janus.noop = function() {}; - -Janus.dataChanDefaultLabel = "JanusDataChannel"; - -// Note: in the future we may want to change this, e.g., as was -// attempted in https://github.com/meetecho/janus-gateway/issues/1670 -Janus.endOfCandidates = null; - -// Stop all tracks from a given stream -Janus.stopAllTracks = function(stream) { - try { - // Try a MediaStreamTrack.stop() for each track - var tracks = stream.getTracks(); - for(var mst of tracks) { - Janus.log(mst); - if(mst) { - mst.stop(); - } - } - } catch(e) { - // Do nothing if this fails - } -} - -// Initialization -Janus.init = function(options) { - options = options || {}; - options.callback = (typeof options.callback == "function") ? options.callback : Janus.noop; - if(Janus.initDone) { - // Already initialized - options.callback(); - } else { - if(typeof console.log == "undefined") { - console.log = function() {}; - } - // Console logging (all debugging disabled by default) - Janus.trace = Janus.noop; - Janus.debug = Janus.noop; - Janus.vdebug = Janus.noop; - Janus.log = Janus.noop; - Janus.warn = Janus.noop; - Janus.error = Janus.noop; - if(options.debug === true || options.debug === "all") { - // Enable all debugging levels - Janus.trace = console.trace.bind(console); - Janus.debug = console.debug.bind(console); - Janus.vdebug = console.debug.bind(console); - Janus.log = console.log.bind(console); - Janus.warn = console.warn.bind(console); - Janus.error = console.error.bind(console); - } else if(Array.isArray(options.debug)) { - for(var d of options.debug) { - switch(d) { - case "trace": - Janus.trace = console.trace.bind(console); - break; - case "debug": - Janus.debug = console.debug.bind(console); - break; - case "vdebug": - Janus.vdebug = console.debug.bind(console); - break; - case "log": - Janus.log = console.log.bind(console); - break; - case "warn": - Janus.warn = console.warn.bind(console); - break; - case "error": - Janus.error = console.error.bind(console); - break; - default: - console.error("Unknown debugging option '" + d + "' (supported: 'trace', 'debug', 'vdebug', 'log', warn', 'error')"); - break; - } - } - } - Janus.log("Initializing library"); - - var usedDependencies = options.dependencies || Janus.useDefaultDependencies(); - Janus.isArray = usedDependencies.isArray; - Janus.webRTCAdapter = usedDependencies.webRTCAdapter; - Janus.httpAPICall = usedDependencies.httpAPICall; - Janus.newWebSocket = usedDependencies.newWebSocket; - Janus.extension = usedDependencies.extension; - Janus.extension.init(); - - // Helper method to enumerate devices - Janus.listDevices = function(callback, config) { - callback = (typeof callback == "function") ? callback : Janus.noop; - if (config == null) config = { audio: true, video: true }; - if(Janus.isGetUserMediaAvailable()) { - navigator.mediaDevices.getUserMedia(config) - .then(function(stream) { - navigator.mediaDevices.enumerateDevices().then(function(devices) { - Janus.debug(devices); - callback(devices); - // Get rid of the now useless stream - Janus.stopAllTracks(stream) - }); - }) - .catch(function(err) { - Janus.error(err); - callback([]); - }); - } else { - Janus.warn("navigator.mediaDevices unavailable"); - callback([]); - } - }; - // Helper methods to attach/reattach a stream to a video element (previously part of adapter.js) - Janus.attachMediaStream = function(element, stream) { - try { - element.srcObject = stream; - } catch (e) { - try { - element.src = URL.createObjectURL(stream); - } catch (e) { - Janus.error("Error attaching stream to element"); - } - } - }; - Janus.reattachMediaStream = function(to, from) { - try { - to.srcObject = from.srcObject; - } catch (e) { - try { - to.src = from.src; - } catch (e) { - Janus.error("Error reattaching stream to element"); - } - } - }; - // Detect tab close: make sure we don't loose existing onbeforeunload handlers - // (note: for iOS we need to subscribe to a different event, 'pagehide', see - // https://gist.github.com/thehunmonkgroup/6bee8941a49b86be31a787fe8f4b8cfe) - var iOS = ['iPad', 'iPhone', 'iPod'].indexOf(navigator.platform) >= 0; - var eventName = iOS ? 'pagehide' : 'beforeunload'; - var oldOBF = window["on" + eventName]; - window.addEventListener(eventName, function() { - Janus.log("Closing window"); - for(var s in Janus.sessions) { - if(Janus.sessions[s] && Janus.sessions[s].destroyOnUnload) { - Janus.log("Destroying session " + s); - Janus.sessions[s].destroy({unload: true, notifyDestroyed: false}); - } - } - if(oldOBF && typeof oldOBF == "function") { - oldOBF(); - } - }); - // If this is a Safari, check if VP8 or VP9 are supported - Janus.safariVp8 = false; - Janus.safariVp9 = false; - if(Janus.webRTCAdapter.browserDetails.browser === 'safari' && - Janus.webRTCAdapter.browserDetails.version >= 605) { - // Let's see if RTCRtpSender.getCapabilities() is there - if(RTCRtpSender && RTCRtpSender.getCapabilities && RTCRtpSender.getCapabilities("video") && - RTCRtpSender.getCapabilities("video").codecs && RTCRtpSender.getCapabilities("video").codecs.length) { - for(var codec of RTCRtpSender.getCapabilities("video").codecs) { - if(codec && codec.mimeType && codec.mimeType.toLowerCase() === "video/vp8") { - Janus.safariVp8 = true; - } else if(codec && codec.mimeType && codec.mimeType.toLowerCase() === "video/vp9") { - Janus.safariVp9 = true; - } - } - if(Janus.safariVp8) { - Janus.log("This version of Safari supports VP8"); - } else { - Janus.warn("This version of Safari does NOT support VP8: if you're using a Technology Preview, " + - "try enabling the 'WebRTC VP8 codec' setting in the 'Experimental Features' Develop menu"); - } - } else { - // We do it in a very ugly way, as there's no alternative... - // We create a PeerConnection to see if VP8 is in an offer - var testpc = new RTCPeerConnection({}); - testpc.createOffer({offerToReceiveVideo: true}).then(function(offer) { - Janus.safariVp8 = offer.sdp.indexOf("VP8") !== -1; - Janus.safariVp9 = offer.sdp.indexOf("VP9") !== -1; - if(Janus.safariVp8) { - Janus.log("This version of Safari supports VP8"); - } else { - Janus.warn("This version of Safari does NOT support VP8: if you're using a Technology Preview, " + - "try enabling the 'WebRTC VP8 codec' setting in the 'Experimental Features' Develop menu"); - } - testpc.close(); - testpc = null; - }); - } - } - // Check if this browser supports Unified Plan and transceivers - // Based on https://codepen.io/anon/pen/ZqLwWV?editors=0010 - Janus.unifiedPlan = false; - if(Janus.webRTCAdapter.browserDetails.browser === 'firefox' && - Janus.webRTCAdapter.browserDetails.version >= 59) { - // Firefox definitely does, starting from version 59 - Janus.unifiedPlan = true; - } else if(Janus.webRTCAdapter.browserDetails.browser === 'chrome' && - Janus.webRTCAdapter.browserDetails.version >= 72) { - // Chrome does, but it's only usable from version 72 on - Janus.unifiedPlan = true; - } else if(!window.RTCRtpTransceiver || !('currentDirection' in RTCRtpTransceiver.prototype)) { - // Safari supports addTransceiver() but not Unified Plan when - // currentDirection is not defined (see codepen above). - Janus.unifiedPlan = false; - } else { - // Check if addTransceiver() throws an exception - var tempPc = new RTCPeerConnection(); - try { - tempPc.addTransceiver('audio'); - Janus.unifiedPlan = true; - } catch (e) {} - tempPc.close(); - } - Janus.initDone = true; - options.callback(); - } -}; - -// Helper method to check whether WebRTC is supported by this browser -Janus.isWebrtcSupported = function() { - return !!window.RTCPeerConnection; -}; -// Helper method to check whether devices can be accessed by this browser (e.g., not possible via plain HTTP) -Janus.isGetUserMediaAvailable = function() { - return navigator.mediaDevices && navigator.mediaDevices.getUserMedia; -}; - -// Helper method to create random identifiers (e.g., transaction) -Janus.randomString = function(len) { - var charSet = 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - var randomString = ''; - for (var i = 0; i < len; i++) { - var randomPoz = Math.floor(Math.random() * charSet.length); - randomString += charSet.substring(randomPoz,randomPoz+1); - } - return randomString; -}; - -export function Janus(gatewayCallbacks) { - gatewayCallbacks = gatewayCallbacks || {}; - gatewayCallbacks.success = (typeof gatewayCallbacks.success == "function") ? gatewayCallbacks.success : Janus.noop; - gatewayCallbacks.error = (typeof gatewayCallbacks.error == "function") ? gatewayCallbacks.error : Janus.noop; - gatewayCallbacks.destroyed = (typeof gatewayCallbacks.destroyed == "function") ? gatewayCallbacks.destroyed : Janus.noop; - if(!Janus.initDone) { - gatewayCallbacks.error("Library not initialized"); - return {}; - } - if(!Janus.isWebrtcSupported()) { - gatewayCallbacks.error("WebRTC not supported by this browser"); - return {}; - } - Janus.log("Library initialized: " + Janus.initDone); - if(!gatewayCallbacks.server) { - gatewayCallbacks.error("Invalid server url"); - return {}; - } - var websockets = false; - var ws = null; - var wsHandlers = {}; - var wsKeepaliveTimeoutId = null; - var servers = null; - var serversIndex = 0; - var server = gatewayCallbacks.server; - if(Janus.isArray(server)) { - Janus.log("Multiple servers provided (" + server.length + "), will use the first that works"); - server = null; - servers = gatewayCallbacks.server; - Janus.debug(servers); - } else { - if(server.indexOf("ws") === 0) { - websockets = true; - Janus.log("Using WebSockets to contact Janus: " + server); - } else { - websockets = false; - Janus.log("Using REST API to contact Janus: " + server); - } - } - var iceServers = gatewayCallbacks.iceServers || [{urls: "stun:stun.l.google.com:19302"}]; - var iceTransportPolicy = gatewayCallbacks.iceTransportPolicy; - var bundlePolicy = gatewayCallbacks.bundlePolicy; - // Whether IPv6 candidates should be gathered - var ipv6Support = (gatewayCallbacks.ipv6 === true); - // Whether we should enable the withCredentials flag for XHR requests - var withCredentials = false; - if(gatewayCallbacks.withCredentials !== undefined && gatewayCallbacks.withCredentials !== null) - withCredentials = gatewayCallbacks.withCredentials === true; - // Optional max events - var maxev = 10; - if(gatewayCallbacks.max_poll_events !== undefined && gatewayCallbacks.max_poll_events !== null) - maxev = gatewayCallbacks.max_poll_events; - if(maxev < 1) - maxev = 1; - // Token to use (only if the token based authentication mechanism is enabled) - var token = null; - if(gatewayCallbacks.token !== undefined && gatewayCallbacks.token !== null) - token = gatewayCallbacks.token; - // API secret to use (only if the shared API secret is enabled) - var apisecret = null; - if(gatewayCallbacks.apisecret !== undefined && gatewayCallbacks.apisecret !== null) - apisecret = gatewayCallbacks.apisecret; - // Whether we should destroy this session when onbeforeunload is called - this.destroyOnUnload = true; - if(gatewayCallbacks.destroyOnUnload !== undefined && gatewayCallbacks.destroyOnUnload !== null) - this.destroyOnUnload = (gatewayCallbacks.destroyOnUnload === true); - // Some timeout-related values - var keepAlivePeriod = 25000; - if(gatewayCallbacks.keepAlivePeriod !== undefined && gatewayCallbacks.keepAlivePeriod !== null) - keepAlivePeriod = gatewayCallbacks.keepAlivePeriod; - if(isNaN(keepAlivePeriod)) - keepAlivePeriod = 25000; - var longPollTimeout = 60000; - if(gatewayCallbacks.longPollTimeout !== undefined && gatewayCallbacks.longPollTimeout !== null) - longPollTimeout = gatewayCallbacks.longPollTimeout; - if(isNaN(longPollTimeout)) - longPollTimeout = 60000; - - // overrides for default maxBitrate values for simulcasting - function getMaxBitrates(simulcastMaxBitrates) { - var maxBitrates = { - high: 900000, - medium: 300000, - low: 100000, - }; - - if (simulcastMaxBitrates !== undefined && simulcastMaxBitrates !== null) { - if (simulcastMaxBitrates.high) - maxBitrates.high = simulcastMaxBitrates.high; - if (simulcastMaxBitrates.medium) - maxBitrates.medium = simulcastMaxBitrates.medium; - if (simulcastMaxBitrates.low) - maxBitrates.low = simulcastMaxBitrates.low; - } - - return maxBitrates; - } - - var connected = false; - var sessionId = null; - var pluginHandles = {}; - var that = this; - var retries = 0; - var transactions = {}; - createSession(gatewayCallbacks); - - // Public methods - this.getServer = function() { return server; }; - this.isConnected = function() { return connected; }; - this.reconnect = function(callbacks) { - callbacks = callbacks || {}; - callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop; - callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop; - callbacks["reconnect"] = true; - createSession(callbacks); - }; - this.getSessionId = function() { return sessionId; }; - this.getInfo = function(callbacks) { getInfo(callbacks); }; - this.destroy = function(callbacks) { destroySession(callbacks); }; - this.attach = function(callbacks) { createHandle(callbacks); }; - - function eventHandler() { - if(sessionId == null) - return; - Janus.debug('Long poll...'); - if(!connected) { - Janus.warn("Is the server down? (connected=false)"); - return; - } - var longpoll = server + "/" + sessionId + "?rid=" + new Date().getTime(); - if(maxev) - longpoll = longpoll + "&maxev=" + maxev; - if(token) - longpoll = longpoll + "&token=" + encodeURIComponent(token); - if(apisecret) - longpoll = longpoll + "&apisecret=" + encodeURIComponent(apisecret); - Janus.httpAPICall(longpoll, { - verb: 'GET', - withCredentials: withCredentials, - success: handleEvent, - timeout: longPollTimeout, - error: function(textStatus, errorThrown) { - Janus.error(textStatus + ":", errorThrown); - retries++; - if(retries > 3) { - // Did we just lose the server? :-( - connected = false; - gatewayCallbacks.error("Lost connection to the server (is it down?)"); - return; - } - eventHandler(); - } - }); - } - - // Private event handler: this will trigger plugin callbacks, if set - function handleEvent(json, skipTimeout) { - retries = 0; - if(!websockets && sessionId !== undefined && sessionId !== null && skipTimeout !== true) - eventHandler(); - if(!websockets && Janus.isArray(json)) { - // We got an array: it means we passed a maxev > 1, iterate on all objects - for(var i=0; i data channel: ' + dcState); - if(dcState === 'open') { - // Any pending messages to send? - if(config.dataChannel[label].pending && config.dataChannel[label].pending.length > 0) { - Janus.log("Sending pending messages on <" + label + ">:", config.dataChannel[label].pending.length); - for(var data of config.dataChannel[label].pending) { - Janus.log("Sending data on data channel <" + label + ">"); - Janus.debug(data); - config.dataChannel[label].send(data); - } - config.dataChannel[label].pending = []; - } - // Notify the open data channel - pluginHandle.ondataopen(label, protocol); - } - }; - var onDataChannelError = function(error) { - Janus.error('Got error on data channel:', error); - // TODO - }; - if(!incoming) { - // FIXME Add options (ordered, maxRetransmits, etc.) - var dcoptions = config.dataChannelOptions; - if(dcprotocol) - dcoptions.protocol = dcprotocol; - config.dataChannel[dclabel] = config.pc.createDataChannel(dclabel, dcoptions); - } else { - // The channel was created by Janus - config.dataChannel[dclabel] = incoming; - } - config.dataChannel[dclabel].onmessage = onDataChannelMessage; - config.dataChannel[dclabel].onopen = onDataChannelStateChange; - config.dataChannel[dclabel].onclose = onDataChannelStateChange; - config.dataChannel[dclabel].onerror = onDataChannelError; - config.dataChannel[dclabel].pending = []; - if(pendingData) - config.dataChannel[dclabel].pending.push(pendingData); - } - - // Private method to send a data channel message - function sendData(handleId, callbacks) { - callbacks = callbacks || {}; - callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop; - callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop; - var pluginHandle = pluginHandles[handleId]; - if(!pluginHandle || !pluginHandle.webrtcStuff) { - Janus.warn("Invalid handle"); - callbacks.error("Invalid handle"); - return; - } - var config = pluginHandle.webrtcStuff; - var data = callbacks.text || callbacks.data; - if(!data) { - Janus.warn("Invalid data"); - callbacks.error("Invalid data"); - return; - } - var label = callbacks.label ? callbacks.label : Janus.dataChanDefaultLabel; - if(!config.dataChannel[label]) { - // Create new data channel and wait for it to open - createDataChannel(handleId, label, callbacks.protocol, false, data, callbacks.protocol); - callbacks.success(); - return; - } - if(config.dataChannel[label].readyState !== "open") { - config.dataChannel[label].pending.push(data); - callbacks.success(); - return; - } - Janus.log("Sending data on data channel <" + label + ">"); - Janus.debug(data); - config.dataChannel[label].send(data); - callbacks.success(); - } - - // Private method to send a DTMF tone - function sendDtmf(handleId, callbacks) { - callbacks = callbacks || {}; - callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop; - callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop; - var pluginHandle = pluginHandles[handleId]; - if(!pluginHandle || !pluginHandle.webrtcStuff) { - Janus.warn("Invalid handle"); - callbacks.error("Invalid handle"); - return; - } - var config = pluginHandle.webrtcStuff; - if(!config.dtmfSender) { - // Create the DTMF sender the proper way, if possible - if(config.pc) { - var senders = config.pc.getSenders(); - var audioSender = senders.find(function(sender) { - return sender.track && sender.track.kind === 'audio'; - }); - if(!audioSender) { - Janus.warn("Invalid DTMF configuration (no audio track)"); - callbacks.error("Invalid DTMF configuration (no audio track)"); - return; - } - config.dtmfSender = audioSender.dtmf; - if(config.dtmfSender) { - Janus.log("Created DTMF Sender"); - config.dtmfSender.ontonechange = function(tone) { Janus.debug("Sent DTMF tone: " + tone.tone); }; - } - } - if(!config.dtmfSender) { - Janus.warn("Invalid DTMF configuration"); - callbacks.error("Invalid DTMF configuration"); - return; - } - } - var dtmf = callbacks.dtmf; - if(!dtmf) { - Janus.warn("Invalid DTMF parameters"); - callbacks.error("Invalid DTMF parameters"); - return; - } - var tones = dtmf.tones; - if(!tones) { - Janus.warn("Invalid DTMF string"); - callbacks.error("Invalid DTMF string"); - return; - } - var duration = (typeof dtmf.duration === 'number') ? dtmf.duration : 500; // We choose 500ms as the default duration for a tone - var gap = (typeof dtmf.gap === 'number') ? dtmf.gap : 50; // We choose 50ms as the default gap between tones - Janus.debug("Sending DTMF string " + tones + " (duration " + duration + "ms, gap " + gap + "ms)"); - config.dtmfSender.insertDTMF(tones, duration, gap); - callbacks.success(); - } - - // Private method to destroy a plugin handle - function destroyHandle(handleId, callbacks) { - callbacks = callbacks || {}; - callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop; - callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop; - var noRequest = (callbacks.noRequest === true); - Janus.log("Destroying handle " + handleId + " (only-locally=" + noRequest + ")"); - cleanupWebrtc(handleId); - var pluginHandle = pluginHandles[handleId]; - if(!pluginHandle || pluginHandle.detached) { - // Plugin was already detached by Janus, calling detach again will return a handle not found error, so just exit here - delete pluginHandles[handleId]; - callbacks.success(); - return; - } - pluginHandle.detached = true; - if(noRequest) { - // We're only removing the handle locally - delete pluginHandles[handleId]; - callbacks.success(); - return; - } - if(!connected) { - Janus.warn("Is the server down? (connected=false)"); - callbacks.error("Is the server down? (connected=false)"); - return; - } - var request = { "janus": "detach", "transaction": Janus.randomString(12) }; - if(pluginHandle.token) - request["token"] = pluginHandle.token; - if(apisecret) - request["apisecret"] = apisecret; - if(websockets) { - request["session_id"] = sessionId; - request["handle_id"] = handleId; - ws.send(JSON.stringify(request)); - delete pluginHandles[handleId]; - callbacks.success(); - return; - } - Janus.httpAPICall(server + "/" + sessionId + "/" + handleId, { - verb: 'POST', - withCredentials: withCredentials, - body: request, - success: function(json) { - Janus.log("Destroyed handle:"); - Janus.debug(json); - if(json["janus"] !== "success") { - Janus.error("Ooops: " + json["error"].code + " " + json["error"].reason); // FIXME - } - delete pluginHandles[handleId]; - callbacks.success(); - }, - error: function(textStatus, errorThrown) { - Janus.error(textStatus + ":", errorThrown); // FIXME - // We cleanup anyway - delete pluginHandles[handleId]; - callbacks.success(); - } - }); - } - - // WebRTC stuff - function streamsDone(handleId, jsep, media, callbacks, stream) { - var pluginHandle = pluginHandles[handleId]; - if(!pluginHandle || !pluginHandle.webrtcStuff) { - Janus.warn("Invalid handle"); - // Close all tracks if the given stream has been created internally - if(!callbacks.stream) { - Janus.stopAllTracks(stream); - } - callbacks.error("Invalid handle"); - return; - } - var config = pluginHandle.webrtcStuff; - Janus.debug("streamsDone:", stream); - if(stream) { - Janus.debug(" -- Audio tracks:", stream.getAudioTracks()); - Janus.debug(" -- Video tracks:", stream.getVideoTracks()); - } - // We're now capturing the new stream: check if we're updating or if it's a new thing - var addTracks = false; - if(!config.myStream || !media.update || (config.streamExternal && !media.replaceAudio && !media.replaceVideo)) { - config.myStream = stream; - addTracks = true; - } else { - // We only need to update the existing stream - if(((!media.update && isAudioSendEnabled(media)) || (media.update && (media.addAudio || media.replaceAudio))) && - stream.getAudioTracks() && stream.getAudioTracks().length) { - config.myStream.addTrack(stream.getAudioTracks()[0]); - if(Janus.unifiedPlan) { - // Use Transceivers - Janus.log((media.replaceAudio ? "Replacing" : "Adding") + " audio track:", stream.getAudioTracks()[0]); - var audioTransceiver = null; - const transceivers = config.pc.getTransceivers(); - if(transceivers && transceivers.length > 0) { - for(const t of transceivers) { - if((t.sender && t.sender.track && t.sender.track.kind === "audio") || - (t.receiver && t.receiver.track && t.receiver.track.kind === "audio")) { - audioTransceiver = t; - break; - } - } - } - if(audioTransceiver && audioTransceiver.sender) { - audioTransceiver.sender.replaceTrack(stream.getAudioTracks()[0]); - } else { - config.pc.addTrack(stream.getAudioTracks()[0], stream); - } - } else { - Janus.log((media.replaceAudio ? "Replacing" : "Adding") + " audio track:", stream.getAudioTracks()[0]); - config.pc.addTrack(stream.getAudioTracks()[0], stream); - } - } - if(((!media.update && isVideoSendEnabled(media)) || (media.update && (media.addVideo || media.replaceVideo))) && - stream.getVideoTracks() && stream.getVideoTracks().length) { - config.myStream.addTrack(stream.getVideoTracks()[0]); - if(Janus.unifiedPlan) { - // Use Transceivers - Janus.log((media.replaceVideo ? "Replacing" : "Adding") + " video track:", stream.getVideoTracks()[0]); - var videoTransceiver = null; - const transceivers = config.pc.getTransceivers(); - if(transceivers && transceivers.length > 0) { - for(const t of transceivers) { - if((t.sender && t.sender.track && t.sender.track.kind === "video") || - (t.receiver && t.receiver.track && t.receiver.track.kind === "video")) { - videoTransceiver = t; - break; - } - } - } - if(videoTransceiver && videoTransceiver.sender) { - videoTransceiver.sender.replaceTrack(stream.getVideoTracks()[0]); - } else { - config.pc.addTrack(stream.getVideoTracks()[0], stream); - } - } else { - Janus.log((media.replaceVideo ? "Replacing" : "Adding") + " video track:", stream.getVideoTracks()[0]); - config.pc.addTrack(stream.getVideoTracks()[0], stream); - } - } - } - // If we still need to create a PeerConnection, let's do that - if(!config.pc) { - var pc_config = {"iceServers": iceServers, "iceTransportPolicy": iceTransportPolicy, "bundlePolicy": bundlePolicy}; - if(Janus.webRTCAdapter.browserDetails.browser === "chrome") { - // For Chrome versions before 72, we force a plan-b semantic, and unified-plan otherwise - pc_config["sdpSemantics"] = (Janus.webRTCAdapter.browserDetails.version < 72) ? "plan-b" : "unified-plan"; - } - var pc_constraints = {}; - if(Janus.webRTCAdapter.browserDetails.browser === "edge") { - // This is Edge, enable BUNDLE explicitly - pc_config.bundlePolicy = "max-bundle"; - } - // Check if a sender or receiver transform has been provided - if(RTCRtpSender && (RTCRtpSender.prototype.createEncodedStreams || - (RTCRtpSender.prototype.createEncodedAudioStreams && - RTCRtpSender.prototype.createEncodedVideoStreams)) && - (callbacks.senderTransforms || callbacks.receiverTransforms)) { - config.senderTransforms = callbacks.senderTransforms; - config.receiverTransforms = callbacks.receiverTransforms; - pc_config["forceEncodedAudioInsertableStreams"] = true; - pc_config["forceEncodedVideoInsertableStreams"] = true; - pc_config["encodedInsertableStreams"] = true; - } - Janus.log("Creating PeerConnection"); - Janus.debug(pc_constraints); - config.pc = new RTCPeerConnection(pc_config, pc_constraints); - Janus.debug(config.pc); - if(config.pc.getStats) { // FIXME - config.volume = {}; - config.bitrate.value = "0 kbits/sec"; - } - Janus.log("Preparing local SDP and gathering candidates (trickle=" + config.trickle + ")"); - config.pc.oniceconnectionstatechange = function() { - if(config.pc) - pluginHandle.iceState(config.pc.iceConnectionState); - }; - config.pc.onicecandidate = function(event) { - if (!event.candidate || - (Janus.webRTCAdapter.browserDetails.browser === 'edge' && event.candidate.candidate.indexOf('endOfCandidates') > 0)) { - Janus.log("End of candidates."); - config.iceDone = true; - if(config.trickle === true) { - // Notify end of candidates - sendTrickleCandidate(handleId, {"completed": true}); - } else { - // No trickle, time to send the complete SDP (including all candidates) - sendSDP(handleId, callbacks); - } - } else { - // JSON.stringify doesn't work on some WebRTC objects anymore - // See https://code.google.com/p/chromium/issues/detail?id=467366 - var candidate = { - "candidate": event.candidate.candidate, - "sdpMid": event.candidate.sdpMid, - "sdpMLineIndex": event.candidate.sdpMLineIndex - }; - if(config.trickle === true) { - // Send candidate - sendTrickleCandidate(handleId, candidate); - } - } - }; - config.pc.ontrack = function(event) { - Janus.log("Handling Remote Track"); - Janus.debug(event); - if(!event.streams) - return; - config.remoteStream = event.streams[0]; - pluginHandle.onremotestream(config.remoteStream); - if(event.track.onended) - return; - if(config.receiverTransforms) { - var receiverStreams = null; - if(RTCRtpSender.prototype.createEncodedStreams) { - receiverStreams = event.receiver.createEncodedStreams(); - } else if(RTCRtpSender.prototype.createAudioEncodedStreams || RTCRtpSender.prototype.createEncodedVideoStreams) { - if(event.track.kind === "audio" && config.receiverTransforms["audio"]) { - receiverStreams = event.receiver.createEncodedAudioStreams(); - } else if(event.track.kind === "video" && config.receiverTransforms["video"]) { - receiverStreams = event.receiver.createEncodedVideoStreams(); - } - } - if(receiverStreams) { - console.log(receiverStreams); - if(receiverStreams.readableStream && receiverStreams.writableStream) { - receiverStreams.readableStream - .pipeThrough(config.receiverTransforms[event.track.kind]) - .pipeTo(receiverStreams.writableStream); - } else if(receiverStreams.readable && receiverStreams.writable) { - receiverStreams.readable - .pipeThrough(config.receiverTransforms[event.track.kind]) - .pipeTo(receiverStreams.writable); - } - } - } - var trackMutedTimeoutId = null; - Janus.log("Adding onended callback to track:", event.track); - event.track.onended = function(ev) { - Janus.log("Remote track removed:", ev); - if(config.remoteStream) { - clearTimeout(trackMutedTimeoutId); - config.remoteStream.removeTrack(ev.target); - pluginHandle.onremotestream(config.remoteStream); - } - }; - event.track.onmute = function(ev) { - Janus.log("Remote track muted:", ev); - if(config.remoteStream && trackMutedTimeoutId == null) { - trackMutedTimeoutId = setTimeout(function() { - Janus.log("Removing remote track"); - if (config.remoteStream) { - config.remoteStream.removeTrack(ev.target); - pluginHandle.onremotestream(config.remoteStream); - } - trackMutedTimeoutId = null; - // Chrome seems to raise mute events only at multiples of 834ms; - // we set the timeout to three times this value (rounded to 840ms) - }, 3 * 840); - } - }; - event.track.onunmute = function(ev) { - Janus.log("Remote track flowing again:", ev); - if(trackMutedTimeoutId != null) { - clearTimeout(trackMutedTimeoutId); - trackMutedTimeoutId = null; - } else { - try { - config.remoteStream.addTrack(ev.target); - pluginHandle.onremotestream(config.remoteStream); - } catch(e) { - Janus.error(e); - } - } - }; - }; - } - if(addTracks && stream) { - Janus.log('Adding local stream'); - var simulcast = (callbacks.simulcast === true || callbacks.simulcast2 === true) && Janus.unifiedPlan; - var svc = callbacks.svc; - stream.getTracks().forEach(function(track) { - Janus.log('Adding local track:', track); - var sender = null; - if((!simulcast && !svc) || track.kind === 'audio') { - sender = config.pc.addTrack(track, stream); - } else if(simulcast) { - Janus.log('Enabling rid-based simulcasting:', track); - let maxBitrates = getMaxBitrates(callbacks.simulcastMaxBitrates); - let tr = config.pc.addTransceiver(track, { - direction: "sendrecv", - streams: [stream], - sendEncodings: callbacks.sendEncodings || [ - { rid: "h", active: true, maxBitrate: maxBitrates.high }, - { rid: "m", active: true, maxBitrate: maxBitrates.medium, scaleResolutionDownBy: 2 }, - { rid: "l", active: true, maxBitrate: maxBitrates.low, scaleResolutionDownBy: 4 } - ] - }); - if(tr) - sender = tr.sender; - } else { - Janus.log('Enabling SVC (' + svc + '):', track); - let tr = config.pc.addTransceiver(track, { - direction: "sendrecv", - streams: [stream], - sendEncodings: [ - { scalabilityMode: svc } - ] - }); - if(tr) - sender = tr.sender; - } - // Check if insertable streams are involved - if(sender && config.senderTransforms) { - var senderStreams = null; - if(RTCRtpSender.prototype.createEncodedStreams) { - senderStreams = sender.createEncodedStreams(); - } else if(RTCRtpSender.prototype.createAudioEncodedStreams || RTCRtpSender.prototype.createEncodedVideoStreams) { - if(sender.track.kind === "audio" && config.senderTransforms["audio"]) { - senderStreams = sender.createEncodedAudioStreams(); - } else if(sender.track.kind === "video" && config.senderTransforms["video"]) { - senderStreams = sender.createEncodedVideoStreams(); - } - } - if(senderStreams) { - console.log(senderStreams); - if(senderStreams.readableStream && senderStreams.writableStream) { - senderStreams.readableStream - .pipeThrough(config.senderTransforms[sender.track.kind]) - .pipeTo(senderStreams.writableStream); - } else if(senderStreams.readable && senderStreams.writable) { - senderStreams.readable - .pipeThrough(config.senderTransforms[sender.track.kind]) - .pipeTo(senderStreams.writable); - } - } - } - }); - } - // Any data channel to create? - if(isDataEnabled(media) && !config.dataChannel[Janus.dataChanDefaultLabel]) { - Janus.log("Creating default data channel"); - createDataChannel(handleId, Janus.dataChanDefaultLabel, null, false); - config.pc.ondatachannel = function(event) { - Janus.log("Data channel created by Janus:", event); - createDataChannel(handleId, event.channel.label, event.channel.protocol, event.channel); - }; - } - // If there's a new local stream, let's notify the application - if(config.myStream) { - pluginHandle.onlocalstream(config.myStream); - } - // Create offer/answer now - if(!jsep) { - createOffer(handleId, media, callbacks); - } else { - config.pc.setRemoteDescription(jsep) - .then(function() { - Janus.log("Remote description accepted!"); - config.remoteSdp = jsep.sdp; - // Any trickle candidate we cached? - if(config.candidates && config.candidates.length > 0) { - for(var i = 0; i< config.candidates.length; i++) { - var candidate = config.candidates[i]; - Janus.debug("Adding remote candidate:", candidate); - if(!candidate || candidate.completed === true) { - // end-of-candidates - config.pc.addIceCandidate(Janus.endOfCandidates); - } else { - // New candidate - config.pc.addIceCandidate(candidate); - } - } - config.candidates = []; - } - // Create the answer now - createAnswer(handleId, media, callbacks); - }, callbacks.error); - } - } - - function prepareWebrtc(handleId, offer, callbacks) { - callbacks = callbacks || {}; - callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop; - callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : webrtcError; - var jsep = callbacks.jsep; - if(offer && jsep) { - Janus.error("Provided a JSEP to a createOffer"); - callbacks.error("Provided a JSEP to a createOffer"); - return; - } else if(!offer && (!jsep || !jsep.type || !jsep.sdp)) { - Janus.error("A valid JSEP is required for createAnswer"); - callbacks.error("A valid JSEP is required for createAnswer"); - return; - } - /* Check that callbacks.media is a (not null) Object */ - callbacks.media = (typeof callbacks.media === 'object' && callbacks.media) ? callbacks.media : { audio: true, video: true }; - var media = callbacks.media; - var pluginHandle = pluginHandles[handleId]; - if(!pluginHandle || !pluginHandle.webrtcStuff) { - Janus.warn("Invalid handle"); - callbacks.error("Invalid handle"); - return; - } - var config = pluginHandle.webrtcStuff; - config.trickle = isTrickleEnabled(callbacks.trickle); - // Are we updating a session? - if(!config.pc) { - // Nope, new PeerConnection - media.update = false; - media.keepAudio = false; - media.keepVideo = false; - } else { - Janus.log("Updating existing media session"); - media.update = true; - // Check if there's anything to add/remove/replace, or if we - // can go directly to preparing the new SDP offer or answer - if(callbacks.stream) { - // External stream: is this the same as the one we were using before? - if(callbacks.stream !== config.myStream) { - Janus.log("Renegotiation involves a new external stream"); - } - } else { - // Check if there are changes on audio - if(media.addAudio) { - media.keepAudio = false; - media.replaceAudio = false; - media.removeAudio = false; - media.audioSend = true; - if(config.myStream && config.myStream.getAudioTracks() && config.myStream.getAudioTracks().length) { - Janus.error("Can't add audio stream, there already is one"); - callbacks.error("Can't add audio stream, there already is one"); - return; - } - } else if(media.removeAudio) { - media.keepAudio = false; - media.replaceAudio = false; - media.addAudio = false; - media.audioSend = false; - } else if(media.replaceAudio) { - media.keepAudio = false; - media.addAudio = false; - media.removeAudio = false; - media.audioSend = true; - } - if(!config.myStream) { - // No media stream: if we were asked to replace, it's actually an "add" - if(media.replaceAudio) { - media.keepAudio = false; - media.replaceAudio = false; - media.addAudio = true; - media.audioSend = true; - } - if(isAudioSendEnabled(media)) { - media.keepAudio = false; - media.addAudio = true; - } - } else { - if(!config.myStream.getAudioTracks() || config.myStream.getAudioTracks().length === 0) { - // No audio track: if we were asked to replace, it's actually an "add" - if(media.replaceAudio) { - media.keepAudio = false; - media.replaceAudio = false; - media.addAudio = true; - media.audioSend = true; - } - if(isAudioSendEnabled(media)) { - media.keepAudio = false; - media.addAudio = true; - } - } else { - // We have an audio track: should we keep it as it is? - if(isAudioSendEnabled(media) && - !media.removeAudio && !media.replaceAudio) { - media.keepAudio = true; - } - } - } - // Check if there are changes on video - if(media.addVideo) { - media.keepVideo = false; - media.replaceVideo = false; - media.removeVideo = false; - media.videoSend = true; - if(config.myStream && config.myStream.getVideoTracks() && config.myStream.getVideoTracks().length) { - Janus.error("Can't add video stream, there already is one"); - callbacks.error("Can't add video stream, there already is one"); - return; - } - } else if(media.removeVideo) { - media.keepVideo = false; - media.replaceVideo = false; - media.addVideo = false; - media.videoSend = false; - } else if(media.replaceVideo) { - media.keepVideo = false; - media.addVideo = false; - media.removeVideo = false; - media.videoSend = true; - } - if(!config.myStream) { - // No media stream: if we were asked to replace, it's actually an "add" - if(media.replaceVideo) { - media.keepVideo = false; - media.replaceVideo = false; - media.addVideo = true; - media.videoSend = true; - } - if(isVideoSendEnabled(media)) { - media.keepVideo = false; - media.addVideo = true; - } - } else { - if(!config.myStream.getVideoTracks() || config.myStream.getVideoTracks().length === 0) { - // No video track: if we were asked to replace, it's actually an "add" - if(media.replaceVideo) { - media.keepVideo = false; - media.replaceVideo = false; - media.addVideo = true; - media.videoSend = true; - } - if(isVideoSendEnabled(media)) { - media.keepVideo = false; - media.addVideo = true; - } - } else { - // We have a video track: should we keep it as it is? - if(isVideoSendEnabled(media) && !media.removeVideo && !media.replaceVideo) { - media.keepVideo = true; - } - } - } - // Data channels can only be added - if(media.addData) { - media.data = true; - } - } - // If we're updating and keeping all tracks, let's skip the getUserMedia part - if((isAudioSendEnabled(media) && media.keepAudio) && - (isVideoSendEnabled(media) && media.keepVideo)) { - pluginHandle.consentDialog(false); - streamsDone(handleId, jsep, media, callbacks, config.myStream); - return; - } - } - // If we're updating, check if we need to remove/replace one of the tracks - if(media.update && (!config.streamExternal || (config.streamExternal && (media.replaceAudio || media.replaceVideo)))) { - if(media.removeAudio || media.replaceAudio) { - if(config.myStream && config.myStream.getAudioTracks() && config.myStream.getAudioTracks().length) { - var at = config.myStream.getAudioTracks()[0]; - Janus.log("Removing audio track:", at); - config.myStream.removeTrack(at); - try { - at.stop(); - } catch(e) {} - } - if(config.pc.getSenders() && config.pc.getSenders().length) { - var ra = true; - if(media.replaceAudio && Janus.unifiedPlan) { - // We can use replaceTrack - ra = false; - } - if(ra) { - for(var asnd of config.pc.getSenders()) { - if(asnd && asnd.track && asnd.track.kind === "audio") { - Janus.log("Removing audio sender:", asnd); - config.pc.removeTrack(asnd); - } - } - } - } - } - if(media.removeVideo || media.replaceVideo) { - if(config.myStream && config.myStream.getVideoTracks() && config.myStream.getVideoTracks().length) { - var vt = config.myStream.getVideoTracks()[0]; - Janus.log("Removing video track:", vt); - config.myStream.removeTrack(vt); - try { - vt.stop(); - } catch(e) {} - } - if(config.pc.getSenders() && config.pc.getSenders().length) { - var rv = true; - if(media.replaceVideo && Janus.unifiedPlan) { - // We can use replaceTrack - rv = false; - } - if(rv) { - for(var vsnd of config.pc.getSenders()) { - if(vsnd && vsnd.track && vsnd.track.kind === "video") { - Janus.log("Removing video sender:", vsnd); - config.pc.removeTrack(vsnd); - } - } - } - } - } - } - // Was a MediaStream object passed, or do we need to take care of that? - if(callbacks.stream) { - var stream = callbacks.stream; - Janus.log("MediaStream provided by the application"); - Janus.debug(stream); - // If this is an update, let's check if we need to release the previous stream - if(media.update && config.myStream && config.myStream !== callbacks.stream && !config.streamExternal && !media.replaceAudio && !media.replaceVideo) { - // We're replacing a stream we captured ourselves with an external one - Janus.stopAllTracks(config.myStream); - config.myStream = null; - } - // Skip the getUserMedia part - config.streamExternal = true; - pluginHandle.consentDialog(false); - streamsDone(handleId, jsep, media, callbacks, stream); - return; - } - if(isAudioSendEnabled(media) || isVideoSendEnabled(media)) { - if(!Janus.isGetUserMediaAvailable()) { - callbacks.error("getUserMedia not available"); - return; - } - var constraints = { mandatory: {}, optional: []}; - pluginHandle.consentDialog(true); - var audioSupport = isAudioSendEnabled(media); - if(audioSupport && media && typeof media.audio === 'object') - audioSupport = media.audio; - var videoSupport = isVideoSendEnabled(media); - if(videoSupport && media) { - var simulcast = (callbacks.simulcast === true || callbacks.simulcast2 === true); - var svc = callbacks.svc; - if((simulcast || svc) && !jsep && !media.video) - media.video = "hires"; - if(media.video && media.video != 'screen' && media.video != 'window') { - if(typeof media.video === 'object') { - videoSupport = media.video; - } else { - var width = 0; - var height = 0; - if(media.video === 'lowres') { - // Small resolution, 4:3 - height = 240; - width = 320; - } else if(media.video === 'lowres-16:9') { - // Small resolution, 16:9 - height = 180; - width = 320; - } else if(media.video === 'hires' || media.video === 'hires-16:9' || media.video === 'hdres') { - // High(HD) resolution is only 16:9 - height = 720; - width = 1280; - } else if(media.video === 'fhdres') { - // Full HD resolution is only 16:9 - height = 1080; - width = 1920; - } else if(media.video === '4kres') { - // 4K resolution is only 16:9 - height = 2160; - width = 3840; - } else if(media.video === 'stdres') { - // Normal resolution, 4:3 - height = 480; - width = 640; - } else if(media.video === 'stdres-16:9') { - // Normal resolution, 16:9 - height = 360; - width = 640; - } else { - Janus.log("Default video setting is stdres 4:3"); - height = 480; - width = 640; - } - Janus.log("Adding media constraint:", media.video); - videoSupport = { - 'height': {'ideal': height}, - 'width': {'ideal': width} - }; - Janus.log("Adding video constraint:", videoSupport); - } - } else if(media.video === 'screen' || media.video === 'window') { - if(navigator.mediaDevices && navigator.mediaDevices.getDisplayMedia) { - // The new experimental getDisplayMedia API is available, let's use that - // https://groups.google.com/forum/#!topic/discuss-webrtc/Uf0SrR4uxzk - // https://webrtchacks.com/chrome-screensharing-getdisplaymedia/ - constraints.video = {}; - if(media.screenshareFrameRate) { - constraints.video.frameRate = media.screenshareFrameRate; - } - if(media.screenshareHeight) { - constraints.video.height = media.screenshareHeight; - } - if(media.screenshareWidth) { - constraints.video.width = media.screenshareWidth; - } - constraints.audio = media.captureDesktopAudio; - navigator.mediaDevices.getDisplayMedia(constraints) - .then(function(stream) { - pluginHandle.consentDialog(false); - if(isAudioSendEnabled(media) && !media.keepAudio) { - navigator.mediaDevices.getUserMedia({ audio: true, video: false }) - .then(function (audioStream) { - stream.addTrack(audioStream.getAudioTracks()[0]); - streamsDone(handleId, jsep, media, callbacks, stream); - }) - } else { - streamsDone(handleId, jsep, media, callbacks, stream); - } - }, function (error) { - pluginHandle.consentDialog(false); - callbacks.error(error); - }); - return; - } - // We're going to try and use the extension for Chrome 34+, the old approach - // for older versions of Chrome, or the experimental support in Firefox 33+ - const callbackUserMedia = function(error, stream) { - pluginHandle.consentDialog(false); - if(error) { - callbacks.error(error); - } else { - streamsDone(handleId, jsep, media, callbacks, stream); - } - } - const getScreenMedia = function(constraints, gsmCallback, useAudio) { - Janus.log("Adding media constraint (screen capture)"); - Janus.debug(constraints); - navigator.mediaDevices.getUserMedia(constraints) - .then(function(stream) { - if(useAudio) { - navigator.mediaDevices.getUserMedia({ audio: true, video: false }) - .then(function (audioStream) { - stream.addTrack(audioStream.getAudioTracks()[0]); - gsmCallback(null, stream); - }) - } else { - gsmCallback(null, stream); - } - }) - .catch(function(error) { pluginHandle.consentDialog(false); gsmCallback(error); }); - } - if(Janus.webRTCAdapter.browserDetails.browser === 'chrome') { - var chromever = Janus.webRTCAdapter.browserDetails.version; - var maxver = 33; - if(window.navigator.userAgent.match('Linux')) - maxver = 35; // "known" crash in chrome 34 and 35 on linux - if(chromever >= 26 && chromever <= maxver) { - // Chrome 26->33 requires some awkward chrome://flags manipulation - constraints = { - video: { - mandatory: { - googLeakyBucket: true, - maxWidth: window.screen.width, - maxHeight: window.screen.height, - minFrameRate: media.screenshareFrameRate, - maxFrameRate: media.screenshareFrameRate, - chromeMediaSource: 'screen' - } - }, - audio: isAudioSendEnabled(media) && !media.keepAudio - }; - getScreenMedia(constraints, callbackUserMedia); - } else { - // Chrome 34+ requires an extension - Janus.extension.getScreen(function (error, sourceId) { - if (error) { - pluginHandle.consentDialog(false); - return callbacks.error(error); - } - constraints = { - audio: false, - video: { - mandatory: { - chromeMediaSource: 'desktop', - maxWidth: window.screen.width, - maxHeight: window.screen.height, - minFrameRate: media.screenshareFrameRate, - maxFrameRate: media.screenshareFrameRate, - }, - optional: [ - {googLeakyBucket: true}, - {googTemporalLayeredScreencast: true} - ] - } - }; - constraints.video.mandatory.chromeMediaSourceId = sourceId; - getScreenMedia(constraints, callbackUserMedia, - isAudioSendEnabled(media) && !media.keepAudio); - }); - } - } else if(Janus.webRTCAdapter.browserDetails.browser === 'firefox') { - if(Janus.webRTCAdapter.browserDetails.version >= 33) { - // Firefox 33+ has experimental support for screen sharing - constraints = { - video: { - mozMediaSource: media.video, - mediaSource: media.video - }, - audio: isAudioSendEnabled(media) && !media.keepAudio - }; - getScreenMedia(constraints, function (err, stream) { - callbackUserMedia(err, stream); - // Workaround for https://bugzilla.mozilla.org/show_bug.cgi?id=1045810 - if (!err) { - var lastTime = stream.currentTime; - var polly = window.setInterval(function () { - if(!stream) - window.clearInterval(polly); - if(stream.currentTime == lastTime) { - window.clearInterval(polly); - if(stream.onended) { - stream.onended(); - } - } - lastTime = stream.currentTime; - }, 500); - } - }); - } else { - var error = new Error('NavigatorUserMediaError'); - error.name = 'Your version of Firefox does not support screen sharing, please install Firefox 33 (or more recent versions)'; - pluginHandle.consentDialog(false); - callbacks.error(error); - return; - } - } - return; - } - } - // If we got here, we're not screensharing - if(!media || media.video !== 'screen') { - // Check whether all media sources are actually available or not - navigator.mediaDevices.enumerateDevices().then(function(devices) { - var audioExist = devices.some(function(device) { - return device.kind === 'audioinput'; - }), - videoExist = isScreenSendEnabled(media) || devices.some(function(device) { - return device.kind === 'videoinput'; - }); - - // Check whether a missing device is really a problem - var audioSend = isAudioSendEnabled(media); - var videoSend = isVideoSendEnabled(media); - var needAudioDevice = isAudioSendRequired(media); - var needVideoDevice = isVideoSendRequired(media); - if(audioSend || videoSend || needAudioDevice || needVideoDevice) { - // We need to send either audio or video - var haveAudioDevice = audioSend ? audioExist : false; - var haveVideoDevice = videoSend ? videoExist : false; - if(!haveAudioDevice && !haveVideoDevice) { - // FIXME Should we really give up, or just assume recvonly for both? - pluginHandle.consentDialog(false); - callbacks.error('No capture device found'); - return false; - } else if(!haveAudioDevice && needAudioDevice) { - pluginHandle.consentDialog(false); - callbacks.error('Audio capture is required, but no capture device found'); - return false; - } else if(!haveVideoDevice && needVideoDevice) { - pluginHandle.consentDialog(false); - callbacks.error('Video capture is required, but no capture device found'); - return false; - } - } - - var gumConstraints = { - audio: (audioExist && !media.keepAudio) ? audioSupport : false, - video: (videoExist && !media.keepVideo) ? videoSupport : false - }; - Janus.debug("getUserMedia constraints", gumConstraints); - if (!gumConstraints.audio && !gumConstraints.video) { - pluginHandle.consentDialog(false); - streamsDone(handleId, jsep, media, callbacks, stream); - } else { - navigator.mediaDevices.getUserMedia(gumConstraints) - .then(function(stream) { - pluginHandle.consentDialog(false); - streamsDone(handleId, jsep, media, callbacks, stream); - }).catch(function(error) { - pluginHandle.consentDialog(false); - callbacks.error({code: error.code, name: error.name, message: error.message}); - }); - } - }) - .catch(function(error) { - pluginHandle.consentDialog(false); - callbacks.error(error); - }); - } - } else { - // No need to do a getUserMedia, create offer/answer right away - streamsDone(handleId, jsep, media, callbacks); - } - } - - function prepareWebrtcPeer(handleId, callbacks) { - callbacks = callbacks || {}; - callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop; - callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : webrtcError; - callbacks.customizeSdp = (typeof callbacks.customizeSdp == "function") ? callbacks.customizeSdp : Janus.noop; - var jsep = callbacks.jsep; - var pluginHandle = pluginHandles[handleId]; - if(!pluginHandle || !pluginHandle.webrtcStuff) { - Janus.warn("Invalid handle"); - callbacks.error("Invalid handle"); - return; - } - var config = pluginHandle.webrtcStuff; - if(jsep) { - if(!config.pc) { - Janus.warn("Wait, no PeerConnection?? if this is an answer, use createAnswer and not handleRemoteJsep"); - callbacks.error("No PeerConnection: if this is an answer, use createAnswer and not handleRemoteJsep"); - return; - } - callbacks.customizeSdp(jsep); - config.pc.setRemoteDescription(jsep) - .then(function() { - Janus.log("Remote description accepted!"); - config.remoteSdp = jsep.sdp; - // Any trickle candidate we cached? - if(config.candidates && config.candidates.length > 0) { - for(var i = 0; i< config.candidates.length; i++) { - var candidate = config.candidates[i]; - Janus.debug("Adding remote candidate:", candidate); - if(!candidate || candidate.completed === true) { - // end-of-candidates - config.pc.addIceCandidate(Janus.endOfCandidates); - } else { - // New candidate - config.pc.addIceCandidate(candidate); - } - } - config.candidates = []; - } - // Done - callbacks.success(); - }, callbacks.error); - } else { - callbacks.error("Invalid JSEP"); - } - } - - function createOffer(handleId, media, callbacks) { - callbacks = callbacks || {}; - callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop; - callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop; - callbacks.customizeSdp = (typeof callbacks.customizeSdp == "function") ? callbacks.customizeSdp : Janus.noop; - var pluginHandle = pluginHandles[handleId]; - if(!pluginHandle || !pluginHandle.webrtcStuff) { - Janus.warn("Invalid handle"); - callbacks.error("Invalid handle"); - return; - } - var config = pluginHandle.webrtcStuff; - var simulcast = (callbacks.simulcast === true); - if(!simulcast) { - Janus.log("Creating offer (iceDone=" + config.iceDone + ")"); - } else { - Janus.log("Creating offer (iceDone=" + config.iceDone + ", simulcast=" + simulcast + ")"); - } - // https://code.google.com/p/webrtc/issues/detail?id=3508 - var mediaConstraints = {}; - if(Janus.unifiedPlan) { - // We can use Transceivers - var audioTransceiver = null, videoTransceiver = null; - var transceivers = config.pc.getTransceivers(); - if(transceivers && transceivers.length > 0) { - for(var t of transceivers) { - if((t.sender && t.sender.track && t.sender.track.kind === "audio") || - (t.receiver && t.receiver.track && t.receiver.track.kind === "audio")) { - if(!audioTransceiver) { - audioTransceiver = t; - } - continue; - } - if((t.sender && t.sender.track && t.sender.track.kind === "video") || - (t.receiver && t.receiver.track && t.receiver.track.kind === "video")) { - if(!videoTransceiver) { - videoTransceiver = t; - } - continue; - } - } - } - // Handle audio (and related changes, if any) - var audioSend = isAudioSendEnabled(media); - var audioRecv = isAudioRecvEnabled(media); - if(!audioSend && !audioRecv) { - // Audio disabled: have we removed it? - if(media.removeAudio && audioTransceiver) { - if (audioTransceiver.setDirection) { - audioTransceiver.setDirection("inactive"); - } else { - audioTransceiver.direction = "inactive"; - } - Janus.log("Setting audio transceiver to inactive:", audioTransceiver); - } - } else { - // Take care of audio m-line - if(audioSend && audioRecv) { - if(audioTransceiver) { - if (audioTransceiver.setDirection) { - audioTransceiver.setDirection("sendrecv"); - } else { - audioTransceiver.direction = "sendrecv"; - } - Janus.log("Setting audio transceiver to sendrecv:", audioTransceiver); - } - } else if(audioSend && !audioRecv) { - if(audioTransceiver) { - if (audioTransceiver.setDirection) { - audioTransceiver.setDirection("sendonly"); - } else { - audioTransceiver.direction = "sendonly"; - } - Janus.log("Setting audio transceiver to sendonly:", audioTransceiver); - } - } else if(!audioSend && audioRecv) { - if(audioTransceiver) { - if (audioTransceiver.setDirection) { - audioTransceiver.setDirection("recvonly"); - } else { - audioTransceiver.direction = "recvonly"; - } - Janus.log("Setting audio transceiver to recvonly:", audioTransceiver); - } else { - // In theory, this is the only case where we might not have a transceiver yet - audioTransceiver = config.pc.addTransceiver("audio", { direction: "recvonly" }); - Janus.log("Adding recvonly audio transceiver:", audioTransceiver); - } - } - } - // Handle video (and related changes, if any) - var videoSend = isVideoSendEnabled(media); - var videoRecv = isVideoRecvEnabled(media); - if(!videoSend && !videoRecv) { - // Video disabled: have we removed it? - if(media.removeVideo && videoTransceiver) { - if (videoTransceiver.setDirection) { - videoTransceiver.setDirection("inactive"); - } else { - videoTransceiver.direction = "inactive"; - } - Janus.log("Setting video transceiver to inactive:", videoTransceiver); - } - } else { - // Take care of video m-line - if(videoSend && videoRecv) { - if(videoTransceiver) { - if (videoTransceiver.setDirection) { - videoTransceiver.setDirection("sendrecv"); - } else { - videoTransceiver.direction = "sendrecv"; - } - Janus.log("Setting video transceiver to sendrecv:", videoTransceiver); - } - } else if(videoSend && !videoRecv) { - if(videoTransceiver) { - if (videoTransceiver.setDirection) { - videoTransceiver.setDirection("sendonly"); - } else { - videoTransceiver.direction = "sendonly"; - } - Janus.log("Setting video transceiver to sendonly:", videoTransceiver); - } - } else if(!videoSend && videoRecv) { - if(videoTransceiver) { - if (videoTransceiver.setDirection) { - videoTransceiver.setDirection("recvonly"); - } else { - videoTransceiver.direction = "recvonly"; - } - Janus.log("Setting video transceiver to recvonly:", videoTransceiver); - } else { - // In theory, this is the only case where we might not have a transceiver yet - videoTransceiver = config.pc.addTransceiver("video", { direction: "recvonly" }); - Janus.log("Adding recvonly video transceiver:", videoTransceiver); - } - } - } - } else { - mediaConstraints["offerToReceiveAudio"] = isAudioRecvEnabled(media); - mediaConstraints["offerToReceiveVideo"] = isVideoRecvEnabled(media); - } - var iceRestart = (callbacks.iceRestart === true); - if(iceRestart) { - mediaConstraints["iceRestart"] = true; - } - Janus.debug(mediaConstraints); - // Check if this is Firefox and we've been asked to do simulcasting - var sendVideo = isVideoSendEnabled(media); - if(sendVideo && simulcast && Janus.webRTCAdapter.browserDetails.browser === "firefox") { - // FIXME Based on https://gist.github.com/voluntas/088bc3cc62094730647b - Janus.log("Enabling Simulcasting for Firefox (RID)"); - var sender = config.pc.getSenders().find(function(s) {return s.track && s.track.kind === "video"}); - if(sender) { - var parameters = sender.getParameters(); - if(!parameters) { - parameters = {}; - } - var maxBitrates = getMaxBitrates(callbacks.simulcastMaxBitrates); - parameters.encodings = callbacks.sendEncodings || [ - { rid: "h", active: true, maxBitrate: maxBitrates.high }, - { rid: "m", active: true, maxBitrate: maxBitrates.medium, scaleResolutionDownBy: 2 }, - { rid: "l", active: true, maxBitrate: maxBitrates.low, scaleResolutionDownBy: 4 } - ]; - sender.setParameters(parameters); - } - } - config.pc.createOffer(mediaConstraints) - .then(function(offer) { - Janus.debug(offer); - // JSON.stringify doesn't work on some WebRTC objects anymore - // See https://code.google.com/p/chromium/issues/detail?id=467366 - var jsep = { - "type": offer.type, - "sdp": offer.sdp - }; - callbacks.customizeSdp(jsep); - offer.sdp = jsep.sdp; - Janus.log("Setting local description"); - if(sendVideo && simulcast && !Janus.unifiedPlan) { - // We only do simulcast via SDP munging on older versions of Chrome and Safari - if(Janus.webRTCAdapter.browserDetails.browser === "chrome" || - Janus.webRTCAdapter.browserDetails.browser === "safari") { - Janus.log("Enabling Simulcasting for Chrome (SDP munging)"); - offer.sdp = mungeSdpForSimulcasting(offer.sdp); - } - } - config.mySdp = { - type: "offer", - sdp: offer.sdp - }; - config.pc.setLocalDescription(offer) - .catch(callbacks.error); - config.mediaConstraints = mediaConstraints; - if(!config.iceDone && !config.trickle) { - // Don't do anything until we have all candidates - Janus.log("Waiting for all candidates..."); - return; - } - // If transforms are present, notify Janus that the media is end-to-end encrypted - if(config.senderTransforms || config.receiverTransforms) { - offer["e2ee"] = true; - } - callbacks.success(offer); - }, callbacks.error); - } - - function createAnswer(handleId, media, callbacks) { - callbacks = callbacks || {}; - callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop; - callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop; - callbacks.customizeSdp = (typeof callbacks.customizeSdp == "function") ? callbacks.customizeSdp : Janus.noop; - var pluginHandle = pluginHandles[handleId]; - if(!pluginHandle || !pluginHandle.webrtcStuff) { - Janus.warn("Invalid handle"); - callbacks.error("Invalid handle"); - return; - } - var config = pluginHandle.webrtcStuff; - var simulcast = (callbacks.simulcast === true || callbacks.simulcast2 === true); - if(!simulcast) { - Janus.log("Creating answer (iceDone=" + config.iceDone + ")"); - } else { - Janus.log("Creating answer (iceDone=" + config.iceDone + ", simulcast=" + simulcast + ")"); - } - var mediaConstraints = null; - if(Janus.unifiedPlan) { - // We can use Transceivers - mediaConstraints = {}; - var audioTransceiver = null, videoTransceiver = null; - var transceivers = config.pc.getTransceivers(); - if(transceivers && transceivers.length > 0) { - for(var t of transceivers) { - if((t.sender && t.sender.track && t.sender.track.kind === "audio") || - (t.receiver && t.receiver.track && t.receiver.track.kind === "audio")) { - if(!audioTransceiver) - audioTransceiver = t; - continue; - } - if((t.sender && t.sender.track && t.sender.track.kind === "video") || - (t.receiver && t.receiver.track && t.receiver.track.kind === "video")) { - if(!videoTransceiver) - videoTransceiver = t; - continue; - } - } - } - // Handle audio (and related changes, if any) - var audioSend = isAudioSendEnabled(media); - var audioRecv = isAudioRecvEnabled(media); - if(!audioSend && !audioRecv) { - // Audio disabled: have we removed it? - if(media.removeAudio && audioTransceiver) { - try { - if (audioTransceiver.setDirection) { - audioTransceiver.setDirection("inactive"); - } else { - audioTransceiver.direction = "inactive"; - } - Janus.log("Setting audio transceiver to inactive:", audioTransceiver); - } catch(e) { - Janus.error(e); - } - } - } else { - // Take care of audio m-line - if(audioSend && audioRecv) { - if(audioTransceiver) { - try { - if (audioTransceiver.setDirection) { - audioTransceiver.setDirection("sendrecv"); - } else { - audioTransceiver.direction = "sendrecv"; - } - Janus.log("Setting audio transceiver to sendrecv:", audioTransceiver); - } catch(e) { - Janus.error(e); - } - } - } else if(audioSend && !audioRecv) { - try { - if(audioTransceiver) { - if (audioTransceiver.setDirection) { - audioTransceiver.setDirection("sendonly"); - } else { - audioTransceiver.direction = "sendonly"; - } - Janus.log("Setting audio transceiver to sendonly:", audioTransceiver); - } - } catch(e) { - Janus.error(e); - } - } else if(!audioSend && audioRecv) { - if(audioTransceiver) { - try { - if (audioTransceiver.setDirection) { - audioTransceiver.setDirection("recvonly"); - } else { - audioTransceiver.direction = "recvonly"; - } - Janus.log("Setting audio transceiver to recvonly:", audioTransceiver); - } catch(e) { - Janus.error(e); - } - } else { - // In theory, this is the only case where we might not have a transceiver yet - audioTransceiver = config.pc.addTransceiver("audio", { direction: "recvonly" }); - Janus.log("Adding recvonly audio transceiver:", audioTransceiver); - } - } - } - // Handle video (and related changes, if any) - var videoSend = isVideoSendEnabled(media); - var videoRecv = isVideoRecvEnabled(media); - if(!videoSend && !videoRecv) { - // Video disabled: have we removed it? - if(media.removeVideo && videoTransceiver) { - try { - if (videoTransceiver.setDirection) { - videoTransceiver.setDirection("inactive"); - } else { - videoTransceiver.direction = "inactive"; - } - Janus.log("Setting video transceiver to inactive:", videoTransceiver); - } catch(e) { - Janus.error(e); - } - } - } else { - // Take care of video m-line - if(videoSend && videoRecv) { - if(videoTransceiver) { - try { - if (videoTransceiver.setDirection) { - videoTransceiver.setDirection("sendrecv"); - } else { - videoTransceiver.direction = "sendrecv"; - } - Janus.log("Setting video transceiver to sendrecv:", videoTransceiver); - } catch(e) { - Janus.error(e); - } - } - } else if(videoSend && !videoRecv) { - if(videoTransceiver) { - try { - if (videoTransceiver.setDirection) { - videoTransceiver.setDirection("sendonly"); - } else { - videoTransceiver.direction = "sendonly"; - } - Janus.log("Setting video transceiver to sendonly:", videoTransceiver); - } catch(e) { - Janus.error(e); - } - } - } else if(!videoSend && videoRecv) { - if(videoTransceiver) { - try { - if (videoTransceiver.setDirection) { - videoTransceiver.setDirection("recvonly"); - } else { - videoTransceiver.direction = "recvonly"; - } - Janus.log("Setting video transceiver to recvonly:", videoTransceiver); - } catch(e) { - Janus.error(e); - } - } else { - // In theory, this is the only case where we might not have a transceiver yet - videoTransceiver = config.pc.addTransceiver("video", { direction: "recvonly" }); - Janus.log("Adding recvonly video transceiver:", videoTransceiver); - } - } - } - } else { - if(Janus.webRTCAdapter.browserDetails.browser === "firefox" || Janus.webRTCAdapter.browserDetails.browser === "edge") { - mediaConstraints = { - offerToReceiveAudio: isAudioRecvEnabled(media), - offerToReceiveVideo: isVideoRecvEnabled(media) - }; - } else { - mediaConstraints = { - mandatory: { - OfferToReceiveAudio: isAudioRecvEnabled(media), - OfferToReceiveVideo: isVideoRecvEnabled(media) - } - }; - } - } - Janus.debug(mediaConstraints); - // Check if this is Firefox and we've been asked to do simulcasting - var sendVideo = isVideoSendEnabled(media); - if(sendVideo && simulcast && Janus.webRTCAdapter.browserDetails.browser === "firefox") { - // FIXME Based on https://gist.github.com/voluntas/088bc3cc62094730647b - Janus.log("Enabling Simulcasting for Firefox (RID)"); - var sender = config.pc.getSenders()[1]; - Janus.log(sender); - var parameters = sender.getParameters(); - Janus.log(parameters); - - var maxBitrates = getMaxBitrates(callbacks.simulcastMaxBitrates); - sender.setParameters({encodings: callbacks.sendEncodings || [ - { rid: "h", active: true, maxBitrate: maxBitrates.high }, - { rid: "m", active: true, maxBitrate: maxBitrates.medium, scaleResolutionDownBy: 2}, - { rid: "l", active: true, maxBitrate: maxBitrates.low, scaleResolutionDownBy: 4} - ]}); - } - config.pc.createAnswer(mediaConstraints) - .then(function(answer) { - Janus.debug(answer); - // JSON.stringify doesn't work on some WebRTC objects anymore - // See https://code.google.com/p/chromium/issues/detail?id=467366 - var jsep = { - "type": answer.type, - "sdp": answer.sdp - }; - callbacks.customizeSdp(jsep); - answer.sdp = jsep.sdp; - Janus.log("Setting local description"); - if(sendVideo && simulcast && !Janus.unifiedPlan) { - // We only do simulcast via SDP munging on older versions of Chrome and Safari - if(Janus.webRTCAdapter.browserDetails.browser === "chrome") { - // FIXME Apparently trying to simulcast when answering breaks video in Chrome... - //~ Janus.log("Enabling Simulcasting for Chrome (SDP munging)"); - //~ answer.sdp = mungeSdpForSimulcasting(answer.sdp); - Janus.warn("simulcast=true, but this is an answer, and video breaks in Chrome if we enable it"); - } - } - config.mySdp = { - type: "answer", - sdp: answer.sdp - }; - config.pc.setLocalDescription(answer) - .catch(callbacks.error); - config.mediaConstraints = mediaConstraints; - if(!config.iceDone && !config.trickle) { - // Don't do anything until we have all candidates - Janus.log("Waiting for all candidates..."); - return; - } - // If transforms are present, notify Janus that the media is end-to-end encrypted - if(config.senderTransforms || config.receiverTransforms) { - answer["e2ee"] = true; - } - callbacks.success(answer); - }, callbacks.error); - } - - function sendSDP(handleId, callbacks) { - callbacks = callbacks || {}; - callbacks.success = (typeof callbacks.success == "function") ? callbacks.success : Janus.noop; - callbacks.error = (typeof callbacks.error == "function") ? callbacks.error : Janus.noop; - var pluginHandle = pluginHandles[handleId]; - if(!pluginHandle || !pluginHandle.webrtcStuff) { - Janus.warn("Invalid handle, not sending anything"); - return; - } - var config = pluginHandle.webrtcStuff; - Janus.log("Sending offer/answer SDP..."); - if(!config.mySdp) { - Janus.warn("Local SDP instance is invalid, not sending anything..."); - return; - } - config.mySdp = { - "type": config.pc.localDescription.type, - "sdp": config.pc.localDescription.sdp - }; - if(config.trickle === false) - config.mySdp["trickle"] = false; - Janus.debug(callbacks); - config.sdpSent = true; - callbacks.success(config.mySdp); - } - - function getVolume(handleId, remote) { - var pluginHandle = pluginHandles[handleId]; - if(!pluginHandle || !pluginHandle.webrtcStuff) { - Janus.warn("Invalid handle"); - return 0; - } - var stream = remote ? "remote" : "local"; - var config = pluginHandle.webrtcStuff; - if(!config.volume[stream]) - config.volume[stream] = { value: 0 }; - // Start getting the volume, if audioLevel in getStats is supported (apparently - // they're only available in Chrome/Safari right now: https://webrtc-stats.callstats.io/) - if(config.pc.getStats && (Janus.webRTCAdapter.browserDetails.browser === "chrome" || - Janus.webRTCAdapter.browserDetails.browser === "safari")) { - if(remote && !config.remoteStream) { - Janus.warn("Remote stream unavailable"); - return 0; - } else if(!remote && !config.myStream) { - Janus.warn("Local stream unavailable"); - return 0; - } - if(!config.volume[stream].timer) { - Janus.log("Starting " + stream + " volume monitor"); - config.volume[stream].timer = setInterval(function() { - config.pc.getStats() - .then(function(stats) { - stats.forEach(function (res) { - if(!res || res.kind !== "audio") - return; - if((remote && !res.remoteSource) || (!remote && res.type !== "media-source")) - return; - config.volume[stream].value = (res.audioLevel ? res.audioLevel : 0); - }); - }); - }, 200); - return 0; // We don't have a volume to return yet - } - return config.volume[stream].value; - } else { - // audioInputLevel and audioOutputLevel seem only available in Chrome? audioLevel - // seems to be available on Chrome and Firefox, but they don't seem to work - Janus.warn("Getting the " + stream + " volume unsupported by browser"); - return 0; - } - } - - function isMuted(handleId, video) { - var pluginHandle = pluginHandles[handleId]; - if(!pluginHandle || !pluginHandle.webrtcStuff) { - Janus.warn("Invalid handle"); - return true; - } - var config = pluginHandle.webrtcStuff; - if(!config.pc) { - Janus.warn("Invalid PeerConnection"); - return true; - } - if(!config.myStream) { - Janus.warn("Invalid local MediaStream"); - return true; - } - if(video) { - // Check video track - if(!config.myStream.getVideoTracks() || config.myStream.getVideoTracks().length === 0) { - Janus.warn("No video track"); - return true; - } - return !config.myStream.getVideoTracks()[0].enabled; - } else { - // Check audio track - if(!config.myStream.getAudioTracks() || config.myStream.getAudioTracks().length === 0) { - Janus.warn("No audio track"); - return true; - } - return !config.myStream.getAudioTracks()[0].enabled; - } - } - - function mute(handleId, video, mute) { - var pluginHandle = pluginHandles[handleId]; - if(!pluginHandle || !pluginHandle.webrtcStuff) { - Janus.warn("Invalid handle"); - return false; - } - var config = pluginHandle.webrtcStuff; - if(!config.pc) { - Janus.warn("Invalid PeerConnection"); - return false; - } - if(!config.myStream) { - Janus.warn("Invalid local MediaStream"); - return false; - } - if(video) { - // Mute/unmute video track - if(!config.myStream.getVideoTracks() || config.myStream.getVideoTracks().length === 0) { - Janus.warn("No video track"); - return false; - } - config.myStream.getVideoTracks()[0].enabled = !mute; - return true; - } else { - // Mute/unmute audio track - if(!config.myStream.getAudioTracks() || config.myStream.getAudioTracks().length === 0) { - Janus.warn("No audio track"); - return false; - } - config.myStream.getAudioTracks()[0].enabled = !mute; - return true; - } - } - - function getBitrate(handleId) { - var pluginHandle = pluginHandles[handleId]; - if(!pluginHandle || !pluginHandle.webrtcStuff) { - Janus.warn("Invalid handle"); - return "Invalid handle"; - } - var config = pluginHandle.webrtcStuff; - if(!config.pc) - return "Invalid PeerConnection"; - // Start getting the bitrate, if getStats is supported - if(config.pc.getStats) { - if(!config.bitrate.timer) { - Janus.log("Starting bitrate timer (via getStats)"); - config.bitrate.timer = setInterval(function() { - config.pc.getStats() - .then(function(stats) { - stats.forEach(function (res) { - if(!res) - return; - var inStats = false; - // Check if these are statistics on incoming media - if((res.mediaType === "video" || res.id.toLowerCase().indexOf("video") > -1) && - res.type === "inbound-rtp" && res.id.indexOf("rtcp") < 0) { - // New stats - inStats = true; - } else if(res.type == 'ssrc' && res.bytesReceived && - (res.googCodecName === "VP8" || res.googCodecName === "")) { - // Older Chromer versions - inStats = true; - } - // Parse stats now - if(inStats) { - config.bitrate.bsnow = res.bytesReceived; - config.bitrate.tsnow = res.timestamp; - if(config.bitrate.bsbefore === null || config.bitrate.tsbefore === null) { - // Skip this round - config.bitrate.bsbefore = config.bitrate.bsnow; - config.bitrate.tsbefore = config.bitrate.tsnow; - } else { - // Calculate bitrate - var timePassed = config.bitrate.tsnow - config.bitrate.tsbefore; - if(Janus.webRTCAdapter.browserDetails.browser === "safari") - timePassed = timePassed/1000; // Apparently the timestamp is in microseconds, in Safari - var bitRate = Math.round((config.bitrate.bsnow - config.bitrate.bsbefore) * 8 / timePassed); - if(Janus.webRTCAdapter.browserDetails.browser === "safari") - bitRate = parseInt(bitRate/1000); - config.bitrate.value = bitRate + ' kbits/sec'; - //~ Janus.log("Estimated bitrate is " + config.bitrate.value); - config.bitrate.bsbefore = config.bitrate.bsnow; - config.bitrate.tsbefore = config.bitrate.tsnow; - } - } - }); - }); - }, 1000); - return "0 kbits/sec"; // We don't have a bitrate value yet - } - return config.bitrate.value; - } else { - Janus.warn("Getting the video bitrate unsupported by browser"); - return "Feature unsupported by browser"; - } - } - - function webrtcError(error) { - Janus.error("WebRTC error:", error); - } - - function cleanupWebrtc(handleId, hangupRequest) { - Janus.log("Cleaning WebRTC stuff"); - var pluginHandle = pluginHandles[handleId]; - if(!pluginHandle) { - // Nothing to clean - return; - } - var config = pluginHandle.webrtcStuff; - if(config) { - if(hangupRequest === true) { - // Send a hangup request (we don't really care about the response) - var request = { "janus": "hangup", "transaction": Janus.randomString(12) }; - if(pluginHandle.token) - request["token"] = pluginHandle.token; - if(apisecret) - request["apisecret"] = apisecret; - Janus.debug("Sending hangup request (handle=" + handleId + "):"); - Janus.debug(request); - if(websockets) { - request["session_id"] = sessionId; - request["handle_id"] = handleId; - ws.send(JSON.stringify(request)); - } else { - Janus.httpAPICall(server + "/" + sessionId + "/" + handleId, { - verb: 'POST', - withCredentials: withCredentials, - body: request - }); - } - } - // Cleanup stack - config.remoteStream = null; - if(config.volume) { - if(config.volume["local"] && config.volume["local"].timer) - clearInterval(config.volume["local"].timer); - if(config.volume["remote"] && config.volume["remote"].timer) - clearInterval(config.volume["remote"].timer); - } - config.volume = {}; - if(config.bitrate.timer) - clearInterval(config.bitrate.timer); - config.bitrate.timer = null; - config.bitrate.bsnow = null; - config.bitrate.bsbefore = null; - config.bitrate.tsnow = null; - config.bitrate.tsbefore = null; - config.bitrate.value = null; - if(!config.streamExternal && config.myStream) { - Janus.log("Stopping local stream tracks"); - Janus.stopAllTracks(config.myStream); - } - config.streamExternal = false; - config.myStream = null; - // Close PeerConnection - try { - config.pc.close(); - } catch(e) { - // Do nothing - } - config.pc = null; - config.candidates = null; - config.mySdp = null; - config.remoteSdp = null; - config.iceDone = false; - config.dataChannel = {}; - config.dtmfSender = null; - config.senderTransforms = null; - config.receiverTransforms = null; - } - pluginHandle.oncleanup(); - } - - // Helper method to munge an SDP to enable simulcasting (Chrome only) - function mungeSdpForSimulcasting(sdp) { - // Let's munge the SDP to add the attributes for enabling simulcasting - // (based on https://gist.github.com/ggarber/a19b4c33510028b9c657) - var lines = sdp.split("\r\n"); - var video = false; - var ssrc = [ -1 ], ssrc_fid = [ -1 ]; - var cname = null, msid = null, mslabel = null, label = null; - var insertAt = -1; - for(let i=0; i -1) { - // We're done, let's add the new attributes here - insertAt = i; - break; - } - } - continue; - } - if(!video) - continue; - var sim = lines[i].match(/a=ssrc-group:SIM (\d+) (\d+) (\d+)/); - if(sim) { - Janus.warn("The SDP already contains a SIM attribute, munging will be skipped"); - return sdp; - } - var fid = lines[i].match(/a=ssrc-group:FID (\d+) (\d+)/); - if(fid) { - ssrc[0] = fid[1]; - ssrc_fid[0] = fid[2]; - lines.splice(i, 1); i--; - continue; - } - if(ssrc[0]) { - var match = lines[i].match('a=ssrc:' + ssrc[0] + ' cname:(.+)') - if(match) { - cname = match[1]; - } - match = lines[i].match('a=ssrc:' + ssrc[0] + ' msid:(.+)') - if(match) { - msid = match[1]; - } - match = lines[i].match('a=ssrc:' + ssrc[0] + ' mslabel:(.+)') - if(match) { - mslabel = match[1]; - } - match = lines[i].match('a=ssrc:' + ssrc[0] + ' label:(.+)') - if(match) { - label = match[1]; - } - if(lines[i].indexOf('a=ssrc:' + ssrc_fid[0]) === 0) { - lines.splice(i, 1); i--; - continue; - } - if(lines[i].indexOf('a=ssrc:' + ssrc[0]) === 0) { - lines.splice(i, 1); i--; - continue; - } - } - if(lines[i].length == 0) { - lines.splice(i, 1); i--; - continue; - } - } - if(ssrc[0] < 0) { - // Couldn't find a FID attribute, let's just take the first video SSRC we find - insertAt = -1; - video = false; - for(let i=0; i -1) { - // We're done, let's add the new attributes here - insertAt = i; - break; - } - } - continue; - } - if(!video) - continue; - if(ssrc[0] < 0) { - var value = lines[i].match(/a=ssrc:(\d+)/); - if(value) { - ssrc[0] = value[1]; - lines.splice(i, 1); i--; - continue; - } - } else { - let match = lines[i].match('a=ssrc:' + ssrc[0] + ' cname:(.+)') - if(match) { - cname = match[1]; - } - match = lines[i].match('a=ssrc:' + ssrc[0] + ' msid:(.+)') - if(match) { - msid = match[1]; - } - match = lines[i].match('a=ssrc:' + ssrc[0] + ' mslabel:(.+)') - if(match) { - mslabel = match[1]; - } - match = lines[i].match('a=ssrc:' + ssrc[0] + ' label:(.+)') - if(match) { - label = match[1]; - } - if(lines[i].indexOf('a=ssrc:' + ssrc_fid[0]) === 0) { - lines.splice(i, 1); i--; - continue; - } - if(lines[i].indexOf('a=ssrc:' + ssrc[0]) === 0) { - lines.splice(i, 1); i--; - continue; - } - } - if(lines[i].length === 0) { - lines.splice(i, 1); i--; - continue; - } - } - } - if(ssrc[0] < 0) { - // Still nothing, let's just return the SDP we were asked to munge - Janus.warn("Couldn't find the video SSRC, simulcasting NOT enabled"); - return sdp; - } - if(insertAt < 0) { - // Append at the end - insertAt = lines.length; - } - // Generate a couple of SSRCs (for retransmissions too) - // Note: should we check if there are conflicts, here? - ssrc[1] = Math.floor(Math.random()*0xFFFFFFFF); - ssrc[2] = Math.floor(Math.random()*0xFFFFFFFF); - ssrc_fid[1] = Math.floor(Math.random()*0xFFFFFFFF); - ssrc_fid[2] = Math.floor(Math.random()*0xFFFFFFFF); - // Add attributes to the SDP - for(var i=0; i