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 1842b9a24ed18138c39b045629f78ced7484775f..57dadfa39db2bee69c9cf7db87142069323db572 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 @@ -331,6 +331,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/running/MeetingActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala index c0a1735a92841210e0d1f19457236444c06383ef..28c995302079898028d952b39bf696a664056dda 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 @@ -115,6 +116,7 @@ class MeetingActor( val captionApp2x = new CaptionApp2x val sharedNotesApp2x = new SharedNotesApp2x val chatApp2x = new ChatApp2x + val externalVideoApp2x = new ExternalVideoApp2x val usersApp = new UsersApp(liveMeeting, outGW, eventBus) val groupChatApp = new GroupChatHdlrs val presentationPodsApp = new PresentationPodHdlrs @@ -491,6 +493,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 9c4888d74a5ab7bb6cf1964e998c3838fda8349e..29d52d6c14ac27de7b909f0ba934512320434d99 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 @@ -59,13 +59,13 @@ class FromAkkaAppsMsgSenderActor(msgSender: MessageSender) // Whiteboard case SendWhiteboardAnnotationEvtMsg.NAME => - msgSender.send(fromAkkaAppsWbRedisChannel, json) + msgSender.send("from-akka-apps-frontend-redis-channel", json) case SendCursorPositionEvtMsg.NAME => - msgSender.send(fromAkkaAppsWbRedisChannel, json) + msgSender.send("from-akka-apps-frontend-redis-channel", json) case ClearWhiteboardEvtMsg.NAME => - msgSender.send(fromAkkaAppsWbRedisChannel, json) + msgSender.send("from-akka-apps-frontend-redis-channel", json) case UndoWhiteboardEvtMsg.NAME => - msgSender.send(fromAkkaAppsWbRedisChannel, json) + msgSender.send("from-akka-apps-frontend-redis-channel", json) // Chat case SendPublicMessageEvtMsg.NAME => @@ -107,6 +107,25 @@ class FromAkkaAppsMsgSenderActor(msgSender: MessageSender) case UserRespondedToPollRecordMsg.NAME => //================================================================== + case ValidateAuthTokenRespMsg.NAME => + msgSender.send("from-akka-apps-frontend-redis-channel", json) + + // Message duplicated for frontend and backend processes + case MeetingCreatedEvtMsg.NAME => + msgSender.send(fromAkkaAppsRedisChannel, json) + msgSender.send("from-akka-apps-frontend-redis-channel", json) + + case MeetingEndingEvtMsg.NAME => + msgSender.send(fromAkkaAppsRedisChannel, json) + msgSender.send("from-akka-apps-frontend-redis-channel", json) + + case MeetingDestroyedEvtMsg.NAME => + 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/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/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..c58cd99bf4c773439b3f506d1e7efe869faaf374 --- /dev/null +++ b/bigbluebutton-html5/imports/api/external-videos/server/handlers/startExternalVideo.js @@ -0,0 +1,19 @@ +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) { + Logger.info(`User id=${userId} sharing an external video: ${externalVideoUrl} for meeting ${meetingId}`); + Meetings.update({ meetingId }, { $set: { externalVideoUrl } }); + } +} 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..aea39acaf32dfd2007fe3bb1d36af875cfe2bbe5 --- /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 Users from '/imports/api/users'; +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); + + const user = Users.findOne({ meetingId: meetingId, userId: userId }) + + if (user && user.presenter) { + Logger.info(`User id=${userId} stop sharing an external video for meeting ${meetingId}`); + Meetings.update({ meetingId }, { $set: { externalVideoUrl: null } }); + } +} 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..0fe792d8a97e98749424dcc7003bad4d59048f09 --- /dev/null +++ b/bigbluebutton-html5/imports/api/external-videos/server/handlers/updateExternalVideo.js @@ -0,0 +1,19 @@ +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) { + Logger.info(`UpdateExternalVideoEvtMsg received for user ${userId} and meeting ${meetingId} event:${body.status}`); + ExternalVideoStreamer(meetingId).emit(body.status, {...body, meetingId: meetingId, userId: userId } ); + } + +} 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/emitExternalVideoEvent.js b/bigbluebutton-html5/imports/api/external-videos/server/methods/emitExternalVideoEvent.js index 8c5d1535b8a926cf274e8d783769e4d069df5a58..1771741e116fe232fce6dc8ebfd84250dbf2e7fb 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: 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/startWatchingExternalVideo.js b/bigbluebutton-html5/imports/api/external-videos/server/methods/startWatchingExternalVideo.js index e157b7a0b3e0ede194b9d16fa76152a59633bc1b..36e8962d6a0473aae3c6d538230d8ba746286112 100644 --- a/bigbluebutton-html5/imports/api/external-videos/server/methods/startWatchingExternalVideo.js +++ b/bigbluebutton-html5/imports/api/external-videos/server/methods/startWatchingExternalVideo.js @@ -1,25 +1,24 @@ -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'; 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 } = extractCredentials(this.userId); const { externalVideoUrl } = options; - check(externalVideoUrl, String); + const user = Users.findOne({ meetingId: meetingId, userId: requesterUserId }) - Meetings.update({ meetingId }, { $set: { externalVideoUrl } }); + if (user && user.presenter) { + check(externalVideoUrl, String); + const payload = { externalVideoUrl }; + Logger.debug(`User id=${requesterUserId} sending ${EVENT_NAME} url:${externalVideoUrl} for meeting ${meetingId}`); + return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload); + } - const payload = { externalVideoUrl }; - - Logger.info(`User id=${requesterUserId} sharing an external video: ${externalVideoUrl} for meeting ${meetingId}`); - - return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload); } 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 60384b82f772230d68d5c5399008f7de9e780610..e57b9d7bf3dad55061371631f0b915a950fc3a82 100644 --- a/bigbluebutton-html5/imports/api/external-videos/server/methods/stopWatchingExternalVideo.js +++ b/bigbluebutton-html5/imports/api/external-videos/server/methods/stopWatchingExternalVideo.js @@ -1,27 +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 { 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 EVENT_NAME = 'StopExternalVideoPubMsg'; - if (this.userId) { - options = extractCredentials(this.userId); - } - - const { meetingId, requesterUserId } = options; + const { meetingId, requesterUserId } = extractCredentials(this.userId); - const meeting = Meetings.findOne({ meetingId }); - if (!meeting || meeting.externalVideoUrl === null) return; + const user = Users.findOne({ meetingId: meetingId, userId: requesterUserId }) - Meetings.update({ meetingId }, { $set: { externalVideoUrl: null } }); - const payload = {}; - - Logger.info(`User id=${requesterUserId} stopped sharing an external video for meeting=${meetingId}`); + if (user && user.presenter) { + const payload = { }; + Logger.debug(`User id=${requesterUserId} sending ${EVENT_NAME} for meeting ${meetingId}`); + return RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload); + } - RedisPubSub.publishUserMessage(CHANNEL, EVENT_NAME, meetingId, requesterUserId, payload); } 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 cacfd42b057a6c2bcace371737d19bf4c9e0f296..61c1863f55bcb9aa1b831efe13ba7fcc5f145ca6 100644 --- a/bigbluebutton-html5/imports/api/meetings/server/handlers/meetingDestruction.js +++ b/bigbluebutton-html5/imports/api/meetings/server/handlers/meetingDestruction.js @@ -1,18 +1,21 @@ 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); const { meetingId } = body; check(meetingId, String); - destroyExternalVideo(meetingId); - removeAnnotationsStreamer(meetingId); - removeCursorStreamer(meetingId); + + if (!process.env.METEOR_ROLE || process.env.METEOR_ROLE === 'frontend') { + 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 7f15e961f29f8ed9e213b22aedb5296603d7dc15..698eabfca07f3f9238e6171bdf14d87e4220c70e 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) { @@ -198,8 +199,12 @@ export default function addMeeting(meeting) { ...recordProp, }, cbRecord); - addAnnotationsStreamer(meetingId); - addCursorStreamer(meetingId); + + if (!process.env.METEOR_ROLE || process.env.METEOR_ROLE === 'frontend') { + addAnnotationsStreamer(meetingId); + addCursorStreamer(meetingId); + addExternalVideoStreamer(meetingId); + } return Meetings.upsert(selector, modifier, cb); } diff --git a/bigbluebutton-html5/imports/api/meetings/server/modifiers/meetingHasEnded.js b/bigbluebutton-html5/imports/api/meetings/server/modifiers/meetingHasEnded.js index 3b9efcbb1698899b0e0347f2f196d546067e882a..f736e5d7aa130b7a8388c8db793c87eda8e2a93b 100755 --- a/bigbluebutton-html5/imports/api/meetings/server/modifiers/meetingHasEnded.js +++ b/bigbluebutton-html5/imports/api/meetings/server/modifiers/meetingHasEnded.js @@ -3,6 +3,8 @@ import Logger from '/imports/startup/server/logger'; 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 BannedUsers from '/imports/api/users/server/store/bannedUsers'; import clearUsers from '/imports/api/users/server/modifiers/clearUsers'; import clearUsersSettings from '/imports/api/users-settings/server/modifiers/clearUsersSettings'; @@ -23,9 +25,15 @@ import clearRecordMeeting from './clearRecordMeeting'; import clearVoiceCallStates from '/imports/api/voice-call-states/server/modifiers/clearVoiceCallStates'; import clearVideoStreams from '/imports/api/video-streams/server/modifiers/clearVideoStreams'; + export default function meetingHasEnded(meetingId) { - removeAnnotationsStreamer(meetingId); - removeCursorStreamer(meetingId); + + + if (!process.env.METEOR_ROLE || process.env.METEOR_ROLE === 'frontend') { + removeAnnotationsStreamer(meetingId); + removeCursorStreamer(meetingId); + removeExternalVideoStreamer(meetingId); + } return Meetings.remove({ meetingId }, () => { clearCaptions(meetingId); @@ -46,6 +54,7 @@ export default function meetingHasEnded(meetingId) { clearRecordMeeting(meetingId); clearVoiceCallStates(meetingId); clearVideoStreams(meetingId); + BannedUsers.delete(meetingId); return Logger.info(`Cleared Meetings with id ${meetingId}`); }); diff --git a/bigbluebutton-html5/imports/api/users/server/handlers/validateAuthToken.js b/bigbluebutton-html5/imports/api/users/server/handlers/validateAuthToken.js index 010874c9af1ad9b196390daf6ba9592a5a61a592..1d02b8942a112377461d61cf232757c302c70e9e 100644 --- a/bigbluebutton-html5/imports/api/users/server/handlers/validateAuthToken.js +++ b/bigbluebutton-html5/imports/api/users/server/handlers/validateAuthToken.js @@ -28,6 +28,9 @@ export default function handleValidateAuthToken({ body }, meetingId) { check(waitForApproval, Boolean); const pendingAuths = pendingAuthenticationsStore.take(meetingId, userId, authToken); + + Logger.info(`PendingAuths length [${pendingAuths.length}]`); + if (pendingAuths.length === 0) return; if (!valid) { pendingAuths.forEach( diff --git a/bigbluebutton-html5/imports/api/users/server/store/bannedUsers.js b/bigbluebutton-html5/imports/api/users/server/store/bannedUsers.js index 5355e3d1dcec3bf441259010f1d0ac484201cef3..dfe0745441cf25f7bc2f1b174e2610ea228684f8 100644 --- a/bigbluebutton-html5/imports/api/users/server/store/bannedUsers.js +++ b/bigbluebutton-html5/imports/api/users/server/store/bannedUsers.js @@ -1,34 +1,88 @@ +import { check } from 'meteor/check'; import Logger from '/imports/startup/server/logger'; class BannedUsers { constructor() { Logger.debug('BannedUsers :: Initializing'); - this.store = {}; + this.store = new Mongo.Collection('users-banned'); + + if (Meteor.isServer) { + // types of queries for the users: + // 1. meetingId + // 2. meetingId, userId + this.store._ensureIndex({ meetingId: 1, userId: 1 }); + } } init(meetingId) { Logger.debug('BannedUsers :: init', meetingId); - if (!this.store[meetingId]) this.store[meetingId] = new Set(); + //if (!this.store[meetingId]) this.store[meetingId] = new Set(); } add(meetingId, externalId) { + + check(meetingId, String); + check(externalId, String); + Logger.debug('BannedUsers :: add', { meetingId, externalId }); - if (!this.store[meetingId]) this.store[meetingId] = new Set(); - this.store[meetingId].add(externalId); + const selector = { + meetingId, + externalId: externalId, + }; + + const modifier = Object.assign( + { meetingId }, + { externalId: externalId }, + ); + + const cb = (err, numChanged) => { + if (err != null) { + return Logger.error(`Adding { meetingId, externalId } to BannedUsers collection`); + } + + const { insertedId } = numChanged; + if (insertedId) { + return Logger.info(`Added { meetingId, externalId } to BannedUsers collection`); + } + + return Logger.info(`Upserted { meetingId, externalId } to BannedUsers collection`); + }; + + return this.store.upsert(selector, modifier, cb); + } delete(meetingId) { - Logger.debug('BannedUsers :: delete', meetingId); - delete this.store[meetingId]; + + check(meetingId, String); + + const selector = { + meetingId: meetingId + }; + + const cb = (err) => { + if (err) { + return Logger.error(`Removing BannedUsers from collection: ${err}`); + } + + return Logger.info(`Removed BannedUsers meetingId=${meetingId}`); + }; + + return this.store.remove(selector, cb); + } has(meetingId, externalId) { - Logger.debug('BannedUsers :: has', { meetingId, externalId }); - if (!this.store[meetingId]) this.store[meetingId] = new Set(); - return this.store[meetingId].has(externalId); + check(meetingId, String); + check(externalId, String); + + Logger.info('BannedUsers :: has', { meetingId, externalId }); + + return this.store.findOne({ meetingId: meetingId, externalId: externalId }) + } } diff --git a/bigbluebutton-html5/imports/startup/server/index.js b/bigbluebutton-html5/imports/startup/server/index.js index 63c4d9008b4649b1c45f468ace07e4fff9650bc2..7ea0d33ee6560f9beae640a4e7673b09e6ab8bb3 100755 --- a/bigbluebutton-html5/imports/startup/server/index.js +++ b/bigbluebutton-html5/imports/startup/server/index.js @@ -74,29 +74,31 @@ Meteor.startup(() => { setMinBrowserVersions(); - Meteor.setInterval(() => { - const currentTime = Date.now(); - Logger.info('Checking for inactive users'); - const users = Users.find({ - connectionStatus: 'online', - clientType: 'HTML5', - lastPing: { - $lt: (currentTime - INTERVAL_TIME), // get user who has not pinged in the last 10 seconds - }, - loginTime: { - $lt: (currentTime - INTERVAL_TIME), - }, - }).fetch(); - if (!users.length) return Logger.info('No inactive users'); - Logger.info('Removing inactive users'); - users.forEach((user) => { - Logger.info(`Detected inactive user, userId:${user.userId}, meetingId:${user.meetingId}`); - return userLeaving(user.meetingId, user.userId, user.connectionId); - }); - return Logger.info('All inactive users have been removed'); - }, INTERVAL_TIME); + if (!process.env.METEOR_ROLE || process.env.METEOR_ROLE === 'backend') { + Meteor.setInterval(() => { + const currentTime = Date.now(); + Logger.info('Checking for inactive users'); + const users = Users.find({ + connectionStatus: 'online', + clientType: 'HTML5', + lastPing: { + $lt: (currentTime - INTERVAL_TIME), // get user who has not pinged in the last 10 seconds + }, + loginTime: { + $lt: (currentTime - INTERVAL_TIME), + }, + }).fetch(); + if (!users.length) return Logger.info('No inactive users'); + Logger.info('Removing inactive users'); + users.forEach((user) => { + Logger.info(`Detected inactive user, userId:${user.userId}, meetingId:${user.meetingId}`); + return userLeaving(user.meetingId, user.userId, user.connectionId); + }); + return Logger.info('All inactive users have been removed'); + }, INTERVAL_TIME); + } - Logger.warn(`SERVER STARTED.\nENV=${env},\nnodejs version=${process.version}\nCDN=${CDN_URL}\n`, APP_CONFIG); + Logger.warn(`SERVER STARTED.\nENV=${env},\nnodejs version=${process.version}\nMETEOR_ROLE=${process.env.METEOR_ROLE}\nCDN=${CDN_URL}\n`, APP_CONFIG); }); WebApp.connectHandlers.use('/check', (req, res) => { diff --git a/bigbluebutton-html5/imports/startup/server/redis.js b/bigbluebutton-html5/imports/startup/server/redis.js index 739164c47d70326e5719acbc3ea0e85e1d2e1fe1..812a428332d949e9ed6a3dbe8548f37bfb23ed8b 100755 --- a/bigbluebutton-html5/imports/startup/server/redis.js +++ b/bigbluebutton-html5/imports/startup/server/redis.js @@ -134,11 +134,27 @@ class RedisPubSub { const channelsToSubscribe = this.config.subscribeTo; - channelsToSubscribe.forEach((channel) => { + /*channelsToSubscribe.forEach((channel) => { this.sub.psubscribe(channel); }); - this.debug(`Subscribed to '${channelsToSubscribe}'`); + */ + + switch (process.env.METEOR_ROLE) { + case 'frontend': + this.sub.psubscribe('from-akka-apps-frontend-redis-channel'); + this.debug(`Subscribed to from-akka-apps-frontend-redis-channel`); + break; + default: + const channelsToSubscribe = this.config.subscribeTo; + channelsToSubscribe.forEach((channel) => { + this.sub.psubscribe(channel); + this.debug(`Subscribed to '${channel}'`) + }); + + break; + } + } updateConfig(config) { 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 66bd3ea7ccc3d2fe70c51db90ce3a4c454af614e..bf82da4fee6dadd8d57b4db8a616cab0090d62b1 100644 --- a/bigbluebutton-html5/imports/ui/components/external-video-player/service.js +++ b/bigbluebutton-html5/imports/ui/components/external-video-player/service.js @@ -28,14 +28,31 @@ 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 }; + + //const meetingId = Auth.meetingID; + //const userId = Auth.userID; + + makeCall('emitExternalVideoEvent', { status: event, playerStatus: data }); }; const onMessage = (message, func) => { + const streamer = getStreamer(Auth.meetingID); streamer.on(message, func); }; diff --git a/bigbluebutton-html5/private/config/settings.yml b/bigbluebutton-html5/private/config/settings.yml index 78672e2c02c6cd73c24df763a9223cd0b5b245d2..678ae94945baccd5e86f69437d3343ac8a4a5062 100755 --- a/bigbluebutton-html5/private/config/settings.yml +++ b/bigbluebutton-html5/private/config/settings.yml @@ -487,7 +487,7 @@ private: toThirdParty: to-third-party-redis-channel subscribeTo: - to-html5-redis-channel - - from-akka-apps-* + - from-akka-apps-[^f]* - from-third-party-redis-channel - from-etherpad-redis-channel async: