Merge pull request #15385 from mariogasparoni/fix-15163-v2.6

fix(audio): prevent dial-in waiting / guest users to speak/listen to the room
This commit is contained in:
Anton Georgiev 2022-07-14 16:54:14 -04:00 committed by GitHub
commit e35fee622b
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 484 additions and 0 deletions

View File

@ -40,6 +40,7 @@ trait SystemConfiguration {
lazy val checkVoiceRecordingInterval = Try(config.getInt("voiceConf.checkRecordingInterval")).getOrElse(19)
lazy val syncVoiceUsersStatusInterval = Try(config.getInt("voiceConf.syncUserStatusInterval")).getOrElse(43)
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 recordingChapterBreakLengthInMinutes = Try(config.getInt("recording.chapterBreakLengthInMinutes")).getOrElse(0)

View File

@ -63,6 +63,13 @@ trait UserJoinedVoiceConfEvtMsgHdlr extends SystemConfiguration {
val guest = GuestWaiting(msg.body.intId, msg.body.callerIdName, Roles.VIEWER_ROLE, true, "", true, System.currentTimeMillis())
GuestsWaiting.add(liveMeeting.guestsWaiting, guest)
notifyModeratorsOfGuestWaiting(guest, liveMeeting.users2x, liveMeeting.props.meetingProp.intId)
VoiceApp.toggleUserAudioInVoiceConf(
liveMeeting,
outGW,
msg.body.voiceUserId,
false
)
}
}

View File

@ -353,4 +353,66 @@ object VoiceApp extends SystemConfiguration {
)
}
}
/** Toggle audio for the given user in voice conference.
*
* We first stop the current audio being played, preventing the playback
* to also mix the "You are the first person ..." audio.
* After that we check if we are turning on/off based on enabled param. If
* enabled is false we:
* - play a sound to let user know that an action is required
* (eg. guest approval) from the server/room.
* - put the user on hold, so DTMFs for mute / deaf mute are also disabled
* - mute the user (other participants won't hear users's audio)
* - deaf the user (user won't hear other participant's audio)
* If disabled, we remove user from hold, mute and deaf states, allowing the
* user to interact with the room.
*/
def toggleUserAudioInVoiceConf(
liveMeeting: LiveMeeting,
outGW: OutMsgRouter,
voiceUserId: String,
enabled: Boolean
): Unit = {
val stopEvent = MsgBuilder.buildStopSoundInVoiceConfSysMsg(
liveMeeting.props.meetingProp.intId,
liveMeeting.props.voiceProp.voiceConf,
""
)
outGW.send(stopEvent)
if (!enabled) {
val playEvent = MsgBuilder.buildPlaySoundInVoiceConfSysMsg(
liveMeeting.props.meetingProp.intId,
liveMeeting.props.voiceProp.voiceConf,
voiceUserId,
dialInApprovalAudioPath
)
outGW.send(playEvent)
}
val holdEvent = MsgBuilder.buildHoldUserInVoiceConfSysMsg(
liveMeeting.props.meetingProp.intId,
liveMeeting.props.voiceProp.voiceConf,
voiceUserId,
!enabled
)
outGW.send(holdEvent)
val muteEvent = MsgBuilder.buildMuteUserInVoiceConfSysMsg(
liveMeeting.props.meetingProp.intId,
liveMeeting.props.voiceProp.voiceConf,
voiceUserId,
!enabled
)
outGW.send(muteEvent)
val deafEvent = MsgBuilder.buildDeafUserInVoiceConfSysMsg(
liveMeeting.props.meetingProp.intId,
liveMeeting.props.voiceProp.voiceConf,
voiceUserId,
!enabled
)
outGW.send(deafEvent)
}
}

View File

