diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/externalvideo/ExternalVideoApp2x.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/externalvideo/ExternalVideoApp2x.scala
new file mode 100644
index 0000000000000000000000000000000000000000..0630711d6408f2a45fe3ec0c446c82978ca08142
--- /dev/null
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/externalvideo/ExternalVideoApp2x.scala
@@ -0,0 +1,13 @@
+package org.bigbluebutton.core.apps.externalvideo
+
+import akka.actor.ActorContext
+import akka.event.Logging
+
+class ExternalVideoApp2x(implicit val context: ActorContext)
+  extends StartExternalVideoPubMsgHdlr
+  with UpdateExternalVideoPubMsgHdlr
+  with StopExternalVideoPubMsgHdlr {
+
+  val log = Logging(context.system, getClass)
+
+}
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/externalvideo/StartExternalVideoPubMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/externalvideo/StartExternalVideoPubMsgHdlr.scala
new file mode 100644
index 0000000000000000000000000000000000000000..39a81d9aab31dd6bac923e3dfeaafe53659b66cc
--- /dev/null
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/externalvideo/StartExternalVideoPubMsgHdlr.scala
@@ -0,0 +1,27 @@
+package org.bigbluebutton.core.apps.externalvideo
+
+import org.bigbluebutton.common2.msgs._
+import org.bigbluebutton.core.bus.MessageBus
+import org.bigbluebutton.core.running.{ LiveMeeting }
+
+trait StartExternalVideoPubMsgHdlr {
+  this: ExternalVideoApp2x =>
+
+  def handle(msg: StartExternalVideoPubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
+    log.info("Received StartExternalVideoPubMsgr meetingId={} url={}", liveMeeting.props.meetingProp.intId, msg.body.externalVideoUrl)
+
+    def broadcastEvent(msg: StartExternalVideoPubMsg) {
+
+      val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, liveMeeting.props.meetingProp.intId, "nodeJSapp")
+      val envelope = BbbCoreEnvelope(StartExternalVideoEvtMsg.NAME, routing)
+      val header = BbbClientMsgHeader(StartExternalVideoEvtMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
+
+      val body = StartExternalVideoEvtMsgBody(msg.body.externalVideoUrl)
+      val event = StartExternalVideoEvtMsg(header, body)
+      val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
+      bus.outGW.send(msgEvent)
+    }
+
+    broadcastEvent(msg)
+  }
+}
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/externalvideo/StopExternalVideoPubMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/externalvideo/StopExternalVideoPubMsgHdlr.scala
new file mode 100644
index 0000000000000000000000000000000000000000..fb8ef02953d5936fea88f483f7eaead8f2510e9e
--- /dev/null
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/externalvideo/StopExternalVideoPubMsgHdlr.scala
@@ -0,0 +1,27 @@
+package org.bigbluebutton.core.apps.externalvideo
+
+import org.bigbluebutton.common2.msgs._
+import org.bigbluebutton.core.bus.MessageBus
+import org.bigbluebutton.core.running.{ LiveMeeting }
+
+trait StopExternalVideoPubMsgHdlr {
+  this: ExternalVideoApp2x =>
+
+  def handle(msg: StopExternalVideoPubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
+    log.info("Received StopExternalVideoPubMsgr meetingId={}", liveMeeting.props.meetingProp.intId)
+
+    def broadcastEvent() {
+
+      val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, liveMeeting.props.meetingProp.intId, "nodeJSapp")
+      val envelope = BbbCoreEnvelope(StopExternalVideoEvtMsg.NAME, routing)
+      val header = BbbClientMsgHeader(StopExternalVideoEvtMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
+
+      val body = StopExternalVideoEvtMsgBody()
+      val event = StopExternalVideoEvtMsg(header, body)
+      val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
+      bus.outGW.send(msgEvent)
+    }
+
+    broadcastEvent()
+  }
+}
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/externalvideo/UpdateExternalVideoPubMsgHdlr.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/externalvideo/UpdateExternalVideoPubMsgHdlr.scala
new file mode 100644
index 0000000000000000000000000000000000000000..e343f5fa3a26e2ac8c99e91e45de82f8487ab68d
--- /dev/null
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/apps/externalvideo/UpdateExternalVideoPubMsgHdlr.scala
@@ -0,0 +1,23 @@
+package org.bigbluebutton.core.apps.externalvideo
+
+import org.bigbluebutton.common2.msgs._
+import org.bigbluebutton.core.bus.MessageBus
+import org.bigbluebutton.core.running.{ LiveMeeting }
+
+trait UpdateExternalVideoPubMsgHdlr {
+
+  def handle(msg: UpdateExternalVideoPubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
+    def broadcastEvent(msg: UpdateExternalVideoPubMsg) {
+      val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, liveMeeting.props.meetingProp.intId, "nodeJSapp")
+      val envelope = BbbCoreEnvelope(UpdateExternalVideoEvtMsg.NAME, routing)
+      val header = BbbClientMsgHeader(UpdateExternalVideoEvtMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
+
+      val body = UpdateExternalVideoEvtMsgBody(msg.body.status, msg.body.rate, msg.body.time, msg.body.state)
+      val event = UpdateExternalVideoEvtMsg(header, body)
+      val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
+      bus.outGW.send(msgEvent)
+    }
+
+    broadcastEvent(msg)
+  }
+}
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 f39fcea50fb454d87e16dbc651a7536ad66458f1..4313ed5242a60b9bf908c21baa26113506044d1a 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
@@ -319,6 +319,14 @@ class ReceivedJsonMsgHandlerActor(
       case CreateGroupChatReqMsg.NAME =>
         routeGenericMsg[CreateGroupChatReqMsg](envelope, jsonNode)
 
+      // ExternalVideo
+      case StartExternalVideoPubMsg.NAME =>
+        routeGenericMsg[StartExternalVideoPubMsg](envelope, jsonNode)
+      case UpdateExternalVideoPubMsg.NAME =>
+        routeGenericMsg[UpdateExternalVideoPubMsg](envelope, jsonNode)
+      case StopExternalVideoPubMsg.NAME =>
+        routeGenericMsg[StopExternalVideoPubMsg](envelope, jsonNode)
+
       case _ =>
         log.error("Cannot route envelope name " + envelope.name)
       // do nothing
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/record/events/AbsractExternalVideoRecordEvent.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/record/events/AbsractExternalVideoRecordEvent.scala
new file mode 100644
index 0000000000000000000000000000000000000000..66aefb0cf6e47f3deff4ecab774710930bd5ce92
--- /dev/null
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/record/events/AbsractExternalVideoRecordEvent.scala
@@ -0,0 +1,24 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ *
+ * Copyright (c) 2019 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.core.record.events
+
+trait AbstractExternalVideoRecordEvent extends RecordEvent {
+  setModule("EXTERNAL-VIDEO")
+}
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/record/events/StartExternalVideoRecordEvent.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/record/events/StartExternalVideoRecordEvent.scala
new file mode 100644
index 0000000000000000000000000000000000000000..1c1e33992980e6a99ceb1d306bd805358b6f0e83
--- /dev/null
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/record/events/StartExternalVideoRecordEvent.scala
@@ -0,0 +1,34 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ *
+ * Copyright (c) 2019 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.core.record.events
+
+class StartExternalVideoRecordEvent extends AbstractExternalVideoRecordEvent {
+  import StartExternalVideoRecordEvent._
+
+  setEvent("StartExternalVideoRecordEvent")
+
+  def setExternalVideoUrl(externalVideoUrl: String) {
+    eventMap.put(EXTERNAL_VIDEO_URL, externalVideoUrl)
+  }
+}
+
+object StartExternalVideoRecordEvent {
+  protected final val EXTERNAL_VIDEO_URL = "externalVideoUrl"
+}
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/record/events/StopExternalVideoRecordEvent.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/record/events/StopExternalVideoRecordEvent.scala
new file mode 100644
index 0000000000000000000000000000000000000000..789251bb99203e003225c687dfe1c2233ccd7e02
--- /dev/null
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/record/events/StopExternalVideoRecordEvent.scala
@@ -0,0 +1,24 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ *
+ * Copyright (c) 2019 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.core.record.events
+
+class StopExternalVideoRecordEvent extends AbstractExternalVideoRecordEvent {
+  setEvent("StopExternalVideoRecordEvent")
+}
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/record/events/UpdateExternalVideoRecordEvent.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/record/events/UpdateExternalVideoRecordEvent.scala
new file mode 100644
index 0000000000000000000000000000000000000000..983ab26be60b22ad3f6623b356baadba38e8f518
--- /dev/null
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/record/events/UpdateExternalVideoRecordEvent.scala
@@ -0,0 +1,49 @@
+/**
+ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
+ *
+ * Copyright (c) 2019 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.core.record.events
+
+class UpdateExternalVideoRecordEvent extends AbstractExternalVideoRecordEvent {
+  import UpdateExternalVideoRecordEvent._
+
+  setEvent("UpdateExternalVideoRecordEvent")
+
+  def setStatus(status: String) {
+    eventMap.put(STATUS, status)
+  }
+
+  def setRate(rate: Double) {
+    eventMap.put(RATE, rate.toString)
+  }
+
+  def setTime(time: Double) {
+    eventMap.put(TIME, time.toString)
+  }
+
+  def setState(state: Boolean) {
+    eventMap.put(STATE, state.toString)
+  }
+}
+
+object UpdateExternalVideoRecordEvent {
+  protected final val STATUS = "status"
+  protected final val RATE = "rate"
+  protected final val TIME = "time"
+  protected final val STATE = "state"
+}
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 4a37e15d130c5aee82b1c4486b2e5a2a0480ee0b..cb01e1dbb437e06461245297e5d168bacc16ff41 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
@@ -16,6 +16,7 @@ import org.bigbluebutton.core.api._
 import org.bigbluebutton.core.apps._
 import org.bigbluebutton.core.apps.caption.CaptionApp2x
 import org.bigbluebutton.core.apps.chat.ChatApp2x
+import org.bigbluebutton.core.apps.externalvideo.ExternalVideoApp2x
 import org.bigbluebutton.core.apps.screenshare.ScreenshareApp2x
 import org.bigbluebutton.core.apps.presentation.PresentationApp2x
 import org.bigbluebutton.core.apps.users.UsersApp2x
@@ -113,6 +114,7 @@ class MeetingActor(
   val screenshareApp2x = new ScreenshareApp2x
   val captionApp2x = new CaptionApp2x
   val chatApp2x = new ChatApp2x
+  val externalVideoApp2x = new ExternalVideoApp2x
   val usersApp = new UsersApp(liveMeeting, outGW, eventBus)
   val groupChatApp = new GroupChatHdlrs
   val presentationPodsApp = new PresentationPodHdlrs
@@ -460,6 +462,11 @@ class MeetingActor(
         state = groupChatApp.handle(m, state, liveMeeting, msgBus)
         updateUserLastActivity(m.body.msg.sender.id)
 
+      // ExternalVideo
+      case m: StartExternalVideoPubMsg    => externalVideoApp2x.handle(m, liveMeeting, msgBus)
+      case m: UpdateExternalVideoPubMsg   => externalVideoApp2x.handle(m, liveMeeting, msgBus)
+      case m: StopExternalVideoPubMsg     => externalVideoApp2x.handle(m, liveMeeting, msgBus)
+
       case m: ValidateConnAuthTokenSysMsg => handleValidateConnAuthTokenSysMsg(m)
 
       case m: UserActivitySignCmdMsg      => handleUserActivitySignCmdMsg(m)
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 37062c3978dd7b566b112f04d0023b466fc91fb2..165d9262fbaa9df54e8e0b80d33a24b3fef12ca0 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
@@ -119,7 +119,7 @@ class FromAkkaAppsMsgSenderActor(msgSender: MessageSender)
       //==================================================================
 
       case ValidateAuthTokenRespMsg.NAME =>
-        msgSender.send(fromAkkaAppsRedisChannel, json) // needed for cases when single nodejs process is running (like in development)  
+        msgSender.send(fromAkkaAppsRedisChannel, json) // needed for cases when single nodejs process is running (like in development)
         msgSender.send("from-akka-apps-frontend-redis-channel", json)
 
       // Message duplicated for frontend and backend processes
@@ -143,6 +143,9 @@ class FromAkkaAppsMsgSenderActor(msgSender: MessageSender)
         msgSender.send(fromAkkaAppsRedisChannel, json)
         msgSender.send("from-akka-apps-frontend-redis-channel", json)
 
+      case UpdateExternalVideoEvtMsg.NAME =>
+        msgSender.send("from-akka-apps-frontend-redis-channel", json)
+
       case _ =>
         msgSender.send(fromAkkaAppsRedisChannel, json)
     }
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 3aee1d95028a9d06a5b26b1a70d17fa0da6a3e8d..3469157c086c2e12684888bddc240d942b287d49 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
@@ -108,6 +108,11 @@ class RedisRecorderActor(
       case m: PollStoppedEvtMsg                     => handlePollStoppedEvtMsg(m)
       case m: PollShowResultEvtMsg                  => handlePollShowResultEvtMsg(m)
 
+      // ExternalVideo
+      case m: StartExternalVideoEvtMsg              => handleStartExternalVideoEvtMsg(m)
+      case m: UpdateExternalVideoEvtMsg             => handleUpdateExternalVideoEvtMsg(m)
+      case m: StopExternalVideoEvtMsg               => handleStopExternalVideoEvtMsg(m)
+
       case _                                        => // message not to be recorded.
     }
   }
@@ -456,6 +461,32 @@ class RedisRecorderActor(
   }
   */
 
+  private def handleStartExternalVideoEvtMsg(msg: StartExternalVideoEvtMsg) {
+    val ev = new StartExternalVideoRecordEvent()
+    ev.setMeetingId(msg.header.meetingId)
+    ev.setExternalVideoUrl(msg.body.externalVideoUrl)
+
+    record(msg.header.meetingId, ev.toMap.asJava)
+  }
+
+  private def handleUpdateExternalVideoEvtMsg(msg: UpdateExternalVideoEvtMsg) {
+    val ev = new UpdateExternalVideoRecordEvent()
+    ev.setMeetingId(msg.header.meetingId)
+    ev.setStatus(msg.body.status)
+    ev.setRate(msg.body.rate)
+    ev.setTime(msg.body.time)
+    ev.setState(msg.body.state)
+
+    record(msg.header.meetingId, ev.toMap.asJava)
+  }
+
+  private def handleStopExternalVideoEvtMsg(msg: StopExternalVideoEvtMsg) {
+    val ev = new StopExternalVideoRecordEvent()
+    ev.setMeetingId(msg.header.meetingId)
+
+    record(msg.header.meetingId, ev.toMap.asJava)
+  }
+
   private def handleRecordingStatusChangedEvtMsg(msg: RecordingStatusChangedEvtMsg) {
     val ev = new RecordStatusRecordEvent()
     ev.setMeetingId(msg.header.meetingId)
diff --git a/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/ExternalVideoMsgs.scala b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/ExternalVideoMsgs.scala
new file mode 100644
index 0000000000000000000000000000000000000000..7a66abd7e74828afd2e11092893e7c11129630d0
--- /dev/null
+++ b/bbb-common-message/src/main/scala/org/bigbluebutton/common2/msgs/ExternalVideoMsgs.scala
@@ -0,0 +1,27 @@
+package org.bigbluebutton.common2.msgs
+
+// In messages
+object StartExternalVideoPubMsg { val NAME = "StartExternalVideoPubMsg" }
+case class StartExternalVideoPubMsg(header: BbbClientMsgHeader, body: StartExternalVideoPubMsgBody) extends StandardMsg
+case class StartExternalVideoPubMsgBody(externalVideoUrl: String)
+
+object UpdateExternalVideoPubMsg { val NAME = "UpdateExternalVideoPubMsg" }
+case class UpdateExternalVideoPubMsg(header: BbbClientMsgHeader, body: UpdateExternalVideoPubMsgBody) extends StandardMsg
+case class UpdateExternalVideoPubMsgBody(status: String, rate: Double, time: Double, state: Boolean)
+
+object StopExternalVideoPubMsg { val NAME = "StopExternalVideoPubMsg" }
+case class StopExternalVideoPubMsg(header: BbbClientMsgHeader, body: StopExternalVideoPubMsgBody) extends StandardMsg
+case class StopExternalVideoPubMsgBody()
+
+// Out messages
+object StartExternalVideoEvtMsg { val NAME = "StartExternalVideoEvtMsg" }
+case class StartExternalVideoEvtMsg(header: BbbClientMsgHeader, body: StartExternalVideoEvtMsgBody) extends BbbCoreMsg
+case class StartExternalVideoEvtMsgBody(externalVideoUrl: String)
+
+object UpdateExternalVideoEvtMsg { val NAME = "UpdateExternalVideoEvtMsg" }
+case class UpdateExternalVideoEvtMsg(header: BbbClientMsgHeader, body: UpdateExternalVideoEvtMsgBody) extends BbbCoreMsg
+case class UpdateExternalVideoEvtMsgBody(status: String, rate: Double, time: Double, state: Boolean)
+
+object StopExternalVideoEvtMsg { val NAME = "StopExternalVideoEvtMsg" }
+case class StopExternalVideoEvtMsg(header: BbbClientMsgHeader, body: StopExternalVideoEvtMsgBody) extends BbbCoreMsg
+case class StopExternalVideoEvtMsgBody()
diff --git a/bigbluebutton-html5/imports/api/external-videos/index.js b/bigbluebutton-html5/imports/api/external-videos/index.js
index d4ef796968d67e052deb80e275a7b72b577359c0..64ffab0d0f715cd5c4573b598bcba266ff79e5e1 100644
--- a/bigbluebutton-html5/imports/api/external-videos/index.js
+++ b/bigbluebutton-html5/imports/api/external-videos/index.js
@@ -1,11 +1,9 @@
 import { Meteor } from 'meteor/meteor';
-import { makeCall } from '/imports/ui/services/api';
 
 let streamer = null;
 const getStreamer = (meetingID) => {
   if (!streamer) {
     streamer = new Meteor.Streamer(`external-videos-${meetingID}`);
-    makeCall('initializeExternalVideo');
   }
   return streamer;
 };
diff --git a/bigbluebutton-html5/imports/api/external-videos/server/eventHandlers.js b/bigbluebutton-html5/imports/api/external-videos/server/eventHandlers.js
new file mode 100644
index 0000000000000000000000000000000000000000..2cd4077869200dfe86e8ce0edcc9986aa76b7418
--- /dev/null
+++ b/bigbluebutton-html5/imports/api/external-videos/server/eventHandlers.js
@@ -0,0 +1,8 @@
+import RedisPubSub from '/imports/startup/server/redis';
+import handleStartExternalVideo from './handlers/startExternalVideo';
+import handleStopExternalVideo from './handlers/stopExternalVideo';
+import handleUpdateExternalVideo from './handlers/updateExternalVideo';
+
+RedisPubSub.on('StartExternalVideoEvtMsg', handleStartExternalVideo);
+RedisPubSub.on('StopExternalVideoEvtMsg', handleStopExternalVideo);
+RedisPubSub.on('UpdateExternalVideoEvtMsg', handleUpdateExternalVideo);
diff --git a/bigbluebutton-html5/imports/api/external-videos/server/handlers/startExternalVideo.js b/bigbluebutton-html5/imports/api/external-videos/server/handlers/startExternalVideo.js
new file mode 100644
index 0000000000000000000000000000000000000000..1e84b493c7fc66db50a2b1495bf934e8f79161ea
--- /dev/null
+++ b/bigbluebutton-html5/imports/api/external-videos/server/handlers/startExternalVideo.js
@@ -0,0 +1,23 @@
+import { check } from 'meteor/check';
+import Logger from '/imports/startup/server/logger';
+import Users from '/imports/api/users';
+import Meetings from '/imports/api/meetings';
+
+export default function handleStartExternalVideo({ header, body }, meetingId) {
+  const { userId } = header;
+  check(body, Object);
+  check(meetingId, String);
+  check(userId, String);
+
+  const externalVideoUrl = body.externalVideoUrl;
+  const user = Users.findOne({ meetingId: meetingId, userId: userId })
+
+  if (user && user.presenter) {
+    try {
+      Meetings.update({ meetingId }, { $set: { externalVideoUrl } });
+      Logger.info(`User id=${userId} sharing an external video: ${externalVideoUrl} for meeting ${meetingId}`);
+    } catch (err) {
+      Logger.error(`Error on setting shared external video start in Meetings collection: ${err}`);
+    }
+  }
+}
diff --git a/bigbluebutton-html5/imports/api/external-videos/server/handlers/stopExternalVideo.js b/bigbluebutton-html5/imports/api/external-videos/server/handlers/stopExternalVideo.js
new file mode 100644
index 0000000000000000000000000000000000000000..06e22da1f417f2354b7a801279cfdc1496d905c3
--- /dev/null
+++ b/bigbluebutton-html5/imports/api/external-videos/server/handlers/stopExternalVideo.js
@@ -0,0 +1,18 @@
+import { check } from 'meteor/check';
+import Logger from '/imports/startup/server/logger';
+import Meetings from '/imports/api/meetings';
+
+export default function handleStopExternalVideo({ header, body }, meetingId) {
+  const { userId } = header;
+  check(body, Object);
+  check(meetingId, String);
+  check(userId, String);
+
+  try {
+    Logger.info(`External video stop sharing was initiated by:[${userId}] for meeting ${meetingId}`);
+    Meetings.update({ meetingId }, { $set: { externalVideoUrl: null } });
+  } catch (err) {
+    Logger.error(`Error on setting shared external video stop in Meetings collection: ${err}`);
+  }
+
+}
diff --git a/bigbluebutton-html5/imports/api/external-videos/server/handlers/updateExternalVideo.js b/bigbluebutton-html5/imports/api/external-videos/server/handlers/updateExternalVideo.js
new file mode 100644
index 0000000000000000000000000000000000000000..295171def7cf4574a32adb960c067b7c86cf3e11
--- /dev/null
+++ b/bigbluebutton-html5/imports/api/external-videos/server/handlers/updateExternalVideo.js
@@ -0,0 +1,24 @@
+import { check } from 'meteor/check';
+import Logger from '/imports/startup/server/logger';
+import Users from '/imports/api/users';
+import ExternalVideoStreamer from '/imports/api/external-videos/server/streamer';
+
+export default function handleUpdateExternalVideo({ header, body }, meetingId) {
+  const { userId } = header;
+  check(body, Object);
+  check(meetingId, String);
+  check(userId, String);
+
+  const user = Users.findOne({ meetingId: meetingId, userId: userId })
+
+  if (user && user.presenter) {
+    try {
+      Logger.info(`UpdateExternalVideoEvtMsg received for user ${userId} and meeting ${meetingId} event:${body.status}`);
+      ExternalVideoStreamer(meetingId).emit(body.status, { ...body, meetingId: meetingId, userId: userId });
+    } catch (err) {
+      Logger.error(`Error on setting shared external video update in Meetings collection: ${err}`);
+    }
+
+  }
+
+}
diff --git a/bigbluebutton-html5/imports/api/external-videos/server/index.js b/bigbluebutton-html5/imports/api/external-videos/server/index.js
index 2e0f48d559b9bc87827266a1d1225cc2af4c156f..b0d5d3295b0d8f62cda43093cb867e79f59ca357 100644
--- a/bigbluebutton-html5/imports/api/external-videos/server/index.js
+++ b/bigbluebutton-html5/imports/api/external-videos/server/index.js
@@ -1,2 +1,2 @@
 import './methods';
-
+import './eventHandlers';
diff --git a/bigbluebutton-html5/imports/api/external-videos/server/methods.js b/bigbluebutton-html5/imports/api/external-videos/server/methods.js
index 7df620cb7a18089285bfccefa51d2aaffbde4e50..10c056a2a9fe61561eb1cf896afcfacaf6ee6b71 100644
--- a/bigbluebutton-html5/imports/api/external-videos/server/methods.js
+++ b/bigbluebutton-html5/imports/api/external-videos/server/methods.js
@@ -1,11 +1,9 @@
 import { Meteor } from 'meteor/meteor';
 import startWatchingExternalVideo from './methods/startWatchingExternalVideo';
 import stopWatchingExternalVideo from './methods/stopWatchingExternalVideo';
-import initializeExternalVideo from './methods/initializeExternalVideo';
 import emitExternalVideoEvent from './methods/emitExternalVideoEvent';
 
 Meteor.methods({
-  initializeExternalVideo,
   startWatchingExternalVideo,
   stopWatchingExternalVideo,
   emitExternalVideoEvent,
diff --git a/bigbluebutton-html5/imports/api/external-videos/server/methods/destroyExternalVideo.js b/bigbluebutton-html5/imports/api/external-videos/server/methods/destroyExternalVideo.js
deleted file mode 100644
index 0be97c31985c1188cec4c5a9c228ed4eaf9a0352..0000000000000000000000000000000000000000
--- a/bigbluebutton-html5/imports/api/external-videos/server/methods/destroyExternalVideo.js
+++ /dev/null
@@ -1,11 +0,0 @@
-import { Meteor } from 'meteor/meteor';
-import Logger from '/imports/startup/server/logger';
-
-export default function destroyExternalVideo(meetingId) {
-  const streamName = `external-videos-${meetingId}`;
-
-  if (Meteor.StreamerCentral.instances[streamName]) {
-    Logger.info(`Destroying External Video streamer object for ${streamName}`);
-    delete Meteor.StreamerCentral.instances[streamName];
-  }
-}
diff --git a/bigbluebutton-html5/imports/api/external-videos/server/methods/emitExternalVideoEvent.js b/bigbluebutton-html5/imports/api/external-videos/server/methods/emitExternalVideoEvent.js
index 8c5d1535b8a926cf274e8d783769e4d069df5a58..29123dbd8628a0d914f54dd9290e8c5a1b055c3e 100644
--- a/bigbluebutton-html5/imports/api/external-videos/server/methods/emitExternalVideoEvent.js
+++ b/bigbluebutton-html5/imports/api/external-videos/server/methods/emitExternalVideoEvent.js
@@ -1,20 +1,36 @@
-import Users from '/imports/api/users';
+import { check } from 'meteor/check';
 import Logger from '/imports/startup/server/logger';
+import Users from '/imports/api/users';
+import RedisPubSub from '/imports/startup/server/redis';
 import { extractCredentials } from '/imports/api/common/server/helpers';
 
-export default function emitExternalVideoEvent(messageName, ...rest) {
-  const { meetingId, requesterUserId: userId } = extractCredentials(this.userId);
+export default function emitExternalVideoEvent(options) {
+  const REDIS_CONFIG = Meteor.settings.private.redis;
+  const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
+  const EVENT_NAME = 'UpdateExternalVideoPubMsg';
+
+  const { meetingId, requesterUserId } = extractCredentials(this.userId);
+
+  const { status, playerStatus } = options;
 
-  const user = Users.findOne({ userId, meetingId });
+  const user = Users.findOne({ meetingId, userId: requesterUserId })
 
   if (user && user.presenter) {
-    const streamerName = `external-videos-${meetingId}`;
-    const streamer = Meteor.StreamerCentral.instances[streamerName];
-
-    if (streamer) {
-      streamer.emit(messageName, ...rest);
-    } else {
-      Logger.error(`External Video Streamer not found for meetingId: ${meetingId} userId: ${userId}`);
-    }
+
+    check(status, String);
+    check(playerStatus, {
+      rate: Match.Maybe(Number),
+      time: Match.Maybe(Number),
+      state: Match.Maybe(Boolean),
+    });
+
+    let rate = playerStatus.rate || 0;
+    let time = playerStatus.time || 0;
+    let state = playerStatus.state || 0;
+    const payload = { status, rate, time, state };
+
+    Logger.debug(`User id=${requesterUserId} sending ${EVENT_NAME} event:${state} for meeting ${meetingId}`);
+    return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
+
   }
 }
diff --git a/bigbluebutton-html5/imports/api/external-videos/server/methods/initializeExternalVideo.js b/bigbluebutton-html5/imports/api/external-videos/server/methods/initializeExternalVideo.js
deleted file mode 100644
index 2106862339e5af945a28132c02ce05fb56c4d4e2..0000000000000000000000000000000000000000
--- a/bigbluebutton-html5/imports/api/external-videos/server/methods/initializeExternalVideo.js
+++ /dev/null
@@ -1,32 +0,0 @@
-import { extractCredentials } from '/imports/api/common/server/helpers';
-import Logger from '/imports/startup/server/logger';
-
-const allowRecentMessages = (eventName, message) => {
-  const {
-    userId,
-    meetingId,
-    time,
-    rate,
-    state,
-  } = message;
-
-  Logger.debug('ExternalVideo Streamer auth allowed', {
-    userId, meetingId, eventName, time, rate, state,
-  });
-  return true;
-};
-
-export default function initializeExternalVideo() {
-  const { meetingId } = extractCredentials(this.userId);
-
-  const streamName = `external-videos-${meetingId}`;
-  if (!Meteor.StreamerCentral.instances[streamName]) {
-    const streamer = new Meteor.Streamer(streamName);
-    streamer.allowRead('all');
-    streamer.allowWrite('none');
-    streamer.allowEmit(allowRecentMessages);
-    Logger.info(`Created External Video streamer for ${streamName}`);
-  } else {
-    Logger.debug('External Video streamer is already created', { streamName });
-  }
-}
diff --git a/bigbluebutton-html5/imports/api/external-videos/server/methods/startWatchingExternalVideo.js b/bigbluebutton-html5/imports/api/external-videos/server/methods/startWatchingExternalVideo.js
index df2b88abe3fc2d0ee78002adc9cc560aa6d15388..c33a42fd955b4e23f18b97e40521eb3ea13baa67 100644
--- a/bigbluebutton-html5/imports/api/external-videos/server/methods/startWatchingExternalVideo.js
+++ b/bigbluebutton-html5/imports/api/external-videos/server/methods/startWatchingExternalVideo.js
@@ -1,7 +1,5 @@
-import { Meteor } from 'meteor/meteor';
 import { check } from 'meteor/check';
 import Logger from '/imports/startup/server/logger';
-import Meetings from '/imports/api/meetings';
 import Users from '/imports/api/users';
 import RedisPubSub from '/imports/startup/server/redis';
 import { extractCredentials } from '/imports/api/common/server/helpers';
@@ -9,7 +7,7 @@ import { extractCredentials } from '/imports/api/common/server/helpers';
 export default function startWatchingExternalVideo(options) {
   const REDIS_CONFIG = Meteor.settings.private.redis;
   const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
-  const EVENT_NAME = 'StartExternalVideoMsg';
+  const EVENT_NAME = 'StartExternalVideoPubMsg';
 
   const { meetingId, requesterUserId: userId } = extractCredentials(this.userId);
   const { externalVideoUrl } = options;
@@ -19,20 +17,15 @@ export default function startWatchingExternalVideo(options) {
     check(userId, String);
     check(externalVideoUrl, String);
 
-    const user = Users.findOne({ meetingId, userId, presenter: true }, { presenter: 1 });
+    const user = Users.findOne({ meetingId, userId }, { presenter: 1 });
 
-    if (!user) {
-      Logger.error(`Only presenters are allowed to start external video for a meeting. meeting=${meetingId} userId=${userId}`);
-      return;
+    if (user && user.presenter) {
+      check(externalVideoUrl, String);
+      const payload = { externalVideoUrl };
+      Logger.debug(`User id=${userId} sending ${EVENT_NAME} url:${externalVideoUrl} for meeting ${meetingId}`);
+      return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, userId, payload);
     }
-
-    Meetings.update({ meetingId }, { $set: { externalVideoUrl } });
-
-    const payload = { externalVideoUrl };
-
-    Logger.info(`User id=${userId} sharing an external video: ${externalVideoUrl} for meeting ${meetingId}`);
-
-    return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, userId, payload);
+    Logger.error(`Only presenters are allowed to start external video for a meeting. meeting=${meetingId} userId=${userId}`);
   } catch (error) {
     Logger.error(`Error on sharing an external video: ${externalVideoUrl} ${error}`);
   }
diff --git a/bigbluebutton-html5/imports/api/external-videos/server/methods/stopWatchingExternalVideo.js b/bigbluebutton-html5/imports/api/external-videos/server/methods/stopWatchingExternalVideo.js
index c1fe37b6c7378c69f1bd747e6a28c4a8326e4d59..fba0d10e0cea2f8f519581c6396a0d26a42f5aa5 100644
--- a/bigbluebutton-html5/imports/api/external-videos/server/methods/stopWatchingExternalVideo.js
+++ b/bigbluebutton-html5/imports/api/external-videos/server/methods/stopWatchingExternalVideo.js
@@ -1,41 +1,22 @@
-import { Meteor } from 'meteor/meteor';
+import { check } from 'meteor/check';
 import Logger from '/imports/startup/server/logger';
-import Meetings from '/imports/api/meetings';
 import Users from '/imports/api/users';
-import RedisPubSub from '/imports/startup/server/redis';
+import stopWatchingExternalVideoSystemCall from '/imports/api/external-videos/server/methods/stopWatchingExternalVideoSystemCall';
 import { extractCredentials } from '/imports/api/common/server/helpers';
 
-export default function stopWatchingExternalVideo(options) {
-  const REDIS_CONFIG = Meteor.settings.private.redis;
-  const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
-  const EVENT_NAME = 'StopExternalVideoMsg';
-
-  const { meetingId, requesterUserId } = this.userId ? extractCredentials(this.userId) : options;
+export default function stopWatchingExternalVideo() {
+  const { meetingId, requesterUserId } = extractCredentials(this.userId);
 
   try {
     check(meetingId, String);
     check(requesterUserId, String);
 
-    const user = Users.findOne({
-      meetingId,
-      userId: requesterUserId,
-      presenter: true,
-    }, { presenter: 1 });
+    const user = Users.findOne({ meetingId, userId: requesterUserId });
 
-    if (this.userId && !user) {
-      Logger.error(`Only presenters are allowed to stop external video for a meeting. meeting=${meetingId} userId=${requesterUserId}`);
-      return;
+    if (user && user.presenter) {
+      // proceed and publish the event
+      stopWatchingExternalVideoSystemCall({ meetingId, requesterUserId });
     }
-
-    const meeting = Meetings.findOne({ meetingId });
-    if (!meeting || meeting.externalVideoUrl === null) return;
-
-    Meetings.update({ meetingId }, { $set: { externalVideoUrl: null } });
-    const payload = {};
-
-    Logger.info(`User id=${requesterUserId} stopped sharing an external video for meeting=${meetingId}`);
-
-    RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
   } catch (error) {
     Logger.error(`Error on stop sharing an external video for meeting=${meetingId} ${error}`);
   }
diff --git a/bigbluebutton-html5/imports/api/external-videos/server/methods/stopWatchingExternalVideoSystemCall.js b/bigbluebutton-html5/imports/api/external-videos/server/methods/stopWatchingExternalVideoSystemCall.js
new file mode 100644
index 0000000000000000000000000000000000000000..70d5a6561ba5d6555071451d6f79daa96352a99d
--- /dev/null
+++ b/bigbluebutton-html5/imports/api/external-videos/server/methods/stopWatchingExternalVideoSystemCall.js
@@ -0,0 +1,26 @@
+import { check } from 'meteor/check';
+import Logger from '/imports/startup/server/logger';
+import Meetings from '/imports/api/meetings';
+import RedisPubSub from '/imports/startup/server/redis';
+
+export default function stopWatchingExternalVideoSystemCall({ meetingId, requesterUserId }) {
+  const REDIS_CONFIG = Meteor.settings.private.redis;
+  const CHANNEL = REDIS_CONFIG.channels.toAkkaApps;
+  const EVENT_NAME = 'StopExternalVideoPubMsg';
+
+  try {
+    check(meetingId, String);
+    check(requesterUserId, String);
+
+    // check if there is ongoing video shared
+    const meeting = Meetings.findOne({ meetingId });
+    if (!meeting || meeting.externalVideoUrl === null) return;
+
+    Logger.info('ExternalVideo::stopWatchingExternalVideo was triggered ', { meetingId, requesterUserId });
+
+    const payload = { };
+    return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload);
+  } catch (error) {
+    Logger.error(`Error on stop sharing an external video for meeting=${meetingId} ${error}`);
+  }
+}
diff --git a/bigbluebutton-html5/imports/api/external-videos/server/streamer.js b/bigbluebutton-html5/imports/api/external-videos/server/streamer.js
new file mode 100644
index 0000000000000000000000000000000000000000..fbb34bf112b6f4241b9b4f130d86ac05cb1273fe
--- /dev/null
+++ b/bigbluebutton-html5/imports/api/external-videos/server/streamer.js
@@ -0,0 +1,45 @@
+import { Meteor } from 'meteor/meteor';
+import Logger from '/imports/startup/server/logger';
+
+const allowRecentMessages = (eventName, message) => {
+
+  const {
+    userId,
+    meetingId,
+    time,
+    rate,
+    state,
+  } = message;
+
+  Logger.debug(`ExternalVideo Streamer auth allowed userId: ${userId}, meetingId: ${meetingId}, event: ${eventName}, time: ${time} rate: ${rate}, state: ${state}`);
+  return true;
+};
+
+export function removeExternalVideoStreamer(meetingId) {
+  const streamName = `external-videos-${meetingId}`;
+
+  if (Meteor.StreamerCentral.instances[streamName]) {
+    Logger.info(`Destroying External Video streamer object for ${streamName}`);
+    delete Meteor.StreamerCentral.instances[streamName];
+  }
+}
+
+export function addExternalVideoStreamer(meetingId) {
+
+  const streamName = `external-videos-${meetingId}`;
+  if (!Meteor.StreamerCentral.instances[streamName]) {
+
+    const streamer = new Meteor.Streamer(streamName);
+    streamer.allowRead('all');
+    streamer.allowWrite('none');
+    streamer.allowEmit(allowRecentMessages);
+    Logger.info(`Created External Video streamer for ${streamName}`);
+  } else {
+    Logger.debug(`External Video streamer is already created for ${streamName}`);
+  }
+}
+
+export default function get(meetingId) {
+  const streamName = `external-videos-${meetingId}`;
+  return Meteor.StreamerCentral.instances[streamName];
+}
diff --git a/bigbluebutton-html5/imports/api/meetings/server/handlers/meetingDestruction.js b/bigbluebutton-html5/imports/api/meetings/server/handlers/meetingDestruction.js
index 31559458d55553e4c72e5bb77be1ad136ea1256a..a090f4d14f0f99d7f9c0044ccaa0b8f7fa06f45c 100644
--- a/bigbluebutton-html5/imports/api/meetings/server/handlers/meetingDestruction.js
+++ b/bigbluebutton-html5/imports/api/meetings/server/handlers/meetingDestruction.js
@@ -1,9 +1,9 @@
 import RedisPubSub from '/imports/startup/server/redis';
 import { check } from 'meteor/check';
 
-import destroyExternalVideo from '/imports/api/external-videos/server/methods/destroyExternalVideo';
 import { removeAnnotationsStreamer } from '/imports/api/annotations/server/streamer';
 import { removeCursorStreamer } from '/imports/api/cursor/server/streamer';
+import { removeExternalVideoStreamer } from '/imports/api/external-videos/server/streamer';
 
 export default function handleMeetingDestruction({ body }) {
   check(body, Object);
@@ -11,9 +11,9 @@ export default function handleMeetingDestruction({ body }) {
   check(meetingId, String);
 
   if (!process.env.BBB_HTML5_ROLE || process.env.BBB_HTML5_ROLE === 'frontend') {
-    destroyExternalVideo(meetingId);
     removeAnnotationsStreamer(meetingId);
     removeCursorStreamer(meetingId);
+    removeExternalVideoStreamer(meetingId);
   }
 
   return RedisPubSub.destroyMeetingQueue(meetingId);
diff --git a/bigbluebutton-html5/imports/api/meetings/server/modifiers/addMeeting.js b/bigbluebutton-html5/imports/api/meetings/server/modifiers/addMeeting.js
index 48fad9cb24dfa93aaa5d12e66db3f0d5585a9ced..a17bec63f2b851c3e0b2c3305371cda8fff4ce4d 100755
--- a/bigbluebutton-html5/imports/api/meetings/server/modifiers/addMeeting.js
+++ b/bigbluebutton-html5/imports/api/meetings/server/modifiers/addMeeting.js
@@ -10,6 +10,7 @@ import createNote from '/imports/api/note/server/methods/createNote';
 import createCaptions from '/imports/api/captions/server/methods/createCaptions';
 import { addAnnotationsStreamer } from '/imports/api/annotations/server/streamer';
 import { addCursorStreamer } from '/imports/api/cursor/server/streamer';
+import { addExternalVideoStreamer } from '/imports/api/external-videos/server/streamer';
 import BannedUsers from '/imports/api/users/server/store/bannedUsers';
 
 export default function addMeeting(meeting) {
@@ -156,7 +157,7 @@ export default function addMeeting(meeting) {
   if (!process.env.BBB_HTML5_ROLE || process.env.BBB_HTML5_ROLE === 'frontend') {
     addAnnotationsStreamer(meetingId);
     addCursorStreamer(meetingId);
-    // TODO add addExternalVideoStreamer(meetingId);
+    addExternalVideoStreamer(meetingId);
 
     // we don't want to fully process the create meeting message in frontend since it can lead to duplication of meetings in mongo.
     if (process.env.BBB_HTML5_ROLE === 'frontend') {
diff --git a/bigbluebutton-html5/imports/api/meetings/server/modifiers/meetingHasEnded.js b/bigbluebutton-html5/imports/api/meetings/server/modifiers/meetingHasEnded.js
index 4d916a4b363840068c07a11157a48819c6a8a944..4448dc1908b13e6b6023d0faedbaa3f1b5d27dd6 100755
--- a/bigbluebutton-html5/imports/api/meetings/server/modifiers/meetingHasEnded.js
+++ b/bigbluebutton-html5/imports/api/meetings/server/modifiers/meetingHasEnded.js
@@ -4,6 +4,7 @@ import Logger from '/imports/startup/server/logger';
 import BannedUsers from '/imports/api/users/server/store/bannedUsers';
 import { removeAnnotationsStreamer } from '/imports/api/annotations/server/streamer';
 import { removeCursorStreamer } from '/imports/api/cursor/server/streamer';
+import { removeExternalVideoStreamer } from '/imports/api/external-videos/server/streamer';
 
 import clearUsers from '/imports/api/users/server/modifiers/clearUsers';
 import clearUsersSettings from '/imports/api/users-settings/server/modifiers/clearUsersSettings';
@@ -34,7 +35,7 @@ export default function meetingHasEnded(meetingId) {
   if (!process.env.BBB_HTML5_ROLE || process.env.BBB_HTML5_ROLE === 'frontend') {
     removeAnnotationsStreamer(meetingId);
     removeCursorStreamer(meetingId);
-    // TODO add removeExternalVideoStreamer(meetingId);
+    removeExternalVideoStreamer(meetingId);
   }
 
   return Meetings.remove({ meetingId }, () => {
diff --git a/bigbluebutton-html5/imports/api/screenshare/server/handlers/screenshareStarted.js b/bigbluebutton-html5/imports/api/screenshare/server/handlers/screenshareStarted.js
index 503020326345ff4987d48745cf0f184fadab5ec3..09b45e4be0b83470988a06cb43e5e3818c7ab7d2 100644
--- a/bigbluebutton-html5/imports/api/screenshare/server/handlers/screenshareStarted.js
+++ b/bigbluebutton-html5/imports/api/screenshare/server/handlers/screenshareStarted.js
@@ -1,20 +1,17 @@
 import { check } from 'meteor/check';
 import Meetings from '/imports/api/meetings';
-import Users from '/imports/api/users';
 import addScreenshare from '../modifiers/addScreenshare';
 import Logger from '/imports/startup/server/logger';
-import stopWatchingExternalVideo from '/imports/api/external-videos/server/methods/stopWatchingExternalVideo';
+import stopWatchingExternalVideoSystemCall from '/imports/api/external-videos/server/methods/stopWatchingExternalVideoSystemCall';
 
 export default function handleScreenshareStarted({ body }, meetingId) {
   check(meetingId, String);
   check(body, Object);
 
   const meeting = Meetings.findOne({ meetingId });
-  const presenter = Users.findOne({ meetingId, presenter: true });
-  const presenterId = presenter && presenter.userId ? presenter.userId : 'system-screenshare-starting';
   if (meeting && meeting.externalVideoUrl) {
     Logger.info(`ScreenshareStarted: There is external video being shared. Stopping it due to presenter change, ${meeting.externalVideoUrl}`);
-    stopWatchingExternalVideo({ meetingId, requesterUserId: presenterId });
+    stopWatchingExternalVideoSystemCall({ meetingId, requesterUserId: 'system-screenshare-starting' });
   }
   return addScreenshare(meetingId, body);
 }
diff --git a/bigbluebutton-html5/imports/api/users/server/modifiers/changePresenter.js b/bigbluebutton-html5/imports/api/users/server/modifiers/changePresenter.js
index dcfe69f36a2ee951340ac0e242b945c7887e2583..baeaadbce4e20fa8353ed26572b50d7fb78bccbb 100755
--- a/bigbluebutton-html5/imports/api/users/server/modifiers/changePresenter.js
+++ b/bigbluebutton-html5/imports/api/users/server/modifiers/changePresenter.js
@@ -1,7 +1,7 @@
 import Logger from '/imports/startup/server/logger';
 import Users from '/imports/api/users';
 import Meetings from '/imports/api/meetings';
-import stopWatchingExternalVideo from '/imports/api/external-videos/server/methods/stopWatchingExternalVideo';
+import stopWatchingExternalVideoSystemCall from '/imports/api/external-videos/server/methods/stopWatchingExternalVideoSystemCall';
 
 export default function changePresenter(presenter, userId, meetingId, changedBy) {
   const selector = {
@@ -19,7 +19,7 @@ export default function changePresenter(presenter, userId, meetingId, changedBy)
     const meeting = Meetings.findOne({ meetingId });
     if (meeting && meeting.externalVideoUrl) {
       Logger.info(`ChangePresenter:There is external video being shared. Stopping it due to presenter change, ${meeting.externalVideoUrl}`);
-      stopWatchingExternalVideo({ meetingId, requesterUserId: userId });
+      stopWatchingExternalVideoSystemCall({ meetingId, requesterUserId: 'system-presenter-changed' });
     }
 
     const numberAffected = Users.update(selector, modifier);
diff --git a/bigbluebutton-html5/imports/api/users/server/modifiers/removeUser.js b/bigbluebutton-html5/imports/api/users/server/modifiers/removeUser.js
index ead54a6ae451dfc05f2b293d04a1878cc28bfe25..48e005873d09cb61a4126f9de45e1b1b48f0c3f3 100755
--- a/bigbluebutton-html5/imports/api/users/server/modifiers/removeUser.js
+++ b/bigbluebutton-html5/imports/api/users/server/modifiers/removeUser.js
@@ -2,7 +2,7 @@ import { check } from 'meteor/check';
 import Users from '/imports/api/users';
 import VideoStreams from '/imports/api/video-streams';
 import Logger from '/imports/startup/server/logger';
-import stopWatchingExternalVideo from '/imports/api/external-videos/server/methods/stopWatchingExternalVideo';
+import stopWatchingExternalVideoSystemCall from '/imports/api/external-videos/server/methods/stopWatchingExternalVideoSystemCall';
 import clearUserInfoForRequester from '/imports/api/users-infos/server/modifiers/clearUserInfoForRequester';
 import ClientConnections from '/imports/startup/server/ClientConnections';
 
@@ -22,7 +22,7 @@ export default function removeUser(meetingId, userId) {
   if (userToRemove) {
     const { presenter } = userToRemove;
     if (presenter) {
-      stopWatchingExternalVideo({ meetingId, requesterUserId: userId });
+      stopWatchingExternalVideoSystemCall({ meetingId, requesterUserId: 'system-presenter-was-removed' });
     }
   }
 
diff --git a/bigbluebutton-html5/imports/api/users/server/store/bannedUsers.js b/bigbluebutton-html5/imports/api/users/server/store/bannedUsers.js
index 30c7d8f792f62913deb63fa2534983482c82f74e..fff7e6e422e1dc6c162abcc903c3e76ec440d518 100644
--- a/bigbluebutton-html5/imports/api/users/server/store/bannedUsers.js
+++ b/bigbluebutton-html5/imports/api/users/server/store/bannedUsers.js
@@ -1,4 +1,4 @@
-import {check} from 'meteor/check';
+import { check } from 'meteor/check';
 import Logger from '/imports/startup/server/logger';
 
 class BannedUsers {
@@ -10,12 +10,12 @@ class BannedUsers {
       // types of queries for the users:
       // 1. meetingId
       // 2. meetingId, userId
-      this.store._ensureIndex({meetingId: 1, userId: 1});
+      this.store._ensureIndex({ meetingId: 1, userId: 1 });
     }
   }
 
   init(meetingId) {
-    Logger.debug('BannedUsers :: init', {meetingId});
+    Logger.debug('BannedUsers :: init', { meetingId });
 
     // if (!this.store[meetingId]) this.store[meetingId] = new Set();
   }
@@ -24,7 +24,7 @@ class BannedUsers {
     check(meetingId, String);
     check(externalId, String);
 
-    Logger.debug('BannedUsers :: add', {meetingId, externalId});
+    Logger.debug('BannedUsers :: add', { meetingId, externalId });
 
     const selector = {
       meetingId,
@@ -32,18 +32,18 @@ class BannedUsers {
     };
 
     const modifier = Object.assign( // TODO
-      {meetingId},
-      {externalId},
+      { meetingId },
+      { externalId },
     );
 
     try {
       const insertedId = this.store.upsert(selector, modifier);
 
       if (insertedId) {
-        Logger.info('BannedUsers :: Added to BannedUsers collection', {meetingId, externalId});
+        Logger.info('BannedUsers :: Added to BannedUsers collection', { meetingId, externalId });
       }
     } catch (err) {
-      Logger.error('BannedUsers :: Error on adding to BannedUsers collection', {meetingId, externalId, err});
+      Logger.error('BannedUsers :: Error on adding to BannedUsers collection', { meetingId, externalId, err });
     }
   }
 
@@ -56,9 +56,9 @@ class BannedUsers {
 
     try {
       this.store.remove(selector);
-      Logger.info('BannedUsers :: Removed meeting', {meetingId});
+      Logger.info('BannedUsers :: Removed meeting', { meetingId });
     } catch (err) {
-      Logger.error('BannedUsers :: Removing from collection', {err});
+      Logger.error('BannedUsers :: Removing from collection', { err });
     }
   }
 
@@ -66,9 +66,9 @@ class BannedUsers {
     check(meetingId, String);
     check(externalId, String);
 
-    Logger.info('BannedUsers :: has', {meetingId, externalId});
+    Logger.info('BannedUsers :: has', { meetingId, externalId });
 
-    return this.store.findOne({meetingId, externalId});
+    return this.store.findOne({ meetingId, externalId });
   }
 }
 
diff --git a/bigbluebutton-html5/imports/ui/components/external-video-player/service.js b/bigbluebutton-html5/imports/ui/components/external-video-player/service.js
index 9de4de3c7aa4f426542756442ce902a89c1335ec..153ba4e8047d727b56f0178e60dc123db5a265a7 100644
--- a/bigbluebutton-html5/imports/ui/components/external-video-player/service.js
+++ b/bigbluebutton-html5/imports/ui/components/external-video-player/service.js
@@ -1,7 +1,5 @@
 import Meetings from '/imports/api/meetings';
-import Users from '/imports/api/users';
 import Auth from '/imports/ui/services/auth';
-import Logger from '/imports/startup/client/logger';
 
 import { getStreamer } from '/imports/api/external-videos';
 import { makeCall } from '/imports/ui/services/api';
@@ -28,11 +26,24 @@ const stopWatching = () => {
   makeCall('stopWatchingExternalVideo');
 };
 
+let lastMessage = null;
+
 const sendMessage = (event, data) => {
-  const meetingId = Auth.meetingID;
-  const userId = Auth.userID;
 
-  makeCall('emitExternalVideoEvent', event, { ...data, meetingId, userId });
+  // don't re-send repeated update messages
+  if (lastMessage && lastMessage.event === event
+    && event === 'playerUpdate' && lastMessage.time === data.time) {
+    return;
+  }
+
+  // don't register to redis a viewer joined message
+  if (event === 'viewerJoined') {
+    return;
+  }
+
+  lastMessage = { ...data, event };
+
+  makeCall('emitExternalVideoEvent', { status: event, playerStatus: data });
 };
 
 const onMessage = (message, func) => {