From 722fb707cc4d8f9ec9ad2c9811a7f2d2f9b5c1fe Mon Sep 17 00:00:00 2001
From: Richard Alam <ritzalam@gmail.com>
Date: Fri, 23 Apr 2021 21:25:06 +0000
Subject: [PATCH]  - fix issue
 https://github.com/bigbluebutton/bigbluebutton/issues/11358 by having the
 parent meeting    tell the breakout rooms the remaining time. Currently, each
 room has its own countdown timer which    could introduce drifting resulting
 in breakout rooms ending at different times.

---
 .../bigbluebutton/core/api/InMessages.scala   |  7 ++++++
 ...BreakoutTimeRemainingInternalMsgHdlr.scala | 15 ++++++++++++
 .../core/running/MeetingActor.scala           | 12 ++++++++--
 .../SendBreakoutTimeRemainingMsgHdlr.scala    | 23 ++++++++++++-------
 .../SendTimeRemainingUpdateHdlr.scala         | 20 ++++------------
 .../core2/message/senders/MsgBuilder.scala    | 10 ++++++++
 6 files changed, 61 insertions(+), 26 deletions(-)
 create mode 100755 akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/breakout/SendBreakoutTimeRemainingInternalMsgHdlr.scala

diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/api/InMessages.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/api/InMessages.scala
index 19ef329125..c08cb4ad4a 100755
--- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/api/InMessages.scala
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/api/InMessages.scala
@@ -26,6 +26,13 @@ case class MonitorNumberOfUsersInternalMsg(meetingID: String) extends InMessage
  */
 case class SendTimeRemainingAuditInternalMsg(meetingId: String) extends InMessage
 
