├── README.md ├── css └── main.css ├── index.html └── js ├── adapter-latest.js └── main.js /README.md: -------------------------------------------------------------------------------- 1 | # Media Recorder API Demo 2 | A demo implementation of the (new) [Media Recorder API](http://w3c.github.io/mediacapture-record/MediaRecorder.html) (also known as MediaStream Recording API). 3 | 4 | Works on: 5 | * Firefox 30+ 6 | * Chrome 47,48 (video only, enable experimental Web Platform features at chrome://flags) 7 | * Chrome 49+ 8 | 9 | Containers & codecs: 10 | * Chrome 52+ : webm, VP8/VP9/H.264, Opus @ 48kHz 11 | * Chrome 49+ : webm, VP8/VP9, Opus @ 48kHz 12 | * Firefox 30+: webm,VP8, Vorbis @ 44.1 kHz 13 | 14 | Issues: 15 | * Pause does not stop audio recording on Chrome 49,50 16 | 17 | 18 | Links: 19 | * [Live demo of this code](https://addpipe.com/media-recorder-api-demo/) 20 | 21 | * [Article: HTML5’s Media Recorder API in Action on Chrome and Firefox](https://blog.addpipe.com/mediarecorder-api/) 22 | 23 | * [W3C Draft (Latest published version)](https://www.w3.org/TR/mediastream-recording/) 24 | 25 | * [Media Recorder API at 65% penetration thanks to Chrome](https://addpipe.com/media-recorder-api-demo/) 26 | -------------------------------------------------------------------------------- /css/main.css: -------------------------------------------------------------------------------- 1 | body { 2 | line-height: 1.5; 3 | font-family: sans-serif; 4 | word-wrap: break-word; 5 | overflow-wrap: break-word; 6 | color:black; 7 | margin:2em; 8 | } 9 | 10 | h1 { 11 | text-decoration: underline red; 12 | text-decoration-thickness: 3px; 13 | text-underline-offset: 6px; 14 | font-size: 220%; 15 | font-weight: bold; 16 | } 17 | 18 | h2 { 19 | font-weight: bold; 20 | color: #005A9C; 21 | font-size: 140%; 22 | text-transform: uppercase; 23 | } 24 | 25 | p { 26 | margin: 1em 0; 27 | } 28 | 29 | pre { 30 | padding: 0px; 31 | border:0px; 32 | border-radius: 0px; 33 | } 34 | 35 | red { 36 | color: red; 37 | } 38 | 39 | a { 40 | color: #4183c4; 41 | font-weight: 300; 42 | text-decoration: none; 43 | } 44 | 45 | a:hover { 46 | color: #3d85c6; 47 | text-decoration: underline; 48 | } 49 | 50 | a#downloadLink { 51 | display: block; 52 | margin: 0 0 1em 0; 53 | min-height: 1.2em; 54 | } 55 | 56 | div#container { 57 | margin: 0 auto 0 auto; 58 | max-width: 720px; 59 | padding: 1em 1.5em 1.3em 1.5em; 60 | } 61 | 62 | h3 { 63 | border-top: 1px solid #eee; 64 | color: #666; 65 | font-size: 0.9em; 66 | font-weight: 500; 67 | margin: 20px 0 10px 0; 68 | padding: 10px 0 0 0; 69 | white-space: nowrap; 70 | } 71 | 72 | p#data { 73 | border-top: 1px dotted #666; 74 | font-family: Courier New, monospace; 75 | line-height: 1.3em; 76 | min-height: 6em; 77 | max-height: 1000px; 78 | overflow-y: auto; 79 | padding: 1em 0 0 0; 80 | } 81 | 82 | video { 83 | background: #222; 84 | margin: 10px auto; 85 | width: 320px; 86 | height: 240px; 87 | } 88 | 89 | #controls { 90 | display: flex; 91 | margin-top: 2rem; 92 | max-width: 28em; 93 | } 94 | 95 | button { 96 | flex-grow: 1; 97 | height: 3rem; 98 | min-width: 10rem; 99 | border: none; 100 | border-radius: 0.15rem; 101 | background: #ed341d; 102 | margin-left: 2px; 103 | box-shadow: inset 0 -0.15rem 0 rgba(0, 0, 0, 0.2); 104 | cursor: pointer; 105 | display: flex; 106 | justify-content: center; 107 | align-items: center; 108 | color:#ffffff; 109 | font-weight: bold; 110 | font-size: 1rem; 111 | } 112 | button:hover, button:focus { 113 | outline: none; 114 | background: #c72d1c; 115 | } 116 | button::-moz-focus-inner { 117 | border: 0; 118 | } 119 | button:active { 120 | box-shadow: inset 0 1px 0 rgba(0, 0, 0, 0.2); 121 | line-height: 3rem; 122 | } 123 | button:disabled { 124 | pointer-events: none; 125 | background: lightgray; 126 | } 127 | button:first-child { 128 | margin-left: 0; 129 | } 130 | video{ 131 | width: 1280px; 132 | height: 720px; 133 | } -------------------------------------------------------------------------------- /index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | Media Recorder API Demo - addpipe.com 8 | 9 | 10 | 11 | 12 |
13 |

Media Recorder API Demo

14 |

Made by the Pipe Recording Platform

15 |

The demo below uses the Media​Stream Recording API and getUserMedia() to record a 1280x720 video file. For more info check out our article: Media Recorder API in Action on Chrome and Firefox. 16 |

17 |
18 |
19 | 20 | 21 | 22 | 23 |
24 |
25 | 26 |

27 | 28 | 29 |
30 |

Works on:

31 | 32 | Issues: 33 | 34 |

Containers & codecs:

35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | 47 | 48 | 49 | 50 | 51 | 52 |
 Chrome 47 & 48Chrome 49+Chrome 52+Firefox 30+Safari 14.0.2+ on macOSSafari on iOS 14.3+
Containerwebmwebmwebmwebmmp4mp4
VideoVP8VP8/VP9VP8/VP9/H264VP8H264H264
AudiononeOpus @ 48kHzOpus @ 48kHzVorbis @ 44.1 kHzStereo AAC @ 48kHzStereo AAC @ 44.1kHz or 48kHz
53 |

Links:

