diff --git a/.adxstudio/workspace.json b/.adxstudio/workspace.json index f895eb3..46e78d1 100644 --- a/.adxstudio/workspace.json +++ b/.adxstudio/workspace.json @@ -1 +1 @@ -{"tabs":[{"id":"38de036d-07f0-462b-be8f-63dc5c14ae61","pane":"main","path":"C:\\Users\\Quentin\\Documents\\ADC_Microphone\\resources\\dynamic\\open.html","type":"file"},{"id":"3c6b6cc6-b5cb-407e-8788-f42094e28da4","pane":"main","path":"C:\\Users\\Quentin\\Documents\\ADC_Microphone\\resources\\static\\adc_microphone.js","type":"file"},{"id":"8ff54ff3-2c8f-47ea-9f07-17bd7bcc8e34","pane":"main","path":"C:\\Users\\Quentin\\Documents\\ADC_Microphone\\resources\\dynamic\\default.css","type":"file"},{"id":"08794c7f-866e-46aa-9171-21b10a3e3d9a","pane":"main","path":"C:\\Users\\Quentin\\Documents\\ADC_Microphone\\resources\\dynamic\\fallback_mobile_open.html","type":"file"},{"id":"a426712c-6257-4fff-9f5a-7e4e243a28f9","pane":"main","path":"C:\\Users\\Quentin\\Documents\\ADC_Microphone\\resources\\dynamic\\fallback_open.html","type":"file"},{"id":"a1fd4492-942a-497e-903c-0e043331ef9b","pane":"main","path":"C:\\Users\\Quentin\\Documents\\GitHub\\MyADCs\\Microphone\\config.xml","type":"projectSettings","mode":"form","adxVersion":"2.0.0","adxType":"adc"}]} \ No newline at end of file +{"tabs":[{"id":"f075c78e-4988-496a-ae35-ca41cf102dc3","pane":"main","current":true,"path":"C:\\Users\\Quentin\\Documents\\ADC_Microphone\\resources\\dynamic\\open.html","type":"file"},{"id":"09b0e1f8-ba00-440b-ade1-b65bb5e99fad","pane":"main","path":"C:\\Users\\Quentin\\Documents\\ADC_Microphone\\resources\\static\\adc_microphone.js","type":"file"},{"id":"8554908c-c63f-4ed4-a6b7-a084fe998987","pane":"main","path":"C:\\Users\\Quentin\\Documents\\ADC_Microphone\\resources\\dynamic\\default.css","type":"file"},{"id":"69f00b3d-6329-45b7-b0a3-d19cb827a7e8","pane":"main","path":"C:\\Users\\Quentin\\Documents\\ADC_Microphone\\resources\\dynamic\\fallback_mobile_open.html","type":"file"},{"id":"21fa85ae-28eb-4a50-b098-54cec0da577a","pane":"main","path":"C:\\Users\\Quentin\\Documents\\ADC_Microphone\\resources\\dynamic\\fallback_open.html","type":"file"},{"id":"f1d8a70b-814a-41c6-b8e0-9df0f4b03601","pane":"main","path":"C:\\Users\\Quentin\\Documents\\GitHub\\MyADCs\\Microphone\\config.xml","type":"projectSettings","mode":"form"},{"id":"ea4b64b6-317e-4aa4-9f7c-48d14d49515c","pane":"main","path":"C:\\Users\\JordanGrindle\\Documents\\GitHub\\Microphone\\resources\\static\\RecordRTC.js","type":"file"},{"id":"e588e8f7-564f-468a-976e-93b1bddcf540","pane":"main","path":"C:\\Users\\JordanGrindle\\Documents\\GitHub\\Microphone\\config.xml","type":"projectSettings","mode":"code","adxVersion":"2.0.0","adxType":"adc"}]} \ No newline at end of file diff --git a/bin/Microphone.adc b/bin/Microphone.adc index 083864c..0bc85f5 100644 Binary files a/bin/Microphone.adc and b/bin/Microphone.adc differ diff --git a/changelog.md b/changelog.md index bf29449..0682898 100644 --- a/changelog.md +++ b/changelog.md @@ -1,4 +1,11 @@ -Version 1.0.0 - -- Feature 1 -- Feature 2 \ No newline at end of file +Audio capture via the microphone + v1.0.1 - change the `stopRecordingAudio` func code to get the promise instead of a blob + v1.0.2 - + v1.0.3 - + v1.0.4 - Fixed, dof runtime errors + v2.0.0 - Fixed issues of the control not running correctly on mobile devices. MIME types not supported. + v2.0.1 - changed button behavior to not be a hidden label. updated RecordRTC library to v5.6.3 + v2.0.2 - changed .catch() error message to a more robust feedback instead of "cannot record" + v2.0.3 - updated WebRTC lib + v2.0.4 - updated missing libraries in config.xml for mobile browser fallback + v2.0.5 - changed to button type instead of input type, changed to addeventhandler instead of onclick listeners \ No newline at end of file diff --git a/config.xml b/config.xml index 825af39..dfcd24c 100644 --- a/config.xml +++ b/config.xml @@ -7,15 +7,11 @@ Microphone d7c94a7c-10f9-4d31-8e51-82dc9c96c089 - 1.0.4 - 2022-10-04 - + 2.0.5 + 2026-04-20 + Askia - ]]> + ]]> http://www.askia.com @@ -60,6 +56,9 @@ + + + @@ -140,7 +139,7 @@ - + @@ -203,4 +202,4 @@ - + \ No newline at end of file diff --git a/example/Microphone.qex b/example/Microphone.qex index 78cf702..f3a99be 100644 Binary files a/example/Microphone.qex and b/example/Microphone.qex differ diff --git a/resources/dynamic/open.html b/resources/dynamic/open.html index bb61e03..3bf9858 100644 --- a/resources/dynamic/open.html +++ b/resources/dynamic/open.html @@ -17,6 +17,7 @@ Dim enabledNextAfterUpload = (CurrentADC.PropValue("nextAfterUpload") = "1") %} + + {%if enabledNextAfterUpload then%} {%EndIf%} - +
-
    -
