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
new file mode 100644
index 0000000000000000000000000000000000000000..43015c26f5f85aa94aa2652f2adca50806717d3a
--- /dev/null
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/users/UserJoinMeetingAfterReconnectReqMsgHdlr.scala
@@ -0,0 +1,34 @@
+package org.bigbluebutton.core.apps.users
+
+import org.bigbluebutton.common2.msgs.UserJoinMeetingAfterReconnectReqMsg
+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.VoiceUsers
+import org.bigbluebutton.core.running.{ BaseMeetingActor, HandlerHelpers, LiveMeeting, OutMsgRouter }
+
+trait UserJoinMeetingAfterReconnectReqMsgHdlr extends HandlerHelpers with BreakoutHdlrHelpers with UserJoinedVoiceConfEvtMsgHdlr {
+  this: BaseMeetingActor =>
+
+  val liveMeeting: LiveMeeting
+  val outGW: OutMsgRouter
+
+  def handleUserJoinMeetingAfterReconnectReqMsg(msg: UserJoinMeetingAfterReconnectReqMsg, state: MeetingState2x): MeetingState2x = {
+    val newState = userJoinMeeting(outGW, msg.body.authToken, liveMeeting, state)
+
+    if (liveMeeting.props.meetingProp.isBreakout) {
+      updateParentMeetingWithUsers()
+    }
+
+    // recover voice user
+    for {
+      vu <- VoiceUsers.recoverVoiceUser(liveMeeting.voiceUsers, msg.body.userId)
+    } yield {
+      handleUserJoinedVoiceConfEvtMsg(liveMeeting.props.voiceProp.voiceConf, vu.intId, vu.voiceUserId, vu.callingWith, vu.callerName, vu.callerNum, vu.muted, vu.talking)
+    }
+
+    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 28854e5a74e96dc53e3dea5d54a85d57d999eca5..f2b451b3dc3471871554d6d0598ce24bcfdf1ca1 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
@@ -2,6 +2,7 @@ package org.bigbluebutton.core.apps.users
 
 import org.bigbluebutton.common2.msgs.UserJoinMeetingReqMsg
 import org.bigbluebutton.core.apps.breakout.BreakoutHdlrHelpers
+import org.bigbluebutton.core.models.VoiceUsers
 import org.bigbluebutton.core.domain.MeetingState2x
 import org.bigbluebutton.core.running.{ BaseMeetingActor, HandlerHelpers, LiveMeeting, OutMsgRouter }
 
@@ -18,6 +19,9 @@ trait UserJoinMeetingReqMsgHdlr extends HandlerHelpers with BreakoutHdlrHelpers
       updateParentMeetingWithUsers()
     }
 
+    // fresh user joined (not due to reconnection). Clear (pop) the cached voice user
+    VoiceUsers.recoverVoiceUser(liveMeeting.voiceUsers, msg.body.userId)
+
     newState
   }
 
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 53b3988cf139d9a50637fcc2bbf9236d2d2dd31a..282999b16d1040aeb33beadff7fc2b2f6faf829a 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
@@ -25,6 +25,10 @@ object VoiceUsers {
     users.toVector.find(u => u.intId == intId)
   }
 
