diff --git a/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js b/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js
index 2a6ee420161e1c22ad90c0ccdf296f3ab24ad67f..1fadd60a0a93ee47bfd576b0c3beee185a1d7f86 100755
--- a/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js
+++ b/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js
@@ -1,4 +1,3 @@
-import browser from 'browser-detect';
 import BaseAudioBridge from './base';
 import logger from '/imports/startup/client/logger';
 import {
@@ -19,6 +18,7 @@ import CallStateOptions from '/imports/api/voice-call-states/utils/callStates';
 import Auth from '/imports/ui/services/auth';
 import Settings from '/imports/ui/services/settings';
 import Storage from '/imports/ui/services/storage/session';
+import browserInfo from '/imports/utils/browserInfo';
 
 const MEDIA = Meteor.settings.public.media;
 const MEDIA_TAG = MEDIA.mediaTag;
@@ -1084,7 +1084,9 @@ class SIPSession {
       const matchConstraints = this.filterSupportedConstraints(constraints);
 
       //Chromium bug - see: https://bugs.chromium.org/p/chromium/issues/detail?id=796964&q=applyConstraints&can=2
-      if (browser().name === 'chrome') {
+      const { isChrome } = browserInfo;
+
+      if (isChrome) {
         matchConstraints.deviceId = this.inputDeviceId;
 
         const stream = await navigator.mediaDevices.getUserMedia(
diff --git a/bigbluebutton-html5/imports/startup/client/base.jsx b/bigbluebutton-html5/imports/startup/client/base.jsx
index b83d3a5e58b9a06fec948c4d38a1fcd539ccee1c..db9afa1ea225770ed1dd0bdbd91fb7fadb375c7e 100755
--- a/bigbluebutton-html5/imports/startup/client/base.jsx
+++ b/bigbluebutton-html5/imports/startup/client/base.jsx
@@ -403,7 +403,7 @@ const BaseContainer = withTracker(() => {
 
   if (Session.equals('openPanel', undefined) || Session.equals('subscriptionsReady', true)) {
     if (!checkedUserSettings) {
-      if (getFromUserSettings('bbb_show_participants_on_login', Meteor.settings.public.layout.showParticipantsOnLogin) && !deviceInfo.type().isPhone) {
+      if (getFromUserSettings('bbb_show_participants_on_login', Meteor.settings.public.layout.showParticipantsOnLogin) && !deviceInfo.isPhone) {
         if (CHAT_ENABLED && getFromUserSettings('bbb_show_public_chat_on_login', !Meteor.settings.public.chat.startClosed)) {
           Session.set('openPanel', 'chat');
           Session.set('idChatOpen', PUBLIC_CHAT_ID);
diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/component.jsx
index bd336850491e17d239e62fe4cdc89e24ce7847ec..828cfcc83f2543812c5885427867ff2fb5c73f8e 100755
--- a/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/actions-bar/create-breakout-room/component.jsx
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
 import { defineMessages, injectIntl } from 'react-intl';
 import _ from 'lodash';
 import cx from 'classnames';
-import browser from 'browser-detect';
+import deviceInfo from '/imports/utils/deviceInfo';
 import Button from '/imports/ui/components/button/component';
 import { Session } from 'meteor/session';
 import Modal from '/imports/ui/components/modal/fullscreen/component';
@@ -807,8 +807,7 @@ class BreakoutRoom extends PureComponent {
       numberOfRoomsIsValid,
     } = this.state;
 
-    const BROWSER_RESULTS = browser();
-    const isMobileBrowser = BROWSER_RESULTS.mobile || BROWSER_RESULTS.os.includes('Android');
+    const { isMobile } = deviceInfo;
 
     return (
       <Modal
@@ -834,7 +833,7 @@ class BreakoutRoom extends PureComponent {
       >
         <div className={styles.content}>
           {isInvitation || this.renderTitle()}
-          {isMobileBrowser ? this.renderMobile() : this.renderDesktop()}
+          {isMobile ? this.renderMobile() : this.renderDesktop()}
         </div>
       </Modal>
     );
diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/screenshare/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/screenshare/component.jsx
index 64dc749da3acd1d22649ee19fe1e38035063c73f..634a58adb28dca69b9b43cf3aecf7ab54e20c990 100755
--- a/bigbluebutton-html5/imports/ui/components/actions-bar/screenshare/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/actions-bar/screenshare/component.jsx
@@ -1,7 +1,8 @@
 import React, { memo } from 'react';
 import PropTypes from 'prop-types';
 import { defineMessages, injectIntl } from 'react-intl';
-import browser from 'browser-detect';
+import deviceInfo from '/imports/utils/deviceInfo';
+import browserInfo from '/imports/utils/browserInfo';
 import Button from '/imports/ui/components/button/component';
 import logger from '/imports/startup/client/logger';
 import { notify } from '/imports/ui/services/notification';
@@ -19,12 +20,8 @@ import {
 } from '/imports/ui/components/screenshare/service';
 import { SCREENSHARING_ERRORS } from '/imports/api/screenshare/client/bridge/errors';
 
-const BROWSER_RESULTS = browser();
-const isMobileBrowser = (BROWSER_RESULTS ? BROWSER_RESULTS.mobile : false)
-  || (BROWSER_RESULTS && BROWSER_RESULTS.os
-    ? BROWSER_RESULTS.os.includes('Android') // mobile flag doesn't always work
-    : false);
-const IS_SAFARI = BROWSER_RESULTS.name === 'safari';
+const { isMobile } = deviceInfo;
+const { isSafari } = browserInfo;
 
 const propTypes = {
   intl: PropTypes.objectOf(Object).isRequired,
@@ -174,7 +171,7 @@ const ScreenshareButton = ({
     ? intlMessages.stopDesktopShareDesc : intlMessages.desktopShareDesc;
 
   const shouldAllowScreensharing = enabled
-    && !isMobileBrowser
+    && !isMobile
     && amIPresenter;
 
   return shouldAllowScreensharing
@@ -193,7 +190,7 @@ const ScreenshareButton = ({
         onClick={isVideoBroadcasting
           ? screenshareHasEnded
           : () => {
-            if (IS_SAFARI && !ScreenshareBridgeService.HAS_DISPLAY_MEDIA) {
+            if (isSafari && !ScreenshareBridgeService.HAS_DISPLAY_MEDIA) {
               renderScreenshareUnavailableModal();
             } else {
               shareScreen(handleFailure);
diff --git a/bigbluebutton-html5/imports/ui/components/app/component.jsx b/bigbluebutton-html5/imports/ui/components/app/component.jsx
index 890d75a857dcc36b44530afbbb3decbb6fd73132..3ddbaca3dbb920fd0c1d2bc33641e4c8a737725a 100755
--- a/bigbluebutton-html5/imports/ui/components/app/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/app/component.jsx
@@ -3,7 +3,8 @@ import PropTypes from 'prop-types';
 import { throttle } from 'lodash';
 import { defineMessages, injectIntl } from 'react-intl';
 import Modal from 'react-modal';
-import browser from 'browser-detect';
+import browserInfo from '/imports/utils/browserInfo';
+import deviceInfo from '/imports/utils/deviceInfo';
 import PanelManager from '/imports/ui/components/panel-manager/component';
 import PollingContainer from '/imports/ui/components/polling/container';
 import logger from '/imports/startup/client/logger';
@@ -124,21 +125,18 @@ class App extends Component {
     const {
       locale, notify, intl, validIOSVersion, startBandwidthMonitoring, handleNetworkConnection,
     } = this.props;
-    const BROWSER_RESULTS = browser();
-    const isMobileBrowser = BROWSER_RESULTS.mobile || BROWSER_RESULTS.os.includes('Android');
+    const { browserName } = browserInfo;
+    const { isMobile, osName } = deviceInfo;
 
     MediaService.setSwapLayout();
     Modal.setAppElement('#app');
     document.getElementsByTagName('html')[0].lang = locale;
-    document.getElementsByTagName('html')[0].style.fontSize = isMobileBrowser ? MOBILE_FONT_SIZE : DESKTOP_FONT_SIZE;
+    document.getElementsByTagName('html')[0].style.fontSize = isMobile ? MOBILE_FONT_SIZE : DESKTOP_FONT_SIZE;
 
     const body = document.getElementsByTagName('body')[0];
-    if (BROWSER_RESULTS && BROWSER_RESULTS.name) {
-      body.classList.add(`browser-${BROWSER_RESULTS.name}`);
-    }
-    if (BROWSER_RESULTS && BROWSER_RESULTS.os) {
-      body.classList.add(`os-${BROWSER_RESULTS.os.split(' ').shift().toLowerCase()}`);
-    }
+
+    body.classList.add(`browser-${browserName.toLowerCase()}`);
+    body.classList.add(`os-${osName.split(' ').shift().toLowerCase()}`);
 
     if (!validIOSVersion()) {
       notify(
@@ -160,7 +158,7 @@ class App extends Component {
       startBandwidthMonitoring();
     }
 
-    if (isMobileBrowser) makeCall('setMobileUser');
+    if (isMobile) makeCall('setMobileUser');
 
     ConnectionStatusService.startRoundTripTime();
 
diff --git a/bigbluebutton-html5/imports/ui/components/app/container.jsx b/bigbluebutton-html5/imports/ui/components/app/container.jsx
index 71c0a129c6efbe4d92dc827f0acb1f1d53fee33d..5329a67fe801da17c9c5f2d1a2917a956169327f 100755
--- a/bigbluebutton-html5/imports/ui/components/app/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/app/container.jsx
@@ -115,7 +115,7 @@ export default injectIntl(withModalMounter(withTracker(({ intl, baseControls })
     UserInfo,
     notify,
     validIOSVersion,
-    isPhone: deviceInfo.type().isPhone,
+    isPhone: deviceInfo.isPhone,
     isRTL: document.documentElement.getAttribute('dir') === 'rtl',
     meetingMuted: voiceProp.muteOnStart,
     currentUserEmoji: currentUserEmoji(currentUser),
diff --git a/bigbluebutton-html5/imports/ui/components/app/service.js b/bigbluebutton-html5/imports/ui/components/app/service.js
index d758bb7b8a517303f76cd7ffc9e916f5d7734016..1de523d006e453df94cc8a9dfbed6608db6fd38d 100644
--- a/bigbluebutton-html5/imports/ui/components/app/service.js
+++ b/bigbluebutton-html5/imports/ui/components/app/service.js
@@ -2,6 +2,7 @@ import Breakouts from '/imports/api/breakouts';
 import Meetings from '/imports/api/meetings';
 import Settings from '/imports/ui/services/settings';
 import Auth from '/imports/ui/services/auth/index';
+import deviceInfo from '/imports/utils/deviceInfo';
 
 const getFontSize = () => {
   const applicationSettings = Settings.application;
@@ -17,12 +18,10 @@ function meetingIsBreakout() {
 }
 
 const validIOSVersion = () => {
-  const SUPPORTED_OS_VERSION = 12.2;
-  const iosMatch = navigator.userAgent.match(/OS (\d+)_(\d+)/);
-  if (iosMatch) {
-    const versionNumber = iosMatch[0].split(' ')[1].replace('_', '.');
-    const isInvalid = parseFloat(versionNumber) < SUPPORTED_OS_VERSION;
-    if (isInvalid) return false;
+  const { isIos, isIosVersionSupported } = deviceInfo;
+
+  if (isIos) {
+    return isIosVersionSupported();
   }
   return true;
 };
diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-controls/component.jsx b/bigbluebutton-html5/imports/ui/components/audio/audio-controls/component.jsx
index 31504bffaf3df7ee194838a8fffe0d77547ae4af..8c40bf140b98bddf73366468fcff18b0522cc57d 100755
--- a/bigbluebutton-html5/imports/ui/components/audio/audio-controls/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/audio/audio-controls/component.jsx
@@ -1,8 +1,8 @@
 import React, { PureComponent } from 'react';
-import { isMobile } from 'react-device-detect';
 import PropTypes from 'prop-types';
 import cx from 'classnames';
 import { defineMessages, injectIntl } from 'react-intl';
+import deviceInfo from '/imports/utils/deviceInfo';
 import Button from '/imports/ui/components/button/component';
 import getFromUserSettings from '/imports/ui/services/users-settings';
 import withShortcutHelper from '/imports/ui/components/shortcut-help/service';
@@ -138,6 +138,8 @@ class AudioControls extends PureComponent {
       inAudio,
     } = this.props;
 
+    const { isMobile } = deviceInfo;
+
     let { enableDynamicAudioDeviceSelection } = Meteor.settings.public.app;
 
     if (typeof enableDynamicAudioDeviceSelection === 'undefined') {
diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-modal/container.jsx b/bigbluebutton-html5/imports/ui/components/audio/audio-modal/container.jsx
index 7c6e2c8f3bd0f5695e995ddcb07f4376f5353e8e..6002950a4e9595c63a4487caed28048dde1063db 100755
--- a/bigbluebutton-html5/imports/ui/components/audio/audio-modal/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/audio/audio-modal/container.jsx
@@ -1,7 +1,8 @@
 import React from 'react';
 import { withTracker } from 'meteor/react-meteor-data';
 import { withModalMounter } from '/imports/ui/components/modal/service';
-import browser from 'browser-detect';
+import deviceInfo from '/imports/utils/deviceInfo';
+import browserInfo from '/imports/utils/browserInfo';
 import getFromUserSettings from '/imports/ui/services/users-settings';
 import AudioModal from './component';
 import Meetings from '/imports/api/meetings';
@@ -56,6 +57,9 @@ export default lockContextContainer(withModalMounter(withTracker(({ userLocks })
 
   const forceListenOnlyAttendee = forceListenOnly && !Service.isUserModerator();
 
+  const { isIos } = deviceInfo;
+  const { isChrome, isEdge, isIe } = browserInfo;
+
   return ({
     joinedAudio,
     meetingIsBreakout,
@@ -80,9 +84,9 @@ export default lockContextContainer(withModalMounter(withTracker(({ userLocks })
     audioLocked: userLocks.userMic,
     joinFullAudioImmediately,
     forceListenOnlyAttendee,
-    isIOSChrome: browser().name === 'crios',
+    isIOSChrome: isIos && isChrome,
     isMobileNative: navigator.userAgent.toLowerCase().includes('bbbnative'),
-    isIEOrEdge: browser().name === 'edge' || browser().name === 'ie',
+    isIEOrEdge: isEdge || isIe,
     autoplayBlocked: Service.autoplayBlocked(),
     handleAllowAutoplay: () => Service.handleAllowAutoplay(),
     isRTL,
diff --git a/bigbluebutton-html5/imports/ui/components/audio/device-selector/component.jsx b/bigbluebutton-html5/imports/ui/components/audio/device-selector/component.jsx
index 152a71794e4411011ff2a01e411805916c7ca1c3..629cb2c39b3b031e9c5dfb388bccb2edc6e1e55c 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/device-selector/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/audio/device-selector/component.jsx
@@ -3,7 +3,7 @@ import _ from 'lodash';
 import PropTypes from 'prop-types';
 import cx from 'classnames';
 import logger from '/imports/startup/client/logger';
-import browser from 'browser-detect';
+import browserInfo from '/imports/utils/browserInfo';
 import { styles } from '../audio-modal/styles';
 
 const propTypes = {
@@ -82,6 +82,7 @@ class DeviceSelector extends Component {
     } = this.props;
 
     const { options, value } = this.state;
+    const { isSafari } = browserInfo;
 
     return (
       <select
@@ -102,7 +103,7 @@ class DeviceSelector extends Component {
               </option>
             ))
             : (
-              (kind === 'audiooutput' && browser().name === 'safari')
+              (kind === 'audiooutput' && isSafari)
                 ? <option value="not-found">Default</option>
                 : <option value="not-found">{`no ${kind} found`}</option>
             )
diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-form/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/message-form/component.jsx
index 662ebce99b7df10287ce8af3480d5af1725126cb..997f939c3656da07f1a5c331d4104e57332eea87 100755
--- a/bigbluebutton-html5/imports/ui/components/chat/message-form/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/chat/message-form/component.jsx
@@ -2,7 +2,7 @@ import React, { PureComponent } from 'react';
 import { defineMessages, injectIntl } from 'react-intl';
 import cx from 'classnames';
 import TextareaAutosize from 'react-autosize-textarea';
-import browser from 'browser-detect';
+import deviceInfo from '/imports/utils/deviceInfo';
 import PropTypes from 'prop-types';
 import _ from 'lodash';
 import TypingIndicatorContainer from './typing-indicator/container';
@@ -80,8 +80,6 @@ class MessageForm extends PureComponent {
       hasErrors: false,
     };
 
-    this.BROWSER_RESULTS = browser();
-
     this.handleMessageChange = this.handleMessageChange.bind(this);
     this.handleMessageKeyDown = this.handleMessageKeyDown.bind(this);
     this.handleSubmit = this.handleSubmit.bind(this);
@@ -91,11 +89,11 @@ class MessageForm extends PureComponent {
   }
 
   componentDidMount() {
-    const { mobile } = this.BROWSER_RESULTS;
+    const { isMobile } = deviceInfo;
     this.setMessageState();
     this.setMessageHint();
 
-    if (!mobile) {
+    if (!isMobile) {
       if (this.textarea) this.textarea.focus();
     }
   }
@@ -108,9 +106,9 @@ class MessageForm extends PureComponent {
       partnerIsLoggedOut,
     } = this.props;
     const { message } = this.state;
-    const { mobile } = this.BROWSER_RESULTS;
+    const { isMobile } = deviceInfo;
 
-    if (prevProps.chatId !== chatId && !mobile) {
+    if (prevProps.chatId !== chatId && !isMobile) {
       if (this.textarea) this.textarea.focus();
     }
 
diff --git a/bigbluebutton-html5/imports/ui/components/chat/message-form/typing-indicator/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/message-form/typing-indicator/component.jsx
index b379f75af8054046c04c615b2ebfc0391047c809..08024cbaac08f00a7cb62392d17d0cd14a70d4f4 100644
--- a/bigbluebutton-html5/imports/ui/components/chat/message-form/typing-indicator/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/chat/message-form/typing-indicator/component.jsx
@@ -2,7 +2,6 @@ import React, { PureComponent } from 'react';
 import {
   defineMessages, injectIntl, FormattedMessage,
 } from 'react-intl';
-import browser from 'browser-detect';
 import PropTypes from 'prop-types';
 import cx from 'classnames';
 import { styles } from '../styles.scss';
@@ -23,8 +22,6 @@ class TypingIndicator extends PureComponent {
   constructor(props) {
     super(props);
 
-    this.BROWSER_RESULTS = browser();
-
     this.renderTypingElement = this.renderTypingElement.bind(this);
   }
 
diff --git a/bigbluebutton-html5/imports/ui/components/dropdown/component.jsx b/bigbluebutton-html5/imports/ui/components/dropdown/component.jsx
index 2a1e908d28295849e628d12e03845b7c11f498a5..48498da0f54d9d28db401387d491f3b7d0f594f1 100644
--- a/bigbluebutton-html5/imports/ui/components/dropdown/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/dropdown/component.jsx
@@ -1,10 +1,10 @@
 import React, { Component, Fragment } from 'react';
 import PropTypes from 'prop-types';
 import { findDOMNode } from 'react-dom';
-import { isMobile, withOrientationChange } from 'react-device-detect';
 import TetherComponent from 'react-tether';
 import cx from 'classnames';
 import { defineMessages, injectIntl } from 'react-intl';
+import deviceInfo from '/imports/utils/deviceInfo';
 import Button from '/imports/ui/components/button/component';
 import screenreaderTrap from 'makeup-screenreader-trap';
 import { styles } from './styles';
@@ -78,7 +78,7 @@ const targetAttachments = {
 class Dropdown extends Component {
   constructor(props) {
     super(props);
-    this.state = { isOpen: false };
+    this.state = { isOpen: false, isPortrait:deviceInfo.isPortrait() };
     this.handleShow = this.handleShow.bind(this);
     this.handleHide = this.handleHide.bind(this);
     this.handleToggle = this.handleToggle.bind(this);
@@ -129,6 +129,17 @@ class Dropdown extends Component {
     });
   }
 
+  componentDidMount() {
+    window.addEventListener('resize', this.updateOrientation);
+  }
+  componentWillUnmount() {
+    window.removeEventListener('resize', this.updateOrientation);
+  }
+
+  updateOrientation = () => {
+    this.setState({ isPortrait:deviceInfo.isPortrait() });
+  };
+
   handleWindowClick(event) {
     const { keepOpen, onHide } = this.props;
     const { isOpen } = this.state;
@@ -179,17 +190,13 @@ class Dropdown extends Component {
       tethered,
       placement,
       getContent,
-      isPortrait,
       ...otherProps
     } = this.props;
 
-    const { isOpen } = this.state;
-
-    const MOBILE_MEDIA = 'only screen and (max-width: 40em)';
-    const isSmall = window.matchMedia(MOBILE_MEDIA).matches;
-
+    const { isOpen, isPortrait } = this.state;
+    const { isPhone } = deviceInfo;
     const placements = placement && placement.replace(' ', '-');
-    const test = isMobile && isPortrait && isSmall ? {
+    const test = isPhone && isPortrait ? {
       width: '100%',
       height: '100%',
       transform: 'translateY(0)',
@@ -245,11 +252,11 @@ class Dropdown extends Component {
                   ...test,
                 }}
                 attachment={
-                  isMobile && isPortrait && isSmall ? 'middle center'
+                  isPhone && isPortrait ? 'middle center'
                     : attachments[placements]
                 }
                 targetAttachment={
-                  isMobile && isPortrait && isSmall ? 'auto auto'
+                  isPhone && isPortrait ? 'auto auto'
                     : targetAttachments[placements]
                 }
                 constraints={[
@@ -304,4 +311,4 @@ class Dropdown extends Component {
 
 Dropdown.propTypes = propTypes;
 Dropdown.defaultProps = defaultProps;
-export default injectIntl(withOrientationChange(Dropdown), { forwardRef: true });
+export default injectIntl(Dropdown, { forwardRef: true });
diff --git a/bigbluebutton-html5/imports/ui/components/legacy/component.jsx b/bigbluebutton-html5/imports/ui/components/legacy/component.jsx
index b859270bd6d4c88bf16b98e5fec0126d5cf2b665..f192ed59a4fda3090d35c27c5806ac549b61aa24 100755
--- a/bigbluebutton-html5/imports/ui/components/legacy/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/legacy/component.jsx
@@ -1,6 +1,7 @@
 import React, { Component } from 'react';
-import { IntlProvider, FormattedMessage, addLocaleData } from 'react-intl';
-import { browserName } from 'react-device-detect';
+import { IntlProvider, FormattedMessage } from 'react-intl';
+import browserInfo from '/imports/utils/browserInfo';
+import deviceInfo from '/imports/utils/deviceInfo';
 import './styles.css';
 
 
@@ -68,7 +69,7 @@ import './styles.css';
 const FETCHING = 'fetching';
 const FALLBACK = 'fallback';
 const READY = 'ready';
-const supportedBrowsers = ['chrome', 'firefox', 'safari', 'opera', 'edge', 'yandex'];
+const supportedBrowsers = ['Chrome', 'Firefox', 'Safari', 'Opera', 'Microsoft Edge', 'Yandex Browser'];
 const DEFAULT_LANGUAGE = Meteor.settings.public.app.defaultSettings.application.fallbackLocale;
 
 export default class Legacy extends Component {
@@ -148,9 +149,12 @@ export default class Legacy extends Component {
   }
 
   render() {
+    const { browserName, isChrome } = browserInfo;
+    const { isIos } = deviceInfo;
+
     const { messages, normalizedLocale, viewState } = this.state;
     const isSupportedBrowser = supportedBrowsers.includes(browserName);
-    const isChromeIos = browserName === 'crios';
+    const isChromeIos = isIos && isChrome;
 
     let messageId = isSupportedBrowser ? 'app.legacy.upgradeBrowser' : 'app.legacy.unsupportedBrowser';
     if (isChromeIos) messageId = 'app.legacy.criosBrowser';
diff --git a/bigbluebutton-html5/imports/ui/components/media/component.jsx b/bigbluebutton-html5/imports/ui/components/media/component.jsx
index e3dd38baf921d3234362dbc11323c7ae1229e6e2..32d86b03ab392b59d08228e3c7fb2289839f3880 100644
--- a/bigbluebutton-html5/imports/ui/components/media/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/media/component.jsx
@@ -1,13 +1,13 @@
 import React, { Component } from 'react';
 import PropTypes from 'prop-types';
 import cx from 'classnames';
+import deviceInfo from '/imports/utils/deviceInfo';
 import Settings from '/imports/ui/services/settings';
-import { isMobile, isIPad13 } from 'react-device-detect';
 import WebcamDraggable from './webcam-draggable-overlay/component';
 import { styles } from './styles';
 import Storage from '../../services/storage/session';
 
-const BROWSER_ISMOBILE = isMobile || isIPad13;
+const { isMobile } = deviceInfo;
 
 const propTypes = {
   children: PropTypes.element.isRequired,
@@ -91,7 +91,7 @@ export default class Media extends Component {
             )
               ? '80%'
               : '100%',
-            minHeight: BROWSER_ISMOBILE && window.innerWidth > window.innerHeight ? '50%' : '20%',
+            minHeight: isMobile && window.innerWidth > window.innerHeight ? '50%' : '20%',
             maxWidth: usersVideo.length > 0
             && (
               webcamsPlacement !== 'top'
diff --git a/bigbluebutton-html5/imports/ui/components/media/webcam-draggable-overlay/component.jsx b/bigbluebutton-html5/imports/ui/components/media/webcam-draggable-overlay/component.jsx
index 4c9cf5a67b3915746a348d3e62737ccb0b79f445..e3b78e881f1938e4d59b14b797b9c106f6ee92b7 100644
--- a/bigbluebutton-html5/imports/ui/components/media/webcam-draggable-overlay/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/media/webcam-draggable-overlay/component.jsx
@@ -3,7 +3,7 @@ import Draggable from 'react-draggable';
 import cx from 'classnames';
 import PropTypes from 'prop-types';
 import Resizable from 're-resizable';
-import { isMobile, isIPad13 } from 'react-device-detect';
+import deviceInfo from '/imports/utils/deviceInfo';
 import { withDraggableConsumer } from './context';
 import VideoProviderContainer from '/imports/ui/components/video-provider/container';
 import { styles } from '../styles.scss';
@@ -11,8 +11,6 @@ import Storage from '../../../services/storage/session';
 import { withLayoutConsumer } from '/imports/ui/components/layout/context';
 import { WEBCAMSAREA_MIN_PERCENT, PRESENTATIONAREA_MIN_WIDTH } from '/imports/ui/components/layout/layout-manager';
 
-const BROWSER_ISMOBILE = isMobile || isIPad13;
-
 const propTypes = {
   swapLayout: PropTypes.bool,
   hideOverlay: PropTypes.bool,
@@ -286,6 +284,8 @@ class WebcamDraggable extends PureComponent {
       audioModalIsOpen,
     } = this.props;
 
+    const { isMobile } = deviceInfo;
+
     const { resizing, webcamsAreaResizable, hideWebcams } = this.state;
 
     const {
@@ -316,7 +316,7 @@ class WebcamDraggable extends PureComponent {
       };
     }
 
-    if (swapLayout || isCameraFullscreen || BROWSER_ISMOBILE) {
+    if (swapLayout || isCameraFullscreen || isMobile) {
       position = {
         x: 0,
         y: 0,
@@ -445,7 +445,7 @@ class WebcamDraggable extends PureComponent {
           onStart={this.handleWebcamDragStart}
           onStop={this.handleWebcamDragStop}
           onMouseDown={e => e.preventDefault()}
-          disabled={swapLayout || isCameraFullscreen || BROWSER_ISMOBILE || resizing}
+          disabled={swapLayout || isCameraFullscreen || isMobile || resizing}
           position={position}
         >
           <Resizable
diff --git a/bigbluebutton-html5/imports/ui/components/nav-bar/settings-dropdown/container.jsx b/bigbluebutton-html5/imports/ui/components/nav-bar/settings-dropdown/container.jsx
index e21f0ccf0c8b305eddf49080ff6ccb0afb7c625b..6c7b282fad1e3b94e352945ff1b8e9037246b37f 100755
--- a/bigbluebutton-html5/imports/ui/components/nav-bar/settings-dropdown/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/nav-bar/settings-dropdown/container.jsx
@@ -1,14 +1,15 @@
 import React from 'react';
 import { withTracker } from 'meteor/react-meteor-data';
-import browser from 'browser-detect';
+import deviceInfo from '/imports/utils/deviceInfo';
+import browserInfo from '/imports/utils/browserInfo';
 import SettingsDropdown from './component';
 import FullscreenService from '../../fullscreen-button/service';
 import { meetingIsBreakout } from '/imports/ui/components/app/service';
 
-const BROWSER_RESULTS = browser();
-const isSafari = BROWSER_RESULTS.name === 'safari';
-const isIphone = (navigator.userAgent.match(/iPhone/i)) ? true : false;
-const noIOSFullscreen = ((isSafari && BROWSER_RESULTS.versionNumber < 12) || isIphone) ? true : false;
+const { isIphone } = deviceInfo;
+const { isSafari, isValidSafariVersion } = browserInfo;
+
+const noIOSFullscreen = !!(((isSafari && !isValidSafariVersion) || isIphone));
 
 const SettingsDropdownContainer = props => (
   <SettingsDropdown {...props} />
diff --git a/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/component.jsx b/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/component.jsx
index 473ec85b88e13e09fcf1fed2b239c7b19a8f43e7..11eeab3ec6d2fdbcda9f23184cd0ae5811c7c894 100755
--- a/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/presentation/presentation-toolbar/component.jsx
@@ -1,7 +1,7 @@
 import React, { PureComponent } from 'react';
 import PropTypes from 'prop-types';
 import { defineMessages, injectIntl } from 'react-intl';
-import browser from 'browser-detect';
+import deviceInfo from '/imports/utils/deviceInfo';
 import injectWbResizeEvent from '/imports/ui/components/presentation/resize-wrapper/component';
 import Button from '/imports/ui/components/button/component';
 import { HUNDRED_PERCENT, MAX_PERCENT, STEP } from '/imports/utils/slideCalcUtils';
@@ -225,9 +225,7 @@ class PresentationToolbar extends PureComponent {
       currentSlide,
     } = this.props;
 
-    const BROWSER_RESULTS = browser();
-    const isMobileBrowser = BROWSER_RESULTS.mobile
-      || BROWSER_RESULTS.os.includes('Android');
+    const { isMobile } = deviceInfo;
 
     const startOfSlides = !(currentSlideNum > 1);
     const endOfSlides = !(currentSlideNum < numberOfSlides);
@@ -314,7 +312,7 @@ class PresentationToolbar extends PureComponent {
         {
           <div className={styles.presentationZoomControls}>
             {
-              !isMobileBrowser
+              !isMobile
                 ? (
                   <TooltipContainer>
                     <ZoomTool
diff --git a/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/component.jsx b/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/component.jsx
index 2c7053c0c406011f016d48b59447cb9a0f07d122..09b31515ac47b1282186dde0e5237872712f32e6 100755
--- a/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/component.jsx
@@ -2,6 +2,7 @@ import React, { Component } from 'react';
 import PropTypes from 'prop-types';
 import { defineMessages, injectIntl } from 'react-intl';
 import cx from 'classnames';
+import deviceInfo from '/imports/utils/deviceInfo';
 import Button from '/imports/ui/components/button/component';
 import Checkbox from '/imports/ui/components/checkbox/component';
 import Icon from '/imports/ui/components/icon/component';
@@ -10,15 +11,10 @@ import update from 'immutability-helper';
 import logger from '/imports/startup/client/logger';
 import { notify } from '/imports/ui/services/notification';
 import { toast } from 'react-toastify';
-import browser from 'browser-detect';
 import _ from 'lodash';
 import { styles } from './styles';
 
-const BROWSER_RESULTS = browser();
-const isMobileBrowser = (BROWSER_RESULTS ? BROWSER_RESULTS.mobile : false)
-  || (BROWSER_RESULTS && BROWSER_RESULTS.os
-    ? BROWSER_RESULTS.os.includes('Android') // mobile flag doesn't always work
-    : false);
+const { isMobile } = deviceInfo;
 
 const propTypes = {
   intl: PropTypes.object.isRequired,
@@ -968,7 +964,7 @@ class PresentationUploader extends Component {
             {`${intl.formatMessage(intlMessages.message)}`}
           </div>
           {this.renderPresentationList()}
-          {isMobileBrowser ? this.renderPicDropzone() : null}
+          {isMobile ? this.renderPicDropzone() : null}
           {this.renderDropzone()}
         </div>
       </div>
diff --git a/bigbluebutton-html5/imports/ui/components/shortcut-help/component.jsx b/bigbluebutton-html5/imports/ui/components/shortcut-help/component.jsx
index a55b0c8417c270abecebd39f97bce1065a73f05e..fa54f28de77071c313845ae75401a0d680cdf9bc 100644
--- a/bigbluebutton-html5/imports/ui/components/shortcut-help/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/shortcut-help/component.jsx
@@ -1,7 +1,8 @@
 import React from 'react';
 import PropTypes from 'prop-types';
 import { defineMessages, injectIntl } from 'react-intl';
-import browser from 'browser-detect';
+import browserInfo from '/imports/utils/browserInfo';
+import deviceInfo from '/imports/utils/deviceInfo';
 import Modal from '/imports/ui/components/modal/simple/component';
 import _ from 'lodash';
 import { styles } from './styles';
@@ -103,32 +104,32 @@ const CHAT_ENABLED = CHAT_CONFIG.enabled;
 
 const ShortcutHelpComponent = (props) => {
   const { intl, shortcuts } = props;
-  const { name, os } = browser();
+  const { browserName } = browserInfo;
+  const { isIos, isMacos } = deviceInfo;
 
   let accessMod = null;
 
   // different browsers use different access modifier keys
   // on different systems when using accessKey property.
   // Overview how different browsers behave: https://www.w3schools.com/jsref/prop_html_accesskey.asp
-  switch (name) {
-    case 'chrome':
-    case 'edge':
+  switch (browserName) {
+    case 'Chrome':
+    case 'Microsoft Edge':
       accessMod = 'Alt';
       break;
-    case 'firefox':
+    case 'Firefox':
       accessMod = 'Alt + Shift';
       break;
-    case 'safari':
-    case 'crios':
-    case 'fxios':
-      accessMod = 'Control + Alt';
-      break;
     default:
       break;
   }
 
+  // all Browsers on iOS are using Control + Alt as access modifier
+  if (isIos) {
+    accessMod = 'Control + Alt';
+  }
   // all Browsers on MacOS are using Control + Option as access modifier
-  if (os.includes('OS X 10')) {
+  if (isMacos) {
     accessMod = 'Control + Option';
   }
 
diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-dropdown/theteredDropdown/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-dropdown/theteredDropdown/component.jsx
index 542cb560af113c4e85b7b3521d30f261cf445404..4ec2b441eb35d382936b16bdfecef2e716e704e1 100644
--- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-dropdown/theteredDropdown/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-list-item/user-dropdown/theteredDropdown/component.jsx
@@ -2,8 +2,8 @@ import React, { Component } from 'react';
 import PropTypes from 'prop-types';
 import { findDOMNode } from 'react-dom';
 import cx from 'classnames';
-import { isMobile } from 'react-device-detect';
 import { defineMessages, injectIntl } from 'react-intl';
+import deviceInfo from '/imports/utils/deviceInfo';
 import Button from '/imports/ui/components/button/component';
 import screenreaderTrap from 'makeup-screenreader-trap';
 import TetherComponent from 'react-tether';
@@ -180,6 +180,8 @@ class Dropdown extends Component {
 
     const { isOpen } = this.state;
 
+    const { isMobile } = deviceInfo;
+
     let trigger = children.find(x => x.type === DropdownTrigger);
     let content = children.find(x => x.type === DropdownContent);
 
diff --git a/bigbluebutton-html5/imports/ui/components/video-preview/component.jsx b/bigbluebutton-html5/imports/ui/components/video-preview/component.jsx
index fed89b40d6957a069acfb81d6c3a3a7236a5d0c7..cc57006b6925438a55a0be92d8d61e5204a7dc8b 100755
--- a/bigbluebutton-html5/imports/ui/components/video-preview/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/video-preview/component.jsx
@@ -4,10 +4,9 @@ import {
   defineMessages, injectIntl, FormattedMessage,
 } from 'react-intl';
 import Button from '/imports/ui/components/button/component';
-// import { notify } from '/imports/ui/services/notification';
 import logger from '/imports/startup/client/logger';
 import Modal from '/imports/ui/components/modal/simple/component';
-import browser from 'browser-detect';
+import browserInfo from '/imports/utils/browserInfo';
 import cx from 'classnames';
 import Service from './service';
 import VideoService from '../video-provider/service';
@@ -652,9 +651,11 @@ class VideoPreview extends Component {
 
     const shared = sharedDevices.includes(webcamDeviceId);
 
+    const { isEdge, isIe } = browserInfo;
+
     return (
       <div>
-        {browser().name === 'edge' || browser().name === 'ie' ? (
+        {isEdge || isIe ? (
           <p className={styles.browserWarning}>
             <FormattedMessage
               id="app.audioModal.unsupportedBrowserLabel"
diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/service.js b/bigbluebutton-html5/imports/ui/components/video-provider/service.js
index 05e0196d817ede7a0453919c9f7f54289e631200..40de0d01295a35034f0b69d2ac2b7c97624b4e84 100755
--- a/bigbluebutton-html5/imports/ui/components/video-provider/service.js
+++ b/bigbluebutton-html5/imports/ui/components/video-provider/service.js
@@ -9,7 +9,8 @@ import UserListService from '/imports/ui/components/user-list/service';
 import { makeCall } from '/imports/ui/services/api';
 import { notify } from '/imports/ui/services/notification';
 import { monitorVideoConnection } from '/imports/utils/stats';
-import browser from 'browser-detect';
+import deviceInfo from '/imports/utils/deviceInfo';
+import browserInfo from '/imports/utils/browserInfo';
 import getFromUserSettings from '/imports/ui/services/users-settings';
 import VideoPreviewService from '../video-preview/service';
 import Storage from '/imports/ui/services/storage/session';
@@ -70,9 +71,9 @@ class VideoService {
       pageSize: 0,
     });
     this.userParameterProfile = null;
-    const BROWSER_RESULTS = browser();
-    this.isMobile = BROWSER_RESULTS.mobile || BROWSER_RESULTS.os.includes('Android');
-    this.isSafari = BROWSER_RESULTS.name === 'safari';
+
+    this.isMobile = deviceInfo.isMobile;
+    this.isSafari = browserInfo.isSafari;
     this.numberOfDevices = 0;
 
     this.record = null;
diff --git a/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/component.jsx b/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/component.jsx
index 21bb8333bdd9683277a297479f1ba7b4685cf3db..0c6d51e1908ce982fd325947f46eee6cb2ab166f 100755
--- a/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/video-provider/video-list/video-list-item/component.jsx
@@ -1,5 +1,5 @@
 import React, { Component } from 'react';
-import browser from 'browser-detect';
+import browserInfo from '/imports/utils/browserInfo';
 import { Meteor } from 'meteor/meteor';
 import PropTypes from 'prop-types';
 import _ from 'lodash';
@@ -12,7 +12,6 @@ import DropdownListTitle from '/imports/ui/components/dropdown/list/title/compon
 import DropdownListSeparator from '/imports/ui/components/dropdown/list/separator/component';
 import DropdownListItem from '/imports/ui/components/dropdown/list/item/component';
 import Icon from '/imports/ui/components/icon/component';
-import logger from '/imports/startup/client/logger';
 import FullscreenService from '/imports/ui/components/fullscreen-button/service';
 import FullscreenButtonContainer from '/imports/ui/components/fullscreen-button/container';
 import { styles } from '../styles';
@@ -44,7 +43,7 @@ class VideoListItem extends Component {
     this.onStreamStateChange = this.onStreamStateChange.bind(this);
   }
 
-  onStreamStateChange (e) {
+  onStreamStateChange(e) {
     const { streamState } = e.detail;
     const { isStreamHealthy } = this.state;
 
@@ -173,26 +172,29 @@ class VideoListItem extends Component {
       numOfStreams,
       webcamDraggableState,
       swapLayout,
-      mirrored
+      mirrored,
     } = this.props;
     const availableActions = this.getAvailableActions();
     const enableVideoMenu = Meteor.settings.public.kurento.enableVideoMenu || false;
     const shouldRenderReconnect = !isStreamHealthy && videoIsReady;
 
-    const result = browser();
-    const isFirefox = (result && result.name) ? result.name.includes('firefox') : false;
+    const { isFirefox } = browserInfo;
 
     return (
-      <div data-test={voiceUser.talking ? 'webcamItemTalkingUser' : 'webcamItem'} className={cx({
-        [styles.content]: true,
-        [styles.talking]: voiceUser.talking,
-      })}
+      <div
+        data-test={voiceUser.talking ? 'webcamItemTalkingUser' : 'webcamItem'}
+        className={cx({
+          [styles.content]: true,
+          [styles.talking]: voiceUser.talking,
+        })}
       >
         {
-          !videoIsReady &&
+          !videoIsReady
+            && (
             <div data-test="webcamConnecting" className={styles.connecting}>
               <span className={styles.loadingText}>{name}</span>
             </div>
+            )
 
         }
 
@@ -223,39 +225,41 @@ class VideoListItem extends Component {
           />
           {videoIsReady && this.renderFullscreenButton()}
         </div>
-        { videoIsReady &&
+        { videoIsReady
+          && (
           <div className={styles.info}>
-          {enableVideoMenu && availableActions.length >= 3
-            ? (
-              <Dropdown className={isFirefox ? styles.dropdownFireFox : styles.dropdown}>
-                <DropdownTrigger className={styles.dropdownTrigger}>
-                  <span>{name}</span>
-                </DropdownTrigger>
-                <DropdownContent placement="top left" className={styles.dropdownContent}>
-                  <DropdownList className={styles.dropdownList}>
-                    {availableActions}
-                  </DropdownList>
-                </DropdownContent>
-              </Dropdown>
-            )
-            : (
-              <div className={isFirefox ? styles.dropdownFireFox
-                : styles.dropdown}
-              >
-                <span className={cx({
-                  [styles.userName]: true,
-                  [styles.noMenu]: numOfStreams < 3,
-                })}
+            {enableVideoMenu && availableActions.length >= 3
+              ? (
+                <Dropdown className={isFirefox ? styles.dropdownFireFox : styles.dropdown}>
+                  <DropdownTrigger className={styles.dropdownTrigger}>
+                    <span>{name}</span>
+                  </DropdownTrigger>
+                  <DropdownContent placement="top left" className={styles.dropdownContent}>
+                    <DropdownList className={styles.dropdownList}>
+                      {availableActions}
+                    </DropdownList>
+                  </DropdownContent>
+                </Dropdown>
+              )
+              : (
+                <div className={isFirefox ? styles.dropdownFireFox
+                  : styles.dropdown}
                 >
-                  {name}
-                </span>
-              </div>
-            )
+                  <span className={cx({
+                    [styles.userName]: true,
+                    [styles.noMenu]: numOfStreams < 3,
+                  })}
+                  >
+                    {name}
+                  </span>
+                </div>
+              )
           }
-          {voiceUser.muted && !voiceUser.listenOnly ? <Icon className={styles.muted} iconName="unmute_filled" /> : null}
-          {voiceUser.listenOnly ? <Icon className={styles.voice} iconName="listen" /> : null}
-          {voiceUser.joined && !voiceUser.muted ? <Icon className={styles.voice} iconName="unmute" /> : null}
-        </div>
+            {voiceUser.muted && !voiceUser.listenOnly ? <Icon className={styles.muted} iconName="unmute_filled" /> : null}
+            {voiceUser.listenOnly ? <Icon className={styles.voice} iconName="listen" /> : null}
+            {voiceUser.joined && !voiceUser.muted ? <Icon className={styles.voice} iconName="unmute" /> : null}
+          </div>
+          )
         }
       </div>
     );
diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/component.jsx
index f776cc807be11d89199614684374b80706406e4e..a5ce6bfb40e44f278da5562150810c645dc44296 100755
--- a/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/whiteboard/whiteboard-toolbar/component.jsx
@@ -3,7 +3,7 @@ import PropTypes from 'prop-types';
 import cx from 'classnames';
 import { HEXToINTColor, INTToHEXColor } from '/imports/utils/hexInt';
 import { defineMessages, injectIntl } from 'react-intl';
-import browser from 'browser-detect';
+import browserInfo from '/imports/utils/browserInfo';
 import { noop } from 'lodash';
 import KEY_CODES from '/imports/utils/keyCodes';
 import injectWbResizeEvent from '/imports/ui/components/presentation/resize-wrapper/component';
@@ -64,7 +64,7 @@ const intlMessages = defineMessages({
   },
 });
 
-const isEdge = browser().name === 'edge';
+const { isEdge } = browserInfo;
 const runExceptInEdge = fn => (isEdge ? noop : fn);
 
 class WhiteboardToolbar extends Component {
diff --git a/bigbluebutton-html5/imports/utils/browserInfo.js b/bigbluebutton-html5/imports/utils/browserInfo.js
new file mode 100755
index 0000000000000000000000000000000000000000..2459cb8af63b67b3125677298f2b2499fd3744db
--- /dev/null
+++ b/bigbluebutton-html5/imports/utils/browserInfo.js
@@ -0,0 +1,29 @@
+import Bowser from 'bowser';
+
+const BOWSER_RESULTS = Bowser.parse(window.navigator.userAgent);
+
+const isChrome = BOWSER_RESULTS.browser.name === 'Chrome';
+const isSafari = BOWSER_RESULTS.browser.name === 'Safari';
+const isEdge = BOWSER_RESULTS.browser.name === 'Microsoft Edge';
+const isIe = BOWSER_RESULTS.browser.name === 'Internet Explorer';
+const isFirefox = BOWSER_RESULTS.browser.name === 'Firefox';
+
+const browserName = BOWSER_RESULTS.browser.name;
+const versionNumber = BOWSER_RESULTS.browser.version;
+
+const isValidSafariVersion = Bowser.getParser(window.navigator.userAgent).satisfies({
+  safari: '>12',
+});
+
+const browserInfo = {
+  isChrome,
+  isSafari,
+  isEdge,
+  isIe,
+  isFirefox,
+  browserName,
+  versionNumber,
+  isValidSafariVersion,
+};
+
+export default browserInfo;
diff --git a/bigbluebutton-html5/imports/utils/deviceInfo.js b/bigbluebutton-html5/imports/utils/deviceInfo.js
index a5dfb72da1d334e4b44f09e6cdaa0bbbdce5bb3a..9471862c58986673f3f5de47cfc84688b4538924 100755
--- a/bigbluebutton-html5/imports/utils/deviceInfo.js
+++ b/bigbluebutton-html5/imports/utils/deviceInfo.js
@@ -1,19 +1,34 @@
-const deviceInfo = {
-  type() {
-    // Listing of Device Viewport sizes, Updated : March 25th, 2018
-    // http://mediag.com/news/popular-screen-resolutions-designing-for-all/
-    const MAX_PHONE_SHORT_SIDE = 480;
+import Bowser from 'bowser';
 
-    const smallSide = window.screen.width < window.screen.height
-      ? window.screen.width
-      : window.screen.height;
+const BOWSER_RESULTS = Bowser.parse(window.navigator.userAgent);
 
-    return {
-      isPhone: smallSide <= MAX_PHONE_SHORT_SIDE,
-    };
-  },
-  hasMediaDevices: !!navigator.mediaDevices,
-};
+const isPhone = BOWSER_RESULTS.platform.type === 'mobile';
+// we need a 'hack' to correctly detect ipads with ios > 13
+const isTablet = BOWSER_RESULTS.platform.type === 'tablet' || (BOWSER_RESULTS.os.name === 'macOS' && window.navigator.maxTouchPoints > 0);
+const isMobile = isPhone || isTablet;
+const hasMediaDevices = !!navigator.mediaDevices;
+const osName = BOWSER_RESULTS.os.name;
+const osVersion = BOWSER_RESULTS.os.version;
+const isIos = osName === 'iOS';
+const isMacos = osName === 'macOS';
+const isIphone = !!(window.navigator.userAgent.match(/iPhone/i));
+
+const SUPPORTED_IOS_VERSION = 12.2;
+const isIosVersionSupported = () => parseFloat(osVersion) >= SUPPORTED_IOS_VERSION;
 
+const isPortrait = () => window.innerHeight > window.innerWidth;
+
+const deviceInfo = {
+  isTablet,
+  isPhone,
+  isMobile,
+  hasMediaDevices,
+  osName,
+  isPortrait,
+  isIos,
+  isMacos,
+  isIphone,
+  isIosVersionSupported,
+};
 
 export default deviceInfo;
diff --git a/bigbluebutton-html5/package-lock.json b/bigbluebutton-html5/package-lock.json
index af01ac900518db48e3fcb84f2f0b244e1c94ac9e..8762657cb4f97eec3812ec6d7a21eab2493360c0 100644
--- a/bigbluebutton-html5/package-lock.json
+++ b/bigbluebutton-html5/package-lock.json
@@ -2015,6 +2015,11 @@
         "inherits": "~2.0.0"
       }
     },
+    "bowser": {
+      "version": "2.11.0",
+      "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.11.0.tgz",
+      "integrity": "sha512-AlcaJBi/pqqJBIQ8U9Mcpc9i8Aqxn88Skv5d+xBX006BY5u8N3mGLHa5Lgppa7L/HfwgwLgZ6NYs+Ag6uUmJRA=="
+    },
     "brace-expansion": {
       "version": "1.1.11",
       "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
@@ -2044,14 +2049,6 @@
         "@browser-bunyan/levels": "^1.6.0"
       }
     },
-    "browser-detect": {
-      "version": "0.2.28",
-      "resolved": "https://registry.npmjs.org/browser-detect/-/browser-detect-0.2.28.tgz",
-      "integrity": "sha512-KeWGHqYQmHDkCFG2dIiX/2wFUgqevbw/rd6wNi9N6rZbaSJFtG5kel0HtprRwCGp8sqpQP79LzDJXf/WCx4WAw==",
-      "requires": {
-        "core-js": "^2.5.7"
-      }
-    },
     "browser-or-node": {
       "version": "1.2.1",
       "resolved": "https://registry.npmjs.org/browser-or-node/-/browser-or-node-1.2.1.tgz",
@@ -9283,14 +9280,6 @@
         "tinycolor2": "^1.4.1"
       }
     },
-    "react-device-detect": {
-      "version": "1.17.0",
-      "resolved": "https://registry.npmjs.org/react-device-detect/-/react-device-detect-1.17.0.tgz",
-      "integrity": "sha512-bBblIStwpHmoS281JFIVqeimcN3LhpoP5YKDWzxQdBIUP8S2xPvHDgizLDhUq2ScguLfVPmwfF5y268EEQR60w==",
-      "requires": {
-        "ua-parser-js": "^0.7.24"
-      }
-    },
     "react-dom": {
       "version": "16.14.0",
       "resolved": "https://registry.npmjs.org/react-dom/-/react-dom-16.14.0.tgz",
@@ -11061,11 +11050,6 @@
         "is-typedarray": "^1.0.0"
       }
     },
-    "ua-parser-js": {
-      "version": "0.7.24",
-      "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-0.7.24.tgz",
-      "integrity": "sha512-yo+miGzQx5gakzVK3QFfN0/L9uVhosXBBO7qmnk7c2iw1IhL212wfA3zbnI54B0obGwC/5NWub/iT9sReMx+Fw=="
-    },
     "unbzip2-stream": {
       "version": "1.4.3",
       "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz",
diff --git a/bigbluebutton-html5/package.json b/bigbluebutton-html5/package.json
index 257e4063c26cc83f75cc3fbaba9bb24ad1f46868..4fb265608a5c87b607044588071492d538efbde2 100755
--- a/bigbluebutton-html5/package.json
+++ b/bigbluebutton-html5/package.json
@@ -35,8 +35,8 @@
     "autoprefixer": "~9.3.1",
     "axios": "^0.21.1",
     "babel-runtime": "~6.26.0",
+    "bowser": "^2.11.0",
     "browser-bunyan": "^1.6.3",
-    "browser-detect": "^0.2.28",
     "classnames": "^2.2.6",
     "clipboard": "^2.0.8",
     "crypto-js": "^4.0.0",
@@ -58,7 +58,6 @@
     "react": "^16.14.0",
     "react-autosize-textarea": "^5.0.1",
     "react-color": "^2.19.3",
-    "react-device-detect": "^1.17.0",
     "react-dom": "^16.14.0",
     "react-draggable": "^3.3.2",
     "react-dropzone": "^7.0.1",