diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/SelectRandomViewerReqMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/SelectRandomViewerReqMsgHdlr.scala
new file mode 100644
index 0000000000000000000000000000000000000000..904d88028bba4b00cae0a44c0b4dc579e4ba78f4
--- /dev/null
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/SelectRandomViewerReqMsgHdlr.scala
@@ -0,0 +1,42 @@
+package org.bigbluebutton.core.apps.users
+
+import org.bigbluebutton.common2.msgs._
+import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
+import org.bigbluebutton.core.models.{ UserState, Users2x }
+import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
+import org.bigbluebutton.core2.MeetingStatus2x
+import scala.util.Random
+
+trait SelectRandomViewerReqMsgHdlr extends RightsManagementTrait {
+  this: UsersApp =>
+
+  val outGW: OutMsgRouter
+
+  def handleSelectRandomViewerReqMsg(msg: SelectRandomViewerReqMsg): Unit = {
+    log.debug("Received SelectRandomViewerReqMsg {}", SelectRandomViewerReqMsg)
+
+    def broadcastEvent(msg: SelectRandomViewerReqMsg, selectedUser: UserState): Unit = {
+      val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, msg.header.userId)
+      val envelope = BbbCoreEnvelope(SelectRandomViewerRespMsg.NAME, routing)
+      val header = BbbClientMsgHeader(SelectRandomViewerRespMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
+
+      val body = SelectRandomViewerRespMsgBody(msg.header.userId, selectedUser.intId)
+      val event = SelectRandomViewerRespMsg(header, body)
+      val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
+      outGW.send(msgEvent)
+    }
+
+    if (permissionFailed(PermissionCheck.GUEST_LEVEL, PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
+      val meetingId = liveMeeting.props.meetingProp.intId
+      val reason = "No permission to select random user."
+      PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
+    } else {
+      val users = Users2x.findViewers(liveMeeting.users2x)
+      val randNum = new scala.util.Random
+
+      if (users.size > 0) {
+        broadcastEvent(msg, users(randNum.nextInt(users.size)))
+      }
+    }
+  }
+}
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/UsersApp.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/UsersApp.scala
index dc13f8c106867dae8fe4e552351a0a0a7118d250..040aa8f766782dde2ef9f1be600f652b353387e8 100755
--- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/UsersApp.scala
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/UsersApp.scala
@@ -145,6 +145,7 @@ class UsersApp(
   with SendRecordingTimerInternalMsgHdlr
   with UpdateWebcamsOnlyForModeratorCmdMsgHdlr
   with GetRecordingStatusReqMsgHdlr
+  with SelectRandomViewerReqMsgHdlr
   with GetWebcamsOnlyForModeratorReqMsgHdlr
   with AssignPresenterReqMsgHdlr
   with EjectDuplicateUserReqMsgHdlr
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/Users2x.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/Users2x.scala
index 3e00cca80fe660be7b8fd03bb257915af567a110..826e83cd8cdbb9e7d7eeec3956dbe1f720515e24 100755
--- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/Users2x.scala
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/Users2x.scala
@@ -59,6 +59,10 @@ object Users2x {
     users.toVector.filter(u => !u.presenter)
   }
 
+  def findViewers(users: Users2x): Vector[UserState] = {
+    users.toVector.filter(u => u.role == Roles.VIEWER_ROLE)
+  }
+
   def updateLastUserActivity(users: Users2x, u: UserState): UserState = {
     val newUserState = modify(u)(_.lastActivityTime).setTo(TimeUtil.timeNowInMs())
     users.save(newUserState)
@@ -241,6 +245,7 @@ class Users2x {
       }
     }
   }
+
 }
 
 case class OldPresenter(userId: String, changedPresenterOn: Long)
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/pubsub/senders/ReceivedJsonMsgHandlerActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/pubsub/senders/ReceivedJsonMsgHandlerActor.scala
index 683f07d0b0aabcd85962600150d2e8d9751bb5d4..5d9de23e6a4b3421d1fc5d8637bc61f73e068616 100755
--- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/pubsub/senders/ReceivedJsonMsgHandlerActor.scala
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/pubsub/senders/ReceivedJsonMsgHandlerActor.scala
@@ -103,6 +103,8 @@ class ReceivedJsonMsgHandlerActor(
         routeGenericMsg[GetPresenterGroupReqMsg](envelope, jsonNode)
       case UserActivitySignCmdMsg.NAME =>
         routeGenericMsg[UserActivitySignCmdMsg](envelope, jsonNode)
+      case SelectRandomViewerReqMsg.NAME =>
+        routeGenericMsg[SelectRandomViewerReqMsg](envelope, jsonNode)
 
       // Poll
       case StartCustomPollReqMsg.NAME =>
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala
index c280dbc2bf83ca23004c76ad2fe5170e9c9ae5f0..b410bd7ad35a1cee64dfcf3de9dab98547ce9213 100755
--- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala
@@ -308,6 +308,7 @@ class MeetingActor(
       case m: UpdateWebcamsOnlyForModeratorCmdMsg => usersApp.handleUpdateWebcamsOnlyForModeratorCmdMsg(m)
       case m: GetRecordingStatusReqMsg            => usersApp.handleGetRecordingStatusReqMsg(m)
       case m: ChangeUserEmojiCmdMsg               => handleChangeUserEmojiCmdMsg(m)
+      case m: SelectRandomViewerReqMsg            => usersApp.handleSelectRandomViewerReqMsg(m)
 
       // Client requested to eject user
       case m: EjectUserFromMeetingCmdMsg =>
diff --git a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/UsersMgs.scala b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/UsersMgs.scala
index 3d62d9dfa43bc15dc61f60809c548acba2d3599d..776ae62ea105a618366e6653e4a564e2314734ad 100755
--- a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/UsersMgs.scala
+++ b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/UsersMgs.scala
@@ -394,3 +394,17 @@ case class UserInactivityInspectMsgBody(meetingId: String, responseDelay: Long)
 object UserActivitySignCmdMsg { val NAME = "UserActivitySignCmdMsg" }
 case class UserActivitySignCmdMsg(header: BbbClientMsgHeader, body: UserActivitySignCmdMsgBody) extends StandardMsg
 case class UserActivitySignCmdMsgBody(userId: String)
+
+/**
+ * Sent from client to randomly select a viewer
+ */
+object SelectRandomViewerReqMsg { val NAME = "SelectRandomViewerReqMsg" }
+case class SelectRandomViewerReqMsg(header: BbbClientMsgHeader, body: SelectRandomViewerReqMsgBody) extends StandardMsg
+case class SelectRandomViewerReqMsgBody(requestedBy: String)
+
+/**
+ * Response to request for a random viewer
+ */
+object SelectRandomViewerRespMsg { val NAME = "SelectRandomViewerRespMsg" }
+case class SelectRandomViewerRespMsg(header: BbbClientMsgHeader, body: SelectRandomViewerRespMsgBody) extends StandardMsg
+case class SelectRandomViewerRespMsgBody(requestedBy: String, selectedUserId: String)
diff --git a/bigbluebutton-html5/imports/api/meetings/server/eventHandlers.js b/bigbluebutton-html5/imports/api/meetings/server/eventHandlers.js
index 5d08df5ac10c0511ad7d1390f0b02aba7ee24bb7..81f0ef206a42523e373fd5b1f58f316a19b870b6 100644
--- a/bigbluebutton-html5/imports/api/meetings/server/eventHandlers.js
+++ b/bigbluebutton-html5/imports/api/meetings/server/eventHandlers.js
@@ -9,6 +9,7 @@ import handleRecordingStatusChange from './handlers/recordingStatusChange';
 import handleRecordingTimerChange from './handlers/recordingTimerChange';
 import handleTimeRemainingUpdate from './handlers/timeRemainingUpdate';
 import handleChangeWebcamOnlyModerator from './handlers/webcamOnlyModerator';
+import handleSelectRandomViewer from './handlers/selectRandomViewer';
 
 RedisPubSub.on('MeetingCreatedEvtMsg', handleMeetingCreation);
 RedisPubSub.on('SyncGetMeetingInfoRespMsg', handleGetAllMeetings);
@@ -21,3 +22,4 @@ RedisPubSub.on('UpdateRecordingTimerEvtMsg', handleRecordingTimerChange);
 RedisPubSub.on('WebcamsOnlyForModeratorChangedEvtMsg', handleChangeWebcamOnlyModerator);
 RedisPubSub.on('GetLockSettingsRespMsg', handleMeetingLocksChange);
 RedisPubSub.on('MeetingTimeRemainingUpdateEvtMsg', handleTimeRemainingUpdate);
+RedisPubSub.on('SelectRandomViewerRespMsg', handleSelectRandomViewer);
diff --git a/bigbluebutton-html5/imports/api/meetings/server/handlers/selectRandomViewer.js b/bigbluebutton-html5/imports/api/meetings/server/handlers/selectRandomViewer.js
new file mode 100644
index 0000000000000000000000000000000000000000..51c1d76f4a016ec254e9b8b2ed5506f08d204b99
--- /dev/null
+++ b/bigbluebutton-html5/imports/api/meetings/server/handlers/selectRandomViewer.js
@@ -0,0 +1,13 @@
+import { check } from 'meteor/check';
+import updateRandomViewer from '../modifiers/updateRandomViewer';
+
+export default function randomlySelectedUser({ header, body }) {
+  const { selectedUserId, requestedBy } = body;
+  const { meetingId } = header;
+
+  check(meetingId, String);
+  check(requestedBy, String);
+  check(selectedUserId, String);
+
+  updateRandomViewer(meetingId, selectedUserId, requestedBy);
+}
diff --git a/bigbluebutton-html5/imports/api/meetings/server/methods.js b/bigbluebutton-html5/imports/api/meetings/server/methods.js
index 960ef8082efae97b4e628dc50ec470774f70c746..df2c1e468f32a3a3e9196177bc234dc0f2773a61 100755
--- a/bigbluebutton-html5/imports/api/meetings/server/methods.js
+++ b/bigbluebutton-html5/imports/api/meetings/server/methods.js
@@ -4,6 +4,7 @@ import toggleRecording from './methods/toggleRecording';
 import transferUser from './methods/transferUser';
 import toggleLockSettings from './methods/toggleLockSettings';
 import toggleWebcamsOnlyForModerator from './methods/toggleWebcamsOnlyForModerator';
+import clearRandomlySelectedUser from './methods/clearRandomlySelectedUser';
 
 Meteor.methods({
   endMeeting,
@@ -11,4 +12,5 @@ Meteor.methods({
   toggleLockSettings,
   transferUser,
   toggleWebcamsOnlyForModerator,
+  clearRandomlySelectedUser,
 });
diff --git a/bigbluebutton-html5/imports/api/meetings/server/methods/clearRandomlySelectedUser.js b/bigbluebutton-html5/imports/api/meetings/server/methods/clearRandomlySelectedUser.js
new file mode 100644
index 0000000000000000000000000000000000000000..1cd5303db8732836d76864d7ff95f0253e8e148f
--- /dev/null
+++ b/bigbluebutton-html5/imports/api/meetings/server/methods/clearRandomlySelectedUser.js
@@ -0,0 +1,26 @@
+import Logger from '/imports/startup/server/logger';
+import Meetings from '/imports/api/meetings';
+import { extractCredentials } from '/imports/api/common/server/helpers';
+
+export default function clearRandomlySelectedUser() {
+  const { meetingId, requesterUserId } = extractCredentials(this.userId);
+
+  const selector = {
+    meetingId,
+  };
+
+  const modifier = {
+    $set: {
+      randomlySelectedUser: '',
+    },
+  };
+
+  try {
+    const { insertedId } = Meetings.update(selector, modifier);
+    if (insertedId) {
+      Logger.info(`Cleared randomly selected user from meeting=${meetingId} by id=${requesterUserId}`);
+    }
+  } catch (err) {
+    Logger.error(`Clearing randomly selected user : ${err}`);
+  }
+}
diff --git a/bigbluebutton-html5/imports/api/meetings/server/modifiers/addMeeting.js b/bigbluebutton-html5/imports/api/meetings/server/modifiers/addMeeting.js
index d4186730315beba3a3fa15e450257b1e279a1ed8..389d816a62a1037b4c7266b7a900e32cdd3b7874 100755
--- a/bigbluebutton-html5/imports/api/meetings/server/modifiers/addMeeting.js
+++ b/bigbluebutton-html5/imports/api/meetings/server/modifiers/addMeeting.js
@@ -146,6 +146,7 @@ export default function addMeeting(meeting) {
       meetingId,
       meetingEnded,
       publishedPoll: false,
+      randomlySelectedUser: '',
     }, flat(newMeeting, {
       safe: true,
     })),
diff --git a/bigbluebutton-html5/imports/api/meetings/server/modifiers/updateRandomViewer.js b/bigbluebutton-html5/imports/api/meetings/server/modifiers/updateRandomViewer.js
new file mode 100644
index 0000000000000000000000000000000000000000..b4a638572722055fda5c12829868b1dcf23f767b
--- /dev/null
+++ b/bigbluebutton-html5/imports/api/meetings/server/modifiers/updateRandomViewer.js
@@ -0,0 +1,28 @@
+import Meetings from '/imports/api/meetings';
+import Logger from '/imports/startup/server/logger';
+import { check } from 'meteor/check';
+
+export default function updateRandomUser(meetingId, userId, requesterId) {
+  check(meetingId, String);
+  check(userId, String);
+  check(requesterId, String);
+
+  const selector = {
+    meetingId,
+  };
+
+  const modifier = {
+    $set: {
+      randomlySelectedUser: userId,
+    },
+  };
+
+  try {
+    const { insertedId } = Meetings.upsert(selector, modifier);
+    if (insertedId) {
+      Logger.info(`Set randomly selected userId=${userId} by requesterId=${requesterId} in meeitingId=${meetingId}`);
+    }
+  } catch (err) {
+    Logger.error(`Setting randomly selected userId=${userId} by requesterId=${requesterId} in meetingId=${meetingId}`);
+  }
+}
diff --git a/bigbluebutton-html5/imports/api/users/server/methods.js b/bigbluebutton-html5/imports/api/users/server/methods.js
index 98aa67982f0e58ce2ce66cfd2c52927127b70d14..9ab6ccb80d4621bf041dbed2c03d154ce3f9dc01 100644
--- a/bigbluebutton-html5/imports/api/users/server/methods.js
+++ b/bigbluebutton-html5/imports/api/users/server/methods.js
@@ -8,6 +8,7 @@ import toggleUserLock from './methods/toggleUserLock';
 import setUserEffectiveConnectionType from './methods/setUserEffectiveConnectionType';
 import userActivitySign from './methods/userActivitySign';
 import userLeftMeeting from './methods/userLeftMeeting';
+import setRandomUser from './methods/setRandomUser';
 
 Meteor.methods({
   setEmojiStatus,
@@ -19,4 +20,5 @@ Meteor.methods({
   setUserEffectiveConnectionType,
   userActivitySign,
   userLeftMeeting,
+  setRandomUser,
 });
diff --git a/bigbluebutton-html5/imports/api/users/server/methods/setRandomUser.js b/bigbluebutton-html5/imports/api/users/server/methods/setRandomUser.js
new file mode 100644
index 0000000000000000000000000000000000000000..a0da24a993a23e6d69cb11557540569beb9354f7
--- /dev/null
+++ b/bigbluebutton-html5/imports/api/users/server/methods/setRandomUser.js
@@ -0,0 +1,17 @@
+import { Meteor } from 'meteor/meteor';
+import RedisPubSub from '/imports/startup/server/redis';
+import { extractCredentials } from '/imports/api/common/server/helpers';
+
+export default function setRandomUser() {
+  const REDIS_CONFIG = Meteor.settings.private.redis;
+  const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
+  const EVENT_NAME = 'SelectRandomViewerReqMsg';
+
+  const { meetingId, requesterUserId } = extractCredentials(this.userId);
+
+  const payload = {
+    requestedBy: requesterUserId,
+  };
+
+  RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
+}
diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/component.jsx
index de55a9fdf108a57d6290bc36f3079155570c57aa..1a84ba7a5a8f2b4f9db508b1995e0306611496d6 100755
--- a/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/actions-bar/actions-dropdown/component.jsx
@@ -12,6 +12,7 @@ import { withModalMounter } from '/imports/ui/components/modal/service';
 import withShortcutHelper from '/imports/ui/components/shortcut-help/service';
 import DropdownListSeparator from '/imports/ui/components/dropdown/list/separator/component';
 import ExternalVideoModal from '/imports/ui/components/external-video-player/modal/container';
+import RandomUserSelectContainer from '/imports/ui/components/modal/random-user/container';
 import cx from 'classnames';
 import { styles } from '../styles';
 
@@ -75,6 +76,14 @@ const intlMessages = defineMessages({
     id: 'app.actionsBar.actionsDropdown.stopShareExternalVideo',
     description: 'Stop sharing external video button',
   },
+  selectRandUserLabel: {
+    id: 'app.actionsBar.actionsDropdown.selectRandUserLabel',
+    description: 'Label for selecting a random user',
+  },
+  selectRandUserDesc: {
+    id: 'app.actionsBar.actionsDropdown.selectRandUserDesc',
+    description: 'Description for select random user option',
+  },
 });
 
 const handlePresentationClick = () => Session.set('showUploadPresentationView', true);
@@ -86,6 +95,7 @@ class ActionsDropdown extends PureComponent {
     this.presentationItemId = _.uniqueId('action-item-');
     this.pollId = _.uniqueId('action-item-');
     this.takePresenterId = _.uniqueId('action-item-');
+    this.selectUserRandId = _.uniqueId('action-item-');
 
     this.handleExternalVideoClick = this.handleExternalVideoClick.bind(this);
     this.makePresentationItems = this.makePresentationItems.bind(this);
@@ -108,6 +118,7 @@ class ActionsDropdown extends PureComponent {
       isSharingVideo,
       isPollingEnabled,
       stopExternalVideoShare,
+      mountModal,
     } = this.props;
 
     const {
@@ -177,6 +188,17 @@ class ActionsDropdown extends PureComponent {
           />
         )
         : null),
+      (amIPresenter
+        ? (
+          <DropdownListItem
+            icon="user"
+            label={intl.formatMessage(intlMessages.selectRandUserLabel)}
+            description={intl.formatMessage(intlMessages.selectRandUserDesc)}
+            key={this.selectUserRandId}
+            onClick={() => mountModal(<RandomUserSelectContainer isSelectedUser={false} />)}
+          />
+        )
+        : null),
     ]);
   }
 
diff --git a/bigbluebutton-html5/imports/ui/components/app/component.jsx b/bigbluebutton-html5/imports/ui/components/app/component.jsx
index 168f73397ae644fa061580917a5d627ad8a09e2a..8b7dfcd2a360df44e4aba30b6aecd5b1d4249c47 100755
--- a/bigbluebutton-html5/imports/ui/components/app/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/app/component.jsx
@@ -22,6 +22,7 @@ import StatusNotifier from '/imports/ui/components/status-notifier/container';
 import MediaService from '/imports/ui/components/media/service';
 import ManyWebcamsNotifier from '/imports/ui/components/video-provider/many-users-notify/container';
 import UploaderContainer from '/imports/ui/components/presentation/presentation-uploader/container';
+import RandomUserSelectContainer from '/imports/ui/components/modal/random-user/container';
 import { withDraggableContext } from '../media/webcam-draggable-overlay/context';
 import { styles } from './styles';
 import { NAVBAR_HEIGHT } from '/imports/ui/components/layout/layout-manager';
@@ -154,9 +155,18 @@ class App extends Component {
 
   componentDidUpdate(prevProps) {
     const {
-      meetingMuted, notify, currentUserEmoji, intl, hasPublishedPoll,
+      meetingMuted,
+      notify,
+      currentUserEmoji,
+      intl,
+      hasPublishedPoll,
+      randomlySelectedUser,
+      currentUserId,
+      mountModal,
     } = this.props;
 
+    if (randomlySelectedUser === currentUserId) mountModal(<RandomUserSelectContainer />);
+
     if (prevProps.currentUserEmoji.status !== currentUserEmoji.status) {
       const formattedEmojiStatus = intl.formatMessage({ id: `app.actionsBar.emojiMenu.${currentUserEmoji.status}Label` })
       || currentUserEmoji.status;
diff --git a/bigbluebutton-html5/imports/ui/components/app/container.jsx b/bigbluebutton-html5/imports/ui/components/app/container.jsx
index 3f9dc2986bcef34c38ecad06e9d4f18bd6ed33a7..6e1bbc806b9ddedd0bfff418d82d7adcb449b21f 100755
--- a/bigbluebutton-html5/imports/ui/components/app/container.jsx
+++ b/bigbluebutton-html5/imports/ui/components/app/container.jsx
@@ -91,10 +91,10 @@ export default injectIntl(withModalMounter(withTracker(({ intl, baseControls })
     },
   });
 
-  const currentUser = Users.findOne({ userId: Auth.userID }, { fields: { approved: 1, emoji: 1 } });
+  const currentUser = Users.findOne({ userId: Auth.userID }, { fields: { approved: 1, emoji: 1, userId: 1 } });
   const currentMeeting = Meetings.findOne({ meetingId: Auth.meetingID },
-    { fields: { publishedPoll: 1, voiceProp: 1 } });
-  const { publishedPoll, voiceProp } = currentMeeting;
+    { fields: { publishedPoll: 1, voiceProp: 1, randomlySelectedUser: 1 } });
+  const { publishedPoll, voiceProp, randomlySelectedUser } = currentMeeting;
 
   if (!currentUser.approved) {
     baseControls.updateLoadingState(intl.formatMessage(intlMessages.waitingApprovalMessage));
@@ -122,6 +122,8 @@ export default injectIntl(withModalMounter(withTracker(({ intl, baseControls })
     hasPublishedPoll: publishedPoll,
     startBandwidthMonitoring,
     handleNetworkConnection: () => updateNavigatorConnection(navigator.connection),
+    randomlySelectedUser,
+    currentUserId: currentUser.userId,
   };
 })(AppContainer)));
 
diff --git a/bigbluebutton-html5/imports/ui/components/modal/random-user/component.jsx b/bigbluebutton-html5/imports/ui/components/modal/random-user/component.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..62ab5698ec1faccb473a644217c40a7556e2adc4
--- /dev/null
+++ b/bigbluebutton-html5/imports/ui/components/modal/random-user/component.jsx
@@ -0,0 +1,122 @@
+import React, { Component } from 'react';
+import { defineMessages, injectIntl } from 'react-intl';
+import PropTypes from 'prop-types';
+import Modal from '/imports/ui/components/modal/simple/component';
+import Button from '/imports/ui/components/button/component';
+import { styles } from './styles';
+
+const messages = defineMessages({
+  noViewers: {
+    id: 'app.modal.randomUser.noViewers.description',
+    description: 'Label displayed when no viewers are avaiable',
+  },
+  selected: {
+    id: 'app.modal.randomUser.selected.description',
+    description: 'Label shown to the selected user',
+  },
+  randUserTitle: {
+    id: 'app.modal.randomUser.title',
+    description: 'Modal title label',
+  },
+  reselect: {
+    id: 'app.modal.randomUser.reselect.label',
+    description: 'select new random user button label',
+  },
+  ariaModalTitle: {
+    id: 'app.modal.randomUser.ariaLabel.title',
+    description: 'modal title displayed to screen reader',
+  },
+});
+
+const propTypes = {
+  intl: PropTypes.shape({
+    formatMessage: PropTypes.func.isRequired,
+  }).isRequired,
+  mountModal: PropTypes.func.isRequired,
+  numAvailableViewers: PropTypes.number.isRequired,
+  randomUserReq: PropTypes.func.isRequired,
+};
+
+class RandomUserSelect extends Component {
+  constructor(props) {
+    super(props);
+
+    if (props.currentUser.presenter) {
+      props.randomUserReq();
+    }
+  }
+
+  componentDidUpdate() {
+    const { selectedUser, currentUser, mountModal } = this.props;
+    if (selectedUser && selectedUser.userId !== currentUser.userId && !currentUser.presenter) {
+      mountModal(null);
+    }
+  }
+
+  render() {
+    const {
+      intl,
+      mountModal,
+      numAvailableViewers,
+      randomUserReq,
+      selectedUser,
+      currentUser,
+      clearRandomlySelectedUser,
+    } = this.props;
+
+    if (!selectedUser) return null;
+
+    const isSelectedUser = currentUser.userId === selectedUser.userId;
+
+    const viewElement = numAvailableViewers < 1 ? (
+      <div className={styles.modalViewContainer}>
+        <div className={styles.modalViewTitle}>
+          {intl.formatMessage(messages.randUserTitle)}
+        </div>
+        <div>{intl.formatMessage(messages.noViewers)}</div>
+      </div>
+    ) : (
+      <div className={styles.modalViewContainer}>
+        <div className={styles.modalViewTitle}>
+          {isSelectedUser
+            ? `${intl.formatMessage(messages.selected)}`
+            : `${intl.formatMessage(messages.randUserTitle)}`
+          }
+        </div>
+        <div aria-hidden className={styles.modalAvatar} style={{ backgroundColor: `${selectedUser.color}` }}>
+          {selectedUser.name.slice(0, 2)}
+        </div>
+        <div className={styles.selectedUserName}>
+          {selectedUser.name}
+        </div>
+        {!isSelectedUser
+          && (
+          <Button
+            label={intl.formatMessage(messages.reselect)}
+            color="primary"
+            size="md"
+            className={styles.selectBtn}
+            onClick={() => randomUserReq()}
+          />
+          )
+        }
+      </div>
+    );
+
+    return (
+      <Modal
+        hideBorder
+        onRequestClose={() => {
+          if (currentUser.presenter) clearRandomlySelectedUser();
+          mountModal(null);
+        }}
+        contentLabel={intl.formatMessage(messages.ariaModalTitle)}
+      >
+        {viewElement}
+      </Modal>
+    );
+  }
+}
+
+RandomUserSelect.propTypes = propTypes;
+export default injectIntl(RandomUserSelect);
diff --git a/bigbluebutton-html5/imports/ui/components/modal/random-user/container.jsx b/bigbluebutton-html5/imports/ui/components/modal/random-user/container.jsx
new file mode 100644
index 0000000000000000000000000000000000000000..50d9b7a3138c94a1b9384dc174c6bf2c42587c6c
--- /dev/null
+++ b/bigbluebutton-html5/imports/ui/components/modal/random-user/container.jsx
@@ -0,0 +1,58 @@
+import React from 'react';
+import { withTracker } from 'meteor/react-meteor-data';
+import Meetings from '/imports/api/meetings';
+import Users from '/imports/api/users';
+import Auth from '/imports/ui/services/auth';
+import { withModalMounter } from '/imports/ui/components/modal/service';
+import { makeCall } from '/imports/ui/services/api';
+import RandomUserSelect from './component';
+
+const RandomUserSelectContainer = props => <RandomUserSelect {...props} />;
+
+export default withModalMounter(withTracker(({ mountModal }) => {
+  const viewerPool = Users.find({
+    meetingId: Auth.meetingID,
+    presenter: { $ne: true },
+    role: { $eq: 'VIEWER' },
+  }, {
+    fields: {
+      userId: 1,
+    },
+  }).fetch();
+
+  const meeting = Meetings.findOne({ meetingId: Auth.meetingID }, {
+    fields: {
+      randomlySelectedUser: 1,
+    },
+  });
+
+  const selectedUser = Users.findOne({
+    meetingId: Auth.meetingID,
+    userId: meeting.randomlySelectedUser,
+  }, {
+    fields: {
+      userId: 1,
+      avatar: 1,
+      color: 1,
+      name: 1,
+    },
+  });
+
+  const currentUser = Users.findOne(
+    { userId: Auth.userID },
+    { fields: { userId: 1, presenter: 1 } },
+  );
+
+  const randomUserReq = () => makeCall('setRandomUser');
+
+  const clearRandomlySelectedUser = () => makeCall('clearRandomlySelectedUser');
+
+  return ({
+    closeModal: () => mountModal(null),
+    numAvailableViewers: viewerPool.length,
+    randomUserReq,
+    selectedUser,
+    currentUser,
+    clearRandomlySelectedUser,
+  });
+})(RandomUserSelectContainer));
diff --git a/bigbluebutton-html5/imports/ui/components/modal/random-user/styles.scss b/bigbluebutton-html5/imports/ui/components/modal/random-user/styles.scss
new file mode 100644
index 0000000000000000000000000000000000000000..a6d7b1f84352bee67ff6f4594ba32f8e1e8497df
--- /dev/null
+++ b/bigbluebutton-html5/imports/ui/components/modal/random-user/styles.scss
@@ -0,0 +1,41 @@
+.modalViewContainer {
+    display: flex;
+    flex-flow: column;
+    align-items: center;
+}
+
+.modalViewTitle {
+    font-weight: 600;
+    font-size: var(--font-size-large);
+    margin-bottom: var(--md-padding-x);
+}
+
+.modalAvatar {
+    height: 6rem;
+    width: 6rem;
+    border-radius: 50%;
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    color: white;
+    font-size: var(--font-size-xxl);
+    font-weight: 400;
+    margin-bottom: var(--sm-padding-x);
+    text-transform: capitalize;
+}
+
+.selectedUserName {
+    margin-bottom: var(--md-padding-x);;
+    font-weight: var(--headings-font-weight);
+    font-size: 2rem;
+    white-space: nowrap;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    position: relative;
+    width: 100%;
+    text-align: center;
+}
+
+.selectBtn {
+    margin-bottom: var(--md-padding-x);
+}
diff --git a/bigbluebutton-html5/imports/ui/stylesheets/variables/typography.scss b/bigbluebutton-html5/imports/ui/stylesheets/variables/typography.scss
index 492a95241e8efe68fb598d2e72ba61e68c458f96..80fa90bcacdbbaf36243fff87968dc7575281b52 100644
--- a/bigbluebutton-html5/imports/ui/stylesheets/variables/typography.scss
+++ b/bigbluebutton-html5/imports/ui/stylesheets/variables/typography.scss
@@ -6,6 +6,7 @@
   --font-family-base: var(--font-family-sans-serif);
 
   --font-size-base: 1rem;
+  --font-size-xxl: 2.75rem;
   --font-size-xl: 1.75rem;
   --font-size-larger: 1.5rem;
   --font-size-large: 1.25rem;
diff --git a/bigbluebutton-html5/private/locales/en.json b/bigbluebutton-html5/private/locales/en.json
index c9448656b5c849d427f5b9785259e3a14aeb8641..9b0493b4ffaa4d925596b6b397f7af8ca7953f5f 100755
--- a/bigbluebutton-html5/private/locales/en.json
+++ b/bigbluebutton-html5/private/locales/en.json
@@ -370,6 +370,8 @@
     "app.actionsBar.actionsDropdown.captionsDesc": "Toggles captions pane",
     "app.actionsBar.actionsDropdown.takePresenter": "Take presenter",
     "app.actionsBar.actionsDropdown.takePresenterDesc": "Assign yourself as the new presenter",
+    "app.actionsBar.actionsDropdown.selectRandUserLabel": "Select Random User",
+    "app.actionsBar.actionsDropdown.selectRandUserDesc": "Chooses a user from available viewers at random",
     "app.actionsBar.emojiMenu.statusTriggerLabel": "Set status",
     "app.actionsBar.emojiMenu.awayLabel": "Away",
     "app.actionsBar.emojiMenu.awayDesc": "Change your status to away",
@@ -487,6 +489,11 @@
     "app.modal.confirm": "Done",
     "app.modal.newTab": "(opens new tab)",
     "app.modal.confirm.description": "Saves changes and closes the modal",
+    "app.modal.randomUser.noViewers.description": "No viewers available to randomly select from",
+    "app.modal.randomUser.selected.description": "You have been randomly selected",
+    "app.modal.randomUser.title": "Randomly selected user",
+    "app.modal.randomUser.reselect.label": "Select again",
+    "app.modal.randomUser.ariaLabel.title": "Randomly selected User Modal",
     "app.dropdown.close": "Close",
     "app.dropdown.list.item.activeLabel": "Active",
     "app.error.400": "Bad Request",