From 3395cdbdc0fda674047905ce5836960aa9b6db2c Mon Sep 17 00:00:00 2001
From: Richard Alam <ritzalam@gmail.com>
Date: Thu, 17 Oct 2019 11:50:12 -0700
Subject: [PATCH]  Make sure we record audio if meeting is recorded  - We had
 an issue where FreeSWITCH, for some unknow reason, stopped recording the
 voice conference    in the middle of the meeting while there are users in the
 voice conference. We've relied on the    voice conf started event to trigger
 recording of wav files. This event is sent when the first user    joins the
 voice conference. In this case, there was no voice user joined after the
 recording stopped    as there were already users in the voice conference. TO
 make sure that the audio is recorded, akka-apps    will send a "check if
 voice conf is running and recording" message to FreeSWITCH every 30sec. If
 akka-apps    receives a "running=true recording=false" response from
 FreeSWITCH, akka-apps will send a start recording    msg to FreeSWITCH.

---
 akka-bbb-apps/.gitignore                      |  2 +
 .../senders/ReceivedJsonMsgHandlerActor.scala |  2 +
 .../core/running/MeetingActor.scala           | 39 +++++++++-
 .../bigbluebutton/core2/AnalyticsActor.scala  |  4 +
 .../core2/FromAkkaAppsMsgSenderActor.scala    |  2 +
 .../bigbluebutton/core2/MeetingStatus2x.scala | 17 +++--
 .../core2/message/senders/MsgBuilder.scala    |  9 +++
 akka-bbb-fsesl/.gitignore                     |  1 +
 akka-bbb-fsesl/run-dev.sh                     |  6 ++
 .../FreeswitchConferenceEventListener.java    | 10 +++
 .../voice/IVoiceConferenceService.java        |  2 +
 .../VoiceConfRunningAndRecordingEvent.java    | 31 ++++++++
 .../freeswitch/FreeswitchApplication.java     |  8 +-
 .../actions/CheckIfConfIsRunningCommand.java  |  2 +-
 .../actions/ConferenceCheckRecordCommand.java | 76 ++++++++++++++++++-
 .../XMLResponseConferenceListParser.java      |  1 -
 .../freeswitch/RxJsonMsgDeserializer.scala    | 18 +++++
 .../freeswitch/RxJsonMsgHdlrActor.scala       |  2 +
 .../freeswitch/VoiceConferenceService.scala   | 13 +++-
 .../common2/msgs/VoiceConfMsgs.scala          | 20 +++++
 20 files changed, 245 insertions(+), 20 deletions(-)
 create mode 100755 akka-bbb-fsesl/run-dev.sh
 create mode 100755 akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/events/VoiceConfRunningAndRecordingEvent.java

diff --git a/akka-bbb-apps/.gitignore b/akka-bbb-apps/.gitignore
index 1c0e395724..28cb15fcec 100644
--- a/akka-bbb-apps/.gitignore
+++ b/akka-bbb-apps/.gitignore
@@ -47,3 +47,5 @@ akka-patterns-store/
 lib_managed/
 .cache
 bin/
+src/main/resources/
+
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 da8d9d6290..78288fbcab 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
@@ -148,6 +148,8 @@ class ReceivedJsonMsgHandlerActor(
         routeGenericMsg[MuteMeetingCmdMsg](envelope, jsonNode)
       case IsMeetingMutedReqMsg.NAME =>
         routeGenericMsg[IsMeetingMutedReqMsg](envelope, jsonNode)
+      case CheckRunningAndRecordingVoiceConfEvtMsg.NAME =>
+        routeVoiceMsg[CheckRunningAndRecordingVoiceConfEvtMsg](envelope, jsonNode)
 
       // Breakout rooms
       case BreakoutRoomsListMsg.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 526e831e23..d31ffee053 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
@@ -376,10 +376,12 @@ class MeetingActor(
       case m: UserConnectedToGlobalAudioMsg      => handleUserConnectedToGlobalAudioMsg(m)
       case m: UserDisconnectedFromGlobalAudioMsg => handleUserDisconnectedFromGlobalAudioMsg(m)
       case m: VoiceConfRunningEvtMsg             => handleVoiceConfRunningEvtMsg(m)
+      case m: CheckRunningAndRecordingVoiceConfEvtMsg =>
+        handleCheckRunningAndRecordingVoiceConfEvtMsg(m)
 
       // Layout
-      case m: GetCurrentLayoutReqMsg             => handleGetCurrentLayoutReqMsg(m)
-      case m: BroadcastLayoutMsg                 => handleBroadcastLayoutMsg(m)
+      case m: GetCurrentLayoutReqMsg => handleGetCurrentLayoutReqMsg(m)
+      case m: BroadcastLayoutMsg     => handleBroadcastLayoutMsg(m)
 
       // Lock Settings
       case m: ChangeLockSettingsInMeetingCmdMsg =>
@@ -532,12 +534,28 @@ class MeetingActor(
 
     sendRttTraceTest()
     setRecordingChapterBreak()
+    checkVoiceConfIsRunningAndRecording()
 
     processUserInactivityAudit()
     flagRegisteredUsersWhoHasNotJoined()
     checkIfNeetToEndMeetingWhenNoAuthedUsers(liveMeeting)
   }
 
+  var lastVoiceRecordingAndRunningCheck = System.currentTimeMillis()
+  def checkVoiceConfIsRunningAndRecording(): Unit = {
+    val now = System.currentTimeMillis()
+    val elapsedTime = now - lastVoiceRecordingAndRunningCheck;
+    val timeToCheck = elapsedTime > 30000 // 30seconds
+    if (props.recordProp.record && timeToCheck) {
+      lastVoiceRecordingAndRunningCheck = now
+      val event = MsgBuilder.buildCheckRunningAndRecordingToVoiceConfSysMsg(
+        props.meetingProp.intId,
+        props.voiceProp.voiceConf
+      )
+      outGW.send(event)
+    }
+  }
+
   var lastRecBreakSentOn = expiryTracker.startedOnInMs
 
   def setRecordingChapterBreak(): Unit = {
@@ -707,4 +725,21 @@ class MeetingActor(
       }
     }
   }
+
+  def handleCheckRunningAndRecordingVoiceConfEvtMsg(msg: CheckRunningAndRecordingVoiceConfEvtMsg): Unit = {
+    if (liveMeeting.props.recordProp.record &&
+      msg.body.isRunning &&
+      !msg.body.isRecording) {
+      // Voice conference is running but not recording. We should start recording.
+      // But first, see if we have recording streams and stop those.
+      VoiceApp.stopRecordingVoiceConference(liveMeeting, outGW)
+
+      // Let us start recording.
+      val meetingId = liveMeeting.props.meetingProp.intId
+      val recordFile = VoiceApp.genRecordPath(voiceConfRecordPath, meetingId, TimeUtil.timeNowInMs())
+      log.info("Forcing START RECORDING voice conf. meetingId=" + meetingId + " voice conf=" + liveMeeting.props.voiceProp.voiceConf)
+
+      VoiceApp.startRecordingVoiceConference(liveMeeting, outGW, recordFile)
+    }
+  }
 }
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/AnalyticsActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/AnalyticsActor.scala
index 6d3b2587fb..a975d0560a 100755
--- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/AnalyticsActor.scala
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/AnalyticsActor.scala
@@ -123,6 +123,10 @@ class AnalyticsActor extends Actor with ActorLogging {
 
       // Recording
       case m: RecordingChapterBreakSysMsg => logMessage(msg)
+      case m: VoiceRecordingStartedEvtMsg => logMessage(msg)
+      case m: VoiceRecordingStoppedEvtMsgBody => logMessage(msg)
+      //case m: CheckRunningAndRecordingToVoiceConfSysMsg => logMessage(msg)
+      //case m: CheckRunningAndRecordingVoiceConfEvtMsg => logMessage(msg)
 
       case m: GetLockSettingsRespMsg => logMessage(msg)
       case m: ChangeLockSettingsInMeetingCmdMsg => logMessage(msg)
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/FromAkkaAppsMsgSenderActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/FromAkkaAppsMsgSenderActor.scala
index abb577ac35..36a4a2cdae 100755
--- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/FromAkkaAppsMsgSenderActor.scala
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/FromAkkaAppsMsgSenderActor.scala
@@ -48,6 +48,8 @@ class FromAkkaAppsMsgSenderActor(msgSender: MessageSender)
         msgSender.send(toVoiceConfRedisChannel, json)
       case TransferUserToVoiceConfSysMsg.NAME =>
         msgSender.send(toVoiceConfRedisChannel, json)
+      case CheckRunningAndRecordingToVoiceConfSysMsg.NAME =>
+        msgSender.send(toVoiceConfRedisChannel, json)
 
       //==================================================================
       // Send chat, presentation, and whiteboard in different channels so as not to
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/MeetingStatus2x.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/MeetingStatus2x.scala
index bea9bd6257..9228d659a9 100755
--- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/MeetingStatus2x.scala
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/MeetingStatus2x.scala
@@ -22,13 +22,6 @@ case class MeetingExtensionProp(maxExtensions: Int = 2, numExtensions: Int = 0,
 
 object MeetingStatus2x {
 
-  def isVoiceRecording(status: MeetingStatus2x): Boolean = {
-    status.voiceRecordings.values.find(s => s.recording) match {
-      case Some(rec) => true
-      case None      => false
-    }
-  }
-
   def isExtensionAllowed(status: MeetingStatus2x): Boolean = status.extension.numExtensions < status.extension.maxExtensions
   def incNumExtension(status: MeetingStatus2x): Int = {
     if (status.extension.numExtensions < status.extension.maxExtensions) {
@@ -56,6 +49,13 @@ object MeetingStatus2x {
   def getLastAuthedUserLeftOn(status: MeetingStatus2x): Long = status.lastAuthedUserLeftOn
   def resetLastAuthedUserLeftOn(status: MeetingStatus2x) = status.lastAuthedUserLeftOn = 0L
 
+  def isVoiceRecording(status: MeetingStatus2x): Boolean = {
+    status.voiceRecordings.values.find(s => s.recording) match {
+      case Some(rec) => true
+      case None      => false
+    }
+  }
+
   def voiceRecordingStart(status2x: MeetingStatus2x, stream: String): VoiceRecordingStream = {
     val vrs = new VoiceRecordingStream(stream, recording = false, createdOn = System.currentTimeMillis, ackedOn = None, stoppedOn = None)
     status2x.voiceRecordings += vrs.stream -> vrs
@@ -111,7 +111,8 @@ object MeetingStatus2x {
 }
 
 class MeetingStatus2x {
-  private var voiceRecordings: collection.immutable.HashMap[String, VoiceRecordingStream] = new collection.immutable.HashMap[String, VoiceRecordingStream]
+  private var voiceRecordings: collection.immutable.HashMap[String, VoiceRecordingStream] =
+    new collection.immutable.HashMap[String, VoiceRecordingStream]
 
   private var audioSettingsInited = false
   private var permissionsInited = 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 ac693a9b1c..33839c0f27 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
@@ -289,6 +289,15 @@ object MsgBuilder {
     BbbCommonEnvCoreMsg(envelope, event)
   }
 
+  def buildCheckRunningAndRecordingToVoiceConfSysMsg(meetingId: String, voiceConf: String): BbbCommonEnvCoreMsg = {
+    val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
+    val envelope = BbbCoreEnvelope(CheckRunningAndRecordingToVoiceConfSysMsg.NAME, routing)
+    val header = BbbCoreHeaderWithMeetingId(CheckRunningAndRecordingToVoiceConfSysMsg.NAME, meetingId)
+    val body = CheckRunningAndRecordingToVoiceConfSysMsgBody(voiceConf, meetingId)
+    val event = CheckRunningAndRecordingToVoiceConfSysMsg(header, body)
+    BbbCommonEnvCoreMsg(envelope, event)
+  }
+
   def buildStartRecordingVoiceConfSysMsg(meetingId: String, voiceConf: String, stream: String): BbbCommonEnvCoreMsg = {
     val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
     val envelope = BbbCoreEnvelope(StartRecordingVoiceConfSysMsg.NAME, routing)
diff --git a/akka-bbb-fsesl/.gitignore b/akka-bbb-fsesl/.gitignore
index 1c0e395724..446435b973 100644
--- a/akka-bbb-fsesl/.gitignore
+++ b/akka-bbb-fsesl/.gitignore
@@ -47,3 +47,4 @@ akka-patterns-store/
 lib_managed/
 .cache
 bin/
+src/main/resources/
diff --git a/akka-bbb-fsesl/run-dev.sh b/akka-bbb-fsesl/run-dev.sh
new file mode 100755
index 0000000000..d08b052304
--- /dev/null
+++ b/akka-bbb-fsesl/run-dev.sh
@@ -0,0 +1,6 @@
+#!/usr/bin/env bash
+
+rm -rf src/main/resources
+cp -R src/universal/conf src/main/resources
+sbt run
+
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 445d169314..a0386f5225 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
@@ -88,7 +88,17 @@ public class FreeswitchConferenceEventListener implements ConferenceEventListene
             vcs.deskShareRTMPBroadcastStopped(evt.getRoom(), evt.getBroadcastingStreamUrl(),
               evt.getVideoWidth(), evt.getVideoHeight(), evt.getTimestamp());
           }
+        } else if (event instanceof VoiceConfRunningAndRecordingEvent) {
+          VoiceConfRunningAndRecordingEvent evt = (VoiceConfRunningAndRecordingEvent) event;
+          if (evt.running && ! evt.recording) {
+            log.warn("Voice conf running but not recording. conf=" + evt.getRoom()
+                    + ",running=" + evt.running
+                    + ",rec=" + evt.recording);
+          }
+
+          vcs.voiceConfRunningAndRecording(evt.getRoom(), evt.running, evt.recording);
         }
+
       }
     };
 
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 dd7fb1d13e..76b43997c5 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
@@ -24,4 +24,6 @@ public interface IVoiceConferenceService {
 
   void deskShareRTMPBroadcastStopped(String room, String streamname, Integer videoWidth, Integer videoHeight, String timestamp);
 
+  void voiceConfRunningAndRecording(String room, Boolean isRunning, Boolean isRecording);
+
 }
diff --git a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/events/VoiceConfRunningAndRecordingEvent.java b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/events/VoiceConfRunningAndRecordingEvent.java
new file mode 100755
index 0000000000..0d3332c55c
--- /dev/null
+++ b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/events/VoiceConfRunningAndRecordingEvent.java
@@ -0,0 +1,31 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ * <p>
+ * Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
+ * <p>
+ * 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.
+ * <p>
+ * 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.
+ * <p>
+ * 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.events;
+
+public class VoiceConfRunningAndRecordingEvent extends VoiceConferenceEvent {
+
+  public final boolean running;
+  public final boolean recording;
+
+  public VoiceConfRunningAndRecordingEvent(String room, boolean running, boolean recording) {
+    super(room);
+    this.running = running;
+    this.recording = recording;
+  }
+
+}
diff --git a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/FreeswitchApplication.java b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/FreeswitchApplication.java
index e031107a3d..36af8398a3 100755
--- a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/FreeswitchApplication.java
+++ b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/FreeswitchApplication.java
@@ -103,12 +103,14 @@ public class FreeswitchApplication implements  IDelayedCommandListener{
     msgSenderExec.execute(sender);
   }
 
+  public void checkRunningAndRecording(String voiceConfId, String meetingId) {
+    ConferenceCheckRecordCommand ccrc = new ConferenceCheckRecordCommand(voiceConfId, meetingId);
+    queueMessage(ccrc);
+  }
+
   public void getAllUsers(String voiceConfId) {
     GetAllUsersCommand prc = new GetAllUsersCommand(voiceConfId, USER);
     queueMessage(prc);
-
-    ConferenceCheckRecordCommand ccrc = new ConferenceCheckRecordCommand(voiceConfId, USER);
-    queueMessage(ccrc);
   }
 
   public void muteUser(String voiceConfId, String voiceUserId, Boolean mute) {
diff --git a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/actions/CheckIfConfIsRunningCommand.java b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/actions/CheckIfConfIsRunningCommand.java
index 934e57826b..68df9d5922 100755
--- a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/actions/CheckIfConfIsRunningCommand.java
+++ b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/actions/CheckIfConfIsRunningCommand.java
@@ -60,7 +60,7 @@ public class CheckIfConfIsRunningCommand extends FreeswitchCommand {
 
         String firstLine = response.getBodyLines().get(0);
 
-        log.info("Check conference first line response: " + firstLine);
+        //log.info("Check conference first line response: " + firstLine);
         //E.g. Conference 85115 not found
         
         if(!firstLine.startsWith("<?xml")) {
diff --git a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/actions/ConferenceCheckRecordCommand.java b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/actions/ConferenceCheckRecordCommand.java
index 56c887d475..b9c648a743 100755
--- a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/actions/ConferenceCheckRecordCommand.java
+++ b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/actions/ConferenceCheckRecordCommand.java
@@ -1,11 +1,24 @@
 package org.bigbluebutton.freeswitch.voice.freeswitch.actions;
 
+import org.apache.commons.lang3.StringUtils;
 import org.bigbluebutton.freeswitch.voice.events.ConferenceEventListener;
+import org.bigbluebutton.freeswitch.voice.events.VoiceConfRunningAndRecordingEvent;
+import org.bigbluebutton.freeswitch.voice.freeswitch.response.ConferenceMember;
+import org.bigbluebutton.freeswitch.voice.freeswitch.response.XMLResponseConferenceListParser;
 import org.freeswitch.esl.client.transport.message.EslMessage;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+import org.xml.sax.SAXException;
 
+import javax.xml.parsers.ParserConfigurationException;
+import javax.xml.parsers.SAXParser;
+import javax.xml.parsers.SAXParserFactory;
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
 import java.util.Iterator;
 
 public class ConferenceCheckRecordCommand extends FreeswitchCommand {
+  private static Logger log = LoggerFactory.getLogger(ConferenceCheckRecordCommand.class);
 
   public ConferenceCheckRecordCommand(String room, String requesterId) {
     super(room, requesterId);
@@ -13,14 +26,69 @@ public class ConferenceCheckRecordCommand extends FreeswitchCommand {
 
   @Override
   public String getCommandArgs() {
-    return room + " chkrecord";
+    //return room + " chkrecord";
+    return getRoom() + SPACE + "xml_list";
   }
 
   public void handleResponse(EslMessage response, ConferenceEventListener eventListener) {
 
-    Iterator iterator = response.getBodyLines().iterator();
-    while(iterator.hasNext()) {
-      System.out.println(iterator.next());
+    String firstLine = response.getBodyLines().get(0);
+    //log.info("Check conference first line response: " + firstLine);
+
+    if(!firstLine.startsWith("<?xml")) {
+      //log.info("Conference is not running and recording {}.", room);
+      VoiceConfRunningAndRecordingEvent voiceConfRunningAndRecordingEvent =
+              new VoiceConfRunningAndRecordingEvent(getRoom(), false, false);
+      eventListener.handleConferenceEvent(voiceConfRunningAndRecordingEvent);
+      return;
+    }
+
+    XMLResponseConferenceListParser confXML = new XMLResponseConferenceListParser();
+    //get a factory
+    SAXParserFactory spf = SAXParserFactory.newInstance();
+    try {
+
+      boolean running = false;
+      boolean recording = false;
+
+      //get a new instance of parser
+      SAXParser sp = spf.newSAXParser();
+
+      //Hack turning body lines back into string then to ByteStream.... BLAH!
+      String responseBody = StringUtils.join(response.getBodyLines(), "\n");
+      //http://mark.koli.ch/2009/02/resolving-orgxmlsaxsaxparseexception-content-is-not-allowed-in-prolog.html
+      //This Sux!
+      responseBody = responseBody.trim().replaceFirst("^([\\W]+)<","<");
+
+      ByteArrayInputStream bs = new ByteArrayInputStream(responseBody.getBytes());
+      sp.parse(bs, confXML);
+
+      Integer numUsers =  confXML.getConferenceList().size();
+      if (numUsers > 0) {
+        //log.info("Check conference response: " + responseBody);
+        running = true;
+
+        for (ConferenceMember member : confXML.getConferenceList()) {
+          if ("caller".equals(member.getMemberType())) {
+            // We don't need this. If there is at least one user in the conference,
+            // then it is running. (ralam Oct 16, 2019)
+
+          } else if ("recording_node".equals(member.getMemberType())) {
+            recording = true;
+          }
+        }
+      }
+
+      VoiceConfRunningAndRecordingEvent voiceConfRunningAndRecordingEvent =
+              new VoiceConfRunningAndRecordingEvent(getRoom(), running, recording);
+      eventListener.handleConferenceEvent(voiceConfRunningAndRecordingEvent);
+
+    }catch(SAXException se) {
+      log.error("Cannot parse response. ", se);
+    }catch(ParserConfigurationException pce) {
+      log.error("ParserConfigurationException. ", pce);
+    }catch (IOException ie) {
+      log.error("Cannot parse response. IO Exception. ", ie);
     }
   }
 }
diff --git a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/response/XMLResponseConferenceListParser.java b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/response/XMLResponseConferenceListParser.java
index 48d7df3b48..7fbbd1a99d 100755
--- a/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/response/XMLResponseConferenceListParser.java
+++ b/akka-bbb-fsesl/src/main/java/org/bigbluebutton/freeswitch/voice/freeswitch/response/XMLResponseConferenceListParser.java
@@ -93,7 +93,6 @@ public class XMLResponseConferenceListParser extends DefaultHandler {
         tempVal = "";
         if(qName.equalsIgnoreCase("member")) {
             String memberType = attributes.getValue("type");
-            System.out.println("******************* Member Type = " + memberType);
 
             //create a new instance of ConferenceMember
             tempMember = new ConferenceMember();
diff --git a/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/freeswitch/RxJsonMsgDeserializer.scala b/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/freeswitch/RxJsonMsgDeserializer.scala
index 7e69fdbdc1..1170cd5e07 100755
--- a/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/freeswitch/RxJsonMsgDeserializer.scala
+++ b/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/freeswitch/RxJsonMsgDeserializer.scala
@@ -9,6 +9,24 @@ trait RxJsonMsgDeserializer {
 
   object JsonDeserializer extends Deserializer
 
+  def routeCheckRunningAndRecordingToVoiceConfSysMsg(envelope: BbbCoreEnvelope, jsonNode: JsonNode): Unit = {
+    def deserialize(jsonNode: JsonNode): Option[CheckRunningAndRecordingToVoiceConfSysMsg] = {
+      val (result, error) = JsonDeserializer.toBbbCommonMsg[CheckRunningAndRecordingToVoiceConfSysMsg](jsonNode)
+      result match {
+        case Some(msg) => Some(msg.asInstanceOf[CheckRunningAndRecordingToVoiceConfSysMsg])
+        case None =>
+          log.error("Failed to deserialize message: error: {} \n msg: {}", error, jsonNode)
+          None
+      }
+    }
+
+    for {
+      m <- deserialize(jsonNode)
+    } yield {
+      fsApp.checkRunningAndRecording(m.body.voiceConf, m.body.meetingId)
+    }
+  }
+
   def routeDeskshareStartRtmpBroadcastVoiceConfMsg(envelope: BbbCoreEnvelope, jsonNode: JsonNode): Unit = {
     def deserialize(jsonNode: JsonNode): Option[ScreenshareStartRtmpBroadcastVoiceConfMsg] = {
       val (result, error) = JsonDeserializer.toBbbCommonMsg[ScreenshareStartRtmpBroadcastVoiceConfMsg](jsonNode)
diff --git a/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/freeswitch/RxJsonMsgHdlrActor.scala b/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/freeswitch/RxJsonMsgHdlrActor.scala
index 546c361cbb..0dba78515a 100755
--- a/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/freeswitch/RxJsonMsgHdlrActor.scala
+++ b/akka-bbb-fsesl/src/main/scala/org/bigbluebutton/freeswitch/RxJsonMsgHdlrActor.scala
@@ -52,6 +52,8 @@ class RxJsonMsgHdlrActor(val fsApp: FreeswitchApplication) extends Actor with Ac
         routeDeskshareStopRtmpBroadcastVoiceConfMsg(envelope, jsonNode)
       case ScreenshareStartRtmpBroadcastVoiceConfMsg.NAME =>
         routeDeskshareStartRtmpBroadcastVoiceConfMsg(envelope, jsonNode)
+      case CheckRunningAndRecordingToVoiceConfSysMsg.NAME =>
+        routeCheckRunningAndRecordingToVoiceConfSysMsg(envelope, jsonNode)
       case _ => // do nothing
     }
   }
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 6ca12d8790..81dee9e5b8 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
@@ -10,6 +10,18 @@ class VoiceConferenceService(sender: RedisPublisher) extends IVoiceConferenceSer
 
   val FROM_VOICE_CONF_SYSTEM_CHAN = "bigbluebutton:from-voice-conf:system"
 
+  def voiceConfRunningAndRecording(voiceConfId: String, isRunning: java.lang.Boolean, isRecording: java.lang.Boolean) {
+    val header = BbbCoreVoiceConfHeader(CheckRunningAndRecordingVoiceConfEvtMsg.NAME, voiceConfId)
+    val body = CheckRunningAndRecordingVoiceConfEvtMsgBody(voiceConfId, isRunning.booleanValue(), isRecording.booleanValue())
+    val envelope = BbbCoreEnvelope(CheckRunningAndRecordingVoiceConfEvtMsg.NAME, Map("voiceConf" -> voiceConfId))
+
+    val msg = new CheckRunningAndRecordingVoiceConfEvtMsg(header, body)
+    val msgEvent = BbbCommonEnvCoreMsg(envelope, msg)
+
+    val json = JsonUtil.toJson(msgEvent)
+    sender.publish(fromVoiceConfRedisChannel, json)
+  }
+
   def voiceConfRecordingStarted(voiceConfId: String, recordStream: String, recording: java.lang.Boolean, timestamp: String) {
     val header = BbbCoreVoiceConfHeader(RecordingStartedVoiceConfEvtMsg.NAME, voiceConfId)
     val body = RecordingStartedVoiceConfEvtMsgBody(voiceConfId, recordStream, recording.booleanValue(), timestamp)
@@ -24,7 +36,6 @@ class VoiceConferenceService(sender: RedisPublisher) extends IVoiceConferenceSer
   }
 
   def voiceConfRunning(voiceConfId: String, running: java.lang.Boolean): Unit = {
-    println("*************######## Conference voiceConfId=" + voiceConfId + " running=" + running)
     val header = BbbCoreVoiceConfHeader(VoiceConfRunningEvtMsg.NAME, voiceConfId)
     val body = VoiceConfRunningEvtMsgBody(voiceConfId, running.booleanValue())
     val envelope = BbbCoreEnvelope(VoiceConfRunningEvtMsg.NAME, Map("voiceConf" -> voiceConfId))
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 c228dbae47..6e4154769d 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
@@ -253,6 +253,26 @@ case class TransferUserToVoiceConfSysMsg(
 ) extends BbbCoreMsg
 case class TransferUserToVoiceConfSysMsgBody(fromVoiceConf: String, toVoiceConf: String, voiceUserId: String)
 
+/**
+ * Sent to FS to check if voice conference is running and recording.
+ */
+object CheckRunningAndRecordingToVoiceConfSysMsg { val NAME = "CheckRunningAndRecordingToVoiceConfSysMsg" }
+case class CheckRunningAndRecordingToVoiceConfSysMsg(
+    header: BbbCoreHeaderWithMeetingId,
+    body:   CheckRunningAndRecordingToVoiceConfSysMsgBody
+) extends BbbCoreMsg
+case class CheckRunningAndRecordingToVoiceConfSysMsgBody(voiceConf: String, meetingId: String)
+
+/**
+ * Received from FS to check if voice conference is running and recording.
+ */
+object CheckRunningAndRecordingVoiceConfEvtMsg { val NAME = "CheckRunningAndRecordingVoiceConfEvtMsg" }
+case class CheckRunningAndRecordingVoiceConfEvtMsg(
+    header: BbbCoreVoiceConfHeader,
+    body:   CheckRunningAndRecordingVoiceConfEvtMsgBody
+) extends VoiceStandardMsg
+case class CheckRunningAndRecordingVoiceConfEvtMsgBody(voiceConf: String, isRunning: Boolean, isRecording: Boolean)
+
 /**
  * Received from FS that user joined voice conference.
  */
-- 
GitLab