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/voice/VoiceApp.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/VoiceApp.scala index efb6218611..fa2bd08e2a 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 @@ -260,7 +260,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 +323,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 +493,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 +518,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 +539,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 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/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 {