You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

RTCBrowserType.js 14KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557
  1. import { getLogger } from 'jitsi-meet-logger';
  2. let browserVersion; // eslint-disable-line prefer-const
  3. let currentBrowser;
  4. const logger = getLogger(__filename);
  5. const RTCBrowserType = {
  6. RTC_BROWSER_CHROME: 'rtc_browser.chrome',
  7. RTC_BROWSER_OPERA: 'rtc_browser.opera',
  8. RTC_BROWSER_FIREFOX: 'rtc_browser.firefox',
  9. RTC_BROWSER_IEXPLORER: 'rtc_browser.iexplorer',
  10. RTC_BROWSER_EDGE: 'rtc_browser.edge',
  11. RTC_BROWSER_SAFARI: 'rtc_browser.safari',
  12. RTC_BROWSER_NWJS: 'rtc_browser.nwjs',
  13. RTC_BROWSER_ELECTRON: 'rtc_browser.electron',
  14. RTC_BROWSER_REACT_NATIVE: 'rtc_browser.react-native',
  15. /**
  16. * Tells whether or not the <tt>MediaStream/tt> is removed from
  17. * the <tt>PeerConnection</tt> and disposed on video mute (in order to turn
  18. * off the camera device).
  19. * @return {boolean} <tt>true</tt> if the current browser supports this
  20. * strategy or <tt>false</tt> otherwise.
  21. */
  22. doesVideoMuteByStreamRemove() {
  23. return !(
  24. RTCBrowserType.isFirefox()
  25. || RTCBrowserType.isEdge()
  26. || RTCBrowserType.isSafariWithWebrtc()
  27. );
  28. },
  29. /**
  30. * Gets current browser type.
  31. * @returns {string}
  32. */
  33. getBrowserType() {
  34. return currentBrowser;
  35. },
  36. /**
  37. * Gets current browser name, split from the type.
  38. * @returns {string}
  39. */
  40. getBrowserName() {
  41. const isAndroid = navigator.userAgent.indexOf('Android') !== -1;
  42. if (isAndroid) {
  43. return 'android';
  44. }
  45. return currentBrowser.split('rtc_browser.')[1];
  46. },
  47. /**
  48. * Checks if current browser is Chrome.
  49. * @returns {boolean}
  50. */
  51. isChrome() {
  52. return currentBrowser === RTCBrowserType.RTC_BROWSER_CHROME;
  53. },
  54. /**
  55. * Checks if current browser is Opera.
  56. * @returns {boolean}
  57. */
  58. isOpera() {
  59. return currentBrowser === RTCBrowserType.RTC_BROWSER_OPERA;
  60. },
  61. /**
  62. * Checks if current browser is Firefox.
  63. * @returns {boolean}
  64. */
  65. isFirefox() {
  66. return currentBrowser === RTCBrowserType.RTC_BROWSER_FIREFOX;
  67. },
  68. /**
  69. * Checks if current browser is Internet Explorer.
  70. * @returns {boolean}
  71. */
  72. isIExplorer() {
  73. return currentBrowser === RTCBrowserType.RTC_BROWSER_IEXPLORER;
  74. },
  75. /**
  76. * Checks if current browser is Microsoft Edge.
  77. * @returns {boolean}
  78. */
  79. isEdge() {
  80. return currentBrowser === RTCBrowserType.RTC_BROWSER_EDGE;
  81. },
  82. /**
  83. * Checks if current browser is Safari.
  84. * @returns {boolean}
  85. */
  86. isSafari() {
  87. return currentBrowser === RTCBrowserType.RTC_BROWSER_SAFARI;
  88. },
  89. /**
  90. * Checks if current browser is a Safari and a version of Safari that
  91. * supports native webrtc.
  92. *
  93. * @returns {boolean}
  94. */
  95. isSafariWithWebrtc() {
  96. return RTCBrowserType.isSafari()
  97. && RTCBrowserType.getSafariVersion() >= 11;
  98. },
  99. /**
  100. * Checks if current environment is NWJS.
  101. * @returns {boolean}
  102. */
  103. isNWJS() {
  104. return currentBrowser === RTCBrowserType.RTC_BROWSER_NWJS;
  105. },
  106. /**
  107. * Checks if current environment is Electron.
  108. * @returns {boolean}
  109. */
  110. isElectron() {
  111. return currentBrowser === RTCBrowserType.RTC_BROWSER_ELECTRON;
  112. },
  113. /**
  114. * Check whether or not the current browser support peer to peer connections
  115. * @return {boolean} <tt>true</tt> if p2p is supported or <tt>false</tt>
  116. * otherwise.
  117. */
  118. isP2PSupported() {
  119. return !RTCBrowserType.isEdge();
  120. },
  121. /**
  122. * Checks if current environment is React Native.
  123. * @returns {boolean}
  124. */
  125. isReactNative() {
  126. return currentBrowser === RTCBrowserType.RTC_BROWSER_REACT_NATIVE;
  127. },
  128. /**
  129. * Checks if Temasys RTC plugin is used.
  130. * @returns {boolean}
  131. */
  132. isTemasysPluginUsed() {
  133. // Temasys do not support Microsoft Edge:
  134. // http://support.temasys.com.sg/support/solutions/articles/
  135. // 5000654345-can-the-temasys-webrtc-plugin-be-used-with-microsoft-edge-
  136. return (
  137. (RTCBrowserType.isSafari()
  138. && !RTCBrowserType.isSafariWithWebrtc())
  139. || (RTCBrowserType.isIExplorer()
  140. && RTCBrowserType.getIExplorerVersion() < 12)
  141. );
  142. },
  143. /**
  144. * Returns whether or not the current browser should be using the new
  145. * getUserMedia flow, which utilizes the adapter shim. This method should
  146. * be temporary and used while migrating all browsers to use adapter and
  147. * the new getUserMedia.
  148. *
  149. * @returns {boolean}
  150. */
  151. usesNewGumFlow() {
  152. return (RTCBrowserType.isChrome()
  153. && RTCBrowserType.getChromeVersion() >= 61)
  154. || RTCBrowserType.isFirefox()
  155. || RTCBrowserType.isSafariWithWebrtc();
  156. },
  157. /**
  158. * Checks if the current browser triggers 'onmute'/'onunmute' events when
  159. * user's connection is interrupted and the video stops playback.
  160. * @returns {*|boolean} 'true' if the event is supported or 'false'
  161. * otherwise.
  162. */
  163. isVideoMuteOnConnInterruptedSupported() {
  164. return RTCBrowserType.isChrome() || RTCBrowserType.isElectron();
  165. },
  166. /**
  167. * Returns Firefox version.
  168. * @returns {number|null}
  169. */
  170. getFirefoxVersion() {
  171. return RTCBrowserType.isFirefox() ? browserVersion : null;
  172. },
  173. /**
  174. * Returns Chrome version.
  175. * @returns {number|null}
  176. */
  177. getChromeVersion() {
  178. return RTCBrowserType.isChrome() ? browserVersion : null;
  179. },
  180. /**
  181. * Returns Opera version.
  182. * @returns {number|null}
  183. */
  184. getOperaVersion() {
  185. return RTCBrowserType.isOpera() ? browserVersion : null;
  186. },
  187. /**
  188. * Returns Internet Explorer version.
  189. *
  190. * @returns {number|null}
  191. */
  192. getIExplorerVersion() {
  193. return RTCBrowserType.isIExplorer() ? browserVersion : null;
  194. },
  195. /**
  196. * Returns Edge version.
  197. *
  198. * @returns {number|null}
  199. */
  200. getEdgeVersion() {
  201. return RTCBrowserType.isEdge() ? browserVersion : null;
  202. },
  203. /**
  204. * Returns Safari version.
  205. *
  206. * @returns {number|null}
  207. */
  208. getSafariVersion() {
  209. return RTCBrowserType.isSafari ? browserVersion : null;
  210. },
  211. usesPlanB() {
  212. return !RTCBrowserType.usesUnifiedPlan();
  213. },
  214. usesUnifiedPlan() {
  215. return RTCBrowserType.isFirefox();
  216. },
  217. /**
  218. * Checks if the current browser reports upload and download bandwidth
  219. * statistics.
  220. * @return {boolean}
  221. */
  222. supportsBandwidthStatistics() {
  223. // FIXME bandwidth stats are currently not implemented for FF on our
  224. // side, but not sure if not possible ?
  225. return !RTCBrowserType.isFirefox() && !RTCBrowserType.isEdge();
  226. },
  227. /**
  228. * Checks if the current browser supports WebRTC datachannels.
  229. * @return {boolean}
  230. */
  231. supportsDataChannels() {
  232. // NOTE: Edge does not yet implement DataChannel.
  233. return !RTCBrowserType.isEdge();
  234. },
  235. /**
  236. * Checks if the current browser reports round trip time statistics for
  237. * the ICE candidate pair.
  238. * @return {boolean}
  239. */
  240. supportsRTTStatistics() {
  241. // Firefox does not seem to report RTT for ICE candidate pair:
  242. // eslint-disable-next-line max-len
  243. // https://www.w3.org/TR/webrtc-stats/#dom-rtcicecandidatepairstats-currentroundtriptime
  244. // It does report mozRTT for RTP streams, but at the time of this
  245. // writing it's value does not make sense most of the time
  246. // (is reported as 1):
  247. // https://bugzilla.mozilla.org/show_bug.cgi?id=1241066
  248. // For Chrome and others we rely on 'googRtt'.
  249. return !RTCBrowserType.isFirefox() && !RTCBrowserType.isEdge();
  250. },
  251. /**
  252. * Whether jitsi-meet supports simulcast on the current browser.
  253. * @returns {boolean}
  254. */
  255. supportsSimulcast() {
  256. return RTCBrowserType.isChrome()
  257. || RTCBrowserType.isFirefox()
  258. || RTCBrowserType.isElectron()
  259. || RTCBrowserType.isNWJS()
  260. || RTCBrowserType.isReactNative();
  261. },
  262. supportsRtx() {
  263. return !RTCBrowserType.isFirefox();
  264. },
  265. supportsRtpSender() {
  266. return RTCBrowserType.isFirefox();
  267. },
  268. /**
  269. * Returns whether or not the current browser can support capturing video,
  270. * be it camera or desktop, and displaying received video.
  271. *
  272. * @returns {boolean}
  273. */
  274. supportsVideo() {
  275. // Currently Safari using webrtc/adapter does not support video due in
  276. // part to Safari only supporting H264 and the bridge sending VP8.
  277. return !RTCBrowserType.isSafariWithWebrtc();
  278. }
  279. };
  280. /**
  281. * detectOpera() must be called before detectChrome() !!!
  282. * otherwise Opera wil be detected as Chrome
  283. */
  284. function detectChrome() {
  285. if (navigator.webkitGetUserMedia) {
  286. currentBrowser = RTCBrowserType.RTC_BROWSER_CHROME;
  287. // We can assume that user agent is chrome, because it's enforced when
  288. // 'ext' streaming method is set.
  289. const verMatch = navigator.userAgent.match(/chrome\/(\d+)\./i);
  290. const ver = verMatch ? parseInt(verMatch[1], 10) : 'undefined';
  291. logger.log(`This appears to be Chrome, ver: ${ver}`);
  292. return ver;
  293. }
  294. return null;
  295. }
  296. /**
  297. *
  298. */
  299. function detectOpera() {
  300. const userAgent = navigator.userAgent;
  301. if (userAgent.match(/Opera|OPR/)) {
  302. currentBrowser = RTCBrowserType.RTC_BROWSER_OPERA;
  303. const version = userAgent.match(/(Opera|OPR) ?\/?(\d+)\.?/)[2];
  304. logger.info(`This appears to be Opera, ver: ${version}`);
  305. return version;
  306. }
  307. return null;
  308. }
  309. /**
  310. *
  311. */
  312. function detectFirefox() {
  313. if (navigator.mozGetUserMedia) {
  314. currentBrowser = RTCBrowserType.RTC_BROWSER_FIREFOX;
  315. const version = parseInt(
  316. navigator.userAgent.match(/Firefox\/([0-9]+)\./)[1], 10);
  317. logger.log(`This appears to be Firefox, ver: ${version}`);
  318. return version;
  319. }
  320. return null;
  321. }
  322. /**
  323. *
  324. */
  325. function detectSafari() {
  326. if (/^((?!chrome).)*safari/i.test(navigator.userAgent)) {
  327. currentBrowser = RTCBrowserType.RTC_BROWSER_SAFARI;
  328. const matchedVersionParts
  329. = navigator.userAgent.match(/version\/(\d+(\.\d+)?)/i);
  330. const versionString = matchedVersionParts && matchedVersionParts[1];
  331. const version = versionString ? parseInt(versionString, 10) : -1;
  332. logger.info(`This appears to be Safari, ver: ${version}`);
  333. return version;
  334. }
  335. return null;
  336. }
  337. /**
  338. * Detects IE.
  339. */
  340. function detectIE() {
  341. let version;
  342. const ua = window.navigator.userAgent;
  343. const msie = ua.indexOf('MSIE ');
  344. if (msie > 0) {
  345. // IE 10 or older => return version number
  346. version = parseInt(ua.substring(msie + 5, ua.indexOf('.', msie)), 10);
  347. }
  348. const trident = ua.indexOf('Trident/');
  349. if (!version && trident > 0) {
  350. // IE 11 => return version number
  351. const rv = ua.indexOf('rv:');
  352. version = parseInt(ua.substring(rv + 3, ua.indexOf('.', rv)), 10);
  353. }
  354. if (version) {
  355. currentBrowser = RTCBrowserType.RTC_BROWSER_IEXPLORER;
  356. logger.info(`This appears to be IExplorer, ver: ${version}`);
  357. }
  358. return version;
  359. }
  360. /**
  361. * Detects Edge.
  362. */
  363. function detectEdge() {
  364. let version;
  365. const ua = window.navigator.userAgent;
  366. const edge = ua.indexOf('Edge/');
  367. if (!version && edge > 0) {
  368. version = parseInt(ua.substring(edge + 5, ua.indexOf('.', edge)), 10);
  369. }
  370. if (version) {
  371. currentBrowser = RTCBrowserType.RTC_BROWSER_EDGE;
  372. logger.info(`This appears to be Edge, ver: ${version}`);
  373. }
  374. return version;
  375. }
  376. /**
  377. * Detects Electron environment.
  378. */
  379. function detectElectron() {
  380. const userAgent = navigator.userAgent;
  381. if (userAgent.match(/Electron/)) {
  382. currentBrowser = RTCBrowserType.RTC_BROWSER_ELECTRON;
  383. const version = userAgent.match(/Electron\/([\d.]+)/)[1];
  384. logger.info(`This appears to be Electron, ver: ${version}`);
  385. return version;
  386. }
  387. return null;
  388. }
  389. /**
  390. *
  391. */
  392. function detectNWJS() {
  393. const userAgent = navigator.userAgent;
  394. if (userAgent.match(/JitsiMeetNW/)) {
  395. currentBrowser = RTCBrowserType.RTC_BROWSER_NWJS;
  396. const version = userAgent.match(/JitsiMeetNW\/([\d.]+)/)[1];
  397. logger.info(`This appears to be JitsiMeetNW, ver: ${version}`);
  398. return version;
  399. }
  400. return null;
  401. }
  402. /**
  403. *
  404. */
  405. function detectReactNative() {
  406. const match
  407. = navigator.userAgent.match(/\b(react[ \t_-]*native)(?:\/(\S+))?/i);
  408. let version;
  409. // If we're remote debugging a React Native app, it may be treated as
  410. // Chrome. Check navigator.product as well and always return some version
  411. // even if we can't get the real one.
  412. if (match || navigator.product === 'ReactNative') {
  413. currentBrowser = RTCBrowserType.RTC_BROWSER_REACT_NATIVE;
  414. let name;
  415. if (match && match.length > 2) {
  416. name = match[1];
  417. version = match[2];
  418. }
  419. name || (name = 'react-native');
  420. version || (version = 'unknown');
  421. console.info(`This appears to be ${name}, ver: ${version}`);
  422. } else {
  423. // We're not running in a React Native environment.
  424. version = null;
  425. }
  426. return version;
  427. }
  428. /**
  429. *
  430. */
  431. function detectBrowser() {
  432. let version;
  433. const detectors = [
  434. detectReactNative,
  435. detectElectron,
  436. detectNWJS,
  437. detectOpera,
  438. detectChrome,
  439. detectFirefox,
  440. detectEdge,
  441. detectIE,
  442. detectSafari
  443. ];
  444. // Try all browser detectors
  445. for (let i = 0; i < detectors.length; i++) {
  446. version = detectors[i]();
  447. if (version) {
  448. return version;
  449. }
  450. }
  451. logger.warn('Browser type defaults to Safari ver 1');
  452. currentBrowser = RTCBrowserType.RTC_BROWSER_SAFARI;
  453. return 1;
  454. }
  455. browserVersion = detectBrowser();
  456. export default RTCBrowserType;