@ -65,6 +65,10 @@ class AnalyticsActor(val includeChat: Boolean) extends Actor with ActorLogging {
case m: RecordingStartedVoiceConfEvtMsg => logMessage(msg)
case m: MuteUserCmdMsg => logMessage(msg)
case m: MuteUserInVoiceConfSysMsg => logMessage(msg)
case m: DeafUserInVoiceConfSysMsg => logMessage(msg)
case m: HoldUserInVoiceConfSysMsg => logMessage(msg)
case m: PlaySoundInVoiceConfSysMsg => logMessage(msg)
case m: StopSoundInVoiceConfSysMsg => logMessage(msg)
case m: MuteAllExceptPresentersCmdMsg => logMessage(msg)
case m: EjectUserFromVoiceCmdMsg => logMessage(msg)
case m: MuteMeetingCmdMsg => logMessage(msg)

View File

@ -49,6 +49,14 @@ class FromAkkaAppsMsgSenderActor(msgSender: MessageSender)
msgSender.send(toVoiceConfRedisChannel, json)
case MuteUserInVoiceConfSysMsg.NAME =>
msgSender.send(toVoiceConfRedisChannel, json)
case DeafUserInVoiceConfSysMsg.NAME =>
msgSender.send(toVoiceConfRedisChannel, json)
case HoldUserInVoiceConfSysMsg.NAME =>
msgSender.send(toVoiceConfRedisChannel, json)
case PlaySoundInVoiceConfSysMsg.NAME =>
msgSender.send(toVoiceConfRedisChannel, json)
case StopSoundInVoiceConfSysMsg.NAME =>
msgSender.send(toVoiceConfRedisChannel, json)
case StartRecordingVoiceConfSysMsg.NAME =>
msgSender.send(toVoiceConfRedisChannel, json)
case StopRecordingVoiceConfSysMsg.NAME =>

View File

@ -45,6 +45,23 @@ trait GuestsWaitingApprovedMsgHdlr extends HandlerHelpers with RightsManagementT
false,
"freeswitch"
)
VoiceUsers.findWithIntId(
liveMeeting.voiceUsers,
dialInUser.intId
) match {
case Some(vu) =>
VoiceApp.toggleUserAudioInVoiceConf(
liveMeeting,
outGW,
vu.voiceUserId,
true
)
case None =>
println(s"Skipping transferring dial-in user to the "
+ "voiceconf: dial-in user already left. meetingId= "
+ "${liveMeeting.props.meetingProp.intId}, userId="
+ "${dialInUser.intId}")
}
} else {
VoiceApp.removeUserFromVoiceConf(liveMeeting, outGW, dialInUser.extId)
val event = MsgBuilder.buildEjectUserFromVoiceConfSysMsg(liveMeeting.props.meetingProp.intId, liveMeeting.props.voiceProp.voiceConf, g.guest)

View File

@ -336,6 +336,46 @@ object MsgBuilder {
BbbCommonEnvCoreMsg(envelope, event)
}
def buildDeafUserInVoiceConfSysMsg(meetingId: String, voiceConf: String, voiceUserId: String, deaf: Boolean): BbbCommonEnvCoreMsg = {
val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
val envelope = BbbCoreEnvelope(DeafUserInVoiceConfSysMsg.NAME, routing)
val body = DeafUserInVoiceConfSysMsgBody(voiceConf, voiceUserId, deaf)
val header = BbbCoreHeaderWithMeetingId(DeafUserInVoiceConfSysMsg.NAME, meetingId)
val event = DeafUserInVoiceConfSysMsg(header, body)
BbbCommonEnvCoreMsg(envelope, event)
}
def buildHoldUserInVoiceConfSysMsg(meetingId: String, voiceConf: String, voiceUserId: String, hold: Boolean): BbbCommonEnvCoreMsg = {
val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
val envelope = BbbCoreEnvelope(HoldUserInVoiceConfSysMsg.NAME, routing)
val body = HoldUserInVoiceConfSysMsgBody(voiceConf, voiceUserId, hold)
val header = BbbCoreHeaderWithMeetingId(HoldUserInVoiceConfSysMsg.NAME, meetingId)
val event = HoldUserInVoiceConfSysMsg(header, body)
BbbCommonEnvCoreMsg(envelope, event)
}
def buildPlaySoundInVoiceConfSysMsg(meetingId: String, voiceConf: String, voiceUserId: String, soundPath: String): BbbCommonEnvCoreMsg = {
val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
val envelope = BbbCoreEnvelope(PlaySoundInVoiceConfSysMsg.NAME, routing)
val body = PlaySoundInVoiceConfSysMsgBody(voiceConf, voiceUserId, soundPath)
val header = BbbCoreHeaderWithMeetingId(PlaySoundInVoiceConfSysMsg.NAME, meetingId)
val event = PlaySoundInVoiceConfSysMsg(header, body)
BbbCommonEnvCoreMsg(envelope, event)
}
def buildStopSoundInVoiceConfSysMsg(meetingId: String, voiceConf: String, voiceUserId: String): BbbCommonEnvCoreMsg = {
val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
val envelope = BbbCoreEnvelope(StopSoundInVoiceConfSysMsg.NAME, routing)
val body = StopSoundInVoiceConfSysMsgBody(voiceConf, voiceUserId)
val header = BbbCoreHeaderWithMeetingId(StopSoundInVoiceConfSysMsg.NAME, meetingId)
val event = StopSoundInVoiceConfSysMsg(header, body)
BbbCommonEnvCoreMsg(envelope, event)
}
def buildBreakoutRoomEndedEvtMsg(meetingId: String, userId: String, breakoutRoomId: String): BbbCommonEnvCoreMsg = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, meetingId, userId)
val envelope = BbbCoreEnvelope(BreakoutRoomEndedEvtMsg.NAME, routing)

