diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/ListenOnlyModeToggledInSfuEvtMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/ListenOnlyModeToggledInSfuEvtMsgHdlr.scala index 0b16a39a61..b2e137c1e6 100644 --- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/ListenOnlyModeToggledInSfuEvtMsgHdlr.scala +++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/ListenOnlyModeToggledInSfuEvtMsgHdlr.scala @@ -12,11 +12,34 @@ trait ListenOnlyModeToggledInSfuEvtMsgHdlr { def handleListenOnlyModeToggledInSfuEvtMsg(msg: ListenOnlyModeToggledInSfuEvtMsg): Unit = { for { - vu <- VoiceUsers.findWithIntId(liveMeeting.voiceUsers, msg.body.userId) + vu <- VoiceUsers.findWithIntIdAndCallerNum( + liveMeeting.voiceUsers, + msg.body.userId, + msg.body.callerNum + ) } yield { - VoiceApp.holdChannelInVoiceConf( + // Do not execute if the command is asking for the channel to be HELD + // and the channel is already HELD. This is an edge case with the uuid_hold + // command being used through FSESL or fsapi where holding only works via + // the uuid_hold subcommand, which may cause the channel to be the + // opposite of what we want. + // The unhold (uuid_hold off) command is not affected by this, but we don't + // want to send it if the channel is already unheld. + if ((msg.body.enabled && !vu.hold) || !msg.body.enabled) { + VoiceApp.holdChannelInVoiceConf( + liveMeeting, + outGW, + vu.uuid, + msg.body.enabled + ) + } + + // If the channel is already in the desired state, just make sure + // any pending mute or unmute commands are sent. + VoiceApp.handleChannelHoldChanged( liveMeeting, outGW, + msg.body.userId, vu.uuid, msg.body.enabled ) 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 96a2b121c9..efb6218611 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 @@ -131,6 +131,7 @@ object VoiceApp extends SystemConfiguration { liveMeeting, outGW, mutedUser.intId, + mutedUser.callerNum, muted, toggleListenOnlyAfterMuteTimer ) @@ -476,6 +477,7 @@ object VoiceApp extends SystemConfiguration { liveMeeting: LiveMeeting, outGW: OutMsgRouter, userId: String, + callerNum: String, enabled: Boolean, delay: Int = 0 )(implicit context: ActorContext): Unit = { @@ -485,6 +487,7 @@ object VoiceApp extends SystemConfiguration { liveMeeting.props.meetingProp.intId, liveMeeting.props.voiceProp.voiceConf, userId, + callerNum, enabled ) outGW.send(event) @@ -543,13 +546,15 @@ object VoiceApp extends SystemConfiguration { hold ) match { case Some(vu) => - // Mute vs hold state mismatch, enforce hold state again. - // Mute state is the predominant one here. - if (vu.muted != hold) { + // Mute vs hold state mismatch. Enforce it if the user is unmuted, + // but hold is active, to avoid the user being unable to talk when + // the channel is active again. + if (!vu.muted && vu.hold) { toggleListenOnlyMode( liveMeeting, outGW, intId, + vu.callerNum, vu.muted ) } 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 8b9adbb15f..abac75842b 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 @@ -15,6 +15,18 @@ object VoiceUsers { users.toVector.find(u => u.uuid == uuid && u.intId == intId) } + def findWithIntIdAndCallerNum(users: VoiceUsers, intId: String, callerNum: String): Option[VoiceUserState] = { + // prlanzarin: This is a hack to allow for partial matching of callerNums. + // This is needed because the callerNums are incorrectly generated by + // FREESWITCH's ESL events when special characters are in place. + // e.g.: w_etc_0-bbbID-User;Semi (notice the semicolon) will be generated by + // FS as w_etc_0-bbbID-User (everything after the semicolon is ignored). + // We should review callerNum generation in the future as well as stop + // relying on it for session matching (use UUIDs or client session numbers instead). + users.toVector.find(u => u.intId == intId && + (u.callerNum.startsWith(callerNum) || callerNum.startsWith(u.callerNum))) + } + def findAll(users: VoiceUsers): Vector[VoiceUserState] = users.toVector def findAllNonListenOnlyVoiceUsers(users: VoiceUsers): Vector[VoiceUserState] = users.toVector.filter(u => u.listenOnly == false) 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 de5e3e7389..f74badeb48 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 @@ -637,11 +637,12 @@ object MsgBuilder { meetingId: String, voiceConf: String, userId: String, + callerNum: String, enabled: Boolean ): BbbCommonEnvCoreMsg = { val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka") val envelope = BbbCoreEnvelope(ToggleListenOnlyModeSysMsg.NAME, routing) - val body = ToggleListenOnlyModeSysMsgBody(voiceConf, userId, enabled) + val body = ToggleListenOnlyModeSysMsgBody(voiceConf, userId, callerNum, enabled) val header = BbbCoreHeaderWithMeetingId(ToggleListenOnlyModeSysMsg.NAME, meetingId) val event = ToggleListenOnlyModeSysMsg(header, body) diff --git a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/FreeswitchConferenceEventListener.java b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/FreeswitchConferenceEventListener.java index 076d82cc54..b136b0fd57 100755 --- a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/FreeswitchConferenceEventListener.java +++ b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/FreeswitchConferenceEventListener.java @@ -109,6 +109,7 @@ public class FreeswitchConferenceEventListener implements ConferenceEventListene evt.callSession, evt.clientSession, evt.userId, + evt.getVoiceUserId(), evt.callerName, evt.callState, evt.origCallerIdName, diff --git a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/IVoiceConferenceService.java b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/IVoiceConferenceService.java index 5ca5f7b4c6..72080f8732 100755 --- a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/IVoiceConferenceService.java +++ b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/IVoiceConferenceService.java @@ -59,6 +59,7 @@ public interface IVoiceConferenceService { String callSession, String clientSession, String userId, + String voiceUserId, String callerName, String callState, String origCallerIdName, diff --git a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/events/VoiceCallStateEvent.java b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/events/VoiceCallStateEvent.java index 9d0a9277e6..f48b3576fd 100755 --- a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/events/VoiceCallStateEvent.java +++ b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/events/VoiceCallStateEvent.java @@ -4,6 +4,8 @@ public class VoiceCallStateEvent extends VoiceConferenceEvent { public final String callSession; public final String clientSession; public final String userId; + // AKA mod_conference memberId + public final String voiceUserId; public final String callerName; public final String callState; public final String origCallerIdName; @@ -14,6 +16,7 @@ public class VoiceCallStateEvent extends VoiceConferenceEvent { String callSession, String clientSession, String userId, + String voiceUserId, String callerName, String callState, String origCallerIdName, @@ -22,9 +25,14 @@ public class VoiceCallStateEvent extends VoiceConferenceEvent { this.callSession = callSession; this.clientSession = clientSession; this.userId = userId; + this.voiceUserId = voiceUserId; this.callerName = callerName; this.callState = callState; this.origCallerIdName = origCallerIdName; this.origCalledDest = origCalledDest; } + + public String getVoiceUserId() { + return voiceUserId; + } } diff --git a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/ESLEventListener.java b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/ESLEventListener.java index a67e6e5768..0549d6babf 100755 --- a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/ESLEventListener.java +++ b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/ESLEventListener.java @@ -84,6 +84,7 @@ public class ESLEventListener implements IEslEventListener { String origCallerIdName = headers.get("Caller-Caller-ID-Name"); String origCallerDestNumber = headers.get("Caller-Destination-Number"); String clientSession = "0"; + String memberIdStr = memberId != null ? memberId.toString() : ""; Matcher matcher = CALLERNAME_PATTERN.matcher(callerIdName); Matcher callWithSess = CALLERNAME_WITH_SESS_INFO_PATTERN.matcher(callerIdName); @@ -106,6 +107,7 @@ public class ESLEventListener implements IEslEventListener { coreuuid, clientSession, voiceUserId, + memberIdStr, callerIdName, callState, origCallerIdName, @@ -281,6 +283,7 @@ public class ESLEventListener implements IEslEventListener { String varvBridge = (eventHeaders.get("variable_vbridge") == null) ? "" : eventHeaders.get("variable_vbridge"); if ("echo".equalsIgnoreCase(application) && !varvBridge.isEmpty()) { + Integer memberId = this.getMemberId(eventHeaders); String origCallerIdName = eventHeaders.get("Caller-Caller-ID-Name"); String origCallerDestNumber = eventHeaders.get("Caller-Destination-Number"); String coreuuid = eventHeaders.get("Core-UUID"); @@ -291,6 +294,7 @@ public class ESLEventListener implements IEslEventListener { String callerName = origCallerIdName; String clientSession = "0"; String callState = "IN_ECHO_TEST"; + String memberIdStr = memberId != null ? memberId.toString() : ""; Matcher callerListenOnly = CALLERNAME_LISTENONLY_PATTERN.matcher(origCallerIdName); Matcher callWithSess = CALLERNAME_WITH_SESS_INFO_PATTERN.matcher(origCallerIdName); @@ -314,6 +318,7 @@ public class ESLEventListener implements IEslEventListener { coreuuid, clientSession, voiceUserId, + memberIdStr, callerName, callState, origCallerIdName, @@ -321,6 +326,7 @@ public class ESLEventListener implements IEslEventListener { conferenceEventListener.handleConferenceEvent(csEvent); } else if ("RINGING".equalsIgnoreCase(channelCallState) && !varvBridge.isEmpty()) { + Integer memberId = this.getMemberId(eventHeaders); String origCallerIdName = eventHeaders.get("Caller-Caller-ID-Name"); String origCallerDestNumber = eventHeaders.get("Caller-Destination-Number"); String coreuuid = eventHeaders.get("Core-UUID"); @@ -330,6 +336,7 @@ public class ESLEventListener implements IEslEventListener { String callerName = origCallerIdName; String clientSession = "0"; String callState = "CALL_STARTED"; + String memberIdStr = memberId != null ? memberId.toString() : ""; Matcher callerListenOnly = CALLERNAME_LISTENONLY_PATTERN.matcher(origCallerIdName); Matcher callWithSess = CALLERNAME_WITH_SESS_INFO_PATTERN.matcher(origCallerIdName); @@ -353,6 +360,7 @@ public class ESLEventListener implements IEslEventListener { coreuuid, clientSession, voiceUserId, + memberIdStr, callerName, callState, origCallerIdName, @@ -365,6 +373,7 @@ public class ESLEventListener implements IEslEventListener { String channelState = (eventHeaders.get("Channel-State") == null) ? "" : eventHeaders.get("Channel-State"); if ("HANGUP".equalsIgnoreCase(channelCallState) && "CS_DESTROY".equalsIgnoreCase(channelState)) { + Integer memberId = this.getMemberId(eventHeaders); String origCallerIdName = eventHeaders.get("Caller-Caller-ID-Name"); String origCallerDestNumber = eventHeaders.get("Caller-Destination-Number"); String coreuuid = eventHeaders.get("Core-UUID"); @@ -374,6 +383,7 @@ public class ESLEventListener implements IEslEventListener { String callerName = origCallerIdName; String clientSession = "0"; String callState = "CALL_ENDED"; + String memberIdStr = memberId != null ? memberId.toString() : ""; Matcher callerListenOnly = CALLERNAME_LISTENONLY_PATTERN.matcher(origCallerIdName); Matcher callWithSess = CALLERNAME_WITH_SESS_INFO_PATTERN.matcher(origCallerIdName); @@ -397,6 +407,7 @@ public class ESLEventListener implements IEslEventListener { coreuuid, clientSession, voiceUserId, + memberIdStr, callerName, callState, origCallerIdName, @@ -405,6 +416,7 @@ public class ESLEventListener implements IEslEventListener { conferenceEventListener.handleConferenceEvent(csEvent); } else if ("RINGING".equalsIgnoreCase(channelCallState) && "CS_EXECUTE".equalsIgnoreCase(channelState)) { + Integer memberId = this.getMemberId(eventHeaders); String origCallerIdName = eventHeaders.get("Caller-Caller-ID-Name"); String origCallerDestNumber = eventHeaders.get("Caller-Destination-Number"); String coreuuid = eventHeaders.get("Core-UUID"); @@ -414,6 +426,7 @@ public class ESLEventListener implements IEslEventListener { String callerName = origCallerIdName; String clientSession = "0"; String callState = "CALL_STARTED"; + String memberIdStr = memberId != null ? memberId.toString() : ""; Matcher callerListenOnly = CALLERNAME_LISTENONLY_PATTERN.matcher(origCallerIdName); Matcher callWithSess = CALLERNAME_WITH_SESS_INFO_PATTERN.matcher(origCallerIdName); @@ -437,6 +450,7 @@ public class ESLEventListener implements IEslEventListener { coreuuid, clientSession, voiceUserId, + memberIdStr, callerName, callState, origCallerIdName, diff --git a/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/freeswitch/VoiceConferenceService.scala b/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/freeswitch/VoiceConferenceService.scala index 4e520a82dc..4c73bedcf5 100755 --- a/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/freeswitch/VoiceConferenceService.scala +++ b/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/freeswitch/VoiceConferenceService.scala @@ -229,6 +229,7 @@ class VoiceConferenceService(healthz: HealthzService, callSession: String, clientSession: String, userId: String, + voiceUserId: String, callerName: String, callState: String, origCallerIdName: String, @@ -240,6 +241,7 @@ class VoiceConferenceService(healthz: HealthzService, callSession = callSession, clientSession = clientSession, userId = userId, + voiceUserId = voiceUserId, callerName = callerName, callState = callState, origCallerIdName = origCallerIdName, diff --git a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/VoiceConfMsgs.scala b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/VoiceConfMsgs.scala index 86d878e6c1..4bea316d11 100755 --- a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/VoiceConfMsgs.scala +++ b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/VoiceConfMsgs.scala @@ -555,6 +555,7 @@ case class VoiceConfCallStateEvtMsgBody( callSession: String, clientSession: String, userId: String, + voiceUserId: String, callerName: String, callState: String, origCallerIdName: String, @@ -685,6 +686,7 @@ case class ToggleListenOnlyModeSysMsg( case class ToggleListenOnlyModeSysMsgBody( voiceConf: String, userId: String, + callerNum: String, enabled: Boolean ) @@ -701,5 +703,6 @@ case class ListenOnlyModeToggledInSfuEvtMsgBody( meetingId: String, voiceConf: String, userId: String, + callerNum: String, enabled: Boolean ) diff --git a/bbb-webrtc-sfu.placeholder.sh b/bbb-webrtc-sfu.placeholder.sh index e94cdce28c..ed899aacbe 100755 --- a/bbb-webrtc-sfu.placeholder.sh +++ b/bbb-webrtc-sfu.placeholder.sh @@ -1 +1 @@ -git clone --branch v2.13.3 --depth 1 https://github.com/bigbluebutton/bbb-webrtc-sfu bbb-webrtc-sfu +git clone --branch v2.14.0 --depth 1 https://github.com/bigbluebutton/bbb-webrtc-sfu bbb-webrtc-sfu