diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/LockSettingsUtil.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/LockSettingsUtil.scala index 69ddbf8e74..530cbe8b79 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/LockSettingsUtil.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/LockSettingsUtil.scala @@ -1,9 +1,10 @@ package org.bigbluebutton -import org.bigbluebutton.common2.msgs.{ BbbCommonEnvCoreMsg, BbbCoreEnvelope, BbbCoreHeaderWithMeetingId, MessageTypes, MuteUserInVoiceConfSysMsg, MuteUserInVoiceConfSysMsgBody, Routing } +import org.bigbluebutton.common2.msgs.{ BbbCommonEnvCoreMsg, BbbCoreEnvelope, BbbCoreHeaderWithMeetingId, MessageTypes, Routing } import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter } import org.bigbluebutton.core2.{ MeetingStatus2x } import org.bigbluebutton.core.apps.webcam.CameraHdlrHelpers +import org.bigbluebutton.core.apps.voice.VoiceApp import org.bigbluebutton.core.models.{ Roles, Users2x, @@ -16,19 +17,19 @@ import org.bigbluebutton.core.models.{ object LockSettingsUtil { - private def muteUserInVoiceConf(liveMeeting: LiveMeeting, outGW: OutMsgRouter, vu: VoiceUserState, mute: Boolean): Unit = { - val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, vu.intId) - val envelope = BbbCoreEnvelope(MuteUserInVoiceConfSysMsg.NAME, routing) - val header = BbbCoreHeaderWithMeetingId(MuteUserInVoiceConfSysMsg.NAME, liveMeeting.props.meetingProp.intId) - - val body = MuteUserInVoiceConfSysMsgBody(liveMeeting.props.voiceProp.voiceConf, vu.voiceUserId, mute) - val event = MuteUserInVoiceConfSysMsg(header, body) - val msgEvent = BbbCommonEnvCoreMsg(envelope, event) - - outGW.send(msgEvent) + private def muteUserInVoiceConf( + liveMeeting: LiveMeeting, + outGW: OutMsgRouter, + vu: VoiceUserState, mute: Boolean + )(implicit context: akka.actor.ActorContext): Unit = { + VoiceApp.muteUserInVoiceConf(liveMeeting, outGW, vu.intId, mute) } - private def applyMutingOfUsers(disableMic: Boolean, liveMeeting: LiveMeeting, outGW: OutMsgRouter): Unit = { + private def applyMutingOfUsers( + disableMic: Boolean, + liveMeeting: LiveMeeting, + outGW: OutMsgRouter + )(implicit context: akka.actor.ActorContext): Unit = { VoiceUsers.findAll(liveMeeting.voiceUsers) foreach { vu => Users2x.findWithIntId(liveMeeting.users2x, vu.intId).foreach { user => if (user.role == Roles.VIEWER_ROLE && !vu.listenOnly && user.locked) { @@ -44,12 +45,20 @@ object LockSettingsUtil { } } - def enforceLockSettingsForAllVoiceUsers(liveMeeting: LiveMeeting, outGW: OutMsgRouter): Unit = { + def enforceLockSettingsForAllVoiceUsers( + liveMeeting: LiveMeeting, + outGW: OutMsgRouter + )(implicit context: akka.actor.ActorContext): Unit = { val permissions = MeetingStatus2x.getPermissions(liveMeeting.status) applyMutingOfUsers(permissions.disableMic, liveMeeting, outGW) } - def enforceLockSettingsForVoiceUser(voiceUser: VoiceUserState, liveMeeting: LiveMeeting, outGW: OutMsgRouter): Unit = { + def enforceLockSettingsForVoiceUser( + voiceUser: VoiceUserState, + liveMeeting: LiveMeeting, + outGW: OutMsgRouter + )(implicit context: akka.actor.ActorContext): Unit = { + val permissions = MeetingStatus2x.getPermissions(liveMeeting.status) if (permissions.disableMic) { Users2x.findWithIntId(liveMeeting.users2x, voiceUser.intId).foreach { user => @@ -65,7 +74,11 @@ object LockSettingsUtil { } } - private def enforceListenOnlyUserIsMuted(intUserId: String, liveMeeting: LiveMeeting, outGW: OutMsgRouter): Unit = { + private def enforceListenOnlyUserIsMuted( + intUserId: String, + liveMeeting: LiveMeeting, + outGW: OutMsgRouter + )(implicit context: akka.actor.ActorContext): Unit = { val voiceUser = VoiceUsers.findWithIntId(liveMeeting.voiceUsers, intUserId) voiceUser.foreach { vu => // Make sure that listen only user is muted. (ralam dec 6, 2019 diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/SystemConfiguration.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/SystemConfiguration.scala index aacc7ff279..c0ce28edec 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/SystemConfiguration.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/SystemConfiguration.scala @@ -43,6 +43,7 @@ trait SystemConfiguration { lazy val ejectRogueVoiceUsers = Try(config.getBoolean("voiceConf.ejectRogueVoiceUsers")).getOrElse(true) lazy val dialInApprovalAudioPath = Try(config.getString("voiceConf.dialInApprovalAudioPath")).getOrElse("ivr/ivr-please_hold_while_party_contacted.wav") lazy val toggleListenOnlyAfterMuteTimer = Try(config.getInt("voiceConf.toggleListenOnlyAfterMuteTimer")).getOrElse(4) + lazy val transparentListenOnlyThreshold = Try(config.getInt("voiceConf.transparentListenOnlyThreshold")).getOrElse(0) lazy val recordingChapterBreakLengthInMinutes = Try(config.getInt("recording.chapterBreakLengthInMinutes")).getOrElse(0) diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/MuteUserCmdMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/MuteUserCmdMsgHdlr.scala index d29f40d113..e383ce3ba5 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/MuteUserCmdMsgHdlr.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/MuteUserCmdMsgHdlr.scala @@ -2,6 +2,7 @@ package org.bigbluebutton.core.apps.users import org.bigbluebutton.common2.msgs.MuteUserCmdMsg import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait } +import org.bigbluebutton.core.apps.voice.VoiceApp import org.bigbluebutton.core.models.{ Roles, Users2x, VoiceUsers } import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter } import org.bigbluebutton.core2.MeetingStatus2x @@ -51,13 +52,12 @@ trait MuteUserCmdMsgHdlr extends RightsManagementTrait { } else { if (u.muted != msg.body.mute) { log.info("Send mute user request. meetingId=" + meetingId + " userId=" + u.intId + " user=" + u) - val event = MsgBuilder.buildMuteUserInVoiceConfSysMsg( - meetingId, - voiceConf, - u.voiceUserId, + VoiceApp.muteUserInVoiceConf( + liveMeeting, + outGW, + u.intId, msg.body.mute ) - outGW.send(event) } } } diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/VoiceApp.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/VoiceApp.scala index efb6218611..41e4704962 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/VoiceApp.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/VoiceApp.scala @@ -138,7 +138,7 @@ object VoiceApp extends SystemConfiguration { // If the user is muted or unmuted with an unheld channel, broadcast // the event right away. - // If the user is unmuted, but channel is held, we need to wait for the + // If the user is unmuted, but channel is held, we need to wait for the // channel to be active again to broadcast the event. See // VoiceApp.handleChannelHoldChanged for this second case. if (muted || (!muted && !mutedUser.hold)) { @@ -149,7 +149,6 @@ object VoiceApp extends SystemConfiguration { outGW ) } - } } @@ -260,7 +259,7 @@ object VoiceApp extends SystemConfiguration { callingInto: String, hold: Boolean, uuid: String = "unused" - ): Unit = { + )(implicit context: akka.actor.ActorContext): Unit = { def broadcastEvent(voiceUserState: VoiceUserState): Unit = { val routing = Routing.addMsgToClientRouting( @@ -323,8 +322,28 @@ object VoiceApp extends SystemConfiguration { hold, uuid ) + + val prevTransparentLOStatus = VoiceHdlrHelpers.transparentListenOnlyAllowed( + liveMeeting + ) + VoiceUsers.add(liveMeeting.voiceUsers, voiceUserState) + val newTransparentLOStatus = VoiceHdlrHelpers.transparentListenOnlyAllowed( + liveMeeting + ) + + if (prevTransparentLOStatus != newTransparentLOStatus) { + // If the transparent listen only mode was activated or deactivated + // we need to update the listen only mode for all users in the meeting + // that are not muted. + handleTransparentLOModeChange( + liveMeeting, + outGW, + newTransparentLOStatus + ) + } + broadcastEvent(voiceUserState) if (liveMeeting.props.meetingProp.isBreakout) { @@ -473,6 +492,22 @@ object VoiceApp extends SystemConfiguration { } } + def handleTransparentLOModeChange( + liveMeeting: LiveMeeting, + outGW: OutMsgRouter, + allowed: Boolean, + )(implicit context: akka.actor.ActorContext): Unit = { + VoiceUsers.findAllMutedVoiceUsers(liveMeeting.voiceUsers) foreach { vu => + toggleListenOnlyMode( + liveMeeting, + outGW, + vu.intId, + vu.callerNum, + allowed + ) + } + } + def toggleListenOnlyMode( liveMeeting: LiveMeeting, outGW: OutMsgRouter, @@ -482,6 +517,16 @@ object VoiceApp extends SystemConfiguration { delay: Int = 0 )(implicit context: ActorContext): Unit = { implicit def executionContext = context.system.dispatcher + val allowed = VoiceHdlrHelpers.transparentListenOnlyAllowed(liveMeeting) + // Guarantee there are no other tasks for this channel + removeToggleListenOnlyTask(userId) + + // If the meeting has not yet hit the minium amount of duplex channels + // for transparent listen only to be enabled, we don't need to do anything + if (!allowed && enabled) { + return + } + def broacastEvent(): Unit = { val event = MsgBuilder.buildToggleListenOnlyModeSysMsg( liveMeeting.props.meetingProp.intId, @@ -493,9 +538,6 @@ object VoiceApp extends SystemConfiguration { outGW.send(event) } - // Guarantee there are no other tasks for this channel - removeToggleListenOnlyTask(userId) - if (enabled && delay > 0) { // If we are enabling listen only mode, we wait a bit before actually // dispatching the command - the idea is that recently muted users @@ -571,4 +613,48 @@ object VoiceApp extends SystemConfiguration { case _ => } } + + def muteUserInVoiceConf( + liveMeeting: LiveMeeting, + outGW: OutMsgRouter, + userId: String, + muted: Boolean + )(implicit context: akka.actor.ActorContext): Unit = { + for { + u <- VoiceUsers.findWithIntId( + liveMeeting.voiceUsers, + userId + ) + } yield { + if (u.muted != muted) { + val muteEvent = MsgBuilder.buildMuteUserInVoiceConfSysMsg( + liveMeeting.props.meetingProp.intId, + liveMeeting.props.voiceProp.voiceConf, + u.voiceUserId, + muted + ) + + // If we're unmuting, trigger a channel unhold -> toggle listen only + // mode -> unmute + if (!muted) { + holdChannelInVoiceConf( + liveMeeting, + outGW, + u.uuid, + muted + ) + toggleListenOnlyMode( + liveMeeting, + outGW, + u.intId, + u.callerNum, + muted, + 0 + ) + } + + outGW.send(muteEvent) + } + } + } } diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/VoiceHdlrHelpers.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/VoiceHdlrHelpers.scala index cc9e59310f..ad5bf60a61 100644 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/VoiceHdlrHelpers.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/VoiceHdlrHelpers.scala @@ -50,4 +50,14 @@ object VoiceHdlrHelpers extends SystemConfiguration { case _ => false } } + + def transparentListenOnlyAllowed(liveMeeting: LiveMeeting): Boolean = { + // Transparent listen only meeting-wide activation threshold. + // Threshold is the number of muted duplex audio channels in a meeting. + // 0 means no threshold, all users are subject to it + val mutedDuplexChannels = VoiceUsers.findAllMutedVoiceUsers(liveMeeting.voiceUsers).length + val threshold = transparentListenOnlyThreshold + + (threshold == 0) || (mutedDuplexChannels >= threshold) + } } diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/VoiceUsers.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/VoiceUsers.scala index abac75842b..a56b4ef176 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/VoiceUsers.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/VoiceUsers.scala @@ -33,6 +33,7 @@ object VoiceUsers { def findAllListenOnlyVoiceUsers(users: VoiceUsers): Vector[VoiceUserState] = users.toVector.filter(u => u.listenOnly == true) def findAllFreeswitchCallers(users: VoiceUsers): Vector[VoiceUserState] = users.toVector.filter(u => u.calledInto == "freeswitch") def findAllKurentoCallers(users: VoiceUsers): Vector[VoiceUserState] = users.toVector.filter(u => u.calledInto == "kms") + def findAllMutedVoiceUsers(users: VoiceUsers): Vector[VoiceUserState] = users.toVector.filter(u => u.muted == true && u.listenOnly == false) def findAllBannedCallers(users: VoiceUsers): Vector[VoiceUserState] = users.bannedUsers.values.toVector diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/handlers/MuteAllExceptPresentersCmdMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/handlers/MuteAllExceptPresentersCmdMsgHdlr.scala index 6f8f65e2fa..d10c404121 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/handlers/MuteAllExceptPresentersCmdMsgHdlr.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/handlers/MuteAllExceptPresentersCmdMsgHdlr.scala @@ -6,6 +6,7 @@ import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter } import org.bigbluebutton.core2.MeetingStatus2x import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait } import org.bigbluebutton.core2.message.senders.{ MsgBuilder } +import org.bigbluebutton.core.apps.voice.VoiceApp trait MuteAllExceptPresentersCmdMsgHdlr extends RightsManagementTrait { this: MeetingActor => @@ -57,8 +58,8 @@ trait MuteAllExceptPresentersCmdMsgHdlr extends RightsManagementTrait { VoiceUsers.findAll(liveMeeting.voiceUsers) foreach { vu => if (!vu.listenOnly) { Users2x.findWithIntId(liveMeeting.users2x, vu.intId) match { - case Some(u) => if (!u.presenter) muteUserInVoiceConf(vu, muted) - case None => muteUserInVoiceConf(vu, muted) + case Some(u) => if (!u.presenter) VoiceApp.muteUserInVoiceConf(liveMeeting, outGW, vu.intId, muted) + case None => VoiceApp.muteUserInVoiceConf(liveMeeting, outGW, vu.intId, muted) } } } @@ -82,17 +83,4 @@ trait MuteAllExceptPresentersCmdMsgHdlr extends RightsManagementTrait { BbbCommonEnvCoreMsg(envelope, event) } - def muteUserInVoiceConf(vu: VoiceUserState, mute: Boolean): Unit = { - val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, props.meetingProp.intId, vu.intId) - val envelope = BbbCoreEnvelope(MuteUserInVoiceConfSysMsg.NAME, routing) - val header = BbbCoreHeaderWithMeetingId(MuteUserInVoiceConfSysMsg.NAME, props.meetingProp.intId) - - val body = MuteUserInVoiceConfSysMsgBody(props.voiceProp.voiceConf, vu.voiceUserId, mute) - val event = MuteUserInVoiceConfSysMsg(header, body) - val msgEvent = BbbCommonEnvCoreMsg(envelope, event) - - outGW.send(msgEvent) - - } - } diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/handlers/MuteMeetingCmdMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/handlers/MuteMeetingCmdMsgHdlr.scala index cde43a7f5a..5ce1897427 100755 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/handlers/MuteMeetingCmdMsgHdlr.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/handlers/MuteMeetingCmdMsgHdlr.scala @@ -6,6 +6,7 @@ import org.bigbluebutton.core.models.{ VoiceUserState, VoiceUsers } import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter } import org.bigbluebutton.core2.MeetingStatus2x import org.bigbluebutton.core2.message.senders.{ MsgBuilder } +import org.bigbluebutton.core.apps.voice.VoiceApp trait MuteMeetingCmdMsgHdlr extends RightsManagementTrait { this: MeetingActor => @@ -30,19 +31,6 @@ trait MuteMeetingCmdMsgHdlr extends RightsManagementTrait { BbbCommonEnvCoreMsg(envelope, event) } - def muteUserInVoiceConf(vu: VoiceUserState, mute: Boolean): Unit = { - val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, props.meetingProp.intId, vu.intId) - val envelope = BbbCoreEnvelope(MuteUserInVoiceConfSysMsg.NAME, routing) - val header = BbbCoreHeaderWithMeetingId(MuteUserInVoiceConfSysMsg.NAME, props.meetingProp.intId) - - val body = MuteUserInVoiceConfSysMsgBody(props.voiceProp.voiceConf, vu.voiceUserId, mute) - val event = MuteUserInVoiceConfSysMsg(header, body) - val msgEvent = BbbCommonEnvCoreMsg(envelope, event) - - outGW.send(msgEvent) - - } - if (msg.body.mute != MeetingStatus2x.isMeetingMuted(liveMeeting.status)) { if (msg.body.mute) { val notifyEvent = MsgBuilder.buildNotifyAllInMeetingEvtMsg( @@ -79,7 +67,7 @@ trait MuteMeetingCmdMsgHdlr extends RightsManagementTrait { if (muted) { VoiceUsers.findAll(liveMeeting.voiceUsers) foreach { vu => if (!vu.listenOnly) { - muteUserInVoiceConf(vu, muted) + VoiceApp.muteUserInVoiceConf(liveMeeting, outGW, vu.intId, muted) } } } diff --git a/akka-bbb-apps/src/universal/conf/application.conf b/akka-bbb-apps/src/universal/conf/application.conf index c64b995127..bcce19dc92 100755 --- a/akka-bbb-apps/src/universal/conf/application.conf +++ b/akka-bbb-apps/src/universal/conf/application.conf @@ -97,6 +97,11 @@ voiceConf { # Time (seconds) to wait before requesting an audio channel hold after # muting a user. Used in the experimental, transparent listen only mode. toggleListenOnlyAfterMuteTimer = 4 + + # Transparent listen only meeting-wide activation threshold. + # Threshold is the number of muted duplex audio channels in a meeting. + # 0 = disabled + transparentListenOnlyThreshold = 0 } recording {