utils.js 9.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260
  1. /*
  2. * Copyright (c) 2016 The WebRTC project authors. All Rights Reserved.
  3. *
  4. * Use of this source code is governed by a BSD-style license
  5. * that can be found in the LICENSE file in the root of the source
  6. * tree.
  7. */
  8. /* eslint-env node */
  9. 'use strict';
  10. Object.defineProperty(exports, "__esModule", {
  11. value: true
  12. });
  13. exports.compactObject = compactObject;
  14. exports.deprecated = deprecated;
  15. exports.detectBrowser = detectBrowser;
  16. exports.disableLog = disableLog;
  17. exports.disableWarnings = disableWarnings;
  18. exports.extractVersion = extractVersion;
  19. exports.filterStats = filterStats;
  20. exports.log = log;
  21. exports.walkStats = walkStats;
  22. exports.wrapPeerConnectionEvent = wrapPeerConnectionEvent;
  23. function _defineProperty(obj, key, value) { key = _toPropertyKey(key); if (key in obj) { Object.defineProperty(obj, key, { value: value, enumerable: true, configurable: true, writable: true }); } else { obj[key] = value; } return obj; }
  24. function _toPropertyKey(arg) { var key = _toPrimitive(arg, "string"); return _typeof(key) === "symbol" ? key : String(key); }
  25. function _toPrimitive(input, hint) { if (_typeof(input) !== "object" || input === null) return input; var prim = input[Symbol.toPrimitive]; if (prim !== undefined) { var res = prim.call(input, hint || "default"); if (_typeof(res) !== "object") return res; throw new TypeError("@@toPrimitive must return a primitive value."); } return (hint === "string" ? String : Number)(input); }
  26. function _typeof(obj) { "@babel/helpers - typeof"; return _typeof = "function" == typeof Symbol && "symbol" == typeof Symbol.iterator ? function (obj) { return typeof obj; } : function (obj) { return obj && "function" == typeof Symbol && obj.constructor === Symbol && obj !== Symbol.prototype ? "symbol" : typeof obj; }, _typeof(obj); }
  27. var logDisabled_ = true;
  28. var deprecationWarnings_ = true;
  29. /**
  30. * Extract browser version out of the provided user agent string.
  31. *
  32. * @param {!string} uastring userAgent string.
  33. * @param {!string} expr Regular expression used as match criteria.
  34. * @param {!number} pos position in the version string to be returned.
  35. * @return {!number} browser version.
  36. */
  37. function extractVersion(uastring, expr, pos) {
  38. var match = uastring.match(expr);
  39. return match && match.length >= pos && parseInt(match[pos], 10);
  40. }
  41. // Wraps the peerconnection event eventNameToWrap in a function
  42. // which returns the modified event object (or false to prevent
  43. // the event).
  44. function wrapPeerConnectionEvent(window, eventNameToWrap, wrapper) {
  45. if (!window.RTCPeerConnection) {
  46. return;
  47. }
  48. var proto = window.RTCPeerConnection.prototype;
  49. var nativeAddEventListener = proto.addEventListener;
  50. proto.addEventListener = function (nativeEventName, cb) {
  51. if (nativeEventName !== eventNameToWrap) {
  52. return nativeAddEventListener.apply(this, arguments);
  53. }
  54. var wrappedCallback = function wrappedCallback(e) {
  55. var modifiedEvent = wrapper(e);
  56. if (modifiedEvent) {
  57. if (cb.handleEvent) {
  58. cb.handleEvent(modifiedEvent);
  59. } else {
  60. cb(modifiedEvent);
  61. }
  62. }
  63. };
  64. this._eventMap = this._eventMap || {};
  65. if (!this._eventMap[eventNameToWrap]) {
  66. this._eventMap[eventNameToWrap] = new Map();
  67. }
  68. this._eventMap[eventNameToWrap].set(cb, wrappedCallback);
  69. return nativeAddEventListener.apply(this, [nativeEventName, wrappedCallback]);
  70. };
  71. var nativeRemoveEventListener = proto.removeEventListener;
  72. proto.removeEventListener = function (nativeEventName, cb) {
  73. if (nativeEventName !== eventNameToWrap || !this._eventMap || !this._eventMap[eventNameToWrap]) {
  74. return nativeRemoveEventListener.apply(this, arguments);
  75. }
  76. if (!this._eventMap[eventNameToWrap].has(cb)) {
  77. return nativeRemoveEventListener.apply(this, arguments);
  78. }
  79. var unwrappedCb = this._eventMap[eventNameToWrap].get(cb);
  80. this._eventMap[eventNameToWrap]["delete"](cb);
  81. if (this._eventMap[eventNameToWrap].size === 0) {
  82. delete this._eventMap[eventNameToWrap];
  83. }
  84. if (Object.keys(this._eventMap).length === 0) {
  85. delete this._eventMap;
  86. }
  87. return nativeRemoveEventListener.apply(this, [nativeEventName, unwrappedCb]);
  88. };
  89. Object.defineProperty(proto, 'on' + eventNameToWrap, {
  90. get: function get() {
  91. return this['_on' + eventNameToWrap];
  92. },
  93. set: function set(cb) {
  94. if (this['_on' + eventNameToWrap]) {
  95. this.removeEventListener(eventNameToWrap, this['_on' + eventNameToWrap]);
  96. delete this['_on' + eventNameToWrap];
  97. }
  98. if (cb) {
  99. this.addEventListener(eventNameToWrap, this['_on' + eventNameToWrap] = cb);
  100. }
  101. },
  102. enumerable: true,
  103. configurable: true
  104. });
  105. }
  106. function disableLog(bool) {
  107. if (typeof bool !== 'boolean') {
  108. return new Error('Argument type: ' + _typeof(bool) + '. Please use a boolean.');
  109. }
  110. logDisabled_ = bool;
  111. return bool ? 'adapter.js logging disabled' : 'adapter.js logging enabled';
  112. }
  113. /**
  114. * Disable or enable deprecation warnings
  115. * @param {!boolean} bool set to true to disable warnings.
  116. */
  117. function disableWarnings(bool) {
  118. if (typeof bool !== 'boolean') {
  119. return new Error('Argument type: ' + _typeof(bool) + '. Please use a boolean.');
  120. }
  121. deprecationWarnings_ = !bool;
  122. return 'adapter.js deprecation warnings ' + (bool ? 'disabled' : 'enabled');
  123. }
  124. function log() {
  125. if ((typeof window === "undefined" ? "undefined" : _typeof(window)) === 'object') {
  126. if (logDisabled_) {
  127. return;
  128. }
  129. if (typeof console !== 'undefined' && typeof console.log === 'function') {
  130. console.log.apply(console, arguments);
  131. }
  132. }
  133. }
  134. /**
  135. * Shows a deprecation warning suggesting the modern and spec-compatible API.
  136. */
  137. function deprecated(oldMethod, newMethod) {
  138. if (!deprecationWarnings_) {
  139. return;
  140. }
  141. console.warn(oldMethod + ' is deprecated, please use ' + newMethod + ' instead.');
  142. }
  143. /**
  144. * Browser detector.
  145. *
  146. * @return {object} result containing browser and version
  147. * properties.
  148. */
  149. function detectBrowser(window) {
  150. // Returned result object.
  151. var result = {
  152. browser: null,
  153. version: null
  154. };
  155. // Fail early if it's not a browser
  156. if (typeof window === 'undefined' || !window.navigator || !window.navigator.userAgent) {
  157. result.browser = 'Not a browser.';
  158. return result;
  159. }
  160. var navigator = window.navigator;
  161. if (navigator.mozGetUserMedia) {
  162. // Firefox.
  163. result.browser = 'firefox';
  164. result.version = extractVersion(navigator.userAgent, /Firefox\/(\d+)\./, 1);
  165. } else if (navigator.webkitGetUserMedia || window.isSecureContext === false && window.webkitRTCPeerConnection) {
  166. // Chrome, Chromium, Webview, Opera.
  167. // Version matches Chrome/WebRTC version.
  168. // Chrome 74 removed webkitGetUserMedia on http as well so we need the
  169. // more complicated fallback to webkitRTCPeerConnection.
  170. result.browser = 'chrome';
  171. result.version = extractVersion(navigator.userAgent, /Chrom(e|ium)\/(\d+)\./, 2);
  172. } else if (window.RTCPeerConnection && navigator.userAgent.match(/AppleWebKit\/(\d+)\./)) {
  173. // Safari.
  174. result.browser = 'safari';
  175. result.version = extractVersion(navigator.userAgent, /AppleWebKit\/(\d+)\./, 1);
  176. result.supportsUnifiedPlan = window.RTCRtpTransceiver && 'currentDirection' in window.RTCRtpTransceiver.prototype;
  177. } else {
  178. // Default fallthrough: not supported.
  179. result.browser = 'Not a supported browser.';
  180. return result;
  181. }
  182. return result;
  183. }
  184. /**
  185. * Checks if something is an object.
  186. *
  187. * @param {*} val The something you want to check.
  188. * @return true if val is an object, false otherwise.
  189. */
  190. function isObject(val) {
  191. return Object.prototype.toString.call(val) === '[object Object]';
  192. }
  193. /**
  194. * Remove all empty objects and undefined values
  195. * from a nested object -- an enhanced and vanilla version
  196. * of Lodash's `compact`.
  197. */
  198. function compactObject(data) {
  199. if (!isObject(data)) {
  200. return data;
  201. }
  202. return Object.keys(data).reduce(function (accumulator, key) {
  203. var isObj = isObject(data[key]);
  204. var value = isObj ? compactObject(data[key]) : data[key];
  205. var isEmptyObject = isObj && !Object.keys(value).length;
  206. if (value === undefined || isEmptyObject) {
  207. return accumulator;
  208. }
  209. return Object.assign(accumulator, _defineProperty({}, key, value));
  210. }, {});
  211. }
  212. /* iterates the stats graph recursively. */
  213. function walkStats(stats, base, resultSet) {
  214. if (!base || resultSet.has(base.id)) {
  215. return;
  216. }
  217. resultSet.set(base.id, base);
  218. Object.keys(base).forEach(function (name) {
  219. if (name.endsWith('Id')) {
  220. walkStats(stats, stats.get(base[name]), resultSet);
  221. } else if (name.endsWith('Ids')) {
  222. base[name].forEach(function (id) {
  223. walkStats(stats, stats.get(id), resultSet);
  224. });
  225. }
  226. });
  227. }
  228. /* filter getStats for a sender/receiver track. */
  229. function filterStats(result, track, outbound) {
  230. var streamStatsType = outbound ? 'outbound-rtp' : 'inbound-rtp';
  231. var filteredResult = new Map();
  232. if (track === null) {
  233. return filteredResult;
  234. }
  235. var trackStats = [];
  236. result.forEach(function (value) {
  237. if (value.type === 'track' && value.trackIdentifier === track.id) {
  238. trackStats.push(value);
  239. }
  240. });
  241. trackStats.forEach(function (trackStat) {
  242. result.forEach(function (stats) {
  243. if (stats.type === streamStatsType && stats.trackId === trackStat.id) {
  244. walkStats(result, stats, filteredResult);
  245. }
  246. });
  247. });
  248. return filteredResult;
  249. }