diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/BigBlueButtonActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/BigBlueButtonActor.scala
index 5a6d5cc6c79647cf85fbe67c40c5c2872a596bee..44ecc08fdce31b2d8a4b169758d94327c833d63e 100755
--- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/BigBlueButtonActor.scala
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/BigBlueButtonActor.scala
@@ -121,7 +121,7 @@ class BigBlueButtonActor(
 
         val m = RunningMeeting(msg.body.props, outGW, eventBus)
 
-        /** Subscribe to meeting and voice events. **/
+        // Subscribe to meeting and voice events.
         eventBus.subscribe(m.actorRef, m.props.meetingProp.intId)
         eventBus.subscribe(m.actorRef, m.props.voiceProp.voiceConf)
         eventBus.subscribe(m.actorRef, m.props.screenshareProps.screenshareConf)
@@ -161,7 +161,7 @@ class BigBlueButtonActor(
       m <- RunningMeetings.findWithId(meetings, msg.meetingId)
       m2 <- RunningMeetings.remove(meetings, msg.meetingId)
     } yield {
-      /** Unsubscribe to meeting and voice events. **/
+      // Unsubscribe to meeting and voice events.
       eventBus.unsubscribe(m.actorRef, m.props.meetingProp.intId)
       eventBus.unsubscribe(m.actorRef, m.props.voiceProp.voiceConf)
       eventBus.unsubscribe(m.actorRef, m.props.screenshareProps.screenshareConf)
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/breakout/BreakoutHdlrHelpers.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/breakout/BreakoutHdlrHelpers.scala
index a51591fa59cf375c31e615e08a55456379fbeaa6..0d8361a8abbb2b5341020cf24bedea262167dc42 100755
--- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/breakout/BreakoutHdlrHelpers.scala
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/breakout/BreakoutHdlrHelpers.scala
@@ -8,12 +8,15 @@ import org.bigbluebutton.core.domain.{ BreakoutUser, BreakoutVoiceUser }
 import org.bigbluebutton.core.models.{ Users2x, VoiceUsers }
 import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
 
-trait BreakoutHdlrHelpers extends SystemConfiguration {
-  val liveMeeting: LiveMeeting
-  val outGW: OutMsgRouter
-  val eventBus: InternalEventBus
-
-  def sendJoinURL(userId: String, externalMeetingId: String, roomSequence: String, breakoutId: String) {
+object BreakoutHdlrHelpers extends SystemConfiguration {
+  def sendJoinURL(
+      liveMeeting:       LiveMeeting,
+      outGW:             OutMsgRouter,
+      userId:            String,
+      externalMeetingId: String,
+      roomSequence:      String,
+      breakoutId:        String
+  ) {
     for {
       user <- Users2x.findWithIntId(liveMeeting.users2x, userId)
       apiCall = "join"
@@ -28,13 +31,27 @@ trait BreakoutHdlrHelpers extends SystemConfiguration {
       redirectToHtml5JoinURL = BreakoutRoomsUtil.createJoinURL(bbbWebAPI, apiCall, redirectToHtml5BaseString,
         BreakoutRoomsUtil.calculateChecksum(apiCall, redirectToHtml5BaseString, bbbWebSharedSecret))
     } yield {
-      sendJoinURLMsg(liveMeeting.props.meetingProp.intId, breakoutId, externalMeetingId,
-        userId, redirectJoinURL, redirectToHtml5JoinURL)
+      sendJoinURLMsg(
+        outGW,
+        liveMeeting.props.meetingProp.intId,
+        breakoutId,
+        externalMeetingId,
+        userId,
+        redirectJoinURL,
+        redirectToHtml5JoinURL
+      )
     }
   }
 
-  def sendJoinURLMsg(meetingId: String, breakoutId: String, externalId: String,
-                     userId: String, redirectJoinURL: String, redirectToHtml5JoinURL: String): Unit = {
+  def sendJoinURLMsg(
+      outGW:                  OutMsgRouter,
+      meetingId:              String,
+      breakoutId:             String,
+      externalId:             String,
+      userId:                 String,
+      redirectJoinURL:        String,
+      redirectToHtml5JoinURL: String
+  ): Unit = {
     def build(meetingId: String, breakoutId: String,
               userId: String, redirectJoinURL: String, redirectToHtml5JoinURL: String): BbbCommonEnvCoreMsg = {
       val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, meetingId, userId)
@@ -52,7 +69,10 @@ trait BreakoutHdlrHelpers extends SystemConfiguration {
 
   }
 
-  def updateParentMeetingWithUsers(): Unit = {
+  def updateParentMeetingWithUsers(
+      liveMeeting: LiveMeeting,
+      eventBus:    InternalEventBus
+  ): Unit = {
 
     val users = Users2x.findAll(liveMeeting.users2x)
     val breakoutUsers = users map { u => new BreakoutUser(u.extId, u.name) }
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/breakout/BreakoutRoomCreatedMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/breakout/BreakoutRoomCreatedMsgHdlr.scala
index cccb9fa0f69c261f4775f039f317113ab4dcb893..bce40f1cf045025ee7be18d103bc9e26c462dfff 100755
--- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/breakout/BreakoutRoomCreatedMsgHdlr.scala
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/breakout/BreakoutRoomCreatedMsgHdlr.scala
@@ -4,10 +4,10 @@ import org.bigbluebutton.common2.msgs._
 import org.bigbluebutton.core.api.BreakoutRoomCreatedInternalMsg
 import org.bigbluebutton.core.apps.BreakoutModel
 import org.bigbluebutton.core.domain.{ BreakoutRoom2x, MeetingState2x }
-import org.bigbluebutton.core.running.{ BaseMeetingActor, LiveMeeting, OutMsgRouter }
+import org.bigbluebutton.core.running.{ LiveMeeting, MeetingActor, OutMsgRouter }
 
-trait BreakoutRoomCreatedMsgHdlr extends BreakoutHdlrHelpers {
-  this: BaseMeetingActor =>
+trait BreakoutRoomCreatedMsgHdlr {
+  this: MeetingActor =>
 
   val liveMeeting: LiveMeeting
   val outGW: OutMsgRouter
@@ -41,7 +41,14 @@ trait BreakoutRoomCreatedMsgHdlr extends BreakoutHdlrHelpers {
     breakoutModel.rooms.values.foreach { room =>
       log.debug("Sending invitations for room {} with num users {}", room.name, room.assignedUsers.toVector.length)
       room.assignedUsers.foreach { user =>
-        sendJoinURL(user, room.externalId, room.sequence.toString(), room.id)
+        BreakoutHdlrHelpers.sendJoinURL(
+          liveMeeting,
+          outGW,
+          user,
+          room.externalId,
+          room.sequence.toString(),
+          room.id
+        )
       }
     }
 
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/breakout/RequestBreakoutJoinURLReqMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/breakout/RequestBreakoutJoinURLReqMsgHdlr.scala
index 71e2d21a9e2bb2476ce3cdc56fee01a54f816ab2..2089416bc892c1bcbf3fdef0bd2c909dc509b4e0 100755
--- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/breakout/RequestBreakoutJoinURLReqMsgHdlr.scala
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/breakout/RequestBreakoutJoinURLReqMsgHdlr.scala
@@ -5,7 +5,7 @@ import org.bigbluebutton.core.domain.MeetingState2x
 import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter }
 import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
 
-trait RequestBreakoutJoinURLReqMsgHdlr extends BreakoutHdlrHelpers with RightsManagementTrait {
+trait RequestBreakoutJoinURLReqMsgHdlr extends RightsManagementTrait {
   this: MeetingActor =>
 
   val outGW: OutMsgRouter
@@ -20,7 +20,14 @@ trait RequestBreakoutJoinURLReqMsgHdlr extends BreakoutHdlrHelpers with RightsMa
         model <- state.breakout
         room <- model.find(msg.body.breakoutId)
       } yield {
-        sendJoinURL(msg.body.userId, room.externalId, room.sequence.toString(), room.id)
+        BreakoutHdlrHelpers.sendJoinURL(
+          liveMeeting,
+          outGW,
+          msg.body.userId,
+          room.externalId,
+          room.sequence.toString(),
+          room.id
+        )
       }
     }
 
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/breakout/SendBreakoutUsersUpdateMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/breakout/SendBreakoutUsersUpdateMsgHdlr.scala
index f97bf0d93be85158225e12ddd1764790d5e0af80..d12843d45ae5c704cbc89bc1f7d078b8e37c8e7c 100755
--- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/breakout/SendBreakoutUsersUpdateMsgHdlr.scala
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/breakout/SendBreakoutUsersUpdateMsgHdlr.scala
@@ -10,6 +10,9 @@ trait SendBreakoutUsersUpdateMsgHdlr {
 
   def handleSendBreakoutUsersUpdateInternalMsg(msg: SendBreakoutUsersAuditInternalMsg): Unit = {
 
-    updateParentMeetingWithUsers()
+    BreakoutHdlrHelpers.updateParentMeetingWithUsers(
+      liveMeeting,
+      eventBus
+    )
   }
 }
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/UserJoinMeetingAfterReconnectReqMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/UserJoinMeetingAfterReconnectReqMsgHdlr.scala
index 9f2ae24579d7ef1a67b2ce2822f56e03bd35d946..88a06ea09e13495b0cb9ec1845137dc6c69c659a 100755
--- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/UserJoinMeetingAfterReconnectReqMsgHdlr.scala
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/UserJoinMeetingAfterReconnectReqMsgHdlr.scala
@@ -5,10 +5,10 @@ import org.bigbluebutton.core.apps.breakout.BreakoutHdlrHelpers
 import org.bigbluebutton.core.apps.voice.UserJoinedVoiceConfEvtMsgHdlr
 import org.bigbluebutton.core.domain.MeetingState2x
 import org.bigbluebutton.core.models.Users2x
-import org.bigbluebutton.core.running.{ BaseMeetingActor, HandlerHelpers, LiveMeeting, OutMsgRouter }
+import org.bigbluebutton.core.running.{ HandlerHelpers, LiveMeeting, MeetingActor, OutMsgRouter }
 
-trait UserJoinMeetingAfterReconnectReqMsgHdlr extends HandlerHelpers with BreakoutHdlrHelpers with UserJoinedVoiceConfEvtMsgHdlr {
-  this: BaseMeetingActor =>
+trait UserJoinMeetingAfterReconnectReqMsgHdlr extends HandlerHelpers with UserJoinedVoiceConfEvtMsgHdlr {
+  this: MeetingActor =>
 
   val liveMeeting: LiveMeeting
   val outGW: OutMsgRouter
@@ -27,7 +27,7 @@ trait UserJoinMeetingAfterReconnectReqMsgHdlr extends HandlerHelpers with Breako
       case None =>
         val newState = userJoinMeeting(outGW, msg.body.authToken, msg.body.clientType, liveMeeting, state)
         if (liveMeeting.props.meetingProp.isBreakout) {
-          updateParentMeetingWithUsers()
+          BreakoutHdlrHelpers.updateParentMeetingWithUsers(liveMeeting, eventBus)
         }
         newState
     }
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/UserJoinMeetingReqMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/UserJoinMeetingReqMsgHdlr.scala
index 9aee3e87c41d96680709d9a4721f5bb567e78ecd..cd8011ff15b45366cc33fc75c14fdaab39332061 100755
--- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/UserJoinMeetingReqMsgHdlr.scala
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/UserJoinMeetingReqMsgHdlr.scala
@@ -4,10 +4,10 @@ import org.bigbluebutton.common2.msgs.UserJoinMeetingReqMsg
 import org.bigbluebutton.core.apps.breakout.BreakoutHdlrHelpers
 import org.bigbluebutton.core.models.{ Users2x, VoiceUsers }
 import org.bigbluebutton.core.domain.MeetingState2x
-import org.bigbluebutton.core.running.{ BaseMeetingActor, HandlerHelpers, LiveMeeting, OutMsgRouter }
+import org.bigbluebutton.core.running.{ HandlerHelpers, LiveMeeting, MeetingActor, OutMsgRouter }
 
-trait UserJoinMeetingReqMsgHdlr extends HandlerHelpers with BreakoutHdlrHelpers {
-  this: BaseMeetingActor =>
+trait UserJoinMeetingReqMsgHdlr extends HandlerHelpers {
+  this: MeetingActor =>
 
   val liveMeeting: LiveMeeting
   val outGW: OutMsgRouter
@@ -27,7 +27,7 @@ trait UserJoinMeetingReqMsgHdlr extends HandlerHelpers with BreakoutHdlrHelpers
         val newState = userJoinMeeting(outGW, msg.body.authToken, msg.body.clientType, liveMeeting, state)
 
         if (liveMeeting.props.meetingProp.isBreakout) {
-          updateParentMeetingWithUsers()
+          BreakoutHdlrHelpers.updateParentMeetingWithUsers(liveMeeting, eventBus)
         }
 
         // fresh user joined (not due to reconnection). Clear (pop) the cached voice user
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/UserJoinedVoiceConfEvtMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/UserJoinedVoiceConfEvtMsgHdlr.scala
index 4e73d2b693b8b59e6a2506115b09fa1626e8b1c5..ac38722dd642859065d440c58d6be7547b225262 100755
--- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/UserJoinedVoiceConfEvtMsgHdlr.scala
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/UserJoinedVoiceConfEvtMsgHdlr.scala
@@ -2,14 +2,10 @@ package org.bigbluebutton.core.apps.voice
 
 import org.bigbluebutton.SystemConfiguration
 import org.bigbluebutton.common2.msgs._
-import org.bigbluebutton.core.apps.breakout.BreakoutHdlrHelpers
-import org.bigbluebutton.core.models.{ VoiceUserState, VoiceUsers }
-import org.bigbluebutton.core.running.{ BaseMeetingActor, LiveMeeting, OutMsgRouter }
-import org.bigbluebutton.core2.MeetingStatus2x
-import org.bigbluebutton.core2.message.senders.MsgBuilder
+import org.bigbluebutton.core.running.{ LiveMeeting, MeetingActor, OutMsgRouter }
 
-trait UserJoinedVoiceConfEvtMsgHdlr extends BreakoutHdlrHelpers with SystemConfiguration {
-  this: BaseMeetingActor =>
+trait UserJoinedVoiceConfEvtMsgHdlr extends SystemConfiguration {
+  this: MeetingActor =>
 
   val liveMeeting: LiveMeeting
   val outGW: OutMsgRouter
@@ -17,7 +13,10 @@ trait UserJoinedVoiceConfEvtMsgHdlr extends BreakoutHdlrHelpers with SystemConfi
   def handleUserJoinedVoiceConfEvtMsg(msg: UserJoinedVoiceConfEvtMsg): Unit = {
     log.info("Received user joined voice conference " + msg)
 
-    handleUserJoinedVoiceConfEvtMsg(
+    VoiceApp.handleUserJoinedVoiceConfEvtMsg(
+      liveMeeting,
+      outGW,
+      eventBus,
       msg.body.voiceConf,
       msg.body.intId,
       msg.body.voiceUserId,
@@ -29,91 +28,4 @@ trait UserJoinedVoiceConfEvtMsgHdlr extends BreakoutHdlrHelpers with SystemConfi
       "freeswitch"
     )
   }
-
-  def handleUserJoinedVoiceConfEvtMsg(
-      voiceConf:    String,
-      intId:        String,
-      voiceUserId:  String,
-      callingWith:  String,
-      callerIdName: String,
-      callerIdNum:  String,
-      muted:        Boolean,
-      talking:      Boolean,
-      callingInto:  String
-  ): Unit = {
-
-    def broadcastEvent(voiceUserState: VoiceUserState): Unit = {
-      val routing = Routing.addMsgToClientRouting(
-        MessageTypes.BROADCAST_TO_MEETING,
-        liveMeeting.props.meetingProp.intId,
-        voiceUserState.intId
-      )
-      val envelope = BbbCoreEnvelope(
-        UserJoinedVoiceConfToClientEvtMsg.NAME,
-        routing
-      )
-      val header = BbbClientMsgHeader(
-        UserJoinedVoiceConfToClientEvtMsg.NAME,
-        liveMeeting.props.meetingProp.intId,
-        voiceUserState.intId
-      )
-
-      val body = UserJoinedVoiceConfToClientEvtMsgBody(
-        voiceConf,
-        voiceUserState.intId,
-        voiceUserState.voiceUserId,
-        voiceUserState.callerName,
-        voiceUserState.callerNum,
-        voiceUserState.muted,
-        voiceUserState.talking,
-        voiceUserState.callingWith,
-        voiceUserState.listenOnly
-      )
-
-      val event = UserJoinedVoiceConfToClientEvtMsg(
-        header,
-        body
-      )
-      val msgEvent = BbbCommonEnvCoreMsg(
-        envelope,
-        event
-      )
-      outGW.send(msgEvent)
-    }
-
-    val isListenOnly = if (callerIdName.startsWith("LISTENONLY")) true else false
-
-    val voiceUserState = VoiceUserState(
-      intId,
-      voiceUserId,
-      callingWith,
-      callerIdName,
-      callerIdNum,
-      muted,
-      talking,
-      listenOnly = isListenOnly,
-      callingInto,
-      System.currentTimeMillis()
-    )
-    VoiceUsers.add(liveMeeting.voiceUsers, voiceUserState)
-
-    broadcastEvent(voiceUserState)
-
-    if (liveMeeting.props.meetingProp.isBreakout) {
-      updateParentMeetingWithUsers()
-    }
-
-    // if the meeting is muted tell freeswitch to mute the new person
-    if (!isListenOnly
-      && MeetingStatus2x.isMeetingMuted(liveMeeting.status)) {
-      val event = MsgBuilder.buildMuteUserInVoiceConfSysMsg(
-        liveMeeting.props.meetingProp.intId,
-        voiceConf,
-        voiceUserId,
-        true
-      )
-      outGW.send(event)
-    }
-  }
-
 }
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/UserLeftVoiceConfEvtMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/UserLeftVoiceConfEvtMsgHdlr.scala
index 209b01acd5c326ab06975ba16e49a4eb1832a3e2..c9eec16579c01f9ff94def46e455f93befdc039c 100755
--- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/UserLeftVoiceConfEvtMsgHdlr.scala
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/UserLeftVoiceConfEvtMsgHdlr.scala
@@ -3,10 +3,10 @@ package org.bigbluebutton.core.apps.voice
 import org.bigbluebutton.common2.msgs._
 import org.bigbluebutton.core.apps.breakout.BreakoutHdlrHelpers
 import org.bigbluebutton.core.models.{ VoiceUserState, VoiceUsers }
-import org.bigbluebutton.core.running.{ BaseMeetingActor, LiveMeeting, OutMsgRouter }
+import org.bigbluebutton.core.running.{ LiveMeeting, MeetingActor, OutMsgRouter }
 
-trait UserLeftVoiceConfEvtMsgHdlr extends BreakoutHdlrHelpers {
-  this: BaseMeetingActor =>
+trait UserLeftVoiceConfEvtMsgHdlr {
+  this: MeetingActor =>
 
   val liveMeeting: LiveMeeting
   val outGW: OutMsgRouter
@@ -34,7 +34,7 @@ trait UserLeftVoiceConfEvtMsgHdlr extends BreakoutHdlrHelpers {
     }
 
     if (liveMeeting.props.meetingProp.isBreakout) {
-      updateParentMeetingWithUsers()
+      BreakoutHdlrHelpers.updateParentMeetingWithUsers(liveMeeting, eventBus)
     }
   }
 }
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/UserStatusVoiceConfEvtMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/UserStatusVoiceConfEvtMsgHdlr.scala
index a49e8ba66145f4477f662a8ed03e964bd7f73cd3..ccd3ca137f862f8c81c592a91cae4805cb607e8d 100755
--- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/UserStatusVoiceConfEvtMsgHdlr.scala
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/voice/UserStatusVoiceConfEvtMsgHdlr.scala
@@ -1,22 +1,22 @@
 package org.bigbluebutton.core.apps.voice
 
 import org.bigbluebutton.common2.msgs.UserStatusVoiceConfEvtMsg
-import org.bigbluebutton.core.running.{ BaseMeetingActor, LiveMeeting, OutMsgRouter }
+import org.bigbluebutton.core.running.{ LiveMeeting, MeetingActor, OutMsgRouter }
 
 trait UserStatusVoiceConfEvtMsgHdlr {
-  this: BaseMeetingActor =>
+  this: MeetingActor =>
 
   val liveMeeting: LiveMeeting
   val outGW: OutMsgRouter
 
   def handleUserStatusVoiceConfEvtMsg(msg: UserStatusVoiceConfEvtMsg): Unit = {
-    println("************* RECEIVED UserStatusVoiceConfEvtMsg *************")
-    msg.body.confUsers foreach { cm =>
-      println("user " + cm.callerIdName)
-    }
 
-    msg.body.confRecordings foreach { cr =>
-      println("rec = " + cr.recordPath)
-    }
+    VoiceApp.processUserStatusVoiceConfEvtMsg(
+      liveMeeting,
+      outGW,
+      eventBus,
+      msg.body.confUsers
+    )
+    
   }
 }
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 b26bc6765b57e6a50f09153f016468ab58d37a8b..b5b1dc9e360f698a14ecabddaf987434dc814397 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
@@ -1,5 +1,9 @@
 package org.bigbluebutton.core.apps.voice
 
+import org.bigbluebutton.common2.msgs.{ BbbClientMsgHeader, BbbCommonEnvCoreMsg, BbbCoreEnvelope, ConfVoiceUser, MessageTypes, Routing, UserJoinedVoiceConfToClientEvtMsg, UserJoinedVoiceConfToClientEvtMsgBody, UserLeftVoiceConfToClientEvtMsg, UserLeftVoiceConfToClientEvtMsgBody, UserMutedVoiceEvtMsg, UserMutedVoiceEvtMsgBody }
+import org.bigbluebutton.core.apps.breakout.BreakoutHdlrHelpers
+import org.bigbluebutton.core.bus.InternalEventBus
+import org.bigbluebutton.core.models.{ VoiceUserState, VoiceUsers }
 import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
 import org.bigbluebutton.core2.MeetingStatus2x
 import org.bigbluebutton.core2.message.senders.MsgBuilder
@@ -35,6 +39,246 @@ object VoiceApp {
       )
       outGW.send(event)
     }
+  }
+
+  def broadcastUserMutedVoiceEvtMsg(
+      meetingId: String,
+      vu:        VoiceUserState,
+      voiceConf: String,
+      outGW:     OutMsgRouter
+  ): Unit = {
+    val routing = Routing.addMsgToClientRouting(
+      MessageTypes.BROADCAST_TO_MEETING,
+      meetingId,
+      vu.intId
+    )
+    val envelope = BbbCoreEnvelope(UserMutedVoiceEvtMsg.NAME, routing)
+    val header = BbbClientMsgHeader(
+      UserMutedVoiceEvtMsg.NAME,
+      meetingId,
+      vu.intId
+    )
+
+    val body = UserMutedVoiceEvtMsgBody(
+      voiceConf = voiceConf,
+      intId = vu.intId,
+      voiceUserId = vu.intId,
+      vu.muted
+    )
+
+    val event = UserMutedVoiceEvtMsg(header, body)
+    val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
+    outGW.send(msgEvent)
+  }
+
+  def handleUserMutedInVoiceConfEvtMsg(
+      liveMeeting: LiveMeeting,
+      outGW:       OutMsgRouter,
+      voiceUserId: String,
+      muted:       Boolean
+  ): Unit = {
+    for {
+      mutedUser <- VoiceUsers.userMuted(liveMeeting.voiceUsers, voiceUserId, muted)
+    } yield {
+      broadcastUserMutedVoiceEvtMsg(
+        liveMeeting.props.meetingProp.intId,
+        mutedUser,
+        liveMeeting.props.voiceProp.voiceConf,
+        outGW
+      )
+    }
+  }
+
+  def processUserStatusVoiceConfEvtMsg(
+      liveMeeting: LiveMeeting,
+      outGW:       OutMsgRouter,
+      eventBus:    InternalEventBus,
+      users:       Vector[ConfVoiceUser]
+  ): Unit = {
+    users foreach { u =>
+      VoiceUsers.findWithVoiceUserId(
+        liveMeeting.voiceUsers,
+        u.voiceUserId
+      ) match {
+          case Some(vu) =>
+            if (vu.muted != u.muted) {
+              handleUserMutedInVoiceConfEvtMsg(
+                liveMeeting,
+                outGW,
+                u.voiceUserId,
+                u.muted
+              )
+            }
 
+            // Update the user status to indicate they are stll in the voice conference.
+            VoiceUsers.setLastStatusUpdate(liveMeeting.voiceUsers, vu)
+          case None =>
+            handleUserJoinedVoiceConfEvtMsg(
+              liveMeeting,
+              outGW,
+              eventBus,
+              liveMeeting.props.voiceProp.voiceConf,
+              u.intId,
+              u.voiceUserId,
+              u.callingWith,
+              u.callerIdName,
+              u.callerIdNum,
+              u.muted,
+              u.talking,
+              u.calledInto
+            )
+
+            // Update this new users status time.
+            for {
+              vu <- VoiceUsers.findWithVoiceUserId(liveMeeting.voiceUsers, u.voiceUserId)
+            } yield {
+              VoiceUsers.setLastStatusUpdate(liveMeeting.voiceUsers, vu)
+            }
+        }
+    }
+
+    // Remove users that hasn't been updated for the last two minutes as
+    // these users might have already left.
+    val twoMinutes = 2 * 60 * 1000
+    val now = System.currentTimeMillis()
+    VoiceUsers.findAllFreeswitchCallers(liveMeeting.voiceUsers) foreach { fsu =>
+      if (now - fsu.lastStatusUpdateOn > twoMinutes) {
+        handleUserLeftVoiceConfEvtMsg(
+          liveMeeting,
+          outGW,
+          eventBus,
+          liveMeeting.props.voiceProp.voiceConf,
+          fsu.voiceUserId
+        )
+      }
+    }
+  }
+
+  def handleUserJoinedVoiceConfEvtMsg(
+      liveMeeting:  LiveMeeting,
+      outGW:        OutMsgRouter,
+      eventBus:     InternalEventBus,
+      voiceConf:    String,
+      intId:        String,
+      voiceUserId:  String,
+      callingWith:  String,
+      callerIdName: String,
+      callerIdNum:  String,
+      muted:        Boolean,
+      talking:      Boolean,
+      callingInto:  String
+  ): Unit = {
+
+    def broadcastEvent(voiceUserState: VoiceUserState): Unit = {
+      val routing = Routing.addMsgToClientRouting(
+        MessageTypes.BROADCAST_TO_MEETING,
+        liveMeeting.props.meetingProp.intId,
+        voiceUserState.intId
+      )
+      val envelope = BbbCoreEnvelope(
+        UserJoinedVoiceConfToClientEvtMsg.NAME,
+        routing
+      )
+      val header = BbbClientMsgHeader(
+        UserJoinedVoiceConfToClientEvtMsg.NAME,
+        liveMeeting.props.meetingProp.intId,
+        voiceUserState.intId
+      )
+
+      val body = UserJoinedVoiceConfToClientEvtMsgBody(
+        voiceConf,
+        voiceUserState.intId,
+        voiceUserState.voiceUserId,
+        voiceUserState.callerName,
+        voiceUserState.callerNum,
+        voiceUserState.muted,
+        voiceUserState.talking,
+        voiceUserState.callingWith,
+        voiceUserState.listenOnly
+      )
+
+      val event = UserJoinedVoiceConfToClientEvtMsg(
+        header,
+        body
+      )
+      val msgEvent = BbbCommonEnvCoreMsg(
+        envelope,
+        event
+      )
+      outGW.send(msgEvent)
+    }
+
+    val isListenOnly = if (callerIdName.startsWith("LISTENONLY")) true else false
+
+    val voiceUserState = VoiceUserState(
+      intId,
+      voiceUserId,
+      callingWith,
+      callerIdName,
+      callerIdNum,
+      muted,
+      talking,
+      listenOnly = isListenOnly,
+      callingInto,
+      System.currentTimeMillis()
+    )
+    VoiceUsers.add(liveMeeting.voiceUsers, voiceUserState)
+
+    broadcastEvent(voiceUserState)
+
+    if (liveMeeting.props.meetingProp.isBreakout) {
+      BreakoutHdlrHelpers.updateParentMeetingWithUsers(
+        liveMeeting,
+        eventBus
+      )
+    }
+
+    // if the meeting is muted tell freeswitch to mute the new person
+    if (!isListenOnly
+      && MeetingStatus2x.isMeetingMuted(liveMeeting.status)) {
+      val event = MsgBuilder.buildMuteUserInVoiceConfSysMsg(
+        liveMeeting.props.meetingProp.intId,
+        voiceConf,
+        voiceUserId,
+        true
+      )
+      outGW.send(event)
+    }
+  }
+
+  def handleUserLeftVoiceConfEvtMsg(
+      liveMeeting: LiveMeeting,
+      outGW:       OutMsgRouter,
+      eventBus:    InternalEventBus,
+      voiceConf:   String,
+      voiceUserId: String
+  ): Unit = {
+
+    def broadcastEvent(vu: VoiceUserState): Unit = {
+      val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId,
+        vu.intId)
+      val envelope = BbbCoreEnvelope(UserLeftVoiceConfToClientEvtMsg.NAME, routing)
+      val header = BbbClientMsgHeader(UserLeftVoiceConfToClientEvtMsg.NAME, liveMeeting.props.meetingProp.intId, vu.intId)
+
+      val body = UserLeftVoiceConfToClientEvtMsgBody(voiceConf = voiceConf, intId = vu.intId, voiceUserId = vu.intId)
+
+      val event = UserLeftVoiceConfToClientEvtMsg(header, body)
+      val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
+      outGW.send(msgEvent)
+    }
+
+    for {
+      user <- VoiceUsers.findWithVoiceUserId(liveMeeting.voiceUsers, voiceUserId)
+    } yield {
+      VoiceUsers.removeWithIntId(liveMeeting.voiceUsers, user.intId)
+      broadcastEvent(user)
+    }
+
+    if (liveMeeting.props.meetingProp.isBreakout) {
+      BreakoutHdlrHelpers.updateParentMeetingWithUsers(
+        liveMeeting,
+        eventBus
+      )
+    }
   }
 }
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/RegisteredUsers.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/RegisteredUsers.scala
index df9efbbdf6db0a769d6e8c9ecaeb993208dea832..c009167680c03bc19a158c44e716d8fa730ff579 100755
--- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/RegisteredUsers.scala
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/models/RegisteredUsers.scala
@@ -1,6 +1,5 @@
 package org.bigbluebutton.core.models
 
-import org.bigbluebutton.common2.domain.UserVO
 import com.softwaremill.quicklens._
 
 object RegisteredUsers {
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 f4041b0ac706eba9342309dc7b8238282bf6f47a..2055586d05b06d88c4901032e383853724172b56 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
@@ -14,6 +14,8 @@ object VoiceUsers {
   def findAll(users: VoiceUsers): Vector[VoiceUserState] = users.toVector
 
   def findAllNonListenOnlyVoiceUsers(users: VoiceUsers): Vector[VoiceUserState] = users.toVector.filter(u => u.listenOnly == false)
+  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 add(users: VoiceUsers, user: VoiceUserState): Unit = {
     users.save(user)
@@ -74,6 +76,12 @@ object VoiceUsers {
       vu
     }
   }
+
+  def setLastStatusUpdate(users: VoiceUsers, user: VoiceUserState): VoiceUserState = {
+    val vu = user.copy(lastStatusUpdateOn = System.currentTimeMillis())
+    users.save(vu)
+    vu
+  }
 }
 
 class VoiceUsers {
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala
index 37314f21fe887fb544920bc2b0093687d375cf82..7ab5efb756aefdfebf709eb9131a11a11560fa6c 100755
--- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala
@@ -670,7 +670,7 @@ class MeetingActor(
     stopRecordingIfAutoStart2x(outGW, liveMeeting, state)
 
     if (liveMeeting.props.meetingProp.isBreakout) {
-      updateParentMeetingWithUsers()
+      BreakoutHdlrHelpers.updateParentMeetingWithUsers(liveMeeting, eventBus)
     }
 
     if (state.expiryTracker.userHasJoined &&
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/AkkaAppsRedisSubscriberActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/AkkaAppsRedisSubscriberActor.scala
index 8a8c296bac5ba2153bbf38b2e26b01b49c6beee3..9b3eb27333ad29ba032ebdadb20940a6fec3ee0d 100755
--- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/AkkaAppsRedisSubscriberActor.scala
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/AkkaAppsRedisSubscriberActor.scala
@@ -1,7 +1,7 @@
 package org.bigbluebutton.endpoint.redis
 
 import org.bigbluebutton.common2.bus.IncomingJsonMessageBus
-import org.bigbluebutton.common2.redis.{ RedisConfig, RedisSubscriber, RedisSubscriberProvider }
+import org.bigbluebutton.common2.redis.{ RedisConfig, RedisSubscriberProvider }
 import akka.actor.ActorSystem
 import akka.actor.Props
 
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/RedisRecorderActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/RedisRecorderActor.scala
index 936499d4759b49d6edfde4f55fd235d1d35bd8e0..3aee1d95028a9d06a5b26b1a70d17fa0da6a3e8d 100755
--- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/RedisRecorderActor.scala
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/RedisRecorderActor.scala
@@ -2,7 +2,6 @@ package org.bigbluebutton.endpoint.redis
 
 import scala.collection.immutable.StringOps
 import scala.collection.JavaConverters._
-import org.bigbluebutton.SystemConfiguration
 import org.bigbluebutton.common2.msgs._
 import org.bigbluebutton.common2.redis.{ RedisConfig, RedisStorageProvider }
 import org.bigbluebutton.core.apps.groupchats.GroupChatApp