From 6979432c36abdc31d3a1a6cfef8a987293e4c530 Mon Sep 17 00:00:00 2001 From: Lucas Fialho Zawacki Date: Thu, 15 Dec 2022 16:06:42 -0300 Subject: [PATCH 001/113] feat(transcription): Server side open source transcriptions --- .../UpdateTranscriptPubMsgHdlr.scala | 7 ++++--- .../common2/msgs/AudioCaptionsMsgs.scala | 5 +++-- .../audio/captions/button/component.jsx | 10 ++++++++++ .../components/audio/captions/speech/service.js | 17 ++++++++++++++--- .../ui/components/captions/container.jsx | 14 -------------- .../captions/writer-menu/container.jsx | 11 ++++++----- .../user-options/component.jsx | 5 ----- bigbluebutton-html5/private/config/settings.yml | 2 ++ 8 files changed, 39 insertions(+), 32 deletions(-) diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/audiocaptions/UpdateTranscriptPubMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/audiocaptions/UpdateTranscriptPubMsgHdlr.scala index c690fa1389..c4115dc1d9 100644 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/audiocaptions/UpdateTranscriptPubMsgHdlr.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/audiocaptions/UpdateTranscriptPubMsgHdlr.scala @@ -11,11 +11,11 @@ trait UpdateTranscriptPubMsgHdlr { def handle(msg: UpdateTranscriptPubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = { val meetingId = liveMeeting.props.meetingProp.intId - def broadcastEvent(userId: String, transcriptId: String, transcript: String, locale: String): Unit = { + def broadcastEvent(userId: String, transcriptId: String, transcript: String, locale: String, result: Boolean): Unit = { val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, meetingId, "nodeJSapp") val envelope = BbbCoreEnvelope(TranscriptUpdatedEvtMsg.NAME, routing) val header = BbbClientMsgHeader(TranscriptUpdatedEvtMsg.NAME, meetingId, userId) - val body = TranscriptUpdatedEvtMsgBody(transcriptId, transcript, locale) + val body = TranscriptUpdatedEvtMsgBody(transcriptId, transcript, locale, result) val event = TranscriptUpdatedEvtMsg(header, body) val msgEvent = BbbCommonEnvCoreMsg(envelope, event) @@ -67,7 +67,8 @@ trait UpdateTranscriptPubMsgHdlr { msg.header.userId, msg.body.transcriptId, transcript, - msg.body.locale + msg.body.locale, + msg.body.result, ) } } diff --git a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/AudioCaptionsMsgs.scala b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/AudioCaptionsMsgs.scala index 838c5f611a..495f325f45 100644 --- a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/AudioCaptionsMsgs.scala +++ b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/AudioCaptionsMsgs.scala @@ -9,10 +9,11 @@ case class UpdateTranscriptPubMsgBody( end: Int, text: String, transcript: String, - locale: String + locale: String, + result: Boolean, ) // Out messages object TranscriptUpdatedEvtMsg { val NAME = "TranscriptUpdatedEvtMsg" } case class TranscriptUpdatedEvtMsg(header: BbbClientMsgHeader, body: TranscriptUpdatedEvtMsgBody) extends BbbCoreMsg -case class TranscriptUpdatedEvtMsgBody(transcriptId: String, transcript: String, locale: String) +case class TranscriptUpdatedEvtMsgBody(transcriptId: String, transcript: String, locale: String, result: Boolean) diff --git a/bigbluebutton-html5/imports/ui/components/audio/captions/button/component.jsx b/bigbluebutton-html5/imports/ui/components/audio/captions/button/component.jsx index d87503540c..1834f28d2e 100644 --- a/bigbluebutton-html5/imports/ui/components/audio/captions/button/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/audio/captions/button/component.jsx @@ -3,9 +3,11 @@ import PropTypes from 'prop-types'; import { defineMessages, injectIntl } from 'react-intl'; import Service from '/imports/ui/components/audio/captions/service'; import SpeechService from '/imports/ui/components/audio/captions/speech/service'; +import ServiceOldCaptions from '/imports/ui/components/captions/service'; import ButtonEmoji from '/imports/ui/components/common/button/button-emoji/ButtonEmoji'; import BBBMenu from '/imports/ui/components/common/menu/component'; import Styled from './styles'; +import OldCaptionsService from '/imports/ui/components/captions/service'; const intlMessages = defineMessages({ start: { @@ -89,6 +91,14 @@ const CaptionsButton = ({ isSupported, isVoiceUser, }) => { + const usePrevious = (value) => { + const ref = useRef(); + useEffect(() => { + ref.current = value; + }); + return ref.current; + } + const isTranscriptionDisabled = () => ( currentSpeechLocale === DISABLED ); diff --git a/bigbluebutton-html5/imports/ui/components/audio/captions/speech/service.js b/bigbluebutton-html5/imports/ui/components/audio/captions/speech/service.js index 77a0927f0d..6b18baa5f7 100644 --- a/bigbluebutton-html5/imports/ui/components/audio/captions/speech/service.js +++ b/bigbluebutton-html5/imports/ui/components/audio/captions/speech/service.js @@ -13,6 +13,8 @@ import { unique } from 'radash'; const THROTTLE_TIMEOUT = 1000; const CONFIG = Meteor.settings.public.app.audioCaptions; +const ENABLED = CONFIG.enabled; +const PROVIDER = CONFIG.provider; const LANGUAGES = CONFIG.language.available; const VALID_ENVIRONMENT = !deviceInfo.isMobile || CONFIG.mobile; @@ -32,8 +34,9 @@ const setSpeechVoices = () => { setSpeechVoices(); const getSpeechVoices = () => { - const voices = Session.get('speechVoices') || []; + if (!isWebSpeechApi()) return LANGUAGES; + const voices = Session.get('speechVoices') || []; return voices.filter((v) => LANGUAGES.includes(v)); }; @@ -51,7 +54,7 @@ const setSpeechLocale = (value) => { const useFixedLocale = () => isEnabled() && CONFIG.language.forceLocale; const initSpeechRecognition = () => { - if (!isEnabled()) return null; + if (!isEnabled() || !isWebSpeechApi()) return null; if (hasSpeechRecognitionSupport()) { // Effectivate getVoices setSpeechVoices(); @@ -129,7 +132,15 @@ const isLocaleValid = (locale) => LANGUAGES.includes(locale); const isEnabled = () => isLiveTranscriptionEnabled(); -const isActive = () => isEnabled() && hasSpeechRecognitionSupport() && hasSpeechLocale(); +const isWebSpeechApi = () => PROVIDER === 'webspeech' && hasSpeechRecognitionSupport() && hasSpeechLocale(); + +const isVosk = () => PROVIDER === 'vosk'; + +const isWhispering = () => PROVIDER === 'whisper'; + +const isDeepSpeech = () => PROVIDER === 'deepSpeech' + +const isActive = () => isEnabled() && (isWebSpeechApi() || isVosk() || isWhispering() || isDeepSpeech()); const getStatus = () => { const active = isActive(); diff --git a/bigbluebutton-html5/imports/ui/components/captions/container.jsx b/bigbluebutton-html5/imports/ui/components/captions/container.jsx index fc0ca2c311..a3b15ca08d 100644 --- a/bigbluebutton-html5/imports/ui/components/captions/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/captions/container.jsx @@ -11,20 +11,6 @@ const Container = (props) => { const { isResizing } = cameraDock; const layoutContextDispatch = layoutDispatch(); - const { amIModerator } = props; - - if (!amIModerator) { - layoutContextDispatch({ - type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN, - value: false, - }); - layoutContextDispatch({ - type: ACTIONS.SET_SIDEBAR_CONTENT_PANEL, - value: PANELS.NONE, - }); - return null; - } - return ; }; diff --git a/bigbluebutton-html5/imports/ui/components/captions/writer-menu/container.jsx b/bigbluebutton-html5/imports/ui/components/captions/writer-menu/container.jsx index a4c55d5aef..8a6352e305 100644 --- a/bigbluebutton-html5/imports/ui/components/captions/writer-menu/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/captions/writer-menu/container.jsx @@ -5,6 +5,7 @@ import WriterMenu from './component'; import { layoutDispatch } from '../../layout/context'; import Auth from '/imports/ui/services/auth'; import { UsersContext } from '/imports/ui/components/components-data/users-context/context'; +import SpeechService from '/imports/ui/components/audio/captions/speech/service'; const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator; @@ -14,11 +15,11 @@ const WriterMenuContainer = (props) => { const usingUsersContext = useContext(UsersContext); const { users } = usingUsersContext; const currentUser = users[Auth.meetingID][Auth.userID]; - const amIModerator = currentUser.role === ROLE_MODERATOR; - return amIModerator && ; + return ; }; -export default withTracker(() => ({ - availableLocales: Service.getAvailableLocales(), -}))(WriterMenuContainer); +export default withModalMounter(withTracker(({ mountModal }) => ({ + closeModal: () => mountModal(null), + availableLocales: SpeechService.getSpeechVoices(), +}))(WriterMenuContainer)); diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/component.jsx index d97ffd64f3..1c1daa8f6f 100755 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/component.jsx @@ -150,7 +150,6 @@ class UserOptions extends PureComponent { } this.handleCreateBreakoutRoomClick = this.handleCreateBreakoutRoomClick.bind(this); - this.handleCaptionsClick = this.handleCaptionsClick.bind(this); this.onCreateBreakouts = this.onCreateBreakouts.bind(this); this.onInvitationUsers = this.onInvitationUsers.bind(this); this.renderMenuItems = this.renderMenuItems.bind(this); @@ -194,10 +193,6 @@ class UserOptions extends PureComponent { return this.setCreateBreakoutRoomModalIsOpen(true); } - handleCaptionsClick() { - this.setWriterMenuModalIsOpen(true); - } - renderMenuItems() { const { intl, diff --git a/bigbluebutton-html5/private/config/settings.yml b/bigbluebutton-html5/private/config/settings.yml index fd0c4ca2e0..7e8d5a14db 100755 --- a/bigbluebutton-html5/private/config/settings.yml +++ b/bigbluebutton-html5/private/config/settings.yml @@ -88,6 +88,8 @@ public: enabled: false # mobile: - controls speech transcription availability on mobile mobile: false + # provider: [webspeech, vosk, whisper, deepspeech] + provider: webspeech language: available: # - de-DE From fee6ff026a7e0ed885751bd9c6f34ff9a3fad972 Mon Sep 17 00:00:00 2001 From: Lucas Fialho Zawacki Date: Fri, 23 Dec 2022 16:13:26 -0300 Subject: [PATCH 002/113] feat(captions): Use setUserSpeechLocale as an akka event and catch it in the transcription manager --- .../bigbluebutton/core/apps/users/UsersApp.scala | 1 + .../org/bigbluebutton/core/models/Users2x.scala | 11 +++++++++++ .../senders/ReceivedJsonMsgHandlerActor.scala | 2 ++ .../core/running/MeetingActor.scala | 1 + .../bigbluebutton/common2/msgs/UsersMsgs.scala | 8 ++++++++ .../api2/bus/ReceivedJsonMsgHdlrActor.scala | 2 ++ .../imports/api/users/server/eventHandlers.js | 2 ++ .../api/users/server/methods/setSpeechLocale.js | 16 +++++++++++++--- .../components/audio/captions/speech/service.js | 3 ++- 9 files changed, 42 insertions(+), 4 deletions(-) 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 5d821efc35..9caafb6266 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 @@ -149,6 +149,7 @@ class UsersApp( with GetUsersMeetingReqMsgHdlr with RegisterUserReqMsgHdlr with ChangeUserRoleCmdMsgHdlr + with SetUserSpeechLocaleMsgHdlr with SyncGetUsersMeetingRespMsgHdlr with LogoutAndEndMeetingCmdMsgHdlr with SetRecordingStatusCmdMsgHdlr 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 9f94230fda..4ece55d58d 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 @@ -203,6 +203,16 @@ object Users2x { } } + def setUserSpeechLocale(users: Users2x, intId: String, locale: String): Option[UserState] = { + for { + u <- findWithIntId(users, intId) + } yield { + val newUser = u.modify(_.speechLocale).setTo(locale) + users.save(newUser) + newUser + } + } + def hasPresenter(users: Users2x): Boolean = { findPresenter(users) match { case Some(p) => true @@ -374,6 +384,7 @@ case class UserState( clientType: String, pickExempted: Boolean, userLeftFlag: UserLeftFlag + speechLocale: String = "" ) case class UserIdAndName(id: String, name: String) 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 98808f3e90..7101f51674 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 @@ -111,6 +111,8 @@ class ReceivedJsonMsgHandlerActor( routeGenericMsg[ChangeUserPinStateReqMsg](envelope, jsonNode) case ChangeUserMobileFlagReqMsg.NAME => routeGenericMsg[ChangeUserMobileFlagReqMsg](envelope, jsonNode) + case SetUserSpeechLocaleReqMsg.NAME => + routeGenericMsg[SetUserSpeechLocaleReqMsg](envelope, jsonNode) case SelectRandomViewerReqMsg.NAME => routeGenericMsg[SelectRandomViewerReqMsg](envelope, jsonNode) 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 3c62400cff..0212d8eeac 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 @@ -389,6 +389,7 @@ class MeetingActor( case m: SelectRandomViewerReqMsg => usersApp.handleSelectRandomViewerReqMsg(m) case m: ChangeUserPinStateReqMsg => usersApp.handleChangeUserPinStateReqMsg(m) case m: ChangeUserMobileFlagReqMsg => usersApp.handleChangeUserMobileFlagReqMsg(m) + case m: SetUserSpeechLocaleReqMsg => usersApp.handleSetUserSpeechLocaleReqMsg(m) // Client requested to eject user case m: EjectUserFromMeetingCmdMsg => diff --git a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/UsersMsgs.scala b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/UsersMsgs.scala index 9e7d62f8f2..d8fbc77e00 100755 --- a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/UsersMsgs.scala +++ b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/UsersMsgs.scala @@ -438,3 +438,11 @@ case class SelectRandomViewerReqMsgBody(requestedBy: String) object SelectRandomViewerRespMsg { val NAME = "SelectRandomViewerRespMsg" } case class SelectRandomViewerRespMsg(header: BbbClientMsgHeader, body: SelectRandomViewerRespMsgBody) extends StandardMsg case class SelectRandomViewerRespMsgBody(requestedBy: String, userIds: Vector[String], choice: String) + +object SetUserSpeechLocaleReqMsg { val NAME = "SetUserSpeechLocaleReqMsg" } +case class SetUserSpeechLocaleReqMsg(header: BbbClientMsgHeader, body: SetUserSpeechLocaleReqMsgBody) extends StandardMsg +case class SetUserSpeechLocaleReqMsgBody(locale: String, provider: String) + +object UserSpeechLocaleChangedEvtMsg { val NAME = "UserSpeechLocaleChangedEvtMsg" } +case class UserSpeechLocaleChangedEvtMsg(header: BbbClientMsgHeader, body: UserSpeechLocaleChangedEvtMsgBody) extends BbbCoreMsg +case class UserSpeechLocaleChangedEvtMsgBody(locale: String, provider: String) diff --git a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/bus/ReceivedJsonMsgHdlrActor.scala b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/bus/ReceivedJsonMsgHdlrActor.scala index a7f9d8f5ed..d91cca326c 100755 --- a/bbb-common-web/src/main/scala/org/bigbluebutton/api2/bus/ReceivedJsonMsgHdlrActor.scala +++ b/bbb-common-web/src/main/scala/org/bigbluebutton/api2/bus/ReceivedJsonMsgHdlrActor.scala @@ -86,6 +86,8 @@ class ReceivedJsonMsgHdlrActor(val msgFromAkkaAppsEventBus: MsgFromAkkaAppsEvent route[UserBroadcastCamStoppedEvtMsg](envelope, jsonNode) case UserRoleChangedEvtMsg.NAME => route[UserRoleChangedEvtMsg](envelope, jsonNode) + case UserSpeechLocaleChangedEvtMsg.NAME => + route[UserSpeechLocaleChangedEvtMsg](envelope, jsonNode) case CreateBreakoutRoomSysCmdMsg.NAME => route[CreateBreakoutRoomSysCmdMsg](envelope, jsonNode) case PresentationUploadTokenSysPubMsg.NAME => diff --git a/bigbluebutton-html5/imports/api/users/server/eventHandlers.js b/bigbluebutton-html5/imports/api/users/server/eventHandlers.js index 7891bd0d0e..a4609c3048 100644 --- a/bigbluebutton-html5/imports/api/users/server/eventHandlers.js +++ b/bigbluebutton-html5/imports/api/users/server/eventHandlers.js @@ -9,6 +9,7 @@ import handleChangeRole from './handlers/changeRole'; import handleUserPinChanged from './handlers/userPinChanged'; import handleUserInactivityInspect from './handlers/userInactivityInspect'; import handleChangeMobileFlag from '/imports/api/users/server/handlers/changeMobileFlag'; +import handleUserSpeechLocaleChanged from './handlers/userSpeechLocaleChanged'; RedisPubSub.on('PresenterAssignedEvtMsg', handlePresenterAssigned); RedisPubSub.on('UserJoinedMeetingEvtMsg', handleUserJoined); @@ -20,3 +21,4 @@ RedisPubSub.on('UserMobileFlagChangedEvtMsg', handleChangeMobileFlag); RedisPubSub.on('UserLeftFlagUpdatedEvtMsg', handleUserLeftFlagUpdated); RedisPubSub.on('UserPinStateChangedEvtMsg', handleUserPinChanged); RedisPubSub.on('UserInactivityInspectMsg', handleUserInactivityInspect); +RedisPubSub.on('UserSpeechLocaleChangedEvtMsg', handleUserSpeechLocaleChanged); diff --git a/bigbluebutton-html5/imports/api/users/server/methods/setSpeechLocale.js b/bigbluebutton-html5/imports/api/users/server/methods/setSpeechLocale.js index 6945cdc0f6..2aaac7fd10 100644 --- a/bigbluebutton-html5/imports/api/users/server/methods/setSpeechLocale.js +++ b/bigbluebutton-html5/imports/api/users/server/methods/setSpeechLocale.js @@ -1,20 +1,30 @@ import { check } from 'meteor/check'; import Logger from '/imports/startup/server/logger'; -import updateSpeechLocale from '../modifiers/updateSpeechLocale'; +import RedisPubSub from '/imports/startup/server/redis'; import { extractCredentials } from '/imports/api/common/server/helpers'; const LANGUAGES = Meteor.settings.public.app.audioCaptions.language.available; -export default async function setSpeechLocale(locale) { +export default function setSpeechLocale(locale, provider) { try { const { meetingId, requesterUserId } = extractCredentials(this.userId); + const REDIS_CONFIG = Meteor.settings.private.redis; + const CHANNEL = REDIS_CONFIG.channels.toAkkaApps; + const EVENT_NAME = 'SetUserSpeechLocaleReqMsg'; + check(meetingId, String); check(requesterUserId, String); check(locale, String); + check(provider, String); + + const payload = { + locale, + provider, + }; if (LANGUAGES.includes(locale) || locale === '') { - await updateSpeechLocale(meetingId, requesterUserId, locale); + RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload); } } catch (err) { Logger.error(`Exception while invoking method setSpeechLocale ${err.stack}`); diff --git a/bigbluebutton-html5/imports/ui/components/audio/captions/speech/service.js b/bigbluebutton-html5/imports/ui/components/audio/captions/speech/service.js index 6b18baa5f7..3aa6aac53d 100644 --- a/bigbluebutton-html5/imports/ui/components/audio/captions/speech/service.js +++ b/bigbluebutton-html5/imports/ui/components/audio/captions/speech/service.js @@ -42,8 +42,9 @@ const getSpeechVoices = () => { const setSpeechLocale = (value) => { const voices = getSpeechVoices(); + if (voices.includes(value) || value === '') { - makeCall('setSpeechLocale', value); + makeCall('setSpeechLocale', value, CONFIG.provider); } else { logger.error({ logCode: 'captions_speech_locale', From 6f927a38354a809ff895661da0872a187601e238 Mon Sep 17 00:00:00 2001 From: prlanzarin <4529051+prlanzarin@users.noreply.github.com> Date: Thu, 22 Dec 2022 18:07:17 -0300 Subject: [PATCH 003/113] build(freeswitch): add mod_audio_fork and libwebsockets Add mod_audio_fork to FreeSWITCH's build alongside libwebsockets (which mod_audio_fork depends on). mod_audio_fork is used by the built in transcription feature as a way to extract L16 streams from FreeSWITCH via WebSockets for further processing by arbitrary transcription servers. For full details on mod_audio_fork itself, please check drachtio's source repo: github.com/drachtio/drachtio-freeswitch-modules.git A few cautionary tales about this one: - The new patch (mod_audio_fork_build.patch) guarantees libwebsockets is properly linked to FreeSWITCH and that mod_audio_fork is built as well. That's because mod_audio_fork is not an upstream module. - The patch _may_ introduce conflicts on FreeSWITCH bumps more easily than the other patches we have. They shouldn't be too hard to adapt, though. - There's fine tuning to be done to FreeSWITCH's unit file regarding mod_audio_fork's capabilities. Again: check drachtio's repo. --- .../bbb-freeswitch-core/build.sh | 35 ++++++++- .../mod_audio_fork_build.patch | 72 +++++++++++++++++++ .../bbb-freeswitch-core/modules.conf | 1 + 3 files changed, 107 insertions(+), 1 deletion(-) create mode 100644 build/packages-template/bbb-freeswitch-core/mod_audio_fork_build.patch diff --git a/build/packages-template/bbb-freeswitch-core/build.sh b/build/packages-template/bbb-freeswitch-core/build.sh index 7bbd187a3e..a92d95414c 100755 --- a/build/packages-template/bbb-freeswitch-core/build.sh +++ b/build/packages-template/bbb-freeswitch-core/build.sh @@ -73,12 +73,45 @@ cd .. ldconfig +# libwebsockets start +# mod_audio_fork needs it (used in built-in speech transcription) +if [ ! -d libwebsockets ]; then + git clone https://github.com/warmcat/libwebsockets.git +fi +cd libwebsockets/ +git checkout v3.2.3 + +mkdir -p build +cd build + +cmake .. +make -j $(nproc) +make install +cd ../../ + +ldconfig +# libwebsockets end + +# mod_audio_fork start +# copy mod_audio_fork into place (used in built-in speech transcription) +if [ ! -d drachtio-freeswitch-modules ]; then + git clone https://github.com/drachtio/drachtio-freeswitch-modules.git +fi + +cd drachtio-freeswitch-modules +git checkout 4198b1c114268829627069afeea7eb40c86a81af +cp -r modules/mod_audio_fork $BUILDDIR/freeswitch/src/mod/applications/mod_audio_fork +cd .. +# mod_audio_fork end + # we already cloned the FS repo in freeswitch.placeholder.sh and selected tag/branch cd $BUILDDIR/freeswitch patch -p0 < $BUILDDIR/floor.patch patch -p0 --ignore-whitespace < $BUILDDIR/audio.patch # Provisional patch for https://github.com/signalwire/freeswitch/pull/1531 +# Enables mod_audio_fork in the build process (used in built-in speech transcription) +patch -p1 < $BUILDDIR/mod_audio_fork_build.patch # Patch: https://github.com/signalwire/freeswitch/pull/1914 # There are some long-standing issues with the way FreeSWITCH changes @@ -96,7 +129,7 @@ patch -p1 < $BUILDDIR/1914.patch ./bootstrap.sh ./configure --disable-core-odbc-support --disable-core-pgsql-support \ - --without-python --without-erlang --without-java \ + --without-python --without-erlang --without-java --with-lws=yes \ --prefix=/opt/freeswitch # Overrides for generating debug version diff --git a/build/packages-template/bbb-freeswitch-core/mod_audio_fork_build.patch b/build/packages-template/bbb-freeswitch-core/mod_audio_fork_build.patch new file mode 100644 index 0000000000..3b4b5a3415 --- /dev/null +++ b/build/packages-template/bbb-freeswitch-core/mod_audio_fork_build.patch @@ -0,0 +1,72 @@ +diff --git a/Makefile.am b/Makefile.am +index f869072ff7..b31807a6f8 100644 +--- a/Makefile.am ++++ b/Makefile.am +@@ -166,6 +166,14 @@ if HAVE_FVAD + CORE_CFLAGS += -DSWITCH_HAVE_FVAD $(LIBFVAD_CFLAGS) + endif + ++## ++## libwebsockets ++## ++if HAVE_LWS ++CORE_CFLAGS += -DSWITCH_HAVE_LWS $(LWS_CFLAGS) ++LWS_LIBS += -lwebsockets ++endif ++ + ## + ## libfreeswitch + ## +@@ -233,7 +241,7 @@ endif + lib_LTLIBRARIES = libfreeswitch.la + libfreeswitch_la_CFLAGS = $(CORE_CFLAGS) $(SQLITE_CFLAGS) $(GUMBO_CFLAGS) $(FVAD_CFLAGS) $(FREETYPE_CFLAGS) $(CURL_CFLAGS) $(PCRE_CFLAGS) $(SPEEX_CFLAGS) $(LIBEDIT_CFLAGS) $(openssl_CFLAGS) $(SOFIA_SIP_CFLAGS) $(AM_CFLAGS) $(TPL_CFLAGS) + libfreeswitch_la_LDFLAGS = -version-info 1:0:0 $(AM_LDFLAGS) $(PLATFORM_CORE_LDFLAGS) -no-undefined +-libfreeswitch_la_LIBADD = $(CORE_LIBS) $(APR_LIBS) $(SQLITE_LIBS) $(GUMBO_LIBS) $(FVAD_LIBS) $(FREETYPE_LIBS) $(CURL_LIBS) $(PCRE_LIBS) $(SPEEX_LIBS) $(LIBEDIT_LIBS) $(SYSTEMD_LIBS) $(openssl_LIBS) $(PLATFORM_CORE_LIBS) $(TPL_LIBS) $(SPANDSP_LIBS) $(SOFIA_SIP_LIBS) ++libfreeswitch_la_LIBADD = $(CORE_LIBS) $(APR_LIBS) $(SQLITE_LIBS) $(GUMBO_LIBS) $(FVAD_LIBS) $(FREETYPE_LIBS) $(CURL_LIBS) $(PCRE_LIBS) $(SPEEX_LIBS) $(LIBEDIT_LIBS) $(SYSTEMD_LIBS) $(openssl_LIBS) $(PLATFORM_CORE_LIBS) $(TPL_LIBS) $(SPANDSP_LIBS) $(SOFIA_SIP_LIBS) $(LWS_LIBS) + libfreeswitch_la_DEPENDENCIES = $(BUILT_SOURCES) + + if HAVE_PNG +diff --git a/build/modules.conf.in b/build/modules.conf.in +index 7bf59e2acc..9cab2f6fdb 100644 +--- a/build/modules.conf.in ++++ b/build/modules.conf.in +@@ -1,3 +1,4 @@ ++applications/mod_audio_fork + #applications/mod_abstraction + applications/mod_av + #applications/mod_avmd +diff --git a/configure.ac b/configure.ac +index f09196bdfd..fba7b9d676 100644 +--- a/configure.ac ++++ b/configure.ac +@@ -1590,6 +1590,22 @@ AS_IF([test "x$enable_core_libedit_support" != "xno"],[ + AC_MSG_ERROR([You need to either install libedit-dev (>= 2.11) or configure with --disable-core-libedit-support]) + ])])]) + ++dnl --------------------------------------------------------------------------- ++dnl - libwebsockets ++dnl --------------------------------------------------------------------------- ++AC_ARG_WITH(lws, ++ [AS_HELP_STRING([--with-lws], ++ [enable support for libwebsockets])], ++ [with_lws="$withval"], ++ [with_lws="no"]) ++if test "$with_lws" = "yes"; then ++ PKG_CHECK_MODULES([LWS], [libwebsockets], [ ++ AM_CONDITIONAL([HAVE_LWS],[true])], [ ++ AC_MSG_RESULT([no]); AM_CONDITIONAL([HAVE_LWS],[false])]) ++else ++ AM_CONDITIONAL([HAVE_LWS],[false]) ++fi ++ + AC_ARG_ENABLE(systemd, + [AS_HELP_STRING([--enable-systemd], [Compile with systemd notify support])]) + +@@ -2081,6 +2097,7 @@ AC_CONFIG_FILES([Makefile + tests/unit/Makefile + src/Makefile + src/mod/Makefile ++ src/mod/applications/mod_audio_fork/Makefile + src/mod/applications/mod_abstraction/Makefile + src/mod/applications/mod_avmd/Makefile + src/mod/applications/mod_bert/Makefile diff --git a/build/packages-template/bbb-freeswitch-core/modules.conf b/build/packages-template/bbb-freeswitch-core/modules.conf index 7df3f615a9..199817fcfe 100644 --- a/build/packages-template/bbb-freeswitch-core/modules.conf +++ b/build/packages-template/bbb-freeswitch-core/modules.conf @@ -52,6 +52,7 @@ applications/mod_valet_parking #applications/mod_vmd applications/mod_voicemail #applications/mod_voicemail_ivr +applications/mod_audio_fork #asr_tts/mod_cepstral #asr_tts/mod_flite #asr_tts/mod_pocketsphinx From 8200a0ef64858aa9b31899104863d857cf347fec Mon Sep 17 00:00:00 2001 From: prlanzarin <4529051+prlanzarin@users.noreply.github.com> Date: Thu, 22 Dec 2022 18:07:38 -0300 Subject: [PATCH 004/113] build(freeswitch): load mod_audio_fork by default --- .../config/freeswitch/conf/autoload_configs/modules.conf.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/bbb-voice-conference/config/freeswitch/conf/autoload_configs/modules.conf.xml b/bbb-voice-conference/config/freeswitch/conf/autoload_configs/modules.conf.xml index af9611bd6a..aa58ac38ea 100644 --- a/bbb-voice-conference/config/freeswitch/conf/autoload_configs/modules.conf.xml +++ b/bbb-voice-conference/config/freeswitch/conf/autoload_configs/modules.conf.xml @@ -14,6 +14,7 @@ + From 4400cc68c80bbfddc58b723148a9d23809cc279c Mon Sep 17 00:00:00 2001 From: Lucas Fialho Zawacki Date: Wed, 11 Jan 2023 14:48:16 -0300 Subject: [PATCH 005/113] feat(captions): Add SetUserSpeechLocaleMsg files --- .../users/SetUserSpeechLocaleMsgHdlr.scala | 41 +++++++++++++++++++ .../handlers/userSpeechLocaleChanged.js | 13 ++++++ 2 files changed, 54 insertions(+) create mode 100644 akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/SetUserSpeechLocaleMsgHdlr.scala create mode 100644 bigbluebutton-html5/imports/api/users/server/handlers/userSpeechLocaleChanged.js diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/SetUserSpeechLocaleMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/SetUserSpeechLocaleMsgHdlr.scala new file mode 100644 index 0000000000..212ab2e548 --- /dev/null +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/SetUserSpeechLocaleMsgHdlr.scala @@ -0,0 +1,41 @@ +package org.bigbluebutton.core.apps.users + +import org.bigbluebutton.common2.msgs._ +import org.bigbluebutton.core.models.{ UserState, Users2x } +import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter } +import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait } +import org.bigbluebutton.core.domain.MeetingState2x + +trait SetUserSpeechLocaleMsgHdlr extends RightsManagementTrait { + this: UsersApp => + + val liveMeeting: LiveMeeting + val outGW: OutMsgRouter + + def handleSetUserSpeechLocaleReqMsg(msg: SetUserSpeechLocaleReqMsg): Unit = { + log.info("handleSetUserSpeechLocaleReqMsg: locale={} provider={} userId={}", msg.body.locale, msg.body.provider, msg.header.userId) + + def broadcastUserSpeechLocaleChanged(user: UserState, locale: String, provider: String): Unit = { + val routingChange = Routing.addMsgToClientRouting( + MessageTypes.BROADCAST_TO_MEETING, + liveMeeting.props.meetingProp.intId, user.intId + ) + val envelopeChange = BbbCoreEnvelope(UserSpeechLocaleChangedEvtMsg.NAME, routingChange) + val headerChange = BbbClientMsgHeader(UserSpeechLocaleChangedEvtMsg.NAME, liveMeeting.props.meetingProp.intId, user.intId) + + val bodyChange = UserSpeechLocaleChangedEvtMsgBody(locale, provider) + val eventChange = UserSpeechLocaleChangedEvtMsg(headerChange, bodyChange) + val msgEventChange = BbbCommonEnvCoreMsg(envelopeChange, eventChange) + outGW.send(msgEventChange) + } + + for { + user <- Users2x.findWithIntId(liveMeeting.users2x, msg.header.userId) + } yield { + var changeLocale: Option[UserState] = None; + changeLocale = Users2x.setUserSpeechLocale(liveMeeting.users2x, msg.header.userId, msg.body.locale) + broadcastUserSpeechLocaleChanged(user, msg.body.locale, msg.body.provider) + } + + } +} diff --git a/bigbluebutton-html5/imports/api/users/server/handlers/userSpeechLocaleChanged.js b/bigbluebutton-html5/imports/api/users/server/handlers/userSpeechLocaleChanged.js new file mode 100644 index 0000000000..205cfee3b9 --- /dev/null +++ b/bigbluebutton-html5/imports/api/users/server/handlers/userSpeechLocaleChanged.js @@ -0,0 +1,13 @@ +import { check } from 'meteor/check'; +import updateSpeechLocale from '../modifiers/updateSpeechLocale'; + +export default function handleUserSpeechLocaleChanged({ body, header }, meetingId) { + const { locale } = body; + const { userId } = header; + + check(meetingId, String); + check(userId, String); + check(locale, String); + + return updateSpeechLocale(meetingId, userId, locale); +} From 54b6578b03b6de20e9b8e9569214edc3aaed996b Mon Sep 17 00:00:00 2001 From: prlanzarin <4529051+prlanzarin@users.noreply.github.com> Date: Tue, 31 Jan 2023 14:04:17 -0300 Subject: [PATCH 006/113] fix(audio): forcefully disable stereo when using Vosk transcription The current Vosk CC provider does not support stereo mic streams (pending investigation as to why). This commits makes sure stereo is forcefully disabled via SDP munging only when transcription is active and using Vosk. Having it disabled in the server side (FreeSWITCH) is not enough because the stereo parameter is client mandated and replicated by FS on its answer. So we need to make sure it's always disabled for the time being. SFU audio does munging server side (and stereo is always off), so no changes needed there. The rest of the providers (except WebSpeech) need to be validated against stereo audio as well. This is also intended to be temporary - ideally this needs to be fixed in mod_audio_fork/Vosk/wherever this is breaking. --- .../imports/api/audio/client/bridge/sip.js | 16 ++++++++++++++++ .../components/audio/captions/speech/service.js | 3 +++ bigbluebutton-html5/imports/utils/sdpUtils.js | 6 ++++++ 3 files changed, 25 insertions(+) diff --git a/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js b/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js index 9104392764..e7f013c9b7 100755 --- a/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js +++ b/bigbluebutton-html5/imports/api/audio/client/bridge/sip.js @@ -12,6 +12,7 @@ import { filterValidIceCandidates, analyzeSdp, logSelectedCandidate, + forceDisableStereo, } from '/imports/utils/sdpUtils'; import { Tracker } from 'meteor/tracker'; import VoiceCallStates from '/imports/api/voice-call-states'; @@ -25,6 +26,7 @@ import { filterSupportedConstraints, doGUM, } from '/imports/api/audio/client/bridge/service'; +import SpeechService from '/imports/ui/components/audio/captions/speech/service'; const MEDIA = Meteor.settings.public.media; const MEDIA_TAG = MEDIA.mediaTag; @@ -708,12 +710,25 @@ class SIPSession { const target = SIP.UserAgent.makeURI(`sip:${callExtension}@${hostname}`); const matchConstraints = getAudioConstraints({ deviceId: this.inputDeviceId }); + const sessionDescriptionHandlerModifiers = []; const iceModifiers = [ filterValidIceCandidates.bind(this, this.validIceCandidates), ]; if (!SIPJS_ALLOW_MDNS) iceModifiers.push(stripMDnsCandidates); + // The current Vosk provider does not support stereo when transcribing + // microphone streams, so we need to make sure it is forcefully disabled + // via SDP munging. Having it disabled on server side FS _does not suffice_ + // because the stereo parameter is client-mandated (ie replicated in the + // answer) + if (SpeechService.stereoUnsupported()) { + logger.debug({ + logCode: 'sipjs_transcription_disable_stereo', + }, 'Transcription provider does not support stereo, forcing stereo=0'); + sessionDescriptionHandlerModifiers.push(forceDisableStereo); + } + const inviterOptions = { sessionDescriptionHandlerOptions: { constraints: { @@ -724,6 +739,7 @@ class SIPSession { }, iceGatheringTimeout: ICE_GATHERING_TIMEOUT, }, + sessionDescriptionHandlerModifiers, sessionDescriptionHandlerModifiersPostICEGathering: iceModifiers, delegate: { onSessionDescriptionHandler: diff --git a/bigbluebutton-html5/imports/ui/components/audio/captions/speech/service.js b/bigbluebutton-html5/imports/ui/components/audio/captions/speech/service.js index 3aa6aac53d..c437e61c3d 100644 --- a/bigbluebutton-html5/imports/ui/components/audio/captions/speech/service.js +++ b/bigbluebutton-html5/imports/ui/components/audio/captions/speech/service.js @@ -168,6 +168,8 @@ const getLocale = () => { return locale; }; +const stereoUnsupported = () => isActive() && isVosk() && !!getSpeechLocale(); + export default { LANGUAGES, hasSpeechRecognitionSupport, @@ -184,4 +186,5 @@ export default { getStatus, generateId, useFixedLocale, + stereoUnsupported, }; diff --git a/bigbluebutton-html5/imports/utils/sdpUtils.js b/bigbluebutton-html5/imports/utils/sdpUtils.js index 5730fdb9de..dd15a876b3 100755 --- a/bigbluebutton-html5/imports/utils/sdpUtils.js +++ b/bigbluebutton-html5/imports/utils/sdpUtils.js @@ -333,6 +333,11 @@ const logSelectedCandidate = async (peer, isIpv6) => { }); }; +const forceDisableStereo = ({ sdp, type }) => ({ + sdp: sdp.replace(/stereo=1/ig, 'stereo=0'), + type, +}); + export { interop, isUnifiedPlan, @@ -342,4 +347,5 @@ export { filterValidIceCandidates, analyzeSdp, logSelectedCandidate, + forceDisableStereo, }; From eafa0f200ea2d4a706afe712d3ff90a6a5538cc8 Mon Sep 17 00:00:00 2001 From: Arthurk12 Date: Tue, 14 Feb 2023 14:41:45 -0300 Subject: [PATCH 007/113] feat(captions): no longer writes in the pad This feature was too coupled to the old closed captions' pads. (e.g. the old closed captions feature should be enabled for this to work properly) Some things were hardcoded and others didn't make sense from the user experience perspective. Reverts #876d8aa. Partially reverts #802964f, removes changes to make closed captions' pads compatible with live-transcription but keeps provider settings. --- .../components/audio/captions/button/component.jsx | 10 ---------- .../imports/ui/components/captions/container.jsx | 14 ++++++++++++++ .../components/captions/writer-menu/container.jsx | 6 +++--- .../user-participants/user-options/component.jsx | 6 ++++++ 4 files changed, 23 insertions(+), 13 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/audio/captions/button/component.jsx b/bigbluebutton-html5/imports/ui/components/audio/captions/button/component.jsx index 1834f28d2e..d87503540c 100644 --- a/bigbluebutton-html5/imports/ui/components/audio/captions/button/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/audio/captions/button/component.jsx @@ -3,11 +3,9 @@ import PropTypes from 'prop-types'; import { defineMessages, injectIntl } from 'react-intl'; import Service from '/imports/ui/components/audio/captions/service'; import SpeechService from '/imports/ui/components/audio/captions/speech/service'; -import ServiceOldCaptions from '/imports/ui/components/captions/service'; import ButtonEmoji from '/imports/ui/components/common/button/button-emoji/ButtonEmoji'; import BBBMenu from '/imports/ui/components/common/menu/component'; import Styled from './styles'; -import OldCaptionsService from '/imports/ui/components/captions/service'; const intlMessages = defineMessages({ start: { @@ -91,14 +89,6 @@ const CaptionsButton = ({ isSupported, isVoiceUser, }) => { - const usePrevious = (value) => { - const ref = useRef(); - useEffect(() => { - ref.current = value; - }); - return ref.current; - } - const isTranscriptionDisabled = () => ( currentSpeechLocale === DISABLED ); diff --git a/bigbluebutton-html5/imports/ui/components/captions/container.jsx b/bigbluebutton-html5/imports/ui/components/captions/container.jsx index a3b15ca08d..fc0ca2c311 100644 --- a/bigbluebutton-html5/imports/ui/components/captions/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/captions/container.jsx @@ -11,6 +11,20 @@ const Container = (props) => { const { isResizing } = cameraDock; const layoutContextDispatch = layoutDispatch(); + const { amIModerator } = props; + + if (!amIModerator) { + layoutContextDispatch({ + type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN, + value: false, + }); + layoutContextDispatch({ + type: ACTIONS.SET_SIDEBAR_CONTENT_PANEL, + value: PANELS.NONE, + }); + return null; + } + return ; }; diff --git a/bigbluebutton-html5/imports/ui/components/captions/writer-menu/container.jsx b/bigbluebutton-html5/imports/ui/components/captions/writer-menu/container.jsx index 8a6352e305..8625be82fa 100644 --- a/bigbluebutton-html5/imports/ui/components/captions/writer-menu/container.jsx +++ b/bigbluebutton-html5/imports/ui/components/captions/writer-menu/container.jsx @@ -5,7 +5,6 @@ import WriterMenu from './component'; import { layoutDispatch } from '../../layout/context'; import Auth from '/imports/ui/services/auth'; import { UsersContext } from '/imports/ui/components/components-data/users-context/context'; -import SpeechService from '/imports/ui/components/audio/captions/speech/service'; const ROLE_MODERATOR = Meteor.settings.public.user.role_moderator; @@ -15,11 +14,12 @@ const WriterMenuContainer = (props) => { const usingUsersContext = useContext(UsersContext); const { users } = usingUsersContext; const currentUser = users[Auth.meetingID][Auth.userID]; + const amIModerator = currentUser.role === ROLE_MODERATOR; - return ; + return amIModerator && ; }; export default withModalMounter(withTracker(({ mountModal }) => ({ closeModal: () => mountModal(null), - availableLocales: SpeechService.getSpeechVoices(), + availableLocales: Service.getAvailableLocales(), }))(WriterMenuContainer)); diff --git a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/component.jsx b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/component.jsx index 1c1daa8f6f..277807cae6 100755 --- a/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/user-list/user-list-content/user-participants/user-options/component.jsx @@ -150,6 +150,7 @@ class UserOptions extends PureComponent { } this.handleCreateBreakoutRoomClick = this.handleCreateBreakoutRoomClick.bind(this); + this.handleCaptionsClick = this.handleCaptionsClick.bind(this); this.onCreateBreakouts = this.onCreateBreakouts.bind(this); this.onInvitationUsers = this.onInvitationUsers.bind(this); this.renderMenuItems = this.renderMenuItems.bind(this); @@ -193,6 +194,11 @@ class UserOptions extends PureComponent { return this.setCreateBreakoutRoomModalIsOpen(true); } + handleCaptionsClick() { + const { mountModal } = this.props; + mountModal(); + } + renderMenuItems() { const { intl, From 3b871e5ca2c5fe43b34ffe250cf5cbf62a2db6a6 Mon Sep 17 00:00:00 2001 From: Arthurk12 Date: Tue, 14 Feb 2023 16:07:24 -0300 Subject: [PATCH 008/113] fix(captions): "not supported" in chrome Fixes a case where the locale selector don't show up in Chrome when using 'webspeech' provider. And adds missing fields to the webspeech transcription messages, after the addition of some new parameters to those messages with the open transcription server. --- .../server/methods/updateTranscript.js | 4 +++- .../ui/components/audio/captions/speech/service.js | 12 ++++++------ 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/bigbluebutton-html5/imports/api/audio-captions/server/methods/updateTranscript.js b/bigbluebutton-html5/imports/api/audio-captions/server/methods/updateTranscript.js index 3b1b7bf68c..a00802a17e 100644 --- a/bigbluebutton-html5/imports/api/audio-captions/server/methods/updateTranscript.js +++ b/bigbluebutton-html5/imports/api/audio-captions/server/methods/updateTranscript.js @@ -3,7 +3,7 @@ import RedisPubSub from '/imports/startup/server/redis'; import { extractCredentials } from '/imports/api/common/server/helpers'; import Logger from '/imports/startup/server/logger'; -export default function updateTranscript(transcriptId, start, end, text, transcript, locale) { +export default function updateTranscript(transcriptId, start, end, text, transcript, locale, isFinal) { try { const REDIS_CONFIG = Meteor.settings.private.redis; const CHANNEL = REDIS_CONFIG.channels.toAkkaApps; @@ -19,6 +19,7 @@ export default function updateTranscript(transcriptId, start, end, text, transcr check(text, String); check(transcript, String); check(locale, String); + check(isFinal, Boolean); // Ignore irrelevant updates if (start !== -1 && end !== -1) { @@ -29,6 +30,7 @@ export default function updateTranscript(transcriptId, start, end, text, transcr text, transcript, locale, + result: isFinal, }; RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload); diff --git a/bigbluebutton-html5/imports/ui/components/audio/captions/speech/service.js b/bigbluebutton-html5/imports/ui/components/audio/captions/speech/service.js index c437e61c3d..dbfc908122 100644 --- a/bigbluebutton-html5/imports/ui/components/audio/captions/speech/service.js +++ b/bigbluebutton-html5/imports/ui/components/audio/captions/speech/service.js @@ -81,7 +81,7 @@ const initSpeechRecognition = () => { let prevId = ''; let prevTranscript = ''; -const updateTranscript = (id, transcript, locale) => { +const updateTranscript = (id, transcript, locale, isFinal) => { // If it's a new sentence if (id !== prevId) { prevId = id; @@ -102,7 +102,7 @@ const updateTranscript = (id, transcript, locale) => { // Stores current transcript as previous prevTranscript = transcript; - makeCall('updateTranscript', id, start, end, text, transcript, locale); + makeCall('updateTranscript', id, start, end, text, transcript, locale, isFinal); }; const throttledTranscriptUpdate = throttle(updateTranscript, THROTTLE_TIMEOUT, { @@ -111,12 +111,12 @@ const throttledTranscriptUpdate = throttle(updateTranscript, THROTTLE_TIMEOUT, { }); const updateInterimTranscript = (id, transcript, locale) => { - throttledTranscriptUpdate(id, transcript, locale); + throttledTranscriptUpdate(id, transcript, locale, false); }; const updateFinalTranscript = (id, transcript, locale) => { throttledTranscriptUpdate.cancel(); - updateTranscript(id, transcript, locale); + updateTranscript(id, transcript, locale, true); }; const getSpeechLocale = (userId = Auth.userID) => { @@ -133,7 +133,7 @@ const isLocaleValid = (locale) => LANGUAGES.includes(locale); const isEnabled = () => isLiveTranscriptionEnabled(); -const isWebSpeechApi = () => PROVIDER === 'webspeech' && hasSpeechRecognitionSupport() && hasSpeechLocale(); +const isWebSpeechApi = () => PROVIDER === 'webspeech'; const isVosk = () => PROVIDER === 'vosk'; @@ -141,7 +141,7 @@ const isWhispering = () => PROVIDER === 'whisper'; const isDeepSpeech = () => PROVIDER === 'deepSpeech' -const isActive = () => isEnabled() && (isWebSpeechApi() || isVosk() || isWhispering() || isDeepSpeech()); +const isActive = () => isEnabled() && ((isWebSpeechApi() && hasSpeechLocale()) || isVosk() || isWhispering() || isDeepSpeech()); const getStatus = () => { const active = isActive(); From 894bd3e126811f5454a3e21f545f91924903a3bc Mon Sep 17 00:00:00 2001 From: Lucas Zawacki Date: Mon, 6 Mar 2023 19:04:20 -0300 Subject: [PATCH 009/113] fix(transcription): Don't set webspeech provider for transcription-controller --- .../imports/api/users/server/methods/setSpeechLocale.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigbluebutton-html5/imports/api/users/server/methods/setSpeechLocale.js b/bigbluebutton-html5/imports/api/users/server/methods/setSpeechLocale.js index 2aaac7fd10..3bc0625221 100644 --- a/bigbluebutton-html5/imports/api/users/server/methods/setSpeechLocale.js +++ b/bigbluebutton-html5/imports/api/users/server/methods/setSpeechLocale.js @@ -20,7 +20,7 @@ export default function setSpeechLocale(locale, provider) { const payload = { locale, - provider, + provider: provider !== 'webspeech' ? provider : '', }; if (LANGUAGES.includes(locale) || locale === '') { From cf4ff9453caca7fcc8ade492fe66dcf1ec57a812 Mon Sep 17 00:00:00 2001 From: prlanzarin <4529051+prlanzarin@users.noreply.github.com> Date: Fri, 19 May 2023 12:38:55 -0300 Subject: [PATCH 010/113] build: add bbb-transcription-controller Added as an optional package --- .github/workflows/automated-tests.yml | 1 + .gitlab-ci.yml | 6 +++ bbb-transcription-controller.placeholder.sh | 1 + bigbluebutton-config/bin/bbb-conf | 10 ++++- build/package-names.inc.sh | 1 + .../bbb-transcription-controller/.build-files | 5 +++ .../after-install.sh | 18 ++++++++ .../bbb-transcription-controller.service | 18 ++++++++ .../before-remove.sh | 3 ++ .../bbb-transcription-controller/build.sh | 42 +++++++++++++++++++ .../opts-focal.sh | 3 ++ 11 files changed, 106 insertions(+), 2 deletions(-) create mode 100755 bbb-transcription-controller.placeholder.sh create mode 100644 build/packages-template/bbb-transcription-controller/.build-files create mode 100755 build/packages-template/bbb-transcription-controller/after-install.sh create mode 100644 build/packages-template/bbb-transcription-controller/bbb-transcription-controller.service create mode 100755 build/packages-template/bbb-transcription-controller/before-remove.sh create mode 100755 build/packages-template/bbb-transcription-controller/build.sh create mode 100644 build/packages-template/bbb-transcription-controller/opts-focal.sh diff --git a/.github/workflows/automated-tests.yml b/.github/workflows/automated-tests.yml index e520bf1bbf..f1b7355b48 100644 --- a/.github/workflows/automated-tests.yml +++ b/.github/workflows/automated-tests.yml @@ -44,6 +44,7 @@ jobs: - run: ./build/setup.sh bbb-web - run: ./build/setup.sh bbb-webrtc-sfu - run: ./build/setup.sh bbb-webrtc-recorder + - run: ./build/setup.sh bbb-transcription-controller - run: ./build/setup.sh bigbluebutton - run: tar cvf artifacts.tar artifacts/ - name: Archive packages diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 718973c721..9f7b812277 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -51,6 +51,7 @@ get_external_dependencies: - freeswitch - bbb-pads - bbb-playback + - bbb-transcription-controller expire_in: 1h 30min # template job for build step @@ -176,6 +177,11 @@ bbb-webrtc-recorder-build: script: - build/setup-inside-docker.sh bbb-webrtc-recorder +bbb-transcription-controller-build: + extends: .build_job + script: + - build/setup-inside-docker.sh bbb-transcription-controller + bigbluebutton-build: extends: .build_job script: diff --git a/bbb-transcription-controller.placeholder.sh b/bbb-transcription-controller.placeholder.sh new file mode 100755 index 0000000000..c668a62e07 --- /dev/null +++ b/bbb-transcription-controller.placeholder.sh @@ -0,0 +1 @@ +git clone --branch v0.1.0 --depth 1 https://github.com/bigbluebutton/bbb-transcription-controller bbb-transcription-controller diff --git a/bigbluebutton-config/bin/bbb-conf b/bigbluebutton-config/bin/bbb-conf index ab39b8638e..b1e73bd2be 100755 --- a/bigbluebutton-config/bin/bbb-conf +++ b/bigbluebutton-config/bin/bbb-conf @@ -489,11 +489,15 @@ display_bigbluebutton_status () { units="$units bbb-rap-starter" fi + if [ -f /usr/lib/systemd/system/bbb-transcription-controller.service ]; then + units="$units bbb-transcription-controller" + fi + if systemctl list-units --full -all | grep -q $TOMCAT_USER.service; then TOMCAT_SERVICE=$TOMCAT_USER fi - line='——————————————————————►' + line='—————————————————————————————►' for unit in $units; do status=$(systemctl is-active "$unit") if [ "$status" = "active" ]; then @@ -1724,7 +1728,9 @@ if [ -n "$HOST" ]; then sudo yq w -i /usr/local/bigbluebutton/bbb-webrtc-sfu/config/default.yml freeswitch.esl_password "$ESL_PASSWORD" sudo xmlstarlet edit --inplace --update 'configuration/settings//param[@name="password"]/@value' --value $ESL_PASSWORD /opt/freeswitch/etc/freeswitch/autoload_configs/event_socket.conf.xml - + if [ -f /usr/local/bigbluebutton/bbb-transcription-controller/config/default.yml ]; then + sudo yq w -i /usr/local/bigbluebutton/bbb-transcription-controller/config/default.yml freeswitch.esl_password "$ESL_PASSWORD" + fi echo "Restarting BigBlueButton $BIGBLUEBUTTON_RELEASE ..." stop_bigbluebutton diff --git a/build/package-names.inc.sh b/build/package-names.inc.sh index 28ab6234fb..4769a11805 100644 --- a/build/package-names.inc.sh +++ b/build/package-names.inc.sh @@ -25,6 +25,7 @@ DEBNAME_TO_SOURCEDIR[bbb-web]="bigbluebutton-web bbb-common-web bbb-common-messa DEBNAME_TO_SOURCEDIR[bbb-webhooks]="bbb-webhooks" DEBNAME_TO_SOURCEDIR[bbb-webrtc-sfu]="bbb-webrtc-sfu" DEBNAME_TO_SOURCEDIR[bbb-webrtc-recorder]="bbb-webrtc-recorder" +DEBNAME_TO_SOURCEDIR[bbb-transcription-controller]="bbb-transcription-controller" DEBNAME_TO_SOURCEDIR[bigbluebutton]="do_not_copy_anything" export DEBNAME_TO_SOURCEDIR diff --git a/build/packages-template/bbb-transcription-controller/.build-files b/build/packages-template/bbb-transcription-controller/.build-files new file mode 100644 index 0000000000..fbd3759de7 --- /dev/null +++ b/build/packages-template/bbb-transcription-controller/.build-files @@ -0,0 +1,5 @@ +after-install.sh +bbb-transcription-controller.service +before-remove.sh +build.sh +opts-focal.sh diff --git a/build/packages-template/bbb-transcription-controller/after-install.sh b/build/packages-template/bbb-transcription-controller/after-install.sh new file mode 100755 index 0000000000..9d0b38dd29 --- /dev/null +++ b/build/packages-template/bbb-transcription-controller/after-install.sh @@ -0,0 +1,18 @@ +#!/bin/bash -e + +case "$1" in + configure|upgrade|1|2) + TARGET=/usr/local/bigbluebutton/bbb-transcription-controller/config/default.yml + cp /usr/local/bigbluebutton/bbb-transcription-controller/config/default.example.yml $TARGET + + startService bbb-transcription-controller|| echo "bbb-transcription-controller could not be registered or started" + ;; + + abort-upgrade|abort-remove|abort-deconfigure) + ;; + + *) + echo "postinst called with unknown argument \`$1'" >&2 + exit 1 + ;; +esac diff --git a/build/packages-template/bbb-transcription-controller/bbb-transcription-controller.service b/build/packages-template/bbb-transcription-controller/bbb-transcription-controller.service new file mode 100644 index 0000000000..d0d964bd09 --- /dev/null +++ b/build/packages-template/bbb-transcription-controller/bbb-transcription-controller.service @@ -0,0 +1,18 @@ +[Unit] +Description=BigBlueButton Transcription Controller +Wants=redis-server.service +After=syslog.target network.target redis-server.service freeswitch.service bbb-apps-akka.service +PartOf=bigbluebutton.target + +[Service] +WorkingDirectory=/usr/local/bigbluebutton/bbb-transcription-controller +ExecStart=/usr/bin/node app.js +Restart=always +SyslogIdentifier=bbb-transcription-controller +User=bigbluebutton +Group=bigbluebutton +Environment=NODE_ENV=production +Environment=NODE_CONFIG_DIR=/etc/bigbluebutton/bbb-transcription-controller/:/usr/local/bigbluebutton/bbb-transcription-controller/config/ + +[Install] +WantedBy=multi-user.target bigbluebutton.target diff --git a/build/packages-template/bbb-transcription-controller/before-remove.sh b/build/packages-template/bbb-transcription-controller/before-remove.sh new file mode 100755 index 0000000000..a4cc2af16b --- /dev/null +++ b/build/packages-template/bbb-transcription-controller/before-remove.sh @@ -0,0 +1,3 @@ +#!/bin/bash -e + +stopService bbb-transcription-controller || echo "bbb-transcription-controller could not be unregistered or stopped" diff --git a/build/packages-template/bbb-transcription-controller/build.sh b/build/packages-template/bbb-transcription-controller/build.sh new file mode 100755 index 0000000000..57740a01b1 --- /dev/null +++ b/build/packages-template/bbb-transcription-controller/build.sh @@ -0,0 +1,42 @@ +#!/bin/bash -ex + +TARGET=`basename $(pwd)` + +PACKAGE=$(echo $TARGET | cut -d'_' -f1) +VERSION=$(echo $TARGET | cut -d'_' -f2) +DISTRO=$(echo $TARGET | cut -d'_' -f3) +TAG=$(echo $TARGET | cut -d'_' -f4) + +# +# Clean up directories +rm -rf staging + +# +# package + +mkdir -p staging/usr/local/bigbluebutton/bbb-transcription-controller + +find -maxdepth 1 ! -path . ! -name staging $(printf "! -name %s " $(cat .build-files)) -exec cp -r {} staging/usr/local/bigbluebutton/bbb-transcription-controller/ \; + +pushd . +cd staging/usr/local/bigbluebutton/bbb-transcription-controller/ +npm install --production +popd + +mkdir -p staging/usr/lib/systemd/system +cp bbb-transcription-controller.service staging/usr/lib/systemd/system + +## + +. ./opts-$DISTRO.sh + +# +# Build RPM package +fpm -s dir -C ./staging -n $PACKAGE \ + --version $VERSION --epoch $EPOCH \ + --after-install after-install.sh \ + --before-remove before-remove.sh \ + --description "BigBlueButton Transcription Controller" \ + $DIRECTORIES \ + $OPTS + diff --git a/build/packages-template/bbb-transcription-controller/opts-focal.sh b/build/packages-template/bbb-transcription-controller/opts-focal.sh new file mode 100644 index 0000000000..849bbb2d04 --- /dev/null +++ b/build/packages-template/bbb-transcription-controller/opts-focal.sh @@ -0,0 +1,3 @@ +. ./opts-global.sh + +OPTS="$OPTS -d nodejs,npm,bbb-apps-akka,bbb-freeswitch-core -t deb" From 9840e6630f35ede6a8f0aa4b84f668f5bce7a98c Mon Sep 17 00:00:00 2001 From: Paul Trudel Date: Tue, 23 May 2023 19:34:34 +0000 Subject: [PATCH 011/113] Added check for parentMeetingId if isBreakout is true --- .../org/bigbluebutton/web/controllers/ApiController.groovy | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy b/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy index 61f0a7a3f3..3826605cd1 100755 --- a/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy +++ b/bigbluebutton-web/grails-app/controllers/org/bigbluebutton/web/controllers/ApiController.groovy @@ -128,6 +128,11 @@ class ApiController { return } + if(params.isBreakout && !params.parentIdMeetingId) { + invalid("parentMeetingIdMissing", "No parent meeting ID was provided for the breakout room") + return + } + // Ensure unique TelVoice. Uniqueness is not guaranteed by paramsProcessorUtil. if (!params.voiceBridge) { // Try up to 10 times. We should find a valid telVoice quickly unless From cf9c0899d0b433e59d0c9e99fbe32f00a0b6de56 Mon Sep 17 00:00:00 2001 From: danielpetri1 Date: Mon, 29 May 2023 13:19:49 +0000 Subject: [PATCH 012/113] Use Tika as MIME detection tool --- .../presentation/SupportedFileTypes.java | 68 +++++-------------- bigbluebutton-web/build.gradle | 3 + 2 files changed, 20 insertions(+), 51 deletions(-) diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/SupportedFileTypes.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/SupportedFileTypes.java index eaee3e6add..8483395bb9 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/SupportedFileTypes.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/SupportedFileTypes.java @@ -24,6 +24,7 @@ import org.slf4j.LoggerFactory; import static org.bigbluebutton.presentation.FileTypeConstants.*; +import org.apache.tika.Tika; import java.io.BufferedReader; import java.io.File; import java.io.IOException; @@ -71,7 +72,7 @@ public final class SupportedFileTypes { } /* - * Returns if the office file is supported. + * Returns if the Office file is supported. */ public static boolean isOfficeFile(String fileExtension) { return OFFICE_FILE_LIST.contains(fileExtension.toLowerCase()); @@ -82,62 +83,27 @@ public final class SupportedFileTypes { } /* - * Returns if the iamge file is supported + * Returns if the image file is supported */ public static boolean isImageFile(String fileExtension) { return IMAGE_FILE_LIST.contains(fileExtension.toLowerCase()); } - /* - * It was tested native java methods to detect mimetypes, such as: - * - URLConnection.guessContentTypeFromStream(InputStream is); - * - Files.probeContentType(Path path); - * - FileNameMap fileNameMap.getContentTypeFor(String file.getName()); - * - MimetypesFileTypeMap fileTypeMap.getContentType(File file); - * But none of them was as successful as the linux based command - */ public static String detectMimeType(File pres) { + if (pres == null) { + log.error("Presentation is null"); + return ""; + } + + if (!pres.isFile()) { + log.error("Presentation is not a file"); + return ""; + } + try { - if (pres == null) throw new NullPointerException("Presentation is null"); - if (!pres.isFile()) throw new RuntimeException("Presentation is not a file"); - - ProcessBuilder processBuilder = new ProcessBuilder(); - processBuilder.command("bash", "-c", "file -b --mime-type \"" + pres.getAbsolutePath() + "\""); - Process process = processBuilder.start(); - StringBuilder output = new StringBuilder(); - BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream())); - String line; - while ((line = reader.readLine()) != null) { - output.append(line + "\n"); - } - int exitVal = process.waitFor(); - if (exitVal == 0) { - return output.toString().trim(); - } else { - String executedCommand = processBuilder.command().toArray(new String[0])[2]; - - //Read error stream - BufferedReader stdError = new BufferedReader(new InputStreamReader(process.getErrorStream())); - StringBuilder errorString = new StringBuilder(); - while (stdError.ready()) { - errorString.append(stdError.readLine()); - if (stdError.ready()) { - errorString.append("\n"); - } - } - - log.error("Error while executing command '{}': {}", executedCommand, errorString); - - if (exitVal == 127) { - // 127 - command not found - // use Java method to detect in this case (based on file name) - return URLConnection.getFileNameMap().getContentTypeFor(pres.getAbsolutePath()); - } else { - throw new RuntimeException(errorString.toString()); - } - } - } catch (Exception e) { - log.error("Error while executing detectMimeType: {}", e.getMessage()); + return (new Tika()).detect(pres); + } catch (IOException e) { + log.error("Error while executing detectMimeType: {}", e); } return ""; @@ -146,7 +112,7 @@ public final class SupportedFileTypes { public static Boolean isPresentationMimeTypeValid(File pres, String fileExtension) { String mimeType = detectMimeType(pres); - if (mimeType == null || mimeType.equals("")) { + if (mimeType.equals("")) { return false; } diff --git a/bigbluebutton-web/build.gradle b/bigbluebutton-web/build.gradle index 4b3783ce6f..7fdf065589 100755 --- a/bigbluebutton-web/build.gradle +++ b/bigbluebutton-web/build.gradle @@ -54,6 +54,9 @@ repositories { dependencies { runtimeOnly "com.bertramlabs.plugins:asset-pipeline-grails:4.0.0" + runtimeOnly 'org.apache.tika:tika-core:2.8.0' + runtimeOnly 'org.apache.tika:tika-parsers-standard-package:2.8.0' + implementation "org.springframework:spring-core:5.3.21" implementation "org.springframework:spring-context:5.3.27" implementation "org.springframework.boot:spring-boot:${springVersion}" From 0711e69ff9a65899b65453e5962e23f963b1091e Mon Sep 17 00:00:00 2001 From: danielpetri1 Date: Mon, 29 May 2023 13:26:44 +0000 Subject: [PATCH 013/113] Typo - Office as proper noun --- bigbluebutton-html5/public/locales/en.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/bigbluebutton-html5/public/locales/en.json b/bigbluebutton-html5/public/locales/en.json index 302126bb53..cb8e447f39 100755 --- a/bigbluebutton-html5/public/locales/en.json +++ b/bigbluebutton-html5/public/locales/en.json @@ -239,7 +239,7 @@ "app.presentation.presentationToolbar.showToolsDesc": "Show Toolbars", "app.presentation.placeholder": "There is no currently active presentation", "app.presentationUploder.title": "Presentation", - "app.presentationUploder.message": "As a presenter you have the ability to upload any office document or PDF file. We recommend PDF file for best results. Please ensure that a presentation is selected using the circle checkbox on the left hand side.", + "app.presentationUploder.message": "As a presenter you have the ability to upload any Office document or PDF file. We recommend PDF file for best results. Please ensure that a presentation is selected using the circle checkbox on the left hand side.", "app.presentationUploader.exportHint": "Selecting \"Send to chat\" will provide users with a downloadable link with annotations in public chat.", "app.presentationUploader.exportToastHeader": "Sending to chat ({0} item)", "app.presentationUploader.exportToastHeaderPlural": "Sending to chat ({0} items)", @@ -284,8 +284,8 @@ "app.presentationUploder.conversion.pageCountExceeded": "Number of pages exceeded maximum of {0}", "app.presentationUploder.conversion.invalidMimeType": "Invalid format detected (extension={0}, content type={1})", "app.presentationUploder.conversion.conversionTimeout": "Slide {0} could not be processed within {1} attempts.", - "app.presentationUploder.conversion.officeDocConversionInvalid": "Failed to process office document. Please upload a PDF instead.", - "app.presentationUploder.conversion.officeDocConversionFailed": "Failed to process office document. Please upload a PDF instead.", + "app.presentationUploder.conversion.officeDocConversionInvalid": "Failed to process Office document. Please upload a PDF instead.", + "app.presentationUploder.conversion.officeDocConversionFailed": "Failed to process Office document. Please upload a PDF instead.", "app.presentationUploder.conversion.pdfHasBigPage": "We could not convert the PDF file, please try optimizing it. Max page size {0}", "app.presentationUploder.conversion.timeout": "Ops, the conversion took too long", "app.presentationUploder.conversion.pageCountFailed": "Failed to determine the number of pages.", From a53419576aa19414c0586249dd2482899414999b Mon Sep 17 00:00:00 2001 From: Scroody Date: Mon, 29 May 2023 16:44:33 -0300 Subject: [PATCH 014/113] Fix: White squares displayed when rotatin to mobile orientation --- .../imports/ui/components/chat/component.jsx | 10 +++++++++- .../imports/ui/components/chat/styles.js | 5 ++++- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/chat/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/component.jsx index 3d978365e5..eab1cf9a47 100755 --- a/bigbluebutton-html5/imports/ui/components/chat/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/component.jsx @@ -1,4 +1,4 @@ -import React, { memo } from 'react'; +import React, { memo, useState } from 'react'; import PropTypes from 'prop-types'; import { defineMessages, injectIntl } from 'react-intl'; import injectWbResizeEvent from '/imports/ui/components/presentation/resize-wrapper/component'; @@ -56,6 +56,12 @@ const Chat = (props) => { width, } = props; + const [isMenuOpen, setMenuOpen] = useState(false); + + const handleToggleMenu = () => { + setMenuOpen((prevState) => !prevState); + }; + const userSentMessage = UserSentMessageCollection.findOne({ userId: Auth.userID, sent: true }); const { isChrome } = browserInfo; @@ -67,6 +73,7 @@ const Chat = (props) => {
{ 'data-test': isPublicChat ? 'hidePublicChat' : 'hidePrivateChat', label: title, onClick: () => { + handleToggleMenu; layoutContextDispatch({ type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN, value: false, diff --git a/bigbluebutton-html5/imports/ui/components/chat/styles.js b/bigbluebutton-html5/imports/ui/components/chat/styles.js index dadb0815b4..fc3f5dad64 100644 --- a/bigbluebutton-html5/imports/ui/components/chat/styles.js +++ b/bigbluebutton-html5/imports/ui/components/chat/styles.js @@ -1,7 +1,7 @@ import styled from 'styled-components'; import { colorWhite, - colorPrimary + colorPrimary, } from '/imports/ui/stylesheets/styled-components/palette'; import { smallOnly } from '/imports/ui/stylesheets/styled-components/breakpoints'; import { mdPaddingX } from '/imports/ui/stylesheets/styled-components/general'; @@ -52,6 +52,9 @@ const Chat = styled.div` @media ${smallOnly} { transform: none !important; + &.no-padding { + padding: 0; + } } `; From ba6b33a83f74352f7120ff6cc925b6c607a0e36a Mon Sep 17 00:00:00 2001 From: danielpetri1 Date: Tue, 30 May 2023 15:59:58 +0000 Subject: [PATCH 015/113] Don't rely on filename to determine MIME type --- .../org/bigbluebutton/presentation/SupportedFileTypes.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/SupportedFileTypes.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/SupportedFileTypes.java index 8483395bb9..6a6a0a3198 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/SupportedFileTypes.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/SupportedFileTypes.java @@ -27,6 +27,8 @@ import static org.bigbluebutton.presentation.FileTypeConstants.*; import org.apache.tika.Tika; import java.io.BufferedReader; import java.io.File; +import java.io.InputStream; +import java.io.FileInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.net.URLConnection; @@ -100,8 +102,8 @@ public final class SupportedFileTypes { return ""; } - try { - return (new Tika()).detect(pres); + try (InputStream presStream = new FileInputStream(pres)) { + return (new Tika()).detect(presStream); } catch (IOException e) { log.error("Error while executing detectMimeType: {}", e); } From 0f64f5ba1a256e9a46dec9c5f6064f35c5c8a807 Mon Sep 17 00:00:00 2001 From: Scroody Date: Mon, 5 Jun 2023 13:42:24 -0300 Subject: [PATCH 016/113] Code replicated to other components --- .../imports/ui/components/notes/component.jsx | 27 +++++++++++++------ .../imports/ui/components/notes/styles.js | 3 +++ .../components/sidebar-content/component.jsx | 25 +++++++++++------ .../ui/components/sidebar-content/styles.js | 3 +++ 4 files changed, 42 insertions(+), 16 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/notes/component.jsx b/bigbluebutton-html5/imports/ui/components/notes/component.jsx index bdf82dc31b..5e93bff0ba 100644 --- a/bigbluebutton-html5/imports/ui/components/notes/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/notes/component.jsx @@ -65,6 +65,12 @@ const Notes = ({ shouldShowSharedNotesOnPresentationArea, }) => { const [shouldRenderNotes, setShouldRenderNotes] = useState(false); + const [isNotesOpen, setNotesOpen] = useState(false); + + const handleToggleNotes = () => { + setNotesOpen((prevState) => !prevState); + }; + const { isChrome } = browserInfo; const isOnMediaArea = area === 'media'; const style = isOnMediaArea ? { @@ -73,9 +79,9 @@ const Notes = ({ } : {}; const isHidden = (isOnMediaArea && (style.width === 0 || style.height === 0)) - || (!isToSharedNotesBeShow - && !sidebarContentToIgnoreDelay.includes(sidebarContent.sidebarContentPanel)) - || shouldShowSharedNotesOnPresentationArea; + || (!isToSharedNotesBeShow + && !sidebarContentToIgnoreDelay.includes(sidebarContent.sidebarContentPanel)) + || shouldShowSharedNotesOnPresentationArea; if (isHidden && !isOnMediaArea) { style.padding = 0; @@ -89,7 +95,7 @@ const Notes = ({ timoutRef = setTimeout(() => { setShouldRenderNotes(false); }, (sidebarContentToIgnoreDelay.includes(sidebarContent.sidebarContentPanel) - || shouldShowSharedNotesOnPresentationArea) + || shouldShowSharedNotesOnPresentationArea) ? 0 : DELAY_UNMOUNT_SHARED_NOTES); } return () => clearTimeout(timoutRef); @@ -115,7 +121,6 @@ const Notes = ({ type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN, value: false, }); - layoutContextDispatch({ type: ACTIONS.SET_SIDEBAR_CONTENT_PANEL, value: PANELS.NONE, @@ -141,8 +146,8 @@ const Notes = ({ value: Session.get('presentationLastState'), }); }; - }else{ - if(shouldShowSharedNotesOnPresentationArea) { + } else { + if (shouldShowSharedNotesOnPresentationArea) { layoutContextDispatch({ type: ACTIONS.SET_NOTES_IS_PINNED, value: true, @@ -168,11 +173,17 @@ const Notes = ({ }; return (shouldRenderNotes || shouldShowSharedNotesOnPresentationArea) && ( - + {!isOnMediaArea ? (
{ + handleToggleNotes; layoutContextDispatch({ type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN, value: false, diff --git a/bigbluebutton-html5/imports/ui/components/notes/styles.js b/bigbluebutton-html5/imports/ui/components/notes/styles.js index cb369b9b83..5b3bdd9b54 100644 --- a/bigbluebutton-html5/imports/ui/components/notes/styles.js +++ b/bigbluebutton-html5/imports/ui/components/notes/styles.js @@ -21,6 +21,9 @@ const Notes = styled.div` @media ${smallOnly} { transform: none !important; + &.no-padding { + padding: 0; + } } `; diff --git a/bigbluebutton-html5/imports/ui/components/sidebar-content/component.jsx b/bigbluebutton-html5/imports/ui/components/sidebar-content/component.jsx index 9ca73eca0a..b840e66f54 100644 --- a/bigbluebutton-html5/imports/ui/components/sidebar-content/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/sidebar-content/component.jsx @@ -56,11 +56,17 @@ const SidebarContent = (props) => { const [isResizing, setIsResizing] = useState(false); const [resizeStartWidth, setResizeStartWidth] = useState(0); const [resizeStartHeight, setResizeStartHeight] = useState(0); + const [isPollOpen, setPollOpen] = useState(false); + + const handleTogglePoll = () => { + setPollOpen((prevState) => !prevState); + }; useEffect(() => { if (!isResizing) { setResizableWidth(width); setResizableHeight(height); + handleTogglePoll; } }, [width, height]); @@ -131,13 +137,13 @@ const SidebarContent = (props) => { }} > {sidebarContentPanel === PANELS.CHAT - && ( - - - - )} + && ( + + + + )} {!isSharedNotesPinned && ( { {sidebarContentPanel === PANELS.BREAKOUT && } {sidebarContentPanel === PANELS.WAITING_USERS && } {sidebarContentPanel === PANELS.POLL && ( - + )} diff --git a/bigbluebutton-html5/imports/ui/components/sidebar-content/styles.js b/bigbluebutton-html5/imports/ui/components/sidebar-content/styles.js index 3ea3a0f1c3..04c43f15c4 100644 --- a/bigbluebutton-html5/imports/ui/components/sidebar-content/styles.js +++ b/bigbluebutton-html5/imports/ui/components/sidebar-content/styles.js @@ -27,6 +27,9 @@ const Poll = styled.div` height: auto; top: ${navbarHeight}; overflow: auto; + &.no-padding { + padding: 0; + } } @media ${mediumUp} { From 09286137c2743dd1ed139c5041d2c7c02fb6e9cd Mon Sep 17 00:00:00 2001 From: Scroody Date: Mon, 5 Jun 2023 16:46:04 -0300 Subject: [PATCH 017/113] Approach to solve issue changed. --- .../imports/ui/components/chat/component.jsx | 8 -------- .../ui/components/layout/layout-manager/customLayout.jsx | 2 +- .../ui/components/nav-bar/settings-dropdown/component.jsx | 2 +- .../imports/ui/components/notes/component.jsx | 7 ------- .../imports/ui/components/sidebar-content/component.jsx | 6 ------ 5 files changed, 2 insertions(+), 23 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/chat/component.jsx b/bigbluebutton-html5/imports/ui/components/chat/component.jsx index eab1cf9a47..0e55df2c6a 100755 --- a/bigbluebutton-html5/imports/ui/components/chat/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/chat/component.jsx @@ -56,12 +56,6 @@ const Chat = (props) => { width, } = props; - const [isMenuOpen, setMenuOpen] = useState(false); - - const handleToggleMenu = () => { - setMenuOpen((prevState) => !prevState); - }; - const userSentMessage = UserSentMessageCollection.findOne({ userId: Auth.userID, sent: true }); const { isChrome } = browserInfo; @@ -73,7 +67,6 @@ const Chat = (props) => {
{ 'data-test': isPublicChat ? 'hidePublicChat' : 'hidePrivateChat', label: title, onClick: () => { - handleToggleMenu; layoutContextDispatch({ type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN, value: false, diff --git a/bigbluebutton-html5/imports/ui/components/layout/layout-manager/customLayout.jsx b/bigbluebutton-html5/imports/ui/components/layout/layout-manager/customLayout.jsx index e5b84a9eeb..07bcea1363 100644 --- a/bigbluebutton-html5/imports/ui/components/layout/layout-manager/customLayout.jsx +++ b/bigbluebutton-html5/imports/ui/components/layout/layout-manager/customLayout.jsx @@ -145,7 +145,7 @@ const CustomLayout = (props) => { sidebarNavPanel: sidebarNavigationInput.sidebarNavPanel, }, sidebarContent: { - isOpen: false, + isOpen: true, sidebarContentPanel: sidebarContentInput.sidebarContentPanel, }, sidebarContentHorizontalResizer: { diff --git a/bigbluebutton-html5/imports/ui/components/nav-bar/settings-dropdown/component.jsx b/bigbluebutton-html5/imports/ui/components/nav-bar/settings-dropdown/component.jsx index 9255019f35..a721743ee4 100644 --- a/bigbluebutton-html5/imports/ui/components/nav-bar/settings-dropdown/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/nav-bar/settings-dropdown/component.jsx @@ -264,7 +264,7 @@ class SettingsDropdown extends PureComponent { icon: 'popout_window', label: intl.formatMessage(intlMessages.openAppLabel), onClick: () => mountModal(), - } + }, ); } diff --git a/bigbluebutton-html5/imports/ui/components/notes/component.jsx b/bigbluebutton-html5/imports/ui/components/notes/component.jsx index 5e93bff0ba..f622a7dc13 100644 --- a/bigbluebutton-html5/imports/ui/components/notes/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/notes/component.jsx @@ -65,11 +65,6 @@ const Notes = ({ shouldShowSharedNotesOnPresentationArea, }) => { const [shouldRenderNotes, setShouldRenderNotes] = useState(false); - const [isNotesOpen, setNotesOpen] = useState(false); - - const handleToggleNotes = () => { - setNotesOpen((prevState) => !prevState); - }; const { isChrome } = browserInfo; const isOnMediaArea = area === 'media'; @@ -177,13 +172,11 @@ const Notes = ({ data-test="notes" isChrome={isChrome} style={style} - className={isNotesOpen ? '' : 'no-padding'} > {!isOnMediaArea ? (
{ - handleToggleNotes; layoutContextDispatch({ type: ACTIONS.SET_SIDEBAR_CONTENT_IS_OPEN, value: false, diff --git a/bigbluebutton-html5/imports/ui/components/sidebar-content/component.jsx b/bigbluebutton-html5/imports/ui/components/sidebar-content/component.jsx index b840e66f54..8f00cd6806 100644 --- a/bigbluebutton-html5/imports/ui/components/sidebar-content/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/sidebar-content/component.jsx @@ -56,17 +56,11 @@ const SidebarContent = (props) => { const [isResizing, setIsResizing] = useState(false); const [resizeStartWidth, setResizeStartWidth] = useState(0); const [resizeStartHeight, setResizeStartHeight] = useState(0); - const [isPollOpen, setPollOpen] = useState(false); - - const handleTogglePoll = () => { - setPollOpen((prevState) => !prevState); - }; useEffect(() => { if (!isResizing) { setResizableWidth(width); setResizableHeight(height); - handleTogglePoll; } }, [width, height]); From ffeb8c3acbe4954b83ff02a913c5bbe026fa88d2 Mon Sep 17 00:00:00 2001 From: danielpetri1 Date: Tue, 6 Jun 2023 10:08:14 +0000 Subject: [PATCH 018/113] Allow multiple MIME types per extension --- bbb-common-web/build.sbt | 4 +- bbb-common-web/project/Dependencies.scala | 3 + .../presentation/MimeTypeUtils.java | 58 +++++++++---------- .../imp/SlidesGenerationProgressNotifier.java | 2 +- .../handlers/presentationConversionUpdate.js | 6 +- .../presentation-uploader-toast/component.jsx | 4 +- .../presentation-uploader/service.js | 2 +- bigbluebutton-web/build.gradle | 2 - 8 files changed, 42 insertions(+), 39 deletions(-) diff --git a/bbb-common-web/build.sbt b/bbb-common-web/build.sbt index b0d487da22..d750095767 100755 --- a/bbb-common-web/build.sbt +++ b/bbb-common-web/build.sbt @@ -109,5 +109,7 @@ libraryDependencies ++= Seq( "org.postgresql" % "postgresql" % "42.4.3", "org.hibernate" % "hibernate-core" % "5.6.1.Final", "org.flywaydb" % "flyway-core" % "7.8.2", - "com.zaxxer" % "HikariCP" % "4.0.3" + "com.zaxxer" % "HikariCP" % "4.0.3", + "org.apache.tika" % "tika-core" % "2.8.0", + "org.apache.tika" % "tika-parsers-standard-package" % "2.8.0" ) diff --git a/bbb-common-web/project/Dependencies.scala b/bbb-common-web/project/Dependencies.scala index c3db90290c..eea63c6324 100644 --- a/bbb-common-web/project/Dependencies.scala +++ b/bbb-common-web/project/Dependencies.scala @@ -23,6 +23,7 @@ object Dependencies { // Office and document conversion val apachePoi = "5.1.0" val nuProcess = "2.0.6" + val tika = "2.8.0" // Server val servlet = "4.0.1" @@ -56,6 +57,7 @@ object Dependencies { val poiXml = "org.apache.poi" % "poi-ooxml" % Versions.apachePoi val nuProcess = "com.zaxxer" % "nuprocess" % Versions.nuProcess + val tika = "org.apache.tika" % "tika-core" % Versions.tika val servletApi = "javax.servlet" % "javax.servlet-api" % Versions.servlet @@ -93,6 +95,7 @@ object Dependencies { Compile.apacheHttpAsync, Compile.poiXml, Compile.nuProcess, + Compile.tika, Compile.servletApi, Compile.apacheLang, Compile.apacheIo, diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/MimeTypeUtils.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/MimeTypeUtils.java index aa8a2d5277..dd913d8616 100644 --- a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/MimeTypeUtils.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/MimeTypeUtils.java @@ -11,6 +11,7 @@ public class MimeTypeUtils { private static final String DOCX = "application/vnd.openxmlformats-officedocument.wordprocessingml.document"; private static final String PPT = "application/vnd.ms-powerpoint"; private static final String PPTX = "application/vnd.openxmlformats-officedocument.presentationml.presentation"; + private static final String TIKA_MSOFFICE = "application/x-tika-msoffice"; private static final String ODT = "application/vnd.oasis.opendocument.text"; private static final String RTF = "application/rtf"; private static final String TXT = "text/plain"; @@ -21,46 +22,45 @@ public class MimeTypeUtils { private static final String PNG = "image/png"; private static final String SVG = "image/svg+xml"; - private static final HashMap EXTENSIONS_MIME = new HashMap(16) { + private static final HashMap> EXTENSIONS_MIME = new HashMap>(16) { { - // Add all the supported files - put(FileTypeConstants.XLS, XLS); - put(FileTypeConstants.XLSX, XLSX); - put(FileTypeConstants.DOC, DOC); - put(FileTypeConstants.DOCX, DOCX); - put(FileTypeConstants.PPT, PPT); - put(FileTypeConstants.PPTX, PPTX); - put(FileTypeConstants.ODT, ODT); - put(FileTypeConstants.RTF, RTF); - put(FileTypeConstants.TXT, TXT); - put(FileTypeConstants.ODS, ODS); - put(FileTypeConstants.ODP, ODP); - put(FileTypeConstants.PDF, PDF); - put(FileTypeConstants.JPG, JPEG); - put(FileTypeConstants.JPEG, JPEG); - put(FileTypeConstants.PNG, PNG); - put(FileTypeConstants.SVG, SVG); + put(FileTypeConstants.DOC, Arrays.asList(DOC, DOCX, TIKA_MSOFFICE)); + put(FileTypeConstants.XLS, Arrays.asList(XLS, XLSX, TIKA_MSOFFICE)); + put(FileTypeConstants.PPT, Arrays.asList(PPT, PPTX, TIKA_MSOFFICE)); + put(FileTypeConstants.DOCX, Arrays.asList(DOCX)); + put(FileTypeConstants.PPTX, Arrays.asList(PPTX)); + put(FileTypeConstants.XLSX, Arrays.asList(XLSX)); + put(FileTypeConstants.ODT, Arrays.asList(ODT)); + put(FileTypeConstants.RTF, Arrays.asList(RTF)); + put(FileTypeConstants.TXT, Arrays.asList(TXT)); + put(FileTypeConstants.ODS, Arrays.asList(ODS)); + put(FileTypeConstants.ODP, Arrays.asList(ODP)); + put(FileTypeConstants.PDF, Arrays.asList(PDF)); + put(FileTypeConstants.JPG, Arrays.asList(JPEG)); + put(FileTypeConstants.JPEG, Arrays.asList(JPEG)); + put(FileTypeConstants.PNG, Arrays.asList(PNG)); + put(FileTypeConstants.SVG, Arrays.asList(SVG)); } }; - + public Boolean extensionMatchMimeType(String mimeType, String finalExtension) { - if(EXTENSIONS_MIME.containsKey(finalExtension.toLowerCase()) && - EXTENSIONS_MIME.get(finalExtension.toLowerCase()).equalsIgnoreCase(mimeType)) { - return true; - } else if(EXTENSIONS_MIME.containsKey(finalExtension.toLowerCase() + 'x') && - EXTENSIONS_MIME.get(finalExtension.toLowerCase() + 'x').equalsIgnoreCase(mimeType)) { - //Exception for MS Office files named with old extension but using internally the new mime type - //e.g. a file named with extension `ppt` but has the content of a `pptx` - return true; - } + finalExtension = finalExtension.toLowerCase(); + if (EXTENSIONS_MIME.containsKey(finalExtension)) { + for (String validMimeType : EXTENSIONS_MIME.get(finalExtension)) { + if (validMimeType.equalsIgnoreCase(mimeType)) { + return true; + } + } + } + return false; } public List getValidMimeTypes() { List validMimeTypes = Arrays.asList(XLS, XLSX, DOC, DOCX, PPT, PPTX, ODT, RTF, TXT, ODS, ODP, - PDF, JPEG, PNG, SVG + PDF, JPEG, PNG, SVG, TIKA_MSOFFICE ); return validMimeTypes; } diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/SlidesGenerationProgressNotifier.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/SlidesGenerationProgressNotifier.java index 9f8a9bcdb8..ea64b56f5d 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/SlidesGenerationProgressNotifier.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/imp/SlidesGenerationProgressNotifier.java @@ -58,7 +58,7 @@ public class SlidesGenerationProgressNotifier { pres.getTemporaryPresentationId(), pres.getName(), pres.getAuthzToken(), - "IVALID_MIME_TYPE", + "INVALID_MIME_TYPE", fileMime, fileExtension ); diff --git a/bigbluebutton-html5/imports/api/presentations/server/handlers/presentationConversionUpdate.js b/bigbluebutton-html5/imports/api/presentations/server/handlers/presentationConversionUpdate.js index 377c5f0bd6..996e5c6aeb 100755 --- a/bigbluebutton-html5/imports/api/presentations/server/handlers/presentationConversionUpdate.js +++ b/bigbluebutton-html5/imports/api/presentations/server/handlers/presentationConversionUpdate.js @@ -12,8 +12,8 @@ const PAGE_COUNT_EXCEEDED_KEY = 'PAGE_COUNT_EXCEEDED'; const PDF_HAS_BIG_PAGE_KEY = 'PDF_HAS_BIG_PAGE'; const GENERATED_SLIDE_KEY = 'GENERATED_SLIDE'; const FILE_TOO_LARGE_KEY = 'FILE_TOO_LARGE'; -const CONVERSION_TIMEOUT_KEY = "CONVERSION_TIMEOUT"; -const IVALID_MIME_TYPE_KEY = "IVALID_MIME_TYPE"; +const CONVERSION_TIMEOUT_KEY = 'CONVERSION_TIMEOUT'; +const INVALID_MIME_TYPE_KEY = 'INVALID_MIME_TYPE'; const NO_CONTENT = '204'; // const GENERATING_THUMBNAIL_KEY = 'GENERATING_THUMBNAIL'; // const GENERATED_THUMBNAIL_KEY = 'GENERATED_THUMBNAIL'; @@ -52,7 +52,7 @@ export default async function handlePresentationConversionUpdate({ body }, meeti statusModifier['conversion.maxFileSize'] = body.maxFileSize; case UNSUPPORTED_DOCUMENT_KEY: case OFFICE_DOC_CONVERSION_FAILED_KEY: - case IVALID_MIME_TYPE_KEY: + case INVALID_MIME_TYPE_KEY: statusModifier['conversion.error'] = true; statusModifier['conversion.fileMime'] = body.fileMime; statusModifier['conversion.fileExtension'] = body.fileExtension; diff --git a/bigbluebutton-html5/imports/ui/components/presentation/presentation-toast/presentation-uploader-toast/component.jsx b/bigbluebutton-html5/imports/ui/components/presentation/presentation-toast/presentation-uploader-toast/component.jsx index f0be52384e..c87a2549f2 100644 --- a/bigbluebutton-html5/imports/ui/components/presentation/presentation-toast/presentation-uploader-toast/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/presentation/presentation-toast/presentation-uploader-toast/component.jsx @@ -54,7 +54,7 @@ const intlMessages = defineMessages({ id: 'app.presentationUploder.upload.413', description: 'error that file exceed the size limit', }, - IVALID_MIME_TYPE: { + INVALID_MIME_TYPE: { id: 'app.presentationUploder.conversion.invalidMimeType', description: 'warns user that the file\'s mime type is not supported or it doesn\'t match the extension', }, @@ -151,7 +151,7 @@ function renderPresentationItemStatus(item, intl) { case 'PDF_HAS_BIG_PAGE': constraint['0'] = (item.conversion.bigPageSize / 1000 / 1000).toFixed(2); break; - case 'IVALID_MIME_TYPE': + case 'INVALID_MIME_TYPE': constraint['0'] = item.conversion.fileExtension; constraint['1'] = item.conversion.fileMime; break; diff --git a/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/service.js b/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/service.js index 8ce5da2963..27cb2fb831 100644 --- a/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/service.js +++ b/bigbluebutton-html5/imports/ui/components/presentation/presentation-uploader/service.js @@ -108,7 +108,7 @@ const observePresentationConversion = ( if (doc.temporaryPresentationId !== temporaryPresentationId && doc.id !== tokenId) return; if (doc.conversion.status === 'FILE_TOO_LARGE' || doc.conversion.status === 'UNSUPPORTED_DOCUMENT' - || doc.conversion.status === 'CONVERSION_TIMEOUT' || doc.conversion.status === 'IVALID_MIME_TYPE') { + || doc.conversion.status === 'CONVERSION_TIMEOUT' || doc.conversion.status === 'INVALID_MIME_TYPE') { Presentations.update( { id: tokenId }, { $set: { temporaryPresentationId, renderedInToast: false } }, ); diff --git a/bigbluebutton-web/build.gradle b/bigbluebutton-web/build.gradle index 7fdf065589..27bee504cb 100755 --- a/bigbluebutton-web/build.gradle +++ b/bigbluebutton-web/build.gradle @@ -54,8 +54,6 @@ repositories { dependencies { runtimeOnly "com.bertramlabs.plugins:asset-pipeline-grails:4.0.0" - runtimeOnly 'org.apache.tika:tika-core:2.8.0' - runtimeOnly 'org.apache.tika:tika-parsers-standard-package:2.8.0' implementation "org.springframework:spring-core:5.3.21" implementation "org.springframework:spring-context:5.3.27" From bc090d71bbf3ea1b800073a244fa39b7dcbb69cc Mon Sep 17 00:00:00 2001 From: danielpetri1 Date: Tue, 6 Jun 2023 12:08:21 +0000 Subject: [PATCH 019/113] Permit .docx with .doc content --- .../bigbluebutton/presentation/MimeTypeUtils.java | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/MimeTypeUtils.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/MimeTypeUtils.java index dd913d8616..fa29c20541 100644 --- a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/MimeTypeUtils.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/MimeTypeUtils.java @@ -12,6 +12,7 @@ public class MimeTypeUtils { private static final String PPT = "application/vnd.ms-powerpoint"; private static final String PPTX = "application/vnd.openxmlformats-officedocument.presentationml.presentation"; private static final String TIKA_MSOFFICE = "application/x-tika-msoffice"; + private static final String TIKA_MSOFFICE_X = "application/x-tika-ooxml"; private static final String ODT = "application/vnd.oasis.opendocument.text"; private static final String RTF = "application/rtf"; private static final String TXT = "text/plain"; @@ -24,12 +25,12 @@ public class MimeTypeUtils { private static final HashMap> EXTENSIONS_MIME = new HashMap>(16) { { - put(FileTypeConstants.DOC, Arrays.asList(DOC, DOCX, TIKA_MSOFFICE)); - put(FileTypeConstants.XLS, Arrays.asList(XLS, XLSX, TIKA_MSOFFICE)); - put(FileTypeConstants.PPT, Arrays.asList(PPT, PPTX, TIKA_MSOFFICE)); - put(FileTypeConstants.DOCX, Arrays.asList(DOCX)); - put(FileTypeConstants.PPTX, Arrays.asList(PPTX)); - put(FileTypeConstants.XLSX, Arrays.asList(XLSX)); + put(FileTypeConstants.DOC, Arrays.asList(DOC, DOCX, TIKA_MSOFFICE, TIKA_MSOFFICE_X)); + put(FileTypeConstants.XLS, Arrays.asList(XLS, XLSX, TIKA_MSOFFICE, TIKA_MSOFFICE_X)); + put(FileTypeConstants.PPT, Arrays.asList(PPT, PPTX, TIKA_MSOFFICE, TIKA_MSOFFICE_X)); + put(FileTypeConstants.DOCX, Arrays.asList(DOC, DOCX, TIKA_MSOFFICE, TIKA_MSOFFICE_X)); + put(FileTypeConstants.PPTX, Arrays.asList(PPT, PPTX, TIKA_MSOFFICE, TIKA_MSOFFICE_X)); + put(FileTypeConstants.XLSX, Arrays.asList(XLS, XLSX, TIKA_MSOFFICE, TIKA_MSOFFICE_X)); put(FileTypeConstants.ODT, Arrays.asList(ODT)); put(FileTypeConstants.RTF, Arrays.asList(RTF)); put(FileTypeConstants.TXT, Arrays.asList(TXT)); @@ -60,7 +61,7 @@ public class MimeTypeUtils { public List getValidMimeTypes() { List validMimeTypes = Arrays.asList(XLS, XLSX, DOC, DOCX, PPT, PPTX, ODT, RTF, TXT, ODS, ODP, - PDF, JPEG, PNG, SVG, TIKA_MSOFFICE + PDF, JPEG, PNG, SVG, TIKA_MSOFFICE, TIKA_MSOFFICE_X ); return validMimeTypes; } From e138d17b4f8cad260fcc72ac3d8e3d238339271c Mon Sep 17 00:00:00 2001 From: KDSBrowne Date: Wed, 7 Jun 2023 05:12:07 +0000 Subject: [PATCH 020/113] preserve canvas position on slide change --- .../ui/components/whiteboard/component.jsx | 25 ++++++++++++++++--- 1 file changed, 21 insertions(+), 4 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/component.jsx index 56aba71daf..c900c8f975 100644 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/component.jsx @@ -96,6 +96,7 @@ export default function Whiteboard(props) { const language = mapLanguage(Settings?.application?.locale?.toLowerCase() || 'en'); const [currentTool, setCurrentTool] = React.useState(null); const [currentStyle, setCurrentStyle] = React.useState({}); + const [currentCameraPoint, setCurrentCameraPoint] = React.useState({}); const [isMoving, setIsMoving] = React.useState(false); const [isPanning, setIsPanning] = React.useState(shortcutPanning); const [panSelected, setPanSelected] = React.useState(isPanning); @@ -733,6 +734,9 @@ export default function Whiteboard(props) { if (reason && isPresenter && slidePosition && (reason.includes('zoomed') || reason.includes('panned'))) { const camera = tldrawAPI?.getPageState()?.camera; + if (currentCameraPoint[curPageId] && !isPanning) { + camera.point = currentCameraPoint[curPageId]; + } // limit bounds if (tldrawAPI?.viewport.maxX > slidePosition.width) { @@ -747,13 +751,12 @@ export default function Whiteboard(props) { if (camera.point[1] > 0 || tldrawAPI?.viewport.minY < 0) { camera.point[1] = 0; } + const zoomFitSlide = calculateZoom(slidePosition.width, slidePosition.height); if (camera.zoom < zoomFitSlide) { camera.zoom = zoomFitSlide; } - tldrawAPI?.setCamera([camera.point[0], camera.point[1]], camera.zoom); - const zoomToolbar = Math.round(((HUNDRED_PERCENT * camera.zoom) / zoomFitSlide) * 100) / 100; if (zoom !== zoomToolbar) { setZoom(zoomToolbar); @@ -772,13 +775,20 @@ export default function Whiteboard(props) { viewedRegionH = HUNDRED_PERCENT; } + if (e?.currentPageId == curPageId) { + setCurrentCameraPoint({ + ...currentCameraPoint, + [e?.currentPageId]: camera?.point, + }) + } + zoomSlide( parseInt(curPageId, 10), podId, viewedRegionW, viewedRegionH, - camera.point[0], - camera.point[1], + currentCameraPoint[curPageId] ? currentCameraPoint[curPageId][0] : camera.point[0], + currentCameraPoint[curPageId] ? currentCameraPoint[curPageId][1] : camera.point[1], ); } // don't allow non-presenters to pan&zoom @@ -917,6 +927,13 @@ export default function Whiteboard(props) { setCurrentStyle({ ...currentStyle, ...command?.after?.appState?.currentStyle }); } + if (command && command?.id?.includes('change_page')) { + const camera = tldrawAPI?.getPageState()?.camera; + if (currentCameraPoint[app?.currentPageId]) { + tldrawAPI?.setCamera([currentCameraPoint[app?.currentPageId][0], currentCameraPoint[app?.currentPageId][1]], camera.zoom); + } + } + const changedShapes = command.after?.document?.pages[app.currentPageId]?.shapes; if (!isMounting && app.currentPageId !== curPageId) { // can happen then the "move to page action" is called, or using undo after changing a page From a6d353a7b17aa144edf613cb8665a6f9a0dc1630 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Wed, 7 Jun 2023 22:46:47 +0900 Subject: [PATCH 021/113] Update component.jsx --- .../whiteboard/cursors/cursor/component.jsx | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/cursors/cursor/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/cursors/cursor/component.jsx index 478036c397..bd8e86b938 100644 --- a/bigbluebutton-html5/imports/ui/components/whiteboard/cursors/cursor/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/whiteboard/cursors/cursor/component.jsx @@ -1,5 +1,8 @@ import React from 'react'; import PropTypes from 'prop-types'; +import { Meteor } from 'meteor/meteor'; + +const { pointerDiameter } = Meteor.settings.public.whiteboard; const Cursor = (props) => { const { @@ -28,10 +31,10 @@ const Cursor = (props) => { style={{ zIndex: z, position: 'absolute', - left: (_x || x) - 2.5, - top: (_y || y) - 2.5, - width: 5, - height: 5, + left: (_x || x) - pointerDiameter/2, + top: (_y || y) - pointerDiameter/2, + width: pointerDiameter, + height: pointerDiameter, borderRadius: '50%', background: `${color}`, pointerEvents: 'none', From 97f660e268f441cd567758fa20285ffadbeea9cc Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Wed, 7 Jun 2023 22:47:34 +0900 Subject: [PATCH 022/113] Update settings.yml --- bigbluebutton-html5/private/config/settings.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/bigbluebutton-html5/private/config/settings.yml b/bigbluebutton-html5/private/config/settings.yml index 6235069816..0cf013c8a2 100755 --- a/bigbluebutton-html5/private/config/settings.yml +++ b/bigbluebutton-html5/private/config/settings.yml @@ -759,6 +759,7 @@ public: whiteboard: annotationsQueueProcessInterval: 60 cursorInterval: 150 + pointerDiameter: 10 maxStickyNoteLength: 1000 # limit number of annotations per slide maxNumberOfAnnotations: 300 From 4da8b099113be13844403441bee34c60aa161019 Mon Sep 17 00:00:00 2001 From: Gabriel Porfirio Date: Wed, 7 Jun 2023 11:19:09 -0300 Subject: [PATCH 023/113] joining disabledFeatures test with disabledFeaturesExclude test --- .../playwright/core/elements.js | 2 +- .../playwright/customparameters/constants.js | 18 ++ .../customparameters/customparameters.js | 86 +++++++ .../customparameters/customparameters.spec.js | 243 +++++++++++++----- 4 files changed, 283 insertions(+), 66 deletions(-) diff --git a/bigbluebutton-tests/playwright/core/elements.js b/bigbluebutton-tests/playwright/core/elements.js index 8e1f078695..49529b77d0 100644 --- a/bigbluebutton-tests/playwright/core/elements.js +++ b/bigbluebutton-tests/playwright/core/elements.js @@ -153,7 +153,7 @@ exports.webcamMirroredVideoContainer = 'video[data-test="mirroredVideoContainer" exports.usersList = 'div[data-test="userList"]'; exports.selectCameraQualityId = 'select[id="setQuality"]'; exports.virtualBackgrounds = 'div[data-test="virtualBackground"]'; -exports.liveTranscription = 'div[data-test="liveTranscription"]'; +exports.liveTranscription = 'div[data-test="speechRecognition"]'; exports.learningDashboard = 'li[data-test="learningDashboard"]'; exports.propagateLayout = 'li[data-test="propagateLayout"]'; exports.layoutModal = 'li[data-test="layoutModal"]'; diff --git a/bigbluebutton-tests/playwright/customparameters/constants.js b/bigbluebutton-tests/playwright/customparameters/constants.js index 47d9e3936c..68082f53b4 100644 --- a/bigbluebutton-tests/playwright/customparameters/constants.js +++ b/bigbluebutton-tests/playwright/customparameters/constants.js @@ -57,6 +57,24 @@ exports.layouts = 'disabledFeatures=layouts'; exports.presentation = 'disabledFeatures=presentation'; exports.customVirtualBackground = 'disabledFeatures=customVirtualBackgrounds'; +// Disabled Features Exclude +exports.breakoutRoomsExclude = 'disabledFeatures=breakoutRooms,presentation,chat&disabledFeaturesExclude=breakoutRooms'; +exports.liveTranscriptionExclude = 'disabledFeatures=breakoutRooms,presentation,chat,liveTranscription&disabledFeaturesExclude=liveTranscription'; +exports.captionsExclude = 'disabledFeatures=captions,presentation,chat&disabledFeaturesExclude=captions'; +exports.chatExclude = 'disabledFeatures=presentation,chat&disabledFeaturesExclude=chat'; +exports.externalVideosExclude = 'disabledFeatures=presentation,chat,externalVideos&disabledFeaturesExclude=externalVideos'; +exports.layoutsExclude = 'disabledFeatures=presentation,chat,layouts&disabledFeaturesExclude=layouts'; +exports.learningDashboardExclude = 'disabledFeatures=presentation,chat,learningDashboard&disabledFeaturesExclude=learningDashboard'; +exports.pollsExclude = 'disabledFeatures=layouts,polls&disabledFeaturesExclude=polls'; +exports.screenshareExclude = 'disabledFeatures=presentation,chat,screenshare&disabledFeaturesExclude=screenshare'; +exports.sharedNotesExclude = 'disabledFeatures=presentation,chat,sharedNotes&disabledFeaturesExclude=sharedNotes'; +exports.virtualBackgroundsExclude = 'disabledFeatures=presentation,chat,virtualBackgrounds&disabledFeaturesExclude=virtualBackgrounds'; +exports.downloadPresentationWithAnnotationsExclude = 'disabledFeatures=chat,downloadPresentationWithAnnotations&disabledFeaturesExclude=downloadPresentationWithAnnotations'; +exports.importPresentationWithAnnotationsFromBreakoutRoomsExclude = 'disabledFeatures=presentation,chat,importPresentationWithAnnotationsFromBreakoutRooms&disabledFeaturesExclude=importPresentationWithAnnotationsFromBreakoutRooms'; +exports.importSharedNotesFromBreakoutRoomsExclude = 'disabledFeatures=presentation,chat,importSharedNotesFromBreakoutRooms&disabledFeaturesExclude=importSharedNotesFromBreakoutRooms'; +exports.presentationExclude = 'disabledFeatures=presentation,chat&disabledFeaturesExclude=presentation'; +exports.customVirtualBackgroundExclude = 'disabledFeatures=presentation,chat,customVirtualBackground&disabledFeaturesExclude=customVirtualBackground'; + // Shortcuts exports.shortcuts = 'userdata-bbb_shortcuts=[$]'; exports.initialShortcuts = [{ diff --git a/bigbluebutton-tests/playwright/customparameters/customparameters.js b/bigbluebutton-tests/playwright/customparameters/customparameters.js index 0ce28cae65..0c41ebb528 100644 --- a/bigbluebutton-tests/playwright/customparameters/customparameters.js +++ b/bigbluebutton-tests/playwright/customparameters/customparameters.js @@ -317,6 +317,92 @@ class CustomParameters extends MultiUsers { await this.modPage.waitForSelector(e.webcamSettingsModal); await this.modPage.wasRemoved(e.inputBackgroundButton); } + + // Disabled Features Exclude + async breakoutRoomsExclude() { + await this.modPage.waitAndClick(e.manageUsers); + await this.modPage.hasElement(e.createBreakoutRooms); + } + + async liveTranscriptionExclude() { + const { speechRecognitionEnabled } = getSettings(); + test.fail(!speechRecognitionEnabled, 'Live Transcription is disabled'); + await this.modPage.waitForSelector(e.audioModal, ELEMENT_WAIT_LONGER_TIME); + await this.modPage.hasElement(e.liveTranscription); + } + + async captionsExclude() { + await this.modPage.waitAndClick(e.manageUsers); + await this.modPage.hasElement(e.writeClosedCaptions); + } + + async chatExclude() { + await this.modPage.hasElement(e.publicChat); + } + + async externalVideosExclude() { + await this.modPage.waitAndClick(e.actions); + await this.modPage.hasElement(e.shareExternalVideoBtn); + } + + async layoutsExclude() { + await this.modPage.waitAndClick(e.actions); + await this.modPage.hasElement(e.propagateLayout); + await this.modPage.hasElement(e.layoutModal); + } + + async learningDashboardExclude() { + await this.modPage.waitAndClick(e.manageUsers); + await this.modPage.hasElement(e.learningDashboard); + } + + async pollsExclude() { + await this.modPage.waitAndClick(e.actions); + await this.modPage.hasElement(e.polling); + } + + async screenshareExclude() { + await this.modPage.hasElement(e.startScreenSharing); + } + + async sharedNotesExclude() { + await this.modPage.hasElement(e.sharedNotes); + } + + async virtualBackgroundsExclude() { + await this.modPage.waitAndClick(e.joinVideo); + await this.modPage.hasElement(e.virtualBackgrounds); + } + + async downloadPresentationWithAnnotationsExclude() { + await this.modPage.waitAndClick(e.actions); + await this.modPage.waitAndClick(e.managePresentations); + await this.modPage.hasElement(e.exportPresentationToPublicChat); + } + + async importPresentationWithAnnotationsFromBreakoutRoomsExclude() { + await this.modPage.waitAndClick(e.manageUsers); + await this.modPage.waitAndClick(e.createBreakoutRooms); + await this.modPage.hasElement(e.captureBreakoutWhiteboard); + } + + async importSharedNotesFromBreakoutRoomsExclude() { + await this.modPage.waitAndClick(e.manageUsers); + await this.modPage.waitAndClick(e.createBreakoutRooms); + await this.modPage.hasElement(e.captureBreakoutSharedNotes); + } + + async presentationExclude() { + await this.modPage.hasElement(e.whiteboard); + await this.modPage.waitAndClick(e.minimizePresentation); + await this.modPage.hasElement(e.restorePresentation); + } + + async customVirtualBackgroundExclude() { + await this.modPage.waitAndClick (e.joinVideo); + await this.modPage.waitForSelector(e.webcamSettingsModal); + await this.modPage.hasElement(e.inputBackgroundButton); + } } exports.CustomParameters = CustomParameters; diff --git a/bigbluebutton-tests/playwright/customparameters/customparameters.spec.js b/bigbluebutton-tests/playwright/customparameters/customparameters.spec.js index 44145d746a..cdb144b336 100644 --- a/bigbluebutton-tests/playwright/customparameters/customparameters.spec.js +++ b/bigbluebutton-tests/playwright/customparameters/customparameters.spec.js @@ -2,6 +2,7 @@ const { test } = require('@playwright/test'); const { CustomParameters } = require('./customparameters'); const c = require('./constants'); const { encodeCustomParams, getAllShortcutParams, hexToRgb } = require('./util'); +const { fullName } = require('../core/parameters'); test.describe.parallel('CustomParameters', () => { test('Show Public Chat On Login', async ({ browser, context, page }) => { @@ -209,101 +210,213 @@ test.describe.parallel('CustomParameters', () => { }); }); - test.describe.parallel('Disabled Features @ci', () => { - test('Breakout Rooms', async ({ browser, context, page }) => { - const customParam = new CustomParameters(browser, context); - await customParam.initModPage(page, true, { customParameter: c.breakoutRooms }); - await customParam.breakoutRooms(); + test.describe.parallel.only('Disabled Features @ci', () => { + test.describe.serial(() => { + test('Breakout rooms', async ({ browser, context, page}) => { + const customParam = new CustomParameters(browser, context); + await customParam.initModPage(page, true, { customParameter: c.breakoutRooms }); + await customParam.breakoutRooms(); + }); + test('Breakout rooms (exclude)', async ({ browser, context, page}) => { + const customParam = new CustomParameters(browser, context); + await customParam.initModPage(page, true, { customParameter: c.breakoutRoomsExclude }); + await customParam.breakoutRoomsExclude(); + }); }); - test('Live Transcription', async ({ browser, context, page }) => { - const customParam = new CustomParameters(browser, context); - await customParam.initModPage(page, false, { customParameter: c.liveTranscription }); - await customParam.liveTranscription(); + test.describe.serial(() => { + test('Live Transcription', async ({ browser, context, page}) => { + const customParam = new CustomParameters(browser, context); + await customParam.initModPage(page, false, { customParameter: c.liveTranscription }); + await customParam.liveTranscription(); + }); + test('Live Transcription (exclude)', async ({ browser, context, page}) => { + const customParam = new CustomParameters(browser, context); + await customParam.initModPage(page, false, { customParameter: c.liveTranscriptionExclude }); + await customParam.liveTranscriptionExclude(); + }); }); - test('Captions', async ({ browser, context, page }) => { - const customParam = new CustomParameters(browser, context); - await customParam.initModPage(page, true, { customParameter: c.captions }); - await customParam.captions(); + test.describe.serial(() => { + test('Captions', async ({ browser, context, page }) => { + const customParam = new CustomParameters(browser, context); + await customParam.initModPage(page, true, { customParameter: c.captions }); + await customParam.captions(); + }); + test('Captions (exclude)', async ({ browser, context, page }) => { + const customParam = new CustomParameters(browser, context); + await customParam.initModPage(page, true, { customParameter: c.captionsExclude }); + await customParam.captionsExclude(); + }); }); - test('Chat', async ({ browser, context, page }) => { - const customParam = new CustomParameters(browser, context); - await customParam.initModPage(page, true, { customParameter: c.chat }); - await customParam.chat(); + test.describe.serial(() => { + test('Chat', async ({ browser, context, page }) => { + const customParam = new CustomParameters(browser, context); + await customParam.initModPage(page, true, { customParameter: c.chat }); + await customParam.chat(); + }); + test('Chat (exclude)', async ({ browser, context, page }) => { + const customParam = new CustomParameters(browser, context); + await customParam.initModPage(page, true, { customParameter: c.chatExclude }); + await customParam.chatExclude(); + }); }); - test('External Videos', async ({ browser, context, page }) => { - const customParam = new CustomParameters(browser, context); - await customParam.initModPage(page, true, { customParameter: c.externalVideos }); - await customParam.externalVideos(); + test.describe.serial(() => { + test('External Videos', async ({ browser, context, page }) => { + const customParam = new CustomParameters(browser, context); + await customParam.initModPage(page, true, { customParameter: c.externalVideos }); + await customParam.externalVideos(); + }); + test('External Videos (exclude)', async ({ browser, context, page }) => { + const customParam = new CustomParameters(browser, context); + await customParam.initModPage(page, true, { customParameter: c.externalVideosExclude }); + await customParam.externalVideosExclude(); + }); }); - test('Layouts', async ({ browser, context, page }) => { - const customParam = new CustomParameters(browser, context); - await customParam.initModPage(page, true, { customParameter: c.layouts }); - await customParam.layouts(); + test.describe.serial(() => { + test('Layouts', async ({ browser, context, page }) => { + const customParam = new CustomParameters(browser, context); + await customParam.initModPage(page, true, { customParameter: c.layouts }); + await customParam.layouts(); + }); + test('Layouts (exclude)', async ({ browser, context, page }) => { + const customParam = new CustomParameters(browser, context); + await customParam.initModPage(page, true, { customParameter: c.layoutsExclude }); + await customParam.layoutsExclude(); + }); }); - test('Learning Dashboard', async ({ browser, context, page }) => { - const customParam = new CustomParameters(browser, context); - await customParam.initModPage(page, true, { customParameter: c.learningDashboard }); - await customParam.learningDashboard(); + test.describe.serial(() => { + test('Learning Dashboard', async ({ browser, context, page }) => { + const customParam = new CustomParameters(browser, context); + await customParam.initModPage(page, true, { customParameter: c.learningDashboard }); + await customParam.learningDashboard(); + }); + test('Learning Dashboard (exclude)', async ({ browser, context, page }) => { + const customParam = new CustomParameters(browser, context); + await customParam.initModPage(page, true, { customParameter: c.learningDashboardExclude }); + await customParam.learningDashboardExclude(); + }); }); - test('Polls', async ({ browser, context, page }) => { - const customParam = new CustomParameters(browser, context); - await customParam.initModPage(page, true, { customParameter: c.polls }); - await customParam.polls(); + test.describe.serial(() => { + test('Polls', async ({ browser, context, page }) => { + const customParam = new CustomParameters(browser, context); + await customParam.initModPage(page, true, { customParameter: c.polls }); + await customParam.polls(); + }); + test('Polls (exclude)', async ({ browser, context, page }) => { + const customParam = new CustomParameters(browser, context); + await customParam.initModPage(page, true, { customParameter: c.pollsExclude }); + await customParam.pollsExclude(); + }); }); - test('Screenshare', async ({ browser, context, page }) => { - const customParam = new CustomParameters(browser, context); - await customParam.initModPage(page, true, { customParameter: c.screenshare }); - await customParam.screenshare(); + test.describe.serial(() => { + test('Screenshare', async ({ browser, context, page }) => { + const customParam = new CustomParameters(browser, context); + await customParam.initModPage(page, true, { customParameter: c.screenshare }); + await customParam.screenshare(); + }); + test('Screenshare (exclude)', async ({ browser, context, page }) => { + const customParam = new CustomParameters(browser, context); + await customParam.initModPage(page, true, { customParameter: c.screenshareExclude }); + await customParam.screenshareExclude(); + }); }); - test('Shared Notes', async ({ browser, context, page }) => { - const customParam = new CustomParameters(browser, context); - await customParam.initModPage(page, true, { customParameter: c.sharedNotes }); - await customParam.sharedNotes(); + test.describe.serial(() => { + test('Shared Notes', async ({ browser, context, page }) => { + const customParam = new CustomParameters(browser, context); + await customParam.initModPage(page, true, { customParameter: c.sharedNotes }); + await customParam.sharedNotes(); + }); + test('Shared Notes (exclude)', async ({ browser, context, page }) => { + const customParam = new CustomParameters(browser, context); + await customParam.initModPage(page, true, { customParameter: c.sharedNotesExclude }); + await customParam.sharedNotesExclude(); + }); }); - test('Virtual Backgrounds', async ({ browser, context, page }) => { - const customParam = new CustomParameters(browser, context); - await customParam.initModPage(page, true, { customParameter: c.virtualBackgrounds }); - await customParam.virtualBackgrounds(); + test.describe.serial(() => { + test('Virtual Background', async ({ browser, context, page }) => { + const customParam = new CustomParameters(browser, context); + await customParam.initModPage(page, true, { customParameter: c.virtualBackgrounds }); + await customParam.virtualBackgrounds(); + }); + test('Virtual Background (exclude)', async ({ browser, context, page }) => { + const customParam = new CustomParameters(browser, context); + await customParam.initModPage(page, true, { customParameter: c.virtualBackgroundsExclude }); + await customParam.virtualBackgroundsExclude(); + }); }); - test('Download Presentation With Annotations', async ({ browser, context, page }) => { - const customParam = new CustomParameters(browser, context); - await customParam.initModPage(page, true, { customParameter: c.downloadPresentationWithAnnotations }); - await customParam.downloadPresentationWithAnnotations(); + test.describe.serial(() => { + test('Download Presentation With Annotations', async ({ browser, context, page }) => { + const customParam = new CustomParameters(browser, context); + await customParam.initModPage(page, true, { customParameter: c.downloadPresentationWithAnnotations }); + await customParam.downloadPresentationWithAnnotations(); + }); + test('Download Presentation With Annotations (exclude)', async ({ browser, context, page }) => { + const customParam = new CustomParameters(browser, context); + await customParam.initModPage(page, true, { customParameter: c.downloadPresentationWithAnnotationsExclude }); + await customParam.downloadPresentationWithAnnotationsExclude(); + }); }); - test('Import Presentation With Annotations From Breakout Rooms', async ({ browser, context, page }) => { - const customParam = new CustomParameters(browser, context); - await customParam.initModPage(page, true, { customParameter: c.importPresentationWithAnnotationsFromBreakoutRooms }); - await customParam.importPresentationWithAnnotationsFromBreakoutRooms(); + test.describe.serial(() => { + test('Import Presentation With Annotations From Breakout Rooms', async ({ browser, context, page }) => { + const customParam = new CustomParameters(browser, context); + await customParam.initModPage(page, true, { customParameter: c.importPresentationWithAnnotationsFromBreakoutRooms }); + await customParam.importPresentationWithAnnotationsFromBreakoutRooms(); + }); + test('Import Presentation With Annotations From Breakout Rooms (exclude)', async ({ browser, context, page }) => { + const customParam = new CustomParameters(browser, context); + await customParam.initModPage(page, true, { customParameter: c.importPresentationWithAnnotationsFromBreakoutRoomsExclude }); + await customParam.importPresentationWithAnnotationsFromBreakoutRoomsExclude(); + }); }); - test('Import Shared Notes From Breakout Rooms', async ({ browser, context, page }) => { - const customParam = new CustomParameters(browser, context); - await customParam.initModPage(page, true, { customParameter: c.importSharedNotesFromBreakoutRooms }); - await customParam.importSharedNotesFromBreakoutRooms(); + test.describe.serial(() => { + test('Import Shared Notes From Breakout Rooms', async ({ browser, context, page }) => { + const customParam = new CustomParameters(browser, context); + await customParam.initModPage(page, true, { customParameter: c.importSharedNotesFromBreakoutRooms }); + await customParam.importSharedNotesFromBreakoutRooms(); + }); + test('Import Shared Notes From Breakout Rooms (exclude)', async ({ browser, context, page }) => { + const customParam = new CustomParameters(browser, context); + await customParam.initModPage(page, true, { customParameter: c.importSharedNotesFromBreakoutRoomsExclude }); + await customParam.importSharedNotesFromBreakoutRoomsExclude(); + }); }); - test('Presentation', async ({ browser, context, page }) => { - const customParam = new CustomParameters(browser, context); - await customParam.initModPage(page, true, { customParameter: c.presentation }); - await customParam.presentation(); + test.describe.serial(() => { + test('Presentation', async ({ browser, context, page }) => { + const customParam = new CustomParameters(browser, context); + await customParam.initModPage(page, true, { customParameter: c.presentation }); + await customParam.presentation(); + }); + test('Presentation (exclude)', async ({ browser, context, page }) => { + const customParam = new CustomParameters(browser, context); + await customParam.initModPage(page, true, { customParameter: c.presentationExclude }); + await customParam.presentationExclude(); + }); }); - test('Custom Virtual Background', async ({ browser, context, page }) => { - const customParam = new CustomParameters(browser, context); - await customParam.initModPage(page, true, { customParameter: c.customVirtualBackground }); - await customParam.customVirtualBackground(); + test.describe.serial(() => { + test('Custom Virtual Background', async ({ browser, context, page }) => { + const customParam = new CustomParameters(browser, context); + await customParam.initModPage(page, true, { customParameter: c.customVirtualBackground }); + await customParam.customVirtualBackground(); + }); + test('Custom Virtual Background (exclude)', async ({ browser, context, page }) => { + const customParam = new CustomParameters(browser, context); + await customParam.initModPage(page, true, { customParameter: c.customVirtualBackgroundExclude }); + await customParam.customVirtualBackgroundExclude(); + }); }); }); }); From 37691f65b13e895dbd2a5a5438bbc32835e2326a Mon Sep 17 00:00:00 2001 From: Scroody Date: Wed, 7 Jun 2023 11:42:27 -0300 Subject: [PATCH 024/113] Fix: Presentation area in front of talking indicator elements --- .../nav-bar/talking-indicator/styles.js | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/bigbluebutton-html5/imports/ui/components/nav-bar/talking-indicator/styles.js b/bigbluebutton-html5/imports/ui/components/nav-bar/talking-indicator/styles.js index 5aaf86e6cb..6677a1753a 100644 --- a/bigbluebutton-html5/imports/ui/components/nav-bar/talking-indicator/styles.js +++ b/bigbluebutton-html5/imports/ui/components/nav-bar/talking-indicator/styles.js @@ -63,7 +63,7 @@ const TalkingIndicatorButton = styled(Button)` font-size: ${fontSizeXS}; } - [dir="rtl"] & { + [dir='rtl'] & { margin-left: ${talkerMarginSm}; } } @@ -84,7 +84,7 @@ const TalkingIndicatorButton = styled(Button)` font-size: ${fontSizeXS}; } - [dir="rtl"] & { + [dir='rtl'] & { right: calc(${talkerMarginSm} * -1); } } @@ -93,7 +93,8 @@ const TalkingIndicatorButton = styled(Button)` opacity: 1; } - ${({ $spoke }) => $spoke && ` + ${({ $spoke }) => $spoke + && ` opacity: ${spokeOpacity}; [dir="rtl"] & { @@ -101,7 +102,8 @@ const TalkingIndicatorButton = styled(Button)` } `} - ${({ $muted }) => $muted && ` + ${({ $muted }) => $muted + && ` cursor: default; i { @@ -109,7 +111,8 @@ const TalkingIndicatorButton = styled(Button)` } `} - ${({ $isViewer }) => $isViewer && ` + ${({ $isViewer }) => $isViewer + && ` cursor: default; `} `; @@ -118,19 +121,16 @@ const CCIcon = styled(Icon)` align-self: center; color: ${colorWhite}; margin: 0 ${borderRadius}; - font-size: calc(${fontSizeSmall} * .85); - opacity: ${({ muted, talking }) => ((muted || !talking) && `${spokeOpacity};`) - || '1;'}; + font-size: calc(${fontSizeSmall} * 0.85); + opacity: ${({ muted, talking }) => ((muted || !talking) && `${spokeOpacity};`) || '1;'}; `; const TalkingIndicatorWrapper = styled.div` border-radius: ${talkerBorderRadius} ${talkerBorderRadius}; display: flex; margin: 0 ${borderRadius}; - opacity: ${({ muted, talking }) => ((muted || !talking) && `${spokeOpacity};`) - || '1;'}; - background: ${({ muted, talking, floor }) => ((muted || !talking || !floor) && `${colorBackground};`) - || `${colorSuccess}`} + opacity: ${({ muted, talking }) => ((muted || !talking) && `${spokeOpacity};`) || '1;'}; + background: ${({ muted, talking, floor }) => ((muted || !talking || !floor) && `${colorBackground};`) || `${colorSuccess}`}; `; const Hidden = styled.div` @@ -141,7 +141,6 @@ const IsTalkingWrapper = styled.div` display: flex; flex-direction: row; position: relative; - margin-top: ${talkerMarginSm}; overflow: hidden; `; From 74e88d0a28a2fcef9f56102edde4f98f41de293d Mon Sep 17 00:00:00 2001 From: danielpetri1 Date: Wed, 7 Jun 2023 14:44:02 +0000 Subject: [PATCH 025/113] Remove unused imports --- .../org/bigbluebutton/presentation/SupportedFileTypes.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/SupportedFileTypes.java b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/SupportedFileTypes.java index 6a6a0a3198..71f73ec893 100755 --- a/bbb-common-web/src/main/java/org/bigbluebutton/presentation/SupportedFileTypes.java +++ b/bbb-common-web/src/main/java/org/bigbluebutton/presentation/SupportedFileTypes.java @@ -25,13 +25,10 @@ import org.slf4j.LoggerFactory; import static org.bigbluebutton.presentation.FileTypeConstants.*; import org.apache.tika.Tika; -import java.io.BufferedReader; import java.io.File; import java.io.InputStream; import java.io.FileInputStream; import java.io.IOException; -import java.io.InputStreamReader; -import java.net.URLConnection; import java.util.ArrayList; import java.util.List; import java.util.Collections; From deabab363f4e4e148f7a7fe161be9377c5d68032 Mon Sep 17 00:00:00 2001 From: Anton Georgiev Date: Wed, 7 Jun 2023 12:22:09 -0400 Subject: [PATCH 026/113] docs: make RecentReleases a heading --- docs/docs/new-features.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/docs/docs/new-features.md b/docs/docs/new-features.md index e4ae76901c..2119723ae8 100644 --- a/docs/docs/new-features.md +++ b/docs/docs/new-features.md @@ -183,7 +183,9 @@ Under the hood, BigBlueButton 2.6 installs on Ubuntu 20.04 64-bit, and the follo - Grails 5.2.4 - Spring 2.7.12 -For full details on what is new in BigBlueButton 2.6, see the release notes. Recent releases: +For full details on what is new in BigBlueButton 2.6, see the release notes. + +### Recent releases: - [2.6.9](https://github.com/bigbluebutton/bigbluebutton/releases/tag/v2.6.9) - [2.6.8](https://github.com/bigbluebutton/bigbluebutton/releases/tag/v2.6.8) From 204ae7d91d79801ba1c6096a23ca3852212cb75f Mon Sep 17 00:00:00 2001 From: Gabriel Porfirio Date: Wed, 7 Jun 2023 13:31:42 -0300 Subject: [PATCH 027/113] changing spec custom parameters --- .../playwright/customparameters/customparameters.spec.js | 1 - 1 file changed, 1 deletion(-) diff --git a/bigbluebutton-tests/playwright/customparameters/customparameters.spec.js b/bigbluebutton-tests/playwright/customparameters/customparameters.spec.js index cdb144b336..4470548db2 100644 --- a/bigbluebutton-tests/playwright/customparameters/customparameters.spec.js +++ b/bigbluebutton-tests/playwright/customparameters/customparameters.spec.js @@ -2,7 +2,6 @@ const { test } = require('@playwright/test'); const { CustomParameters } = require('./customparameters'); const c = require('./constants'); const { encodeCustomParams, getAllShortcutParams, hexToRgb } = require('./util'); -const { fullName } = require('../core/parameters'); test.describe.parallel('CustomParameters', () => { test('Show Public Chat On Login', async ({ browser, context, page }) => { From 0bc97f9b7b75029bf72a74040b9ec8cbb12e3d7a Mon Sep 17 00:00:00 2001 From: Anton Georgiev Date: Wed, 7 Jun 2023 13:18:00 -0400 Subject: [PATCH 028/113] docs: Document maxNumberOfAnnotations --- docs/docs/administration/customize.md | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/docs/docs/administration/customize.md b/docs/docs/administration/customize.md index 165a255cf4..13153a8fc0 100644 --- a/docs/docs/administration/customize.md +++ b/docs/docs/administration/customize.md @@ -978,6 +978,22 @@ Then copy the font (.ttf) to `/usr/share/fonts/` That's all! The font will be available on next presentations. + +#### Change the limit of 300 annotations per page + +In BigBlueButton 2.6 we introduced a cap for how many annotations can be added to a single whiteboard (slide). The default value is set to 300. The reason for this cap is that when a large number of annotations was added, it was often times done in the case of multi user whiteboard being enabled and a student trying to be funny. This had a negative effect on other participants in the session, specifically on limited CPU devices. In almost all cases we observed during the testing phase of BigBlueButton 2.6 this cap was sufficient for the normal run of classes. In very rare instances normal use of the whiteboard led to hitting the cap. + +In order to change the value (on per-server basis) to, say, 500 annotations in your deployment, add the following to `/etc/bigbluebutton/bbb-html5.yml` + +``` +public: + whiteboard: + maxNumberOfAnnotations: 500 +``` + +and restart BigBlueButton via `sudo bbb-conf --restart` + + ### Frontends #### Remove the API demos From cccac664418e91cd98d3e39c583c4ed0a6336ad8 Mon Sep 17 00:00:00 2001 From: Anton Georgiev Date: Wed, 7 Jun 2023 13:27:05 -0400 Subject: [PATCH 029/113] docs: Add a note in the 2.6-whats-new about max annotations cap --- docs/docs/new-features.md | 3 +++ 1 file changed, 3 insertions(+) diff --git a/docs/docs/new-features.md b/docs/docs/new-features.md index 2119723ae8..74f6874f87 100644 --- a/docs/docs/new-features.md +++ b/docs/docs/new-features.md @@ -245,6 +245,9 @@ We used to keep the default presentation (`default.pdf` on a stock installation) In BigBlueButton 2.6 we added a directory `assets` so now the full path is `/var/www/bigbluebutton-default/assets/default.pdf`. In case you are overriding the file/filename, please pass `beans.presentationService.defaultUploadedPresentation=${bigbluebutton.web.serverURL}/assets/file.pdf` in `/etc/bigbluebutton/bbb-web.properties` +#### Limiting the whiteboard annotations to 300 per slide (configurable) + +We introduced this configuration as a safeguard against people deliberately trying to deteriorate others' experience. In some cases the default limit could be reached in normal use of the whiteboard (small letter handwriting while zoomed in, etc). We have exposed this value in the configurations file for bbb-html5. You can find more info in the [customization presentation section](https://docs.bigbluebutton.org/administration/customize#change-the-limit-of-300-annotations-per-page) . ### Development From 95254460c1398dd705fac9122e6641af9c6663aa Mon Sep 17 00:00:00 2001 From: Daniel Schreiber Date: Wed, 7 Jun 2023 20:32:11 +0200 Subject: [PATCH 030/113] Docs: add missing configuration option for cluster setup `bbb-web` needs configuration for correct CORS headers in cluster setup. Otherwise API calls from `bbb-html5` to `bbb-web` will be rejected by grails. --- docs/docs/administration/cluster-proxy.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/docs/administration/cluster-proxy.md b/docs/docs/administration/cluster-proxy.md index 9bf9d17da4..255934e19d 100644 --- a/docs/docs/administration/cluster-proxy.md +++ b/docs/docs/administration/cluster-proxy.md @@ -179,6 +179,12 @@ Create the file `/etc/bigbluebutton/etherpad.json` with the following content: } ``` +Adjust the CORS settings in `/etc/default/bbb-web`: + +```shell +JDK_JAVA_OPTIONS="-Dgrails.cors.enabled=true -Dgrails.cors.allowCredentials=true -Dgrails.cors.allowedOrigins=https://bbb-proxy.example.org,https://https://bbb-01.example.com" +``` + Restart BigBlueButton: From d62a51ad25c937f19e9b50fc277e0c7d53d8c359 Mon Sep 17 00:00:00 2001 From: Daniel Schreiber Date: Wed, 7 Jun 2023 20:42:56 +0200 Subject: [PATCH 031/113] Fix: open documents download presentation in new tab Tell the browser to open presentations sent to chat in a new tab. Otherwise it might try to replace the BBB session. --- bigbluebutton-html5/imports/ui/components/chat/service.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigbluebutton-html5/imports/ui/components/chat/service.js b/bigbluebutton-html5/imports/ui/components/chat/service.js index 0c369012b9..8b45a3c565 100755 --- a/bigbluebutton-html5/imports/ui/components/chat/service.js +++ b/bigbluebutton-html5/imports/ui/components/chat/service.js @@ -332,7 +332,7 @@ const getExportedPresentationString = (fileURI, filename, intl) => { const warningIcon = ``; const label = `${intl.formatMessage(intlMessages.download)}`; const notAccessibleWarning = `${warningIcon}`; - const link = `${label} ${notAccessibleWarning}`; + const link = `${label} ${notAccessibleWarning}`; const name = `${filename}`; return `${name}
${link}`; }; From 99919edb33f1c2992687454370649e703d11ade5 Mon Sep 17 00:00:00 2001 From: hiroshisuga <45039819+hiroshisuga@users.noreply.github.com> Date: Thu, 8 Jun 2023 08:25:24 +0900 Subject: [PATCH 032/113] default pointer size 10 -> 5 --- bigbluebutton-html5/private/config/settings.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/bigbluebutton-html5/private/config/settings.yml b/bigbluebutton-html5/private/config/settings.yml index 0cf013c8a2..9c48cee5d1 100755 --- a/bigbluebutton-html5/private/config/settings.yml +++ b/bigbluebutton-html5/private/config/settings.yml @@ -759,7 +759,7 @@ public: whiteboard: annotationsQueueProcessInterval: 60 cursorInterval: 150 - pointerDiameter: 10 + pointerDiameter: 5 maxStickyNoteLength: 1000 # limit number of annotations per slide maxNumberOfAnnotations: 300 From 8263dd17ca1a79c2322148b4efaadfb61f317413 Mon Sep 17 00:00:00 2001 From: Gustavo Trott Date: Wed, 7 Jun 2023 22:10:07 -0300 Subject: [PATCH 033/113] Introduces flags user.away and user.raiseHand --- .../apps/users/ChangeUserAwayReqMsgHdlr.scala | 50 ++++++++++ .../users/ChangeUserEmojiCmdMsgHdlr.scala | 23 +++-- .../users/ChangeUserRaiseHandReqMsgHdlr.scala | 74 +++++++++++++++ .../ChangeUserReactionEmojiReqMsgHdlr.scala | 41 ++++++++ .../users/ClearAllUsersEmojiCmdMsgHdlr.scala | 3 +- .../UserReactionTimeExpiredCmdMsgHdlr.scala | 7 +- .../core/apps/users/UsersApp.scala | 3 + .../voice/UserJoinedVoiceConfEvtMsgHdlr.scala | 3 + .../bigbluebutton/core/models/Users2x.scala | 36 +++++++ .../senders/ReceivedJsonMsgHandlerActor.scala | 6 ++ .../core/running/HandlerHelpers.scala | 3 + .../core/running/MeetingActor.scala | 3 + .../core2/message/senders/MsgBuilder.scala | 28 ++++++ .../UserJoinedMeetingEvtMsgBuilder.scala | 3 + .../core2/testdata/FakeTestData.scala | 3 +- .../redis/LearningDashboardActor.scala | 48 +++++++++- .../core2/testdata/TestDataGen.scala | 5 +- .../common2/msgs/UsersMsgs.scala | 73 ++++++++++++--- .../meetings/server/modifiers/addMeeting.js | 2 +- .../api/user-reaction/server/eventHandlers.js | 4 +- .../server/handlers/setUserReaction.js | 12 +-- .../api/user-reaction/server/helpers.js | 32 +++---- .../api/user-reaction/server/methods.js | 2 - .../server/methods/setUserReaction.js | 14 +-- .../server/modifiers/addUserPersistentData.js | 3 + .../imports/api/users/server/eventHandlers.js | 10 +- .../api/users/server/handlers/changeAway.js | 11 +++ .../users/server/handlers/changeRaiseHand.js | 11 +++ .../server/handlers/clearUsersEmoji.js | 0 .../server/handlers/emojiStatus.js} | 10 +- .../users/server/handlers/reactionEmoji.js | 32 +++++++ .../imports/api/users/server/methods.js | 8 ++ .../api/users/server/methods/changeAway.js | 31 +++++++ .../users/server/methods/changeRaiseHand.js | 31 +++++++ .../server/methods/clearAllUsersEmoji.js | 0 .../server/methods/sendAwayStatusChatMsg.js} | 9 +- .../users/server/methods/setEmojiStatus.js | 33 +++++++ .../api/users/server/modifiers/addUser.js | 3 + .../api/users/server/modifiers/changeAway.js | 30 ++++++ .../users/server/modifiers/changeRaiseHand.js | 26 ++++++ .../server/modifiers/clearUsersEmoji.js | 6 +- .../ui/components/actions-bar/component.jsx | 10 +- .../interactions-button/component.jsx | 40 +++----- .../interactions-button/container.jsx | 2 + .../actions-bar/raise-hand/component.jsx | 64 +++++-------- .../ui/components/actions-bar/service.js | 9 +- .../imports/ui/components/app/component.jsx | 8 +- .../component.jsx | 93 +++++++++---------- .../container.jsx | 28 +++--- .../styles.js | 0 .../ui/components/user-list/service.js | 76 +++++++-------- .../user-participants/component.jsx | 1 - .../user-list-item/component.jsx | 8 +- .../user-options/component.jsx | 15 ++- .../user-options/container.jsx | 9 +- .../ui/components/user-reaction/service.js | 4 +- 56 files changed, 816 insertions(+), 283 deletions(-) create mode 100644 akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/ChangeUserAwayReqMsgHdlr.scala create mode 100644 akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/ChangeUserRaiseHandReqMsgHdlr.scala create mode 100644 akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/ChangeUserReactionEmojiReqMsgHdlr.scala create mode 100644 bigbluebutton-html5/imports/api/users/server/handlers/changeAway.js create mode 100644 bigbluebutton-html5/imports/api/users/server/handlers/changeRaiseHand.js rename bigbluebutton-html5/imports/api/{user-reaction => users}/server/handlers/clearUsersEmoji.js (100%) rename bigbluebutton-html5/imports/api/{user-reaction/server/modifiers/addUserEmoji.js => users/server/handlers/emojiStatus.js} (59%) create mode 100644 bigbluebutton-html5/imports/api/users/server/handlers/reactionEmoji.js create mode 100644 bigbluebutton-html5/imports/api/users/server/methods/changeAway.js create mode 100644 bigbluebutton-html5/imports/api/users/server/methods/changeRaiseHand.js rename bigbluebutton-html5/imports/api/{user-reaction => users}/server/methods/clearAllUsersEmoji.js (100%) rename bigbluebutton-html5/imports/api/{user-reaction/server/modifiers/sendStatusChatMsg.js => users/server/methods/sendAwayStatusChatMsg.js} (90%) create mode 100644 bigbluebutton-html5/imports/api/users/server/methods/setEmojiStatus.js create mode 100644 bigbluebutton-html5/imports/api/users/server/modifiers/changeAway.js create mode 100644 bigbluebutton-html5/imports/api/users/server/modifiers/changeRaiseHand.js rename bigbluebutton-html5/imports/api/{user-reaction => users}/server/modifiers/clearUsersEmoji.js (86%) rename bigbluebutton-html5/imports/ui/components/{status-notifier => raisehand-notifier}/component.jsx (67%) rename bigbluebutton-html5/imports/ui/components/{status-notifier => raisehand-notifier}/container.jsx (66%) rename bigbluebutton-html5/imports/ui/components/{status-notifier => raisehand-notifier}/styles.js (100%) diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/ChangeUserAwayReqMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/ChangeUserAwayReqMsgHdlr.scala new file mode 100644 index 0000000000..81e30beab0 --- /dev/null +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/ChangeUserAwayReqMsgHdlr.scala @@ -0,0 +1,50 @@ +package org.bigbluebutton.core.apps.users + +import org.bigbluebutton.common2.msgs._ +import org.bigbluebutton.core.apps.RightsManagementTrait +import org.bigbluebutton.core.models.{ UserState, Users2x } +import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter } +import org.bigbluebutton.core2.message.senders.MsgBuilder + +trait ChangeUserAwayReqMsgHdlr extends RightsManagementTrait { + this: UsersApp => + + val liveMeeting: LiveMeeting + val outGW: OutMsgRouter + + def handleChangeUserAwayReqMsg(msg: ChangeUserAwayReqMsg): Unit = { + log.info("handleChangeUserAwayReqMsg: away={} userId={}", msg.body.away, msg.body.userId) + + def broadcast(user: UserState, away: Boolean): Unit = { + val routingChange = Routing.addMsgToClientRouting( + MessageTypes.BROADCAST_TO_MEETING, + liveMeeting.props.meetingProp.intId, user.intId + ) + val envelopeChange = BbbCoreEnvelope(UserAwayChangedEvtMsg.NAME, routingChange) + val headerChange = BbbClientMsgHeader(UserAwayChangedEvtMsg.NAME, liveMeeting.props.meetingProp.intId, + user.intId) + + val bodyChange = UserAwayChangedEvtMsgBody(user.intId, away) + val eventChange = UserAwayChangedEvtMsg(headerChange, bodyChange) + val msgEventChange = BbbCommonEnvCoreMsg(envelopeChange, eventChange) + outGW.send(msgEventChange) + } + + for { + user <- Users2x.findWithIntId(liveMeeting.users2x, msg.body.userId) + newUserState <- Users2x.setUserAway(liveMeeting.users2x, user.intId, msg.body.away) + } yield { + if (msg.body.away && user.emoji == "") { + Users2x.setEmojiStatus(liveMeeting.users2x, msg.body.userId, "away") + outGW.send(MsgBuilder.buildUserEmojiChangedEvtMsg(liveMeeting.props.meetingProp.intId, msg.body.userId, "away")) + } + + if (msg.body.away == false && user.emoji == "away") { + Users2x.setEmojiStatus(liveMeeting.users2x, msg.body.userId, "none") + outGW.send(MsgBuilder.buildUserEmojiChangedEvtMsg(liveMeeting.props.meetingProp.intId, msg.body.userId, "none")) + } + + broadcast(newUserState, msg.body.away) + } + } +} \ No newline at end of file diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/ChangeUserEmojiCmdMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/ChangeUserEmojiCmdMsgHdlr.scala index 80b6862f91..20318d9a02 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/ChangeUserEmojiCmdMsgHdlr.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/ChangeUserEmojiCmdMsgHdlr.scala @@ -4,6 +4,7 @@ import org.bigbluebutton.common2.msgs._ import org.bigbluebutton.core.models.Users2x import org.bigbluebutton.core.running.{ BaseMeetingActor, LiveMeeting, OutMsgRouter } import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait } +import org.bigbluebutton.core2.message.senders.MsgBuilder trait ChangeUserEmojiCmdMsgHdlr extends RightsManagementTrait { this: BaseMeetingActor => @@ -37,7 +38,18 @@ trait ChangeUserEmojiCmdMsgHdlr extends RightsManagementTrait { for { uvo <- Users2x.setEmojiStatus(liveMeeting.users2x, msg.body.userId, msg.body.emoji) } yield { - sendUserEmojiChangedEvtMsg(outGW, liveMeeting.props.meetingProp.intId, msg.body.userId, msg.body.emoji) + outGW.send(MsgBuilder.buildUserEmojiChangedEvtMsg(liveMeeting.props.meetingProp.intId, msg.body.userId, msg.body.emoji)) + + if (initialEmojiState == "raiseHand" || nextEmojiState == "raiseHand") { + Users2x.setUserRaiseHand(liveMeeting.users2x, msg.body.userId, msg.body.emoji == "raiseHand") + outGW.send(MsgBuilder.buildUserRaiseHandChangedEvtMsg(liveMeeting.props.meetingProp.intId, msg.body.userId, msg.body.emoji == "raiseHand")) + } + + if (initialEmojiState == "away" || nextEmojiState == "away") { + Users2x.setUserAway(liveMeeting.users2x, msg.body.userId, msg.body.emoji == "away") + outGW.send(MsgBuilder.buildUserAwayChangedEvtMsg(liveMeeting.props.meetingProp.intId, msg.body.userId, msg.body.emoji == "away")) + } + } } else { val meetingId = liveMeeting.props.meetingProp.intId @@ -46,13 +58,4 @@ trait ChangeUserEmojiCmdMsgHdlr extends RightsManagementTrait { } } - def sendUserEmojiChangedEvtMsg(outGW: OutMsgRouter, meetingId: String, userId: String, emoji: String): Unit = { - val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, meetingId, userId) - val envelope = BbbCoreEnvelope(UserEmojiChangedEvtMsg.NAME, routing) - val header = BbbClientMsgHeader(UserEmojiChangedEvtMsg.NAME, meetingId, userId) - val body = UserEmojiChangedEvtMsgBody(userId, emoji) - val event = UserEmojiChangedEvtMsg(header, body) - val msgEvent = BbbCommonEnvCoreMsg(envelope, event) - outGW.send(msgEvent) - } } diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/ChangeUserRaiseHandReqMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/ChangeUserRaiseHandReqMsgHdlr.scala new file mode 100644 index 0000000000..1eab60093b --- /dev/null +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/ChangeUserRaiseHandReqMsgHdlr.scala @@ -0,0 +1,74 @@ +package org.bigbluebutton.core.apps.users + +import org.bigbluebutton.common2.msgs._ +import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait } +import org.bigbluebutton.core.models.{ UserState, Users2x } +import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter } +import org.bigbluebutton.core2.message.senders.MsgBuilder + +trait ChangeUserRaiseHandReqMsgHdlr extends RightsManagementTrait { + this: UsersApp => + + val liveMeeting: LiveMeeting + val outGW: OutMsgRouter + + def handleChangeUserRaiseHandReqMsg(msg: ChangeUserRaiseHandReqMsg): Unit = { + log.info("handleChangeUserRaiseHandReqMsg: raiseHand={} userId={}", msg.body.raiseHand, msg.body.userId) + + def broadcast(user: UserState, raiseHand: Boolean): Unit = { + val routingChange = Routing.addMsgToClientRouting( + MessageTypes.BROADCAST_TO_MEETING, + liveMeeting.props.meetingProp.intId, user.intId + ) + val envelopeChange = BbbCoreEnvelope(UserRaiseHandChangedEvtMsg.NAME, routingChange) + val headerChange = BbbClientMsgHeader(UserRaiseHandChangedEvtMsg.NAME, liveMeeting.props.meetingProp.intId, + user.intId) + + val bodyChange = UserRaiseHandChangedEvtMsgBody(user.intId, raiseHand) + val eventChange = UserRaiseHandChangedEvtMsg(headerChange, bodyChange) + val msgEventChange = BbbCommonEnvCoreMsg(envelopeChange, eventChange) + outGW.send(msgEventChange) + } + + val isUserSettingOwnProps = (msg.header.userId == msg.body.userId) + + val isUserModerator = !permissionFailed( + PermissionCheck.MOD_LEVEL, + PermissionCheck.VIEWER_LEVEL, + liveMeeting.users2x, + msg.header.userId + ) + + val isUserPresenter = !permissionFailed( + PermissionCheck.VIEWER_LEVEL, + PermissionCheck.PRESENTER_LEVEL, + liveMeeting.users2x, + msg.header.userId + ) + + if (isUserSettingOwnProps || isUserModerator || isUserPresenter) { + for { + user <- Users2x.findWithIntId(liveMeeting.users2x, msg.body.userId) + newUserState <- Users2x.setUserRaiseHand(liveMeeting.users2x, user.intId, msg.body.raiseHand) + } yield { + + if (msg.body.raiseHand && user.emoji == "") { + Users2x.setEmojiStatus(liveMeeting.users2x, msg.body.userId, "raiseHand") + outGW.send(MsgBuilder.buildUserEmojiChangedEvtMsg(liveMeeting.props.meetingProp.intId, msg.body.userId, "raiseHand")) + } + + if (msg.body.raiseHand == false && user.emoji == "raiseHand") { + Users2x.setEmojiStatus(liveMeeting.users2x, msg.body.userId, "none") + outGW.send(MsgBuilder.buildUserEmojiChangedEvtMsg(liveMeeting.props.meetingProp.intId, msg.body.userId, "none")) + } + + broadcast(newUserState, msg.body.raiseHand) + } + } else { + val meetingId = liveMeeting.props.meetingProp.intId + val reason = "No permission to change user raiseHand prop." + PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting) + } + + } +} \ No newline at end of file diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/ChangeUserReactionEmojiReqMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/ChangeUserReactionEmojiReqMsgHdlr.scala new file mode 100644 index 0000000000..7fd7ad99c5 --- /dev/null +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/ChangeUserReactionEmojiReqMsgHdlr.scala @@ -0,0 +1,41 @@ +package org.bigbluebutton.core.apps.users + +import org.bigbluebutton.common2.msgs._ +import org.bigbluebutton.core.apps.RightsManagementTrait +import org.bigbluebutton.core.models.{ UserState, Users2x } +import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter } + +trait ChangeUserReactionEmojiReqMsgHdlr extends RightsManagementTrait { + this: UsersApp => + + val liveMeeting: LiveMeeting + val outGW: OutMsgRouter + + def handleChangeUserReactionEmojiReqMsg(msg: ChangeUserReactionEmojiReqMsg): Unit = { + log.info("handleChangeUserReactionEmojiReqMsg: reactionEmoji={} userId={}", msg.body.reactionEmoji, msg.body.userId) + + def broadcast(user: UserState, reactionEmoji: String): Unit = { + val routingChange = Routing.addMsgToClientRouting( + MessageTypes.BROADCAST_TO_MEETING, + liveMeeting.props.meetingProp.intId, user.intId + ) + val envelopeChange = BbbCoreEnvelope(UserReactionEmojiChangedEvtMsg.NAME, routingChange) + val headerChange = BbbClientMsgHeader(UserReactionEmojiChangedEvtMsg.NAME, liveMeeting.props.meetingProp.intId, + user.intId) + + val bodyChange = UserReactionEmojiChangedEvtMsgBody(user.intId, reactionEmoji) + val eventChange = UserReactionEmojiChangedEvtMsg(headerChange, bodyChange) + val msgEventChange = BbbCommonEnvCoreMsg(envelopeChange, eventChange) + outGW.send(msgEventChange) + } + + for { + user <- Users2x.findWithIntId(liveMeeting.users2x, msg.body.userId) + newUserState <- Users2x.setReactionEmoji(liveMeeting.users2x, user.intId, msg.body.reactionEmoji) + } yield { + if (user.reactionEmoji != msg.body.reactionEmoji) { + broadcast(newUserState, msg.body.reactionEmoji) + } + } + } +} \ No newline at end of file diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/ClearAllUsersEmojiCmdMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/ClearAllUsersEmojiCmdMsgHdlr.scala index e5c6876e62..b50d6d1c20 100644 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/ClearAllUsersEmojiCmdMsgHdlr.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/ClearAllUsersEmojiCmdMsgHdlr.scala @@ -22,9 +22,10 @@ trait ClearAllUsersEmojiCmdMsgHdlr extends RightsManagementTrait { if (isUserModerator) { for { user <- Users2x.findAll(liveMeeting.users2x) - if user.emoji.equals("raiseHand") || user.emoji.equals("away") || user.emoji.equals("notAway") } yield { Users2x.setEmojiStatus(liveMeeting.users2x, user.intId, "none") + Users2x.setUserAway(liveMeeting.users2x, user.intId, false) + Users2x.setUserRaiseHand(liveMeeting.users2x, user.intId, false) } sendClearedAllUsersEmojiEvtMsg(outGW, liveMeeting.props.meetingProp.intId, msg.header.userId) } else { diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/UserReactionTimeExpiredCmdMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/UserReactionTimeExpiredCmdMsgHdlr.scala index 25bfa06bd9..0f1902a3e1 100644 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/UserReactionTimeExpiredCmdMsgHdlr.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/UserReactionTimeExpiredCmdMsgHdlr.scala @@ -12,11 +12,8 @@ trait UserReactionTimeExpiredCmdMsgHdlr extends RightsManagementTrait { def handleUserReactionTimeExpiredCmdMsg(msg: UserReactionTimeExpiredCmdMsg) { val isNodeUser = msg.header.userId.equals("nodeJSapp") - - val currentEmojiState = Users2x.findWithIntId(liveMeeting.users2x, msg.body.userId).get.emoji - - if (isNodeUser && (!currentEmojiState.equals("raiseHand") && !currentEmojiState.equals("away") && !currentEmojiState.equals("notAway"))) { - Users2x.setEmojiStatus(liveMeeting.users2x, msg.body.userId, "none") + if (isNodeUser) { + Users2x.setReactionEmoji(liveMeeting.users2x, msg.body.userId, "none") } } } 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 5d821efc35..7568e032fe 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 @@ -159,6 +159,9 @@ class UsersApp( with AssignPresenterReqMsgHdlr with ChangeUserPinStateReqMsgHdlr with ChangeUserMobileFlagReqMsgHdlr + with ChangeUserReactionEmojiReqMsgHdlr + with ChangeUserRaiseHandReqMsgHdlr + with ChangeUserAwayReqMsgHdlr with EjectUserFromMeetingCmdMsgHdlr with EjectUserFromMeetingSysMsgHdlr with MuteUserCmdMsgHdlr { diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/UserJoinedVoiceConfEvtMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/UserJoinedVoiceConfEvtMsgHdlr.scala index 6bfa2907ee..4f18ade3a5 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/UserJoinedVoiceConfEvtMsgHdlr.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/UserJoinedVoiceConfEvtMsgHdlr.scala @@ -49,6 +49,9 @@ trait UserJoinedVoiceConfEvtMsgHdlr extends SystemConfiguration { authed = true, guestStatus = GuestStatus.WAIT, emoji = "none", + reactionEmoji = "none", + raiseHand = false, + away = false, pin = false, mobile = false, presenter = false, 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 9f94230fda..4aa4e9e9ef 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 @@ -178,10 +178,42 @@ object Users2x { u <- findWithIntId(users, intId) } yield { val newUser = u.modify(_.emoji).setTo(emoji) + users.save(newUser) newUser } } + def setReactionEmoji(users: Users2x, intId: String, reactionEmoji: String): Option[UserState] = { + for { + u <- findWithIntId(users, intId) + } yield { + val newUser = u.modify(_.reactionEmoji).setTo(reactionEmoji) + .modify(_.reactionChangedOn).setTo(System.currentTimeMillis()) + + users.save(newUser) + newUser + } + } + + def setUserRaiseHand(users: Users2x, intId: String, raiseHand: Boolean): Option[UserState] = { + for { + u <- findWithIntId(users, intId) + } yield { + val newUserState = u.modify(_.away).setTo(raiseHand) + users.save(newUserState) + newUserState + } + } + + def setUserAway(users: Users2x, intId: String, away: Boolean): Option[UserState] = { + for { + u <- findWithIntId(users, intId) + } yield { + val newUserState = u.modify(_.away).setTo(away) + users.save(newUserState) + newUserState + } + } def setUserLocked(users: Users2x, intId: String, locked: Boolean): Option[UserState] = { for { @@ -364,6 +396,10 @@ case class UserState( authed: Boolean, guestStatus: String, emoji: String, + reactionEmoji: String, + reactionChangedOn: Long = 0, + raiseHand: Boolean, + away: Boolean, locked: Boolean, presenter: Boolean, avatar: String, 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 a803fb79c6..d1af0bcd91 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 @@ -250,8 +250,14 @@ class ReceivedJsonMsgHandlerActor( case UserLeaveReqMsg.NAME => routeGenericMsg[UserLeaveReqMsg](envelope, jsonNode) + case ChangeUserRaiseHandReqMsg.NAME => + routeGenericMsg[ChangeUserRaiseHandReqMsg](envelope, jsonNode) + case ChangeUserAwayReqMsg.NAME => + routeGenericMsg[ChangeUserAwayReqMsg](envelope, jsonNode) case ChangeUserEmojiCmdMsg.NAME => routeGenericMsg[ChangeUserEmojiCmdMsg](envelope, jsonNode) + case ChangeUserReactionEmojiReqMsg.NAME => + routeGenericMsg[ChangeUserReactionEmojiReqMsg](envelope, jsonNode) case UserReactionTimeExpiredCmdMsg.NAME => routeGenericMsg[UserReactionTimeExpiredCmdMsg](envelope, jsonNode) case ClearAllUsersEmojiCmdMsg.NAME => diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/HandlerHelpers.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/HandlerHelpers.scala index b620c9dc8c..12e661d118 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/HandlerHelpers.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/HandlerHelpers.scala @@ -62,6 +62,9 @@ trait HandlerHelpers extends SystemConfiguration { authed = regUser.authed, guestStatus = regUser.guestStatus, emoji = "none", + reactionEmoji = "none", + raiseHand = false, + away = false, pin = false, mobile = false, presenter = false, 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 7eaf80470a..777d7c1095 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 @@ -386,6 +386,9 @@ class MeetingActor( updateUserLastActivity(m.body.setBy) case m: GetRecordingStatusReqMsg => usersApp.handleGetRecordingStatusReqMsg(m) case m: ChangeUserEmojiCmdMsg => handleChangeUserEmojiCmdMsg(m) + case m: ChangeUserReactionEmojiReqMsg => usersApp.handleChangeUserReactionEmojiReqMsg(m) + case m: ChangeUserRaiseHandReqMsg => usersApp.handleChangeUserRaiseHandReqMsg(m) + case m: ChangeUserAwayReqMsg => usersApp.handleChangeUserAwayReqMsg(m) case m: UserReactionTimeExpiredCmdMsg => handleUserReactionTimeExpiredCmdMsg(m) case m: ClearAllUsersEmojiCmdMsg => handleClearAllUsersEmojiCmdMsg(m) case m: SelectRandomViewerReqMsg => usersApp.handleSelectRandomViewerReqMsg(m) diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/senders/MsgBuilder.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/senders/MsgBuilder.scala index f71375406b..0f452ba146 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/senders/MsgBuilder.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/senders/MsgBuilder.scala @@ -590,4 +590,32 @@ object MsgBuilder { BbbCommonEnvCoreMsg(envelope, event) } + + def buildUserEmojiChangedEvtMsg(meetingId: String, userId: String, emoji: String) = { + val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, meetingId, userId) + val envelope = BbbCoreEnvelope(UserEmojiChangedEvtMsg.NAME, routing) + val header = BbbClientMsgHeader(UserEmojiChangedEvtMsg.NAME, meetingId, userId) + val body = UserEmojiChangedEvtMsgBody(userId, emoji) + val event = UserEmojiChangedEvtMsg(header, body) + BbbCommonEnvCoreMsg(envelope, event) + } + + def buildUserAwayChangedEvtMsg(meetingId: String, userId: String, away: Boolean) = { + val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, meetingId, userId) + val envelope = BbbCoreEnvelope(UserAwayChangedEvtMsg.NAME, routing) + val header = BbbClientMsgHeader(UserAwayChangedEvtMsg.NAME, meetingId, userId) + val body = UserAwayChangedEvtMsgBody(userId, away) + val event = UserAwayChangedEvtMsg(header, body) + BbbCommonEnvCoreMsg(envelope, event) + } + + def buildUserRaiseHandChangedEvtMsg(meetingId: String, userId: String, raiseHand: Boolean) = { + val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, meetingId, userId) + val envelope = BbbCoreEnvelope(UserRaiseHandChangedEvtMsg.NAME, routing) + val header = BbbClientMsgHeader(UserRaiseHandChangedEvtMsg.NAME, meetingId, userId) + val body = UserRaiseHandChangedEvtMsgBody(userId, raiseHand) + val event = UserRaiseHandChangedEvtMsg(header, body) + BbbCommonEnvCoreMsg(envelope, event) + } + } diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/senders/UserJoinedMeetingEvtMsgBuilder.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/senders/UserJoinedMeetingEvtMsgBuilder.scala index 4a6d3d7a9a..4301e11fc3 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/senders/UserJoinedMeetingEvtMsgBuilder.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/senders/UserJoinedMeetingEvtMsgBuilder.scala @@ -12,6 +12,9 @@ object UserJoinedMeetingEvtMsgBuilder { role = userState.role, guest = userState.guest, authed = userState.authed, guestStatus = userState.guestStatus, emoji = userState.emoji, + reactionEmoji = userState.reactionEmoji, + raiseHand = userState.raiseHand, + away = userState.away, pin = userState.pin, presenter = userState.presenter, locked = userState.locked, avatar = userState.avatar, color = userState.color, clientType = userState.clientType) diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/testdata/FakeTestData.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/testdata/FakeTestData.scala index 189be4419c..2e43892f8c 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/testdata/FakeTestData.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/testdata/FakeTestData.scala @@ -68,7 +68,8 @@ trait FakeTestData { def createFakeUser(liveMeeting: LiveMeeting, regUser: RegisteredUser): UserState = { UserState(intId = regUser.id, extId = regUser.externId, name = regUser.name, role = regUser.role, pin = false, mobile = false, guest = regUser.guest, authed = regUser.authed, guestStatus = regUser.guestStatus, - emoji = "none", locked = false, presenter = false, avatar = regUser.avatarURL, color = "#ff6242", clientType = "unknown", + emoji = "none", reactionEmoji = "none", raiseHand = false, away = false, locked = false, presenter = false, + avatar = regUser.avatarURL, color = "#ff6242", clientType = "unknown", pickExempted = false, userLeftFlag = UserLeftFlag(false, 0)) } diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/LearningDashboardActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/LearningDashboardActor.scala index d0ad68473b..bdc12a78a4 100644 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/LearningDashboardActor.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/LearningDashboardActor.scala @@ -39,6 +39,7 @@ case class User( answers: Map[String,Vector[String]] = Map(), talk: Talk = Talk(), emojis: Vector[Emoji] = Vector(), + reactions: Vector[Emoji] = Vector(), webcams: Vector[Webcam] = Vector(), totalOfMessages: Long = 0, ) @@ -140,6 +141,9 @@ class LearningDashboardActor( case m: UserLeaveReqMsg => handleUserLeaveReqMsg(m) case m: UserLeftMeetingEvtMsg => handleUserLeftMeetingEvtMsg(m) case m: UserEmojiChangedEvtMsg => handleUserEmojiChangedEvtMsg(m) + case m: UserAwayChangedEvtMsg => handleUserAwayChangedEvtMsg(m) + case m: UserRaiseHandChangedEvtMsg => handleUserRaiseHandChangedEvtMsg(m) + case m: UserReactionEmojiChangedEvtMsg => handleUserReactionEmojiChangedEvtMsg(m) case m: UserRoleChangedEvtMsg => handleUserRoleChangedEvtMsg(m) case m: UserBroadcastCamStartedEvtMsg => handleUserBroadcastCamStartedEvtMsg(m) case m: UserBroadcastCamStoppedEvtMsg => handleUserBroadcastCamStoppedEvtMsg(m) @@ -350,7 +354,7 @@ class LearningDashboardActor( meeting <- meetings.values.find(m => m.intId == msg.header.meetingId) user <- findUserByIntId(meeting, msg.body.userId) } yield { - if (msg.body.emoji != "none") { + if (msg.body.emoji != "none" && msg.body.emoji != "raiseHand" && msg.body.emoji != "away") { val updatedUser = user.copy(emojis = user.emojis :+ Emoji(msg.body.emoji)) val updatedMeeting = meeting.copy(users = meeting.users + (updatedUser.userKey -> updatedUser)) @@ -359,6 +363,48 @@ class LearningDashboardActor( } } + private def handleUserRaiseHandChangedEvtMsg(msg: UserRaiseHandChangedEvtMsg): Unit = { + for { + meeting <- meetings.values.find(m => m.intId == msg.header.meetingId) + user <- findUserByIntId(meeting, msg.body.userId) + } yield { + if (msg.body.raiseHand) { + val updatedUser = user.copy(emojis = user.emojis :+ Emoji("raiseHand")) + val updatedMeeting = meeting.copy(users = meeting.users + (updatedUser.userKey -> updatedUser)) + + meetings += (updatedMeeting.intId -> updatedMeeting) + } + } + } + + private def handleUserAwayChangedEvtMsg(msg: UserAwayChangedEvtMsg): Unit = { + for { + meeting <- meetings.values.find(m => m.intId == msg.header.meetingId) + user <- findUserByIntId(meeting, msg.body.userId) + } yield { + if (msg.body.away) { + val updatedUser = user.copy(emojis = user.emojis :+ Emoji("away")) + val updatedMeeting = meeting.copy(users = meeting.users + (updatedUser.userKey -> updatedUser)) + + meetings += (updatedMeeting.intId -> updatedMeeting) + } + } + } + + private def handleUserReactionEmojiChangedEvtMsg(msg: UserReactionEmojiChangedEvtMsg): Unit = { + for { + meeting <- meetings.values.find(m => m.intId == msg.header.meetingId) + user <- findUserByIntId(meeting, msg.body.userId) + } yield { + if (msg.body.reactionEmoji != "none") { + val updatedUser = user.copy(reactions = user.reactions :+ Emoji(msg.body.reactionEmoji)) + val updatedMeeting = meeting.copy(users = meeting.users + (updatedUser.userKey -> updatedUser)) + + meetings += (updatedMeeting.intId -> updatedMeeting) + } + } + } + private def handleUserRoleChangedEvtMsg(msg: UserRoleChangedEvtMsg) { for { meeting <- meetings.values.find(m => m.intId == msg.header.meetingId) diff --git a/akka-bbb-apps/src/test/scala/org/bigbluebutton/core2/testdata/TestDataGen.scala b/akka-bbb-apps/src/test/scala/org/bigbluebutton/core2/testdata/TestDataGen.scala index 10cf8ed3cf..7eabfb6bd6 100755 --- a/akka-bbb-apps/src/test/scala/org/bigbluebutton/core2/testdata/TestDataGen.scala +++ b/akka-bbb-apps/src/test/scala/org/bigbluebutton/core2/testdata/TestDataGen.scala @@ -43,8 +43,9 @@ object TestDataGen { def createUserFor(liveMeeting: LiveMeeting, regUser: RegisteredUser, presenter: Boolean): UserState = { val u = UserState(intId = regUser.id, extId = regUser.externId, name = regUser.name, role = regUser.role, guest = regUser.guest, authed = regUser.authed, guestStatus = regUser.guestStatus, - emoji = "none", locked = false, presenter = false, avatar = regUser.avatarURL, color = "#ff6242", - clientType = "unknown", userLeftFlag = UserLeftFlag(false, 0)) + emoji = "none", reactionEmoji = "none", raiseHand = false, away = false, pin = false, mobile = false, + locked = false, presenter = false, avatar = regUser.avatarURL, color = "#ff6242", + clientType = "unknown", pickExempted = false, userLeftFlag = UserLeftFlag(false, 0)) Users2x.add(liveMeeting.users2x, u) u } diff --git a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/UsersMsgs.scala b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/UsersMsgs.scala index 42219020bc..ceb65ff601 100755 --- a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/UsersMsgs.scala +++ b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/UsersMsgs.scala @@ -89,20 +89,23 @@ case class UserJoinedMeetingEvtMsg( body: UserJoinedMeetingEvtMsgBody ) extends BbbCoreMsg case class UserJoinedMeetingEvtMsgBody( - intId: String, - extId: String, - name: String, - role: String, - guest: Boolean, - authed: Boolean, - guestStatus: String, - emoji: String, - pin: Boolean, - presenter: Boolean, - locked: Boolean, - avatar: String, - color: String, - clientType: String + intId: String, + extId: String, + name: String, + role: String, + guest: Boolean, + authed: Boolean, + guestStatus: String, + emoji: String, + reactionEmoji: String, + raiseHand: Boolean, + away: Boolean, + pin: Boolean, + presenter: Boolean, + locked: Boolean, + avatar: String, + color: String, + clientType: String ) /** @@ -205,6 +208,48 @@ object UserEmojiChangedEvtMsg { val NAME = "UserEmojiChangedEvtMsg" } case class UserEmojiChangedEvtMsg(header: BbbClientMsgHeader, body: UserEmojiChangedEvtMsgBody) extends BbbCoreMsg case class UserEmojiChangedEvtMsgBody(userId: String, emoji: String) +/** + * Sent from client about a user changing RaiseHand. + */ +object ChangeUserRaiseHandReqMsg { val NAME = "ChangeUserRaiseHandReqMsg" } +case class ChangeUserRaiseHandReqMsg(header: BbbClientMsgHeader, body: ChangeUserRaiseHandReqMsgBody) extends StandardMsg +case class ChangeUserRaiseHandReqMsgBody(userId: String, raiseHand: Boolean) + +/** + * Sent to all clients about a user changing RaiseHand. + */ +object UserRaiseHandChangedEvtMsg { val NAME = "UserRaiseHandChangedEvtMsg" } +case class UserRaiseHandChangedEvtMsg(header: BbbClientMsgHeader, body: UserRaiseHandChangedEvtMsgBody) extends BbbCoreMsg +case class UserRaiseHandChangedEvtMsgBody(userId: String, raiseHand: Boolean) + +/** + * Sent from client about a user changing Away. + */ +object ChangeUserAwayReqMsg { val NAME = "ChangeUserAwayReqMsg" } +case class ChangeUserAwayReqMsg(header: BbbClientMsgHeader, body: ChangeUserAwayReqMsgBody) extends StandardMsg +case class ChangeUserAwayReqMsgBody(userId: String, away: Boolean) + +/** + * Sent to all clients about a user changing Away. + */ +object UserAwayChangedEvtMsg { val NAME = "UserAwayChangedEvtMsg" } +case class UserAwayChangedEvtMsg(header: BbbClientMsgHeader, body: UserAwayChangedEvtMsgBody) extends BbbCoreMsg +case class UserAwayChangedEvtMsgBody(userId: String, away: Boolean) + +/** + * Sent from client about a user changing ReactionEmoji. + */ +object ChangeUserReactionEmojiReqMsg { val NAME = "ChangeUserReactionEmojiReqMsg" } +case class ChangeUserReactionEmojiReqMsg(header: BbbClientMsgHeader, body: ChangeUserReactionEmojiReqMsgBody) extends StandardMsg +case class ChangeUserReactionEmojiReqMsgBody(userId: String, reactionEmoji: String) + +/** + * Sent to all clients about a user changing ReactionEmoji. + */ +object UserReactionEmojiChangedEvtMsg { val NAME = "UserReactionEmojiChangedEvtMsg" } +case class UserReactionEmojiChangedEvtMsg(header: BbbClientMsgHeader, body: UserReactionEmojiChangedEvtMsgBody) extends BbbCoreMsg +case class UserReactionEmojiChangedEvtMsgBody(userId: String, reactionEmoji: String) + /** * Sent from meteor about a user reaction's expiration. */ diff --git a/bigbluebutton-html5/imports/api/meetings/server/modifiers/addMeeting.js b/bigbluebutton-html5/imports/api/meetings/server/modifiers/addMeeting.js index ac814d411c..5ea80accbc 100755 --- a/bigbluebutton-html5/imports/api/meetings/server/modifiers/addMeeting.js +++ b/bigbluebutton-html5/imports/api/meetings/server/modifiers/addMeeting.js @@ -15,7 +15,7 @@ import { initCaptions } from '/imports/api/captions/server/helpers'; import { addAnnotationsStreamer } from '/imports/api/annotations/server/streamer'; import { addCursorStreamer } from '/imports/api/cursor/server/streamer'; import { addExternalVideoStreamer } from '/imports/api/external-videos/server/streamer'; -import { addUserReactionsObserver } from '/imports/api/user-reaction/server/helpers'; +import addUserReactionsObserver from '/imports/api/user-reaction/server/helpers'; import { LAYOUT_TYPE } from '/imports/ui/components/layout/enums'; const addExternalVideo = async (meetingId) => { diff --git a/bigbluebutton-html5/imports/api/user-reaction/server/eventHandlers.js b/bigbluebutton-html5/imports/api/user-reaction/server/eventHandlers.js index d1779ea878..4e1c1d2c5a 100644 --- a/bigbluebutton-html5/imports/api/user-reaction/server/eventHandlers.js +++ b/bigbluebutton-html5/imports/api/user-reaction/server/eventHandlers.js @@ -1,6 +1,4 @@ import RedisPubSub from '/imports/startup/server/redis'; import handleSetUserReaction from './handlers/setUserReaction'; -import handleClearUsersEmoji from './handlers/clearUsersEmoji'; -RedisPubSub.on('UserEmojiChangedEvtMsg', handleSetUserReaction); -RedisPubSub.on('ClearedAllUsersEmojiEvtMsg', handleClearUsersEmoji); +RedisPubSub.on('UserReactionEmojiChangedEvtMsg', handleSetUserReaction); diff --git a/bigbluebutton-html5/imports/api/user-reaction/server/handlers/setUserReaction.js b/bigbluebutton-html5/imports/api/user-reaction/server/handlers/setUserReaction.js index 8786e075ac..4b8e286291 100644 --- a/bigbluebutton-html5/imports/api/user-reaction/server/handlers/setUserReaction.js +++ b/bigbluebutton-html5/imports/api/user-reaction/server/handlers/setUserReaction.js @@ -1,15 +1,7 @@ -import Logger from '/imports/startup/server/logger'; -import { check } from 'meteor/check'; -import UsersReaction from '/imports/api/users'; import addUserReaction from '../modifiers/addUserReaction'; -import addUserEmoji from '../modifiers/addUserEmoji'; export default function handleSetUserReaction({ body }, meetingId) { - const { userId, emoji } = body; + const { userId, reactionEmoji } = body; - if (emoji == 'none' || emoji == 'raiseHand' || emoji == 'away' || emoji == 'notAway') { - addUserEmoji(meetingId, userId, emoji); - } else { - addUserReaction(meetingId, userId, emoji); - } + addUserReaction(meetingId, userId, reactionEmoji); } diff --git a/bigbluebutton-html5/imports/api/user-reaction/server/helpers.js b/bigbluebutton-html5/imports/api/user-reaction/server/helpers.js index e93da99ace..3a7824d41c 100644 --- a/bigbluebutton-html5/imports/api/user-reaction/server/helpers.js +++ b/bigbluebutton-html5/imports/api/user-reaction/server/helpers.js @@ -3,19 +3,7 @@ import UserReactions from '/imports/api/user-reaction'; import Logger from '/imports/startup/server/logger'; const expireSeconds = Meteor.settings.public.userReaction.expire; -const expireMilliseconds = expireSeconds * 1000 - -const addUserReactionsObserver = (meetingId) => { - const meetingUserReactions = UserReactions.find({ meetingId }); - return meetingUserReactions.observe({ - removed(document) { - const isExpirationTriggeredRemoval = (Date.now() - Date.parse(document.creationDate)) >= expireMilliseconds - if (isExpirationTriggeredRemoval) { - notifyExpiredReaction(meetingId, document.userId); - } - } - }) -} +const expireMilliseconds = expireSeconds * 1000; const notifyExpiredReaction = (meetingId, userId) => { try { @@ -40,8 +28,18 @@ const notifyExpiredReaction = (meetingId, userId) => { } catch (err) { Logger.error(`Exception while invoking method resetUserReaction ${err.stack}`); } -} +}; -export { - addUserReactionsObserver, -}; \ No newline at end of file +const addUserReactionsObserver = (meetingId) => { + const meetingUserReactions = UserReactions.find({ meetingId }); + return meetingUserReactions.observe({ + removed(document) { + const isExpirationTriggeredRemoval = (Date.now() - Date.parse(document.creationDate)) >= expireMilliseconds; + if (isExpirationTriggeredRemoval) { + notifyExpiredReaction(meetingId, document.userId); + } + }, + }); +}; + +export default addUserReactionsObserver; diff --git a/bigbluebutton-html5/imports/api/user-reaction/server/methods.js b/bigbluebutton-html5/imports/api/user-reaction/server/methods.js index 6cc308d7ad..87b8c5d77e 100644 --- a/bigbluebutton-html5/imports/api/user-reaction/server/methods.js +++ b/bigbluebutton-html5/imports/api/user-reaction/server/methods.js @@ -1,8 +1,6 @@ import { Meteor } from 'meteor/meteor'; import setUserReaction from './methods/setUserReaction'; -import clearAllUsersEmoji from './methods/clearAllUsersEmoji'; Meteor.methods({ setUserReaction, - clearAllUsersEmoji, }); diff --git a/bigbluebutton-html5/imports/api/user-reaction/server/methods/setUserReaction.js b/bigbluebutton-html5/imports/api/user-reaction/server/methods/setUserReaction.js index 133c81390c..2277ac6150 100644 --- a/bigbluebutton-html5/imports/api/user-reaction/server/methods/setUserReaction.js +++ b/bigbluebutton-html5/imports/api/user-reaction/server/methods/setUserReaction.js @@ -4,25 +4,25 @@ import RedisPubSub from '/imports/startup/server/redis'; import Logger from '/imports/startup/server/logger'; import { extractCredentials } from '/imports/api/common/server/helpers'; -export default function setUserReaction(emoji, userId = undefined) { +export default function setUserReaction(reactionEmoji, userId = undefined) { try { const REDIS_CONFIG = Meteor.settings.private.redis; const CHANNEL = REDIS_CONFIG.channels.toAkkaApps; - const EVENT_NAME = 'ChangeUserEmojiCmdMsg'; + const EVENT_NAME = 'ChangeUserReactionEmojiReqMsg'; const { meetingId, requesterUserId } = extractCredentials(this.userId); check(meetingId, String); check(requesterUserId, String); - check(emoji, String); + check(reactionEmoji, String); const payload = { - emoji, - userId: userId ? userId : requesterUserId, + reactionEmoji, + userId: userId || requesterUserId, }; - Logger.verbose('User emoji status updated', { - emoji, requesterUserId, meetingId, + Logger.verbose('User reactionEmoji status updated', { + reactionEmoji, requesterUserId, meetingId, }); RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload); diff --git a/bigbluebutton-html5/imports/api/users-persistent-data/server/modifiers/addUserPersistentData.js b/bigbluebutton-html5/imports/api/users-persistent-data/server/modifiers/addUserPersistentData.js index 71288d6ae5..4e833027fc 100644 --- a/bigbluebutton-html5/imports/api/users-persistent-data/server/modifiers/addUserPersistentData.js +++ b/bigbluebutton-html5/imports/api/users-persistent-data/server/modifiers/addUserPersistentData.js @@ -23,6 +23,9 @@ export default async function addUserPersistentData(user) { waitingForAcceptance: Match.Maybe(Boolean), guestStatus: String, emoji: String, + reactionEmoji: String, + raiseHand: Boolean, + away: Boolean, presenter: Boolean, locked: Boolean, avatar: String, diff --git a/bigbluebutton-html5/imports/api/users/server/eventHandlers.js b/bigbluebutton-html5/imports/api/users/server/eventHandlers.js index 1933aa2bad..8edb52a40f 100644 --- a/bigbluebutton-html5/imports/api/users/server/eventHandlers.js +++ b/bigbluebutton-html5/imports/api/users/server/eventHandlers.js @@ -4,15 +4,23 @@ import handleUserJoined from './handlers/userJoined'; import handleUserLeftFlagUpdated from './handlers/userLeftFlagUpdated'; import handleValidateAuthToken from './handlers/validateAuthToken'; import handlePresenterAssigned from './handlers/presenterAssigned'; +import handleEmojiStatus from './handlers/emojiStatus'; import handleChangeRole from './handlers/changeRole'; import handleUserPinChanged from './handlers/userPinChanged'; import handleUserInactivityInspect from './handlers/userInactivityInspect'; -import handleChangeMobileFlag from '/imports/api/users/server/handlers/changeMobileFlag'; +import handleChangeMobileFlag from './handlers/changeMobileFlag'; +import handleChangeRaiseHand from './handlers/changeRaiseHand'; +import handleAway from './handlers/changeAway'; +import handleClearUsersEmoji from './handlers/clearUsersEmoji'; RedisPubSub.on('PresenterAssignedEvtMsg', handlePresenterAssigned); RedisPubSub.on('UserJoinedMeetingEvtMsg', handleUserJoined); RedisPubSub.on('UserLeftMeetingEvtMsg', handleRemoveUser); RedisPubSub.on('ValidateAuthTokenRespMsg', handleValidateAuthToken); +RedisPubSub.on('UserEmojiChangedEvtMsg', handleEmojiStatus); +RedisPubSub.on('ClearedAllUsersEmojiEvtMsg', handleClearUsersEmoji); +RedisPubSub.on('UserRaiseHandChangedEvtMsg', handleChangeRaiseHand); +RedisPubSub.on('UserAwayChangedEvtMsg', handleAway); RedisPubSub.on('UserRoleChangedEvtMsg', handleChangeRole); RedisPubSub.on('UserMobileFlagChangedEvtMsg', handleChangeMobileFlag); RedisPubSub.on('UserLeftFlagUpdatedEvtMsg', handleUserLeftFlagUpdated); diff --git a/bigbluebutton-html5/imports/api/users/server/handlers/changeAway.js b/bigbluebutton-html5/imports/api/users/server/handlers/changeAway.js new file mode 100644 index 0000000000..eff0b91e34 --- /dev/null +++ b/bigbluebutton-html5/imports/api/users/server/handlers/changeAway.js @@ -0,0 +1,11 @@ +import { check } from 'meteor/check'; +import changeAway from '/imports/api/users/server/modifiers/changeAway'; + +export default async function handleAway(payload, meetingId) { + check(payload.body, Object); + check(meetingId, String); + + const { userId: requesterUserId, away } = payload.body; + + await changeAway(meetingId, requesterUserId, away); +} diff --git a/bigbluebutton-html5/imports/api/users/server/handlers/changeRaiseHand.js b/bigbluebutton-html5/imports/api/users/server/handlers/changeRaiseHand.js new file mode 100644 index 0000000000..6cef5bee76 --- /dev/null +++ b/bigbluebutton-html5/imports/api/users/server/handlers/changeRaiseHand.js @@ -0,0 +1,11 @@ +import { check } from 'meteor/check'; +import changeRaiseHand from '/imports/api/users/server/modifiers/changeRaiseHand'; + +export default async function handleChangeRaiseHand(payload, meetingId) { + check(payload.body, Object); + check(meetingId, String); + + const { userId: requesterUserId, raiseHand } = payload.body; + + await changeRaiseHand(meetingId, requesterUserId, raiseHand); +} diff --git a/bigbluebutton-html5/imports/api/user-reaction/server/handlers/clearUsersEmoji.js b/bigbluebutton-html5/imports/api/users/server/handlers/clearUsersEmoji.js similarity index 100% rename from bigbluebutton-html5/imports/api/user-reaction/server/handlers/clearUsersEmoji.js rename to bigbluebutton-html5/imports/api/users/server/handlers/clearUsersEmoji.js diff --git a/bigbluebutton-html5/imports/api/user-reaction/server/modifiers/addUserEmoji.js b/bigbluebutton-html5/imports/api/users/server/handlers/emojiStatus.js similarity index 59% rename from bigbluebutton-html5/imports/api/user-reaction/server/modifiers/addUserEmoji.js rename to bigbluebutton-html5/imports/api/users/server/handlers/emojiStatus.js index a7b927b29a..4f67a4b405 100644 --- a/bigbluebutton-html5/imports/api/user-reaction/server/modifiers/addUserEmoji.js +++ b/bigbluebutton-html5/imports/api/users/server/handlers/emojiStatus.js @@ -1,9 +1,9 @@ import Logger from '/imports/startup/server/logger'; import { check } from 'meteor/check'; import Users from '/imports/api/users'; -import sendStatusChatMsg from './sendStatusChatMsg'; -export default function handleEmojiStatus(meetingId, userId, emoji) { +export default async function handleEmojiStatus({ body }, meetingId) { + const { userId, emoji } = body; check(userId, String); check(emoji, String); @@ -21,11 +21,7 @@ export default function handleEmojiStatus(meetingId, userId, emoji) { }; try { - // must be called before modifying the users collection, because it - // needs to be consulted in order to know the previous emoji - sendStatusChatMsg(meetingId, userId, emoji); - - const numberAffected = Users.update(selector, modifier); + const numberAffected = await Users.updateAsync(selector, modifier); if (numberAffected) { Logger.info(`Assigned user emoji status ${emoji} id=${userId} meeting=${meetingId}`); diff --git a/bigbluebutton-html5/imports/api/users/server/handlers/reactionEmoji.js b/bigbluebutton-html5/imports/api/users/server/handlers/reactionEmoji.js new file mode 100644 index 0000000000..bff5d4d26c --- /dev/null +++ b/bigbluebutton-html5/imports/api/users/server/handlers/reactionEmoji.js @@ -0,0 +1,32 @@ +import Logger from '/imports/startup/server/logger'; +import { check } from 'meteor/check'; +import Users from '/imports/api/users'; + +export default async function handleReactionEmoji({ body }, meetingId) { + const { userId, reactionEmoji } = body; +aaa + check(userId, String); + check(reactionEmoji, String); + + const selector = { + meetingId, + userId, + }; + + const modifier = { + $set: { + reactionEmojiTime: (new Date()).getTime(), + reactionEmoji, + }, + }; + + try { + const numberAffected = await Users.updateAsync(selector, modifier); + + if (numberAffected) { + Logger.info(`Assigned user rectionEmoji ${reactionEmoji} id=${userId} meeting=${meetingId}`); + } + } catch (err) { + Logger.error(`Assigning user reactionEmoji: ${err}`); + } +} diff --git a/bigbluebutton-html5/imports/api/users/server/methods.js b/bigbluebutton-html5/imports/api/users/server/methods.js index 0851453c1c..c67a644f1c 100644 --- a/bigbluebutton-html5/imports/api/users/server/methods.js +++ b/bigbluebutton-html5/imports/api/users/server/methods.js @@ -2,6 +2,9 @@ import { Meteor } from 'meteor/meteor'; import validateAuthToken from './methods/validateAuthToken'; import setSpeechLocale from './methods/setSpeechLocale'; import setMobileUser from './methods/setMobileUser'; +import setEmojiStatus from './methods/setEmojiStatus'; +import changeAway from './methods/changeAway'; +import changeRaiseHand from './methods/changeRaiseHand'; import assignPresenter from './methods/assignPresenter'; import changeRole from './methods/changeRole'; import removeUser from './methods/removeUser'; @@ -12,10 +15,15 @@ import userLeftMeeting from './methods/userLeftMeeting'; import changePin from './methods/changePin'; import setRandomUser from './methods/setRandomUser'; import setExitReason from './methods/setExitReason'; +import clearAllUsersEmoji from './methods/clearAllUsersEmoji'; Meteor.methods({ setSpeechLocale, setMobileUser, + setEmojiStatus, + clearAllUsersEmoji, + changeAway, + changeRaiseHand, assignPresenter, changeRole, removeUser, diff --git a/bigbluebutton-html5/imports/api/users/server/methods/changeAway.js b/bigbluebutton-html5/imports/api/users/server/methods/changeAway.js new file mode 100644 index 0000000000..929dfad52b --- /dev/null +++ b/bigbluebutton-html5/imports/api/users/server/methods/changeAway.js @@ -0,0 +1,31 @@ +import { check } from 'meteor/check'; +import Logger from '/imports/startup/server/logger'; +import { extractCredentials } from '/imports/api/common/server/helpers'; +import RedisPubSub from '/imports/startup/server/redis'; + +export default async function changeAway(away) { + try { + const REDIS_CONFIG = Meteor.settings.private.redis; + const CHANNEL = REDIS_CONFIG.channels.toAkkaApps; + const EVENT_NAME = 'ChangeUserAwayReqMsg'; + + const { meetingId, requesterUserId } = extractCredentials(this.userId); + + check(meetingId, String); + check(requesterUserId, String); + check(away, Boolean); + + const payload = { + userId: requesterUserId, + away, + }; + + Logger.verbose('Updated away status for user', { + meetingId, requesterUserId, away, + }); + + RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload); + } catch (err) { + Logger.error(`Exception while invoking method changeAway ${err.stack}`); + } +} diff --git a/bigbluebutton-html5/imports/api/users/server/methods/changeRaiseHand.js b/bigbluebutton-html5/imports/api/users/server/methods/changeRaiseHand.js new file mode 100644 index 0000000000..77d7d8b756 --- /dev/null +++ b/bigbluebutton-html5/imports/api/users/server/methods/changeRaiseHand.js @@ -0,0 +1,31 @@ +import { check } from 'meteor/check'; +import Logger from '/imports/startup/server/logger'; +import { extractCredentials } from '/imports/api/common/server/helpers'; +import RedisPubSub from '/imports/startup/server/redis'; + +export default async function changeRaiseHand(raiseHand, userId = undefined) { + try { + const REDIS_CONFIG = Meteor.settings.private.redis; + const CHANNEL = REDIS_CONFIG.channels.toAkkaApps; + const EVENT_NAME = 'ChangeUserRaiseHandReqMsg'; + + const { meetingId, requesterUserId } = extractCredentials(this.userId); + + check(meetingId, String); + check(requesterUserId, String); + check(raiseHand, Boolean); + + const payload = { + userId: userId || requesterUserId, + raiseHand, + }; + + Logger.verbose('Updated raiseHand status for user', { + meetingId, requesterUserId, raiseHand, + }); + + RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload); + } catch (err) { + Logger.error(`Exception while invoking method changeRaiseHand ${err.stack}`); + } +} diff --git a/bigbluebutton-html5/imports/api/user-reaction/server/methods/clearAllUsersEmoji.js b/bigbluebutton-html5/imports/api/users/server/methods/clearAllUsersEmoji.js similarity index 100% rename from bigbluebutton-html5/imports/api/user-reaction/server/methods/clearAllUsersEmoji.js rename to bigbluebutton-html5/imports/api/users/server/methods/clearAllUsersEmoji.js diff --git a/bigbluebutton-html5/imports/api/user-reaction/server/modifiers/sendStatusChatMsg.js b/bigbluebutton-html5/imports/api/users/server/methods/sendAwayStatusChatMsg.js similarity index 90% rename from bigbluebutton-html5/imports/api/user-reaction/server/modifiers/sendStatusChatMsg.js rename to bigbluebutton-html5/imports/api/users/server/methods/sendAwayStatusChatMsg.js index 4b63d283b5..d2c082d74b 100644 --- a/bigbluebutton-html5/imports/api/user-reaction/server/modifiers/sendStatusChatMsg.js +++ b/bigbluebutton-html5/imports/api/users/server/methods/sendAwayStatusChatMsg.js @@ -5,13 +5,12 @@ import Users from '/imports/api/users'; import addSystemMsg from '/imports/api/group-chat-msg/server/modifiers/addSystemMsg'; const ROLE_VIEWER = Meteor.settings.public.user.role_viewer; - const CHAT_CONFIG = Meteor.settings.public.chat; const PUBLIC_GROUP_CHAT_ID = CHAT_CONFIG.public_group_id; const CHAT_USER_STATUS_MESSAGE = CHAT_CONFIG.system_messages_keys.chat_status_message; const SYSTEM_CHAT_TYPE = CHAT_CONFIG.type_system; -export default function sendStatusChatMsg(meetingId, userId, emoji) { +export default function sendAwayStatusChatMsg(meetingId, userId, newAwayStatus) { const user = Users.findOne( { meetingId, userId }, { @@ -19,7 +18,7 @@ export default function sendStatusChatMsg(meetingId, userId, emoji) { name: 1, role: 1, locked: 1, - emoji: 1, + away: 1, }, }, ); @@ -44,9 +43,9 @@ export default function sendStatusChatMsg(meetingId, userId, emoji) { // Send message if previous emoji or actual emoji is 'away' let status; - if (user.emoji === 'away') { + if (user.away && !newAwayStatus) { status = 'notAway'; - } else if (emoji === 'away') { + } else if (!user.away && newAwayStatus) { status = 'away'; } else { return null; diff --git a/bigbluebutton-html5/imports/api/users/server/methods/setEmojiStatus.js b/bigbluebutton-html5/imports/api/users/server/methods/setEmojiStatus.js new file mode 100644 index 0000000000..dd799886ce --- /dev/null +++ b/bigbluebutton-html5/imports/api/users/server/methods/setEmojiStatus.js @@ -0,0 +1,33 @@ +import { Meteor } from 'meteor/meteor'; +import { check } from 'meteor/check'; +import RedisPubSub from '/imports/startup/server/redis'; +import Logger from '/imports/startup/server/logger'; +import { extractCredentials } from '/imports/api/common/server/helpers'; + +export default function setEmojiStatus(userId, status) { + try { + const REDIS_CONFIG = Meteor.settings.private.redis; + const CHANNEL = REDIS_CONFIG.channels.toAkkaApps; + const EVENT_NAME = 'ChangeUserEmojiCmdMsg'; + + const { meetingId, requesterUserId } = extractCredentials(this.userId); + + check(meetingId, String); + check(requesterUserId, String); + check(userId, String); + check(status, String); + + const payload = { + emoji: status, + userId, + }; + + Logger.verbose('User emoji status updated', { + userId, status, requesterUserId, meetingId, + }); + + RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload); + } catch (err) { + Logger.error(`Exception while invoking method setEmojiStatus ${err.stack}`); + } +} diff --git a/bigbluebutton-html5/imports/api/users/server/modifiers/addUser.js b/bigbluebutton-html5/imports/api/users/server/modifiers/addUser.js index b8a476989b..6945110ce2 100755 --- a/bigbluebutton-html5/imports/api/users/server/modifiers/addUser.js +++ b/bigbluebutton-html5/imports/api/users/server/modifiers/addUser.js @@ -24,6 +24,9 @@ export default async function addUser(meetingId, userData) { waitingForAcceptance: Match.Maybe(Boolean), guestStatus: String, emoji: String, + reactionEmoji: String, + raiseHand: Boolean, + away: Boolean, presenter: Boolean, locked: Boolean, avatar: String, diff --git a/bigbluebutton-html5/imports/api/users/server/modifiers/changeAway.js b/bigbluebutton-html5/imports/api/users/server/modifiers/changeAway.js new file mode 100644 index 0000000000..c5ec077c1b --- /dev/null +++ b/bigbluebutton-html5/imports/api/users/server/modifiers/changeAway.js @@ -0,0 +1,30 @@ +import Logger from '/imports/startup/server/logger'; +import Users from '/imports/api/users'; +import sendAwayStatusChatMsg from '../methods/sendAwayStatusChatMsg'; + +export default async function changeAway(meetingId, userId, away) { + const selector = { + meetingId, + userId, + }; + + const modifier = { + $set: { + away, + awayTime: away ? (new Date()).getTime() : 0, + }, + }; + + try { + // must be called before modifying the users collection, because it + // needs to be consulted in order to know the previous emoji + sendAwayStatusChatMsg(meetingId, userId, away); + + const numberAffected = await Users.updateAsync(selector, modifier); + if (numberAffected) { + Logger.info(`Assigned away=${away} user id=${userId} meeting=${meetingId}`); + } + } catch (err) { + Logger.error(`Assigning away user: ${err}`); + } +} diff --git a/bigbluebutton-html5/imports/api/users/server/modifiers/changeRaiseHand.js b/bigbluebutton-html5/imports/api/users/server/modifiers/changeRaiseHand.js new file mode 100644 index 0000000000..543e1d7e34 --- /dev/null +++ b/bigbluebutton-html5/imports/api/users/server/modifiers/changeRaiseHand.js @@ -0,0 +1,26 @@ +import Logger from '/imports/startup/server/logger'; +import Users from '/imports/api/users'; + +export default async function changeRaiseHand(meetingId, userId, raiseHand) { + const selector = { + meetingId, + userId, + }; + + const modifier = { + $set: { + raiseHand, + raiseHandTime: raiseHand ? (new Date()).getTime() : 0, + }, + }; + + try { + const numberAffected = await Users.updateAsync(selector, modifier); + + if (numberAffected) { + Logger.info(`Assigned raiseHand=${raiseHand} user id=${userId} meeting=${meetingId}`); + } + } catch (err) { + Logger.error(`Assigning raiseHand user: ${err}`); + } +} diff --git a/bigbluebutton-html5/imports/api/user-reaction/server/modifiers/clearUsersEmoji.js b/bigbluebutton-html5/imports/api/users/server/modifiers/clearUsersEmoji.js similarity index 86% rename from bigbluebutton-html5/imports/api/user-reaction/server/modifiers/clearUsersEmoji.js rename to bigbluebutton-html5/imports/api/users/server/modifiers/clearUsersEmoji.js index 0f0e43ac9a..ed9ad03eb7 100644 --- a/bigbluebutton-html5/imports/api/user-reaction/server/modifiers/clearUsersEmoji.js +++ b/bigbluebutton-html5/imports/api/users/server/modifiers/clearUsersEmoji.js @@ -12,7 +12,11 @@ export default function clearUsersEmoji(meetingId) { const numberAffected = Users.update(selector, { $set: { emojiTime: (new Date()).getTime(), - emoji: "none", + emoji: 'none', + awayTime: 0, + away: false, + raiseHandTime: 0, + raiseHand: false, }, }, { multi: true }); diff --git a/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx b/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx index 808db70514..89567b3802 100755 --- a/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx +++ b/bigbluebutton-html5/imports/ui/components/actions-bar/component.jsx @@ -30,11 +30,13 @@ class ActionsBar extends PureComponent { } renderRaiseHand() { - const { isInteractionsButtonEnabled, isRaiseHandButtonEnabled } = this.props; + const { + isInteractionsButtonEnabled, isRaiseHandButtonEnabled, setEmojiStatus, currentUser, intl, + } = this.props; return (<> - {isInteractionsButtonEnabled ? : - isRaiseHandButtonEnabled ? + {isInteractionsButtonEnabled ? + : isRaiseHandButtonEnabled ? : null} ); } @@ -104,7 +106,7 @@ class ActionsBar extends PureComponent { {isCaptionsAvailable ? ( <> - { isCaptionsReaderMenuModalOpen ? { const { userId, emoji, + away, + raiseHand, intl, sidebarContentPanel, layoutContextDispatch, @@ -83,14 +85,11 @@ const InteractionsButton = (props) => { key: 'raise-hand', dataTest: 'raise-hand', icon: 'hand', - label: emoji === 'raiseHand' + label: raiseHand ? intl.formatMessage(intlMessages.notRaiseHandLabel) : intl.formatMessage(intlMessages.raiseHandLabel), onClick: () => { - UserListService.setEmojiStatus( - userId, - emoji === 'raiseHand' ? 'none' : 'raiseHand', - ); + UserListService.setUserRaiseHand(userId, !raiseHand); }, }); @@ -152,17 +151,11 @@ const InteractionsButton = (props) => { ); const handlePresent = () => { - UserListService.setEmojiStatus( - userId, - 'none', - ); + UserListService.setUserAway(userId, false); }; const handleAFK = () => { - UserListService.setEmojiStatus( - userId, - 'away', - ); + UserListService.setUserAway(userId, true); }; const buttonStatus = () => ( @@ -172,18 +165,18 @@ const InteractionsButton = (props) => { onClick={() => handlePresent()} id="btn" icon="user" - disabled={emoji !== 'away'} + disabled={!away} size="md" - color={emoji !== 'away' ? 'primary' : 'default'} + color={!away ? 'primary' : 'default'} />