View File

@ -88,6 +88,10 @@ voiceConf {
syncUserStatusInterval = 41
# Voice users with no matching user record
ejectRogueVoiceUsers = true
# Path to the audio file being played when dial-in user is waiting for
# approval. This can be relative to FreeSWITCH sounds folder
dialInApprovalAudioPath = "ivr/ivr-please_hold_while_party_contacted.wav"
}
recording {

View File

@ -204,6 +204,34 @@ public class ConnectionManager {
}
}
public void deaf(DeafUserCommand duc) {
Client c = manager.getESLClient();
if (c.canSend()) {
c.sendAsyncApiCommand(duc.getCommand(), duc.getCommandArgs());
}
}
public void hold(HoldUserCommand huc) {
Client c = manager.getESLClient();
if (c.canSend()) {
c.sendAsyncApiCommand(huc.getCommand(), huc.getCommandArgs());
}
}
public void playSound(PlaySoundCommand psc) {
Client c = manager.getESLClient();
if (c.canSend()) {
c.sendAsyncApiCommand(psc.getCommand(), psc.getCommandArgs());
}
}
public void stopSound(StopSoundCommand ssc) {
Client c = manager.getESLClient();
if (c.canSend()) {
c.sendAsyncApiCommand(ssc.getCommand(), ssc.getCommandArgs());
}
}
public void tranfer(TransferUserToMeetingCommand tutmc) {
Client c = manager.getESLClient();
if (c.canSend()) {

View File

@ -28,6 +28,10 @@ import org.bigbluebutton.freeswitch.voice.freeswitch.actions.EjectUserCommand;
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.FreeswitchCommand;
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.GetAllUsersCommand;
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.MuteUserCommand;
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.DeafUserCommand;
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.HoldUserCommand;
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.PlaySoundCommand;
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.StopSoundCommand;
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.RecordConferenceCommand;
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.TransferUserToMeetingCommand;
import org.bigbluebutton.freeswitch.voice.freeswitch.actions.*;
@ -123,6 +127,26 @@ public class FreeswitchApplication implements IDelayedCommandListener{
queueMessage(mpc);
}
public void deafUser(String voiceConfId, String voiceUserId, Boolean deaf) {
DeafUserCommand duc = new DeafUserCommand(voiceConfId, voiceUserId, deaf, USER);
queueMessage(duc);
}
public void holdUser(String voiceConfId, String voiceUserId, Boolean hold) {
HoldUserCommand huc = new HoldUserCommand(voiceConfId, voiceUserId, hold, USER);
queueMessage(huc);
}
public void playSound(String voiceConfId, String voiceUserId, String soundPath) {
PlaySoundCommand psc = new PlaySoundCommand(voiceConfId, voiceUserId, soundPath, USER);
queueMessage(psc);
}
public void stopSound(String voiceConfId, String voiceUserId) {
StopSoundCommand ssc = new StopSoundCommand(voiceConfId, voiceUserId, USER);
queueMessage(ssc);
}
public void eject(String voiceConfId, String voiceUserId) {
EjectUserCommand mpc = new EjectUserCommand(voiceConfId, voiceUserId, USER);
queueMessage(mpc);
@ -158,6 +182,18 @@ public class FreeswitchApplication implements IDelayedCommandListener{
} else if (command instanceof MuteUserCommand) {
MuteUserCommand cmd = (MuteUserCommand) command;
manager.mute(cmd);
} else if (command instanceof DeafUserCommand) {
DeafUserCommand cmd = (DeafUserCommand) command;
manager.deaf(cmd);
} else if (command instanceof HoldUserCommand) {
HoldUserCommand cmd = (HoldUserCommand) command;
manager.hold(cmd);
} else if (command instanceof PlaySoundCommand) {
PlaySoundCommand cmd = (PlaySoundCommand) command;
manager.playSound(cmd);
} else if (command instanceof StopSoundCommand) {
StopSoundCommand cmd = (StopSoundCommand) command;
manager.stopSound(cmd);
} else if (command instanceof EjectUserCommand) {
EjectUserCommand cmd = (EjectUserCommand) command;
manager.eject(cmd);

View File

@ -0,0 +1,40 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2022 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.freeswitch.voice.freeswitch.actions;
public class DeafUserCommand extends FreeswitchCommand {
private final String participant;
private final Boolean deaf;
public DeafUserCommand(String room, String participant, Boolean deaf, String requesterId) {
super(room, requesterId);
this.participant = participant;
this.deaf = deaf;
}
@Override
public String getCommandArgs() {
String action = "undeaf";
if (deaf) action = "deaf";
return room + SPACE + action + SPACE + participant;
}
}

View File

@ -0,0 +1,40 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2022 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.freeswitch.voice.freeswitch.actions;
public class HoldUserCommand extends FreeswitchCommand {
private final String participant;
private final Boolean hold;
public HoldUserCommand(String room, String participant, Boolean hold, String requesterId) {
super(room, requesterId);
this.participant = participant;
this.hold = hold;
}
@Override
public String getCommandArgs() {
String action = "unhold";
if (hold) action = "hold";
return room + SPACE + action + SPACE + participant;
}
}

View File

@ -0,0 +1,40 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2022 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.freeswitch.voice.freeswitch.actions;
public class PlaySoundCommand extends FreeswitchCommand {
private final String participant;
private final String soundPath;
public PlaySoundCommand(String room, String participant, String soundPath, String requesterId) {
super(room, requesterId);
this.participant = participant;
this.soundPath = soundPath;
}
@Override
public String getCommandArgs() {
String action = "play";
return room + SPACE + action + SPACE
+ this.soundPath + SPACE + participant;
}
}

View File

@ -0,0 +1,37 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2022 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.freeswitch.voice.freeswitch.actions;
public class StopSoundCommand extends FreeswitchCommand {
private final String participant;
public StopSoundCommand(String room, String participant, String requesterId) {
super(room, requesterId);
this.participant = participant;
}
@Override
public String getCommandArgs() {
String action = "stop all";
return room + SPACE + action + SPACE + participant;
}
}

View File

@ -117,6 +117,78 @@ trait RxJsonMsgDeserializer {
}
}
def routeDeafUserInVoiceConfMsg(envelope: BbbCoreEnvelope, jsonNode: JsonNode): Unit = {
def deserialize(jsonNode: JsonNode): Option[DeafUserInVoiceConfSysMsg] = {
val (result, error) = JsonDeserializer.toBbbCommonMsg[DeafUserInVoiceConfSysMsg](jsonNode)
result match {
case Some(msg) => Some(msg.asInstanceOf[DeafUserInVoiceConfSysMsg])
case None =>
log.error("Failed to deserialize message: error: {} \n msg: {}", error, jsonNode)
None
}
}
for {
m <- deserialize(jsonNode)
} yield {
fsApp.deafUser(m.body.voiceConf, m.body.voiceUserId, m.body.deaf)
}
}
def routeHoldUserInVoiceConfMsg(envelope: BbbCoreEnvelope, jsonNode: JsonNode): Unit = {
def deserialize(jsonNode: JsonNode): Option[HoldUserInVoiceConfSysMsg] = {
val (result, error) = JsonDeserializer.toBbbCommonMsg[HoldUserInVoiceConfSysMsg](jsonNode)
result match {
case Some(msg) => Some(msg.asInstanceOf[HoldUserInVoiceConfSysMsg])
case None =>
log.error("Failed to deserialize message: error: {} \n msg: {}", error, jsonNode)
None
}
}
for {
m <- deserialize(jsonNode)
} yield {
fsApp.holdUser(m.body.voiceConf, m.body.voiceUserId, m.body.hold)
}
}
def routePlaySoundInVoiceConfMsg(envelope: BbbCoreEnvelope, jsonNode: JsonNode): Unit = {
def deserialize(jsonNode: JsonNode): Option[PlaySoundInVoiceConfSysMsg] = {
val (result, error) = JsonDeserializer.toBbbCommonMsg[PlaySoundInVoiceConfSysMsg](jsonNode)
result match {
case Some(msg) => Some(msg.asInstanceOf[PlaySoundInVoiceConfSysMsg])
case None =>
log.error("Failed to deserialize message: error: {} \n msg: {}", error, jsonNode)
None
}
}
for {
m <- deserialize(jsonNode)
} yield {
fsApp.playSound(m.body.voiceConf, m.body.voiceUserId, m.body.soundPath)
}
}
def routeStopSoundInVoiceConfMsg(envelope: BbbCoreEnvelope, jsonNode: JsonNode): Unit = {
def deserialize(jsonNode: JsonNode): Option[StopSoundInVoiceConfSysMsg] = {
val (result, error) = JsonDeserializer.toBbbCommonMsg[StopSoundInVoiceConfSysMsg](jsonNode)
result match {
case Some(msg) => Some(msg.asInstanceOf[StopSoundInVoiceConfSysMsg])
case None =>
log.error("Failed to deserialize message: error: {} \n msg: {}", error, jsonNode)
None
}
}
for {
m <- deserialize(jsonNode)
} yield {
fsApp.stopSound(m.body.voiceConf, m.body.voiceUserId)
}
}
def routeTransferUserToVoiceConfMsg(envelope: BbbCoreEnvelope, jsonNode: JsonNode): Unit = {
def deserialize(jsonNode: JsonNode): Option[TransferUserToVoiceConfSysMsg] = {
val (result, error) = JsonDeserializer.toBbbCommonMsg[TransferUserToVoiceConfSysMsg](jsonNode)

View File

@ -42,6 +42,14 @@ class RxJsonMsgHdlrActor(val fsApp: FreeswitchApplication) extends Actor with Ac
routeEjectUserFromVoiceConfMsg(envelope, jsonNode)
case MuteUserInVoiceConfSysMsg.NAME =>
routeMuteUserInVoiceConfMsg(envelope, jsonNode)
case DeafUserInVoiceConfSysMsg.NAME =>
routeDeafUserInVoiceConfMsg(envelope, jsonNode)
case HoldUserInVoiceConfSysMsg.NAME =>
routeHoldUserInVoiceConfMsg(envelope, jsonNode)
case PlaySoundInVoiceConfSysMsg.NAME =>
routePlaySoundInVoiceConfMsg(envelope, jsonNode)
case StopSoundInVoiceConfSysMsg.NAME =>
routeStopSoundInVoiceConfMsg(envelope, jsonNode)
case TransferUserToVoiceConfSysMsg.NAME =>
routeTransferUserToVoiceConfMsg(envelope, jsonNode)
case StartRecordingVoiceConfSysMsg.NAME =>

View File

@ -258,6 +258,46 @@ case class MeetingMutedEvtMsg(
) extends BbbCoreMsg
case class MeetingMutedEvtMsgBody(muted: Boolean, mutedBy: String)
/**
* Send to FS to deaf user in the voice conference.
*/
object DeafUserInVoiceConfSysMsg { val NAME = "DeafUserInVoiceConfSysMsg" }
case class DeafUserInVoiceConfSysMsg(
header: BbbCoreHeaderWithMeetingId,
body: DeafUserInVoiceConfSysMsgBody
) extends BbbCoreMsg
case class DeafUserInVoiceConfSysMsgBody(voiceConf: String, voiceUserId: String, deaf: Boolean)
/**
* Send to FS to hold user in the voice conference.
*/
object HoldUserInVoiceConfSysMsg { val NAME = "HoldUserInVoiceConfSysMsg" }
case class HoldUserInVoiceConfSysMsg(
header: BbbCoreHeaderWithMeetingId,
body: HoldUserInVoiceConfSysMsgBody
) extends BbbCoreMsg
case class HoldUserInVoiceConfSysMsgBody(voiceConf: String, voiceUserId: String, hold: Boolean)
/**
* Send to FS to play sound in the voice conference, or specific user
*/
object PlaySoundInVoiceConfSysMsg { val NAME = "PlaySoundInVoiceConfSysMsg" }
case class PlaySoundInVoiceConfSysMsg(
header: BbbCoreHeaderWithMeetingId,
body: PlaySoundInVoiceConfSysMsgBody
) extends BbbCoreMsg
case class PlaySoundInVoiceConfSysMsgBody(voiceConf: String, voiceUserId: String, soundPath: String)
/**
* Send to FS to stop current sound in the voice conference, or specific user
*/
object StopSoundInVoiceConfSysMsg { val NAME = "StopSoundInVoiceConfSysMsg" }
case class StopSoundInVoiceConfSysMsg(
header: BbbCoreHeaderWithMeetingId,
body: StopSoundInVoiceConfSysMsgBody
) extends BbbCoreMsg
case class StopSoundInVoiceConfSysMsgBody(voiceConf: String, voiceUserId: String)
/**
* Received from FS that voice is being recorded.
*/