+  def recoverVoiceUser(users: VoiceUsers, intId: String): Option[VoiceUserState] = {
+    users.removeFromCache(intId)
+  }
+
   def userMuted(users: VoiceUsers, voiceUserId: String, muted: Boolean): Option[VoiceUserState] = {
     for {
       u <- findWithVoiceUserId(users, voiceUserId)
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/pubsub/senders/ReceivedJsonMsgHandlerActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/pubsub/senders/ReceivedJsonMsgHandlerActor.scala
index b7c76f69835e10e3a97b018c03458fd3de5fdae7..e986aa1f57b3d77624018c66229099234d02ef03 100755
--- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/pubsub/senders/ReceivedJsonMsgHandlerActor.scala
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/pubsub/senders/ReceivedJsonMsgHandlerActor.scala
@@ -62,6 +62,8 @@ class ReceivedJsonMsgHandlerActor(
         route[RegisterUserReqMsg](meetingManagerChannel, envelope, jsonNode)
       case UserJoinMeetingReqMsg.NAME =>
         routeGenericMsg[UserJoinMeetingReqMsg](envelope, jsonNode)
+      case UserJoinMeetingAfterReconnectReqMsg.NAME =>
+        routeGenericMsg[UserJoinMeetingAfterReconnectReqMsg](envelope, jsonNode)
       case GetAllMeetingsReqMsg.NAME =>
         route[GetAllMeetingsReqMsg](meetingManagerChannel, envelope, jsonNode)
       case DestroyMeetingSysCmdMsg.NAME =>
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 bff889698bc5e07f8c5b2742df513036e2865b9b..21714a66cbf3ee781f2686a0f87e4de1964179d1 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
@@ -63,6 +63,7 @@ class MeetingActor(
     with PermisssionCheck
     with UserBroadcastCamStartMsgHdlr
     with UserJoinMeetingReqMsgHdlr
+    with UserJoinMeetingAfterReconnectReqMsgHdlr
     with UserBroadcastCamStopMsgHdlr
     with UserConnectedToGlobalAudioMsgHdlr
     with UserDisconnectedFromGlobalAudioMsgHdlr
@@ -180,6 +181,8 @@ class MeetingActor(
         state = usersApp.handleValidateAuthTokenReqMsg(m, state)
       case m: UserJoinMeetingReqMsg =>
         state = handleUserJoinMeetingReqMsg(m, state)
+      case m: UserJoinMeetingAfterReconnectReqMsg =>
+        state = handleUserJoinMeetingAfterReconnectReqMsg(m, state)
       case m: UserLeaveReqMsg =>
         state = handleUserLeaveReqMsg(m, state)
       case m: UserBroadcastCamStartMsg  => handleUserBroadcastCamStartMsg(m)
diff --git a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/UsersMgs.scala b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/UsersMgs.scala
index 7734cd1e7f47309e483483c7ddd1b99553c08807..6dbddaa6301225990f86ab5ce6480a6c29a13d00 100755
--- a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/UsersMgs.scala
+++ b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/UsersMgs.scala
@@ -238,6 +238,14 @@ object UserJoinMeetingReqMsg { val NAME = "UserJoinMeetingReqMsg" }
 case class UserJoinMeetingReqMsg(header: BbbClientMsgHeader, body: UserJoinMeetingReqMsgBody) extends StandardMsg
 case class UserJoinMeetingReqMsgBody(userId: String, authToken: String)
 
+/**
+  * Sent from Flash client to rejoin meeting after reconnection
+  */
+object UserJoinMeetingAfterReconnectReqMsg { val NAME = "UserJoinMeetingAfterReconnectReqMsg" }
+case class UserJoinMeetingAfterReconnectReqMsg(header: BbbClientMsgHeader, body: UserJoinMeetingAfterReconnectReqMsgBody) extends StandardMsg
+case class UserJoinMeetingAfterReconnectReqMsgBody(userId: String, authToken: String)
+
+
 object UserLeaveReqMsg { val NAME = "UserLeaveReqMsg" }
 case class UserLeaveReqMsg(header: BbbClientMsgHeader, body: UserLeaveReqMsgBody) extends StandardMsg
 case class UserLeaveReqMsgBody(userId: String, sessionId: String)
diff --git a/bigbluebutton-client/src/org/bigbluebutton/core/events/TokenValidReconnectEvent.as b/bigbluebutton-client/src/org/bigbluebutton/core/events/TokenValidReconnectEvent.as
new file mode 100644
index 0000000000000000000000000000000000000000..3ff05da640dc68766fd18c288521cc5d49cb2381
--- /dev/null
+++ b/bigbluebutton-client/src/org/bigbluebutton/core/events/TokenValidReconnectEvent.as
@@ -0,0 +1,16 @@
+package org.bigbluebutton.core.events
+{
+import flash.events.Event;
+
+public class TokenValidReconnectEvent extends Event
+{
+    public static const TOKEN_VALID_RECONNECT_EVENT:String = "auth token valid reconnect event";
+
+    public function TokenValidReconnectEvent()
+    {
+        super(TOKEN_VALID_RECONNECT_EVENT, true, false);
+    }
+
+}
+
+}
diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/model/users/NetConnectionDelegate.as b/bigbluebutton-client/src/org/bigbluebutton/main/model/users/NetConnectionDelegate.as
index a25939d158262660efe9e81d95055ca1930a93d2..f28129db21ec5e01eaf4df0855e01eee7f5e30f5 100755
--- a/bigbluebutton-client/src/org/bigbluebutton/main/model/users/NetConnectionDelegate.as
+++ b/bigbluebutton-client/src/org/bigbluebutton/main/model/users/NetConnectionDelegate.as
@@ -38,6 +38,7 @@ package org.bigbluebutton.main.model.users
 	import org.bigbluebutton.core.connection.messages.ValidateAuthTokenReqMsg;
 	import org.bigbluebutton.core.connection.messages.ValidateAuthTokenReqMsgBody;
 	import org.bigbluebutton.core.events.TokenValidEvent;
+	import org.bigbluebutton.core.events.TokenValidReconnectEvent;
 	import org.bigbluebutton.core.managers.ReconnectionManager;
 	import org.bigbluebutton.core.model.LiveMeeting;
 	import org.bigbluebutton.core.services.BandwidthMonitor;
@@ -137,15 +138,19 @@ package org.bigbluebutton.main.model.users
             LOGGER.info(JSON.stringify(logData));
             
             if (tokenValid) {
-                LiveMeeting.inst().me.authTokenValid = true;
-                if (waitForApproval) {
-                  var waitCommand:BBBEvent = new BBBEvent(BBBEvent.WAITING_FOR_MODERATOR_ACCEPTANCE);
-                  dispatcher.dispatchEvent(waitCommand);
+              LiveMeeting.inst().me.authTokenValid = true;
+              if (waitForApproval) {
+                var waitCommand:BBBEvent = new BBBEvent(BBBEvent.WAITING_FOR_MODERATOR_ACCEPTANCE);
+                dispatcher.dispatchEvent(waitCommand);
+              } else {
+                LiveMeeting.inst().me.waitingForApproval = false;
+                if (reconnecting) {
+                  dispatcher.dispatchEvent(new TokenValidReconnectEvent());
                 } else {
-                  LiveMeeting.inst().me.waitingForApproval = false;
                   dispatcher.dispatchEvent(new TokenValidEvent());
-                  sendConnectionSuccessEvent(userId);
                 }
+                sendConnectionSuccessEvent(userId);
+              }
             } else {
                 dispatcher.dispatchEvent(new InvalidAuthTokenEvent());
             }
diff --git a/bigbluebutton-client/src/org/bigbluebutton/main/model/users/UserService.as b/bigbluebutton-client/src/org/bigbluebutton/main/model/users/UserService.as
index d4662156bb81be7c8ebb315665215bac979f87e1..040da18c3cc35c5213fed69721a2472cc7621f9c 100755
--- a/bigbluebutton-client/src/org/bigbluebutton/main/model/users/UserService.as
+++ b/bigbluebutton-client/src/org/bigbluebutton/main/model/users/UserService.as
@@ -29,6 +29,7 @@ package org.bigbluebutton.main.model.users
 	import org.bigbluebutton.core.UsersUtil;
 	import org.bigbluebutton.core.events.LockControlEvent;
 	import org.bigbluebutton.core.events.TokenValidEvent;
+	import org.bigbluebutton.core.events.TokenValidReconnectEvent;
 	import org.bigbluebutton.core.events.VoiceConfEvent;
 	import org.bigbluebutton.core.managers.ConnectionManager;
 	import org.bigbluebutton.core.model.LiveMeeting;
@@ -148,7 +149,11 @@ package org.bigbluebutton.main.model.users
     public function tokenValidEventHandler(event: TokenValidEvent): void {
       sender.joinMeeting();
     }
-    
+
+    public function tokenValidReconnectEventHandler(event: TokenValidReconnectEvent): void {
+      sender.joinMeetingAfterReconnect();
+    }
+
 	public function logoutEndMeeting():void{
 		if (this.isModerator()) {
 			var myUserId: String = UsersUtil.getMyUserID();
@@ -187,6 +192,7 @@ package org.bigbluebutton.main.model.users
 				reconnecting = false;
 			} else {
         onAllowedToJoin();
+
       }
 		}
 
diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/users/maps/UsersMainEventMap.mxml b/bigbluebutton-client/src/org/bigbluebutton/modules/users/maps/UsersMainEventMap.mxml
index 2a67382431576c3e78d11a26678054975b9037b5..610e50e8a3619e71b97bcd2cd730faa981dc10f2 100755
--- a/bigbluebutton-client/src/org/bigbluebutton/modules/users/maps/UsersMainEventMap.mxml
+++ b/bigbluebutton-client/src/org/bigbluebutton/modules/users/maps/UsersMainEventMap.mxml
@@ -27,6 +27,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
       
       import org.bigbluebutton.core.events.LockControlEvent;
       import org.bigbluebutton.core.events.TokenValidEvent;
+      import org.bigbluebutton.core.events.TokenValidReconnectEvent;
       import org.bigbluebutton.core.events.VoiceConfEvent;
       import org.bigbluebutton.main.events.BBBEvent;
       import org.bigbluebutton.main.events.BreakoutRoomEvent;
@@ -115,7 +116,11 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
     <EventHandlers type="{TokenValidEvent.TOKEN_VALID_EVENT}" >
       <MethodInvoker generator="{UserService}" method="tokenValidEventHandler" arguments="{event}" />
     </EventHandlers>
-        
+
+	<EventHandlers type="{TokenValidReconnectEvent.TOKEN_VALID_RECONNECT_EVENT}" >
+		<MethodInvoker generator="{UserService}" method="tokenValidReconnectEventHandler" arguments="{event}" />
+	</EventHandlers>
+
 	  <EventHandlers type="{ChangeRoleEvent.CHANGE_ROLE_EVENT}" >
 	    <MethodInvoker generator="{UserService}" method="changeRole" arguments="{event}" />
 	  </EventHandlers>
diff --git a/bigbluebutton-client/src/org/bigbluebutton/modules/users/services/MessageSender.as b/bigbluebutton-client/src/org/bigbluebutton/modules/users/services/MessageSender.as
index ef7299cfff612f9b2894bbfa1770fe2fe66af356..3c4cc56458addb93ea6fbace0ad1f6c76c281a27 100755
--- a/bigbluebutton-client/src/org/bigbluebutton/modules/users/services/MessageSender.as
+++ b/bigbluebutton-client/src/org/bigbluebutton/modules/users/services/MessageSender.as
@@ -73,11 +73,29 @@ package org.bigbluebutton.modules.users.services
       }, function(status:String):void { // status - On error occurred
         var logData:Object = UsersUtil.initLogData();
         logData.tags = ["apps"];
-        logData.message = "Error occurred assigning a presenter.";
+        logData.message = "Error occurred when user joining.";
         LOGGER.info(JSON.stringify(logData));
       }, JSON.stringify(message));
     }
-    
+
+    public function joinMeetingAfterReconnect(): void {
+      LOGGER.info("Sending JOIN MEETING AFTER RECONNECT message");
+
+      var message:Object = {
+        header: {name: "UserJoinMeetingAfterReconnectReqMsg", meetingId: UsersUtil.getInternalMeetingID(), userId: UsersUtil.getMyUserID()},
+        body: {userId: UsersUtil.getMyUserID(), authToken: LiveMeeting.inst().me.authToken}
+      };
+
+      var _nc:ConnectionManager = BBB.initConnectionManager();
+      _nc.sendMessage2x(function(result:String):void { // On successful result
+      }, function(status:String):void { // status - On error occurred
+          var logData:Object = UsersUtil.initLogData();
+          logData.tags = ["apps"];
+          logData.message = "Error occurred when user joining after reconnect.";
+          LOGGER.info(JSON.stringify(logData));
+      }, JSON.stringify(message));
+    }
+
     public function assignPresenter(newPresenterUserId:String, newPresenterName:String, assignedBy:String):void {
       var message:Object = {
         header: {name: "AssignPresenterReqMsg", meetingId: UsersUtil.getInternalMeetingID(), userId: UsersUtil.getMyUserID()},