Merge pull request #20921 from prlanzarin/u27/fix/tlo-unhold-unmute-delay
fix(audio): change unmute/unhold flow to work around FS unmute stutter
This commit is contained in:
commit
7371aa6c18
@ -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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
||||
|
@ -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)
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
Loading…
Reference in New Issue
Block a user