+
    +
    +
    - - -
    - + + + + +
    + +
    + {%if allowSaveFile then%} - + {%EndIf%} - - + + + + + + + \ No newline at end of file diff --git a/resources/static/RecordRTC.js b/resources/static/RecordRTC.js index 3f71a48..d771c2b 100644 --- a/resources/static/RecordRTC.js +++ b/resources/static/RecordRTC.js @@ -1,9 +1,9 @@ 'use strict'; -// Last time updated: 2019-06-24 7:56:58 AM UTC +// Last time updated: 2024-01-03 12:51:19 PM UTC // ________________ -// RecordRTC v5.5.9 +// RecordRTC v5.6.3 // Open-Sourced: https://github.com/muaz-khan/RecordRTC @@ -781,7 +781,7 @@ function RecordRTC(mediaStream, config) { * @example * alert(recorder.version); */ - version: '5.5.9' + version: '5.6.3' }; if (!this) { @@ -799,7 +799,7 @@ function RecordRTC(mediaStream, config) { return returnObject; } -RecordRTC.version = '5.5.9'; +RecordRTC.version = '5.6.3'; if (typeof module !== 'undefined' /* && !!module.exports*/ ) { module.exports = RecordRTC; @@ -1815,7 +1815,10 @@ function invokeSaveAsDialog(file, fileName) { } var fileExtension = (file.type || 'video/webm').split('/')[1]; - + if (fileExtension.indexOf(';') !== -1) { + // extended mimetype, e.g. 'video/webm;codecs=vp8,opus' + fileExtension = fileExtension.split(';')[0]; + } if (fileName && fileName.indexOf('.') !== -1) { var splitted = fileName.split('.'); fileName = splitted[0]; @@ -2149,7 +2152,7 @@ function MediaStreamRecorder(mediaStream, config) { } if (typeof config.timeSlice === 'number') { - if (e.data && e.data.size && e.data.size > 100) { + if (e.data && e.data.size) { arrayOfBlobs.push(e.data); updateTimeStamp(); @@ -2258,10 +2261,10 @@ function MediaStreamRecorder(mediaStream, config) { updateTimeStamp(); mediaRecorder.start(config.timeSlice); } else { - // default is 60 minutes; enough? + // default is 24 hours; enough? (thanks https://github.com/slidevjs/slidev/pull/488) // use config => {timeSlice: 1000} otherwise - mediaRecorder.start(3.6e+6); + mediaRecorder.start(24 * 60 * 60 * 1000); } if (config.initCallback) { @@ -2756,7 +2759,7 @@ function StereoAudioRecorder(mediaStream, config) { view.setUint32(24, sampleRate, true); // byte rate (sample rate * block align) - view.setUint32(28, sampleRate * 2, true); + view.setUint32(28, sampleRate * numberOfAudioChannels * 2, true); // block align (channel count * bytes per sample) view.setUint16(32, numberOfAudioChannels * 2, true); @@ -2899,18 +2902,18 @@ function StereoAudioRecorder(mediaStream, config) { }); }; - if (typeof Storage === 'undefined') { - var Storage = { + if (typeof RecordRTC.Storage === 'undefined') { + RecordRTC.Storage = { AudioContextConstructor: null, AudioContext: window.AudioContext || window.webkitAudioContext }; } - if (!Storage.AudioContextConstructor) { - Storage.AudioContextConstructor = new Storage.AudioContext(); + if (!RecordRTC.Storage.AudioContextConstructor || RecordRTC.Storage.AudioContextConstructor.state === 'closed') { + RecordRTC.Storage.AudioContextConstructor = new RecordRTC.Storage.AudioContext(); } - var context = Storage.AudioContextConstructor; + var context = RecordRTC.Storage.AudioContextConstructor; // creates an audio node from the microphone incoming stream var audioInput = context.createMediaStreamSource(mediaStream); @@ -4567,9 +4570,7 @@ if (typeof RecordRTC !== 'undefined') { function GifRecorder(mediaStream, config) { if (typeof GIFEncoder === 'undefined') { - var script = document.createElement('script'); - script.src = 'https://www.webrtc-experiment.com/gif-recorder.js'; - (document.body || document.documentElement).appendChild(script); + throw new Error('Missing https://www.webrtc-experiment.com/gif-recorder.js'); } config = config || {}; @@ -4808,6 +4809,7 @@ function GifRecorder(mediaStream, config) { var video = document.createElement('video'); video.muted = true; video.autoplay = true; + video.playsInline = true; isLoadedMetaData = false; video.onloadedmetadata = function() { @@ -5828,7 +5830,7 @@ function RecordRTCPromisesHandler(mediaStream, options) { }; /** - * Destroy RecordRTC instance. Clear all recorders and objects. + * This method returns the internal recording object. * @method * @memberof RecordRTCPromisesHandler * @example @@ -5837,7 +5839,7 @@ function RecordRTCPromisesHandler(mediaStream, options) { * internalRecorder.addStreams([newAudioStream]); * internalRecorder.resetVideoStreams([screenStream]); * } - * @returns {Object} Returns internal recording object. + * @returns {Object} */ this.getInternalRecorder = function() { return new Promise(function(resolve, reject) { @@ -5923,7 +5925,7 @@ function RecordRTCPromisesHandler(mediaStream, options) { * @example * alert(recorder.version); */ - this.version = '5.5.9'; + this.version = '5.6.3'; } if (typeof RecordRTC !== 'undefined') { @@ -5948,7 +5950,7 @@ if (typeof RecordRTC !== 'undefined') { * }); * @see {@link https://github.com/muaz-khan/RecordRTC|RecordRTC Source Code} * @param {MediaStream} mediaStream - MediaStream object fetched using getUserMedia API or generated using captureStreamUntilEnded or WebAudio API. - * @param {object} config - {webAssemblyPath:'webm-wasm.wasm',workerPath: 'webm-worker.js', frameRate: 30, width: 1920, height: 1080, bitrate: 1024} + * @param {object} config - {webAssemblyPath:'webm-wasm.wasm',workerPath: 'webm-worker.js', frameRate: 30, width: 1920, height: 1080, bitrate: 1024, realtime: true} */ function WebAssemblyRecorder(stream, config) { // based on: github.com/GoogleChromeLabs/webm-wasm @@ -5964,6 +5966,7 @@ function WebAssemblyRecorder(stream, config) { config.height = config.height || 480; config.frameRate = config.frameRate || 30; config.bitrate = config.bitrate || 1200; + config.realtime = config.realtime || true; function createBufferURL(buffer, type) { return URL.createObjectURL(new Blob([buffer], { @@ -5971,23 +5974,45 @@ function WebAssemblyRecorder(stream, config) { })); } + var finished; + function cameraStream() { return new ReadableStream({ start: function(controller) { var cvs = document.createElement('canvas'); var video = document.createElement('video'); + var first = true; video.srcObject = stream; + video.muted = true; + video.height = config.height; + video.width = config.width; + video.volume = 0; video.onplaying = function() { cvs.width = config.width; cvs.height = config.height; var ctx = cvs.getContext('2d'); var frameTimeout = 1000 / config.frameRate; - setTimeout(function f() { + var cameraTimer = setInterval(function f() { + if (finished) { + clearInterval(cameraTimer); + controller.close(); + } + + if (first) { + first = false; + if (config.onVideoProcessStarted) { + config.onVideoProcessStarted(); + } + } + ctx.drawImage(video, 0, 0); - controller.enqueue( - ctx.getImageData(0, 0, config.width, config.height) - ); - setTimeout(f, frameTimeout); + if (controller._controlledReadableStream.state !== 'closed') { + try { + controller.enqueue( + ctx.getImageData(0, 0, config.width, config.height) + ); + } catch (e) {} + } }, frameTimeout); }; video.play(); @@ -5999,7 +6024,10 @@ function WebAssemblyRecorder(stream, config) { function startRecording(stream, buffer) { if (!config.workerPath && !buffer) { + finished = false; + // is it safe to use @latest ? + fetch( 'https://unpkg.com/webm-wasm@latest/dist/webm-worker.js' ).then(function(r) { @@ -6031,12 +6059,13 @@ function WebAssemblyRecorder(stream, config) { height: config.height, bitrate: config.bitrate || 1200, timebaseDen: config.frameRate || 30, - realtime: true + realtime: config.realtime }); cameraStream().pipeTo(new WritableStream({ write: function(image) { - if (!worker) { + if (finished) { + console.error('Got image, but recorder is finished!'); return; } @@ -6093,14 +6122,28 @@ function WebAssemblyRecorder(stream, config) { isPaused = false; }; - function terminate() { + function terminate(callback) { if (!worker) { + if (callback) { + callback(); + } + return; } + // Wait for null event data to indicate that the encoding is complete + worker.addEventListener('message', function(event) { + if (event.data === null) { + worker.terminate(); + worker = null; + + if (callback) { + callback(); + } + } + }); + worker.postMessage(null); - worker.terminate(); - worker = null; } var arrayOfBuffers = []; @@ -6116,13 +6159,17 @@ function WebAssemblyRecorder(stream, config) { * }); */ this.stop = function(callback) { - terminate(); + finished = true; - this.blob = new Blob(arrayOfBuffers, { - type: 'video/webm' - }); + var recorder = this; - callback(this.blob); + terminate(function() { + recorder.blob = new Blob(arrayOfBuffers, { + type: 'video/webm' + }); + + callback(recorder.blob); + }); }; // for debugging @@ -6159,4 +6206,4 @@ function WebAssemblyRecorder(stream, config) { if (typeof RecordRTC !== 'undefined') { RecordRTC.WebAssemblyRecorder = WebAssemblyRecorder; -} +} \ No newline at end of file diff --git a/resources/static/adapter-latest.js b/resources/static/adapter-latest.js index 545bf9a..314710a 100644 --- a/resources/static/adapter-latest.js +++ b/resources/static/adapter-latest.js @@ -10,46 +10,27 @@ 'use strict'; -var _adapter_factory = require('./adapter_factory.js'); - -var adapter = (0, _adapter_factory.adapterFactory)({ window: window }); +var _adapter_factory = require("./adapter_factory.js"); +var adapter = (0, _adapter_factory.adapterFactory)({ + window: typeof window === 'undefined' ? undefined : window +}); module.exports = adapter; // this is the difference from adapter_core. },{"./adapter_factory.js":2}],2:[function(require,module,exports){ -'use strict'; +"use strict"; +function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } Object.defineProperty(exports, "__esModule", { value: true }); exports.adapterFactory = adapterFactory; - -var _utils = require('./utils'); - -var utils = _interopRequireWildcard(_utils); - -var _chrome_shim = require('./chrome/chrome_shim'); - -var chromeShim = _interopRequireWildcard(_chrome_shim); - -var _edge_shim = require('./edge/edge_shim'); - -var edgeShim = _interopRequireWildcard(_edge_shim); - -var _firefox_shim = require('./firefox/firefox_shim'); - -var firefoxShim = _interopRequireWildcard(_firefox_shim); - -var _safari_shim = require('./safari/safari_shim'); - -var safariShim = _interopRequireWildcard(_safari_shim); - -var _common_shim = require('./common_shim'); - -var commonShim = _interopRequireWildcard(_common_shim); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } - -// Shimming starts here. +var utils = _interopRequireWildcard(require("./utils")); +var chromeShim = _interopRequireWildcard(require("./chrome/chrome_shim")); +var firefoxShim = _interopRequireWildcard(require("./firefox/firefox_shim")); +var safariShim = _interopRequireWildcard(require("./safari/safari_shim")); +var commonShim = _interopRequireWildcard(require("./common_shim")); +var sdp = _interopRequireWildcard(require("sdp")); +function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, "default": e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); } /* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * @@ -57,27 +38,29 @@ function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; * that can be found in the LICENSE file in the root of the source * tree. */ + +// Browser shims. + +// Shimming starts here. function adapterFactory() { var _ref = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {}, - window = _ref.window; - + window = _ref.window; var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : { shimChrome: true, shimFirefox: true, - shimEdge: 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 + disableWarnings: utils.disableWarnings, + // Expose sdp as a convenience. For production apps include directly. + sdp: sdp }; // Shim browser if found. @@ -87,25 +70,31 @@ function adapterFactory() { 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; - chromeShim.shimGetUserMedia(window); - chromeShim.shimMediaStream(window); - chromeShim.shimPeerConnection(window); - chromeShim.shimOnTrack(window); - chromeShim.shimAddTrackRemoveTrack(window); - chromeShim.shimGetSendersWithDtmf(window); - chromeShim.shimGetStats(window); - chromeShim.shimSenderReceiverGetStats(window); - chromeShim.fixNegotiationNeeded(window); - - commonShim.shimRTCIceCandidate(window); - commonShim.shimConnectionState(window); - commonShim.shimMaxMessageSize(window); - commonShim.shimSendThrowTypeError(window); - commonShim.removeAllowExtmapMixed(window); + // 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.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) { @@ -116,37 +105,25 @@ function adapterFactory() { // Export to the adapter global object visible in the browser. adapter.browserShim = firefoxShim; - firefoxShim.shimGetUserMedia(window); - firefoxShim.shimPeerConnection(window); - firefoxShim.shimOnTrack(window); - firefoxShim.shimRemoveStream(window); - firefoxShim.shimSenderGetStats(window); - firefoxShim.shimReceiverGetStats(window); - firefoxShim.shimRTCDataChannel(window); - - commonShim.shimRTCIceCandidate(window); - commonShim.shimConnectionState(window); - commonShim.shimMaxMessageSize(window); - commonShim.shimSendThrowTypeError(window); - break; - case 'edge': - if (!edgeShim || !edgeShim.shimPeerConnection || !options.shimEdge) { - logging('MS edge shim is not included in this adapter release.'); - return adapter; - } - logging('adapter.js shimming edge.'); - // Export to the adapter global object visible in the browser. - adapter.browserShim = edgeShim; - - edgeShim.shimGetUserMedia(window); - edgeShim.shimGetDisplayMedia(window); - edgeShim.shimPeerConnection(window); - edgeShim.shimReplaceTrack(window); - - // the edge shim implements the full RTCIceCandidate object. - - commonShim.shimMaxMessageSize(window); - commonShim.shimSendThrowTypeError(window); + // Must be called before shimPeerConnection. + commonShim.shimAddIceCandidateNullOrEmpty(window, browserDetails); + commonShim.shimParameterlessSetLocalDescription(window, browserDetails); + firefoxShim.shimGetUserMedia(window, browserDetails); + firefoxShim.shimPeerConnection(window, browserDetails); + firefoxShim.shimGetStats(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) { @@ -157,31 +134,31 @@ function adapterFactory() { // Export to the adapter global object visible in the browser. adapter.browserShim = safariShim; - safariShim.shimRTCIceServerUrls(window); - safariShim.shimCreateOfferLegacy(window); - safariShim.shimCallbacksAPI(window); - safariShim.shimLocalStreamsAPI(window); - safariShim.shimRemoteStreamsAPI(window); - safariShim.shimTrackEventTransceiver(window); - safariShim.shimGetUserMedia(window); - - commonShim.shimRTCIceCandidate(window); - commonShim.shimMaxMessageSize(window); - commonShim.shimSendThrowTypeError(window); - commonShim.removeAllowExtmapMixed(window); + // 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; } -// Browser shims. - -},{"./chrome/chrome_shim":3,"./common_shim":6,"./edge/edge_shim":7,"./firefox/firefox_shim":11,"./safari/safari_shim":14,"./utils":15}],3:[function(require,module,exports){ - +},{"./chrome/chrome_shim":3,"./common_shim":5,"./firefox/firefox_shim":6,"./safari/safari_shim":9,"./utils":10,"sdp":11}],3:[function(require,module,exports){ /* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * @@ -195,51 +172,36 @@ function adapterFactory() { Object.defineProperty(exports, "__esModule", { value: true }); -exports.shimGetDisplayMedia = exports.shimGetUserMedia = undefined; - -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 _getusermedia = require('./getusermedia'); - -Object.defineProperty(exports, 'shimGetUserMedia', { +exports.fixNegotiationNeeded = fixNegotiationNeeded; +exports.shimAddTrackRemoveTrack = shimAddTrackRemoveTrack; +exports.shimAddTrackRemoveTrackWithNative = shimAddTrackRemoveTrackWithNative; +exports.shimGetSendersWithDtmf = shimGetSendersWithDtmf; +Object.defineProperty(exports, "shimGetUserMedia", { enumerable: true, get: function get() { return _getusermedia.shimGetUserMedia; } }); - -var _getdisplaymedia = require('./getdisplaymedia'); - -Object.defineProperty(exports, 'shimGetDisplayMedia', { - enumerable: true, - get: function get() { - return _getdisplaymedia.shimGetDisplayMedia; - } -}); exports.shimMediaStream = shimMediaStream; exports.shimOnTrack = shimOnTrack; -exports.shimGetSendersWithDtmf = shimGetSendersWithDtmf; -exports.shimGetStats = shimGetStats; -exports.shimSenderReceiverGetStats = shimSenderReceiverGetStats; -exports.shimAddTrackRemoveTrackWithNative = shimAddTrackRemoveTrackWithNative; -exports.shimAddTrackRemoveTrack = shimAddTrackRemoveTrack; exports.shimPeerConnection = shimPeerConnection; -exports.fixNegotiationNeeded = fixNegotiationNeeded; - -var _utils = require('../utils.js'); - -var utils = _interopRequireWildcard(_utils); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } - -function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } - +exports.shimSenderReceiverGetStats = shimSenderReceiverGetStats; +var utils = _interopRequireWildcard(require("../utils.js")); +var _getusermedia = require("./getusermedia"); +function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, "default": e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); } +function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } +function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } +function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } +function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } function shimMediaStream(window) { window.MediaStream = window.MediaStream || window.webkitMediaStream; } - -function shimOnTrack(window) { - if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCPeerConnection && !('ontrack' in window.RTCPeerConnection.prototype)) { +function shimOnTrack(window, browserDetails) { + if (browserDetails.version > 102) { + // Unified plan is supported so no need to do anything. + return; + } + if (_typeof(window) === 'object' && window.RTCPeerConnection && !('ontrack' in window.RTCPeerConnection.prototype)) { Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', { get: function get() { return this._ontrack; @@ -250,48 +212,53 @@ function shimOnTrack(window) { } 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 = void 0; + 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 }; + receiver = { + track: te.track + }; } - var event = new Event('track'); event.track = te.track; event.receiver = receiver; - event.transceiver = { receiver: receiver }; + event.transceiver = { + receiver: receiver + }; event.streams = [e.stream]; _this.dispatchEvent(event); }); e.stream.getTracks().forEach(function (track) { - var receiver = void 0; + 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 }; + receiver = { + track: track + }; } var event = new Event('track'); event.track = track; event.receiver = receiver; - event.transceiver = { receiver: receiver }; + event.transceiver = { + receiver: receiver + }; event.streams = [e.stream]; _this.dispatchEvent(event); }); @@ -306,16 +273,19 @@ function shimOnTrack(window) { // to unconditionally wrap the event. utils.wrapPeerConnectionEvent(window, 'track', function (e) { if (!e.transceiver) { - Object.defineProperty(e, 'transceiver', { value: { receiver: e.receiver } }); + Object.defineProperty(e, 'transceiver', { + value: { + receiver: e.receiver + } + }); } return e; }); } } - function shimGetSendersWithDtmf(window) { // Overrides addTrack/removeTrack, depends on shimAddTrackRemoveTrack. - if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCPeerConnection && !('getSenders' in window.RTCPeerConnection.prototype) && 'createDTMFSender' in window.RTCPeerConnection.prototype) { + 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, @@ -348,7 +318,6 @@ function shimGetSendersWithDtmf(window) { } return sender; }; - var origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack; window.RTCPeerConnection.prototype.removeTrack = function removeTrack(sender) { origRemoveTrack.apply(this, arguments); @@ -361,21 +330,17 @@ function shimGetSendersWithDtmf(window) { 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; @@ -386,18 +351,16 @@ function shimGetSendersWithDtmf(window) { } }); }; - } else if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCPeerConnection && 'getSenders' in window.RTCPeerConnection.prototype && 'createDTMFSender' in window.RTCPeerConnection.prototype && window.RTCRtpSender && !('dtmf' in window.RTCRtpSender.prototype)) { + } 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) { @@ -412,82 +375,11 @@ function shimGetSendersWithDtmf(window) { }); } } - -function shimGetStats(window) { - if (!window.RTCPeerConnection) { +function shimSenderReceiverGetStats(window, browserDetails) { + if (browserDetails.version >= 67) { 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 === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCPeerConnection && window.RTCRtpSender && window.RTCRtpReceiver)) { + if (!(_typeof(window) === 'object' && window.RTCPeerConnection && window.RTCRtpSender && window.RTCRtpReceiver)) { return; } @@ -496,16 +388,14 @@ function shimSenderReceiverGetStats(window) { var origGetSenders = window.RTCPeerConnection.prototype.getSenders; if (origGetSenders) { window.RTCPeerConnection.prototype.getSenders = function getSenders() { - var _this6 = this; - + var _this5 = this; var senders = origGetSenders.apply(this, []); senders.forEach(function (sender) { - return sender._pc = _this6; + return sender._pc = _this5; }); return senders; }; } - var origAddTrack = window.RTCPeerConnection.prototype.addTrack; if (origAddTrack) { window.RTCPeerConnection.prototype.addTrack = function addTrack() { @@ -533,11 +423,10 @@ function shimSenderReceiverGetStats(window) { var origGetReceivers = window.RTCPeerConnection.prototype.getReceivers; if (origGetReceivers) { window.RTCPeerConnection.prototype.getReceivers = function getReceivers() { - var _this7 = this; - + var _this6 = this; var receivers = origGetReceivers.apply(this, []); receivers.forEach(function (receiver) { - return receiver._pc = _this7; + return receiver._pc = _this6; }); return receivers; }; @@ -553,7 +442,6 @@ function shimSenderReceiverGetStats(window) { }); }; } - if (!('getStats' in window.RTCRtpSender.prototype && 'getStats' in window.RTCRtpReceiver.prototype)) { return; } @@ -563,9 +451,9 @@ function shimSenderReceiverGetStats(window) { window.RTCPeerConnection.prototype.getStats = function getStats() { if (arguments.length > 0 && arguments[0] instanceof window.MediaStreamTrack) { var track = arguments[0]; - var sender = void 0; - var receiver = void 0; - var err = void 0; + var sender; + var receiver; + var err; this.getSenders().forEach(function (s) { if (s.track === track) { if (sender) { @@ -597,27 +485,23 @@ function shimSenderReceiverGetStats(window) { 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; - + var _this7 = this; this._shimmedLocalStreams = this._shimmedLocalStreams || {}; return Object.keys(this._shimmedLocalStreams).map(function (streamId) { - return _this8._shimmedLocalStreams[streamId][0]; + return _this7._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]; @@ -626,15 +510,12 @@ function shimAddTrackRemoveTrackWithNative(window) { } return sender; }; - var origAddStream = window.RTCPeerConnection.prototype.addStream; window.RTCPeerConnection.prototype.addStream = function addStream(stream) { - var _this9 = this; - + var _this8 = this; this._shimmedLocalStreams = this._shimmedLocalStreams || {}; - stream.getTracks().forEach(function (track) { - var alreadyExists = _this9.getSenders().find(function (s) { + var alreadyExists = _this8.getSenders().find(function (s) { return s.track === track; }); if (alreadyExists) { @@ -648,39 +529,34 @@ function shimAddTrackRemoveTrackWithNative(window) { }); 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; - + var _this9 = this; this._shimmedLocalStreams = this._shimmedLocalStreams || {}; if (sender) { Object.keys(this._shimmedLocalStreams).forEach(function (streamId) { - var idx = _this10._shimmedLocalStreams[streamId].indexOf(sender); + var idx = _this9._shimmedLocalStreams[streamId].indexOf(sender); if (idx !== -1) { - _this10._shimmedLocalStreams[streamId].splice(idx, 1); + _this9._shimmedLocalStreams[streamId].splice(idx, 1); } - if (_this10._shimmedLocalStreams[streamId].length === 1) { - delete _this10._shimmedLocalStreams[streamId]; + if (_this9._shimmedLocalStreams[streamId].length === 1) { + delete _this9._shimmedLocalStreams[streamId]; } }); } return origRemoveTrack.apply(this, arguments); }; } - -function shimAddTrackRemoveTrack(window) { +function shimAddTrackRemoveTrack(window, browserDetails) { if (!window.RTCPeerConnection) { return; } - var browserDetails = utils.detectBrowser(window); // shim addTrack and removeTrack. if (window.RTCPeerConnection.prototype.addTrack && browserDetails.version >= 65) { return shimAddTrackRemoveTrackWithNative(window); @@ -690,24 +566,20 @@ function shimAddTrackRemoveTrack(window) { // to return the original streams. var origGetLocalStreams = window.RTCPeerConnection.prototype.getLocalStreams; window.RTCPeerConnection.prototype.getLocalStreams = function getLocalStreams() { - var _this11 = this; - + var _this0 = this; var nativeStreams = origGetLocalStreams.apply(this); this._reverseStreams = this._reverseStreams || {}; return nativeStreams.map(function (stream) { - return _this11._reverseStreams[stream.id]; + return _this0._reverseStreams[stream.id]; }); }; - var origAddStream = window.RTCPeerConnection.prototype.addStream; window.RTCPeerConnection.prototype.addStream = function addStream(stream) { - var _this12 = this; - + var _this1 = this; this._streams = this._streams || {}; this._reverseStreams = this._reverseStreams || {}; - stream.getTracks().forEach(function (track) { - var alreadyExists = _this12.getSenders().find(function (s) { + var alreadyExists = _this1.getSenders().find(function (s) { return s.track === track; }); if (alreadyExists) { @@ -724,20 +596,16 @@ function shimAddTrackRemoveTrack(window) { } 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; - + var _this10 = this; if (this.signalingState === 'closed') { throw new DOMException('The RTCPeerConnection\'s signalingState is \'closed\'.', 'InvalidStateError'); } @@ -749,14 +617,12 @@ function shimAddTrackRemoveTrack(window) { // [[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]; @@ -769,7 +635,7 @@ function shimAddTrackRemoveTrack(window) { // Trigger ONN async. Promise.resolve().then(function () { - _this13.dispatchEvent(new Event('negotiationneeded')); + _this10.dispatchEvent(new Event('negotiationneeded')); }); } else { var newStream = new window.MediaStream([track]); @@ -811,13 +677,12 @@ function shimAddTrackRemoveTrack(window) { ['createOffer', 'createAnswer'].forEach(function (method) { var nativeMethod = window.RTCPeerConnection.prototype[method]; var methodObj = _defineProperty({}, method, function () { - var _this14 = this; - + var _this11 = 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); + var desc = replaceInternalStreamId(_this11, description); args[0].apply(null, [desc]); }, function (err) { if (args[1]) { @@ -826,12 +691,11 @@ function shimAddTrackRemoveTrack(window) { }, arguments[2]]); } return nativeMethod.apply(this, arguments).then(function (description) { - return replaceInternalStreamId(_this14, description); + return replaceInternalStreamId(_this11, 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) { @@ -853,10 +717,8 @@ function shimAddTrackRemoveTrack(window) { return replaceInternalStreamId(this, description); } }); - window.RTCPeerConnection.prototype.removeTrack = function removeTrack(sender) { - var _this15 = this; - + var _this12 = this; if (this.signalingState === 'closed') { throw new DOMException('The RTCPeerConnection\'s signalingState is \'closed\'.', 'InvalidStateError'); } @@ -872,16 +734,15 @@ function shimAddTrackRemoveTrack(window) { // Search for the native stream the senders track belongs to. this._streams = this._streams || {}; - var stream = void 0; + var stream; Object.keys(this._streams).forEach(function (streamid) { - var hasTrack = _this15._streams[streamid].getTracks().find(function (track) { + var hasTrack = _this12._streams[streamid].getTracks().find(function (track) { return sender.track === track; }); if (hasTrack) { - stream = _this15._streams[streamid]; + stream = _this12._streams[streamid]; } }); - if (stream) { if (stream.getTracks().length === 1) { // if this is the last track of the stream, remove the stream. This @@ -895,10 +756,7 @@ function shimAddTrackRemoveTrack(window) { } }; } - -function shimPeerConnection(window) { - var browserDetails = utils.detectBrowser(window); - +function shimPeerConnection(window, browserDetails) { if (!window.RTCPeerConnection && window.webkitRTCPeerConnection) { // very basic support for old versions. window.RTCPeerConnection = window.webkitRTCPeerConnection; @@ -918,87 +776,26 @@ function shimPeerConnection(window) { window.RTCPeerConnection.prototype[method] = methodObj[method]; }); } - - // support for addIceCandidate(null or undefined) - var nativeAddIceCandidate = window.RTCPeerConnection.prototype.addIceCandidate; - 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 planned for Chrome M77. - if (browserDetails.version < 78 && arguments[0] && arguments[0].candidate === '') { - return Promise.resolve(); - } - return nativeAddIceCandidate.apply(this, arguments); - }; } -function fixNegotiationNeeded(window) { +// Attempt to fix ONN in plan-b mode. +function fixNegotiationNeeded(window, browserDetails) { + if (browserDetails.version > 102) { + // Plan-B is no longer supported. + return; + } utils.wrapPeerConnectionEvent(window, 'negotiationneeded', function (e) { var pc = e.target; - if (pc.signalingState !== 'stable') { - return; + if (browserDetails.version < 72 || pc.getConfiguration && pc.getConfiguration().sdpSemantics === 'plan-b') { + if (pc.signalingState !== 'stable') { + return; + } } return e; }); } -},{"../utils.js":15,"./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){ +},{"../utils.js":10,"./getusermedia":4}],4:[function(require,module,exports){ /* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * @@ -1012,30 +809,18 @@ function shimGetDisplayMedia(window, getSourceId) { Object.defineProperty(exports, "__esModule", { value: true }); - -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; }; - exports.shimGetUserMedia = shimGetUserMedia; - -var _utils = require('../utils.js'); - -var utils = _interopRequireWildcard(_utils); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } - +var utils = _interopRequireWildcard(require("../utils.js")); +function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, "default": e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); } +function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } var logging = utils.log; - -function shimGetUserMedia(window) { +function shimGetUserMedia(window, browserDetails) { var navigator = window && window.navigator; - if (!navigator.mediaDevices) { return; } - - var browserDetails = utils.detectBrowser(window); - var constraintsToChrome_ = function constraintsToChrome_(c) { - if ((typeof c === 'undefined' ? 'undefined' : _typeof(c)) !== 'object' || c.mandatory || c.optional) { + if (_typeof(c) !== 'object' || c.mandatory || c.optional) { return c; } var cc = {}; @@ -1043,7 +828,9 @@ function shimGetUserMedia(window) { if (key === 'require' || key === 'advanced' || key === 'mediaSource') { return; } - var r = _typeof(c[key]) === 'object' ? c[key] : { ideal: c[key] }; + 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; } @@ -1084,7 +871,6 @@ function shimGetUserMedia(window) { } return cc; }; - var shimConstraints_ = function shimConstraints_(constraints, func) { if (browserDetails.version >= 61) { return func(constraints); @@ -1105,12 +891,13 @@ function shimGetUserMedia(window) { if (constraints && _typeof(constraints.video) === 'object') { // Shim facingMode for mobile & surface pro. var face = constraints.video.facingMode; - face = face && ((typeof face === 'undefined' ? 'undefined' : _typeof(face)) === 'object' ? face : { ideal: face }); + 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 = void 0; + var matches; if (face.exact === 'environment' || face.ideal === 'environment') { matches = ['back', 'rear']; } else if (face.exact === 'user' || face.ideal === 'user') { @@ -1131,7 +918,11 @@ function shimGetUserMedia(window) { 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.deviceId = face.exact ? { + exact: dev.deviceId + } : { + ideal: dev.deviceId + }; } constraints.video = constraintsToChrome_(constraints.video); logging('chrome: ' + JSON.stringify(constraints)); @@ -1144,7 +935,6 @@ function shimGetUserMedia(window) { logging('chrome: ' + JSON.stringify(constraints)); return func(constraints); }; - var shimError_ = function shimError_(e) { if (browserDetails.version >= 64) { return e; @@ -1170,7 +960,6 @@ function shimGetUserMedia(window) { } }; }; - var getUserMedia_ = function getUserMedia_(constraints, onSuccess, onError) { shimConstraints_(constraints, function (c) { navigator.webkitGetUserMedia(c, onSuccess, function (e) { @@ -1205,7 +994,7 @@ function shimGetUserMedia(window) { } } -},{"../utils.js":15}],6:[function(require,module,exports){ +},{"../utils.js":10}],5:[function(require,module,exports){ /* * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. * @@ -1219,58 +1008,54 @@ function shimGetUserMedia(window) { Object.defineProperty(exports, "__esModule", { value: true }); - -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; }; - -exports.shimRTCIceCandidate = shimRTCIceCandidate; +exports.removeExtmapAllowMixed = removeExtmapAllowMixed; +exports.shimAddIceCandidateNullOrEmpty = shimAddIceCandidateNullOrEmpty; +exports.shimConnectionState = shimConnectionState; exports.shimMaxMessageSize = shimMaxMessageSize; +exports.shimParameterlessSetLocalDescription = shimParameterlessSetLocalDescription; +exports.shimRTCIceCandidate = shimRTCIceCandidate; +exports.shimRTCIceCandidateRelayProtocol = shimRTCIceCandidateRelayProtocol; exports.shimSendThrowTypeError = shimSendThrowTypeError; -exports.shimConnectionState = shimConnectionState; -exports.removeAllowExtmapMixed = removeAllowExtmapMixed; - -var _sdp = require('sdp'); - -var _sdp2 = _interopRequireDefault(_sdp); - -var _utils = require('./utils'); - -var utils = _interopRequireWildcard(_utils); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - +var _sdp = _interopRequireDefault(require("sdp")); +var utils = _interopRequireWildcard(require("./utils")); +function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, "default": e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); } +function _interopRequireDefault(e) { return e && e.__esModule ? e : { "default": e }; } +function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } 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 === 'undefined' ? 'undefined' : _typeof(args)) === 'object' && args.candidate && args.candidate.indexOf('a=') === 0) { + if (_typeof(args) === 'object' && args.candidate && args.candidate.indexOf('a=') === 0) { args = JSON.parse(JSON.stringify(args)); - args.candidate = args.candidate.substr(2); + 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 = _sdp2.default.parseCandidate(args.candidate); - var augmentedCandidate = Object.assign(nativeCandidate, parsedCandidate); + var parsedCandidate = _sdp["default"].parseCandidate(args.candidate); + for (var key in parsedCandidate) { + if (!(key in nativeCandidate)) { + Object.defineProperty(nativeCandidate, key, { + value: parsedCandidate[key] + }); + } + } - // Add a serializer that does not serialize the extra attributes. - augmentedCandidate.toJSON = function toJSON() { + // Override serializer to not serialize the extra attributes. + nativeCandidate.toJSON = function toJSON() { return { - candidate: augmentedCandidate.candidate, - sdpMid: augmentedCandidate.sdpMid, - sdpMLineIndex: augmentedCandidate.sdpMLineIndex, - usernameFragment: augmentedCandidate.usernameFragment + candidate: nativeCandidate.candidate, + sdpMid: nativeCandidate.sdpMid, + sdpMLineIndex: nativeCandidate.sdpMLineIndex, + usernameFragment: nativeCandidate.usernameFragment }; }; - return augmentedCandidate; + return nativeCandidate; } return new NativeRTCIceCandidate(args); }; @@ -1288,33 +1073,51 @@ function shimRTCIceCandidate(window) { return e; }); } - -function shimMaxMessageSize(window) { - if (!window.RTCPeerConnection) { +function shimRTCIceCandidateRelayProtocol(window) { + if (!window.RTCIceCandidate || window.RTCIceCandidate && 'relayProtocol' in window.RTCIceCandidate.prototype) { return; } - var browserDetails = utils.detectBrowser(window); - if (!('sctp' in window.RTCPeerConnection.prototype)) { - Object.defineProperty(window.RTCPeerConnection.prototype, 'sctp', { - get: function get() { - return typeof this._sctp === 'undefined' ? null : this._sctp; + // 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 = _sdp2.default.splitSections(description.sdp); + var sections = _sdp["default"].splitSections(description.sdp); sections.shift(); return sections.some(function (mediaSection) { - var mLine = _sdp2.default.parseMLine(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+)/); @@ -1325,7 +1128,6 @@ function shimMaxMessageSize(window) { // 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 @@ -1356,7 +1158,6 @@ function shimMaxMessageSize(window) { } 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. @@ -1368,10 +1169,9 @@ function shimMaxMessageSize(window) { if (browserDetails.browser === 'firefox' && browserDetails.version === 57) { maxMessageSize = 65535; } - - var match = _sdp2.default.matchPrefix(description.sdp, 'a=max-message-size:'); + var match = _sdp["default"].matchPrefix(description.sdp, 'a=max-message-size:'); if (match.length > 0) { - maxMessageSize = parseInt(match[0].substr(19), 10); + 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 @@ -1380,7 +1180,6 @@ function shimMaxMessageSize(window) { } return maxMessageSize; }; - var origSetRemoteDescription = window.RTCPeerConnection.prototype.setRemoteDescription; window.RTCPeerConnection.prototype.setRemoteDescription = function setRemoteDescription() { this._sctp = null; @@ -1388,21 +1187,18 @@ function shimMaxMessageSize(window) { // As usual, adapter.js has to do an 'ugly worakaround' // to cover up the mess. if (browserDetails.browser === 'chrome' && browserDetails.version >= 76) { - var _getConfiguration = this.getConfiguration(), - sdpSemantics = _getConfiguration.sdpSemantics; - + 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]); @@ -1414,7 +1210,7 @@ function shimMaxMessageSize(window) { var remoteMMS = getMaxMessageSize(arguments[0], isFirefox); // Determine final maximum message size - var maxMessageSize = void 0; + var maxMessageSize; if (canSendMMS === 0 && remoteMMS === 0) { maxMessageSize = Number.POSITIVE_INFINITY; } else if (canSendMMS === 0 || remoteMMS === 0) { @@ -1433,20 +1229,24 @@ function shimMaxMessageSize(window) { }); this._sctp = sctp; } - return origSetRemoteDescription.apply(this, arguments); }; } - -function shimSendThrowTypeError(window) { +function shimSendThrowTypeError(window, browserDetails) { if (!(window.RTCPeerConnection && 'createDataChannel' in window.RTCPeerConnection.prototype)) { return; } + if (browserDetails.browser === 'chrome' && browserDetails.version > 149) { + // Fixed by https://issues.chromium.org/issues/490588131 + 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 - + if (browserDetails.browser === 'firefox' && browserDetails.version > 60) { + return; + } function wrapDcSend(dc, pc) { var origDataChannelSend = dc.send; dc.send = function send() { @@ -1489,7 +1289,6 @@ function shimConnectionState(window) { checking: 'connecting' }[this.iceConnectionState] || this.iceConnectionState; }, - enumerable: true, configurable: true }); @@ -1506,11 +1305,9 @@ function shimConnectionState(window) { this.addEventListener('connectionstatechange', this._onconnectionstatechange = cb); } }, - enumerable: true, configurable: true }); - ['setLocalDescription', 'setRemoteDescription'].forEach(function (method) { var origMethod = proto[method]; proto[method] = function () { @@ -1530,275 +1327,117 @@ function shimConnectionState(window) { }; }); } - -function removeAllowExtmapMixed(window) { - /* remove a=extmap-allow-mixed for Chrome < M71 */ +function removeExtmapAllowMixed(window, browserDetails) { + /* remove a=extmap-allow-mixed for webrtc.org < M71 */ if (!window.RTCPeerConnection) { return; } - var browserDetails = utils.detectBrowser(window); if (browserDetails.browser === 'chrome' && browserDetails.version >= 71) { return; } + if (browserDetails.browser === 'safari' && browserDetails._safariVersion >= 13.1) { + 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) { - desc.sdp = desc.sdp.split('\n').filter(function (line) { + 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); }; } - -},{"./utils":15,"sdp":17}],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.shimGetDisplayMedia = exports.shimGetUserMedia = undefined; - -var _getusermedia = require('./getusermedia'); - -Object.defineProperty(exports, 'shimGetUserMedia', { - enumerable: true, - get: function get() { - return _getusermedia.shimGetUserMedia; - } -}); - -var _getdisplaymedia = require('./getdisplaymedia'); - -Object.defineProperty(exports, 'shimGetDisplayMedia', { - enumerable: true, - get: function get() { - return _getdisplaymedia.shimGetDisplayMedia; +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; } -}); -exports.shimPeerConnection = shimPeerConnection; -exports.shimReplaceTrack = shimReplaceTrack; - -var _utils = require('../utils'); - -var utils = _interopRequireWildcard(_utils); - -var _filtericeservers = require('./filtericeservers'); - -var _rtcpeerconnectionShim = require('rtcpeerconnection-shim'); - -var _rtcpeerconnectionShim2 = _interopRequireDefault(_rtcpeerconnectionShim); - -function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } - -function shimPeerConnection(window) { - var browserDetails = utils.detectBrowser(window); - - if (window.RTCIceGatherer) { - if (!window.RTCIceCandidate) { - window.RTCIceCandidate = function RTCIceCandidate(args) { - return args; - }; - } - if (!window.RTCSessionDescription) { - window.RTCSessionDescription = function RTCSessionDescription(args) { - return args; - }; - } - // this adds an additional event listener to MediaStrackTrack that signals - // when a tracks enabled property was changed. Workaround for a bug in - // addStream, see below. No longer required in 15025+ - if (browserDetails.version < 15025) { - var origMSTEnabled = Object.getOwnPropertyDescriptor(window.MediaStreamTrack.prototype, 'enabled'); - Object.defineProperty(window.MediaStreamTrack.prototype, 'enabled', { - set: function set(value) { - origMSTEnabled.set.call(this, value); - var ev = new Event('enabled'); - ev.enabled = value; - this.dispatchEvent(ev); - } - }); - } + var nativeAddIceCandidate = window.RTCPeerConnection.prototype.addIceCandidate; + if (!nativeAddIceCandidate || nativeAddIceCandidate.length === 0) { + return; } - - // ORTC defines the DTMF sender a bit different. - // https://github.com/w3c/ortc/issues/714 - if (window.RTCRtpSender && !('dtmf' in window.RTCRtpSender.prototype)) { - Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', { - get: function get() { - if (this._dtmf === undefined) { - if (this.track.kind === 'audio') { - this._dtmf = new window.RTCDtmfSender(this); - } else if (this.track.kind === 'video') { - this._dtmf = null; - } - } - return this._dtmf; + window.RTCPeerConnection.prototype.addIceCandidate = function addIceCandidate() { + if (!arguments[0]) { + if (arguments[1]) { + arguments[1].apply(null); } - }); - } - // Edge currently only implements the RTCDtmfSender, not the - // RTCDTMFSender alias. See http://draft.ortc.org/#rtcdtmfsender2* - if (window.RTCDtmfSender && !window.RTCDTMFSender) { - window.RTCDTMFSender = window.RTCDtmfSender; - } - - var RTCPeerConnectionShim = (0, _rtcpeerconnectionShim2.default)(window, browserDetails.version); - window.RTCPeerConnection = function RTCPeerConnection(config) { - if (config && config.iceServers) { - config.iceServers = (0, _filtericeservers.filterIceServers)(config.iceServers, browserDetails.version); - utils.log('ICE servers after filtering:', config.iceServers); + return Promise.resolve(); } - return new RTCPeerConnectionShim(config); - }; - window.RTCPeerConnection.prototype = RTCPeerConnectionShim.prototype; -} - -function shimReplaceTrack(window) { - // ORTC has replaceTrack -- https://github.com/w3c/ortc/issues/614 - if (window.RTCRtpSender && !('replaceTrack' in window.RTCRtpSender.prototype)) { - window.RTCRtpSender.prototype.replaceTrack = window.RTCRtpSender.prototype.setTrack; - } -} - -},{"../utils":15,"./filtericeservers":8,"./getdisplaymedia":9,"./getusermedia":10,"rtcpeerconnection-shim":16}],8:[function(require,module,exports){ -/* - * Copyright (c) 2018 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.filterIceServers = filterIceServers; - -var _utils = require('../utils'); - -var utils = _interopRequireWildcard(_utils); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } - -// Edge does not like -// 1) stun: filtered after 14393 unless ?transport=udp is present -// 2) turn: that does not have all of turn:host:port?transport=udp -// 3) turn: with ipv6 addresses -// 4) turn: occurring muliple times -function filterIceServers(iceServers, edgeVersion) { - var hasTurn = false; - iceServers = JSON.parse(JSON.stringify(iceServers)); - return iceServers.filter(function (server) { - if (server && (server.urls || server.url)) { - var urls = server.urls || server.url; - if (server.url && !server.urls) { - utils.deprecated('RTCIceServer.url', 'RTCIceServer.urls'); - } - var isString = typeof urls === 'string'; - if (isString) { - urls = [urls]; - } - urls = urls.filter(function (url) { - // filter STUN unconditionally. - if (url.indexOf('stun:') === 0) { - return false; - } - - var validTurn = url.startsWith('turn') && !url.startsWith('turn:[') && url.includes('transport=udp'); - if (validTurn && !hasTurn) { - hasTurn = true; - return true; - } - return validTurn && !hasTurn; - }); - - delete server.url; - server.urls = isString ? urls[0] : urls; - return !!urls.length; + // 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); + }; } -},{"../utils":15}],9:[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) { - if (!('getDisplayMedia' in window.navigator)) { - return; - } - if (!window.navigator.mediaDevices) { +// Note: Make sure to call this ahead of APIs that modify +// setLocalDescription.length +function shimParameterlessSetLocalDescription(window, browserDetails) { + if (!(window.RTCPeerConnection && window.RTCPeerConnection.prototype)) { return; } - if (window.navigator.mediaDevices && 'getDisplayMedia' in window.navigator.mediaDevices) { + var nativeSetLocalDescription = window.RTCPeerConnection.prototype.setLocalDescription; + if (!nativeSetLocalDescription || nativeSetLocalDescription.length === 0) { return; } - window.navigator.mediaDevices.getDisplayMedia = window.navigator.getDisplayMedia.bind(window.navigator); -} - -},{}],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. - */ -/* eslint-env node */ -'use strict'; - -Object.defineProperty(exports, "__esModule", { - value: true -}); -exports.shimGetUserMedia = shimGetUserMedia; -function shimGetUserMedia(window) { - var navigator = window && window.navigator; - - var shimError_ = function shimError_(e) { - return { - name: { PermissionDeniedError: 'NotAllowedError' }[e.name] || e.name, - message: e.message, - constraint: e.constraint, - toString: function toString() { - return this.name; - } + 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 }; - }; - - // getUserMedia error shim. - var origGetUserMedia = navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices); - navigator.mediaDevices.getUserMedia = function (c) { - return origGetUserMedia(c).catch(function (e) { - return Promise.reject(shimError_(e)); + 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]); }); }; } -},{}],11:[function(require,module,exports){ +},{"./utils":10,"sdp":11}],6:[function(require,module,exports){ /* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * @@ -1812,63 +1451,62 @@ function shimGetUserMedia(window) { Object.defineProperty(exports, "__esModule", { value: true }); -exports.shimGetDisplayMedia = exports.shimGetUserMedia = undefined; - -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 _getusermedia = require('./getusermedia'); - -Object.defineProperty(exports, 'shimGetUserMedia', { +exports.shimAddTransceiver = shimAddTransceiver; +exports.shimCreateAnswer = shimCreateAnswer; +exports.shimCreateOffer = shimCreateOffer; +Object.defineProperty(exports, "shimGetDisplayMedia", { enumerable: true, get: function get() { - return _getusermedia.shimGetUserMedia; + return _getdisplaymedia.shimGetDisplayMedia; } }); - -var _getdisplaymedia = require('./getdisplaymedia'); - -Object.defineProperty(exports, 'shimGetDisplayMedia', { +exports.shimGetParameters = shimGetParameters; +exports.shimGetStats = shimGetStats; +Object.defineProperty(exports, "shimGetUserMedia", { enumerable: true, get: function get() { - return _getdisplaymedia.shimGetDisplayMedia; + return _getusermedia.shimGetUserMedia; } }); exports.shimOnTrack = shimOnTrack; exports.shimPeerConnection = shimPeerConnection; -exports.shimSenderGetStats = shimSenderGetStats; +exports.shimRTCDataChannel = shimRTCDataChannel; exports.shimReceiverGetStats = shimReceiverGetStats; exports.shimRemoveStream = shimRemoveStream; -exports.shimRTCDataChannel = shimRTCDataChannel; - -var _utils = require('../utils'); - -var utils = _interopRequireWildcard(_utils); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } - -function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } - +exports.shimSenderGetStats = shimSenderGetStats; +var utils = _interopRequireWildcard(require("../utils")); +var _getusermedia = require("./getusermedia"); +var _getdisplaymedia = require("./getdisplaymedia"); +function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, "default": e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); } +function _toConsumableArray(r) { return _arrayWithoutHoles(r) || _iterableToArray(r) || _unsupportedIterableToArray(r) || _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(r, a) { if (r) { if ("string" == typeof r) return _arrayLikeToArray(r, a); var t = {}.toString.call(r).slice(8, -1); return "Object" === t && r.constructor && (t = r.constructor.name), "Map" === t || "Set" === t ? Array.from(r) : "Arguments" === t || /^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(t) ? _arrayLikeToArray(r, a) : void 0; } } +function _iterableToArray(r) { if ("undefined" != typeof Symbol && null != r[Symbol.iterator] || null != r["@@iterator"]) return Array.from(r); } +function _arrayWithoutHoles(r) { if (Array.isArray(r)) return _arrayLikeToArray(r); } +function _arrayLikeToArray(r, a) { (null == a || a > r.length) && (a = r.length); for (var e = 0, n = Array(a); e < a; e++) n[e] = r[e]; return n; } +function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } +function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } +function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } +function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } function shimOnTrack(window) { - if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCTrackEvent && 'receiver' in window.RTCTrackEvent.prototype && !('transceiver' in window.RTCTrackEvent.prototype)) { + 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 }; + return { + receiver: this.receiver + }; } }); } } - -function shimPeerConnection(window) { - var browserDetails = utils.detectBrowser(window); - - if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) !== 'object' || !(window.RTCPeerConnection || window.mozRTCPeerConnection)) { +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) { @@ -1880,27 +1518,15 @@ function shimPeerConnection(window) { window.RTCPeerConnection.prototype[method] = methodObj[method]; }); } - - // support for addIceCandidate(null or undefined) - // as well as ignoring {sdpMid, candidate: ""} - if (browserDetails.version < 68) { - var nativeAddIceCandidate = window.RTCPeerConnection.prototype.addIceCandidate; - 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. - if (arguments[0] && arguments[0].candidate === '') { - return Promise.resolve(); - } - return nativeAddIceCandidate.apply(this, arguments); - }; +} +function shimGetStats(window, browserDetails) { + if (_typeof(window) !== 'object' || !(window.RTCPeerConnection || window.mozRTCPeerConnection)) { + return; // probably media.peerconnection.enabled=false in about:config + } + if (browserDetails.version >= 151) { + // https://bugzilla.mozilla.org/show_bug.cgi?id=1056433 + return; } - var modernStatsTypes = { inboundrtp: 'inbound-rtp', outboundrtp: 'outbound-rtp', @@ -1908,14 +1534,16 @@ function shimPeerConnection(window) { 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]; - + selector = _arguments[0], + onSucc = _arguments[1], + onErr = _arguments[2]; + if (this.signalingState === 'closed') { + // No longer required in FF151+ + return Promise.resolve(new Map()); + } return nativeGetStats.apply(this, [selector || null]).then(function (stats) { if (browserDetails.version < 53 && !onSucc) { // Shim only promise getStats with spec-hyphens in type names @@ -1940,9 +1568,8 @@ function shimPeerConnection(window) { }).then(onSucc, onErr); }; } - function shimSenderGetStats(window) { - if (!((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCPeerConnection && window.RTCRtpSender)) { + if (!(_typeof(window) === 'object' && window.RTCPeerConnection && window.RTCRtpSender)) { return; } if (window.RTCRtpSender && 'getStats' in window.RTCRtpSender.prototype) { @@ -1952,7 +1579,6 @@ function shimSenderGetStats(window) { if (origGetSenders) { window.RTCPeerConnection.prototype.getSenders = function getSenders() { var _this = this; - var senders = origGetSenders.apply(this, []); senders.forEach(function (sender) { return sender._pc = _this; @@ -1960,7 +1586,6 @@ function shimSenderGetStats(window) { return senders; }; } - var origAddTrack = window.RTCPeerConnection.prototype.addTrack; if (origAddTrack) { window.RTCPeerConnection.prototype.addTrack = function addTrack() { @@ -1973,9 +1598,8 @@ function shimSenderGetStats(window) { return this.track ? this._pc.getStats(this.track) : Promise.resolve(new Map()); }; } - function shimReceiverGetStats(window) { - if (!((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCPeerConnection && window.RTCRtpSender)) { + if (!(_typeof(window) === 'object' && window.RTCPeerConnection && window.RTCRtpSender)) { return; } if (window.RTCRtpSender && 'getStats' in window.RTCRtpReceiver.prototype) { @@ -1985,7 +1609,6 @@ function shimReceiverGetStats(window) { if (origGetReceivers) { window.RTCPeerConnection.prototype.getReceivers = function getReceivers() { var _this2 = this; - var receivers = origGetReceivers.apply(this, []); receivers.forEach(function (receiver) { return receiver._pc = _this2; @@ -2001,14 +1624,12 @@ function shimReceiverGetStats(window) { 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)) { @@ -2017,7 +1638,6 @@ function shimRemoveStream(window) { }); }; } - function shimRTCDataChannel(window) { // rename DataChannel to RTCDataChannel (native fix in FF60): // https://bugzilla.mozilla.org/show_bug.cgi?id=1173851 @@ -2025,39 +1645,164 @@ function shimRTCDataChannel(window) { window.RTCDataChannel = window.DataChannel; } } - -},{"../utils":15,"./getdisplaymedia":12,"./getusermedia":13}],12:[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) { +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; } - window.navigator.mediaDevices.getDisplayMedia = function getDisplayMedia(constraints) { - if (!(constraints && constraints.video)) { - var err = new DOMException('getDisplayMedia without video ' + 'constraints is undefined'); + 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":10,"./getdisplaymedia":7,"./getusermedia":8}],7:[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 }; + constraints.video = { + mediaSource: preferredMediaSource + }; } else { constraints.video.mediaSource = preferredMediaSource; } @@ -2065,7 +1810,7 @@ function shimGetDisplayMedia(window, preferredMediaSource) { }; } -},{}],13:[function(require,module,exports){ +},{}],8:[function(require,module,exports){ /* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * @@ -2079,28 +1824,18 @@ function shimGetDisplayMedia(window, preferredMediaSource) { Object.defineProperty(exports, "__esModule", { value: true }); - -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; }; - exports.shimGetUserMedia = shimGetUserMedia; - -var _utils = require('../utils'); - -var utils = _interopRequireWildcard(_utils); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } - -function shimGetUserMedia(window) { - var browserDetails = utils.detectBrowser(window); +var utils = _interopRequireWildcard(require("../utils")); +function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, "default": e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); } +function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } +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)) { @@ -2108,17 +1843,15 @@ function shimGetUserMedia(window) { delete obj[a]; } }; - var nativeGetUserMedia = navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices); navigator.mediaDevices.getUserMedia = function (c) { - if ((typeof c === 'undefined' ? 'undefined' : _typeof(c)) === 'object' && _typeof(c.audio) === 'object') { + 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 () { @@ -2128,11 +1861,10 @@ function shimGetUserMedia(window) { return obj; }; } - if (MediaStreamTrack && MediaStreamTrack.prototype.applyConstraints) { var nativeApplyConstraints = MediaStreamTrack.prototype.applyConstraints; MediaStreamTrack.prototype.applyConstraints = function (c) { - if (this.kind === 'audio' && (typeof c === 'undefined' ? 'undefined' : _typeof(c)) === 'object') { + if (this.kind === 'audio' && _typeof(c) === 'object') { c = JSON.parse(JSON.stringify(c)); remap(c, 'autoGainControl', 'mozAutoGainControl'); remap(c, 'noiseSuppression', 'mozNoiseSuppression'); @@ -2143,7 +1875,7 @@ function shimGetUserMedia(window) { } } -},{"../utils":15}],14:[function(require,module,exports){ +},{"../utils":10}],9:[function(require,module,exports){ /* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * @@ -2156,26 +1888,20 @@ function shimGetUserMedia(window) { Object.defineProperty(exports, "__esModule", { value: true }); - -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; }; - -exports.shimLocalStreamsAPI = shimLocalStreamsAPI; -exports.shimRemoteStreamsAPI = shimRemoteStreamsAPI; +exports.shimAudioContext = shimAudioContext; exports.shimCallbacksAPI = shimCallbacksAPI; -exports.shimGetUserMedia = shimGetUserMedia; exports.shimConstraints = shimConstraints; +exports.shimCreateOfferLegacy = shimCreateOfferLegacy; +exports.shimGetUserMedia = shimGetUserMedia; +exports.shimLocalStreamsAPI = shimLocalStreamsAPI; exports.shimRTCIceServerUrls = shimRTCIceServerUrls; +exports.shimRemoteStreamsAPI = shimRemoteStreamsAPI; exports.shimTrackEventTransceiver = shimTrackEventTransceiver; -exports.shimCreateOfferLegacy = shimCreateOfferLegacy; - -var _utils = require('../utils'); - -var utils = _interopRequireWildcard(_utils); - -function _interopRequireWildcard(obj) { if (obj && obj.__esModule) { return obj; } else { var newObj = {}; if (obj != null) { for (var key in obj) { if (Object.prototype.hasOwnProperty.call(obj, key)) newObj[key] = obj[key]; } } newObj.default = obj; return newObj; } } - +var utils = _interopRequireWildcard(require("../utils")); +function _interopRequireWildcard(e, t) { if ("function" == typeof WeakMap) var r = new WeakMap(), n = new WeakMap(); return (_interopRequireWildcard = function _interopRequireWildcard(e, t) { if (!t && e && e.__esModule) return e; var o, i, f = { __proto__: null, "default": e }; if (null === e || "object" != _typeof(e) && "function" != typeof e) return f; if (o = t ? n : r) { if (o.has(e)) return o.get(e); o.set(e, f); } for (var _t in e) "default" !== _t && {}.hasOwnProperty.call(e, _t) && ((i = (o = Object.defineProperty) && Object.getOwnPropertyDescriptor(e, _t)) && (i.get || i.set) ? o(f, _t, i) : f[_t] = e[_t]); return f; })(e, t); } +function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } function shimLocalStreamsAPI(window) { - if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) !== 'object' || !window.RTCPeerConnection) { + if (_typeof(window) !== 'object' || !window.RTCPeerConnection) { return; } if (!('getLocalStreams' in window.RTCPeerConnection.prototype)) { @@ -2190,7 +1916,6 @@ function shimLocalStreamsAPI(window) { var _addTrack = window.RTCPeerConnection.prototype.addTrack; window.RTCPeerConnection.prototype.addStream = function addStream(stream) { var _this = this; - if (!this._localStreams) { this._localStreams = []; } @@ -2206,23 +1931,26 @@ function shimLocalStreamsAPI(window) { return _addTrack.call(_this, track, stream); }); }; - window.RTCPeerConnection.prototype.addTrack = function addTrack(track) { - var stream = arguments[1]; - if (stream) { - if (!this._localStreams) { - this._localStreams = [stream]; - } else if (!this._localStreams.includes(stream)) { - this._localStreams.push(stream); - } + 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 _this2 = this; - + var _this3 = this; if (!this._localStreams) { this._localStreams = []; } @@ -2234,15 +1962,14 @@ function shimLocalStreamsAPI(window) { var tracks = stream.getTracks(); this.getSenders().forEach(function (sender) { if (tracks.includes(sender.track)) { - _this2.removeTrack(sender); + _this3.removeTrack(sender); } }); }; } } - function shimRemoteStreamsAPI(window) { - if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) !== 'object' || !window.RTCPeerConnection) { + if (_typeof(window) !== 'object' || !window.RTCPeerConnection) { return; } if (!('getRemoteStreams' in window.RTCPeerConnection.prototype)) { @@ -2256,8 +1983,7 @@ function shimRemoteStreamsAPI(window) { return this._onaddstream; }, set: function set(f) { - var _this3 = this; - + var _this4 = this; if (this._onaddstream) { this.removeEventListener('addstream', this._onaddstream); this.removeEventListener('track', this._onaddstreampoly); @@ -2265,16 +1991,16 @@ function shimRemoteStreamsAPI(window) { this.addEventListener('addstream', this._onaddstream = f); this.addEventListener('track', this._onaddstreampoly = function (e) { e.streams.forEach(function (stream) { - if (!_this3._remoteStreams) { - _this3._remoteStreams = []; + if (!_this4._remoteStreams) { + _this4._remoteStreams = []; } - if (_this3._remoteStreams.includes(stream)) { + if (_this4._remoteStreams.includes(stream)) { return; } - _this3._remoteStreams.push(stream); + _this4._remoteStreams.push(stream); var event = new Event('addstream'); event.stream = stream; - _this3.dispatchEvent(event); + _this4.dispatchEvent(event); }); }); } @@ -2302,9 +2028,8 @@ function shimRemoteStreamsAPI(window) { }; } } - function shimCallbacksAPI(window) { - if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) !== 'object' || !window.RTCPeerConnection) { + if (_typeof(window) !== 'object' || !window.RTCPeerConnection) { return; } var prototype = window.RTCPeerConnection.prototype; @@ -2313,7 +2038,6 @@ function shimCallbacksAPI(window) { 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]); @@ -2323,7 +2047,6 @@ function shimCallbacksAPI(window) { 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]); @@ -2333,7 +2056,6 @@ function shimCallbacksAPI(window) { promise.then(successCallback, failureCallback); return Promise.resolve(); }; - var withCallback = function withCallback(description, successCallback, failureCallback) { var promise = setLocalDescription.apply(this, [description]); if (!failureCallback) { @@ -2343,7 +2065,6 @@ function shimCallbacksAPI(window) { return Promise.resolve(); }; prototype.setLocalDescription = withCallback; - withCallback = function withCallback(description, successCallback, failureCallback) { var promise = setRemoteDescription.apply(this, [description]); if (!failureCallback) { @@ -2353,7 +2074,6 @@ function shimCallbacksAPI(window) { return Promise.resolve(); }; prototype.setRemoteDescription = withCallback; - withCallback = function withCallback(candidate, successCallback, failureCallback) { var promise = addIceCandidate.apply(this, [candidate]); if (!failureCallback) { @@ -2364,10 +2084,8 @@ function shimCallbacksAPI(window) { }; 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; @@ -2376,23 +2094,24 @@ function shimGetUserMedia(window) { 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 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) { @@ -2400,7 +2119,7 @@ function shimRTCIceServerUrls(window) { var newIceServers = []; for (var i = 0; i < pcConfig.iceServers.length; i++) { var server = pcConfig.iceServers[i]; - if (!server.hasOwnProperty('urls') && server.hasOwnProperty('url')) { + if (server.urls === undefined && server.url) { utils.deprecated('RTCIceServer.url', 'RTCIceServer.urls'); server = JSON.parse(JSON.stringify(server)); server.urls = server.url; @@ -2416,7 +2135,7 @@ function shimRTCIceServerUrls(window) { }; window.RTCPeerConnection.prototype = OrigPeerConnection.prototype; // wrap static methods. Currently just generateCertificate. - if ('generateCertificate' in window.RTCPeerConnection) { + if ('generateCertificate' in OrigPeerConnection) { Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', { get: function get() { return OrigPeerConnection.generateCertificate; @@ -2424,18 +2143,18 @@ function shimRTCIceServerUrls(window) { }); } } - function shimTrackEventTransceiver(window) { // Add event.transceiver member over deprecated event.receiver - if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCTrackEvent && 'receiver' in window.RTCTrackEvent.prototype && !('transceiver' in window.RTCTrackEvent.prototype)) { + 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 }; + return { + receiver: this.receiver + }; } }); } } - function shimCreateOfferLegacy(window) { var origCreateOffer = window.RTCPeerConnection.prototype.createOffer; window.RTCPeerConnection.prototype.createOffer = function createOffer(offerOptions) { @@ -2462,9 +2181,10 @@ function shimCreateOfferLegacy(window) { } } } else if (offerOptions.offerToReceiveAudio === true && !audioTransceiver) { - this.addTransceiver('audio'); + this.addTransceiver('audio', { + direction: 'recvonly' + }); } - if (typeof offerOptions.offerToReceiveVideo !== 'undefined') { // support bit values offerOptions.offerToReceiveVideo = !!offerOptions.offerToReceiveVideo; @@ -2487,14 +2207,22 @@ function shimCreateOfferLegacy(window) { } } } else if (offerOptions.offerToReceiveVideo === true && !videoTransceiver) { - this.addTransceiver('video'); + 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":15}],15:[function(require,module,exports){ +},{"../utils":10}],10:[function(require,module,exports){ /* * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. * @@ -2508,22 +2236,20 @@ function shimCreateOfferLegacy(window) { Object.defineProperty(exports, "__esModule", { value: true }); - -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; }; - -exports.extractVersion = extractVersion; -exports.wrapPeerConnectionEvent = wrapPeerConnectionEvent; +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.deprecated = deprecated; -exports.detectBrowser = detectBrowser; -exports.compactObject = compactObject; exports.walkStats = walkStats; -exports.filterStats = filterStats; - -function _defineProperty(obj, key, value) { if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; } - +exports.wrapPeerConnectionEvent = wrapPeerConnectionEvent; +function _defineProperty(e, r, t) { return (r = _toPropertyKey(r)) in e ? Object.defineProperty(e, r, { value: t, enumerable: !0, configurable: !0, writable: !0 }) : e[r] = t, e; } +function _toPropertyKey(t) { var i = _toPrimitive(t, "string"); return "symbol" == _typeof(i) ? i : i + ""; } +function _toPrimitive(t, r) { if ("object" != _typeof(t) || !t) return t; var e = t[Symbol.toPrimitive]; if (void 0 !== e) { var i = e.call(t, r || "default"); if ("object" != _typeof(i)) return i; throw new TypeError("@@toPrimitive must return a primitive value."); } return ("string" === r ? String : Number)(t); } +function _typeof(o) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (o) { return typeof o; } : function (o) { return o && "function" == typeof Symbol && o.constructor === Symbol && o !== Symbol.prototype ? "symbol" : typeof o; }, _typeof(o); } var logDisabled_ = true; var deprecationWarnings_ = true; @@ -2537,7 +2263,7 @@ var deprecationWarnings_ = true; */ function extractVersion(uastring, expr, pos) { var match = uastring.match(expr); - return match && match.length >= pos && parseInt(match[pos], 10); + return match && match.length >= pos && parseFloat(match[pos], 10); } // Wraps the peerconnection event eventNameToWrap in a function @@ -2547,6 +2273,11 @@ function wrapPeerConnectionEvent(window, eventNameToWrap, wrapper) { if (!window.RTCPeerConnection) { return; } + var addEventListener = Object.getOwnPropertyDescriptor(EventTarget.prototype, 'addEventListener'); + if (!addEventListener.writable) { + log('Unable to polyfill events'); + return; + } var proto = window.RTCPeerConnection.prototype; var nativeAddEventListener = proto.addEventListener; proto.addEventListener = function (nativeEventName, cb) { @@ -2556,24 +2287,38 @@ function wrapPeerConnectionEvent(window, eventNameToWrap, wrapper) { var wrappedCallback = function wrappedCallback(e) { var modifiedEvent = wrapper(e); if (modifiedEvent) { - cb(modifiedEvent); + if (cb.handleEvent) { + cb.handleEvent(modifiedEvent); + } else { + cb(modifiedEvent); + } } }; this._eventMap = this._eventMap || {}; - this._eventMap[cb] = wrappedCallback; + 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[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[cb]; - delete this._eventMap[cb]; + 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]; @@ -2587,15 +2332,13 @@ function wrapPeerConnectionEvent(window, eventNameToWrap, wrapper) { 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 === 'undefined' ? 'undefined' : _typeof(bool)) + '. Please use a boolean.'); + return new Error('Argument type: ' + _typeof(bool) + '. Please use a boolean.'); } logDisabled_ = bool; return bool ? 'adapter.js logging disabled' : 'adapter.js logging enabled'; @@ -2607,2066 +2350,211 @@ function disableLog(bool) { */ function disableWarnings(bool) { if (typeof bool !== 'boolean') { - return new Error('Argument type: ' + (typeof bool === 'undefined' ? 'undefined' : _typeof(bool)) + '. Please use a 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) { - var navigator = window.navigator; - - // 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; - } - - if (navigator.mozGetUserMedia) { - // Firefox. - result.browser = 'firefox'; - result.version = extractVersion(navigator.userAgent, /Firefox\/(\d+)\./, 1); - } else if (navigator.webkitGetUserMedia || window.isSecureContext === false && window.webkitRTCPeerConnection && !window.RTCIceGatherer) { - // 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 (navigator.mediaDevices && navigator.userAgent.match(/Edge\/(\d+).(\d+)$/)) { - // Edge. - result.browser = 'edge'; - result.version = extractVersion(navigator.userAgent, /Edge\/(\d+).(\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; -} - -},{}],16:[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'; - -var SDPUtils = require('sdp'); - -function fixStatsType(stat) { - return { - inboundrtp: 'inbound-rtp', - outboundrtp: 'outbound-rtp', - candidatepair: 'candidate-pair', - localcandidate: 'local-candidate', - remotecandidate: 'remote-candidate' - }[stat.type] || stat.type; -} - -function writeMediaSection(transceiver, caps, type, stream, dtlsRole) { - var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps); - - // Map ICE parameters (ufrag, pwd) to SDP. - sdp += SDPUtils.writeIceParameters( - transceiver.iceGatherer.getLocalParameters()); - - // Map DTLS parameters to SDP. - sdp += SDPUtils.writeDtlsParameters( - transceiver.dtlsTransport.getLocalParameters(), - type === 'offer' ? 'actpass' : dtlsRole || 'active'); - - sdp += 'a=mid:' + transceiver.mid + '\r\n'; - - if (transceiver.rtpSender && transceiver.rtpReceiver) { - sdp += 'a=sendrecv\r\n'; - } else if (transceiver.rtpSender) { - sdp += 'a=sendonly\r\n'; - } else if (transceiver.rtpReceiver) { - sdp += 'a=recvonly\r\n'; - } else { - sdp += 'a=inactive\r\n'; - } - - if (transceiver.rtpSender) { - var trackId = transceiver.rtpSender._initialTrackId || - transceiver.rtpSender.track.id; - transceiver.rtpSender._initialTrackId = trackId; - // spec. - var msid = 'msid:' + (stream ? stream.id : '-') + ' ' + - trackId + '\r\n'; - sdp += 'a=' + msid; - // for Chrome. Legacy should no longer be required. - sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc + - ' ' + msid; - - // RTX - if (transceiver.sendEncodingParameters[0].rtx) { - sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc + - ' ' + msid; - sdp += 'a=ssrc-group:FID ' + - transceiver.sendEncodingParameters[0].ssrc + ' ' + - transceiver.sendEncodingParameters[0].rtx.ssrc + - '\r\n'; - } - } - // FIXME: this should be written by writeRtpDescription. - sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc + - ' cname:' + SDPUtils.localCName + '\r\n'; - if (transceiver.rtpSender && transceiver.sendEncodingParameters[0].rtx) { - sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc + - ' cname:' + SDPUtils.localCName + '\r\n'; - } - return sdp; -} - -// Edge does not like -// 1) stun: filtered after 14393 unless ?transport=udp is present -// 2) turn: that does not have all of turn:host:port?transport=udp -// 3) turn: with ipv6 addresses -// 4) turn: occurring muliple times -function filterIceServers(iceServers, edgeVersion) { - var hasTurn = false; - iceServers = JSON.parse(JSON.stringify(iceServers)); - return iceServers.filter(function(server) { - if (server && (server.urls || server.url)) { - var urls = server.urls || server.url; - if (server.url && !server.urls) { - console.warn('RTCIceServer.url is deprecated! Use urls instead.'); - } - var isString = typeof urls === 'string'; - if (isString) { - urls = [urls]; - } - urls = urls.filter(function(url) { - var validTurn = url.indexOf('turn:') === 0 && - url.indexOf('transport=udp') !== -1 && - url.indexOf('turn:[') === -1 && - !hasTurn; - - if (validTurn) { - hasTurn = true; - return true; - } - return url.indexOf('stun:') === 0 && edgeVersion >= 14393 && - url.indexOf('?transport=udp') === -1; - }); - - delete server.url; - server.urls = isString ? urls[0] : urls; - return !!urls.length; - } - }); -} - -// Determines the intersection of local and remote capabilities. -function getCommonCapabilities(localCapabilities, remoteCapabilities) { - var commonCapabilities = { - codecs: [], - headerExtensions: [], - fecMechanisms: [] - }; - - var findCodecByPayloadType = function(pt, codecs) { - pt = parseInt(pt, 10); - for (var i = 0; i < codecs.length; i++) { - if (codecs[i].payloadType === pt || - codecs[i].preferredPayloadType === pt) { - return codecs[i]; - } - } - }; - - var rtxCapabilityMatches = function(lRtx, rRtx, lCodecs, rCodecs) { - var lCodec = findCodecByPayloadType(lRtx.parameters.apt, lCodecs); - var rCodec = findCodecByPayloadType(rRtx.parameters.apt, rCodecs); - return lCodec && rCodec && - lCodec.name.toLowerCase() === rCodec.name.toLowerCase(); - }; - - localCapabilities.codecs.forEach(function(lCodec) { - for (var i = 0; i < remoteCapabilities.codecs.length; i++) { - var rCodec = remoteCapabilities.codecs[i]; - if (lCodec.name.toLowerCase() === rCodec.name.toLowerCase() && - lCodec.clockRate === rCodec.clockRate) { - if (lCodec.name.toLowerCase() === 'rtx' && - lCodec.parameters && rCodec.parameters.apt) { - // for RTX we need to find the local rtx that has a apt - // which points to the same local codec as the remote one. - if (!rtxCapabilityMatches(lCodec, rCodec, - localCapabilities.codecs, remoteCapabilities.codecs)) { - continue; - } - } - rCodec = JSON.parse(JSON.stringify(rCodec)); // deepcopy - // number of channels is the highest common number of channels - rCodec.numChannels = Math.min(lCodec.numChannels, - rCodec.numChannels); - // push rCodec so we reply with offerer payload type - commonCapabilities.codecs.push(rCodec); - - // determine common feedback mechanisms - rCodec.rtcpFeedback = rCodec.rtcpFeedback.filter(function(fb) { - for (var j = 0; j < lCodec.rtcpFeedback.length; j++) { - if (lCodec.rtcpFeedback[j].type === fb.type && - lCodec.rtcpFeedback[j].parameter === fb.parameter) { - return true; - } - } - return false; - }); - // FIXME: also need to determine .parameters - // see https://github.com/openpeer/ortc/issues/569 - break; - } - } - }); - - localCapabilities.headerExtensions.forEach(function(lHeaderExtension) { - for (var i = 0; i < remoteCapabilities.headerExtensions.length; - i++) { - var rHeaderExtension = remoteCapabilities.headerExtensions[i]; - if (lHeaderExtension.uri === rHeaderExtension.uri) { - commonCapabilities.headerExtensions.push(rHeaderExtension); - break; - } - } - }); - - // FIXME: fecMechanisms - return commonCapabilities; -} - -// is action=setLocalDescription with type allowed in signalingState -function isActionAllowedInSignalingState(action, type, signalingState) { - return { - offer: { - setLocalDescription: ['stable', 'have-local-offer'], - setRemoteDescription: ['stable', 'have-remote-offer'] - }, - answer: { - setLocalDescription: ['have-remote-offer', 'have-local-pranswer'], - setRemoteDescription: ['have-local-offer', 'have-remote-pranswer'] - } - }[type][action].indexOf(signalingState) !== -1; -} - -function maybeAddCandidate(iceTransport, candidate) { - // Edge's internal representation adds some fields therefore - // not all fieldŅ• are taken into account. - var alreadyAdded = iceTransport.getRemoteCandidates() - .find(function(remoteCandidate) { - return candidate.foundation === remoteCandidate.foundation && - candidate.ip === remoteCandidate.ip && - candidate.port === remoteCandidate.port && - candidate.priority === remoteCandidate.priority && - candidate.protocol === remoteCandidate.protocol && - candidate.type === remoteCandidate.type; - }); - if (!alreadyAdded) { - iceTransport.addRemoteCandidate(candidate); - } - return !alreadyAdded; -} - - -function makeError(name, description) { - var e = new Error(description); - e.name = name; - // legacy error codes from https://heycam.github.io/webidl/#idl-DOMException-error-names - e.code = { - NotSupportedError: 9, - InvalidStateError: 11, - InvalidAccessError: 15, - TypeError: undefined, - OperationError: undefined - }[name]; - return e; -} - -module.exports = function(window, edgeVersion) { - // https://w3c.github.io/mediacapture-main/#mediastream - // Helper function to add the track to the stream and - // dispatch the event ourselves. - function addTrackToStreamAndFireEvent(track, stream) { - stream.addTrack(track); - stream.dispatchEvent(new window.MediaStreamTrackEvent('addtrack', - {track: track})); - } - - function removeTrackFromStreamAndFireEvent(track, stream) { - stream.removeTrack(track); - stream.dispatchEvent(new window.MediaStreamTrackEvent('removetrack', - {track: track})); - } - - function fireAddTrack(pc, track, receiver, streams) { - var trackEvent = new Event('track'); - trackEvent.track = track; - trackEvent.receiver = receiver; - trackEvent.transceiver = {receiver: receiver}; - trackEvent.streams = streams; - window.setTimeout(function() { - pc._dispatchEvent('track', trackEvent); - }); - } - - var RTCPeerConnection = function(config) { - var pc = this; - - var _eventTarget = document.createDocumentFragment(); - ['addEventListener', 'removeEventListener', 'dispatchEvent'] - .forEach(function(method) { - pc[method] = _eventTarget[method].bind(_eventTarget); - }); - - this.canTrickleIceCandidates = null; - - this.needNegotiation = false; - - this.localStreams = []; - this.remoteStreams = []; - - this._localDescription = null; - this._remoteDescription = null; - - this.signalingState = 'stable'; - this.iceConnectionState = 'new'; - this.connectionState = 'new'; - this.iceGatheringState = 'new'; - - config = JSON.parse(JSON.stringify(config || {})); - - this.usingBundle = config.bundlePolicy === 'max-bundle'; - if (config.rtcpMuxPolicy === 'negotiate') { - throw(makeError('NotSupportedError', - 'rtcpMuxPolicy \'negotiate\' is not supported')); - } else if (!config.rtcpMuxPolicy) { - config.rtcpMuxPolicy = 'require'; - } - - switch (config.iceTransportPolicy) { - case 'all': - case 'relay': - break; - default: - config.iceTransportPolicy = 'all'; - break; - } - - switch (config.bundlePolicy) { - case 'balanced': - case 'max-compat': - case 'max-bundle': - break; - default: - config.bundlePolicy = 'balanced'; - break; - } - - config.iceServers = filterIceServers(config.iceServers || [], edgeVersion); - - this._iceGatherers = []; - if (config.iceCandidatePoolSize) { - for (var i = config.iceCandidatePoolSize; i > 0; i--) { - this._iceGatherers.push(new window.RTCIceGatherer({ - iceServers: config.iceServers, - gatherPolicy: config.iceTransportPolicy - })); - } - } else { - config.iceCandidatePoolSize = 0; - } - - this._config = config; - - // per-track iceGathers, iceTransports, dtlsTransports, rtpSenders, ... - // everything that is needed to describe a SDP m-line. - this.transceivers = []; - - this._sdpSessionId = SDPUtils.generateSessionId(); - this._sdpSessionVersion = 0; - - this._dtlsRole = undefined; // role for a=setup to use in answers. - - this._isClosed = false; - }; - - Object.defineProperty(RTCPeerConnection.prototype, 'localDescription', { - configurable: true, - get: function() { - return this._localDescription; - } - }); - Object.defineProperty(RTCPeerConnection.prototype, 'remoteDescription', { - configurable: true, - get: function() { - return this._remoteDescription; - } - }); - - // set up event handlers on prototype - RTCPeerConnection.prototype.onicecandidate = null; - RTCPeerConnection.prototype.onaddstream = null; - RTCPeerConnection.prototype.ontrack = null; - RTCPeerConnection.prototype.onremovestream = null; - RTCPeerConnection.prototype.onsignalingstatechange = null; - RTCPeerConnection.prototype.oniceconnectionstatechange = null; - RTCPeerConnection.prototype.onconnectionstatechange = null; - RTCPeerConnection.prototype.onicegatheringstatechange = null; - RTCPeerConnection.prototype.onnegotiationneeded = null; - RTCPeerConnection.prototype.ondatachannel = null; - - RTCPeerConnection.prototype._dispatchEvent = function(name, event) { - if (this._isClosed) { - return; - } - this.dispatchEvent(event); - if (typeof this['on' + name] === 'function') { - this['on' + name](event); - } - }; - - RTCPeerConnection.prototype._emitGatheringStateChange = function() { - var event = new Event('icegatheringstatechange'); - this._dispatchEvent('icegatheringstatechange', event); - }; - - RTCPeerConnection.prototype.getConfiguration = function() { - return this._config; - }; - - RTCPeerConnection.prototype.getLocalStreams = function() { - return this.localStreams; - }; - - RTCPeerConnection.prototype.getRemoteStreams = function() { - return this.remoteStreams; - }; - - // internal helper to create a transceiver object. - // (which is not yet the same as the WebRTC 1.0 transceiver) - RTCPeerConnection.prototype._createTransceiver = function(kind, doNotAdd) { - var hasBundleTransport = this.transceivers.length > 0; - var transceiver = { - track: null, - iceGatherer: null, - iceTransport: null, - dtlsTransport: null, - localCapabilities: null, - remoteCapabilities: null, - rtpSender: null, - rtpReceiver: null, - kind: kind, - mid: null, - sendEncodingParameters: null, - recvEncodingParameters: null, - stream: null, - associatedRemoteMediaStreams: [], - wantReceive: true - }; - if (this.usingBundle && hasBundleTransport) { - transceiver.iceTransport = this.transceivers[0].iceTransport; - transceiver.dtlsTransport = this.transceivers[0].dtlsTransport; - } else { - var transports = this._createIceAndDtlsTransports(); - transceiver.iceTransport = transports.iceTransport; - transceiver.dtlsTransport = transports.dtlsTransport; - } - if (!doNotAdd) { - this.transceivers.push(transceiver); - } - return transceiver; - }; - - RTCPeerConnection.prototype.addTrack = function(track, stream) { - if (this._isClosed) { - throw makeError('InvalidStateError', - 'Attempted to call addTrack on a closed peerconnection.'); - } - - var alreadyExists = this.transceivers.find(function(s) { - return s.track === track; - }); - - if (alreadyExists) { - throw makeError('InvalidAccessError', 'Track already exists.'); - } - - var transceiver; - for (var i = 0; i < this.transceivers.length; i++) { - if (!this.transceivers[i].track && - this.transceivers[i].kind === track.kind) { - transceiver = this.transceivers[i]; - } - } - if (!transceiver) { - transceiver = this._createTransceiver(track.kind); - } - - this._maybeFireNegotiationNeeded(); - - if (this.localStreams.indexOf(stream) === -1) { - this.localStreams.push(stream); - } - - transceiver.track = track; - transceiver.stream = stream; - transceiver.rtpSender = new window.RTCRtpSender(track, - transceiver.dtlsTransport); - return transceiver.rtpSender; - }; - - RTCPeerConnection.prototype.addStream = function(stream) { - var pc = this; - if (edgeVersion >= 15025) { - stream.getTracks().forEach(function(track) { - pc.addTrack(track, stream); - }); - } else { - // Clone is necessary for local demos mostly, attaching directly - // to two different senders does not work (build 10547). - // Fixed in 15025 (or earlier) - var clonedStream = stream.clone(); - stream.getTracks().forEach(function(track, idx) { - var clonedTrack = clonedStream.getTracks()[idx]; - track.addEventListener('enabled', function(event) { - clonedTrack.enabled = event.enabled; - }); - }); - clonedStream.getTracks().forEach(function(track) { - pc.addTrack(track, clonedStream); - }); - } - }; - - RTCPeerConnection.prototype.removeTrack = function(sender) { - if (this._isClosed) { - throw makeError('InvalidStateError', - 'Attempted to call removeTrack on a closed peerconnection.'); - } - - if (!(sender instanceof window.RTCRtpSender)) { - throw new TypeError('Argument 1 of RTCPeerConnection.removeTrack ' + - 'does not implement interface RTCRtpSender.'); - } - - var transceiver = this.transceivers.find(function(t) { - return t.rtpSender === sender; - }); - - if (!transceiver) { - throw makeError('InvalidAccessError', - 'Sender was not created by this connection.'); - } - var stream = transceiver.stream; - - transceiver.rtpSender.stop(); - transceiver.rtpSender = null; - transceiver.track = null; - transceiver.stream = null; - - // remove the stream from the set of local streams - var localStreams = this.transceivers.map(function(t) { - return t.stream; - }); - if (localStreams.indexOf(stream) === -1 && - this.localStreams.indexOf(stream) > -1) { - this.localStreams.splice(this.localStreams.indexOf(stream), 1); - } - - this._maybeFireNegotiationNeeded(); - }; - - RTCPeerConnection.prototype.removeStream = function(stream) { - var pc = this; - stream.getTracks().forEach(function(track) { - var sender = pc.getSenders().find(function(s) { - return s.track === track; - }); - if (sender) { - pc.removeTrack(sender); - } - }); - }; - - RTCPeerConnection.prototype.getSenders = function() { - return this.transceivers.filter(function(transceiver) { - return !!transceiver.rtpSender; - }) - .map(function(transceiver) { - return transceiver.rtpSender; - }); - }; - - RTCPeerConnection.prototype.getReceivers = function() { - return this.transceivers.filter(function(transceiver) { - return !!transceiver.rtpReceiver; - }) - .map(function(transceiver) { - return transceiver.rtpReceiver; - }); - }; - - - RTCPeerConnection.prototype._createIceGatherer = function(sdpMLineIndex, - usingBundle) { - var pc = this; - if (usingBundle && sdpMLineIndex > 0) { - return this.transceivers[0].iceGatherer; - } else if (this._iceGatherers.length) { - return this._iceGatherers.shift(); - } - var iceGatherer = new window.RTCIceGatherer({ - iceServers: this._config.iceServers, - gatherPolicy: this._config.iceTransportPolicy - }); - Object.defineProperty(iceGatherer, 'state', - {value: 'new', writable: true} - ); - - this.transceivers[sdpMLineIndex].bufferedCandidateEvents = []; - this.transceivers[sdpMLineIndex].bufferCandidates = function(event) { - var end = !event.candidate || Object.keys(event.candidate).length === 0; - // polyfill since RTCIceGatherer.state is not implemented in - // Edge 10547 yet. - iceGatherer.state = end ? 'completed' : 'gathering'; - if (pc.transceivers[sdpMLineIndex].bufferedCandidateEvents !== null) { - pc.transceivers[sdpMLineIndex].bufferedCandidateEvents.push(event); - } - }; - iceGatherer.addEventListener('localcandidate', - this.transceivers[sdpMLineIndex].bufferCandidates); - return iceGatherer; - }; - - // start gathering from an RTCIceGatherer. - RTCPeerConnection.prototype._gather = function(mid, sdpMLineIndex) { - var pc = this; - var iceGatherer = this.transceivers[sdpMLineIndex].iceGatherer; - if (iceGatherer.onlocalcandidate) { - return; - } - var bufferedCandidateEvents = - this.transceivers[sdpMLineIndex].bufferedCandidateEvents; - this.transceivers[sdpMLineIndex].bufferedCandidateEvents = null; - iceGatherer.removeEventListener('localcandidate', - this.transceivers[sdpMLineIndex].bufferCandidates); - iceGatherer.onlocalcandidate = function(evt) { - if (pc.usingBundle && sdpMLineIndex > 0) { - // if we know that we use bundle we can drop candidates with - // Ņ•dpMLineIndex > 0. If we don't do this then our state gets - // confused since we dispose the extra ice gatherer. - return; - } - var event = new Event('icecandidate'); - event.candidate = {sdpMid: mid, sdpMLineIndex: sdpMLineIndex}; - - var cand = evt.candidate; - // Edge emits an empty object for RTCIceCandidateCompleteâ€Ĩ - var end = !cand || Object.keys(cand).length === 0; - if (end) { - // polyfill since RTCIceGatherer.state is not implemented in - // Edge 10547 yet. - if (iceGatherer.state === 'new' || iceGatherer.state === 'gathering') { - iceGatherer.state = 'completed'; - } - } else { - if (iceGatherer.state === 'new') { - iceGatherer.state = 'gathering'; - } - // RTCIceCandidate doesn't have a component, needs to be added - cand.component = 1; - // also the usernameFragment. TODO: update SDP to take both variants. - cand.ufrag = iceGatherer.getLocalParameters().usernameFragment; - - var serializedCandidate = SDPUtils.writeCandidate(cand); - event.candidate = Object.assign(event.candidate, - SDPUtils.parseCandidate(serializedCandidate)); - - event.candidate.candidate = serializedCandidate; - event.candidate.toJSON = function() { - return { - candidate: event.candidate.candidate, - sdpMid: event.candidate.sdpMid, - sdpMLineIndex: event.candidate.sdpMLineIndex, - usernameFragment: event.candidate.usernameFragment - }; - }; - } - - // update local description. - var sections = SDPUtils.getMediaSections(pc._localDescription.sdp); - if (!end) { - sections[event.candidate.sdpMLineIndex] += - 'a=' + event.candidate.candidate + '\r\n'; - } else { - sections[event.candidate.sdpMLineIndex] += - 'a=end-of-candidates\r\n'; - } - pc._localDescription.sdp = - SDPUtils.getDescription(pc._localDescription.sdp) + - sections.join(''); - var complete = pc.transceivers.every(function(transceiver) { - return transceiver.iceGatherer && - transceiver.iceGatherer.state === 'completed'; - }); - - if (pc.iceGatheringState !== 'gathering') { - pc.iceGatheringState = 'gathering'; - pc._emitGatheringStateChange(); - } - - // Emit candidate. Also emit null candidate when all gatherers are - // complete. - if (!end) { - pc._dispatchEvent('icecandidate', event); - } - if (complete) { - pc._dispatchEvent('icecandidate', new Event('icecandidate')); - pc.iceGatheringState = 'complete'; - pc._emitGatheringStateChange(); - } - }; - - // emit already gathered candidates. - window.setTimeout(function() { - bufferedCandidateEvents.forEach(function(e) { - iceGatherer.onlocalcandidate(e); - }); - }, 0); - }; - - // Create ICE transport and DTLS transport. - RTCPeerConnection.prototype._createIceAndDtlsTransports = function() { - var pc = this; - var iceTransport = new window.RTCIceTransport(null); - iceTransport.onicestatechange = function() { - pc._updateIceConnectionState(); - pc._updateConnectionState(); - }; - - var dtlsTransport = new window.RTCDtlsTransport(iceTransport); - dtlsTransport.ondtlsstatechange = function() { - pc._updateConnectionState(); - }; - dtlsTransport.onerror = function() { - // onerror does not set state to failed by itself. - Object.defineProperty(dtlsTransport, 'state', - {value: 'failed', writable: true}); - pc._updateConnectionState(); - }; - - return { - iceTransport: iceTransport, - dtlsTransport: dtlsTransport - }; - }; - - // Destroy ICE gatherer, ICE transport and DTLS transport. - // Without triggering the callbacks. - RTCPeerConnection.prototype._disposeIceAndDtlsTransports = function( - sdpMLineIndex) { - var iceGatherer = this.transceivers[sdpMLineIndex].iceGatherer; - if (iceGatherer) { - delete iceGatherer.onlocalcandidate; - delete this.transceivers[sdpMLineIndex].iceGatherer; - } - var iceTransport = this.transceivers[sdpMLineIndex].iceTransport; - if (iceTransport) { - delete iceTransport.onicestatechange; - delete this.transceivers[sdpMLineIndex].iceTransport; - } - var dtlsTransport = this.transceivers[sdpMLineIndex].dtlsTransport; - if (dtlsTransport) { - delete dtlsTransport.ondtlsstatechange; - delete dtlsTransport.onerror; - delete this.transceivers[sdpMLineIndex].dtlsTransport; - } - }; - - // Start the RTP Sender and Receiver for a transceiver. - RTCPeerConnection.prototype._transceive = function(transceiver, - send, recv) { - var params = getCommonCapabilities(transceiver.localCapabilities, - transceiver.remoteCapabilities); - if (send && transceiver.rtpSender) { - params.encodings = transceiver.sendEncodingParameters; - params.rtcp = { - cname: SDPUtils.localCName, - compound: transceiver.rtcpParameters.compound - }; - if (transceiver.recvEncodingParameters.length) { - params.rtcp.ssrc = transceiver.recvEncodingParameters[0].ssrc; - } - transceiver.rtpSender.send(params); - } - if (recv && transceiver.rtpReceiver && params.codecs.length > 0) { - // remove RTX field in Edge 14942 - if (transceiver.kind === 'video' - && transceiver.recvEncodingParameters - && edgeVersion < 15019) { - transceiver.recvEncodingParameters.forEach(function(p) { - delete p.rtx; - }); - } - if (transceiver.recvEncodingParameters.length) { - params.encodings = transceiver.recvEncodingParameters; - } else { - params.encodings = [{}]; - } - params.rtcp = { - compound: transceiver.rtcpParameters.compound - }; - if (transceiver.rtcpParameters.cname) { - params.rtcp.cname = transceiver.rtcpParameters.cname; - } - if (transceiver.sendEncodingParameters.length) { - params.rtcp.ssrc = transceiver.sendEncodingParameters[0].ssrc; - } - transceiver.rtpReceiver.receive(params); - } - }; - - RTCPeerConnection.prototype.setLocalDescription = function(description) { - var pc = this; - - // Note: pranswer is not supported. - if (['offer', 'answer'].indexOf(description.type) === -1) { - return Promise.reject(makeError('TypeError', - 'Unsupported type "' + description.type + '"')); - } - - if (!isActionAllowedInSignalingState('setLocalDescription', - description.type, pc.signalingState) || pc._isClosed) { - return Promise.reject(makeError('InvalidStateError', - 'Can not set local ' + description.type + - ' in state ' + pc.signalingState)); - } - - var sections; - var sessionpart; - if (description.type === 'offer') { - // VERY limited support for SDP munging. Limited to: - // * changing the order of codecs - sections = SDPUtils.splitSections(description.sdp); - sessionpart = sections.shift(); - sections.forEach(function(mediaSection, sdpMLineIndex) { - var caps = SDPUtils.parseRtpParameters(mediaSection); - pc.transceivers[sdpMLineIndex].localCapabilities = caps; - }); - - pc.transceivers.forEach(function(transceiver, sdpMLineIndex) { - pc._gather(transceiver.mid, sdpMLineIndex); - }); - } else if (description.type === 'answer') { - sections = SDPUtils.splitSections(pc._remoteDescription.sdp); - sessionpart = sections.shift(); - var isIceLite = SDPUtils.matchPrefix(sessionpart, - 'a=ice-lite').length > 0; - sections.forEach(function(mediaSection, sdpMLineIndex) { - var transceiver = pc.transceivers[sdpMLineIndex]; - var iceGatherer = transceiver.iceGatherer; - var iceTransport = transceiver.iceTransport; - var dtlsTransport = transceiver.dtlsTransport; - var localCapabilities = transceiver.localCapabilities; - var remoteCapabilities = transceiver.remoteCapabilities; - - // treat bundle-only as not-rejected. - var rejected = SDPUtils.isRejected(mediaSection) && - SDPUtils.matchPrefix(mediaSection, 'a=bundle-only').length === 0; - - if (!rejected && !transceiver.rejected) { - var remoteIceParameters = SDPUtils.getIceParameters( - mediaSection, sessionpart); - var remoteDtlsParameters = SDPUtils.getDtlsParameters( - mediaSection, sessionpart); - if (isIceLite) { - remoteDtlsParameters.role = 'server'; - } - - if (!pc.usingBundle || sdpMLineIndex === 0) { - pc._gather(transceiver.mid, sdpMLineIndex); - if (iceTransport.state === 'new') { - iceTransport.start(iceGatherer, remoteIceParameters, - isIceLite ? 'controlling' : 'controlled'); - } - if (dtlsTransport.state === 'new') { - dtlsTransport.start(remoteDtlsParameters); - } - } - - // Calculate intersection of capabilities. - var params = getCommonCapabilities(localCapabilities, - remoteCapabilities); - - // Start the RTCRtpSender. The RTCRtpReceiver for this - // transceiver has already been started in setRemoteDescription. - pc._transceive(transceiver, - params.codecs.length > 0, - false); - } - }); - } - - pc._localDescription = { - type: description.type, - sdp: description.sdp - }; - if (description.type === 'offer') { - pc._updateSignalingState('have-local-offer'); - } else { - pc._updateSignalingState('stable'); - } - - return Promise.resolve(); - }; - - RTCPeerConnection.prototype.setRemoteDescription = function(description) { - var pc = this; - - // Note: pranswer is not supported. - if (['offer', 'answer'].indexOf(description.type) === -1) { - return Promise.reject(makeError('TypeError', - 'Unsupported type "' + description.type + '"')); - } - - if (!isActionAllowedInSignalingState('setRemoteDescription', - description.type, pc.signalingState) || pc._isClosed) { - return Promise.reject(makeError('InvalidStateError', - 'Can not set remote ' + description.type + - ' in state ' + pc.signalingState)); - } - - var streams = {}; - pc.remoteStreams.forEach(function(stream) { - streams[stream.id] = stream; - }); - var receiverList = []; - var sections = SDPUtils.splitSections(description.sdp); - var sessionpart = sections.shift(); - var isIceLite = SDPUtils.matchPrefix(sessionpart, - 'a=ice-lite').length > 0; - var usingBundle = SDPUtils.matchPrefix(sessionpart, - 'a=group:BUNDLE ').length > 0; - pc.usingBundle = usingBundle; - var iceOptions = SDPUtils.matchPrefix(sessionpart, - 'a=ice-options:')[0]; - if (iceOptions) { - pc.canTrickleIceCandidates = iceOptions.substr(14).split(' ') - .indexOf('trickle') >= 0; - } else { - pc.canTrickleIceCandidates = false; - } - - sections.forEach(function(mediaSection, sdpMLineIndex) { - var lines = SDPUtils.splitLines(mediaSection); - var kind = SDPUtils.getKind(mediaSection); - // treat bundle-only as not-rejected. - var rejected = SDPUtils.isRejected(mediaSection) && - SDPUtils.matchPrefix(mediaSection, 'a=bundle-only').length === 0; - var protocol = lines[0].substr(2).split(' ')[2]; - - var direction = SDPUtils.getDirection(mediaSection, sessionpart); - var remoteMsid = SDPUtils.parseMsid(mediaSection); - - var mid = SDPUtils.getMid(mediaSection) || SDPUtils.generateIdentifier(); - - // Reject datachannels which are not implemented yet. - if (rejected || (kind === 'application' && (protocol === 'DTLS/SCTP' || - protocol === 'UDP/DTLS/SCTP'))) { - // TODO: this is dangerous in the case where a non-rejected m-line - // becomes rejected. - pc.transceivers[sdpMLineIndex] = { - mid: mid, - kind: kind, - protocol: protocol, - rejected: true - }; - return; - } - - if (!rejected && pc.transceivers[sdpMLineIndex] && - pc.transceivers[sdpMLineIndex].rejected) { - // recycle a rejected transceiver. - pc.transceivers[sdpMLineIndex] = pc._createTransceiver(kind, true); - } - - var transceiver; - var iceGatherer; - var iceTransport; - var dtlsTransport; - var rtpReceiver; - var sendEncodingParameters; - var recvEncodingParameters; - var localCapabilities; - - var track; - // FIXME: ensure the mediaSection has rtcp-mux set. - var remoteCapabilities = SDPUtils.parseRtpParameters(mediaSection); - var remoteIceParameters; - var remoteDtlsParameters; - if (!rejected) { - remoteIceParameters = SDPUtils.getIceParameters(mediaSection, - sessionpart); - remoteDtlsParameters = SDPUtils.getDtlsParameters(mediaSection, - sessionpart); - remoteDtlsParameters.role = 'client'; - } - recvEncodingParameters = - SDPUtils.parseRtpEncodingParameters(mediaSection); - - var rtcpParameters = SDPUtils.parseRtcpParameters(mediaSection); - - var isComplete = SDPUtils.matchPrefix(mediaSection, - 'a=end-of-candidates', sessionpart).length > 0; - var cands = SDPUtils.matchPrefix(mediaSection, 'a=candidate:') - .map(function(cand) { - return SDPUtils.parseCandidate(cand); - }) - .filter(function(cand) { - return cand.component === 1; - }); - - // Check if we can use BUNDLE and dispose transports. - if ((description.type === 'offer' || description.type === 'answer') && - !rejected && usingBundle && sdpMLineIndex > 0 && - pc.transceivers[sdpMLineIndex]) { - pc._disposeIceAndDtlsTransports(sdpMLineIndex); - pc.transceivers[sdpMLineIndex].iceGatherer = - pc.transceivers[0].iceGatherer; - pc.transceivers[sdpMLineIndex].iceTransport = - pc.transceivers[0].iceTransport; - pc.transceivers[sdpMLineIndex].dtlsTransport = - pc.transceivers[0].dtlsTransport; - if (pc.transceivers[sdpMLineIndex].rtpSender) { - pc.transceivers[sdpMLineIndex].rtpSender.setTransport( - pc.transceivers[0].dtlsTransport); - } - if (pc.transceivers[sdpMLineIndex].rtpReceiver) { - pc.transceivers[sdpMLineIndex].rtpReceiver.setTransport( - pc.transceivers[0].dtlsTransport); - } - } - if (description.type === 'offer' && !rejected) { - transceiver = pc.transceivers[sdpMLineIndex] || - pc._createTransceiver(kind); - transceiver.mid = mid; - - if (!transceiver.iceGatherer) { - transceiver.iceGatherer = pc._createIceGatherer(sdpMLineIndex, - usingBundle); - } - - if (cands.length && transceiver.iceTransport.state === 'new') { - if (isComplete && (!usingBundle || sdpMLineIndex === 0)) { - transceiver.iceTransport.setRemoteCandidates(cands); - } else { - cands.forEach(function(candidate) { - maybeAddCandidate(transceiver.iceTransport, candidate); - }); - } - } - - localCapabilities = window.RTCRtpReceiver.getCapabilities(kind); - - // filter RTX until additional stuff needed for RTX is implemented - // in adapter.js - if (edgeVersion < 15019) { - localCapabilities.codecs = localCapabilities.codecs.filter( - function(codec) { - return codec.name !== 'rtx'; - }); - } - - sendEncodingParameters = transceiver.sendEncodingParameters || [{ - ssrc: (2 * sdpMLineIndex + 2) * 1001 - }]; - - // TODO: rewrite to use http://w3c.github.io/webrtc-pc/#set-associated-remote-streams - var isNewTrack = false; - if (direction === 'sendrecv' || direction === 'sendonly') { - isNewTrack = !transceiver.rtpReceiver; - rtpReceiver = transceiver.rtpReceiver || - new window.RTCRtpReceiver(transceiver.dtlsTransport, kind); - - if (isNewTrack) { - var stream; - track = rtpReceiver.track; - // FIXME: does not work with Plan B. - if (remoteMsid && remoteMsid.stream === '-') { - // no-op. a stream id of '-' means: no associated stream. - } else if (remoteMsid) { - if (!streams[remoteMsid.stream]) { - streams[remoteMsid.stream] = new window.MediaStream(); - Object.defineProperty(streams[remoteMsid.stream], 'id', { - get: function() { - return remoteMsid.stream; - } - }); - } - Object.defineProperty(track, 'id', { - get: function() { - return remoteMsid.track; - } - }); - stream = streams[remoteMsid.stream]; - } else { - if (!streams.default) { - streams.default = new window.MediaStream(); - } - stream = streams.default; - } - if (stream) { - addTrackToStreamAndFireEvent(track, stream); - transceiver.associatedRemoteMediaStreams.push(stream); - } - receiverList.push([track, rtpReceiver, stream]); - } - } else if (transceiver.rtpReceiver && transceiver.rtpReceiver.track) { - transceiver.associatedRemoteMediaStreams.forEach(function(s) { - var nativeTrack = s.getTracks().find(function(t) { - return t.id === transceiver.rtpReceiver.track.id; - }); - if (nativeTrack) { - removeTrackFromStreamAndFireEvent(nativeTrack, s); - } - }); - transceiver.associatedRemoteMediaStreams = []; - } - - transceiver.localCapabilities = localCapabilities; - transceiver.remoteCapabilities = remoteCapabilities; - transceiver.rtpReceiver = rtpReceiver; - transceiver.rtcpParameters = rtcpParameters; - transceiver.sendEncodingParameters = sendEncodingParameters; - transceiver.recvEncodingParameters = recvEncodingParameters; - - // Start the RTCRtpReceiver now. The RTPSender is started in - // setLocalDescription. - pc._transceive(pc.transceivers[sdpMLineIndex], - false, - isNewTrack); - } else if (description.type === 'answer' && !rejected) { - transceiver = pc.transceivers[sdpMLineIndex]; - iceGatherer = transceiver.iceGatherer; - iceTransport = transceiver.iceTransport; - dtlsTransport = transceiver.dtlsTransport; - rtpReceiver = transceiver.rtpReceiver; - sendEncodingParameters = transceiver.sendEncodingParameters; - localCapabilities = transceiver.localCapabilities; - - pc.transceivers[sdpMLineIndex].recvEncodingParameters = - recvEncodingParameters; - pc.transceivers[sdpMLineIndex].remoteCapabilities = - remoteCapabilities; - pc.transceivers[sdpMLineIndex].rtcpParameters = rtcpParameters; - - if (cands.length && iceTransport.state === 'new') { - if ((isIceLite || isComplete) && - (!usingBundle || sdpMLineIndex === 0)) { - iceTransport.setRemoteCandidates(cands); - } else { - cands.forEach(function(candidate) { - maybeAddCandidate(transceiver.iceTransport, candidate); - }); - } - } - - if (!usingBundle || sdpMLineIndex === 0) { - if (iceTransport.state === 'new') { - iceTransport.start(iceGatherer, remoteIceParameters, - 'controlling'); - } - if (dtlsTransport.state === 'new') { - dtlsTransport.start(remoteDtlsParameters); - } - } - - // If the offer contained RTX but the answer did not, - // remove RTX from sendEncodingParameters. - var commonCapabilities = getCommonCapabilities( - transceiver.localCapabilities, - transceiver.remoteCapabilities); - - var hasRtx = commonCapabilities.codecs.filter(function(c) { - return c.name.toLowerCase() === 'rtx'; - }).length; - if (!hasRtx && transceiver.sendEncodingParameters[0].rtx) { - delete transceiver.sendEncodingParameters[0].rtx; - } - - pc._transceive(transceiver, - direction === 'sendrecv' || direction === 'recvonly', - direction === 'sendrecv' || direction === 'sendonly'); - - // TODO: rewrite to use http://w3c.github.io/webrtc-pc/#set-associated-remote-streams - if (rtpReceiver && - (direction === 'sendrecv' || direction === 'sendonly')) { - track = rtpReceiver.track; - if (remoteMsid) { - if (!streams[remoteMsid.stream]) { - streams[remoteMsid.stream] = new window.MediaStream(); - } - addTrackToStreamAndFireEvent(track, streams[remoteMsid.stream]); - receiverList.push([track, rtpReceiver, streams[remoteMsid.stream]]); - } else { - if (!streams.default) { - streams.default = new window.MediaStream(); - } - addTrackToStreamAndFireEvent(track, streams.default); - receiverList.push([track, rtpReceiver, streams.default]); - } - } else { - // FIXME: actually the receiver should be created later. - delete transceiver.rtpReceiver; - } - } - }); - - if (pc._dtlsRole === undefined) { - pc._dtlsRole = description.type === 'offer' ? 'active' : 'passive'; - } - - pc._remoteDescription = { - type: description.type, - sdp: description.sdp - }; - if (description.type === 'offer') { - pc._updateSignalingState('have-remote-offer'); - } else { - pc._updateSignalingState('stable'); - } - Object.keys(streams).forEach(function(sid) { - var stream = streams[sid]; - if (stream.getTracks().length) { - if (pc.remoteStreams.indexOf(stream) === -1) { - pc.remoteStreams.push(stream); - var event = new Event('addstream'); - event.stream = stream; - window.setTimeout(function() { - pc._dispatchEvent('addstream', event); - }); - } - - receiverList.forEach(function(item) { - var track = item[0]; - var receiver = item[1]; - if (stream.id !== item[2].id) { - return; - } - fireAddTrack(pc, track, receiver, [stream]); - }); - } - }); - receiverList.forEach(function(item) { - if (item[2]) { - return; - } - fireAddTrack(pc, item[0], item[1], []); - }); - - // check whether addIceCandidate({}) was called within four seconds after - // setRemoteDescription. - window.setTimeout(function() { - if (!(pc && pc.transceivers)) { - return; - } - pc.transceivers.forEach(function(transceiver) { - if (transceiver.iceTransport && - transceiver.iceTransport.state === 'new' && - transceiver.iceTransport.getRemoteCandidates().length > 0) { - console.warn('Timeout for addRemoteCandidate. Consider sending ' + - 'an end-of-candidates notification'); - transceiver.iceTransport.addRemoteCandidate({}); - } - }); - }, 4000); - - return Promise.resolve(); - }; - - RTCPeerConnection.prototype.close = function() { - this.transceivers.forEach(function(transceiver) { - /* not yet - if (transceiver.iceGatherer) { - transceiver.iceGatherer.close(); - } - */ - if (transceiver.iceTransport) { - transceiver.iceTransport.stop(); - } - if (transceiver.dtlsTransport) { - transceiver.dtlsTransport.stop(); - } - if (transceiver.rtpSender) { - transceiver.rtpSender.stop(); - } - if (transceiver.rtpReceiver) { - transceiver.rtpReceiver.stop(); - } - }); - // FIXME: clean up tracks, local streams, remote streams, etc - this._isClosed = true; - this._updateSignalingState('closed'); - }; - - // Update the signaling state. - RTCPeerConnection.prototype._updateSignalingState = function(newState) { - this.signalingState = newState; - var event = new Event('signalingstatechange'); - this._dispatchEvent('signalingstatechange', event); - }; - - // Determine whether to fire the negotiationneeded event. - RTCPeerConnection.prototype._maybeFireNegotiationNeeded = function() { - var pc = this; - if (this.signalingState !== 'stable' || this.needNegotiation === true) { - return; - } - this.needNegotiation = true; - window.setTimeout(function() { - if (pc.needNegotiation) { - pc.needNegotiation = false; - var event = new Event('negotiationneeded'); - pc._dispatchEvent('negotiationneeded', event); - } - }, 0); - }; - - // Update the ice connection state. - RTCPeerConnection.prototype._updateIceConnectionState = function() { - var newState; - var states = { - 'new': 0, - closed: 0, - checking: 0, - connected: 0, - completed: 0, - disconnected: 0, - failed: 0 - }; - this.transceivers.forEach(function(transceiver) { - if (transceiver.iceTransport && !transceiver.rejected) { - states[transceiver.iceTransport.state]++; - } - }); - - newState = 'new'; - if (states.failed > 0) { - newState = 'failed'; - } else if (states.checking > 0) { - newState = 'checking'; - } else if (states.disconnected > 0) { - newState = 'disconnected'; - } else if (states.new > 0) { - newState = 'new'; - } else if (states.connected > 0) { - newState = 'connected'; - } else if (states.completed > 0) { - newState = 'completed'; - } - - if (newState !== this.iceConnectionState) { - this.iceConnectionState = newState; - var event = new Event('iceconnectionstatechange'); - this._dispatchEvent('iceconnectionstatechange', event); - } - }; - - // Update the connection state. - RTCPeerConnection.prototype._updateConnectionState = function() { - var newState; - var states = { - 'new': 0, - closed: 0, - connecting: 0, - connected: 0, - completed: 0, - disconnected: 0, - failed: 0 - }; - this.transceivers.forEach(function(transceiver) { - if (transceiver.iceTransport && transceiver.dtlsTransport && - !transceiver.rejected) { - states[transceiver.iceTransport.state]++; - states[transceiver.dtlsTransport.state]++; - } - }); - // ICETransport.completed and connected are the same for this purpose. - states.connected += states.completed; - - newState = 'new'; - if (states.failed > 0) { - newState = 'failed'; - } else if (states.connecting > 0) { - newState = 'connecting'; - } else if (states.disconnected > 0) { - newState = 'disconnected'; - } else if (states.new > 0) { - newState = 'new'; - } else if (states.connected > 0) { - newState = 'connected'; - } - - if (newState !== this.connectionState) { - this.connectionState = newState; - var event = new Event('connectionstatechange'); - this._dispatchEvent('connectionstatechange', event); - } - }; - - RTCPeerConnection.prototype.createOffer = function() { - var pc = this; - - if (pc._isClosed) { - return Promise.reject(makeError('InvalidStateError', - 'Can not call createOffer after close')); - } - - var numAudioTracks = pc.transceivers.filter(function(t) { - return t.kind === 'audio'; - }).length; - var numVideoTracks = pc.transceivers.filter(function(t) { - return t.kind === 'video'; - }).length; - - // Determine number of audio and video tracks we need to send/recv. - var offerOptions = arguments[0]; - if (offerOptions) { - // Reject Chrome legacy constraints. - if (offerOptions.mandatory || offerOptions.optional) { - throw new TypeError( - 'Legacy mandatory/optional constraints not supported.'); - } - if (offerOptions.offerToReceiveAudio !== undefined) { - if (offerOptions.offerToReceiveAudio === true) { - numAudioTracks = 1; - } else if (offerOptions.offerToReceiveAudio === false) { - numAudioTracks = 0; - } else { - numAudioTracks = offerOptions.offerToReceiveAudio; - } - } - if (offerOptions.offerToReceiveVideo !== undefined) { - if (offerOptions.offerToReceiveVideo === true) { - numVideoTracks = 1; - } else if (offerOptions.offerToReceiveVideo === false) { - numVideoTracks = 0; - } else { - numVideoTracks = offerOptions.offerToReceiveVideo; - } - } - } - - pc.transceivers.forEach(function(transceiver) { - if (transceiver.kind === 'audio') { - numAudioTracks--; - if (numAudioTracks < 0) { - transceiver.wantReceive = false; - } - } else if (transceiver.kind === 'video') { - numVideoTracks--; - if (numVideoTracks < 0) { - transceiver.wantReceive = false; - } - } - }); - - // Create M-lines for recvonly streams. - while (numAudioTracks > 0 || numVideoTracks > 0) { - if (numAudioTracks > 0) { - pc._createTransceiver('audio'); - numAudioTracks--; - } - if (numVideoTracks > 0) { - pc._createTransceiver('video'); - numVideoTracks--; - } - } - - var sdp = SDPUtils.writeSessionBoilerplate(pc._sdpSessionId, - pc._sdpSessionVersion++); - pc.transceivers.forEach(function(transceiver, sdpMLineIndex) { - // For each track, create an ice gatherer, ice transport, - // dtls transport, potentially rtpsender and rtpreceiver. - var track = transceiver.track; - var kind = transceiver.kind; - var mid = transceiver.mid || SDPUtils.generateIdentifier(); - transceiver.mid = mid; - - if (!transceiver.iceGatherer) { - transceiver.iceGatherer = pc._createIceGatherer(sdpMLineIndex, - pc.usingBundle); - } - - var localCapabilities = window.RTCRtpSender.getCapabilities(kind); - // filter RTX until additional stuff needed for RTX is implemented - // in adapter.js - if (edgeVersion < 15019) { - localCapabilities.codecs = localCapabilities.codecs.filter( - function(codec) { - return codec.name !== 'rtx'; - }); - } - localCapabilities.codecs.forEach(function(codec) { - // work around https://bugs.chromium.org/p/webrtc/issues/detail?id=6552 - // by adding level-asymmetry-allowed=1 - if (codec.name === 'H264' && - codec.parameters['level-asymmetry-allowed'] === undefined) { - codec.parameters['level-asymmetry-allowed'] = '1'; - } - - // for subsequent offers, we might have to re-use the payload - // type of the last offer. - if (transceiver.remoteCapabilities && - transceiver.remoteCapabilities.codecs) { - transceiver.remoteCapabilities.codecs.forEach(function(remoteCodec) { - if (codec.name.toLowerCase() === remoteCodec.name.toLowerCase() && - codec.clockRate === remoteCodec.clockRate) { - codec.preferredPayloadType = remoteCodec.payloadType; - } - }); - } - }); - localCapabilities.headerExtensions.forEach(function(hdrExt) { - var remoteExtensions = transceiver.remoteCapabilities && - transceiver.remoteCapabilities.headerExtensions || []; - remoteExtensions.forEach(function(rHdrExt) { - if (hdrExt.uri === rHdrExt.uri) { - hdrExt.id = rHdrExt.id; - } - }); - }); - - // generate an ssrc now, to be used later in rtpSender.send - var sendEncodingParameters = transceiver.sendEncodingParameters || [{ - ssrc: (2 * sdpMLineIndex + 1) * 1001 - }]; - if (track) { - // add RTX - if (edgeVersion >= 15019 && kind === 'video' && - !sendEncodingParameters[0].rtx) { - sendEncodingParameters[0].rtx = { - ssrc: sendEncodingParameters[0].ssrc + 1 - }; - } - } - - if (transceiver.wantReceive) { - transceiver.rtpReceiver = new window.RTCRtpReceiver( - transceiver.dtlsTransport, kind); - } - - transceiver.localCapabilities = localCapabilities; - transceiver.sendEncodingParameters = sendEncodingParameters; - }); - - // always offer BUNDLE and dispose on return if not supported. - if (pc._config.bundlePolicy !== 'max-compat') { - sdp += 'a=group:BUNDLE ' + pc.transceivers.map(function(t) { - return t.mid; - }).join(' ') + '\r\n'; - } - sdp += 'a=ice-options:trickle\r\n'; - - pc.transceivers.forEach(function(transceiver, sdpMLineIndex) { - sdp += writeMediaSection(transceiver, transceiver.localCapabilities, - 'offer', transceiver.stream, pc._dtlsRole); - sdp += 'a=rtcp-rsize\r\n'; - - if (transceiver.iceGatherer && pc.iceGatheringState !== 'new' && - (sdpMLineIndex === 0 || !pc.usingBundle)) { - transceiver.iceGatherer.getLocalCandidates().forEach(function(cand) { - cand.component = 1; - sdp += 'a=' + SDPUtils.writeCandidate(cand) + '\r\n'; - }); - - if (transceiver.iceGatherer.state === 'completed') { - sdp += 'a=end-of-candidates\r\n'; - } - } - }); - - var desc = new window.RTCSessionDescription({ - type: 'offer', - sdp: sdp - }); - return Promise.resolve(desc); - }; - - RTCPeerConnection.prototype.createAnswer = function() { - var pc = this; - - if (pc._isClosed) { - return Promise.reject(makeError('InvalidStateError', - 'Can not call createAnswer after close')); - } - - if (!(pc.signalingState === 'have-remote-offer' || - pc.signalingState === 'have-local-pranswer')) { - return Promise.reject(makeError('InvalidStateError', - 'Can not call createAnswer in signalingState ' + pc.signalingState)); + if ((typeof window === "undefined" ? "undefined" : _typeof(window)) === 'object') { + if (logDisabled_) { + return; } - - var sdp = SDPUtils.writeSessionBoilerplate(pc._sdpSessionId, - pc._sdpSessionVersion++); - if (pc.usingBundle) { - sdp += 'a=group:BUNDLE ' + pc.transceivers.map(function(t) { - return t.mid; - }).join(' ') + '\r\n'; + if (typeof console !== 'undefined' && typeof console.log === 'function') { + console.log.apply(console, arguments); } - sdp += 'a=ice-options:trickle\r\n'; - - var mediaSectionsInOffer = SDPUtils.getMediaSections( - pc._remoteDescription.sdp).length; - pc.transceivers.forEach(function(transceiver, sdpMLineIndex) { - if (sdpMLineIndex + 1 > mediaSectionsInOffer) { - return; - } - if (transceiver.rejected) { - if (transceiver.kind === 'application') { - if (transceiver.protocol === 'DTLS/SCTP') { // legacy fmt - sdp += 'm=application 0 DTLS/SCTP 5000\r\n'; - } else { - sdp += 'm=application 0 ' + transceiver.protocol + - ' webrtc-datachannel\r\n'; - } - } else if (transceiver.kind === 'audio') { - sdp += 'm=audio 0 UDP/TLS/RTP/SAVPF 0\r\n' + - 'a=rtpmap:0 PCMU/8000\r\n'; - } else if (transceiver.kind === 'video') { - sdp += 'm=video 0 UDP/TLS/RTP/SAVPF 120\r\n' + - 'a=rtpmap:120 VP8/90000\r\n'; - } - sdp += 'c=IN IP4 0.0.0.0\r\n' + - 'a=inactive\r\n' + - 'a=mid:' + transceiver.mid + '\r\n'; - return; - } - - // FIXME: look at direction. - if (transceiver.stream) { - var localTrack; - if (transceiver.kind === 'audio') { - localTrack = transceiver.stream.getAudioTracks()[0]; - } else if (transceiver.kind === 'video') { - localTrack = transceiver.stream.getVideoTracks()[0]; - } - if (localTrack) { - // add RTX - if (edgeVersion >= 15019 && transceiver.kind === 'video' && - !transceiver.sendEncodingParameters[0].rtx) { - transceiver.sendEncodingParameters[0].rtx = { - ssrc: transceiver.sendEncodingParameters[0].ssrc + 1 - }; - } - } - } + } +} - // Calculate intersection of capabilities. - var commonCapabilities = getCommonCapabilities( - transceiver.localCapabilities, - transceiver.remoteCapabilities); +/** + * 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.'); +} - var hasRtx = commonCapabilities.codecs.filter(function(c) { - return c.name.toLowerCase() === 'rtx'; - }).length; - if (!hasRtx && transceiver.sendEncodingParameters[0].rtx) { - delete transceiver.sendEncodingParameters[0].rtx; - } +/** + * Browser detector. + * + * @return {object} result containing browser and version + * properties. + */ +function detectBrowser(window) { + // Returned result object. + var result = { + browser: null, + version: null + }; - sdp += writeMediaSection(transceiver, commonCapabilities, - 'answer', transceiver.stream, pc._dtlsRole); - if (transceiver.rtcpParameters && - transceiver.rtcpParameters.reducedSize) { - sdp += 'a=rtcp-rsize\r\n'; - } - }); + // Fail early if it's not a browser + if (typeof window === 'undefined' || !window.navigator || !window.navigator.userAgent) { + result.browser = 'Not a browser.'; + return result; + } + var navigator = window.navigator; - var desc = new window.RTCSessionDescription({ - type: 'answer', - sdp: sdp + // Prefer navigator.userAgentData. + if (navigator.userAgentData && navigator.userAgentData.brands) { + var chromium = navigator.userAgentData.brands.find(function (brand) { + return brand.brand === 'Chromium'; }); - return Promise.resolve(desc); - }; - - RTCPeerConnection.prototype.addIceCandidate = function(candidate) { - var pc = this; - var sections; - if (candidate && !(candidate.sdpMLineIndex !== undefined || - candidate.sdpMid)) { - return Promise.reject(new TypeError('sdpMLineIndex or sdpMid required')); + if (chromium) { + return { + browser: 'chrome', + version: parseInt(chromium.version, 10) + }; } + } + if (navigator.mozGetUserMedia) { + // Firefox. + result.browser = 'firefox'; + result.version = parseInt(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 = parseInt(extractVersion(navigator.userAgent, /Chrom(e|ium)\/(\d+)\./, 2)) || null; + } else if (window.RTCPeerConnection && navigator.userAgent.match(/AppleWebKit\/(\d+)\./)) { + // Safari. + result.browser = 'safari'; + result.version = parseInt(extractVersion(navigator.userAgent, /AppleWebKit\/(\d+)\./, 1)); + result.supportsUnifiedPlan = window.RTCRtpTransceiver && 'currentDirection' in window.RTCRtpTransceiver.prototype; + // Only for internal usage. + result._safariVersion = extractVersion(navigator.userAgent, /Version\/(\d+(\.?\d+))/, 1); + } else { + // Default fallthrough: not supported. + result.browser = 'Not a supported browser.'; + return result; + } + return result; +} - // TODO: needs to go into ops queue. - return new Promise(function(resolve, reject) { - if (!pc._remoteDescription) { - return reject(makeError('InvalidStateError', - 'Can not add ICE candidate without a remote description')); - } else if (!candidate || candidate.candidate === '') { - for (var j = 0; j < pc.transceivers.length; j++) { - if (pc.transceivers[j].rejected) { - continue; - } - pc.transceivers[j].iceTransport.addRemoteCandidate({}); - sections = SDPUtils.getMediaSections(pc._remoteDescription.sdp); - sections[j] += 'a=end-of-candidates\r\n'; - pc._remoteDescription.sdp = - SDPUtils.getDescription(pc._remoteDescription.sdp) + - sections.join(''); - if (pc.usingBundle) { - break; - } - } - } else { - var sdpMLineIndex = candidate.sdpMLineIndex; - if (candidate.sdpMid) { - for (var i = 0; i < pc.transceivers.length; i++) { - if (pc.transceivers[i].mid === candidate.sdpMid) { - sdpMLineIndex = i; - break; - } - } - } - var transceiver = pc.transceivers[sdpMLineIndex]; - if (transceiver) { - if (transceiver.rejected) { - return resolve(); - } - var cand = Object.keys(candidate.candidate).length > 0 ? - SDPUtils.parseCandidate(candidate.candidate) : {}; - // Ignore Chrome's invalid candidates since Edge does not like them. - if (cand.protocol === 'tcp' && (cand.port === 0 || cand.port === 9)) { - return resolve(); - } - // Ignore RTCP candidates, we assume RTCP-MUX. - if (cand.component && cand.component !== 1) { - return resolve(); - } - // when using bundle, avoid adding candidates to the wrong - // ice transport. And avoid adding candidates added in the SDP. - if (sdpMLineIndex === 0 || (sdpMLineIndex > 0 && - transceiver.iceTransport !== pc.transceivers[0].iceTransport)) { - if (!maybeAddCandidate(transceiver.iceTransport, cand)) { - return reject(makeError('OperationError', - 'Can not add ICE candidate')); - } - } - - // update the remoteDescription. - var candidateString = candidate.candidate.trim(); - if (candidateString.indexOf('a=') === 0) { - candidateString = candidateString.substr(2); - } - sections = SDPUtils.getMediaSections(pc._remoteDescription.sdp); - sections[sdpMLineIndex] += 'a=' + - (cand.type ? candidateString : 'end-of-candidates') - + '\r\n'; - pc._remoteDescription.sdp = - SDPUtils.getDescription(pc._remoteDescription.sdp) + - sections.join(''); - } else { - return reject(makeError('OperationError', - 'Can not add ICE candidate')); - } - } - resolve(); - }); - }; +/** + * 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]'; +} - RTCPeerConnection.prototype.getStats = function(selector) { - if (selector && selector instanceof window.MediaStreamTrack) { - var senderOrReceiver = null; - this.transceivers.forEach(function(transceiver) { - if (transceiver.rtpSender && - transceiver.rtpSender.track === selector) { - senderOrReceiver = transceiver.rtpSender; - } else if (transceiver.rtpReceiver && - transceiver.rtpReceiver.track === selector) { - senderOrReceiver = transceiver.rtpReceiver; - } - }); - if (!senderOrReceiver) { - throw makeError('InvalidAccessError', 'Invalid selector.'); - } - return senderOrReceiver.getStats(); +/** + * 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)); + }, {}); +} - var promises = []; - this.transceivers.forEach(function(transceiver) { - ['rtpSender', 'rtpReceiver', 'iceGatherer', 'iceTransport', - 'dtlsTransport'].forEach(function(method) { - if (transceiver[method]) { - promises.push(transceiver[method].getStats()); - } - }); - }); - return Promise.all(promises).then(function(allStats) { - var results = new Map(); - allStats.forEach(function(stats) { - stats.forEach(function(stat) { - results.set(stat.id, stat); - }); +/* 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); }); - return results; - }); - }; - - // fix low-level stat names and return Map instead of object. - var ortcObjects = ['RTCRtpSender', 'RTCRtpReceiver', 'RTCIceGatherer', - 'RTCIceTransport', 'RTCDtlsTransport']; - ortcObjects.forEach(function(ortcObjectName) { - var obj = window[ortcObjectName]; - if (obj && obj.prototype && obj.prototype.getStats) { - var nativeGetstats = obj.prototype.getStats; - obj.prototype.getStats = function() { - return nativeGetstats.apply(this) - .then(function(nativeStats) { - var mapStats = new Map(); - Object.keys(nativeStats).forEach(function(id) { - nativeStats[id].type = fixStatsType(nativeStats[id]); - mapStats.set(id, nativeStats[id]); - }); - return mapStats; - }); - }; } }); +} - // legacy callback shims. Should be moved to adapter.js some days. - var methods = ['createOffer', 'createAnswer']; - methods.forEach(function(method) { - var nativeMethod = RTCPeerConnection.prototype[method]; - RTCPeerConnection.prototype[method] = function() { - var args = arguments; - if (typeof args[0] === 'function' || - typeof args[1] === 'function') { // legacy - return nativeMethod.apply(this, [arguments[2]]) - .then(function(description) { - if (typeof args[0] === 'function') { - args[0].apply(null, [description]); - } - }, function(error) { - if (typeof args[1] === 'function') { - args[1].apply(null, [error]); - } - }); - } - return nativeMethod.apply(this, arguments); - }; - }); - - methods = ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate']; - methods.forEach(function(method) { - var nativeMethod = RTCPeerConnection.prototype[method]; - RTCPeerConnection.prototype[method] = function() { - var args = arguments; - if (typeof args[1] === 'function' || - typeof args[2] === 'function') { // legacy - return nativeMethod.apply(this, arguments) - .then(function() { - if (typeof args[1] === 'function') { - args[1].apply(null); - } - }, function(error) { - if (typeof args[2] === 'function') { - args[2].apply(null, [error]); - } - }); - } - return nativeMethod.apply(this, arguments); - }; +/* 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); + } }); - - // getStats is special. It doesn't have a spec legacy method yet we support - // getStats(something, cb) without error callbacks. - ['getStats'].forEach(function(method) { - var nativeMethod = RTCPeerConnection.prototype[method]; - RTCPeerConnection.prototype[method] = function() { - var args = arguments; - if (typeof args[1] === 'function') { - return nativeMethod.apply(this, arguments) - .then(function() { - if (typeof args[1] === 'function') { - args[1].apply(null); - } - }); + trackStats.forEach(function (trackStat) { + result.forEach(function (stats) { + if (stats.type === streamStatsType && stats.trackId === trackStat.id) { + walkStats(result, stats, filteredResult); } - return nativeMethod.apply(this, arguments); - }; + }); }); + return filteredResult; +} - return RTCPeerConnection; -}; - -},{"sdp":17}],17:[function(require,module,exports){ - /* eslint-env node */ +},{}],11:[function(require,module,exports){ +/* eslint-env node */ 'use strict'; // SDP helpers. -var SDPUtils = {}; + +const 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).substr(2, 10); +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(); - }); +SDPUtils.splitLines = function (blob) { + return blob.trim().split('\n').map(line => 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'; - }); +SDPUtils.splitSections = function (blob) { + const parts = blob.split('\nm='); + return parts.map((part, index) => (index > 0 ? 'm=' + part : part).trim() + '\r\n'); }; -// returns the session description. -SDPUtils.getDescription = function(blob) { - var sections = SDPUtils.splitSections(blob); +// Returns the session description. +SDPUtils.getDescription = function (blob) { + const sections = SDPUtils.splitSections(blob); return sections && sections[0]; }; -// returns the individual media sections. -SDPUtils.getMediaSections = function(blob) { - var sections = SDPUtils.splitSections(blob); +// Returns the individual media sections. +SDPUtils.getMediaSections = function (blob) { + const 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; - }); +SDPUtils.matchPrefix = function (blob, prefix) { + return SDPUtils.splitLines(blob).filter(line => 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" -SDPUtils.parseCandidate = function(line) { - var parts; +// Input can be prefixed with a=. +SDPUtils.parseCandidate = function (line) { + let parts; // Parse both variants. if (line.indexOf('a=candidate:') === 0) { parts = line.substring(12).split(' '); @@ -4674,9 +2562,9 @@ SDPUtils.parseCandidate = function(line) { parts = line.substring(10).split(' '); } - var candidate = { + const candidate = { foundation: parts[0], - component: parseInt(parts[1], 10), + component: { 1: 'rtp', 2: 'rtcp' }[parts[1]] || parts[1], protocol: parts[2].toLowerCase(), priority: parseInt(parts[3], 10), ip: parts[4], @@ -4686,7 +2574,7 @@ SDPUtils.parseCandidate = function(line) { type: parts[7] }; - for (var i = 8; i < parts.length; i += 2) { + for (let i = 8; i < parts.length; i += 2) { switch (parts[i]) { case 'raddr': candidate.relatedAddress = parts[i + 1]; @@ -4698,11 +2586,14 @@ SDPUtils.parseCandidate = function(line) { candidate.tcpType = parts[i + 1]; break; case 'ufrag': - candidate.ufrag = parts[i + 1]; // for backward compability. + candidate.ufrag = parts[i + 1]; // for backward compatibility. candidate.usernameFragment = parts[i + 1]; break; - default: // extension handling, in particular ufrag - candidate[parts[i]] = parts[i + 1]; + default: + // extension handling, in particular ufrag. Don't overwrite. + if (candidate[parts[i]] === undefined) { + candidate[parts[i]] = parts[i + 1]; + } break; } } @@ -4710,20 +2601,28 @@ SDPUtils.parseCandidate = function(line) { }; // Translates a candidate object into SDP candidate attribute. -SDPUtils.writeCandidate = function(candidate) { - var sdp = []; +// This does not include the a= prefix! +SDPUtils.writeCandidate = function (candidate) { + const sdp = []; sdp.push(candidate.foundation); - sdp.push(candidate.component); + + const 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; + const type = candidate.type; sdp.push('typ'); sdp.push(type); - if (type !== 'host' && candidate.relatedAddress && - candidate.relatedPort) { + if (type !== 'host' && candidate.relatedAddress && candidate.relatedPort !== undefined) { sdp.push('raddr'); sdp.push(candidate.relatedAddress); sdp.push('rport'); @@ -4741,16 +2640,17 @@ SDPUtils.writeCandidate = function(candidate) { }; // Parses an ice-options line, returns an array of option tags. +// Sample input: // a=ice-options:foo bar -SDPUtils.parseIceOptions = function(line) { - return line.substr(14).split(' '); +SDPUtils.parseIceOptions = function (line) { + return line.substring(14).split(' '); }; -// Parses an rtpmap line, returns RTCRtpCoddecParameters. Sample input: +// Parses a rtpmap line, returns RTCRtpCoddecParameters. Sample input: // a=rtpmap:111 opus/48000/2 -SDPUtils.parseRtpMap = function(line) { - var parts = line.substr(9).split(' '); - var parsed = { +SDPUtils.parseRtpMap = function (line) { + let parts = line.substring(9).split(' '); + const parsed = { payloadType: parseInt(parts.shift(), 10) // was: id }; @@ -4764,65 +2664,63 @@ SDPUtils.parseRtpMap = function(line) { return parsed; }; -// Generate an a=rtpmap line from RTCRtpCodecCapability or +// Generates a rtpmap line from RTCRtpCodecCapability or // RTCRtpCodecParameters. -SDPUtils.writeRtpMap = function(codec) { - var pt = codec.payloadType; +SDPUtils.writeRtpMap = function (codec) { + let 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'; + const channels = codec.channels || codec.numChannels || 1; + return 'a=rtpmap:' + pt + ' ' + codec.name + '/' + codec.clockRate + (channels !== 1 ? '/' + channels : '') + '\r\n'; }; -// Parses an a=extmap line (headerextension from RFC 5285). Sample input: +// 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.substr(9).split(' '); +SDPUtils.parseExtmap = function (line) { + const parts = line.substring(9).split(' '); return { id: parseInt(parts[0], 10), direction: parts[0].indexOf('/') > 0 ? parts[0].split('/')[1] : 'sendrecv', - uri: parts[1] + uri: parts[1], + attributes: parts.slice(2).join(' ') }; }; -// Generates a=extmap line from RTCRtpHeaderExtensionParameters or +// 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 + '\r\n'; +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 an ftmp line, returns dictionary. Sample input: +// 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; - var parts = line.substr(line.indexOf(' ') + 1).split(';'); - for (var j = 0; j < parts.length; j++) { +// Non-key-value such as telephone-events `0-15` get parsed as +// {`0-15`:undefined} +SDPUtils.parseFmtp = function (line) { + const parsed = {}; + let kv; + const parts = line.substring(line.indexOf(' ') + 1).split(';'); + for (let j = 0; j < parts.length; j++) { kv = parts[j].trim().split('='); parsed[kv[0].trim()] = kv[1]; } return parsed; }; -// Generates an a=ftmp line from RTCRtpCodecCapability or RTCRtpCodecParameters. -SDPUtils.writeFmtp = function(codec) { - var line = ''; - var pt = codec.payloadType; +// Generates a fmtp line from RTCRtpCodecCapability or RTCRtpCodecParameters. +SDPUtils.writeFmtp = function (codec) { + let line = ''; + let 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]) { + const params = []; + Object.keys(codec.parameters).forEach(param => { + if (codec.parameters[param] !== undefined) { params.push(param + '=' + codec.parameters[param]); } else { params.push(param); @@ -4833,85 +2731,83 @@ SDPUtils.writeFmtp = function(codec) { return line; }; -// Parses an rtcp-fb line, returns RTCPRtcpFeedback object. Sample input: +// Parses a rtcp-fb line, returns RTCPRtcpFeedback object. Sample input: // a=rtcp-fb:98 nack rpsi -SDPUtils.parseRtcpFb = function(line) { - var parts = line.substr(line.indexOf(' ') + 1).split(' '); +SDPUtils.parseRtcpFb = function (line) { + const 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; +SDPUtils.writeRtcpFb = function (codec) { + let lines = ''; + let 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'; + codec.rtcpFeedback.forEach(fb => { + lines += 'a=rtcp-fb:' + pt + ' ' + fb.type + (fb.parameter && fb.parameter.length ? ' ' + fb.parameter : '') + '\r\n'; }); } return lines; }; -// Parses an RFC 5576 ssrc media attribute. Sample input: +// 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.substr(7, sp - 7), 10) +SDPUtils.parseSsrcMedia = function (line) { + const sp = line.indexOf(' '); + const parts = { + ssrc: parseInt(line.substring(7, sp), 10) }; - var colon = line.indexOf(':', sp); + const colon = line.indexOf(':', sp); if (colon > -1) { - parts.attribute = line.substr(sp + 1, colon - sp - 1); - parts.value = line.substr(colon + 1); + parts.attribute = line.substring(sp + 1, colon); + parts.value = line.substring(colon + 1); } else { - parts.attribute = line.substr(sp + 1); + parts.attribute = line.substring(sp + 1); } return parts; }; -SDPUtils.parseSsrcGroup = function(line) { - var parts = line.substr(13).split(' '); +// Parse a ssrc-group line (see RFC 5576). Sample input: +// a=ssrc-group:semantics 12 34 +SDPUtils.parseSsrcGroup = function (line) { + const parts = line.substring(13).split(' '); return { semantics: parts.shift(), - ssrcs: parts.map(function(ssrc) { - return parseInt(ssrc, 10); - }) + ssrcs: parts.map(ssrc => 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]; +// Returns the MID or undefined if no mid line was found. +SDPUtils.getMid = function (mediaSection) { + const mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:')[0]; if (mid) { - return mid.substr(6); + return mid.substring(6); } }; -SDPUtils.parseFingerprint = function(line) { - var parts = line.substr(14).split(' '); +// Parses a fingerprint line for DTLS-SRTP. +SDPUtils.parseFingerprint = function (line) { + const parts = line.substring(14).split(' '); return { algorithm: parts[0].toLowerCase(), // algorithm is case-sensitive in Edge. - value: parts[1] + 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. - // Note2: 'algorithm' is not case sensitive except in Edge. +SDPUtils.getDtlsParameters = function (mediaSection, sessionpart) { + const 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) @@ -4919,60 +2815,101 @@ SDPUtils.getDtlsParameters = function(mediaSection, sessionpart) { }; // Serializes DTLS parameters to SDP. -SDPUtils.writeDtlsParameters = function(params, setupType) { - var sdp = 'a=setup:' + setupType + '\r\n'; - params.fingerprints.forEach(function(fp) { +SDPUtils.writeDtlsParameters = function (params, setupType) { + let sdp = 'a=setup:' + setupType + '\r\n'; + params.fingerprints.forEach(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) { + const 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; + } + const 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) { + const 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 lines = SDPUtils.splitLines(mediaSection); - // Search in session part, too. - lines = lines.concat(SDPUtils.splitLines(sessionpart)); - var iceParameters = { - usernameFragment: lines.filter(function(line) { - return line.indexOf('a=ice-ufrag:') === 0; - })[0].substr(12), - password: lines.filter(function(line) { - return line.indexOf('a=ice-pwd:') === 0; - })[0].substr(10) +SDPUtils.getIceParameters = function (mediaSection, sessionpart) { + const ufrag = SDPUtils.matchPrefix(mediaSection + sessionpart, 'a=ice-ufrag:')[0]; + const pwd = SDPUtils.matchPrefix(mediaSection + sessionpart, 'a=ice-pwd:')[0]; + if (!(ufrag && pwd)) { + return null; + } + return { + usernameFragment: ufrag.substring(12), + password: pwd.substring(10) }; - return iceParameters; }; // Serializes ICE parameters to SDP. -SDPUtils.writeIceParameters = function(params) { - return 'a=ice-ufrag:' + params.usernameFragment + '\r\n' + - 'a=ice-pwd:' + params.password + '\r\n'; +SDPUtils.writeIceParameters = function (params) { + let 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 = { +SDPUtils.parseRtpParameters = function (mediaSection) { + const description = { codecs: [], headerExtensions: [], fecMechanisms: [], rtcp: [] }; - var lines = SDPUtils.splitLines(mediaSection); - var mline = lines[0].split(' '); - 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]; + const lines = SDPUtils.splitLines(mediaSection); + const mline = lines[0].split(' '); + description.profile = mline[2]; + for (let i = 3; i < mline.length; i++) { + // find all codecs from mline[3..] + const pt = mline[i]; + const rtpmapline = SDPUtils.matchPrefix(mediaSection, 'a=rtpmap:' + pt + ' ')[0]; if (rtpmapline) { - var codec = SDPUtils.parseRtpMap(rtpmapline); - var fmtps = SDPUtils.matchPrefix( - mediaSection, 'a=fmtp:' + pt + ' '); + const codec = SDPUtils.parseRtpMap(rtpmapline); + const 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); + 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()) { @@ -4980,28 +2917,40 @@ SDPUtils.parseRtpParameters = function(mediaSection) { case 'ULPFEC': description.fecMechanisms.push(codec.name.toUpperCase()); break; - default: // only RED and ULPFEC are recognized as FEC mechanisms. + default: + // only RED and ULPFEC are recognized as FEC mechanisms. break; } } } - SDPUtils.matchPrefix(mediaSection, 'a=extmap:').forEach(function(line) { + SDPUtils.matchPrefix(mediaSection, 'a=extmap:').forEach(line => { description.headerExtensions.push(SDPUtils.parseExtmap(line)); }); + const wildcardRtcpFb = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-fb:* ').map(SDPUtils.parseRtcpFb); + description.codecs.forEach(codec => { + wildcardRtcpFb.forEach(fb => { + const duplicate = codec.rtcpFeedback.find(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 = ''; +SDPUtils.writeRtpDescription = function (kind, caps) { + let sdp = ''; // Build the mline. sdp += 'm=' + kind + ' '; sdp += caps.codecs.length > 0 ? '9' : '0'; // reject if no codecs. - sdp += ' UDP/TLS/RTP/SAVPF '; - sdp += caps.codecs.map(function(codec) { + sdp += ' ' + (caps.profile || 'UDP/TLS/RTP/SAVPF') + ' '; + sdp += caps.codecs.map(codec => { if (codec.preferredPayloadType !== undefined) { return codec.preferredPayloadType; } @@ -5012,13 +2961,13 @@ SDPUtils.writeRtpDescription = function(kind, caps) { 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) { + caps.codecs.forEach(codec => { sdp += SDPUtils.writeRtpMap(codec); sdp += SDPUtils.writeFmtp(codec); sdp += SDPUtils.writeRtcpFb(codec); }); - var maxptime = 0; - caps.codecs.forEach(function(codec) { + let maxptime = 0; + caps.codecs.forEach(codec => { if (codec.maxptime > maxptime) { maxptime = codec.maxptime; } @@ -5026,10 +2975,9 @@ SDPUtils.writeRtpDescription = function(kind, caps) { if (maxptime > 0) { sdp += 'a=maxptime:' + maxptime + '\r\n'; } - sdp += 'a=rtcp-mux\r\n'; if (caps.headerExtensions) { - caps.headerExtensions.forEach(function(extension) { + caps.headerExtensions.forEach(extension => { sdp += SDPUtils.writeExtmap(extension); }); } @@ -5039,42 +2987,33 @@ SDPUtils.writeRtpDescription = function(kind, caps) { // 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; +SDPUtils.parseRtpEncodingParameters = function (mediaSection) { + const encodingParameters = []; + const description = SDPUtils.parseRtpParameters(mediaSection); + const hasRed = description.fecMechanisms.indexOf('RED') !== -1; + const 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; - - var flows = SDPUtils.matchPrefix(mediaSection, 'a=ssrc-group:FID') - .map(function(line) { - var parts = line.substr(17).split(' '); - return parts.map(function(part) { - return parseInt(part, 10); - }); + const ssrcs = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:').map(line => SDPUtils.parseSsrcMedia(line)).filter(parts => parts.attribute === 'cname'); + const primarySsrc = ssrcs.length > 0 && ssrcs[0].ssrc; + let secondarySsrc; + + const flows = SDPUtils.matchPrefix(mediaSection, 'a=ssrc-group:FID').map(line => { + const parts = line.substring(17).split(' '); + return parts.map(part => parseInt(part, 10)); }); if (flows.length > 0 && flows[0].length > 1 && flows[0][0] === primarySsrc) { secondarySsrc = flows[0][1]; } - description.codecs.forEach(function(codec) { + description.codecs.forEach(codec => { if (codec.name.toUpperCase() === 'RTX' && codec.parameters.apt) { - var encParam = { + let encParam = { ssrc: primarySsrc, codecPayloadType: parseInt(codec.parameters.apt, 10) }; if (primarySsrc && secondarySsrc) { - encParam.rtx = {ssrc: secondarySsrc}; + encParam.rtx = { ssrc: secondarySsrc }; } encodingParameters.push(encParam); if (hasRed) { @@ -5094,18 +3033,17 @@ SDPUtils.parseRtpEncodingParameters = function(mediaSection) { } // we support both b=AS and b=TIAS but interpret AS as TIAS. - var bandwidth = SDPUtils.matchPrefix(mediaSection, 'b='); + let bandwidth = SDPUtils.matchPrefix(mediaSection, 'b='); if (bandwidth.length) { if (bandwidth[0].indexOf('b=TIAS:') === 0) { - bandwidth = parseInt(bandwidth[0].substr(7), 10); + 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].substr(5), 10) * 1000 * 0.95 - - (50 * 40 * 8); + bandwidth = parseInt(bandwidth[0].substring(5), 10) * 1000 * 0.95 - 50 * 40 * 8; } else { bandwidth = undefined; } - encodingParameters.forEach(function(params) { + encodingParameters.forEach(params => { params.maxBitrate = bandwidth; }); } @@ -5113,18 +3051,12 @@ SDPUtils.parseRtpEncodingParameters = function(mediaSection) { }; // parses http://draft.ortc.org/#rtcrtcpparameters* -SDPUtils.parseRtcpParameters = function(mediaSection) { - var rtcpParameters = {}; +SDPUtils.parseRtcpParameters = function (mediaSection) { + const rtcpParameters = {}; - // Gets the first SSRC. Note tha with RTX there might be multiple + // 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]; + const remoteSsrc = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:').map(line => SDPUtils.parseSsrcMedia(line)).filter(obj => obj.attribute === 'cname')[0]; if (remoteSsrc) { rtcpParameters.cname = remoteSsrc.value; rtcpParameters.ssrc = remoteSsrc.ssrc; @@ -5132,137 +3064,137 @@ SDPUtils.parseRtcpParameters = function(mediaSection) { // Edge uses the compound attribute instead of reducedSize // compound is !reducedSize - var rsize = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-rsize'); + const 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'); + const mux = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-mux'); rtcpParameters.mux = mux.length > 0; return rtcpParameters; }; +SDPUtils.writeRtcpParameters = function (rtcpParameters) { + let 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; - var spec = SDPUtils.matchPrefix(mediaSection, 'a=msid:'); +SDPUtils.parseMsid = function (mediaSection) { + let parts; + const spec = SDPUtils.matchPrefix(mediaSection, 'a=msid:'); if (spec.length === 1) { - parts = spec[0].substr(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'; - }); + parts = spec[0].substring(7).split(' '); + return { stream: parts[0], track: parts[1] }; + } + const planB = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:').map(line => SDPUtils.parseSsrcMedia(line)).filter(msidParts => msidParts.attribute === 'msid'); if (planB.length > 0) { parts = planB[0].value.split(' '); - return {stream: parts[0], track: parts[1]}; + 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) { + const mline = SDPUtils.parseMLine(mediaSection); + const maxSizeLine = SDPUtils.matchPrefix(mediaSection, 'a=max-message-size:'); + let maxMessageSize; + if (maxSizeLine.length > 0) { + maxMessageSize = parseInt(maxSizeLine[0].substring(19), 10); + } + if (isNaN(maxMessageSize)) { + maxMessageSize = 65536; + } + const sctpPort = SDPUtils.matchPrefix(mediaSection, 'a=sctp-port:'); + if (sctpPort.length > 0) { + return { + port: parseInt(sctpPort[0].substring(12), 10), + protocol: mline.fmt, + maxMessageSize + }; + } + const sctpMapLines = SDPUtils.matchPrefix(mediaSection, 'a=sctpmap:'); + if (sctpMapLines.length > 0) { + const parts = sctpMapLines[0].substring(10).split(' '); + return { + port: parseInt(parts[0], 10), + protocol: parts[1], + 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) { + let 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, 21); +SDPUtils.generateSessionId = function () { + return Math.random().toString().substr(2, 22); }; -// Write boilder plate for start of SDP +// 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; - var version = sessVer !== undefined ? sessVer : 2; +SDPUtils.writeSessionBoilerplate = function (sessId, sessVer, sessUser) { + let sessionId; + const version = sessVer !== undefined ? sessVer : 2; if (sessId) { sessionId = sessId; } else { sessionId = SDPUtils.generateSessionId(); } - var user = sessUser || 'thisisadapterortc'; + const 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'; -}; - -SDPUtils.writeMediaSection = function(transceiver, caps, type, stream) { - var sdp = SDPUtils.writeRtpDescription(transceiver.kind, caps); - - // Map ICE parameters (ufrag, pwd) to SDP. - sdp += SDPUtils.writeIceParameters( - transceiver.iceGatherer.getLocalParameters()); - - // Map DTLS parameters to SDP. - sdp += SDPUtils.writeDtlsParameters( - transceiver.dtlsTransport.getLocalParameters(), - type === 'offer' ? 'actpass' : 'active'); - - sdp += 'a=mid:' + transceiver.mid + '\r\n'; - - if (transceiver.direction) { - sdp += 'a=' + transceiver.direction + '\r\n'; - } else if (transceiver.rtpSender && transceiver.rtpReceiver) { - sdp += 'a=sendrecv\r\n'; - } else if (transceiver.rtpSender) { - sdp += 'a=sendonly\r\n'; - } else if (transceiver.rtpReceiver) { - sdp += 'a=recvonly\r\n'; - } else { - sdp += 'a=inactive\r\n'; - } - - if (transceiver.rtpSender) { - // spec. - var msid = 'msid:' + stream.id + ' ' + - transceiver.rtpSender.track.id + '\r\n'; - sdp += 'a=' + msid; - - // for Chrome. - sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc + - ' ' + msid; - if (transceiver.sendEncodingParameters[0].rtx) { - sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc + - ' ' + msid; - sdp += 'a=ssrc-group:FID ' + - transceiver.sendEncodingParameters[0].ssrc + ' ' + - transceiver.sendEncodingParameters[0].rtx.ssrc + - '\r\n'; - } - } - // FIXME: this should be written by writeRtpDescription. - sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].ssrc + - ' cname:' + SDPUtils.localCName + '\r\n'; - if (transceiver.rtpSender && transceiver.sendEncodingParameters[0].rtx) { - sdp += 'a=ssrc:' + transceiver.sendEncodingParameters[0].rtx.ssrc + - ' cname:' + SDPUtils.localCName + '\r\n'; - } - return sdp; + 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) { +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++) { + const lines = SDPUtils.splitLines(mediaSection); + for (let i = 0; i < lines.length; i++) { switch (lines[i]) { case 'a=sendrecv': case 'a=sendonly': case 'a=recvonly': case 'a=inactive': - return lines[i].substr(2); + return lines[i].substring(2); default: - // FIXME: What should happen here? + // FIXME: What should happen here? } } if (sessionpart) { @@ -5271,19 +3203,19 @@ SDPUtils.getDirection = function(mediaSection, sessionpart) { return 'sendrecv'; }; -SDPUtils.getKind = function(mediaSection) { - var lines = SDPUtils.splitLines(mediaSection); - var mline = lines[0].split(' '); - return mline[0].substr(2); +SDPUtils.getKind = function (mediaSection) { + const lines = SDPUtils.splitLines(mediaSection); + const mline = lines[0].split(' '); + return mline[0].substring(2); }; -SDPUtils.isRejected = function(mediaSection) { +SDPUtils.isRejected = function (mediaSection) { return mediaSection.split(' ', 2)[1] === '0'; }; -SDPUtils.parseMLine = function(mediaSection) { - var lines = SDPUtils.splitLines(mediaSection); - var parts = lines[0].substr(2).split(' '); +SDPUtils.parseMLine = function (mediaSection) { + const lines = SDPUtils.splitLines(mediaSection); + const parts = lines[0].substring(2).split(' '); return { kind: parts[0], port: parseInt(parts[1], 10), @@ -5292,9 +3224,9 @@ SDPUtils.parseMLine = function(mediaSection) { }; }; -SDPUtils.parseOLine = function(mediaSection) { - var line = SDPUtils.matchPrefix(mediaSection, 'o=')[0]; - var parts = line.substr(2).split(' '); +SDPUtils.parseOLine = function (mediaSection) { + const line = SDPUtils.matchPrefix(mediaSection, 'o=')[0]; + const parts = line.substring(2).split(' '); return { username: parts[0], sessionId: parts[1], @@ -5306,12 +3238,12 @@ SDPUtils.parseOLine = function(mediaSection) { }; // a very naive interpretation of a valid SDP. -SDPUtils.isValidSDP = function(blob) { +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++) { + const lines = SDPUtils.splitLines(blob); + for (let i = 0; i < lines.length; i++) { if (lines[i].length < 2 || lines[i].charAt(1) !== '=') { return false; } @@ -5324,6 +3256,5 @@ SDPUtils.isValidSDP = function(blob) { if (typeof module === 'object') { module.exports = SDPUtils; } - },{}]},{},[1])(1) }); diff --git a/resources/static/adc_microphone.js b/resources/static/adc_microphone.js index d9de636..7916152 100644 --- a/resources/static/adc_microphone.js +++ b/resources/static/adc_microphone.js @@ -1,38 +1,141 @@ +var audioRecorder; + +/* +* Helper: return a supported audio mime type for this browser +*/ +function getSupportedAudioMimeType() { + if (typeof MediaRecorder === "undefined" || !MediaRecorder.isTypeSupported) { + return ""; + } + + var mimeTypes = [ + "audio/webm;codecs=opus", + "audio/webm", + "audio/mp4", + "audio/ogg;codecs=opus", + "audio/ogg" + ]; + + for (var i = 0; i < mimeTypes.length; i++) { + if (MediaRecorder.isTypeSupported(mimeTypes[i])) { + return mimeTypes[i]; + } + } + + return ""; +} + +/* +* Helper: map mime type to file extension +*/ +function getAudioFileExtension(blob) { + if (!blob || !blob.type) { + return "webm"; + } + + var mimeType = blob.type.toLowerCase(); + + if (mimeType.indexOf("webm") > -1) { + return "webm"; + } + if (mimeType.indexOf("mp4") > -1 || mimeType.indexOf("aac") > -1 || mimeType.indexOf("mpeg") > -1) { + return "mp4"; + } + if (mimeType.indexOf("ogg") > -1) { + return "ogg"; + } + if (mimeType.indexOf("wav") > -1) { + return "wav"; + } + + return "webm"; +} + +/* +* Helper: return a filename for the current audio blob +*/ +function getAudioFileName(blob, baseName) { + return (baseName || "my_audio") + "." + getAudioFileExtension(blob); +} + +/* +* Helper: safely set srcObject for media elements +*/ +function setSrcObject(stream, element) { + if ("srcObject" in element) { + element.srcObject = stream; + } else { + element.src = window.URL.createObjectURL(stream); + } +} + +/* +* Helper: safely clear live stream / source from player +*/ +function clearPlayerSource(player) { + try { + player.pause(); + } catch (e) {} + + if ("srcObject" in player && player.srcObject) { + player.srcObject = null; + } + + player.removeAttribute("src"); + + try { + player.load(); + } catch (e) {} +} + +/* +* Helper: stop all tracks on a stream +*/ +function stopStreamTracks(stream) { + if (!stream || !stream.getTracks) { + return; + } + + var tracks = stream.getTracks(); + for (var i = 0; i < tracks.length; i++) { + try { + tracks[i].stop(); + } catch (e) {} + } +} + function showOverlay(instanceId){ - removeClass(getElementByDynamicId("overlay_loader", instanceId),'hidden'); + removeClass(getElementByDynamicId("overlay_loader", instanceId), 'hidden'); } function hideOverlay(instanceId){ - addClass(getElementByDynamicId("overlay_loader", instanceId),'hidden'); + addClass(getElementByDynamicId("overlay_loader", instanceId), 'hidden'); } /* -* Upload the captured photo on the server +* Upload the captured audio on the server * @param {Integer} instanceId ID of the current adc */ function uploadAudio(instanceId){ - hideErrorMessage(instanceId); + hideErrorMessage(instanceId); hideSuccessMessage(instanceId); if (!uploadConfig(instanceId).apiKey || !uploadConfig(instanceId).secretKey) { - displayErrorMessage(uploadConfig(instanceId).ErrMsgInvalidApiSecretKeys, instanceId); + displayErrorMessage(uploadConfig(instanceId).ErrMsgInvalidApiSecretKeys, instanceId); return; } - if(audioRecorder != undefined){ // if audio has been recorded - var audioBlob; - audioBlob = audioRecorder.blob; - if(validFileSize(instanceId, audioBlob)){ + if (audioRecorder != undefined && audioRecorder.blob) { + var audioBlob = audioRecorder.blob; + if (validFileSize(instanceId, audioBlob)) { generateNewToken(function(token){ - uploadConfig(instanceId).token=token; + uploadConfig(instanceId).token = token; sendFileTransferCall(instanceId, audioBlob); }, instanceId); - } - else{ + } else { displayErrorMessage(uploadConfig(instanceId).ErrMsgFileSizeExceeded, instanceId); } - } - else{ + } else { displayErrorMessage(uploadConfig(instanceId).ErrMsgSelectFile, instanceId); } } @@ -45,6 +148,7 @@ function uploadAudio(instanceId){ function validFileSize(instanceId, fileData){ var filesize = 0; var maxsize = uploadConfig(instanceId).maxfilesize; + if (fileData) { filesize = fileData.size / 1024; } @@ -61,7 +165,6 @@ function validFileSize(instanceId, fileData){ * @param {Integer} instanceId ID of the current adc */ function generateNewToken(callback, instanceId) { - var data = { ApiKey: uploadConfig(instanceId).apiKey, SecretKey: uploadConfig(instanceId).secretKey @@ -72,33 +175,32 @@ function generateNewToken(callback, instanceId) { var generateTokenSuccess = function (token) { callback(token); }; - var generateTokenError = function (error) { + + var generateTokenError = function () { hideOverlay(instanceId); displayErrorMessage(uploadConfig(instanceId).ErrMsgInvalidApiSecretKeys, instanceId); }; - var generateTokenBeforeSend=function(){ + + var generateTokenBeforeSend = function(){ showOverlay(instanceId); }; - sendAjaxPostCall(url, data, true, generateTokenSuccess, generateTokenError,generateTokenBeforeSend); - + sendAjaxPostCall(url, data, true, generateTokenSuccess, generateTokenError, generateTokenBeforeSend); } /* * Generates right url, success and error callbacks and transfers it to sendAjaxPostCall function * @param {Integer} instanceId ID of the current adc * @param {Data} fileData Data from the current file -* @param {String} type Type of file (video or photo) */ function sendFileTransferCall(instanceId, fileData) { - if(!uploadConfig(instanceId).token){ + if (!uploadConfig(instanceId).token) { displayErrorMessage(uploadConfig(instanceId).ErrMsgToken, instanceId); return; } var projectName = uploadConfig(instanceId).ausProjectName; - //var fileData = getElementByDynamicId("adc_uploader", instanceId).files[0]; - var fileDataName = "file-name.mpeg"; + var fileDataName = getAudioFileName(fileData, "file-name"); var shortcut = uploadConfig(instanceId).shortcut; var seed = uploadConfig(instanceId).seedvalue; var guid = uploadConfig(instanceId).guidstring; @@ -107,32 +209,37 @@ function sendFileTransferCall(instanceId, fileData) { if (guid.charAt(0) == "{") guid = guid.substr(1); if (guid.charAt(guid.length - 1) == "}") guid = guid.substr(0, guid.length - 1); - var url=uploadConfig(instanceId).uploadUrl + "?tokenkey=" + uploadConfig(instanceId).token + "&filename=" + fileDataName - + "&projectname=" + projectName + "&shortcut=" + shortcut + "&seed=" + seed + "&guid=" + guid; + var url = uploadConfig(instanceId).uploadUrl + + "?tokenkey=" + encodeURIComponent(uploadConfig(instanceId).token) + + "&filename=" + encodeURIComponent(fileDataName) + + "&projectname=" + encodeURIComponent(projectName) + + "&shortcut=" + encodeURIComponent(shortcut) + + "&seed=" + encodeURIComponent(seed) + + "&guid=" + encodeURIComponent(guid); var uploadSuccessCallback = function (response) { - getElementByDynamicId("HidResult", instanceId).value=response.DestinationFileName; + getElementByDynamicId("HidResult", instanceId).value = response.DestinationFileName; displaySuccessMessage(uploadConfig(instanceId).SuccessMsgUpload, uploadConfig(instanceId).SuccessMsgColor, instanceId); hideOverlay(instanceId); + if (uploadConfig(instanceId).disabledUploadBtn == 1) { - disableUploadBtn(instanceId); + disableUploadBtn(instanceId); } + if (uploadConfig(instanceId).AutoSubmitAfterUpload == 1) { document.getElementsByTagName("form")[0].submit(); - } else { - if (uploadConfig(instanceId).EnabledNextAfterUpload == 1) { - document.getElementsByName("Next")[0].hidden = false; - } + } else if (uploadConfig(instanceId).EnabledNextAfterUpload == 1) { + document.getElementsByName("Next")[0].hidden = false; } }; - var uploadErrorCallback = function (error) { - getElementByDynamicId("HidResult", instanceId).value=''; + + var uploadErrorCallback = function () { + getElementByDynamicId("HidResult", instanceId).value = ''; displayErrorMessage(uploadConfig(instanceId).ErrMsgErrorAtUpload, instanceId); hideOverlay(instanceId); }; sendAjaxPostCall(url, fileData, false, uploadSuccessCallback, uploadErrorCallback); - } /* @@ -144,9 +251,10 @@ function sendFileTransferCall(instanceId, fileData) { * @param {Function} errorCallback Function to execute after unsuccessful request * @param {Function} beforeSend Function to execute before sending request (optional) */ -function sendAjaxPostCall(url, data, isJsonRequest, successCallback, errorCallback,beforeSend) { +function sendAjaxPostCall(url, data, isJsonRequest, successCallback, errorCallback, beforeSend) { var http = new XMLHttpRequest(); http.open("POST", url, true); + if (isJsonRequest) { http.setRequestHeader("Content-type", "application/json"); data = JSON.stringify(data); @@ -154,18 +262,26 @@ function sendAjaxPostCall(url, data, isJsonRequest, successCallback, errorCallba http.onreadystatechange = function () { if (http.readyState == 4) { + var response = null; + + try { + response = JSON.parse(http.responseText); + } catch (e) { + response = http.responseText; + } + if (http.status == 200) { - var response = JSON.parse(http.responseText); successCallback(response); } else { - var response = JSON.parse(http.responseText); errorCallback(response); } } - } - if(beforeSend){ + }; + + if (beforeSend) { beforeSend(); } + http.send(data); } @@ -174,8 +290,8 @@ function sendAjaxPostCall(url, data, isJsonRequest, successCallback, errorCallba * @param {HTMLElement} ele Element from the HTML * @param {String} cls A class */ -function hasClass(ele,cls) { - return ele.className.match(new RegExp('(\\s|^)'+cls+'(\\s|$)')); +function hasClass(ele, cls) { + return ele.className.match(new RegExp('(\\s|^)' + cls + '(\\s|$)')); } /* @@ -183,10 +299,10 @@ function hasClass(ele,cls) { * @param {HTML Element} ele Element from the HTML * @param {String} cls A class */ -function removeClass(ele,cls) { - if (hasClass(ele,cls)) { - var reg = new RegExp('(\\s|^)'+cls+'(\\s|$)'); - ele.className=ele.className.replace(reg,' '); +function removeClass(ele, cls) { + if (hasClass(ele, cls)) { + var reg = new RegExp('(\\s|^)' + cls + '(\\s|$)'); + ele.className = ele.className.replace(reg, ' '); } } @@ -195,24 +311,26 @@ function removeClass(ele,cls) { * @param {HTML Element} ele Element from the HTML * @param {String} cls A class */ -function addClass(ele,cls) { - if (!hasClass(ele,cls)) { - ele.className += ' '+ cls; +function addClass(ele, cls) { + if (!hasClass(ele, cls)) { + ele.className += ' ' + cls; } } /* * Displays an error message * @param {String} message The message to display -* @parem {Integer} instanceId ID of the current adc +* @param {Integer} instanceId ID of the current adc */ function displayErrorMessage(message, instanceId){ hideOverlay(instanceId); var div = getElementByDynamicId("adc-errdiv", instanceId); addClass(div, "askia-errors-summary"); div.style.marginBottom = "50px"; + var ul = getElementByDynamicId("ulErrorMessages", instanceId); ul.innerHTML = ""; + var li = document.createElement("li"); li.appendChild(document.createTextNode(message)); ul.appendChild(li); @@ -227,13 +345,14 @@ function displayErrorMessage(message, instanceId){ function displaySuccessMessage(message, colorcode, instanceId){ hideOverlay(instanceId); var div = getElementByDynamicId("adc-succdiv", instanceId); - div.style.backgroundColor= 'rgb(' + colorcode + ')'; + div.style.backgroundColor = 'rgb(' + colorcode + ')'; div.style.color = 'white'; div.style.width = '100%'; div.style.paddingTop = '15px'; div.style.paddingBottom = '15px'; div.style.marginBottom = '50px'; div.style.borderRadius = '3px'; + var span = getElementByDynamicId("spanSuccessMessage", instanceId); span.innerHTML = message; } @@ -244,14 +363,16 @@ function displaySuccessMessage(message, colorcode, instanceId){ */ function disableUploadBtn(instanceId) { var btn = getElementByDynamicId("btnUpload", instanceId); + if (btn.hasAttribute("disabled")) { - btn.setAttribute("disabled","disabled"); + btn.setAttribute("disabled", "disabled"); } else { var att = document.createAttribute("disabled"); att.value = "disabled"; btn.setAttributeNode(att); } - addClass(btn,"disabled"); + + addClass(btn, "disabled"); btn.style.cursor = "not-allowed"; } @@ -261,10 +382,12 @@ function disableUploadBtn(instanceId) { */ function enableUploadBtn(instanceId) { var btn = getElementByDynamicId("btnUpload", instanceId); + if (btn.hasAttribute("disabled")) { btn.removeAttribute("disabled"); } - removeClass(btn,"disabled"); + + removeClass(btn, "disabled"); btn.style.cursor = "pointer"; } @@ -275,6 +398,7 @@ function enableUploadBtn(instanceId) { function hideSuccessMessage(instanceId) { var div = getElementByDynamicId("adc-succdiv", instanceId); div.removeAttribute("style"); + var span = getElementByDynamicId("spanSuccessMessage", instanceId); span.innerHTML = ""; } @@ -287,6 +411,7 @@ function hideErrorMessage(instanceId){ var div = getElementByDynamicId("adc-errdiv", instanceId); removeClass(div, "askia-errors-summary"); div.removeAttribute("style"); + var ul = getElementByDynamicId("ulErrorMessages", instanceId); ul.innerHTML = ""; } @@ -304,7 +429,7 @@ function getElementByDynamicId(elementId, instanceId) { * @param {Integer} instanceId ID of the current adc */ function uploadConfig(instanceId) { - return eval('uploadConfig_' + instanceId); + return eval('uploadConfig_' + instanceId); } /* @@ -318,74 +443,145 @@ function activateBtnUpload(instanceId) { } /* -* Start the record of audio, displays the stream on screen, storing the stream in audioRecorder variable +* Start the recording of audio * @param {Integer} instanceId ID of the current adc */ function startRecordingAudio(instanceId) { - document.getElementById('btn-start-recording').disabled = true; - var player = document.getElementById('player'); + hideErrorMessage(instanceId); + hideSuccessMessage(instanceId); + + var startBtn = getElementByDynamicId("btn-start-recording", instanceId); + var stopBtn = getElementByDynamicId("btn-stop-recording", instanceId); + var player = getElementByDynamicId("player", instanceId); + var btnSave = getElementByDynamicId("btnSave", instanceId); + + startBtn.disabled = true; + stopBtn.disabled = true; + if (uploadConfig(instanceId).allowUploadFileChange == 1) { enableUploadBtn(instanceId); } + + if (!navigator.mediaDevices || !navigator.mediaDevices.getUserMedia) { + displayErrorMessage(uploadConfig(instanceId).ErrMsgUserMediaAccess, instanceId); + startBtn.disabled = false; + return; + } + navigator.mediaDevices.getUserMedia({ audio: true, video: false }).then(function(stream) { + clearPlayerSource(player); setSrcObject(stream, player); - if (!navigator.userAgent.toUpperCase().includes("IPAD")) { - player.play(); - player.muted = true; - audioRecorder = new RecordRTCPromisesHandler(stream, { - mimeType: 'audio/mpeg', - type: 'audio', - audioBitsPerSecond: 128000 - }); - audioRecorder.startRecording().then(function() { - console.info('Recording audio ...'); - }).catch(function(error) { - displayErrorMessage(uploadConfig(instanceId).ErrMsgStartRec, instanceId); - console.error('Cannot start audio recording: ', error); + player.muted = true; + + var recorderOptions = { + type: "audio", + audioBitsPerSecond: 128000 + }; + + var supportedMimeType = getSupportedAudioMimeType(); + if (supportedMimeType) { + recorderOptions.mimeType = supportedMimeType; + } + + audioRecorder = new RecordRTCPromisesHandler(stream, recorderOptions); + audioRecorder.stream = stream; + + var playPromise; + try { + playPromise = player.play(); + } catch (e) {} + + if (playPromise && typeof playPromise.catch === "function") { + playPromise.catch(function() { + // ignore autoplay failures for live preview }); - audioRecorder.stream = stream; } - document.getElementById('btn-stop-recording').disabled = false; - }).catch(function(error) { - displayErrorMessage(uploadConfig(instanceId).ErrMsgUserMediaAccess, instanceId); - console.error("Cannot access media devices: ", error); - }); - removeClass(document.getElementById("label-start"), "primary"); - addClass(document.getElementById("label-stop"), "primary"); + + return audioRecorder.startRecording(); + }).then(function() { + console.info("Recording audio ..."); + stopBtn.disabled = false; + }).catch(function(error) { + var debugMsg = "START FAILED\n"; + + debugMsg += "Error: " + (error && error.message ? error.message : error) + "\n"; + + debugMsg += "MediaRecorder: " + (typeof MediaRecorder !== "undefined") + "\n"; + + if (typeof MediaRecorder !== "undefined") { + debugMsg += "webm: " + MediaRecorder.isTypeSupported("audio/webm;codecs=opus") + "\n"; + debugMsg += "webm(no codec): " + MediaRecorder.isTypeSupported("audio/webm") + "\n"; + debugMsg += "mp4: " + MediaRecorder.isTypeSupported("audio/mp4") + "\n"; + debugMsg += "ogg: " + MediaRecorder.isTypeSupported("audio/ogg") + "\n"; + } + + // SHOW IT DIRECTLY IN THE UI + displayErrorMessage(debugMsg, instanceId); + + console.error("Cannot start audio recording:", error); + }); + removeClass(player, "saved"); - var btnSave = getElementByDynamicId("btnSave",instanceId); - if(btnSave){ + + if (btnSave) { btnSave.disabled = false; } - } +} /* -* Stop the audio recording, diplays the recorded audio on screen and stores the audio in audioRecorder variable +* Stop the audio recording, display the recorded audio, and free the stream * @param {Integer} instanceId ID of the current adc */ function stopRecordingAudio(instanceId) { - removeClass(document.getElementById("label-stop"), "primary"); - addClass(document.getElementById("label-start"), "primary"); - document.getElementById('btn-stop-recording').disabled = true; - var player = document.getElementById('player'); + var startBtn = getElementByDynamicId("btn-start-recording", instanceId); + var stopBtn = getElementByDynamicId("btn-stop-recording", instanceId); + var player = getElementByDynamicId("player", instanceId); + + stopBtn.disabled = true; + + if (!audioRecorder) { + displayErrorMessage(uploadConfig(instanceId).ErrMsgStopRec, instanceId); + startBtn.disabled = false; + return; + } + audioRecorder.stopRecording().then(function() { - console.info('stopRecording success'); + console.info("stopRecording success"); + + clearPlayerSource(player); + + if (!audioRecorder.blob) { + throw "Empty blob."; + } - // Now RecordRTCPromisesHandler function returns a promise with a blob as the promise value. player.src = URL.createObjectURL(audioRecorder.blob); + player.muted = false; + + try { + player.load(); + } catch (e) {} + + var playPromise; + try { + playPromise = player.play(); + } catch (e) {} - if (!navigator.userAgent.toUpperCase().includes("IPAD")) { - player.play(); - player.muted = false; + if (playPromise && typeof playPromise.catch === "function") { + playPromise.catch(function() { + // ignore autoplay failures on playback + }); } - audioRecorder.stream.stop(); - document.getElementById('btn-start-recording').disabled = false; + + stopStreamTracks(audioRecorder.stream); + startBtn.disabled = false; }).catch(function(error) { - displayErrorMessage(uploadConfig(instanceId).ErrMsgStopRec, instanceId); console.log("Stop recording error:", error); + stopStreamTracks(audioRecorder && audioRecorder.stream ? audioRecorder.stream : null); + startBtn.disabled = false; + displayErrorMessage(uploadConfig(instanceId).ErrMsgStopRec, instanceId); }); } @@ -396,27 +592,43 @@ function stopRecordingAudio(instanceId) { function saveAudio(instanceId) { hideErrorMessage(instanceId); hideSuccessMessage(instanceId); - var player = document.getElementById("player"); + + var player = getElementByDynamicId("player", instanceId); + if (!hasClass(player, "saved")) { - if(audioRecorder != undefined){ // if audio has been recorded - if (window.navigator.msSaveOrOpenBlob) { // Edge - window.navigator.msSaveOrOpenBlob(audioRecorder.blob, "my_audio.mpeg"); - } else { // Others + if (audioRecorder != undefined && audioRecorder.blob) { + var fileName = getAudioFileName(audioRecorder.blob, "my_audio"); + + if (window.navigator.msSaveOrOpenBlob) { + window.navigator.msSaveOrOpenBlob(audioRecorder.blob, fileName); + } else { var a = document.createElement("a"); - a.href = player.src; - a.download = "my_audio.mpeg"; + var objectUrl = player.src; + + if (!objectUrl) { + objectUrl = URL.createObjectURL(audioRecorder.blob); + } + + a.href = objectUrl; + a.download = fileName; document.body.appendChild(a); a.click(); + setTimeout(function() { document.body.removeChild(a); - window.URL.revokeObjectURL(player.src); }, 0); } + addClass(player, "saved"); - getElementByDynamicId("btnSave", instanceId).disabled = true; + + var saveBtn = getElementByDynamicId("btnSave", instanceId); + if (saveBtn) { + saveBtn.disabled = true; + } + displaySuccessMessage(uploadConfig(instanceId).SuccessMsgSave, uploadConfig(instanceId).SuccessMsgColor, instanceId); - } else{ + } else { displayErrorMessage(uploadConfig(instanceId).ErrMsgSave, instanceId); } } -} +} \ No newline at end of file