54 | 62 | 63 | -------------------------------------------------------------------------------- /js/adapter-latest.js: -------------------------------------------------------------------------------- 1 | (function(f){if(typeof exports==="object"&&typeof module!=="undefined"){module.exports=f()}else if(typeof define==="function"&&define.amd){define([],f)}else{var g;if(typeof window!=="undefined"){g=window}else if(typeof global!=="undefined"){g=global}else if(typeof self!=="undefined"){g=self}else{g=this}g.adapter = f()}})(function(){var define,module,exports;return (function(){function r(e,n,t){function o(i,f){if(!n[i]){if(!e[i]){var c="function"==typeof require&&require;if(!f&&c)return c(i,!0);if(u)return u(i,!0);var a=new Error("Cannot find module '"+i+"'");throw a.code="MODULE_NOT_FOUND",a}var p=n[i]={exports:{}};e[i][0].call(p.exports,function(r){var n=e[i][1][r];return o(n||r)},p,p.exports,r,e,n,t)}return n[i].exports}for(var u="function"==typeof require&&require,i=0;i 0 && arguments[0] !== undefined ? arguments[0] : {}, 62 | window = _ref.window; 63 | 64 | var options = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : { 65 | shimChrome: true, 66 | shimFirefox: true, 67 | shimSafari: true 68 | }; 69 | 70 | // Utils. 71 | var logging = utils.log; 72 | var browserDetails = utils.detectBrowser(window); 73 | 74 | var adapter = { 75 | browserDetails: browserDetails, 76 | commonShim: commonShim, 77 | extractVersion: utils.extractVersion, 78 | disableLog: utils.disableLog, 79 | disableWarnings: utils.disableWarnings, 80 | // Expose sdp as a convenience. For production apps include directly. 81 | sdp: sdp 82 | }; 83 | 84 | // Shim browser if found. 85 | switch (browserDetails.browser) { 86 | case 'chrome': 87 | if (!chromeShim || !chromeShim.shimPeerConnection || !options.shimChrome) { 88 | logging('Chrome shim is not included in this adapter release.'); 89 | return adapter; 90 | } 91 | if (browserDetails.version === null) { 92 | logging('Chrome shim can not determine version, not shimming.'); 93 | return adapter; 94 | } 95 | logging('adapter.js shimming chrome.'); 96 | // Export to the adapter global object visible in the browser. 97 | adapter.browserShim = chromeShim; 98 | 99 | // Must be called before shimPeerConnection. 100 | commonShim.shimAddIceCandidateNullOrEmpty(window, browserDetails); 101 | commonShim.shimParameterlessSetLocalDescription(window, browserDetails); 102 | 103 | chromeShim.shimGetUserMedia(window, browserDetails); 104 | chromeShim.shimMediaStream(window, browserDetails); 105 | chromeShim.shimPeerConnection(window, browserDetails); 106 | chromeShim.shimOnTrack(window, browserDetails); 107 | chromeShim.shimAddTrackRemoveTrack(window, browserDetails); 108 | chromeShim.shimGetSendersWithDtmf(window, browserDetails); 109 | chromeShim.shimGetStats(window, browserDetails); 110 | chromeShim.shimSenderReceiverGetStats(window, browserDetails); 111 | chromeShim.fixNegotiationNeeded(window, browserDetails); 112 | 113 | commonShim.shimRTCIceCandidate(window, browserDetails); 114 | commonShim.shimConnectionState(window, browserDetails); 115 | commonShim.shimMaxMessageSize(window, browserDetails); 116 | commonShim.shimSendThrowTypeError(window, browserDetails); 117 | commonShim.removeExtmapAllowMixed(window, browserDetails); 118 | break; 119 | case 'firefox': 120 | if (!firefoxShim || !firefoxShim.shimPeerConnection || !options.shimFirefox) { 121 | logging('Firefox shim is not included in this adapter release.'); 122 | return adapter; 123 | } 124 | logging('adapter.js shimming firefox.'); 125 | // Export to the adapter global object visible in the browser. 126 | adapter.browserShim = firefoxShim; 127 | 128 | // Must be called before shimPeerConnection. 129 | commonShim.shimAddIceCandidateNullOrEmpty(window, browserDetails); 130 | commonShim.shimParameterlessSetLocalDescription(window, browserDetails); 131 | 132 | firefoxShim.shimGetUserMedia(window, browserDetails); 133 | firefoxShim.shimPeerConnection(window, browserDetails); 134 | firefoxShim.shimOnTrack(window, browserDetails); 135 | firefoxShim.shimRemoveStream(window, browserDetails); 136 | firefoxShim.shimSenderGetStats(window, browserDetails); 137 | firefoxShim.shimReceiverGetStats(window, browserDetails); 138 | firefoxShim.shimRTCDataChannel(window, browserDetails); 139 | firefoxShim.shimAddTransceiver(window, browserDetails); 140 | firefoxShim.shimGetParameters(window, browserDetails); 141 | firefoxShim.shimCreateOffer(window, browserDetails); 142 | firefoxShim.shimCreateAnswer(window, browserDetails); 143 | 144 | commonShim.shimRTCIceCandidate(window, browserDetails); 145 | commonShim.shimConnectionState(window, browserDetails); 146 | commonShim.shimMaxMessageSize(window, browserDetails); 147 | commonShim.shimSendThrowTypeError(window, browserDetails); 148 | break; 149 | case 'safari': 150 | if (!safariShim || !options.shimSafari) { 151 | logging('Safari shim is not included in this adapter release.'); 152 | return adapter; 153 | } 154 | logging('adapter.js shimming safari.'); 155 | // Export to the adapter global object visible in the browser. 156 | adapter.browserShim = safariShim; 157 | 158 | // Must be called before shimCallbackAPI. 159 | commonShim.shimAddIceCandidateNullOrEmpty(window, browserDetails); 160 | commonShim.shimParameterlessSetLocalDescription(window, browserDetails); 161 | 162 | safariShim.shimRTCIceServerUrls(window, browserDetails); 163 | safariShim.shimCreateOfferLegacy(window, browserDetails); 164 | safariShim.shimCallbacksAPI(window, browserDetails); 165 | safariShim.shimLocalStreamsAPI(window, browserDetails); 166 | safariShim.shimRemoteStreamsAPI(window, browserDetails); 167 | safariShim.shimTrackEventTransceiver(window, browserDetails); 168 | safariShim.shimGetUserMedia(window, browserDetails); 169 | safariShim.shimAudioContext(window, browserDetails); 170 | 171 | commonShim.shimRTCIceCandidate(window, browserDetails); 172 | commonShim.shimMaxMessageSize(window, browserDetails); 173 | commonShim.shimSendThrowTypeError(window, browserDetails); 174 | commonShim.removeExtmapAllowMixed(window, browserDetails); 175 | break; 176 | default: 177 | logging('Unsupported browser!'); 178 | break; 179 | } 180 | 181 | return adapter; 182 | } 183 | 184 | // Browser shims. 185 | 186 | },{"./chrome/chrome_shim":3,"./common_shim":6,"./firefox/firefox_shim":7,"./safari/safari_shim":10,"./utils":11,"sdp":12}],3:[function(require,module,exports){ 187 | /* 188 | * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. 189 | * 190 | * Use of this source code is governed by a BSD-style license 191 | * that can be found in the LICENSE file in the root of the source 192 | * tree. 193 | */ 194 | /* eslint-env node */ 195 | 'use strict'; 196 | 197 | Object.defineProperty(exports, "__esModule", { 198 | value: true 199 | }); 200 | exports.shimGetDisplayMedia = exports.shimGetUserMedia = undefined; 201 | 202 | 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; }; 203 | 204 | var _getusermedia = require('./getusermedia'); 205 | 206 | Object.defineProperty(exports, 'shimGetUserMedia', { 207 | enumerable: true, 208 | get: function get() { 209 | return _getusermedia.shimGetUserMedia; 210 | } 211 | }); 212 | 213 | var _getdisplaymedia = require('./getdisplaymedia'); 214 | 215 | Object.defineProperty(exports, 'shimGetDisplayMedia', { 216 | enumerable: true, 217 | get: function get() { 218 | return _getdisplaymedia.shimGetDisplayMedia; 219 | } 220 | }); 221 | exports.shimMediaStream = shimMediaStream; 222 | exports.shimOnTrack = shimOnTrack; 223 | exports.shimGetSendersWithDtmf = shimGetSendersWithDtmf; 224 | exports.shimGetStats = shimGetStats; 225 | exports.shimSenderReceiverGetStats = shimSenderReceiverGetStats; 226 | exports.shimAddTrackRemoveTrackWithNative = shimAddTrackRemoveTrackWithNative; 227 | exports.shimAddTrackRemoveTrack = shimAddTrackRemoveTrack; 228 | exports.shimPeerConnection = shimPeerConnection; 229 | exports.fixNegotiationNeeded = fixNegotiationNeeded; 230 | 231 | var _utils = require('../utils.js'); 232 | 233 | var utils = _interopRequireWildcard(_utils); 234 | 235 | 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; } } 236 | 237 | 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; } 238 | 239 | function shimMediaStream(window) { 240 | window.MediaStream = window.MediaStream || window.webkitMediaStream; 241 | } 242 | 243 | function shimOnTrack(window) { 244 | if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCPeerConnection && !('ontrack' in window.RTCPeerConnection.prototype)) { 245 | Object.defineProperty(window.RTCPeerConnection.prototype, 'ontrack', { 246 | get: function get() { 247 | return this._ontrack; 248 | }, 249 | set: function set(f) { 250 | if (this._ontrack) { 251 | this.removeEventListener('track', this._ontrack); 252 | } 253 | this.addEventListener('track', this._ontrack = f); 254 | }, 255 | 256 | enumerable: true, 257 | configurable: true 258 | }); 259 | var origSetRemoteDescription = window.RTCPeerConnection.prototype.setRemoteDescription; 260 | window.RTCPeerConnection.prototype.setRemoteDescription = function setRemoteDescription() { 261 | var _this = this; 262 | 263 | if (!this._ontrackpoly) { 264 | this._ontrackpoly = function (e) { 265 | // onaddstream does not fire when a track is added to an existing 266 | // stream. But stream.onaddtrack is implemented so we use that. 267 | e.stream.addEventListener('addtrack', function (te) { 268 | var receiver = void 0; 269 | if (window.RTCPeerConnection.prototype.getReceivers) { 270 | receiver = _this.getReceivers().find(function (r) { 271 | return r.track && r.track.id === te.track.id; 272 | }); 273 | } else { 274 | receiver = { track: te.track }; 275 | } 276 | 277 | var event = new Event('track'); 278 | event.track = te.track; 279 | event.receiver = receiver; 280 | event.transceiver = { receiver: receiver }; 281 | event.streams = [e.stream]; 282 | _this.dispatchEvent(event); 283 | }); 284 | e.stream.getTracks().forEach(function (track) { 285 | var receiver = void 0; 286 | if (window.RTCPeerConnection.prototype.getReceivers) { 287 | receiver = _this.getReceivers().find(function (r) { 288 | return r.track && r.track.id === track.id; 289 | }); 290 | } else { 291 | receiver = { track: track }; 292 | } 293 | var event = new Event('track'); 294 | event.track = track; 295 | event.receiver = receiver; 296 | event.transceiver = { receiver: receiver }; 297 | event.streams = [e.stream]; 298 | _this.dispatchEvent(event); 299 | }); 300 | }; 301 | this.addEventListener('addstream', this._ontrackpoly); 302 | } 303 | return origSetRemoteDescription.apply(this, arguments); 304 | }; 305 | } else { 306 | // even if RTCRtpTransceiver is in window, it is only used and 307 | // emitted in unified-plan. Unfortunately this means we need 308 | // to unconditionally wrap the event. 309 | utils.wrapPeerConnectionEvent(window, 'track', function (e) { 310 | if (!e.transceiver) { 311 | Object.defineProperty(e, 'transceiver', { value: { receiver: e.receiver } }); 312 | } 313 | return e; 314 | }); 315 | } 316 | } 317 | 318 | function shimGetSendersWithDtmf(window) { 319 | // Overrides addTrack/removeTrack, depends on shimAddTrackRemoveTrack. 320 | if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCPeerConnection && !('getSenders' in window.RTCPeerConnection.prototype) && 'createDTMFSender' in window.RTCPeerConnection.prototype) { 321 | var shimSenderWithDtmf = function shimSenderWithDtmf(pc, track) { 322 | return { 323 | track: track, 324 | get dtmf() { 325 | if (this._dtmf === undefined) { 326 | if (track.kind === 'audio') { 327 | this._dtmf = pc.createDTMFSender(track); 328 | } else { 329 | this._dtmf = null; 330 | } 331 | } 332 | return this._dtmf; 333 | }, 334 | _pc: pc 335 | }; 336 | }; 337 | 338 | // augment addTrack when getSenders is not available. 339 | if (!window.RTCPeerConnection.prototype.getSenders) { 340 | window.RTCPeerConnection.prototype.getSenders = function getSenders() { 341 | this._senders = this._senders || []; 342 | return this._senders.slice(); // return a copy of the internal state. 343 | }; 344 | var origAddTrack = window.RTCPeerConnection.prototype.addTrack; 345 | window.RTCPeerConnection.prototype.addTrack = function addTrack(track, stream) { 346 | var sender = origAddTrack.apply(this, arguments); 347 | if (!sender) { 348 | sender = shimSenderWithDtmf(this, track); 349 | this._senders.push(sender); 350 | } 351 | return sender; 352 | }; 353 | 354 | var origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack; 355 | window.RTCPeerConnection.prototype.removeTrack = function removeTrack(sender) { 356 | origRemoveTrack.apply(this, arguments); 357 | var idx = this._senders.indexOf(sender); 358 | if (idx !== -1) { 359 | this._senders.splice(idx, 1); 360 | } 361 | }; 362 | } 363 | var origAddStream = window.RTCPeerConnection.prototype.addStream; 364 | window.RTCPeerConnection.prototype.addStream = function addStream(stream) { 365 | var _this2 = this; 366 | 367 | this._senders = this._senders || []; 368 | origAddStream.apply(this, [stream]); 369 | stream.getTracks().forEach(function (track) { 370 | _this2._senders.push(shimSenderWithDtmf(_this2, track)); 371 | }); 372 | }; 373 | 374 | var origRemoveStream = window.RTCPeerConnection.prototype.removeStream; 375 | window.RTCPeerConnection.prototype.removeStream = function removeStream(stream) { 376 | var _this3 = this; 377 | 378 | this._senders = this._senders || []; 379 | origRemoveStream.apply(this, [stream]); 380 | 381 | stream.getTracks().forEach(function (track) { 382 | var sender = _this3._senders.find(function (s) { 383 | return s.track === track; 384 | }); 385 | if (sender) { 386 | // remove sender 387 | _this3._senders.splice(_this3._senders.indexOf(sender), 1); 388 | } 389 | }); 390 | }; 391 | } 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)) { 392 | var origGetSenders = window.RTCPeerConnection.prototype.getSenders; 393 | window.RTCPeerConnection.prototype.getSenders = function getSenders() { 394 | var _this4 = this; 395 | 396 | var senders = origGetSenders.apply(this, []); 397 | senders.forEach(function (sender) { 398 | return sender._pc = _this4; 399 | }); 400 | return senders; 401 | }; 402 | 403 | Object.defineProperty(window.RTCRtpSender.prototype, 'dtmf', { 404 | get: function get() { 405 | if (this._dtmf === undefined) { 406 | if (this.track.kind === 'audio') { 407 | this._dtmf = this._pc.createDTMFSender(this.track); 408 | } else { 409 | this._dtmf = null; 410 | } 411 | } 412 | return this._dtmf; 413 | } 414 | }); 415 | } 416 | } 417 | 418 | function shimGetStats(window) { 419 | if (!window.RTCPeerConnection) { 420 | return; 421 | } 422 | 423 | var origGetStats = window.RTCPeerConnection.prototype.getStats; 424 | window.RTCPeerConnection.prototype.getStats = function getStats() { 425 | var _this5 = this; 426 | 427 | var _arguments = Array.prototype.slice.call(arguments), 428 | selector = _arguments[0], 429 | onSucc = _arguments[1], 430 | onErr = _arguments[2]; 431 | 432 | // If selector is a function then we are in the old style stats so just 433 | // pass back the original getStats format to avoid breaking old users. 434 | 435 | 436 | if (arguments.length > 0 && typeof selector === 'function') { 437 | return origGetStats.apply(this, arguments); 438 | } 439 | 440 | // When spec-style getStats is supported, return those when called with 441 | // either no arguments or the selector argument is null. 442 | if (origGetStats.length === 0 && (arguments.length === 0 || typeof selector !== 'function')) { 443 | return origGetStats.apply(this, []); 444 | } 445 | 446 | var fixChromeStats_ = function fixChromeStats_(response) { 447 | var standardReport = {}; 448 | var reports = response.result(); 449 | reports.forEach(function (report) { 450 | var standardStats = { 451 | id: report.id, 452 | timestamp: report.timestamp, 453 | type: { 454 | localcandidate: 'local-candidate', 455 | remotecandidate: 'remote-candidate' 456 | }[report.type] || report.type 457 | }; 458 | report.names().forEach(function (name) { 459 | standardStats[name] = report.stat(name); 460 | }); 461 | standardReport[standardStats.id] = standardStats; 462 | }); 463 | 464 | return standardReport; 465 | }; 466 | 467 | // shim getStats with maplike support 468 | var makeMapStats = function makeMapStats(stats) { 469 | return new Map(Object.keys(stats).map(function (key) { 470 | return [key, stats[key]]; 471 | })); 472 | }; 473 | 474 | if (arguments.length >= 2) { 475 | var successCallbackWrapper_ = function successCallbackWrapper_(response) { 476 | onSucc(makeMapStats(fixChromeStats_(response))); 477 | }; 478 | 479 | return origGetStats.apply(this, [successCallbackWrapper_, selector]); 480 | } 481 | 482 | // promise-support 483 | return new Promise(function (resolve, reject) { 484 | origGetStats.apply(_this5, [function (response) { 485 | resolve(makeMapStats(fixChromeStats_(response))); 486 | }, reject]); 487 | }).then(onSucc, onErr); 488 | }; 489 | } 490 | 491 | function shimSenderReceiverGetStats(window) { 492 | if (!((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCPeerConnection && window.RTCRtpSender && window.RTCRtpReceiver)) { 493 | return; 494 | } 495 | 496 | // shim sender stats. 497 | if (!('getStats' in window.RTCRtpSender.prototype)) { 498 | var origGetSenders = window.RTCPeerConnection.prototype.getSenders; 499 | if (origGetSenders) { 500 | window.RTCPeerConnection.prototype.getSenders = function getSenders() { 501 | var _this6 = this; 502 | 503 | var senders = origGetSenders.apply(this, []); 504 | senders.forEach(function (sender) { 505 | return sender._pc = _this6; 506 | }); 507 | return senders; 508 | }; 509 | } 510 | 511 | var origAddTrack = window.RTCPeerConnection.prototype.addTrack; 512 | if (origAddTrack) { 513 | window.RTCPeerConnection.prototype.addTrack = function addTrack() { 514 | var sender = origAddTrack.apply(this, arguments); 515 | sender._pc = this; 516 | return sender; 517 | }; 518 | } 519 | window.RTCRtpSender.prototype.getStats = function getStats() { 520 | var sender = this; 521 | return this._pc.getStats().then(function (result) { 522 | return ( 523 | /* Note: this will include stats of all senders that 524 | * send a track with the same id as sender.track as 525 | * it is not possible to identify the RTCRtpSender. 526 | */ 527 | utils.filterStats(result, sender.track, true) 528 | ); 529 | }); 530 | }; 531 | } 532 | 533 | // shim receiver stats. 534 | if (!('getStats' in window.RTCRtpReceiver.prototype)) { 535 | var origGetReceivers = window.RTCPeerConnection.prototype.getReceivers; 536 | if (origGetReceivers) { 537 | window.RTCPeerConnection.prototype.getReceivers = function getReceivers() { 538 | var _this7 = this; 539 | 540 | var receivers = origGetReceivers.apply(this, []); 541 | receivers.forEach(function (receiver) { 542 | return receiver._pc = _this7; 543 | }); 544 | return receivers; 545 | }; 546 | } 547 | utils.wrapPeerConnectionEvent(window, 'track', function (e) { 548 | e.receiver._pc = e.srcElement; 549 | return e; 550 | }); 551 | window.RTCRtpReceiver.prototype.getStats = function getStats() { 552 | var receiver = this; 553 | return this._pc.getStats().then(function (result) { 554 | return utils.filterStats(result, receiver.track, false); 555 | }); 556 | }; 557 | } 558 | 559 | if (!('getStats' in window.RTCRtpSender.prototype && 'getStats' in window.RTCRtpReceiver.prototype)) { 560 | return; 561 | } 562 | 563 | // shim RTCPeerConnection.getStats(track). 564 | var origGetStats = window.RTCPeerConnection.prototype.getStats; 565 | window.RTCPeerConnection.prototype.getStats = function getStats() { 566 | if (arguments.length > 0 && arguments[0] instanceof window.MediaStreamTrack) { 567 | var track = arguments[0]; 568 | var sender = void 0; 569 | var receiver = void 0; 570 | var err = void 0; 571 | this.getSenders().forEach(function (s) { 572 | if (s.track === track) { 573 | if (sender) { 574 | err = true; 575 | } else { 576 | sender = s; 577 | } 578 | } 579 | }); 580 | this.getReceivers().forEach(function (r) { 581 | if (r.track === track) { 582 | if (receiver) { 583 | err = true; 584 | } else { 585 | receiver = r; 586 | } 587 | } 588 | return r.track === track; 589 | }); 590 | if (err || sender && receiver) { 591 | return Promise.reject(new DOMException('There are more than one sender or receiver for the track.', 'InvalidAccessError')); 592 | } else if (sender) { 593 | return sender.getStats(); 594 | } else if (receiver) { 595 | return receiver.getStats(); 596 | } 597 | return Promise.reject(new DOMException('There is no sender or receiver for the track.', 'InvalidAccessError')); 598 | } 599 | return origGetStats.apply(this, arguments); 600 | }; 601 | } 602 | 603 | function shimAddTrackRemoveTrackWithNative(window) { 604 | // shim addTrack/removeTrack with native variants in order to make 605 | // the interactions with legacy getLocalStreams behave as in other browsers. 606 | // Keeps a mapping stream.id => [stream, rtpsenders...] 607 | window.RTCPeerConnection.prototype.getLocalStreams = function getLocalStreams() { 608 | var _this8 = this; 609 | 610 | this._shimmedLocalStreams = this._shimmedLocalStreams || {}; 611 | return Object.keys(this._shimmedLocalStreams).map(function (streamId) { 612 | return _this8._shimmedLocalStreams[streamId][0]; 613 | }); 614 | }; 615 | 616 | var origAddTrack = window.RTCPeerConnection.prototype.addTrack; 617 | window.RTCPeerConnection.prototype.addTrack = function addTrack(track, stream) { 618 | if (!stream) { 619 | return origAddTrack.apply(this, arguments); 620 | } 621 | this._shimmedLocalStreams = this._shimmedLocalStreams || {}; 622 | 623 | var sender = origAddTrack.apply(this, arguments); 624 | if (!this._shimmedLocalStreams[stream.id]) { 625 | this._shimmedLocalStreams[stream.id] = [stream, sender]; 626 | } else if (this._shimmedLocalStreams[stream.id].indexOf(sender) === -1) { 627 | this._shimmedLocalStreams[stream.id].push(sender); 628 | } 629 | return sender; 630 | }; 631 | 632 | var origAddStream = window.RTCPeerConnection.prototype.addStream; 633 | window.RTCPeerConnection.prototype.addStream = function addStream(stream) { 634 | var _this9 = this; 635 | 636 | this._shimmedLocalStreams = this._shimmedLocalStreams || {}; 637 | 638 | stream.getTracks().forEach(function (track) { 639 | var alreadyExists = _this9.getSenders().find(function (s) { 640 | return s.track === track; 641 | }); 642 | if (alreadyExists) { 643 | throw new DOMException('Track already exists.', 'InvalidAccessError'); 644 | } 645 | }); 646 | var existingSenders = this.getSenders(); 647 | origAddStream.apply(this, arguments); 648 | var newSenders = this.getSenders().filter(function (newSender) { 649 | return existingSenders.indexOf(newSender) === -1; 650 | }); 651 | this._shimmedLocalStreams[stream.id] = [stream].concat(newSenders); 652 | }; 653 | 654 | var origRemoveStream = window.RTCPeerConnection.prototype.removeStream; 655 | window.RTCPeerConnection.prototype.removeStream = function removeStream(stream) { 656 | this._shimmedLocalStreams = this._shimmedLocalStreams || {}; 657 | delete this._shimmedLocalStreams[stream.id]; 658 | return origRemoveStream.apply(this, arguments); 659 | }; 660 | 661 | var origRemoveTrack = window.RTCPeerConnection.prototype.removeTrack; 662 | window.RTCPeerConnection.prototype.removeTrack = function removeTrack(sender) { 663 | var _this10 = this; 664 | 665 | this._shimmedLocalStreams = this._shimmedLocalStreams || {}; 666 | if (sender) { 667 | Object.keys(this._shimmedLocalStreams).forEach(function (streamId) { 668 | var idx = _this10._shimmedLocalStreams[streamId].indexOf(sender); 669 | if (idx !== -1) { 670 | _this10._shimmedLocalStreams[streamId].splice(idx, 1); 671 | } 672 | if (_this10._shimmedLocalStreams[streamId].length === 1) { 673 | delete _this10._shimmedLocalStreams[streamId]; 674 | } 675 | }); 676 | } 677 | return origRemoveTrack.apply(this, arguments); 678 | }; 679 | } 680 | 681 | function shimAddTrackRemoveTrack(window, browserDetails) { 682 | if (!window.RTCPeerConnection) { 683 | return; 684 | } 685 | // shim addTrack and removeTrack. 686 | if (window.RTCPeerConnection.prototype.addTrack && browserDetails.version >= 65) { 687 | return shimAddTrackRemoveTrackWithNative(window); 688 | } 689 | 690 | // also shim pc.getLocalStreams when addTrack is shimmed 691 | // to return the original streams. 692 | var origGetLocalStreams = window.RTCPeerConnection.prototype.getLocalStreams; 693 | window.RTCPeerConnection.prototype.getLocalStreams = function getLocalStreams() { 694 | var _this11 = this; 695 | 696 | var nativeStreams = origGetLocalStreams.apply(this); 697 | this._reverseStreams = this._reverseStreams || {}; 698 | return nativeStreams.map(function (stream) { 699 | return _this11._reverseStreams[stream.id]; 700 | }); 701 | }; 702 | 703 | var origAddStream = window.RTCPeerConnection.prototype.addStream; 704 | window.RTCPeerConnection.prototype.addStream = function addStream(stream) { 705 | var _this12 = this; 706 | 707 | this._streams = this._streams || {}; 708 | this._reverseStreams = this._reverseStreams || {}; 709 | 710 | stream.getTracks().forEach(function (track) { 711 | var alreadyExists = _this12.getSenders().find(function (s) { 712 | return s.track === track; 713 | }); 714 | if (alreadyExists) { 715 | throw new DOMException('Track already exists.', 'InvalidAccessError'); 716 | } 717 | }); 718 | // Add identity mapping for consistency with addTrack. 719 | // Unless this is being used with a stream from addTrack. 720 | if (!this._reverseStreams[stream.id]) { 721 | var newStream = new window.MediaStream(stream.getTracks()); 722 | this._streams[stream.id] = newStream; 723 | this._reverseStreams[newStream.id] = stream; 724 | stream = newStream; 725 | } 726 | origAddStream.apply(this, [stream]); 727 | }; 728 | 729 | var origRemoveStream = window.RTCPeerConnection.prototype.removeStream; 730 | window.RTCPeerConnection.prototype.removeStream = function removeStream(stream) { 731 | this._streams = this._streams || {}; 732 | this._reverseStreams = this._reverseStreams || {}; 733 | 734 | origRemoveStream.apply(this, [this._streams[stream.id] || stream]); 735 | delete this._reverseStreams[this._streams[stream.id] ? this._streams[stream.id].id : stream.id]; 736 | delete this._streams[stream.id]; 737 | }; 738 | 739 | window.RTCPeerConnection.prototype.addTrack = function addTrack(track, stream) { 740 | var _this13 = this; 741 | 742 | if (this.signalingState === 'closed') { 743 | throw new DOMException('The RTCPeerConnection\'s signalingState is \'closed\'.', 'InvalidStateError'); 744 | } 745 | var streams = [].slice.call(arguments, 1); 746 | if (streams.length !== 1 || !streams[0].getTracks().find(function (t) { 747 | return t === track; 748 | })) { 749 | // this is not fully correct but all we can manage without 750 | // [[associated MediaStreams]] internal slot. 751 | throw new DOMException('The adapter.js addTrack polyfill only supports a single ' + ' stream which is associated with the specified track.', 'NotSupportedError'); 752 | } 753 | 754 | var alreadyExists = this.getSenders().find(function (s) { 755 | return s.track === track; 756 | }); 757 | if (alreadyExists) { 758 | throw new DOMException('Track already exists.', 'InvalidAccessError'); 759 | } 760 | 761 | this._streams = this._streams || {}; 762 | this._reverseStreams = this._reverseStreams || {}; 763 | var oldStream = this._streams[stream.id]; 764 | if (oldStream) { 765 | // this is using odd Chrome behaviour, use with caution: 766 | // https://bugs.chromium.org/p/webrtc/issues/detail?id=7815 767 | // Note: we rely on the high-level addTrack/dtmf shim to 768 | // create the sender with a dtmf sender. 769 | oldStream.addTrack(track); 770 | 771 | // Trigger ONN async. 772 | Promise.resolve().then(function () { 773 | _this13.dispatchEvent(new Event('negotiationneeded')); 774 | }); 775 | } else { 776 | var newStream = new window.MediaStream([track]); 777 | this._streams[stream.id] = newStream; 778 | this._reverseStreams[newStream.id] = stream; 779 | this.addStream(newStream); 780 | } 781 | return this.getSenders().find(function (s) { 782 | return s.track === track; 783 | }); 784 | }; 785 | 786 | // replace the internal stream id with the external one and 787 | // vice versa. 788 | function replaceInternalStreamId(pc, description) { 789 | var sdp = description.sdp; 790 | Object.keys(pc._reverseStreams || []).forEach(function (internalId) { 791 | var externalStream = pc._reverseStreams[internalId]; 792 | var internalStream = pc._streams[externalStream.id]; 793 | sdp = sdp.replace(new RegExp(internalStream.id, 'g'), externalStream.id); 794 | }); 795 | return new RTCSessionDescription({ 796 | type: description.type, 797 | sdp: sdp 798 | }); 799 | } 800 | function replaceExternalStreamId(pc, description) { 801 | var sdp = description.sdp; 802 | Object.keys(pc._reverseStreams || []).forEach(function (internalId) { 803 | var externalStream = pc._reverseStreams[internalId]; 804 | var internalStream = pc._streams[externalStream.id]; 805 | sdp = sdp.replace(new RegExp(externalStream.id, 'g'), internalStream.id); 806 | }); 807 | return new RTCSessionDescription({ 808 | type: description.type, 809 | sdp: sdp 810 | }); 811 | } 812 | ['createOffer', 'createAnswer'].forEach(function (method) { 813 | var nativeMethod = window.RTCPeerConnection.prototype[method]; 814 | var methodObj = _defineProperty({}, method, function () { 815 | var _this14 = this; 816 | 817 | var args = arguments; 818 | var isLegacyCall = arguments.length && typeof arguments[0] === 'function'; 819 | if (isLegacyCall) { 820 | return nativeMethod.apply(this, [function (description) { 821 | var desc = replaceInternalStreamId(_this14, description); 822 | args[0].apply(null, [desc]); 823 | }, function (err) { 824 | if (args[1]) { 825 | args[1].apply(null, err); 826 | } 827 | }, arguments[2]]); 828 | } 829 | return nativeMethod.apply(this, arguments).then(function (description) { 830 | return replaceInternalStreamId(_this14, description); 831 | }); 832 | }); 833 | window.RTCPeerConnection.prototype[method] = methodObj[method]; 834 | }); 835 | 836 | var origSetLocalDescription = window.RTCPeerConnection.prototype.setLocalDescription; 837 | window.RTCPeerConnection.prototype.setLocalDescription = function setLocalDescription() { 838 | if (!arguments.length || !arguments[0].type) { 839 | return origSetLocalDescription.apply(this, arguments); 840 | } 841 | arguments[0] = replaceExternalStreamId(this, arguments[0]); 842 | return origSetLocalDescription.apply(this, arguments); 843 | }; 844 | 845 | // TODO: mangle getStats: https://w3c.github.io/webrtc-stats/#dom-rtcmediastreamstats-streamidentifier 846 | 847 | var origLocalDescription = Object.getOwnPropertyDescriptor(window.RTCPeerConnection.prototype, 'localDescription'); 848 | Object.defineProperty(window.RTCPeerConnection.prototype, 'localDescription', { 849 | get: function get() { 850 | var description = origLocalDescription.get.apply(this); 851 | if (description.type === '') { 852 | return description; 853 | } 854 | return replaceInternalStreamId(this, description); 855 | } 856 | }); 857 | 858 | window.RTCPeerConnection.prototype.removeTrack = function removeTrack(sender) { 859 | var _this15 = this; 860 | 861 | if (this.signalingState === 'closed') { 862 | throw new DOMException('The RTCPeerConnection\'s signalingState is \'closed\'.', 'InvalidStateError'); 863 | } 864 | // We can not yet check for sender instanceof RTCRtpSender 865 | // since we shim RTPSender. So we check if sender._pc is set. 866 | if (!sender._pc) { 867 | throw new DOMException('Argument 1 of RTCPeerConnection.removeTrack ' + 'does not implement interface RTCRtpSender.', 'TypeError'); 868 | } 869 | var isLocal = sender._pc === this; 870 | if (!isLocal) { 871 | throw new DOMException('Sender was not created by this connection.', 'InvalidAccessError'); 872 | } 873 | 874 | // Search for the native stream the senders track belongs to. 875 | this._streams = this._streams || {}; 876 | var stream = void 0; 877 | Object.keys(this._streams).forEach(function (streamid) { 878 | var hasTrack = _this15._streams[streamid].getTracks().find(function (track) { 879 | return sender.track === track; 880 | }); 881 | if (hasTrack) { 882 | stream = _this15._streams[streamid]; 883 | } 884 | }); 885 | 886 | if (stream) { 887 | if (stream.getTracks().length === 1) { 888 | // if this is the last track of the stream, remove the stream. This 889 | // takes care of any shimmed _senders. 890 | this.removeStream(this._reverseStreams[stream.id]); 891 | } else { 892 | // relying on the same odd chrome behaviour as above. 893 | stream.removeTrack(sender.track); 894 | } 895 | this.dispatchEvent(new Event('negotiationneeded')); 896 | } 897 | }; 898 | } 899 | 900 | function shimPeerConnection(window, browserDetails) { 901 | if (!window.RTCPeerConnection && window.webkitRTCPeerConnection) { 902 | // very basic support for old versions. 903 | window.RTCPeerConnection = window.webkitRTCPeerConnection; 904 | } 905 | if (!window.RTCPeerConnection) { 906 | return; 907 | } 908 | 909 | // shim implicit creation of RTCSessionDescription/RTCIceCandidate 910 | if (browserDetails.version < 53) { 911 | ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'].forEach(function (method) { 912 | var nativeMethod = window.RTCPeerConnection.prototype[method]; 913 | var methodObj = _defineProperty({}, method, function () { 914 | arguments[0] = new (method === 'addIceCandidate' ? window.RTCIceCandidate : window.RTCSessionDescription)(arguments[0]); 915 | return nativeMethod.apply(this, arguments); 916 | }); 917 | window.RTCPeerConnection.prototype[method] = methodObj[method]; 918 | }); 919 | } 920 | } 921 | 922 | // Attempt to fix ONN in plan-b mode. 923 | function fixNegotiationNeeded(window, browserDetails) { 924 | utils.wrapPeerConnectionEvent(window, 'negotiationneeded', function (e) { 925 | var pc = e.target; 926 | if (browserDetails.version < 72 || pc.getConfiguration && pc.getConfiguration().sdpSemantics === 'plan-b') { 927 | if (pc.signalingState !== 'stable') { 928 | return; 929 | } 930 | } 931 | return e; 932 | }); 933 | } 934 | 935 | },{"../utils.js":11,"./getdisplaymedia":4,"./getusermedia":5}],4:[function(require,module,exports){ 936 | /* 937 | * Copyright (c) 2018 The adapter.js project authors. All Rights Reserved. 938 | * 939 | * Use of this source code is governed by a BSD-style license 940 | * that can be found in the LICENSE file in the root of the source 941 | * tree. 942 | */ 943 | /* eslint-env node */ 944 | 'use strict'; 945 | 946 | Object.defineProperty(exports, "__esModule", { 947 | value: true 948 | }); 949 | exports.shimGetDisplayMedia = shimGetDisplayMedia; 950 | function shimGetDisplayMedia(window, getSourceId) { 951 | if (window.navigator.mediaDevices && 'getDisplayMedia' in window.navigator.mediaDevices) { 952 | return; 953 | } 954 | if (!window.navigator.mediaDevices) { 955 | return; 956 | } 957 | // getSourceId is a function that returns a promise resolving with 958 | // the sourceId of the screen/window/tab to be shared. 959 | if (typeof getSourceId !== 'function') { 960 | console.error('shimGetDisplayMedia: getSourceId argument is not ' + 'a function'); 961 | return; 962 | } 963 | window.navigator.mediaDevices.getDisplayMedia = function getDisplayMedia(constraints) { 964 | return getSourceId(constraints).then(function (sourceId) { 965 | var widthSpecified = constraints.video && constraints.video.width; 966 | var heightSpecified = constraints.video && constraints.video.height; 967 | var frameRateSpecified = constraints.video && constraints.video.frameRate; 968 | constraints.video = { 969 | mandatory: { 970 | chromeMediaSource: 'desktop', 971 | chromeMediaSourceId: sourceId, 972 | maxFrameRate: frameRateSpecified || 3 973 | } 974 | }; 975 | if (widthSpecified) { 976 | constraints.video.mandatory.maxWidth = widthSpecified; 977 | } 978 | if (heightSpecified) { 979 | constraints.video.mandatory.maxHeight = heightSpecified; 980 | } 981 | return window.navigator.mediaDevices.getUserMedia(constraints); 982 | }); 983 | }; 984 | } 985 | 986 | },{}],5:[function(require,module,exports){ 987 | /* 988 | * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. 989 | * 990 | * Use of this source code is governed by a BSD-style license 991 | * that can be found in the LICENSE file in the root of the source 992 | * tree. 993 | */ 994 | /* eslint-env node */ 995 | 'use strict'; 996 | 997 | Object.defineProperty(exports, "__esModule", { 998 | value: true 999 | }); 1000 | 1001 | 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; }; 1002 | 1003 | exports.shimGetUserMedia = shimGetUserMedia; 1004 | 1005 | var _utils = require('../utils.js'); 1006 | 1007 | var utils = _interopRequireWildcard(_utils); 1008 | 1009 | 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; } } 1010 | 1011 | var logging = utils.log; 1012 | 1013 | function shimGetUserMedia(window, browserDetails) { 1014 | var navigator = window && window.navigator; 1015 | 1016 | if (!navigator.mediaDevices) { 1017 | return; 1018 | } 1019 | 1020 | var constraintsToChrome_ = function constraintsToChrome_(c) { 1021 | if ((typeof c === 'undefined' ? 'undefined' : _typeof(c)) !== 'object' || c.mandatory || c.optional) { 1022 | return c; 1023 | } 1024 | var cc = {}; 1025 | Object.keys(c).forEach(function (key) { 1026 | if (key === 'require' || key === 'advanced' || key === 'mediaSource') { 1027 | return; 1028 | } 1029 | var r = _typeof(c[key]) === 'object' ? c[key] : { ideal: c[key] }; 1030 | if (r.exact !== undefined && typeof r.exact === 'number') { 1031 | r.min = r.max = r.exact; 1032 | } 1033 | var oldname_ = function oldname_(prefix, name) { 1034 | if (prefix) { 1035 | return prefix + name.charAt(0).toUpperCase() + name.slice(1); 1036 | } 1037 | return name === 'deviceId' ? 'sourceId' : name; 1038 | }; 1039 | if (r.ideal !== undefined) { 1040 | cc.optional = cc.optional || []; 1041 | var oc = {}; 1042 | if (typeof r.ideal === 'number') { 1043 | oc[oldname_('min', key)] = r.ideal; 1044 | cc.optional.push(oc); 1045 | oc = {}; 1046 | oc[oldname_('max', key)] = r.ideal; 1047 | cc.optional.push(oc); 1048 | } else { 1049 | oc[oldname_('', key)] = r.ideal; 1050 | cc.optional.push(oc); 1051 | } 1052 | } 1053 | if (r.exact !== undefined && typeof r.exact !== 'number') { 1054 | cc.mandatory = cc.mandatory || {}; 1055 | cc.mandatory[oldname_('', key)] = r.exact; 1056 | } else { 1057 | ['min', 'max'].forEach(function (mix) { 1058 | if (r[mix] !== undefined) { 1059 | cc.mandatory = cc.mandatory || {}; 1060 | cc.mandatory[oldname_(mix, key)] = r[mix]; 1061 | } 1062 | }); 1063 | } 1064 | }); 1065 | if (c.advanced) { 1066 | cc.optional = (cc.optional || []).concat(c.advanced); 1067 | } 1068 | return cc; 1069 | }; 1070 | 1071 | var shimConstraints_ = function shimConstraints_(constraints, func) { 1072 | if (browserDetails.version >= 61) { 1073 | return func(constraints); 1074 | } 1075 | constraints = JSON.parse(JSON.stringify(constraints)); 1076 | if (constraints && _typeof(constraints.audio) === 'object') { 1077 | var remap = function remap(obj, a, b) { 1078 | if (a in obj && !(b in obj)) { 1079 | obj[b] = obj[a]; 1080 | delete obj[a]; 1081 | } 1082 | }; 1083 | constraints = JSON.parse(JSON.stringify(constraints)); 1084 | remap(constraints.audio, 'autoGainControl', 'googAutoGainControl'); 1085 | remap(constraints.audio, 'noiseSuppression', 'googNoiseSuppression'); 1086 | constraints.audio = constraintsToChrome_(constraints.audio); 1087 | } 1088 | if (constraints && _typeof(constraints.video) === 'object') { 1089 | // Shim facingMode for mobile & surface pro. 1090 | var face = constraints.video.facingMode; 1091 | face = face && ((typeof face === 'undefined' ? 'undefined' : _typeof(face)) === 'object' ? face : { ideal: face }); 1092 | var getSupportedFacingModeLies = browserDetails.version < 66; 1093 | 1094 | if (face && (face.exact === 'user' || face.exact === 'environment' || face.ideal === 'user' || face.ideal === 'environment') && !(navigator.mediaDevices.getSupportedConstraints && navigator.mediaDevices.getSupportedConstraints().facingMode && !getSupportedFacingModeLies)) { 1095 | delete constraints.video.facingMode; 1096 | var matches = void 0; 1097 | if (face.exact === 'environment' || face.ideal === 'environment') { 1098 | matches = ['back', 'rear']; 1099 | } else if (face.exact === 'user' || face.ideal === 'user') { 1100 | matches = ['front']; 1101 | } 1102 | if (matches) { 1103 | // Look for matches in label, or use last cam for back (typical). 1104 | return navigator.mediaDevices.enumerateDevices().then(function (devices) { 1105 | devices = devices.filter(function (d) { 1106 | return d.kind === 'videoinput'; 1107 | }); 1108 | var dev = devices.find(function (d) { 1109 | return matches.some(function (match) { 1110 | return d.label.toLowerCase().includes(match); 1111 | }); 1112 | }); 1113 | if (!dev && devices.length && matches.includes('back')) { 1114 | dev = devices[devices.length - 1]; // more likely the back cam 1115 | } 1116 | if (dev) { 1117 | constraints.video.deviceId = face.exact ? { exact: dev.deviceId } : { ideal: dev.deviceId }; 1118 | } 1119 | constraints.video = constraintsToChrome_(constraints.video); 1120 | logging('chrome: ' + JSON.stringify(constraints)); 1121 | return func(constraints); 1122 | }); 1123 | } 1124 | } 1125 | constraints.video = constraintsToChrome_(constraints.video); 1126 | } 1127 | logging('chrome: ' + JSON.stringify(constraints)); 1128 | return func(constraints); 1129 | }; 1130 | 1131 | var shimError_ = function shimError_(e) { 1132 | if (browserDetails.version >= 64) { 1133 | return e; 1134 | } 1135 | return { 1136 | name: { 1137 | PermissionDeniedError: 'NotAllowedError', 1138 | PermissionDismissedError: 'NotAllowedError', 1139 | InvalidStateError: 'NotAllowedError', 1140 | DevicesNotFoundError: 'NotFoundError', 1141 | ConstraintNotSatisfiedError: 'OverconstrainedError', 1142 | TrackStartError: 'NotReadableError', 1143 | MediaDeviceFailedDueToShutdown: 'NotAllowedError', 1144 | MediaDeviceKillSwitchOn: 'NotAllowedError', 1145 | TabCaptureError: 'AbortError', 1146 | ScreenCaptureError: 'AbortError', 1147 | DeviceCaptureError: 'AbortError' 1148 | }[e.name] || e.name, 1149 | message: e.message, 1150 | constraint: e.constraint || e.constraintName, 1151 | toString: function toString() { 1152 | return this.name + (this.message && ': ') + this.message; 1153 | } 1154 | }; 1155 | }; 1156 | 1157 | var getUserMedia_ = function getUserMedia_(constraints, onSuccess, onError) { 1158 | shimConstraints_(constraints, function (c) { 1159 | navigator.webkitGetUserMedia(c, onSuccess, function (e) { 1160 | if (onError) { 1161 | onError(shimError_(e)); 1162 | } 1163 | }); 1164 | }); 1165 | }; 1166 | navigator.getUserMedia = getUserMedia_.bind(navigator); 1167 | 1168 | // Even though Chrome 45 has navigator.mediaDevices and a getUserMedia 1169 | // function which returns a Promise, it does not accept spec-style 1170 | // constraints. 1171 | if (navigator.mediaDevices.getUserMedia) { 1172 | var origGetUserMedia = navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices); 1173 | navigator.mediaDevices.getUserMedia = function (cs) { 1174 | return shimConstraints_(cs, function (c) { 1175 | return origGetUserMedia(c).then(function (stream) { 1176 | if (c.audio && !stream.getAudioTracks().length || c.video && !stream.getVideoTracks().length) { 1177 | stream.getTracks().forEach(function (track) { 1178 | track.stop(); 1179 | }); 1180 | throw new DOMException('', 'NotFoundError'); 1181 | } 1182 | return stream; 1183 | }, function (e) { 1184 | return Promise.reject(shimError_(e)); 1185 | }); 1186 | }); 1187 | }; 1188 | } 1189 | } 1190 | 1191 | },{"../utils.js":11}],6:[function(require,module,exports){ 1192 | /* 1193 | * Copyright (c) 2017 The WebRTC project authors. All Rights Reserved. 1194 | * 1195 | * Use of this source code is governed by a BSD-style license 1196 | * that can be found in the LICENSE file in the root of the source 1197 | * tree. 1198 | */ 1199 | /* eslint-env node */ 1200 | 'use strict'; 1201 | 1202 | Object.defineProperty(exports, "__esModule", { 1203 | value: true 1204 | }); 1205 | 1206 | 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; }; 1207 | 1208 | exports.shimRTCIceCandidate = shimRTCIceCandidate; 1209 | exports.shimMaxMessageSize = shimMaxMessageSize; 1210 | exports.shimSendThrowTypeError = shimSendThrowTypeError; 1211 | exports.shimConnectionState = shimConnectionState; 1212 | exports.removeExtmapAllowMixed = removeExtmapAllowMixed; 1213 | exports.shimAddIceCandidateNullOrEmpty = shimAddIceCandidateNullOrEmpty; 1214 | exports.shimParameterlessSetLocalDescription = shimParameterlessSetLocalDescription; 1215 | 1216 | var _sdp = require('sdp'); 1217 | 1218 | var _sdp2 = _interopRequireDefault(_sdp); 1219 | 1220 | var _utils = require('./utils'); 1221 | 1222 | var utils = _interopRequireWildcard(_utils); 1223 | 1224 | 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; } } 1225 | 1226 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 1227 | 1228 | function shimRTCIceCandidate(window) { 1229 | // foundation is arbitrarily chosen as an indicator for full support for 1230 | // https://w3c.github.io/webrtc-pc/#rtcicecandidate-interface 1231 | if (!window.RTCIceCandidate || window.RTCIceCandidate && 'foundation' in window.RTCIceCandidate.prototype) { 1232 | return; 1233 | } 1234 | 1235 | var NativeRTCIceCandidate = window.RTCIceCandidate; 1236 | window.RTCIceCandidate = function RTCIceCandidate(args) { 1237 | // Remove the a= which shouldn't be part of the candidate string. 1238 | if ((typeof args === 'undefined' ? 'undefined' : _typeof(args)) === 'object' && args.candidate && args.candidate.indexOf('a=') === 0) { 1239 | args = JSON.parse(JSON.stringify(args)); 1240 | args.candidate = args.candidate.substr(2); 1241 | } 1242 | 1243 | if (args.candidate && args.candidate.length) { 1244 | // Augment the native candidate with the parsed fields. 1245 | var nativeCandidate = new NativeRTCIceCandidate(args); 1246 | var parsedCandidate = _sdp2.default.parseCandidate(args.candidate); 1247 | var augmentedCandidate = Object.assign(nativeCandidate, parsedCandidate); 1248 | 1249 | // Add a serializer that does not serialize the extra attributes. 1250 | augmentedCandidate.toJSON = function toJSON() { 1251 | return { 1252 | candidate: augmentedCandidate.candidate, 1253 | sdpMid: augmentedCandidate.sdpMid, 1254 | sdpMLineIndex: augmentedCandidate.sdpMLineIndex, 1255 | usernameFragment: augmentedCandidate.usernameFragment 1256 | }; 1257 | }; 1258 | return augmentedCandidate; 1259 | } 1260 | return new NativeRTCIceCandidate(args); 1261 | }; 1262 | window.RTCIceCandidate.prototype = NativeRTCIceCandidate.prototype; 1263 | 1264 | // Hook up the augmented candidate in onicecandidate and 1265 | // addEventListener('icecandidate', ...) 1266 | utils.wrapPeerConnectionEvent(window, 'icecandidate', function (e) { 1267 | if (e.candidate) { 1268 | Object.defineProperty(e, 'candidate', { 1269 | value: new window.RTCIceCandidate(e.candidate), 1270 | writable: 'false' 1271 | }); 1272 | } 1273 | return e; 1274 | }); 1275 | } 1276 | 1277 | function shimMaxMessageSize(window, browserDetails) { 1278 | if (!window.RTCPeerConnection) { 1279 | return; 1280 | } 1281 | 1282 | if (!('sctp' in window.RTCPeerConnection.prototype)) { 1283 | Object.defineProperty(window.RTCPeerConnection.prototype, 'sctp', { 1284 | get: function get() { 1285 | return typeof this._sctp === 'undefined' ? null : this._sctp; 1286 | } 1287 | }); 1288 | } 1289 | 1290 | var sctpInDescription = function sctpInDescription(description) { 1291 | if (!description || !description.sdp) { 1292 | return false; 1293 | } 1294 | var sections = _sdp2.default.splitSections(description.sdp); 1295 | sections.shift(); 1296 | return sections.some(function (mediaSection) { 1297 | var mLine = _sdp2.default.parseMLine(mediaSection); 1298 | return mLine && mLine.kind === 'application' && mLine.protocol.indexOf('SCTP') !== -1; 1299 | }); 1300 | }; 1301 | 1302 | var getRemoteFirefoxVersion = function getRemoteFirefoxVersion(description) { 1303 | // TODO: Is there a better solution for detecting Firefox? 1304 | var match = description.sdp.match(/mozilla...THIS_IS_SDPARTA-(\d+)/); 1305 | if (match === null || match.length < 2) { 1306 | return -1; 1307 | } 1308 | var version = parseInt(match[1], 10); 1309 | // Test for NaN (yes, this is ugly) 1310 | return version !== version ? -1 : version; 1311 | }; 1312 | 1313 | var getCanSendMaxMessageSize = function getCanSendMaxMessageSize(remoteIsFirefox) { 1314 | // Every implementation we know can send at least 64 KiB. 1315 | // Note: Although Chrome is technically able to send up to 256 KiB, the 1316 | // data does not reach the other peer reliably. 1317 | // See: https://bugs.chromium.org/p/webrtc/issues/detail?id=8419 1318 | var canSendMaxMessageSize = 65536; 1319 | if (browserDetails.browser === 'firefox') { 1320 | if (browserDetails.version < 57) { 1321 | if (remoteIsFirefox === -1) { 1322 | // FF < 57 will send in 16 KiB chunks using the deprecated PPID 1323 | // fragmentation. 1324 | canSendMaxMessageSize = 16384; 1325 | } else { 1326 | // However, other FF (and RAWRTC) can reassemble PPID-fragmented 1327 | // messages. Thus, supporting ~2 GiB when sending. 1328 | canSendMaxMessageSize = 2147483637; 1329 | } 1330 | } else if (browserDetails.version < 60) { 1331 | // Currently, all FF >= 57 will reset the remote maximum message size 1332 | // to the default value when a data channel is created at a later 1333 | // stage. :( 1334 | // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831 1335 | canSendMaxMessageSize = browserDetails.version === 57 ? 65535 : 65536; 1336 | } else { 1337 | // FF >= 60 supports sending ~2 GiB 1338 | canSendMaxMessageSize = 2147483637; 1339 | } 1340 | } 1341 | return canSendMaxMessageSize; 1342 | }; 1343 | 1344 | var getMaxMessageSize = function getMaxMessageSize(description, remoteIsFirefox) { 1345 | // Note: 65536 bytes is the default value from the SDP spec. Also, 1346 | // every implementation we know supports receiving 65536 bytes. 1347 | var maxMessageSize = 65536; 1348 | 1349 | // FF 57 has a slightly incorrect default remote max message size, so 1350 | // we need to adjust it here to avoid a failure when sending. 1351 | // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1425697 1352 | if (browserDetails.browser === 'firefox' && browserDetails.version === 57) { 1353 | maxMessageSize = 65535; 1354 | } 1355 | 1356 | var match = _sdp2.default.matchPrefix(description.sdp, 'a=max-message-size:'); 1357 | if (match.length > 0) { 1358 | maxMessageSize = parseInt(match[0].substr(19), 10); 1359 | } else if (browserDetails.browser === 'firefox' && remoteIsFirefox !== -1) { 1360 | // If the maximum message size is not present in the remote SDP and 1361 | // both local and remote are Firefox, the remote peer can receive 1362 | // ~2 GiB. 1363 | maxMessageSize = 2147483637; 1364 | } 1365 | return maxMessageSize; 1366 | }; 1367 | 1368 | var origSetRemoteDescription = window.RTCPeerConnection.prototype.setRemoteDescription; 1369 | window.RTCPeerConnection.prototype.setRemoteDescription = function setRemoteDescription() { 1370 | this._sctp = null; 1371 | // Chrome decided to not expose .sctp in plan-b mode. 1372 | // As usual, adapter.js has to do an 'ugly worakaround' 1373 | // to cover up the mess. 1374 | if (browserDetails.browser === 'chrome' && browserDetails.version >= 76) { 1375 | var _getConfiguration = this.getConfiguration(), 1376 | sdpSemantics = _getConfiguration.sdpSemantics; 1377 | 1378 | if (sdpSemantics === 'plan-b') { 1379 | Object.defineProperty(this, 'sctp', { 1380 | get: function get() { 1381 | return typeof this._sctp === 'undefined' ? null : this._sctp; 1382 | }, 1383 | 1384 | enumerable: true, 1385 | configurable: true 1386 | }); 1387 | } 1388 | } 1389 | 1390 | if (sctpInDescription(arguments[0])) { 1391 | // Check if the remote is FF. 1392 | var isFirefox = getRemoteFirefoxVersion(arguments[0]); 1393 | 1394 | // Get the maximum message size the local peer is capable of sending 1395 | var canSendMMS = getCanSendMaxMessageSize(isFirefox); 1396 | 1397 | // Get the maximum message size of the remote peer. 1398 | var remoteMMS = getMaxMessageSize(arguments[0], isFirefox); 1399 | 1400 | // Determine final maximum message size 1401 | var maxMessageSize = void 0; 1402 | if (canSendMMS === 0 && remoteMMS === 0) { 1403 | maxMessageSize = Number.POSITIVE_INFINITY; 1404 | } else if (canSendMMS === 0 || remoteMMS === 0) { 1405 | maxMessageSize = Math.max(canSendMMS, remoteMMS); 1406 | } else { 1407 | maxMessageSize = Math.min(canSendMMS, remoteMMS); 1408 | } 1409 | 1410 | // Create a dummy RTCSctpTransport object and the 'maxMessageSize' 1411 | // attribute. 1412 | var sctp = {}; 1413 | Object.defineProperty(sctp, 'maxMessageSize', { 1414 | get: function get() { 1415 | return maxMessageSize; 1416 | } 1417 | }); 1418 | this._sctp = sctp; 1419 | } 1420 | 1421 | return origSetRemoteDescription.apply(this, arguments); 1422 | }; 1423 | } 1424 | 1425 | function shimSendThrowTypeError(window) { 1426 | if (!(window.RTCPeerConnection && 'createDataChannel' in window.RTCPeerConnection.prototype)) { 1427 | return; 1428 | } 1429 | 1430 | // Note: Although Firefox >= 57 has a native implementation, the maximum 1431 | // message size can be reset for all data channels at a later stage. 1432 | // See: https://bugzilla.mozilla.org/show_bug.cgi?id=1426831 1433 | 1434 | function wrapDcSend(dc, pc) { 1435 | var origDataChannelSend = dc.send; 1436 | dc.send = function send() { 1437 | var data = arguments[0]; 1438 | var length = data.length || data.size || data.byteLength; 1439 | if (dc.readyState === 'open' && pc.sctp && length > pc.sctp.maxMessageSize) { 1440 | throw new TypeError('Message too large (can send a maximum of ' + pc.sctp.maxMessageSize + ' bytes)'); 1441 | } 1442 | return origDataChannelSend.apply(dc, arguments); 1443 | }; 1444 | } 1445 | var origCreateDataChannel = window.RTCPeerConnection.prototype.createDataChannel; 1446 | window.RTCPeerConnection.prototype.createDataChannel = function createDataChannel() { 1447 | var dataChannel = origCreateDataChannel.apply(this, arguments); 1448 | wrapDcSend(dataChannel, this); 1449 | return dataChannel; 1450 | }; 1451 | utils.wrapPeerConnectionEvent(window, 'datachannel', function (e) { 1452 | wrapDcSend(e.channel, e.target); 1453 | return e; 1454 | }); 1455 | } 1456 | 1457 | /* shims RTCConnectionState by pretending it is the same as iceConnectionState. 1458 | * See https://bugs.chromium.org/p/webrtc/issues/detail?id=6145#c12 1459 | * for why this is a valid hack in Chrome. In Firefox it is slightly incorrect 1460 | * since DTLS failures would be hidden. See 1461 | * https://bugzilla.mozilla.org/show_bug.cgi?id=1265827 1462 | * for the Firefox tracking bug. 1463 | */ 1464 | function shimConnectionState(window) { 1465 | if (!window.RTCPeerConnection || 'connectionState' in window.RTCPeerConnection.prototype) { 1466 | return; 1467 | } 1468 | var proto = window.RTCPeerConnection.prototype; 1469 | Object.defineProperty(proto, 'connectionState', { 1470 | get: function get() { 1471 | return { 1472 | completed: 'connected', 1473 | checking: 'connecting' 1474 | }[this.iceConnectionState] || this.iceConnectionState; 1475 | }, 1476 | 1477 | enumerable: true, 1478 | configurable: true 1479 | }); 1480 | Object.defineProperty(proto, 'onconnectionstatechange', { 1481 | get: function get() { 1482 | return this._onconnectionstatechange || null; 1483 | }, 1484 | set: function set(cb) { 1485 | if (this._onconnectionstatechange) { 1486 | this.removeEventListener('connectionstatechange', this._onconnectionstatechange); 1487 | delete this._onconnectionstatechange; 1488 | } 1489 | if (cb) { 1490 | this.addEventListener('connectionstatechange', this._onconnectionstatechange = cb); 1491 | } 1492 | }, 1493 | 1494 | enumerable: true, 1495 | configurable: true 1496 | }); 1497 | 1498 | ['setLocalDescription', 'setRemoteDescription'].forEach(function (method) { 1499 | var origMethod = proto[method]; 1500 | proto[method] = function () { 1501 | if (!this._connectionstatechangepoly) { 1502 | this._connectionstatechangepoly = function (e) { 1503 | var pc = e.target; 1504 | if (pc._lastConnectionState !== pc.connectionState) { 1505 | pc._lastConnectionState = pc.connectionState; 1506 | var newEvent = new Event('connectionstatechange', e); 1507 | pc.dispatchEvent(newEvent); 1508 | } 1509 | return e; 1510 | }; 1511 | this.addEventListener('iceconnectionstatechange', this._connectionstatechangepoly); 1512 | } 1513 | return origMethod.apply(this, arguments); 1514 | }; 1515 | }); 1516 | } 1517 | 1518 | function removeExtmapAllowMixed(window, browserDetails) { 1519 | /* remove a=extmap-allow-mixed for webrtc.org < M71 */ 1520 | if (!window.RTCPeerConnection) { 1521 | return; 1522 | } 1523 | if (browserDetails.browser === 'chrome' && browserDetails.version >= 71) { 1524 | return; 1525 | } 1526 | if (browserDetails.browser === 'safari' && browserDetails.version >= 605) { 1527 | return; 1528 | } 1529 | var nativeSRD = window.RTCPeerConnection.prototype.setRemoteDescription; 1530 | window.RTCPeerConnection.prototype.setRemoteDescription = function setRemoteDescription(desc) { 1531 | if (desc && desc.sdp && desc.sdp.indexOf('\na=extmap-allow-mixed') !== -1) { 1532 | var sdp = desc.sdp.split('\n').filter(function (line) { 1533 | return line.trim() !== 'a=extmap-allow-mixed'; 1534 | }).join('\n'); 1535 | // Safari enforces read-only-ness of RTCSessionDescription fields. 1536 | if (window.RTCSessionDescription && desc instanceof window.RTCSessionDescription) { 1537 | arguments[0] = new window.RTCSessionDescription({ 1538 | type: desc.type, 1539 | sdp: sdp 1540 | }); 1541 | } else { 1542 | desc.sdp = sdp; 1543 | } 1544 | } 1545 | return nativeSRD.apply(this, arguments); 1546 | }; 1547 | } 1548 | 1549 | function shimAddIceCandidateNullOrEmpty(window, browserDetails) { 1550 | // Support for addIceCandidate(null or undefined) 1551 | // as well as addIceCandidate({candidate: "", ...}) 1552 | // https://bugs.chromium.org/p/chromium/issues/detail?id=978582 1553 | // Note: must be called before other polyfills which change the signature. 1554 | if (!(window.RTCPeerConnection && window.RTCPeerConnection.prototype)) { 1555 | return; 1556 | } 1557 | var nativeAddIceCandidate = window.RTCPeerConnection.prototype.addIceCandidate; 1558 | if (!nativeAddIceCandidate || nativeAddIceCandidate.length === 0) { 1559 | return; 1560 | } 1561 | window.RTCPeerConnection.prototype.addIceCandidate = function addIceCandidate() { 1562 | if (!arguments[0]) { 1563 | if (arguments[1]) { 1564 | arguments[1].apply(null); 1565 | } 1566 | return Promise.resolve(); 1567 | } 1568 | // Firefox 68+ emits and processes {candidate: "", ...}, ignore 1569 | // in older versions. 1570 | // Native support for ignoring exists for Chrome M77+. 1571 | // Safari ignores as well, exact version unknown but works in the same 1572 | // version that also ignores addIceCandidate(null). 1573 | if ((browserDetails.browser === 'chrome' && browserDetails.version < 78 || browserDetails.browser === 'firefox' && browserDetails.version < 68 || browserDetails.browser === 'safari') && arguments[0] && arguments[0].candidate === '') { 1574 | return Promise.resolve(); 1575 | } 1576 | return nativeAddIceCandidate.apply(this, arguments); 1577 | }; 1578 | } 1579 | 1580 | // Note: Make sure to call this ahead of APIs that modify 1581 | // setLocalDescription.length 1582 | function shimParameterlessSetLocalDescription(window, browserDetails) { 1583 | if (!(window.RTCPeerConnection && window.RTCPeerConnection.prototype)) { 1584 | return; 1585 | } 1586 | var nativeSetLocalDescription = window.RTCPeerConnection.prototype.setLocalDescription; 1587 | if (!nativeSetLocalDescription || nativeSetLocalDescription.length === 0) { 1588 | return; 1589 | } 1590 | window.RTCPeerConnection.prototype.setLocalDescription = function setLocalDescription() { 1591 | var _this = this; 1592 | 1593 | var desc = arguments[0] || {}; 1594 | if ((typeof desc === 'undefined' ? 'undefined' : _typeof(desc)) !== 'object' || desc.type && desc.sdp) { 1595 | return nativeSetLocalDescription.apply(this, arguments); 1596 | } 1597 | // The remaining steps should technically happen when SLD comes off the 1598 | // RTCPeerConnection's operations chain (not ahead of going on it), but 1599 | // this is too difficult to shim. Instead, this shim only covers the 1600 | // common case where the operations chain is empty. This is imperfect, but 1601 | // should cover many cases. Rationale: Even if we can't reduce the glare 1602 | // window to zero on imperfect implementations, there's value in tapping 1603 | // into the perfect negotiation pattern that several browsers support. 1604 | desc = { type: desc.type, sdp: desc.sdp }; 1605 | if (!desc.type) { 1606 | switch (this.signalingState) { 1607 | case 'stable': 1608 | case 'have-local-offer': 1609 | case 'have-remote-pranswer': 1610 | desc.type = 'offer'; 1611 | break; 1612 | default: 1613 | desc.type = 'answer'; 1614 | break; 1615 | } 1616 | } 1617 | if (desc.sdp || desc.type !== 'offer' && desc.type !== 'answer') { 1618 | return nativeSetLocalDescription.apply(this, [desc]); 1619 | } 1620 | var func = desc.type === 'offer' ? this.createOffer : this.createAnswer; 1621 | return func.apply(this).then(function (d) { 1622 | return nativeSetLocalDescription.apply(_this, [d]); 1623 | }); 1624 | }; 1625 | } 1626 | 1627 | },{"./utils":11,"sdp":12}],7:[function(require,module,exports){ 1628 | /* 1629 | * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. 1630 | * 1631 | * Use of this source code is governed by a BSD-style license 1632 | * that can be found in the LICENSE file in the root of the source 1633 | * tree. 1634 | */ 1635 | /* eslint-env node */ 1636 | 'use strict'; 1637 | 1638 | Object.defineProperty(exports, "__esModule", { 1639 | value: true 1640 | }); 1641 | exports.shimGetDisplayMedia = exports.shimGetUserMedia = undefined; 1642 | 1643 | 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; }; 1644 | 1645 | var _getusermedia = require('./getusermedia'); 1646 | 1647 | Object.defineProperty(exports, 'shimGetUserMedia', { 1648 | enumerable: true, 1649 | get: function get() { 1650 | return _getusermedia.shimGetUserMedia; 1651 | } 1652 | }); 1653 | 1654 | var _getdisplaymedia = require('./getdisplaymedia'); 1655 | 1656 | Object.defineProperty(exports, 'shimGetDisplayMedia', { 1657 | enumerable: true, 1658 | get: function get() { 1659 | return _getdisplaymedia.shimGetDisplayMedia; 1660 | } 1661 | }); 1662 | exports.shimOnTrack = shimOnTrack; 1663 | exports.shimPeerConnection = shimPeerConnection; 1664 | exports.shimSenderGetStats = shimSenderGetStats; 1665 | exports.shimReceiverGetStats = shimReceiverGetStats; 1666 | exports.shimRemoveStream = shimRemoveStream; 1667 | exports.shimRTCDataChannel = shimRTCDataChannel; 1668 | exports.shimAddTransceiver = shimAddTransceiver; 1669 | exports.shimGetParameters = shimGetParameters; 1670 | exports.shimCreateOffer = shimCreateOffer; 1671 | exports.shimCreateAnswer = shimCreateAnswer; 1672 | 1673 | var _utils = require('../utils'); 1674 | 1675 | var utils = _interopRequireWildcard(_utils); 1676 | 1677 | 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; } } 1678 | 1679 | 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; } 1680 | 1681 | function shimOnTrack(window) { 1682 | if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCTrackEvent && 'receiver' in window.RTCTrackEvent.prototype && !('transceiver' in window.RTCTrackEvent.prototype)) { 1683 | Object.defineProperty(window.RTCTrackEvent.prototype, 'transceiver', { 1684 | get: function get() { 1685 | return { receiver: this.receiver }; 1686 | } 1687 | }); 1688 | } 1689 | } 1690 | 1691 | function shimPeerConnection(window, browserDetails) { 1692 | if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) !== 'object' || !(window.RTCPeerConnection || window.mozRTCPeerConnection)) { 1693 | return; // probably media.peerconnection.enabled=false in about:config 1694 | } 1695 | if (!window.RTCPeerConnection && window.mozRTCPeerConnection) { 1696 | // very basic support for old versions. 1697 | window.RTCPeerConnection = window.mozRTCPeerConnection; 1698 | } 1699 | 1700 | if (browserDetails.version < 53) { 1701 | // shim away need for obsolete RTCIceCandidate/RTCSessionDescription. 1702 | ['setLocalDescription', 'setRemoteDescription', 'addIceCandidate'].forEach(function (method) { 1703 | var nativeMethod = window.RTCPeerConnection.prototype[method]; 1704 | var methodObj = _defineProperty({}, method, function () { 1705 | arguments[0] = new (method === 'addIceCandidate' ? window.RTCIceCandidate : window.RTCSessionDescription)(arguments[0]); 1706 | return nativeMethod.apply(this, arguments); 1707 | }); 1708 | window.RTCPeerConnection.prototype[method] = methodObj[method]; 1709 | }); 1710 | } 1711 | 1712 | var modernStatsTypes = { 1713 | inboundrtp: 'inbound-rtp', 1714 | outboundrtp: 'outbound-rtp', 1715 | candidatepair: 'candidate-pair', 1716 | localcandidate: 'local-candidate', 1717 | remotecandidate: 'remote-candidate' 1718 | }; 1719 | 1720 | var nativeGetStats = window.RTCPeerConnection.prototype.getStats; 1721 | window.RTCPeerConnection.prototype.getStats = function getStats() { 1722 | var _arguments = Array.prototype.slice.call(arguments), 1723 | selector = _arguments[0], 1724 | onSucc = _arguments[1], 1725 | onErr = _arguments[2]; 1726 | 1727 | return nativeGetStats.apply(this, [selector || null]).then(function (stats) { 1728 | if (browserDetails.version < 53 && !onSucc) { 1729 | // Shim only promise getStats with spec-hyphens in type names 1730 | // Leave callback version alone; misc old uses of forEach before Map 1731 | try { 1732 | stats.forEach(function (stat) { 1733 | stat.type = modernStatsTypes[stat.type] || stat.type; 1734 | }); 1735 | } catch (e) { 1736 | if (e.name !== 'TypeError') { 1737 | throw e; 1738 | } 1739 | // Avoid TypeError: "type" is read-only, in old versions. 34-43ish 1740 | stats.forEach(function (stat, i) { 1741 | stats.set(i, Object.assign({}, stat, { 1742 | type: modernStatsTypes[stat.type] || stat.type 1743 | })); 1744 | }); 1745 | } 1746 | } 1747 | return stats; 1748 | }).then(onSucc, onErr); 1749 | }; 1750 | } 1751 | 1752 | function shimSenderGetStats(window) { 1753 | if (!((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCPeerConnection && window.RTCRtpSender)) { 1754 | return; 1755 | } 1756 | if (window.RTCRtpSender && 'getStats' in window.RTCRtpSender.prototype) { 1757 | return; 1758 | } 1759 | var origGetSenders = window.RTCPeerConnection.prototype.getSenders; 1760 | if (origGetSenders) { 1761 | window.RTCPeerConnection.prototype.getSenders = function getSenders() { 1762 | var _this = this; 1763 | 1764 | var senders = origGetSenders.apply(this, []); 1765 | senders.forEach(function (sender) { 1766 | return sender._pc = _this; 1767 | }); 1768 | return senders; 1769 | }; 1770 | } 1771 | 1772 | var origAddTrack = window.RTCPeerConnection.prototype.addTrack; 1773 | if (origAddTrack) { 1774 | window.RTCPeerConnection.prototype.addTrack = function addTrack() { 1775 | var sender = origAddTrack.apply(this, arguments); 1776 | sender._pc = this; 1777 | return sender; 1778 | }; 1779 | } 1780 | window.RTCRtpSender.prototype.getStats = function getStats() { 1781 | return this.track ? this._pc.getStats(this.track) : Promise.resolve(new Map()); 1782 | }; 1783 | } 1784 | 1785 | function shimReceiverGetStats(window) { 1786 | if (!((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCPeerConnection && window.RTCRtpSender)) { 1787 | return; 1788 | } 1789 | if (window.RTCRtpSender && 'getStats' in window.RTCRtpReceiver.prototype) { 1790 | return; 1791 | } 1792 | var origGetReceivers = window.RTCPeerConnection.prototype.getReceivers; 1793 | if (origGetReceivers) { 1794 | window.RTCPeerConnection.prototype.getReceivers = function getReceivers() { 1795 | var _this2 = this; 1796 | 1797 | var receivers = origGetReceivers.apply(this, []); 1798 | receivers.forEach(function (receiver) { 1799 | return receiver._pc = _this2; 1800 | }); 1801 | return receivers; 1802 | }; 1803 | } 1804 | utils.wrapPeerConnectionEvent(window, 'track', function (e) { 1805 | e.receiver._pc = e.srcElement; 1806 | return e; 1807 | }); 1808 | window.RTCRtpReceiver.prototype.getStats = function getStats() { 1809 | return this._pc.getStats(this.track); 1810 | }; 1811 | } 1812 | 1813 | function shimRemoveStream(window) { 1814 | if (!window.RTCPeerConnection || 'removeStream' in window.RTCPeerConnection.prototype) { 1815 | return; 1816 | } 1817 | window.RTCPeerConnection.prototype.removeStream = function removeStream(stream) { 1818 | var _this3 = this; 1819 | 1820 | utils.deprecated('removeStream', 'removeTrack'); 1821 | this.getSenders().forEach(function (sender) { 1822 | if (sender.track && stream.getTracks().includes(sender.track)) { 1823 | _this3.removeTrack(sender); 1824 | } 1825 | }); 1826 | }; 1827 | } 1828 | 1829 | function shimRTCDataChannel(window) { 1830 | // rename DataChannel to RTCDataChannel (native fix in FF60): 1831 | // https://bugzilla.mozilla.org/show_bug.cgi?id=1173851 1832 | if (window.DataChannel && !window.RTCDataChannel) { 1833 | window.RTCDataChannel = window.DataChannel; 1834 | } 1835 | } 1836 | 1837 | function shimAddTransceiver(window) { 1838 | // https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647 1839 | // Firefox ignores the init sendEncodings options passed to addTransceiver 1840 | // https://bugzilla.mozilla.org/show_bug.cgi?id=1396918 1841 | if (!((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCPeerConnection)) { 1842 | return; 1843 | } 1844 | var origAddTransceiver = window.RTCPeerConnection.prototype.addTransceiver; 1845 | if (origAddTransceiver) { 1846 | window.RTCPeerConnection.prototype.addTransceiver = function addTransceiver() { 1847 | this.setParametersPromises = []; 1848 | var initParameters = arguments[1]; 1849 | var shouldPerformCheck = initParameters && 'sendEncodings' in initParameters; 1850 | if (shouldPerformCheck) { 1851 | // If sendEncodings params are provided, validate grammar 1852 | initParameters.sendEncodings.forEach(function (encodingParam) { 1853 | if ('rid' in encodingParam) { 1854 | var ridRegex = /^[a-z0-9]{0,16}$/i; 1855 | if (!ridRegex.test(encodingParam.rid)) { 1856 | throw new TypeError('Invalid RID value provided.'); 1857 | } 1858 | } 1859 | if ('scaleResolutionDownBy' in encodingParam) { 1860 | if (!(parseFloat(encodingParam.scaleResolutionDownBy) >= 1.0)) { 1861 | throw new RangeError('scale_resolution_down_by must be >= 1.0'); 1862 | } 1863 | } 1864 | if ('maxFramerate' in encodingParam) { 1865 | if (!(parseFloat(encodingParam.maxFramerate) >= 0)) { 1866 | throw new RangeError('max_framerate must be >= 0.0'); 1867 | } 1868 | } 1869 | }); 1870 | } 1871 | var transceiver = origAddTransceiver.apply(this, arguments); 1872 | if (shouldPerformCheck) { 1873 | // Check if the init options were applied. If not we do this in an 1874 | // asynchronous way and save the promise reference in a global object. 1875 | // This is an ugly hack, but at the same time is way more robust than 1876 | // checking the sender parameters before and after the createOffer 1877 | // Also note that after the createoffer we are not 100% sure that 1878 | // the params were asynchronously applied so we might miss the 1879 | // opportunity to recreate offer. 1880 | var sender = transceiver.sender; 1881 | 1882 | var params = sender.getParameters(); 1883 | if (!('encodings' in params) || 1884 | // Avoid being fooled by patched getParameters() below. 1885 | params.encodings.length === 1 && Object.keys(params.encodings[0]).length === 0) { 1886 | params.encodings = initParameters.sendEncodings; 1887 | sender.sendEncodings = initParameters.sendEncodings; 1888 | this.setParametersPromises.push(sender.setParameters(params).then(function () { 1889 | delete sender.sendEncodings; 1890 | }).catch(function () { 1891 | delete sender.sendEncodings; 1892 | })); 1893 | } 1894 | } 1895 | return transceiver; 1896 | }; 1897 | } 1898 | } 1899 | 1900 | function shimGetParameters(window) { 1901 | if (!((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCRtpSender)) { 1902 | return; 1903 | } 1904 | var origGetParameters = window.RTCRtpSender.prototype.getParameters; 1905 | if (origGetParameters) { 1906 | window.RTCRtpSender.prototype.getParameters = function getParameters() { 1907 | var params = origGetParameters.apply(this, arguments); 1908 | if (!('encodings' in params)) { 1909 | params.encodings = [].concat(this.sendEncodings || [{}]); 1910 | } 1911 | return params; 1912 | }; 1913 | } 1914 | } 1915 | 1916 | function shimCreateOffer(window) { 1917 | // https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647 1918 | // Firefox ignores the init sendEncodings options passed to addTransceiver 1919 | // https://bugzilla.mozilla.org/show_bug.cgi?id=1396918 1920 | if (!((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCPeerConnection)) { 1921 | return; 1922 | } 1923 | var origCreateOffer = window.RTCPeerConnection.prototype.createOffer; 1924 | window.RTCPeerConnection.prototype.createOffer = function createOffer() { 1925 | var _this4 = this, 1926 | _arguments2 = arguments; 1927 | 1928 | if (this.setParametersPromises && this.setParametersPromises.length) { 1929 | return Promise.all(this.setParametersPromises).then(function () { 1930 | return origCreateOffer.apply(_this4, _arguments2); 1931 | }).finally(function () { 1932 | _this4.setParametersPromises = []; 1933 | }); 1934 | } 1935 | return origCreateOffer.apply(this, arguments); 1936 | }; 1937 | } 1938 | 1939 | function shimCreateAnswer(window) { 1940 | // https://github.com/webrtcHacks/adapter/issues/998#issuecomment-516921647 1941 | // Firefox ignores the init sendEncodings options passed to addTransceiver 1942 | // https://bugzilla.mozilla.org/show_bug.cgi?id=1396918 1943 | if (!((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCPeerConnection)) { 1944 | return; 1945 | } 1946 | var origCreateAnswer = window.RTCPeerConnection.prototype.createAnswer; 1947 | window.RTCPeerConnection.prototype.createAnswer = function createAnswer() { 1948 | var _this5 = this, 1949 | _arguments3 = arguments; 1950 | 1951 | if (this.setParametersPromises && this.setParametersPromises.length) { 1952 | return Promise.all(this.setParametersPromises).then(function () { 1953 | return origCreateAnswer.apply(_this5, _arguments3); 1954 | }).finally(function () { 1955 | _this5.setParametersPromises = []; 1956 | }); 1957 | } 1958 | return origCreateAnswer.apply(this, arguments); 1959 | }; 1960 | } 1961 | 1962 | },{"../utils":11,"./getdisplaymedia":8,"./getusermedia":9}],8:[function(require,module,exports){ 1963 | /* 1964 | * Copyright (c) 2018 The adapter.js project authors. All Rights Reserved. 1965 | * 1966 | * Use of this source code is governed by a BSD-style license 1967 | * that can be found in the LICENSE file in the root of the source 1968 | * tree. 1969 | */ 1970 | /* eslint-env node */ 1971 | 'use strict'; 1972 | 1973 | Object.defineProperty(exports, "__esModule", { 1974 | value: true 1975 | }); 1976 | exports.shimGetDisplayMedia = shimGetDisplayMedia; 1977 | function shimGetDisplayMedia(window, preferredMediaSource) { 1978 | if (window.navigator.mediaDevices && 'getDisplayMedia' in window.navigator.mediaDevices) { 1979 | return; 1980 | } 1981 | if (!window.navigator.mediaDevices) { 1982 | return; 1983 | } 1984 | window.navigator.mediaDevices.getDisplayMedia = function getDisplayMedia(constraints) { 1985 | if (!(constraints && constraints.video)) { 1986 | var err = new DOMException('getDisplayMedia without video ' + 'constraints is undefined'); 1987 | err.name = 'NotFoundError'; 1988 | // from https://heycam.github.io/webidl/#idl-DOMException-error-names 1989 | err.code = 8; 1990 | return Promise.reject(err); 1991 | } 1992 | if (constraints.video === true) { 1993 | constraints.video = { mediaSource: preferredMediaSource }; 1994 | } else { 1995 | constraints.video.mediaSource = preferredMediaSource; 1996 | } 1997 | return window.navigator.mediaDevices.getUserMedia(constraints); 1998 | }; 1999 | } 2000 | 2001 | },{}],9:[function(require,module,exports){ 2002 | /* 2003 | * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. 2004 | * 2005 | * Use of this source code is governed by a BSD-style license 2006 | * that can be found in the LICENSE file in the root of the source 2007 | * tree. 2008 | */ 2009 | /* eslint-env node */ 2010 | 'use strict'; 2011 | 2012 | Object.defineProperty(exports, "__esModule", { 2013 | value: true 2014 | }); 2015 | 2016 | 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; }; 2017 | 2018 | exports.shimGetUserMedia = shimGetUserMedia; 2019 | 2020 | var _utils = require('../utils'); 2021 | 2022 | var utils = _interopRequireWildcard(_utils); 2023 | 2024 | 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; } } 2025 | 2026 | function shimGetUserMedia(window, browserDetails) { 2027 | var navigator = window && window.navigator; 2028 | var MediaStreamTrack = window && window.MediaStreamTrack; 2029 | 2030 | navigator.getUserMedia = function (constraints, onSuccess, onError) { 2031 | // Replace Firefox 44+'s deprecation warning with unprefixed version. 2032 | utils.deprecated('navigator.getUserMedia', 'navigator.mediaDevices.getUserMedia'); 2033 | navigator.mediaDevices.getUserMedia(constraints).then(onSuccess, onError); 2034 | }; 2035 | 2036 | if (!(browserDetails.version > 55 && 'autoGainControl' in navigator.mediaDevices.getSupportedConstraints())) { 2037 | var remap = function remap(obj, a, b) { 2038 | if (a in obj && !(b in obj)) { 2039 | obj[b] = obj[a]; 2040 | delete obj[a]; 2041 | } 2042 | }; 2043 | 2044 | var nativeGetUserMedia = navigator.mediaDevices.getUserMedia.bind(navigator.mediaDevices); 2045 | navigator.mediaDevices.getUserMedia = function (c) { 2046 | if ((typeof c === 'undefined' ? 'undefined' : _typeof(c)) === 'object' && _typeof(c.audio) === 'object') { 2047 | c = JSON.parse(JSON.stringify(c)); 2048 | remap(c.audio, 'autoGainControl', 'mozAutoGainControl'); 2049 | remap(c.audio, 'noiseSuppression', 'mozNoiseSuppression'); 2050 | } 2051 | return nativeGetUserMedia(c); 2052 | }; 2053 | 2054 | if (MediaStreamTrack && MediaStreamTrack.prototype.getSettings) { 2055 | var nativeGetSettings = MediaStreamTrack.prototype.getSettings; 2056 | MediaStreamTrack.prototype.getSettings = function () { 2057 | var obj = nativeGetSettings.apply(this, arguments); 2058 | remap(obj, 'mozAutoGainControl', 'autoGainControl'); 2059 | remap(obj, 'mozNoiseSuppression', 'noiseSuppression'); 2060 | return obj; 2061 | }; 2062 | } 2063 | 2064 | if (MediaStreamTrack && MediaStreamTrack.prototype.applyConstraints) { 2065 | var nativeApplyConstraints = MediaStreamTrack.prototype.applyConstraints; 2066 | MediaStreamTrack.prototype.applyConstraints = function (c) { 2067 | if (this.kind === 'audio' && (typeof c === 'undefined' ? 'undefined' : _typeof(c)) === 'object') { 2068 | c = JSON.parse(JSON.stringify(c)); 2069 | remap(c, 'autoGainControl', 'mozAutoGainControl'); 2070 | remap(c, 'noiseSuppression', 'mozNoiseSuppression'); 2071 | } 2072 | return nativeApplyConstraints.apply(this, [c]); 2073 | }; 2074 | } 2075 | } 2076 | } 2077 | 2078 | },{"../utils":11}],10:[function(require,module,exports){ 2079 | /* 2080 | * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. 2081 | * 2082 | * Use of this source code is governed by a BSD-style license 2083 | * that can be found in the LICENSE file in the root of the source 2084 | * tree. 2085 | */ 2086 | 'use strict'; 2087 | 2088 | Object.defineProperty(exports, "__esModule", { 2089 | value: true 2090 | }); 2091 | 2092 | 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; }; 2093 | 2094 | exports.shimLocalStreamsAPI = shimLocalStreamsAPI; 2095 | exports.shimRemoteStreamsAPI = shimRemoteStreamsAPI; 2096 | exports.shimCallbacksAPI = shimCallbacksAPI; 2097 | exports.shimGetUserMedia = shimGetUserMedia; 2098 | exports.shimConstraints = shimConstraints; 2099 | exports.shimRTCIceServerUrls = shimRTCIceServerUrls; 2100 | exports.shimTrackEventTransceiver = shimTrackEventTransceiver; 2101 | exports.shimCreateOfferLegacy = shimCreateOfferLegacy; 2102 | exports.shimAudioContext = shimAudioContext; 2103 | 2104 | var _utils = require('../utils'); 2105 | 2106 | var utils = _interopRequireWildcard(_utils); 2107 | 2108 | 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; } } 2109 | 2110 | function shimLocalStreamsAPI(window) { 2111 | if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) !== 'object' || !window.RTCPeerConnection) { 2112 | return; 2113 | } 2114 | if (!('getLocalStreams' in window.RTCPeerConnection.prototype)) { 2115 | window.RTCPeerConnection.prototype.getLocalStreams = function getLocalStreams() { 2116 | if (!this._localStreams) { 2117 | this._localStreams = []; 2118 | } 2119 | return this._localStreams; 2120 | }; 2121 | } 2122 | if (!('addStream' in window.RTCPeerConnection.prototype)) { 2123 | var _addTrack = window.RTCPeerConnection.prototype.addTrack; 2124 | window.RTCPeerConnection.prototype.addStream = function addStream(stream) { 2125 | var _this = this; 2126 | 2127 | if (!this._localStreams) { 2128 | this._localStreams = []; 2129 | } 2130 | if (!this._localStreams.includes(stream)) { 2131 | this._localStreams.push(stream); 2132 | } 2133 | // Try to emulate Chrome's behaviour of adding in audio-video order. 2134 | // Safari orders by track id. 2135 | stream.getAudioTracks().forEach(function (track) { 2136 | return _addTrack.call(_this, track, stream); 2137 | }); 2138 | stream.getVideoTracks().forEach(function (track) { 2139 | return _addTrack.call(_this, track, stream); 2140 | }); 2141 | }; 2142 | 2143 | window.RTCPeerConnection.prototype.addTrack = function addTrack(track) { 2144 | var _this2 = this; 2145 | 2146 | for (var _len = arguments.length, streams = Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) { 2147 | streams[_key - 1] = arguments[_key]; 2148 | } 2149 | 2150 | if (streams) { 2151 | streams.forEach(function (stream) { 2152 | if (!_this2._localStreams) { 2153 | _this2._localStreams = [stream]; 2154 | } else if (!_this2._localStreams.includes(stream)) { 2155 | _this2._localStreams.push(stream); 2156 | } 2157 | }); 2158 | } 2159 | return _addTrack.apply(this, arguments); 2160 | }; 2161 | } 2162 | if (!('removeStream' in window.RTCPeerConnection.prototype)) { 2163 | window.RTCPeerConnection.prototype.removeStream = function removeStream(stream) { 2164 | var _this3 = this; 2165 | 2166 | if (!this._localStreams) { 2167 | this._localStreams = []; 2168 | } 2169 | var index = this._localStreams.indexOf(stream); 2170 | if (index === -1) { 2171 | return; 2172 | } 2173 | this._localStreams.splice(index, 1); 2174 | var tracks = stream.getTracks(); 2175 | this.getSenders().forEach(function (sender) { 2176 | if (tracks.includes(sender.track)) { 2177 | _this3.removeTrack(sender); 2178 | } 2179 | }); 2180 | }; 2181 | } 2182 | } 2183 | 2184 | function shimRemoteStreamsAPI(window) { 2185 | if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) !== 'object' || !window.RTCPeerConnection) { 2186 | return; 2187 | } 2188 | if (!('getRemoteStreams' in window.RTCPeerConnection.prototype)) { 2189 | window.RTCPeerConnection.prototype.getRemoteStreams = function getRemoteStreams() { 2190 | return this._remoteStreams ? this._remoteStreams : []; 2191 | }; 2192 | } 2193 | if (!('onaddstream' in window.RTCPeerConnection.prototype)) { 2194 | Object.defineProperty(window.RTCPeerConnection.prototype, 'onaddstream', { 2195 | get: function get() { 2196 | return this._onaddstream; 2197 | }, 2198 | set: function set(f) { 2199 | var _this4 = this; 2200 | 2201 | if (this._onaddstream) { 2202 | this.removeEventListener('addstream', this._onaddstream); 2203 | this.removeEventListener('track', this._onaddstreampoly); 2204 | } 2205 | this.addEventListener('addstream', this._onaddstream = f); 2206 | this.addEventListener('track', this._onaddstreampoly = function (e) { 2207 | e.streams.forEach(function (stream) { 2208 | if (!_this4._remoteStreams) { 2209 | _this4._remoteStreams = []; 2210 | } 2211 | if (_this4._remoteStreams.includes(stream)) { 2212 | return; 2213 | } 2214 | _this4._remoteStreams.push(stream); 2215 | var event = new Event('addstream'); 2216 | event.stream = stream; 2217 | _this4.dispatchEvent(event); 2218 | }); 2219 | }); 2220 | } 2221 | }); 2222 | var origSetRemoteDescription = window.RTCPeerConnection.prototype.setRemoteDescription; 2223 | window.RTCPeerConnection.prototype.setRemoteDescription = function setRemoteDescription() { 2224 | var pc = this; 2225 | if (!this._onaddstreampoly) { 2226 | this.addEventListener('track', this._onaddstreampoly = function (e) { 2227 | e.streams.forEach(function (stream) { 2228 | if (!pc._remoteStreams) { 2229 | pc._remoteStreams = []; 2230 | } 2231 | if (pc._remoteStreams.indexOf(stream) >= 0) { 2232 | return; 2233 | } 2234 | pc._remoteStreams.push(stream); 2235 | var event = new Event('addstream'); 2236 | event.stream = stream; 2237 | pc.dispatchEvent(event); 2238 | }); 2239 | }); 2240 | } 2241 | return origSetRemoteDescription.apply(pc, arguments); 2242 | }; 2243 | } 2244 | } 2245 | 2246 | function shimCallbacksAPI(window) { 2247 | if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) !== 'object' || !window.RTCPeerConnection) { 2248 | return; 2249 | } 2250 | var prototype = window.RTCPeerConnection.prototype; 2251 | var origCreateOffer = prototype.createOffer; 2252 | var origCreateAnswer = prototype.createAnswer; 2253 | var setLocalDescription = prototype.setLocalDescription; 2254 | var setRemoteDescription = prototype.setRemoteDescription; 2255 | var addIceCandidate = prototype.addIceCandidate; 2256 | 2257 | prototype.createOffer = function createOffer(successCallback, failureCallback) { 2258 | var options = arguments.length >= 2 ? arguments[2] : arguments[0]; 2259 | var promise = origCreateOffer.apply(this, [options]); 2260 | if (!failureCallback) { 2261 | return promise; 2262 | } 2263 | promise.then(successCallback, failureCallback); 2264 | return Promise.resolve(); 2265 | }; 2266 | 2267 | prototype.createAnswer = function createAnswer(successCallback, failureCallback) { 2268 | var options = arguments.length >= 2 ? arguments[2] : arguments[0]; 2269 | var promise = origCreateAnswer.apply(this, [options]); 2270 | if (!failureCallback) { 2271 | return promise; 2272 | } 2273 | promise.then(successCallback, failureCallback); 2274 | return Promise.resolve(); 2275 | }; 2276 | 2277 | var withCallback = function withCallback(description, successCallback, failureCallback) { 2278 | var promise = setLocalDescription.apply(this, [description]); 2279 | if (!failureCallback) { 2280 | return promise; 2281 | } 2282 | promise.then(successCallback, failureCallback); 2283 | return Promise.resolve(); 2284 | }; 2285 | prototype.setLocalDescription = withCallback; 2286 | 2287 | withCallback = function withCallback(description, successCallback, failureCallback) { 2288 | var promise = setRemoteDescription.apply(this, [description]); 2289 | if (!failureCallback) { 2290 | return promise; 2291 | } 2292 | promise.then(successCallback, failureCallback); 2293 | return Promise.resolve(); 2294 | }; 2295 | prototype.setRemoteDescription = withCallback; 2296 | 2297 | withCallback = function withCallback(candidate, successCallback, failureCallback) { 2298 | var promise = addIceCandidate.apply(this, [candidate]); 2299 | if (!failureCallback) { 2300 | return promise; 2301 | } 2302 | promise.then(successCallback, failureCallback); 2303 | return Promise.resolve(); 2304 | }; 2305 | prototype.addIceCandidate = withCallback; 2306 | } 2307 | 2308 | function shimGetUserMedia(window) { 2309 | var navigator = window && window.navigator; 2310 | 2311 | if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { 2312 | // shim not needed in Safari 12.1 2313 | var mediaDevices = navigator.mediaDevices; 2314 | var _getUserMedia = mediaDevices.getUserMedia.bind(mediaDevices); 2315 | navigator.mediaDevices.getUserMedia = function (constraints) { 2316 | return _getUserMedia(shimConstraints(constraints)); 2317 | }; 2318 | } 2319 | 2320 | if (!navigator.getUserMedia && navigator.mediaDevices && navigator.mediaDevices.getUserMedia) { 2321 | navigator.getUserMedia = function getUserMedia(constraints, cb, errcb) { 2322 | navigator.mediaDevices.getUserMedia(constraints).then(cb, errcb); 2323 | }.bind(navigator); 2324 | } 2325 | } 2326 | 2327 | function shimConstraints(constraints) { 2328 | if (constraints && constraints.video !== undefined) { 2329 | return Object.assign({}, constraints, { video: utils.compactObject(constraints.video) }); 2330 | } 2331 | 2332 | return constraints; 2333 | } 2334 | 2335 | function shimRTCIceServerUrls(window) { 2336 | if (!window.RTCPeerConnection) { 2337 | return; 2338 | } 2339 | // migrate from non-spec RTCIceServer.url to RTCIceServer.urls 2340 | var OrigPeerConnection = window.RTCPeerConnection; 2341 | window.RTCPeerConnection = function RTCPeerConnection(pcConfig, pcConstraints) { 2342 | if (pcConfig && pcConfig.iceServers) { 2343 | var newIceServers = []; 2344 | for (var i = 0; i < pcConfig.iceServers.length; i++) { 2345 | var server = pcConfig.iceServers[i]; 2346 | if (!server.hasOwnProperty('urls') && server.hasOwnProperty('url')) { 2347 | utils.deprecated('RTCIceServer.url', 'RTCIceServer.urls'); 2348 | server = JSON.parse(JSON.stringify(server)); 2349 | server.urls = server.url; 2350 | delete server.url; 2351 | newIceServers.push(server); 2352 | } else { 2353 | newIceServers.push(pcConfig.iceServers[i]); 2354 | } 2355 | } 2356 | pcConfig.iceServers = newIceServers; 2357 | } 2358 | return new OrigPeerConnection(pcConfig, pcConstraints); 2359 | }; 2360 | window.RTCPeerConnection.prototype = OrigPeerConnection.prototype; 2361 | // wrap static methods. Currently just generateCertificate. 2362 | if ('generateCertificate' in OrigPeerConnection) { 2363 | Object.defineProperty(window.RTCPeerConnection, 'generateCertificate', { 2364 | get: function get() { 2365 | return OrigPeerConnection.generateCertificate; 2366 | } 2367 | }); 2368 | } 2369 | } 2370 | 2371 | function shimTrackEventTransceiver(window) { 2372 | // Add event.transceiver member over deprecated event.receiver 2373 | if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object' && window.RTCTrackEvent && 'receiver' in window.RTCTrackEvent.prototype && !('transceiver' in window.RTCTrackEvent.prototype)) { 2374 | Object.defineProperty(window.RTCTrackEvent.prototype, 'transceiver', { 2375 | get: function get() { 2376 | return { receiver: this.receiver }; 2377 | } 2378 | }); 2379 | } 2380 | } 2381 | 2382 | function shimCreateOfferLegacy(window) { 2383 | var origCreateOffer = window.RTCPeerConnection.prototype.createOffer; 2384 | window.RTCPeerConnection.prototype.createOffer = function createOffer(offerOptions) { 2385 | if (offerOptions) { 2386 | if (typeof offerOptions.offerToReceiveAudio !== 'undefined') { 2387 | // support bit values 2388 | offerOptions.offerToReceiveAudio = !!offerOptions.offerToReceiveAudio; 2389 | } 2390 | var audioTransceiver = this.getTransceivers().find(function (transceiver) { 2391 | return transceiver.receiver.track.kind === 'audio'; 2392 | }); 2393 | if (offerOptions.offerToReceiveAudio === false && audioTransceiver) { 2394 | if (audioTransceiver.direction === 'sendrecv') { 2395 | if (audioTransceiver.setDirection) { 2396 | audioTransceiver.setDirection('sendonly'); 2397 | } else { 2398 | audioTransceiver.direction = 'sendonly'; 2399 | } 2400 | } else if (audioTransceiver.direction === 'recvonly') { 2401 | if (audioTransceiver.setDirection) { 2402 | audioTransceiver.setDirection('inactive'); 2403 | } else { 2404 | audioTransceiver.direction = 'inactive'; 2405 | } 2406 | } 2407 | } else if (offerOptions.offerToReceiveAudio === true && !audioTransceiver) { 2408 | this.addTransceiver('audio', { direction: 'recvonly' }); 2409 | } 2410 | 2411 | if (typeof offerOptions.offerToReceiveVideo !== 'undefined') { 2412 | // support bit values 2413 | offerOptions.offerToReceiveVideo = !!offerOptions.offerToReceiveVideo; 2414 | } 2415 | var videoTransceiver = this.getTransceivers().find(function (transceiver) { 2416 | return transceiver.receiver.track.kind === 'video'; 2417 | }); 2418 | if (offerOptions.offerToReceiveVideo === false && videoTransceiver) { 2419 | if (videoTransceiver.direction === 'sendrecv') { 2420 | if (videoTransceiver.setDirection) { 2421 | videoTransceiver.setDirection('sendonly'); 2422 | } else { 2423 | videoTransceiver.direction = 'sendonly'; 2424 | } 2425 | } else if (videoTransceiver.direction === 'recvonly') { 2426 | if (videoTransceiver.setDirection) { 2427 | videoTransceiver.setDirection('inactive'); 2428 | } else { 2429 | videoTransceiver.direction = 'inactive'; 2430 | } 2431 | } 2432 | } else if (offerOptions.offerToReceiveVideo === true && !videoTransceiver) { 2433 | this.addTransceiver('video', { direction: 'recvonly' }); 2434 | } 2435 | } 2436 | return origCreateOffer.apply(this, arguments); 2437 | }; 2438 | } 2439 | 2440 | function shimAudioContext(window) { 2441 | if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) !== 'object' || window.AudioContext) { 2442 | return; 2443 | } 2444 | window.AudioContext = window.webkitAudioContext; 2445 | } 2446 | 2447 | },{"../utils":11}],11:[function(require,module,exports){ 2448 | /* 2449 | * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved. 2450 | * 2451 | * Use of this source code is governed by a BSD-style license 2452 | * that can be found in the LICENSE file in the root of the source 2453 | * tree. 2454 | */ 2455 | /* eslint-env node */ 2456 | 'use strict'; 2457 | 2458 | Object.defineProperty(exports, "__esModule", { 2459 | value: true 2460 | }); 2461 | 2462 | 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; }; 2463 | 2464 | exports.extractVersion = extractVersion; 2465 | exports.wrapPeerConnectionEvent = wrapPeerConnectionEvent; 2466 | exports.disableLog = disableLog; 2467 | exports.disableWarnings = disableWarnings; 2468 | exports.log = log; 2469 | exports.deprecated = deprecated; 2470 | exports.detectBrowser = detectBrowser; 2471 | exports.compactObject = compactObject; 2472 | exports.walkStats = walkStats; 2473 | exports.filterStats = filterStats; 2474 | 2475 | 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; } 2476 | 2477 | var logDisabled_ = true; 2478 | var deprecationWarnings_ = true; 2479 | 2480 | /** 2481 | * Extract browser version out of the provided user agent string. 2482 | * 2483 | * @param {!string} uastring userAgent string. 2484 | * @param {!string} expr Regular expression used as match criteria. 2485 | * @param {!number} pos position in the version string to be returned. 2486 | * @return {!number} browser version. 2487 | */ 2488 | function extractVersion(uastring, expr, pos) { 2489 | var match = uastring.match(expr); 2490 | return match && match.length >= pos && parseInt(match[pos], 10); 2491 | } 2492 | 2493 | // Wraps the peerconnection event eventNameToWrap in a function 2494 | // which returns the modified event object (or false to prevent 2495 | // the event). 2496 | function wrapPeerConnectionEvent(window, eventNameToWrap, wrapper) { 2497 | if (!window.RTCPeerConnection) { 2498 | return; 2499 | } 2500 | var proto = window.RTCPeerConnection.prototype; 2501 | var nativeAddEventListener = proto.addEventListener; 2502 | proto.addEventListener = function (nativeEventName, cb) { 2503 | if (nativeEventName !== eventNameToWrap) { 2504 | return nativeAddEventListener.apply(this, arguments); 2505 | } 2506 | var wrappedCallback = function wrappedCallback(e) { 2507 | var modifiedEvent = wrapper(e); 2508 | if (modifiedEvent) { 2509 | if (cb.handleEvent) { 2510 | cb.handleEvent(modifiedEvent); 2511 | } else { 2512 | cb(modifiedEvent); 2513 | } 2514 | } 2515 | }; 2516 | this._eventMap = this._eventMap || {}; 2517 | if (!this._eventMap[eventNameToWrap]) { 2518 | this._eventMap[eventNameToWrap] = new Map(); 2519 | } 2520 | this._eventMap[eventNameToWrap].set(cb, wrappedCallback); 2521 | return nativeAddEventListener.apply(this, [nativeEventName, wrappedCallback]); 2522 | }; 2523 | 2524 | var nativeRemoveEventListener = proto.removeEventListener; 2525 | proto.removeEventListener = function (nativeEventName, cb) { 2526 | if (nativeEventName !== eventNameToWrap || !this._eventMap || !this._eventMap[eventNameToWrap]) { 2527 | return nativeRemoveEventListener.apply(this, arguments); 2528 | } 2529 | if (!this._eventMap[eventNameToWrap].has(cb)) { 2530 | return nativeRemoveEventListener.apply(this, arguments); 2531 | } 2532 | var unwrappedCb = this._eventMap[eventNameToWrap].get(cb); 2533 | this._eventMap[eventNameToWrap].delete(cb); 2534 | if (this._eventMap[eventNameToWrap].size === 0) { 2535 | delete this._eventMap[eventNameToWrap]; 2536 | } 2537 | if (Object.keys(this._eventMap).length === 0) { 2538 | delete this._eventMap; 2539 | } 2540 | return nativeRemoveEventListener.apply(this, [nativeEventName, unwrappedCb]); 2541 | }; 2542 | 2543 | Object.defineProperty(proto, 'on' + eventNameToWrap, { 2544 | get: function get() { 2545 | return this['_on' + eventNameToWrap]; 2546 | }, 2547 | set: function set(cb) { 2548 | if (this['_on' + eventNameToWrap]) { 2549 | this.removeEventListener(eventNameToWrap, this['_on' + eventNameToWrap]); 2550 | delete this['_on' + eventNameToWrap]; 2551 | } 2552 | if (cb) { 2553 | this.addEventListener(eventNameToWrap, this['_on' + eventNameToWrap] = cb); 2554 | } 2555 | }, 2556 | 2557 | enumerable: true, 2558 | configurable: true 2559 | }); 2560 | } 2561 | 2562 | function disableLog(bool) { 2563 | if (typeof bool !== 'boolean') { 2564 | return new Error('Argument type: ' + (typeof bool === 'undefined' ? 'undefined' : _typeof(bool)) + '. Please use a boolean.'); 2565 | } 2566 | logDisabled_ = bool; 2567 | return bool ? 'adapter.js logging disabled' : 'adapter.js logging enabled'; 2568 | } 2569 | 2570 | /** 2571 | * Disable or enable deprecation warnings 2572 | * @param {!boolean} bool set to true to disable warnings. 2573 | */ 2574 | function disableWarnings(bool) { 2575 | if (typeof bool !== 'boolean') { 2576 | return new Error('Argument type: ' + (typeof bool === 'undefined' ? 'undefined' : _typeof(bool)) + '. Please use a boolean.'); 2577 | } 2578 | deprecationWarnings_ = !bool; 2579 | return 'adapter.js deprecation warnings ' + (bool ? 'disabled' : 'enabled'); 2580 | } 2581 | 2582 | function log() { 2583 | if ((typeof window === 'undefined' ? 'undefined' : _typeof(window)) === 'object') { 2584 | if (logDisabled_) { 2585 | return; 2586 | } 2587 | if (typeof console !== 'undefined' && typeof console.log === 'function') { 2588 | console.log.apply(console, arguments); 2589 | } 2590 | } 2591 | } 2592 | 2593 | /** 2594 | * Shows a deprecation warning suggesting the modern and spec-compatible API. 2595 | */ 2596 | function deprecated(oldMethod, newMethod) { 2597 | if (!deprecationWarnings_) { 2598 | return; 2599 | } 2600 | console.warn(oldMethod + ' is deprecated, please use ' + newMethod + ' instead.'); 2601 | } 2602 | 2603 | /** 2604 | * Browser detector. 2605 | * 2606 | * @return {object} result containing browser and version 2607 | * properties. 2608 | */ 2609 | function detectBrowser(window) { 2610 | // Returned result object. 2611 | var result = { browser: null, version: null }; 2612 | 2613 | // Fail early if it's not a browser 2614 | if (typeof window === 'undefined' || !window.navigator) { 2615 | result.browser = 'Not a browser.'; 2616 | return result; 2617 | } 2618 | 2619 | var navigator = window.navigator; 2620 | 2621 | 2622 | if (navigator.mozGetUserMedia) { 2623 | // Firefox. 2624 | result.browser = 'firefox'; 2625 | result.version = extractVersion(navigator.userAgent, /Firefox\/(\d+)\./, 1); 2626 | } else if (navigator.webkitGetUserMedia || window.isSecureContext === false && window.webkitRTCPeerConnection && !window.RTCIceGatherer) { 2627 | // Chrome, Chromium, Webview, Opera. 2628 | // Version matches Chrome/WebRTC version. 2629 | // Chrome 74 removed webkitGetUserMedia on http as well so we need the 2630 | // more complicated fallback to webkitRTCPeerConnection. 2631 | result.browser = 'chrome'; 2632 | result.version = extractVersion(navigator.userAgent, /Chrom(e|ium)\/(\d+)\./, 2); 2633 | } else if (window.RTCPeerConnection && navigator.userAgent.match(/AppleWebKit\/(\d+)\./)) { 2634 | // Safari. 2635 | result.browser = 'safari'; 2636 | result.version = extractVersion(navigator.userAgent, /AppleWebKit\/(\d+)\./, 1); 2637 | result.supportsUnifiedPlan = window.RTCRtpTransceiver && 'currentDirection' in window.RTCRtpTransceiver.prototype; 2638 | } else { 2639 | // Default fallthrough: not supported. 2640 | result.browser = 'Not a supported browser.'; 2641 | return result; 2642 | } 2643 | 2644 | return result; 2645 | } 2646 | 2647 | /** 2648 | * Checks if something is an object. 2649 | * 2650 | * @param {*} val The something you want to check. 2651 | * @return true if val is an object, false otherwise. 2652 | */ 2653 | function isObject(val) { 2654 | return Object.prototype.toString.call(val) === '[object Object]'; 2655 | } 2656 | 2657 | /** 2658 | * Remove all empty objects and undefined values 2659 | * from a nested object -- an enhanced and vanilla version 2660 | * of Lodash's `compact`. 2661 | */ 2662 | function compactObject(data) { 2663 | if (!isObject(data)) { 2664 | return data; 2665 | } 2666 | 2667 | return Object.keys(data).reduce(function (accumulator, key) { 2668 | var isObj = isObject(data[key]); 2669 | var value = isObj ? compactObject(data[key]) : data[key]; 2670 | var isEmptyObject = isObj && !Object.keys(value).length; 2671 | if (value === undefined || isEmptyObject) { 2672 | return accumulator; 2673 | } 2674 | return Object.assign(accumulator, _defineProperty({}, key, value)); 2675 | }, {}); 2676 | } 2677 | 2678 | /* iterates the stats graph recursively. */ 2679 | function walkStats(stats, base, resultSet) { 2680 | if (!base || resultSet.has(base.id)) { 2681 | return; 2682 | } 2683 | resultSet.set(base.id, base); 2684 | Object.keys(base).forEach(function (name) { 2685 | if (name.endsWith('Id')) { 2686 | walkStats(stats, stats.get(base[name]), resultSet); 2687 | } else if (name.endsWith('Ids')) { 2688 | base[name].forEach(function (id) { 2689 | walkStats(stats, stats.get(id), resultSet); 2690 | }); 2691 | } 2692 | }); 2693 | } 2694 | 2695 | /* filter getStats for a sender/receiver track. */ 2696 | function filterStats(result, track, outbound) { 2697 | var streamStatsType = outbound ? 'outbound-rtp' : 'inbound-rtp'; 2698 | var filteredResult = new Map(); 2699 | if (track === null) { 2700 | return filteredResult; 2701 | } 2702 | var trackStats = []; 2703 | result.forEach(function (value) { 2704 | if (value.type === 'track' && value.trackIdentifier === track.id) { 2705 | trackStats.push(value); 2706 | } 2707 | }); 2708 | trackStats.forEach(function (trackStat) { 2709 | result.forEach(function (stats) { 2710 | if (stats.type === streamStatsType && stats.trackId === trackStat.id) { 2711 | walkStats(result, stats, filteredResult); 2712 | } 2713 | }); 2714 | }); 2715 | return filteredResult; 2716 | } 2717 | 2718 | },{}],12:[function(require,module,exports){ 2719 | /* eslint-env node */ 2720 | 'use strict'; 2721 | 2722 | // SDP helpers. 2723 | 2724 | 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; }; 2725 | 2726 | var SDPUtils = {}; 2727 | 2728 | // Generate an alphanumeric identifier for cname or mids. 2729 | // TODO: use UUIDs instead? https://gist.github.com/jed/982883 2730 | SDPUtils.generateIdentifier = function () { 2731 | return Math.random().toString(36).substr(2, 10); 2732 | }; 2733 | 2734 | // The RTCP CNAME used by all peerconnections from the same JS. 2735 | SDPUtils.localCName = SDPUtils.generateIdentifier(); 2736 | 2737 | // Splits SDP into lines, dealing with both CRLF and LF. 2738 | SDPUtils.splitLines = function (blob) { 2739 | return blob.trim().split('\n').map(function (line) { 2740 | return line.trim(); 2741 | }); 2742 | }; 2743 | // Splits SDP into sessionpart and mediasections. Ensures CRLF. 2744 | SDPUtils.splitSections = function (blob) { 2745 | var parts = blob.split('\nm='); 2746 | return parts.map(function (part, index) { 2747 | return (index > 0 ? 'm=' + part : part).trim() + '\r\n'; 2748 | }); 2749 | }; 2750 | 2751 | // Returns the session description. 2752 | SDPUtils.getDescription = function (blob) { 2753 | var sections = SDPUtils.splitSections(blob); 2754 | return sections && sections[0]; 2755 | }; 2756 | 2757 | // Returns the individual media sections. 2758 | SDPUtils.getMediaSections = function (blob) { 2759 | var sections = SDPUtils.splitSections(blob); 2760 | sections.shift(); 2761 | return sections; 2762 | }; 2763 | 2764 | // Returns lines that start with a certain prefix. 2765 | SDPUtils.matchPrefix = function (blob, prefix) { 2766 | return SDPUtils.splitLines(blob).filter(function (line) { 2767 | return line.indexOf(prefix) === 0; 2768 | }); 2769 | }; 2770 | 2771 | // Parses an ICE candidate line. Sample input: 2772 | // candidate:702786350 2 udp 41819902 8.8.8.8 60769 typ relay raddr 8.8.8.8 2773 | // rport 55996" 2774 | // Input can be prefixed with a=. 2775 | SDPUtils.parseCandidate = function (line) { 2776 | var parts = void 0; 2777 | // Parse both variants. 2778 | if (line.indexOf('a=candidate:') === 0) { 2779 | parts = line.substring(12).split(' '); 2780 | } else { 2781 | parts = line.substring(10).split(' '); 2782 | } 2783 | 2784 | var candidate = { 2785 | foundation: parts[0], 2786 | component: { 1: 'rtp', 2: 'rtcp' }[parts[1]] || parts[1], 2787 | protocol: parts[2].toLowerCase(), 2788 | priority: parseInt(parts[3], 10), 2789 | ip: parts[4], 2790 | address: parts[4], // address is an alias for ip. 2791 | port: parseInt(parts[5], 10), 2792 | // skip parts[6] == 'typ' 2793 | type: parts[7] 2794 | }; 2795 | 2796 | for (var i = 8; i < parts.length; i += 2) { 2797 | switch (parts[i]) { 2798 | case 'raddr': 2799 | candidate.relatedAddress = parts[i + 1]; 2800 | break; 2801 | case 'rport': 2802 | candidate.relatedPort = parseInt(parts[i + 1], 10); 2803 | break; 2804 | case 'tcptype': 2805 | candidate.tcpType = parts[i + 1]; 2806 | break; 2807 | case 'ufrag': 2808 | candidate.ufrag = parts[i + 1]; // for backward compatibility. 2809 | candidate.usernameFragment = parts[i + 1]; 2810 | break; 2811 | default: 2812 | // extension handling, in particular ufrag. Don't overwrite. 2813 | if (candidate[parts[i]] === undefined) { 2814 | candidate[parts[i]] = parts[i + 1]; 2815 | } 2816 | break; 2817 | } 2818 | } 2819 | return candidate; 2820 | }; 2821 | 2822 | // Translates a candidate object into SDP candidate attribute. 2823 | // This does not include the a= prefix! 2824 | SDPUtils.writeCandidate = function (candidate) { 2825 | var sdp = []; 2826 | sdp.push(candidate.foundation); 2827 | 2828 | var component = candidate.component; 2829 | if (component === 'rtp') { 2830 | sdp.push(1); 2831 | } else if (component === 'rtcp') { 2832 | sdp.push(2); 2833 | } else { 2834 | sdp.push(component); 2835 | } 2836 | sdp.push(candidate.protocol.toUpperCase()); 2837 | sdp.push(candidate.priority); 2838 | sdp.push(candidate.address || candidate.ip); 2839 | sdp.push(candidate.port); 2840 | 2841 | var type = candidate.type; 2842 | sdp.push('typ'); 2843 | sdp.push(type); 2844 | if (type !== 'host' && candidate.relatedAddress && candidate.relatedPort) { 2845 | sdp.push('raddr'); 2846 | sdp.push(candidate.relatedAddress); 2847 | sdp.push('rport'); 2848 | sdp.push(candidate.relatedPort); 2849 | } 2850 | if (candidate.tcpType && candidate.protocol.toLowerCase() === 'tcp') { 2851 | sdp.push('tcptype'); 2852 | sdp.push(candidate.tcpType); 2853 | } 2854 | if (candidate.usernameFragment || candidate.ufrag) { 2855 | sdp.push('ufrag'); 2856 | sdp.push(candidate.usernameFragment || candidate.ufrag); 2857 | } 2858 | return 'candidate:' + sdp.join(' '); 2859 | }; 2860 | 2861 | // Parses an ice-options line, returns an array of option tags. 2862 | // Sample input: 2863 | // a=ice-options:foo bar 2864 | SDPUtils.parseIceOptions = function (line) { 2865 | return line.substr(14).split(' '); 2866 | }; 2867 | 2868 | // Parses a rtpmap line, returns RTCRtpCoddecParameters. Sample input: 2869 | // a=rtpmap:111 opus/48000/2 2870 | SDPUtils.parseRtpMap = function (line) { 2871 | var parts = line.substr(9).split(' '); 2872 | var parsed = { 2873 | payloadType: parseInt(parts.shift(), 10) // was: id 2874 | }; 2875 | 2876 | parts = parts[0].split('/'); 2877 | 2878 | parsed.name = parts[0]; 2879 | parsed.clockRate = parseInt(parts[1], 10); // was: clockrate 2880 | parsed.channels = parts.length === 3 ? parseInt(parts[2], 10) : 1; 2881 | // legacy alias, got renamed back to channels in ORTC. 2882 | parsed.numChannels = parsed.channels; 2883 | return parsed; 2884 | }; 2885 | 2886 | // Generates a rtpmap line from RTCRtpCodecCapability or 2887 | // RTCRtpCodecParameters. 2888 | SDPUtils.writeRtpMap = function (codec) { 2889 | var pt = codec.payloadType; 2890 | if (codec.preferredPayloadType !== undefined) { 2891 | pt = codec.preferredPayloadType; 2892 | } 2893 | var channels = codec.channels || codec.numChannels || 1; 2894 | return 'a=rtpmap:' + pt + ' ' + codec.name + '/' + codec.clockRate + (channels !== 1 ? '/' + channels : '') + '\r\n'; 2895 | }; 2896 | 2897 | // Parses a extmap line (headerextension from RFC 5285). Sample input: 2898 | // a=extmap:2 urn:ietf:params:rtp-hdrext:toffset 2899 | // a=extmap:2/sendonly urn:ietf:params:rtp-hdrext:toffset 2900 | SDPUtils.parseExtmap = function (line) { 2901 | var parts = line.substr(9).split(' '); 2902 | return { 2903 | id: parseInt(parts[0], 10), 2904 | direction: parts[0].indexOf('/') > 0 ? parts[0].split('/')[1] : 'sendrecv', 2905 | uri: parts[1] 2906 | }; 2907 | }; 2908 | 2909 | // Generates an extmap line from RTCRtpHeaderExtensionParameters or 2910 | // RTCRtpHeaderExtension. 2911 | SDPUtils.writeExtmap = function (headerExtension) { 2912 | return 'a=extmap:' + (headerExtension.id || headerExtension.preferredId) + (headerExtension.direction && headerExtension.direction !== 'sendrecv' ? '/' + headerExtension.direction : '') + ' ' + headerExtension.uri + '\r\n'; 2913 | }; 2914 | 2915 | // Parses a fmtp line, returns dictionary. Sample input: 2916 | // a=fmtp:96 vbr=on;cng=on 2917 | // Also deals with vbr=on; cng=on 2918 | SDPUtils.parseFmtp = function (line) { 2919 | var parsed = {}; 2920 | var kv = void 0; 2921 | var parts = line.substr(line.indexOf(' ') + 1).split(';'); 2922 | for (var j = 0; j < parts.length; j++) { 2923 | kv = parts[j].trim().split('='); 2924 | parsed[kv[0].trim()] = kv[1]; 2925 | } 2926 | return parsed; 2927 | }; 2928 | 2929 | // Generates a fmtp line from RTCRtpCodecCapability or RTCRtpCodecParameters. 2930 | SDPUtils.writeFmtp = function (codec) { 2931 | var line = ''; 2932 | var pt = codec.payloadType; 2933 | if (codec.preferredPayloadType !== undefined) { 2934 | pt = codec.preferredPayloadType; 2935 | } 2936 | if (codec.parameters && Object.keys(codec.parameters).length) { 2937 | var params = []; 2938 | Object.keys(codec.parameters).forEach(function (param) { 2939 | if (codec.parameters[param] !== undefined) { 2940 | params.push(param + '=' + codec.parameters[param]); 2941 | } else { 2942 | params.push(param); 2943 | } 2944 | }); 2945 | line += 'a=fmtp:' + pt + ' ' + params.join(';') + '\r\n'; 2946 | } 2947 | return line; 2948 | }; 2949 | 2950 | // Parses a rtcp-fb line, returns RTCPRtcpFeedback object. Sample input: 2951 | // a=rtcp-fb:98 nack rpsi 2952 | SDPUtils.parseRtcpFb = function (line) { 2953 | var parts = line.substr(line.indexOf(' ') + 1).split(' '); 2954 | return { 2955 | type: parts.shift(), 2956 | parameter: parts.join(' ') 2957 | }; 2958 | }; 2959 | 2960 | // Generate a=rtcp-fb lines from RTCRtpCodecCapability or RTCRtpCodecParameters. 2961 | SDPUtils.writeRtcpFb = function (codec) { 2962 | var lines = ''; 2963 | var pt = codec.payloadType; 2964 | if (codec.preferredPayloadType !== undefined) { 2965 | pt = codec.preferredPayloadType; 2966 | } 2967 | if (codec.rtcpFeedback && codec.rtcpFeedback.length) { 2968 | // FIXME: special handling for trr-int? 2969 | codec.rtcpFeedback.forEach(function (fb) { 2970 | lines += 'a=rtcp-fb:' + pt + ' ' + fb.type + (fb.parameter && fb.parameter.length ? ' ' + fb.parameter : '') + '\r\n'; 2971 | }); 2972 | } 2973 | return lines; 2974 | }; 2975 | 2976 | // Parses a RFC 5576 ssrc media attribute. Sample input: 2977 | // a=ssrc:3735928559 cname:something 2978 | SDPUtils.parseSsrcMedia = function (line) { 2979 | var sp = line.indexOf(' '); 2980 | var parts = { 2981 | ssrc: parseInt(line.substr(7, sp - 7), 10) 2982 | }; 2983 | var colon = line.indexOf(':', sp); 2984 | if (colon > -1) { 2985 | parts.attribute = line.substr(sp + 1, colon - sp - 1); 2986 | parts.value = line.substr(colon + 1); 2987 | } else { 2988 | parts.attribute = line.substr(sp + 1); 2989 | } 2990 | return parts; 2991 | }; 2992 | 2993 | // Parse a ssrc-group line (see RFC 5576). Sample input: 2994 | // a=ssrc-group:semantics 12 34 2995 | SDPUtils.parseSsrcGroup = function (line) { 2996 | var parts = line.substr(13).split(' '); 2997 | return { 2998 | semantics: parts.shift(), 2999 | ssrcs: parts.map(function (ssrc) { 3000 | return parseInt(ssrc, 10); 3001 | }) 3002 | }; 3003 | }; 3004 | 3005 | // Extracts the MID (RFC 5888) from a media section. 3006 | // Returns the MID or undefined if no mid line was found. 3007 | SDPUtils.getMid = function (mediaSection) { 3008 | var mid = SDPUtils.matchPrefix(mediaSection, 'a=mid:')[0]; 3009 | if (mid) { 3010 | return mid.substr(6); 3011 | } 3012 | }; 3013 | 3014 | // Parses a fingerprint line for DTLS-SRTP. 3015 | SDPUtils.parseFingerprint = function (line) { 3016 | var parts = line.substr(14).split(' '); 3017 | return { 3018 | algorithm: parts[0].toLowerCase(), // algorithm is case-sensitive in Edge. 3019 | value: parts[1].toUpperCase() // the definition is upper-case in RFC 4572. 3020 | }; 3021 | }; 3022 | 3023 | // Extracts DTLS parameters from SDP media section or sessionpart. 3024 | // FIXME: for consistency with other functions this should only 3025 | // get the fingerprint line as input. See also getIceParameters. 3026 | SDPUtils.getDtlsParameters = function (mediaSection, sessionpart) { 3027 | var lines = SDPUtils.matchPrefix(mediaSection + sessionpart, 'a=fingerprint:'); 3028 | // Note: a=setup line is ignored since we use the 'auto' role in Edge. 3029 | return { 3030 | role: 'auto', 3031 | fingerprints: lines.map(SDPUtils.parseFingerprint) 3032 | }; 3033 | }; 3034 | 3035 | // Serializes DTLS parameters to SDP. 3036 | SDPUtils.writeDtlsParameters = function (params, setupType) { 3037 | var sdp = 'a=setup:' + setupType + '\r\n'; 3038 | params.fingerprints.forEach(function (fp) { 3039 | sdp += 'a=fingerprint:' + fp.algorithm + ' ' + fp.value + '\r\n'; 3040 | }); 3041 | return sdp; 3042 | }; 3043 | 3044 | // Parses a=crypto lines into 3045 | // https://rawgit.com/aboba/edgertc/master/msortc-rs4.html#dictionary-rtcsrtpsdesparameters-members 3046 | SDPUtils.parseCryptoLine = function (line) { 3047 | var parts = line.substr(9).split(' '); 3048 | return { 3049 | tag: parseInt(parts[0], 10), 3050 | cryptoSuite: parts[1], 3051 | keyParams: parts[2], 3052 | sessionParams: parts.slice(3) 3053 | }; 3054 | }; 3055 | 3056 | SDPUtils.writeCryptoLine = function (parameters) { 3057 | return 'a=crypto:' + parameters.tag + ' ' + parameters.cryptoSuite + ' ' + (_typeof(parameters.keyParams) === 'object' ? SDPUtils.writeCryptoKeyParams(parameters.keyParams) : parameters.keyParams) + (parameters.sessionParams ? ' ' + parameters.sessionParams.join(' ') : '') + '\r\n'; 3058 | }; 3059 | 3060 | // Parses the crypto key parameters into 3061 | // https://rawgit.com/aboba/edgertc/master/msortc-rs4.html#rtcsrtpkeyparam* 3062 | SDPUtils.parseCryptoKeyParams = function (keyParams) { 3063 | if (keyParams.indexOf('inline:') !== 0) { 3064 | return null; 3065 | } 3066 | var parts = keyParams.substr(7).split('|'); 3067 | return { 3068 | keyMethod: 'inline', 3069 | keySalt: parts[0], 3070 | lifeTime: parts[1], 3071 | mkiValue: parts[2] ? parts[2].split(':')[0] : undefined, 3072 | mkiLength: parts[2] ? parts[2].split(':')[1] : undefined 3073 | }; 3074 | }; 3075 | 3076 | SDPUtils.writeCryptoKeyParams = function (keyParams) { 3077 | return keyParams.keyMethod + ':' + keyParams.keySalt + (keyParams.lifeTime ? '|' + keyParams.lifeTime : '') + (keyParams.mkiValue && keyParams.mkiLength ? '|' + keyParams.mkiValue + ':' + keyParams.mkiLength : ''); 3078 | }; 3079 | 3080 | // Extracts all SDES parameters. 3081 | SDPUtils.getCryptoParameters = function (mediaSection, sessionpart) { 3082 | var lines = SDPUtils.matchPrefix(mediaSection + sessionpart, 'a=crypto:'); 3083 | return lines.map(SDPUtils.parseCryptoLine); 3084 | }; 3085 | 3086 | // Parses ICE information from SDP media section or sessionpart. 3087 | // FIXME: for consistency with other functions this should only 3088 | // get the ice-ufrag and ice-pwd lines as input. 3089 | SDPUtils.getIceParameters = function (mediaSection, sessionpart) { 3090 | var ufrag = SDPUtils.matchPrefix(mediaSection + sessionpart, 'a=ice-ufrag:')[0]; 3091 | var pwd = SDPUtils.matchPrefix(mediaSection + sessionpart, 'a=ice-pwd:')[0]; 3092 | if (!(ufrag && pwd)) { 3093 | return null; 3094 | } 3095 | return { 3096 | usernameFragment: ufrag.substr(12), 3097 | password: pwd.substr(10) 3098 | }; 3099 | }; 3100 | 3101 | // Serializes ICE parameters to SDP. 3102 | SDPUtils.writeIceParameters = function (params) { 3103 | var sdp = 'a=ice-ufrag:' + params.usernameFragment + '\r\n' + 'a=ice-pwd:' + params.password + '\r\n'; 3104 | if (params.iceLite) { 3105 | sdp += 'a=ice-lite\r\n'; 3106 | } 3107 | return sdp; 3108 | }; 3109 | 3110 | // Parses the SDP media section and returns RTCRtpParameters. 3111 | SDPUtils.parseRtpParameters = function (mediaSection) { 3112 | var description = { 3113 | codecs: [], 3114 | headerExtensions: [], 3115 | fecMechanisms: [], 3116 | rtcp: [] 3117 | }; 3118 | var lines = SDPUtils.splitLines(mediaSection); 3119 | var mline = lines[0].split(' '); 3120 | for (var i = 3; i < mline.length; i++) { 3121 | // find all codecs from mline[3..] 3122 | var pt = mline[i]; 3123 | var rtpmapline = SDPUtils.matchPrefix(mediaSection, 'a=rtpmap:' + pt + ' ')[0]; 3124 | if (rtpmapline) { 3125 | var codec = SDPUtils.parseRtpMap(rtpmapline); 3126 | var fmtps = SDPUtils.matchPrefix(mediaSection, 'a=fmtp:' + pt + ' '); 3127 | // Only the first a=fmtp: is considered. 3128 | codec.parameters = fmtps.length ? SDPUtils.parseFmtp(fmtps[0]) : {}; 3129 | codec.rtcpFeedback = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-fb:' + pt + ' ').map(SDPUtils.parseRtcpFb); 3130 | description.codecs.push(codec); 3131 | // parse FEC mechanisms from rtpmap lines. 3132 | switch (codec.name.toUpperCase()) { 3133 | case 'RED': 3134 | case 'ULPFEC': 3135 | description.fecMechanisms.push(codec.name.toUpperCase()); 3136 | break; 3137 | default: 3138 | // only RED and ULPFEC are recognized as FEC mechanisms. 3139 | break; 3140 | } 3141 | } 3142 | } 3143 | SDPUtils.matchPrefix(mediaSection, 'a=extmap:').forEach(function (line) { 3144 | description.headerExtensions.push(SDPUtils.parseExtmap(line)); 3145 | }); 3146 | // FIXME: parse rtcp. 3147 | return description; 3148 | }; 3149 | 3150 | // Generates parts of the SDP media section describing the capabilities / 3151 | // parameters. 3152 | SDPUtils.writeRtpDescription = function (kind, caps) { 3153 | var sdp = ''; 3154 | 3155 | // Build the mline. 3156 | sdp += 'm=' + kind + ' '; 3157 | sdp += caps.codecs.length > 0 ? '9' : '0'; // reject if no codecs. 3158 | sdp += ' UDP/TLS/RTP/SAVPF '; 3159 | sdp += caps.codecs.map(function (codec) { 3160 | if (codec.preferredPayloadType !== undefined) { 3161 | return codec.preferredPayloadType; 3162 | } 3163 | return codec.payloadType; 3164 | }).join(' ') + '\r\n'; 3165 | 3166 | sdp += 'c=IN IP4 0.0.0.0\r\n'; 3167 | sdp += 'a=rtcp:9 IN IP4 0.0.0.0\r\n'; 3168 | 3169 | // Add a=rtpmap lines for each codec. Also fmtp and rtcp-fb. 3170 | caps.codecs.forEach(function (codec) { 3171 | sdp += SDPUtils.writeRtpMap(codec); 3172 | sdp += SDPUtils.writeFmtp(codec); 3173 | sdp += SDPUtils.writeRtcpFb(codec); 3174 | }); 3175 | var maxptime = 0; 3176 | caps.codecs.forEach(function (codec) { 3177 | if (codec.maxptime > maxptime) { 3178 | maxptime = codec.maxptime; 3179 | } 3180 | }); 3181 | if (maxptime > 0) { 3182 | sdp += 'a=maxptime:' + maxptime + '\r\n'; 3183 | } 3184 | 3185 | if (caps.headerExtensions) { 3186 | caps.headerExtensions.forEach(function (extension) { 3187 | sdp += SDPUtils.writeExtmap(extension); 3188 | }); 3189 | } 3190 | // FIXME: write fecMechanisms. 3191 | return sdp; 3192 | }; 3193 | 3194 | // Parses the SDP media section and returns an array of 3195 | // RTCRtpEncodingParameters. 3196 | SDPUtils.parseRtpEncodingParameters = function (mediaSection) { 3197 | var encodingParameters = []; 3198 | var description = SDPUtils.parseRtpParameters(mediaSection); 3199 | var hasRed = description.fecMechanisms.indexOf('RED') !== -1; 3200 | var hasUlpfec = description.fecMechanisms.indexOf('ULPFEC') !== -1; 3201 | 3202 | // filter a=ssrc:... cname:, ignore PlanB-msid 3203 | var ssrcs = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:').map(function (line) { 3204 | return SDPUtils.parseSsrcMedia(line); 3205 | }).filter(function (parts) { 3206 | return parts.attribute === 'cname'; 3207 | }); 3208 | var primarySsrc = ssrcs.length > 0 && ssrcs[0].ssrc; 3209 | var secondarySsrc = void 0; 3210 | 3211 | var flows = SDPUtils.matchPrefix(mediaSection, 'a=ssrc-group:FID').map(function (line) { 3212 | var parts = line.substr(17).split(' '); 3213 | return parts.map(function (part) { 3214 | return parseInt(part, 10); 3215 | }); 3216 | }); 3217 | if (flows.length > 0 && flows[0].length > 1 && flows[0][0] === primarySsrc) { 3218 | secondarySsrc = flows[0][1]; 3219 | } 3220 | 3221 | description.codecs.forEach(function (codec) { 3222 | if (codec.name.toUpperCase() === 'RTX' && codec.parameters.apt) { 3223 | var encParam = { 3224 | ssrc: primarySsrc, 3225 | codecPayloadType: parseInt(codec.parameters.apt, 10) 3226 | }; 3227 | if (primarySsrc && secondarySsrc) { 3228 | encParam.rtx = { ssrc: secondarySsrc }; 3229 | } 3230 | encodingParameters.push(encParam); 3231 | if (hasRed) { 3232 | encParam = JSON.parse(JSON.stringify(encParam)); 3233 | encParam.fec = { 3234 | ssrc: primarySsrc, 3235 | mechanism: hasUlpfec ? 'red+ulpfec' : 'red' 3236 | }; 3237 | encodingParameters.push(encParam); 3238 | } 3239 | } 3240 | }); 3241 | if (encodingParameters.length === 0 && primarySsrc) { 3242 | encodingParameters.push({ 3243 | ssrc: primarySsrc 3244 | }); 3245 | } 3246 | 3247 | // we support both b=AS and b=TIAS but interpret AS as TIAS. 3248 | var bandwidth = SDPUtils.matchPrefix(mediaSection, 'b='); 3249 | if (bandwidth.length) { 3250 | if (bandwidth[0].indexOf('b=TIAS:') === 0) { 3251 | bandwidth = parseInt(bandwidth[0].substr(7), 10); 3252 | } else if (bandwidth[0].indexOf('b=AS:') === 0) { 3253 | // use formula from JSEP to convert b=AS to TIAS value. 3254 | bandwidth = parseInt(bandwidth[0].substr(5), 10) * 1000 * 0.95 - 50 * 40 * 8; 3255 | } else { 3256 | bandwidth = undefined; 3257 | } 3258 | encodingParameters.forEach(function (params) { 3259 | params.maxBitrate = bandwidth; 3260 | }); 3261 | } 3262 | return encodingParameters; 3263 | }; 3264 | 3265 | // parses http://draft.ortc.org/#rtcrtcpparameters* 3266 | SDPUtils.parseRtcpParameters = function (mediaSection) { 3267 | var rtcpParameters = {}; 3268 | 3269 | // Gets the first SSRC. Note that with RTX there might be multiple 3270 | // SSRCs. 3271 | var remoteSsrc = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:').map(function (line) { 3272 | return SDPUtils.parseSsrcMedia(line); 3273 | }).filter(function (obj) { 3274 | return obj.attribute === 'cname'; 3275 | })[0]; 3276 | if (remoteSsrc) { 3277 | rtcpParameters.cname = remoteSsrc.value; 3278 | rtcpParameters.ssrc = remoteSsrc.ssrc; 3279 | } 3280 | 3281 | // Edge uses the compound attribute instead of reducedSize 3282 | // compound is !reducedSize 3283 | var rsize = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-rsize'); 3284 | rtcpParameters.reducedSize = rsize.length > 0; 3285 | rtcpParameters.compound = rsize.length === 0; 3286 | 3287 | // parses the rtcp-mux attrіbute. 3288 | // Note that Edge does not support unmuxed RTCP. 3289 | var mux = SDPUtils.matchPrefix(mediaSection, 'a=rtcp-mux'); 3290 | rtcpParameters.mux = mux.length > 0; 3291 | 3292 | return rtcpParameters; 3293 | }; 3294 | 3295 | SDPUtils.writeRtcpParameters = function (rtcpParameters) { 3296 | var sdp = ''; 3297 | if (rtcpParameters.reducedSize) { 3298 | sdp += 'a=rtcp-rsize\r\n'; 3299 | } 3300 | if (rtcpParameters.mux) { 3301 | sdp += 'a=rtcp-mux\r\n'; 3302 | } 3303 | if (rtcpParameters.ssrc !== undefined && rtcpParameters.cname) { 3304 | sdp += 'a=ssrc:' + rtcpParameters.ssrc + ' cname:' + rtcpParameters.cname + '\r\n'; 3305 | } 3306 | return sdp; 3307 | }; 3308 | 3309 | // parses either a=msid: or a=ssrc:... msid lines and returns 3310 | // the id of the MediaStream and MediaStreamTrack. 3311 | SDPUtils.parseMsid = function (mediaSection) { 3312 | var parts = void 0; 3313 | var spec = SDPUtils.matchPrefix(mediaSection, 'a=msid:'); 3314 | if (spec.length === 1) { 3315 | parts = spec[0].substr(7).split(' '); 3316 | return { stream: parts[0], track: parts[1] }; 3317 | } 3318 | var planB = SDPUtils.matchPrefix(mediaSection, 'a=ssrc:').map(function (line) { 3319 | return SDPUtils.parseSsrcMedia(line); 3320 | }).filter(function (msidParts) { 3321 | return msidParts.attribute === 'msid'; 3322 | }); 3323 | if (planB.length > 0) { 3324 | parts = planB[0].value.split(' '); 3325 | return { stream: parts[0], track: parts[1] }; 3326 | } 3327 | }; 3328 | 3329 | // SCTP 3330 | // parses draft-ietf-mmusic-sctp-sdp-26 first and falls back 3331 | // to draft-ietf-mmusic-sctp-sdp-05 3332 | SDPUtils.parseSctpDescription = function (mediaSection) { 3333 | var mline = SDPUtils.parseMLine(mediaSection); 3334 | var maxSizeLine = SDPUtils.matchPrefix(mediaSection, 'a=max-message-size:'); 3335 | var maxMessageSize = void 0; 3336 | if (maxSizeLine.length > 0) { 3337 | maxMessageSize = parseInt(maxSizeLine[0].substr(19), 10); 3338 | } 3339 | if (isNaN(maxMessageSize)) { 3340 | maxMessageSize = 65536; 3341 | } 3342 | var sctpPort = SDPUtils.matchPrefix(mediaSection, 'a=sctp-port:'); 3343 | if (sctpPort.length > 0) { 3344 | return { 3345 | port: parseInt(sctpPort[0].substr(12), 10), 3346 | protocol: mline.fmt, 3347 | maxMessageSize: maxMessageSize 3348 | }; 3349 | } 3350 | var sctpMapLines = SDPUtils.matchPrefix(mediaSection, 'a=sctpmap:'); 3351 | if (sctpMapLines.length > 0) { 3352 | var parts = sctpMapLines[0].substr(10).split(' '); 3353 | return { 3354 | port: parseInt(parts[0], 10), 3355 | protocol: parts[1], 3356 | maxMessageSize: maxMessageSize 3357 | }; 3358 | } 3359 | }; 3360 | 3361 | // SCTP 3362 | // outputs the draft-ietf-mmusic-sctp-sdp-26 version that all browsers 3363 | // support by now receiving in this format, unless we originally parsed 3364 | // as the draft-ietf-mmusic-sctp-sdp-05 format (indicated by the m-line 3365 | // protocol of DTLS/SCTP -- without UDP/ or TCP/) 3366 | SDPUtils.writeSctpDescription = function (media, sctp) { 3367 | var output = []; 3368 | if (media.protocol !== 'DTLS/SCTP') { 3369 | 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']; 3370 | } else { 3371 | 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']; 3372 | } 3373 | if (sctp.maxMessageSize !== undefined) { 3374 | output.push('a=max-message-size:' + sctp.maxMessageSize + '\r\n'); 3375 | } 3376 | return output.join(''); 3377 | }; 3378 | 3379 | // Generate a session ID for SDP. 3380 | // https://tools.ietf.org/html/draft-ietf-rtcweb-jsep-20#section-5.2.1 3381 | // recommends using a cryptographically random +ve 64-bit value 3382 | // but right now this should be acceptable and within the right range 3383 | SDPUtils.generateSessionId = function () { 3384 | return Math.random().toString().substr(2, 21); 3385 | }; 3386 | 3387 | // Write boiler plate for start of SDP 3388 | // sessId argument is optional - if not supplied it will 3389 | // be generated randomly 3390 | // sessVersion is optional and defaults to 2 3391 | // sessUser is optional and defaults to 'thisisadapterortc' 3392 | SDPUtils.writeSessionBoilerplate = function (sessId, sessVer, sessUser) { 3393 | var sessionId = void 0; 3394 | var version = sessVer !== undefined ? sessVer : 2; 3395 | if (sessId) { 3396 | sessionId = sessId; 3397 | } else { 3398 | sessionId = SDPUtils.generateSessionId(); 3399 | } 3400 | var user = sessUser || 'thisisadapterortc'; 3401 | // FIXME: sess-id should be an NTP timestamp. 3402 | 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'; 3403 | }; 3404 | 3405 | // Gets the direction from the mediaSection or the sessionpart. 3406 | SDPUtils.getDirection = function (mediaSection, sessionpart) { 3407 | // Look for sendrecv, sendonly, recvonly, inactive, default to sendrecv. 3408 | var lines = SDPUtils.splitLines(mediaSection); 3409 | for (var i = 0; i < lines.length; i++) { 3410 | switch (lines[i]) { 3411 | case 'a=sendrecv': 3412 | case 'a=sendonly': 3413 | case 'a=recvonly': 3414 | case 'a=inactive': 3415 | return lines[i].substr(2); 3416 | default: 3417 | // FIXME: What should happen here? 3418 | } 3419 | } 3420 | if (sessionpart) { 3421 | return SDPUtils.getDirection(sessionpart); 3422 | } 3423 | return 'sendrecv'; 3424 | }; 3425 | 3426 | SDPUtils.getKind = function (mediaSection) { 3427 | var lines = SDPUtils.splitLines(mediaSection); 3428 | var mline = lines[0].split(' '); 3429 | return mline[0].substr(2); 3430 | }; 3431 | 3432 | SDPUtils.isRejected = function (mediaSection) { 3433 | return mediaSection.split(' ', 2)[1] === '0'; 3434 | }; 3435 | 3436 | SDPUtils.parseMLine = function (mediaSection) { 3437 | var lines = SDPUtils.splitLines(mediaSection); 3438 | var parts = lines[0].substr(2).split(' '); 3439 | return { 3440 | kind: parts[0], 3441 | port: parseInt(parts[1], 10), 3442 | protocol: parts[2], 3443 | fmt: parts.slice(3).join(' ') 3444 | }; 3445 | }; 3446 | 3447 | SDPUtils.parseOLine = function (mediaSection) { 3448 | var line = SDPUtils.matchPrefix(mediaSection, 'o=')[0]; 3449 | var parts = line.substr(2).split(' '); 3450 | return { 3451 | username: parts[0], 3452 | sessionId: parts[1], 3453 | sessionVersion: parseInt(parts[2], 10), 3454 | netType: parts[3], 3455 | addressType: parts[4], 3456 | address: parts[5] 3457 | }; 3458 | }; 3459 | 3460 | // a very naive interpretation of a valid SDP. 3461 | SDPUtils.isValidSDP = function (blob) { 3462 | if (typeof blob !== 'string' || blob.length === 0) { 3463 | return false; 3464 | } 3465 | var lines = SDPUtils.splitLines(blob); 3466 | for (var i = 0; i < lines.length; i++) { 3467 | if (lines[i].length < 2 || lines[i].charAt(1) !== '=') { 3468 | return false; 3469 | } 3470 | // TODO: check the modifier a bit more. 3471 | } 3472 | return true; 3473 | }; 3474 | 3475 | // Expose public methods. 3476 | if ((typeof module === 'undefined' ? 'undefined' : _typeof(module)) === 'object') { 3477 | module.exports = SDPUtils; 3478 | } 3479 | },{}]},{},[1])(1) 3480 | }); -------------------------------------------------------------------------------- /js/main.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | /* globals MediaRecorder */ 4 | // Spec is at http://dvcs.w3.org/hg/dap/raw-file/tip/media-stream-capture/RecordingProposal.html 5 | 6 | var constraints = {audio:true,video:{width:{min:640,ideal:1280,max:1280 },height:{ min:480,ideal:720,max:720},framerate:60}}; 7 | 8 | var recBtn = document.querySelector('button#rec'); 9 | var pauseResBtn = document.querySelector('button#pauseRes'); 10 | var stopBtn = document.querySelector('button#stop'); 11 | 12 | var liveVideoElement = document.querySelector('#live'); 13 | var playbackVideoElement = document.querySelector('#playback'); 14 | var dataElement = document.querySelector('#data'); 15 | var downloadLink = document.querySelector('a#downloadLink'); 16 | 17 | liveVideoElement.controls = false; 18 | playbackVideoElement.controls=false; 19 | 20 | var mediaRecorder; 21 | var chunks = []; 22 | var count = 0; 23 | var localStream = null; 24 | var soundMeter = null; 25 | var containerType = "video/webm"; //defaults to webm but we switch to mp4 on Safari 14.0.2+ 26 | 27 | if (!navigator.mediaDevices.getUserMedia){ 28 | alert('navigator.mediaDevices.getUserMedia not supported on your browser, use the latest version of Safari, Edge, Firefox or Chrome'); 29 | }else{ 30 | if (window.MediaRecorder == undefined) { 31 | alert('MediaRecorder not supported on your browser, use the latest version of Safari, Edge, Firefox or Chrome'); 32 | }else{ 33 | navigator.mediaDevices.getUserMedia(constraints) 34 | .then(function(stream) { 35 | localStream = stream; 36 | 37 | localStream.getTracks().forEach(function(track) { 38 | if(track.kind == "audio"){ 39 | track.onended = () => log("audio track.onended track.readyState="+track.readyState+", track.muted=" + track.muted); 40 | track.onmute = () => log("audio track.onmute track.readyState="+track.readyState+", track.muted=" + track.muted); 41 | track.onunmute = () => log("audio track.onunmute track.readyState="+track.readyState+", track.muted=" + track.muted); 42 | } 43 | if(track.kind == "video"){ 44 | track.onended = () => log("video track.onended track.readyState="+track.readyState+", track.muted=" + track.muted); 45 | track.onmute = () => log("video track.onmute track.readyState="+track.readyState+", track.muted=" + track.muted); 46 | track.onunmute = () => log("video track.onunmute track.readyState="+track.readyState+", track.muted=" + track.muted); 47 | } 48 | }); 49 | 50 | liveVideoElement.srcObject = localStream; 51 | liveVideoElement.play(); 52 | 53 | try { 54 | window.AudioContext = window.AudioContext || window.webkitAudioContext; 55 | window.audioContext = new AudioContext(); 56 | } catch (e) { 57 | log('Web Audio API not supported.'); 58 | } 59 | 60 | soundMeter = window.soundMeter = new SoundMeter(window.audioContext); 61 | soundMeter.connectToSource(localStream, function(e) { 62 | if (e) { 63 | log(e); 64 | return; 65 | }else{ 66 | /*setInterval(function() { 67 | log(Math.round(soundMeter.instant.toFixed(2) * 100)); 68 | }, 100);*/ 69 | } 70 | }); 71 | 72 | }).catch(function(err) { 73 | /* handle the error */ 74 | log('navigator.getUserMedia error: '+err); 75 | }); 76 | } 77 | } 78 | 79 | function onBtnRecordClicked (){ 80 | if (localStream == null) { 81 | alert('Could not get local stream from mic/camera'); 82 | }else { 83 | recBtn.disabled = true; 84 | pauseResBtn.disabled = false; 85 | stopBtn.disabled = false; 86 | 87 | chunks = []; 88 | 89 | /* use the stream */ 90 | log('Start recording...'); 91 | if (typeof MediaRecorder.isTypeSupported == 'function'){ 92 | /* 93 | MediaRecorder.isTypeSupported is a function announced in https://developers.google.com/web/updates/2016/01/mediarecorder and later introduced in the MediaRecorder API spec http://www.w3.org/TR/mediastream-recording/ 94 | */ 95 | if (MediaRecorder.isTypeSupported('video/webm;codecs=vp9')) { 96 | var options = {mimeType: 'video/webm;codecs=vp9'}; 97 | } else if (MediaRecorder.isTypeSupported('video/webm;codecs=h264')) { 98 | var options = {mimeType: 'video/webm;codecs=h264'}; 99 | } else if (MediaRecorder.isTypeSupported('video/webm')) { 100 | var options = {mimeType: 'video/webm'}; 101 | } else if (MediaRecorder.isTypeSupported('video/mp4')) { 102 | //Safari 14.0.2 has an EXPERIMENTAL version of MediaRecorder enabled by default 103 | containerType = "video/mp4"; 104 | var options = {mimeType: 'video/mp4'}; 105 | } 106 | log('Using '+options.mimeType); 107 | mediaRecorder = new MediaRecorder(localStream, options); 108 | }else{ 109 | log('isTypeSupported is not supported, using default codecs for browser'); 110 | mediaRecorder = new MediaRecorder(localStream); 111 | } 112 | 113 | mediaRecorder.ondataavailable = function(e) { 114 | log('mediaRecorder.ondataavailable, e.data.size='+e.data.size+', e.timecode='+e.timecode); 115 | if (e.data && e.data.size > 0) { 116 | chunks.push(e.data); 117 | } 118 | }; 119 | 120 | mediaRecorder.onerror = function(e){ 121 | log('mediaRecorder.onerror: ' + e); 122 | }; 123 | 124 | mediaRecorder.onstart = function(){ 125 | log('mediaRecorder.onstart, mediaRecorder.state = ' + mediaRecorder.state); 126 | 127 | localStream.getTracks().forEach(function(track) { 128 | if(track.kind == "audio"){ 129 | log("onstart - Audio track.readyState="+track.readyState+", track.muted=" + track.muted); 130 | } 131 | if(track.kind == "video"){ 132 | log("onstart - Video track.readyState="+track.readyState+", track.muted=" + track.muted); 133 | } 134 | }); 135 | 136 | }; 137 | 138 | mediaRecorder.onstop = function(){ 139 | log('mediaRecorder.onstop, mediaRecorder.state = ' + mediaRecorder.state); 140 | 141 | //var recording = new Blob(chunks, {type: containerType}); 142 | var recording = new Blob(chunks, {type: mediaRecorder.mimeType}); 143 | 144 | 145 | downloadLink.href = URL.createObjectURL(recording); 146 | 147 | /* 148 | srcObject code from https://developer.mozilla.org/en-US/docs/Web/API/HTMLMediaElement/srcObject 149 | */ 150 | 151 | /*if ('srcObject' in playbackVideoElement) { 152 | try { 153 | playbackVideoElement.srcObject = recording; 154 | } catch (err) { 155 | if (err.name != "TypeError") { 156 | throw err; 157 | }*/ 158 | // Even if they do, they may only support MediaStream 159 | playbackVideoElement.src = URL.createObjectURL(recording); 160 | /* } 161 | } else { 162 | playbackVideoElement.src = URL.createObjectURL(recording); 163 | } */ 164 | 165 | playbackVideoElement.controls = true; 166 | playbackVideoElement.play(); 167 | 168 | var rand = Math.floor((Math.random() * 10000000)); 169 | switch(containerType){ 170 | case "video/mp4": 171 | var name = "video_"+rand+".mp4" ; 172 | break; 173 | default: 174 | var name = "video_"+rand+".webm" ; 175 | } 176 | 177 | downloadLink.innerHTML = 'Download '+name; 178 | 179 | downloadLink.setAttribute( "download", name); 180 | downloadLink.setAttribute( "name", name); 181 | }; 182 | 183 | mediaRecorder.onpause = function(){ 184 | log('mediaRecorder.onpause, mediaRecorder.state = ' + mediaRecorder.state); 185 | } 186 | 187 | mediaRecorder.onresume = function(){ 188 | log('mediaRecorder.onresume, mediaRecorder.state = ' + mediaRecorder.state); 189 | } 190 | 191 | mediaRecorder.onwarning = function(e){ 192 | log('mediaRecorder.onwarning: ' + e); 193 | }; 194 | 195 | pauseResBtn.textContent = "Pause"; 196 | 197 | mediaRecorder.start(1000); 198 | 199 | localStream.getTracks().forEach(function(track) { 200 | log(track.kind+":"+JSON.stringify(track.getSettings())); 201 | console.log(track.getSettings()); 202 | }) 203 | } 204 | } 205 | 206 | navigator.mediaDevices.ondevicechange = function(event) { 207 | log("mediaDevices.ondevicechange"); 208 | /* 209 | if (localStream != null){ 210 | localStream.getTracks().forEach(function(track) { 211 | if(track.kind == "audio"){ 212 | track.onended = function(event){ 213 | log("audio track.onended"); 214 | } 215 | } 216 | }); 217 | } 218 | */ 219 | } 220 | 221 | function onBtnStopClicked(){ 222 | mediaRecorder.stop(); 223 | recBtn.disabled = false; 224 | pauseResBtn.disabled = true; 225 | stopBtn.disabled = true; 226 | } 227 | 228 | function onPauseResumeClicked(){ 229 | if(pauseResBtn.textContent === "Pause"){ 230 | pauseResBtn.textContent = "Resume"; 231 | mediaRecorder.pause(); 232 | stopBtn.disabled = true; 233 | }else{ 234 | pauseResBtn.textContent = "Pause"; 235 | mediaRecorder.resume(); 236 | stopBtn.disabled = false; 237 | } 238 | recBtn.disabled = true; 239 | pauseResBtn.disabled = false; 240 | } 241 | 242 | function onStateClicked(){ 243 | 244 | if(mediaRecorder != null && localStream != null && soundMeter != null){ 245 | log("mediaRecorder.state="+mediaRecorder.state); 246 | log("mediaRecorder.mimeType="+mediaRecorder.mimeType); 247 | log("mediaRecorder.videoBitsPerSecond="+mediaRecorder.videoBitsPerSecond); 248 | log("mediaRecorder.audioBitsPerSecond="+mediaRecorder.audioBitsPerSecond); 249 | 250 | localStream.getTracks().forEach(function(track) { 251 | if(track.kind == "audio"){ 252 | log("Audio: track.readyState="+track.readyState+", track.muted=" + track.muted); 253 | } 254 | if(track.kind == "video"){ 255 | log("Video: track.readyState="+track.readyState+", track.muted=" + track.muted); 256 | } 257 | }); 258 | 259 | log("Audio activity: " + Math.round(soundMeter.instant.toFixed(2) * 100)); 260 | } 261 | 262 | } 263 | 264 | function log(message){ 265 | dataElement.innerHTML = dataElement.innerHTML+ '
' + new Date().toISOString() + " " + message; 266 | console.log(message) 267 | } 268 | 269 | // Define the AudioWorkletProcessor as a string to be dynamically loaded 270 | const volumeProcessorCode = ` 271 | class VolumeProcessor extends AudioWorkletProcessor { 272 | constructor() { 273 | super(); 274 | this.instant = 0.0; 275 | this.slow = 0.0; 276 | this.clip = 0.0; 277 | } 278 | 279 | process(inputs, outputs, parameters) { 280 | const input = inputs[0]; 281 | if (input.length > 0) { 282 | const channelData = input[0]; 283 | let sum = 0.0; 284 | let clipcount = 0; 285 | 286 | for (let i = 0; i < channelData.length; ++i) { 287 | sum += channelData[i] * channelData[i]; 288 | if (Math.abs(channelData[i]) > 0.99) { 289 | clipcount += 1; 290 | } 291 | } 292 | 293 | this.instant = Math.sqrt(sum / channelData.length); 294 | this.slow = 0.95 * this.slow + 0.05 * this.instant; 295 | this.clip = clipcount / channelData.length; 296 | 297 | this.port.postMessage({ 298 | instant: this.instant, 299 | slow: this.slow, 300 | clip: this.clip 301 | }); 302 | } 303 | 304 | return true; 305 | } 306 | } 307 | 308 | registerProcessor('volume-processor', VolumeProcessor); 309 | `; 310 | 311 | // Meter class that generates a number correlated to audio volume. 312 | // The meter class itself displays nothing, but it makes the 313 | // instantaneous and time-decaying volumes available for inspection. 314 | // It also reports on the fraction of samples that were at or near 315 | // the top of the measurement range. 316 | class SoundMeter { 317 | constructor(context) { 318 | this.context = context; 319 | this.instant = 0.0; 320 | this.slow = 0.0; 321 | this.clip = 0.0; 322 | 323 | // Create a Blob URL for the AudioWorkletProcessor script 324 | // This approach is taken to avoid the need for a separate file for the processor 325 | const blob = new Blob([volumeProcessorCode], { type: 'application/javascript' }); 326 | const url = URL.createObjectURL(blob); 327 | 328 | // Load the processor module into the AudioWorklet 329 | this.ready = context.audioWorklet.addModule(url).then(() => { 330 | // Create an AudioWorkletNode using the registered processor 331 | this.node = new AudioWorkletNode(context, 'volume-processor'); 332 | // Set up a message event listener to receive volume metrics from the processor 333 | this.node.port.onmessage = (event) => { 334 | this.instant = event.data.instant; 335 | this.slow = event.data.slow; 336 | this.clip = event.data.clip; 337 | }; 338 | }); 339 | } 340 | 341 | async connectToSource(stream, callback) { 342 | console.log("pipe-log at " + new Date().toISOString() + " SoundMeter connecting"); 343 | try { 344 | // Wait for the processor module to be ready 345 | await this.ready; 346 | // Create a MediaStreamSource from the provided audio stream 347 | this.mic = this.context.createMediaStreamSource(stream); 348 | // Connect the media source to the AudioWorkletNode 349 | this.mic.connect(this.node); 350 | // Connect the node to the destination to ensure it works (even though we don't need the output) 351 | this.node.connect(this.context.destination); 352 | // If a callback is provided, call it with no error 353 | if (typeof callback !== 'undefined') { 354 | callback(null); 355 | } 356 | } catch (e) { 357 | // Log any errors that occur during the connection process 358 | console.log("pipe-log at " + timeStamp() + " error occurred in SoundMeter:", e); 359 | // If a callback is provided, call it with the error 360 | if (typeof callback !== 'undefined') { 361 | callback(e); 362 | } 363 | } 364 | } 365 | 366 | stop() { 367 | // Disconnect the media source and the AudioWorkletNode to stop processing 368 | this.mic.disconnect(); 369 | this.node.disconnect(); 370 | } 371 | } 372 | 373 | 374 | //browser ID 375 | function getBrowser(){ 376 | var nVer = navigator.appVersion; 377 | var nAgt = navigator.userAgent; 378 | var browserName = navigator.appName; 379 | var fullVersion = ''+parseFloat(navigator.appVersion); 380 | var majorVersion = parseInt(navigator.appVersion,10); 381 | var nameOffset,verOffset,ix; 382 | 383 | // In Opera, the true version is after "Opera" or after "Version" 384 | if ((verOffset=nAgt.indexOf("Opera"))!=-1) { 385 | browserName = "Opera"; 386 | fullVersion = nAgt.substring(verOffset+6); 387 | if ((verOffset=nAgt.indexOf("Version"))!=-1) 388 | fullVersion = nAgt.substring(verOffset+8); 389 | } 390 | // In MSIE, the true version is after "MSIE" in userAgent 391 | else if ((verOffset=nAgt.indexOf("MSIE"))!=-1) { 392 | browserName = "Microsoft Internet Explorer"; 393 | fullVersion = nAgt.substring(verOffset+5); 394 | } 395 | // In Chrome, the true version is after "Chrome" 396 | else if ((verOffset=nAgt.indexOf("Chrome"))!=-1) { 397 | browserName = "Chrome"; 398 | fullVersion = nAgt.substring(verOffset+7); 399 | } 400 | // In Safari, the true version is after "Safari" or after "Version" 401 | else if ((verOffset=nAgt.indexOf("Safari"))!=-1) { 402 | browserName = "Safari"; 403 | fullVersion = nAgt.substring(verOffset+7); 404 | if ((verOffset=nAgt.indexOf("Version"))!=-1) 405 | fullVersion = nAgt.substring(verOffset+8); 406 | } 407 | // In Firefox, the true version is after "Firefox" 408 | else if ((verOffset=nAgt.indexOf("Firefox"))!=-1) { 409 | browserName = "Firefox"; 410 | fullVersion = nAgt.substring(verOffset+8); 411 | } 412 | // In most other browsers, "name/version" is at the end of userAgent 413 | else if ( (nameOffset=nAgt.lastIndexOf(' ')+1) < 414 | (verOffset=nAgt.lastIndexOf('/')) ) 415 | { 416 | browserName = nAgt.substring(nameOffset,verOffset); 417 | fullVersion = nAgt.substring(verOffset+1); 418 | if (browserName.toLowerCase()==browserName.toUpperCase()) { 419 | browserName = navigator.appName; 420 | } 421 | } 422 | // trim the fullVersion string at semicolon/space if present 423 | if ((ix=fullVersion.indexOf(";"))!=-1) 424 | fullVersion=fullVersion.substring(0,ix); 425 | if ((ix=fullVersion.indexOf(" "))!=-1) 426 | fullVersion=fullVersion.substring(0,ix); 427 | 428 | majorVersion = parseInt(''+fullVersion,10); 429 | if (isNaN(majorVersion)) { 430 | fullVersion = ''+parseFloat(navigator.appVersion); 431 | majorVersion = parseInt(navigator.appVersion,10); 432 | } 433 | 434 | 435 | return browserName; 436 | } 437 | --------------------------------------------------------------------------------