+/**
+ * Parent message sent to breakout rooms to trigger updating clients of meeting time remaining.
+ * @param meetingId
+ * @param timeLeftInSec
+ */
+case class SendBreakoutTimeRemainingInternalMsg(meetingId: String, timeLeftInSec: Long) extends InMessage
+
 case class SendRecordingTimerInternalMsg(meetingId: String) extends InMessage
 
 case class ExtendMeetingDuration(meetingId: String, userId: String) extends InMessage
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/breakout/SendBreakoutTimeRemainingInternalMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/breakout/SendBreakoutTimeRemainingInternalMsgHdlr.scala
new file mode 100755
index 0000000000..c3decbbf73
--- /dev/null
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/breakout/SendBreakoutTimeRemainingInternalMsgHdlr.scala
@@ -0,0 +1,15 @@
+package org.bigbluebutton.core.apps.breakout
+
+import org.bigbluebutton.core.api.SendBreakoutTimeRemainingInternalMsg
+import org.bigbluebutton.core.running.{LiveMeeting, OutMsgRouter}
+import org.bigbluebutton.core2.message.senders.MsgBuilder
+
+trait SendBreakoutTimeRemainingInternalMsgHdlr {
+  val liveMeeting: LiveMeeting
+  val outGW: OutMsgRouter
+
+  def handleSendBreakoutTimeRemainingInternalMsg(msg: SendBreakoutTimeRemainingInternalMsg): Unit = {
+    val event = MsgBuilder.buildMeetingTimeRemainingUpdateEvtMsg(liveMeeting.props.meetingProp.intId, msg.timeLeftInSec.toInt)
+    outGW.send(event)
+  }
+}
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 adcea570d7..a26f62c541 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
@@ -86,6 +86,7 @@ class MeetingActor(
   with DestroyMeetingSysCmdMsgHdlr
   with SendTimeRemainingUpdateHdlr
   with SendBreakoutTimeRemainingMsgHdlr
+  with SendBreakoutTimeRemainingInternalMsgHdlr
   with ChangeLockSettingsInMeetingCmdMsgHdlr
   with SyncGetMeetingInfoRespMsgHdlr
   with ClientToServerLatencyTracerMsgHdlr
@@ -238,16 +239,23 @@ class MeetingActor(
 
     case msg: ExtendMeetingDuration           => handleExtendMeetingDuration(msg)
     case msg: SendTimeRemainingAuditInternalMsg =>
-      state = handleSendTimeRemainingUpdate(msg, state)
+      if (!liveMeeting.props.meetingProp.isBreakout) {
+        // Update users of meeting remaining time.
+        state = handleSendTimeRemainingUpdate(msg, state)
+      }
+
+      // Update breakout rooms of remaining time
       state = handleSendBreakoutTimeRemainingMsg(msg, state)
     case msg: BreakoutRoomCreatedInternalMsg     => state = handleBreakoutRoomCreatedInternalMsg(msg, state)
     case msg: SendBreakoutUsersAuditInternalMsg  => handleSendBreakoutUsersUpdateInternalMsg(msg)
     case msg: BreakoutRoomUsersUpdateInternalMsg => state = handleBreakoutRoomUsersUpdateInternalMsg(msg, state)
     case msg: EndBreakoutRoomInternalMsg         => handleEndBreakoutRoomInternalMsg(msg)
     case msg: BreakoutRoomEndedInternalMsg       => state = handleBreakoutRoomEndedInternalMsg(msg, state)
+    case msg: SendBreakoutTimeRemainingInternalMsg =>
+      handleSendBreakoutTimeRemainingInternalMsg(msg)
 
     // Screenshare
-    case msg: DeskShareGetDeskShareInfoRequest   => handleDeskShareGetDeskShareInfoRequest(msg)
+    case msg: DeskShareGetDeskShareInfoRequest => handleDeskShareGetDeskShareInfoRequest(msg)
 
     case msg: SendRecordingTimerInternalMsg =>
       state = usersApp.handleSendRecordingTimerInternalMsg(msg, state)
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/handlers/SendBreakoutTimeRemainingMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/handlers/SendBreakoutTimeRemainingMsgHdlr.scala
index 7a428f0589..55116e2183 100755
--- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/handlers/SendBreakoutTimeRemainingMsgHdlr.scala
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/handlers/SendBreakoutTimeRemainingMsgHdlr.scala
@@ -1,30 +1,37 @@
 package org.bigbluebutton.core2.message.handlers
 
 import org.bigbluebutton.common2.msgs._
-import org.bigbluebutton.core.api.SendTimeRemainingAuditInternalMsg
+import org.bigbluebutton.core.api.{ EndBreakoutRoomInternalMsg, SendBreakoutTimeRemainingInternalMsg, SendTimeRemainingAuditInternalMsg }
+import org.bigbluebutton.core.bus.BigBlueButtonEvent
 import org.bigbluebutton.core.domain.MeetingState2x
-import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
+import org.bigbluebutton.core.running.{ LiveMeeting, MeetingActor, OutMsgRouter }
 import org.bigbluebutton.core.util.TimeUtil
 
 trait SendBreakoutTimeRemainingMsgHdlr {
+  this: MeetingActor =>
+
   val liveMeeting: LiveMeeting
   val outGW: OutMsgRouter
 
   def handleSendBreakoutTimeRemainingMsg(msg: SendTimeRemainingAuditInternalMsg, state: MeetingState2x): MeetingState2x = {
-
     for {
       model <- state.breakout
       startedOn <- model.startedOn
     } yield {
-      if (!liveMeeting.props.meetingProp.isBreakout) {
-
-        val endMeetingTime = TimeUtil.millisToSeconds(startedOn) + TimeUtil.minutesToSeconds(model.durationInMinutes)
-        val timeRemaining = endMeetingTime - TimeUtil.millisToSeconds(System.currentTimeMillis())
+      val endMeetingTime = TimeUtil.millisToSeconds(startedOn) + TimeUtil.minutesToSeconds(model.durationInMinutes)
+      val timeRemaining = endMeetingTime - TimeUtil.millisToSeconds(System.currentTimeMillis())
 
+      if (!liveMeeting.props.meetingProp.isBreakout) {
+        // Notify parent meeting users of breakout rooms time remaining
         val event = buildBreakoutRoomsTimeRemainingUpdateEvtMsg(liveMeeting.props.meetingProp.intId, timeRemaining.toInt)
-
         outGW.send(event)
       }
+
+      // Tell all breakout rooms of time remaining so they can notify their users.
+      // This syncs all rooms about time remaining.
+      model.rooms.values.foreach { room =>
+        eventBus.publish(BigBlueButtonEvent(room.id, SendBreakoutTimeRemainingInternalMsg(props.breakoutProps.parentId, timeRemaining.toInt)))
+      }
     }
 
     state
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/handlers/SendTimeRemainingUpdateHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/handlers/SendTimeRemainingUpdateHdlr.scala
index b79f000b77..e17880b90c 100755
--- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/handlers/SendTimeRemainingUpdateHdlr.scala
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/message/handlers/SendTimeRemainingUpdateHdlr.scala
@@ -1,10 +1,10 @@
 package org.bigbluebutton.core2.message.handlers
 
-import org.bigbluebutton.common2.msgs._
 import org.bigbluebutton.core.api.SendTimeRemainingAuditInternalMsg
-import org.bigbluebutton.core.domain.{ MeetingState2x }
-import org.bigbluebutton.core.running.{ BaseMeetingActor, LiveMeeting, OutMsgRouter }
+import org.bigbluebutton.core.domain.MeetingState2x
+import org.bigbluebutton.core.running.{BaseMeetingActor, LiveMeeting, OutMsgRouter}
 import org.bigbluebutton.core.util.TimeUtil
+import org.bigbluebutton.core2.message.senders.MsgBuilder
 
 trait SendTimeRemainingUpdateHdlr {
   this: BaseMeetingActor =>
@@ -13,26 +13,14 @@ trait SendTimeRemainingUpdateHdlr {
   val outGW: OutMsgRouter
 
   def handleSendTimeRemainingUpdate(msg: SendTimeRemainingAuditInternalMsg, state: MeetingState2x): MeetingState2x = {
-
     if (state.expiryTracker.durationInMs > 0) {
       val endMeetingTime = state.expiryTracker.endMeetingTime()
       val timeRemaining = TimeUtil.millisToSeconds(endMeetingTime - TimeUtil.timeNowInMs())
 
-      def buildMeetingTimeRemainingUpdateEvtMsg(meetingId: String, timeLeftInSec: Long): BbbCommonEnvCoreMsg = {
-        val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, meetingId, "not-used")
-        val envelope = BbbCoreEnvelope(MeetingTimeRemainingUpdateEvtMsg.NAME, routing)
-        val body = MeetingTimeRemainingUpdateEvtMsgBody(timeLeftInSec)
-        val header = BbbClientMsgHeader(MeetingTimeRemainingUpdateEvtMsg.NAME, meetingId, "not-used")
-        val event = MeetingTimeRemainingUpdateEvtMsg(header, body)
-
-        BbbCommonEnvCoreMsg(envelope, event)
-      }
-
       if (timeRemaining > 0) {
-        val event = buildMeetingTimeRemainingUpdateEvtMsg(liveMeeting.props.meetingProp.intId, timeRemaining.toInt)
+        val event = MsgBuilder.buildMeetingTimeRemainingUpdateEvtMsg(liveMeeting.props.meetingProp.intId, timeRemaining.toInt)
         outGW.send(event)
       }
-
     }
 
     state
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 1104852f6d..93c7a550ca 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
@@ -494,4 +494,14 @@ object MsgBuilder {
 
     BbbCommonEnvCoreMsg(envelope, event)
   }
+
+  def buildMeetingTimeRemainingUpdateEvtMsg(meetingId: String, timeLeftInSec: Long): BbbCommonEnvCoreMsg = {
+    val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, meetingId, "not-used")
+    val envelope = BbbCoreEnvelope(MeetingTimeRemainingUpdateEvtMsg.NAME, routing)
+    val body = MeetingTimeRemainingUpdateEvtMsgBody(timeLeftInSec)
+    val header = BbbClientMsgHeader(MeetingTimeRemainingUpdateEvtMsg.NAME, meetingId, "not-used")
+    val event = MeetingTimeRemainingUpdateEvtMsg(header, body)
+
+    BbbCommonEnvCoreMsg(envelope, event)
+  }
 }
-- 
GitLab