Merge branch 'master' into refactor-meetings-api

* master: (22 commits)
  - fix check for breakout room  - insert breakout metadata into events.xml  - delete breakout metadata from redis
  Rename meteor method on the client
  HTML5 - fixed linebreak with lint
  HTML5 - fixes fullscreen toggle to un-fullscreen
  Monitoring for number of user is not triggered for breakout rooms.
  Updated bbb-record rules to detect recording directories.
  Added new property redirectOnJoin to CreateBreakoutRoomsRequest.
  Added redirect property to RequestBreakoutJoinURL.
  Fix handler for 'get_whiteboard_shapes_reply' not calling the cb
  Store breakout meeting info in redis under "meeting:breakout" key instead of "meeting:info"
  Using blank-presentation.pdf as default breakout room presentation when no current presentation is set to the parent room.
  Re-enable recording option for breakout rooms.
  Store breakout room externalMeetingID to be used for generating join URL.
  Store breakout room sequence in akka-apps and bbb-web.
  Revert replacing the deprecated shaHex DigestUtils by sha1Hex and updated API controller.
  Deleted unused imports from bigbluebutton-apps and bigbluebutton-web
  Replace the deprecated shaHex DigestUtils by sha1Hex.
  Send sequence number when creating breakout rooms and correctly generate a unique externalMeetingId for breakout rooms.
  Breakout rooms are now sorted by their sequence number.
  Added sequence property breakout room.
  ...
This commit is contained in:
Oswaldo Acauan 2016-10-24 11:14:14 +00:00
commit e1421566be
77 changed files with 1152 additions and 1014 deletions

View File

@ -55,7 +55,7 @@ libraryDependencies ++= {
"org.pegdown" % "pegdown" % "1.4.0",
"junit" % "junit" % "4.11",
"com.etaty.rediscala" %% "rediscala" % "1.4.0",
"commons-codec" % "commons-codec" % "1.8",
"commons-codec" % "commons-codec" % "1.10",
"joda-time" % "joda-time" % "2.3",
"com.google.code.gson" % "gson" % "2.5",
"redis.clients" % "jedis" % "2.7.2",

View File

@ -35,7 +35,7 @@ public class MeetingMessageReceiver implements MessageHandler {
public void handleMessage(String pattern, String channel, String message) {
if (channel.equalsIgnoreCase(MessagingConstants.TO_MEETING_CHANNEL)) {
System.out.println("Meeting message: " + channel + " " + message);
System.out.println("Meeting message: " + channel + " " + message);
JsonParser parser = new JsonParser();
JsonObject obj = (JsonObject) parser.parse(message);
@ -45,8 +45,8 @@ public class MeetingMessageReceiver implements MessageHandler {
String messageName = header.get("name").getAsString();
if (CreateMeetingRequest.NAME.equals(messageName)) {
Gson gson = new Gson();
CreateMeetingRequest msg = gson.fromJson(message,
CreateMeetingRequest.class);
CreateMeetingRequest msg = gson.fromJson(message,
CreateMeetingRequest.class);
bbbGW.handleBigBlueButtonMessage(msg);
}
}
@ -59,49 +59,50 @@ public class MeetingMessageReceiver implements MessageHandler {
EndMeetingMessage emm = (EndMeetingMessage) msg;
bbbGW.endMeeting(emm.meetingId);
} else if (msg instanceof RegisterUserMessage) {
RegisterUserMessage emm = (RegisterUserMessage) msg;
bbbGW.registerUser(emm.meetingID, emm.internalUserId, emm.fullname, emm.role, emm.externUserID, emm.authToken, emm.avatarURL);
RegisterUserMessage rum = (RegisterUserMessage) msg;
bbbGW.registerUser(rum.meetingID, rum.internalUserId, rum.fullname, rum.role, rum.externUserID, rum.authToken, rum.avatarURL);
} else if (msg instanceof DestroyMeetingMessage) {
DestroyMeetingMessage emm = (DestroyMeetingMessage) msg;
bbbGW.destroyMeeting(emm.meetingId);
DestroyMeetingMessage dmm = (DestroyMeetingMessage) msg;
bbbGW.destroyMeeting(dmm.meetingId);
} else if (msg instanceof ValidateAuthTokenMessage) {
ValidateAuthTokenMessage emm = (ValidateAuthTokenMessage) msg;
ValidateAuthTokenMessage vam = (ValidateAuthTokenMessage) msg;
String sessionId = "tobeimplemented";
bbbGW.validateAuthToken(emm.meetingId, emm.userId, emm.token, emm.replyTo, sessionId);
bbbGW.validateAuthToken(vam.meetingId, vam.userId, vam.token, vam.replyTo, sessionId);
} else if (msg instanceof UserConnectedToGlobalAudio) {
UserConnectedToGlobalAudio emm = (UserConnectedToGlobalAudio) msg;
UserConnectedToGlobalAudio ustga = (UserConnectedToGlobalAudio) msg;
Map<String, Object> logData = new HashMap<String, Object>();
logData.put("voiceConf", emm.voiceConf);
logData.put("userId", emm.userid);
logData.put("username", emm.name);
logData.put("voiceConf", ustga.voiceConf);
logData.put("userId", ustga.userid);
logData.put("username", ustga.name);
logData.put("event", "user_connected_to_global_audio");
logData.put("description", "User connected to global audio.");
/*
Gson gson = new Gson();
String logStr = gson.toJson(logData);
//System.out.println("User connected to global audio: data={}", logStr);
bbbGW.userConnectedToGlobalAudio(emm.voiceConf, emm.userid, emm.name);
System.out.println("User connected to global audio: data={}", logStr);
*/
bbbGW.userConnectedToGlobalAudio(ustga.voiceConf, ustga.userid, ustga.name);
} else if (msg instanceof UserDisconnectedFromGlobalAudio) {
UserDisconnectedFromGlobalAudio emm = (UserDisconnectedFromGlobalAudio) msg;
UserDisconnectedFromGlobalAudio udfga = (UserDisconnectedFromGlobalAudio) msg;
Map<String, Object> logData = new HashMap<String, Object>();
logData.put("voiceConf", emm.voiceConf);
logData.put("userId", emm.userid);
logData.put("username", emm.name);
logData.put("voiceConf", udfga.voiceConf);
logData.put("userId", udfga.userid);
logData.put("username", udfga.name);
logData.put("event", "user_disconnected_from_global_audio");
logData.put("description", "User disconnected from global audio.");
/*
Gson gson = new Gson();
String logStr = gson.toJson(logData);
//System.out.println("User disconnected from global audio: data={}", logStr);
bbbGW.userDisconnectedFromGlobalAudio(emm.voiceConf, emm.userid, emm.name);
System.out.println("User disconnected from global audio: data={}", logStr);
*/
bbbGW.userDisconnectedFromGlobalAudio(udfga.voiceConf, udfga.userid, udfga.name);
}
else if (msg instanceof GetAllMeetingsRequest) {
GetAllMeetingsRequest emm = (GetAllMeetingsRequest) msg;
GetAllMeetingsRequest gamr = (GetAllMeetingsRequest) msg;
bbbGW.getAllMeetings("no_need_of_a_meeting_id");
} else {
System.out.println("Unknown message: [" + message + "]");
@ -126,8 +127,8 @@ public class MeetingMessageReceiver implements MessageHandler {
if (msg != null) {
if (msg instanceof KeepAliveMessage) {
KeepAliveMessage emm = (KeepAliveMessage) msg;
bbbGW.isAliveAudit(emm.keepAliveId);
KeepAliveMessage kam = (KeepAliveMessage) msg;
bbbGW.isAliveAudit(kam.keepAliveId);
}
} else {
System.out.println("Unknown message: [" + message + "]");

View File

@ -11,13 +11,13 @@ import org.bigbluebutton.SystemConfiguration
object BigBlueButtonActor extends SystemConfiguration {
def props(system: ActorSystem,
eventBus: IncomingEventBus,
outGW: OutMessageGateway): Props =
eventBus: IncomingEventBus,
outGW: OutMessageGateway): Props =
Props(classOf[BigBlueButtonActor], system, eventBus, outGW)
}
class BigBlueButtonActor(val system: ActorSystem,
eventBus: IncomingEventBus, outGW: OutMessageGateway) extends Actor with ActorLogging {
eventBus: IncomingEventBus, outGW: OutMessageGateway) extends Actor with ActorLogging {
implicit def executionContext = system.dispatcher
implicit val timeout = Timeout(5 seconds)
@ -25,19 +25,19 @@ class BigBlueButtonActor(val system: ActorSystem,
private var meetings = new collection.immutable.HashMap[String, RunningMeeting]
def receive = {
case msg: CreateMeeting => handleCreateMeeting(msg)
case msg: DestroyMeeting => handleDestroyMeeting(msg)
case msg: KeepAliveMessage => handleKeepAliveMessage(msg)
case msg: PubSubPing => handlePubSubPingMessage(msg)
case msg: ValidateAuthToken => handleValidateAuthToken(msg)
case msg: GetAllMeetingsRequest => handleGetAllMeetingsRequest(msg)
case msg: UserJoinedVoiceConfMessage => handleUserJoinedVoiceConfMessage(msg)
case msg: UserLeftVoiceConfMessage => handleUserLeftVoiceConfMessage(msg)
case msg: UserLockedInVoiceConfMessage => handleUserLockedInVoiceConfMessage(msg)
case msg: UserMutedInVoiceConfMessage => handleUserMutedInVoiceConfMessage(msg)
case msg: UserTalkingInVoiceConfMessage => handleUserTalkingInVoiceConfMessage(msg)
case msg: CreateMeeting => handleCreateMeeting(msg)
case msg: DestroyMeeting => handleDestroyMeeting(msg)
case msg: KeepAliveMessage => handleKeepAliveMessage(msg)
case msg: PubSubPing => handlePubSubPingMessage(msg)
case msg: ValidateAuthToken => handleValidateAuthToken(msg)
case msg: GetAllMeetingsRequest => handleGetAllMeetingsRequest(msg)
case msg: UserJoinedVoiceConfMessage => handleUserJoinedVoiceConfMessage(msg)
case msg: UserLeftVoiceConfMessage => handleUserLeftVoiceConfMessage(msg)
case msg: UserLockedInVoiceConfMessage => handleUserLockedInVoiceConfMessage(msg)
case msg: UserMutedInVoiceConfMessage => handleUserMutedInVoiceConfMessage(msg)
case msg: UserTalkingInVoiceConfMessage => handleUserTalkingInVoiceConfMessage(msg)
case msg: VoiceConfRecordingStartedMessage => handleVoiceConfRecordingStartedMessage(msg)
case _ => // do nothing
case _ => // do nothing
}
private def findMeetingWithVoiceConfId(voiceConfId: String): Option[RunningMeeting] = {
@ -117,9 +117,9 @@ class BigBlueButtonActor(val system: ActorSystem,
meetings -= msg.meetingID
log.info("Kick everyone out on meetingId={}", msg.meetingID)
if (m.mProps.isBreakout) {
log.info("Informing parent meeting {} that a breakout room has been ended {}", m.mProps.externalMeetingID, m.mProps.meetingID)
eventBus.publish(BigBlueButtonEvent(m.mProps.externalMeetingID,
BreakoutRoomEnded(m.mProps.externalMeetingID, m.mProps.meetingID)))
log.info("Informing parent meeting {} that a breakout room has been ended {}", m.mProps.parentMeetingID, m.mProps.meetingID)
eventBus.publish(BigBlueButtonEvent(m.mProps.parentMeetingID,
BreakoutRoomEnded(m.mProps.parentMeetingID, m.mProps.meetingID)))
}
outGW.send(new EndAndKickAll(msg.meetingID, m.mProps.recorded))
outGW.send(new DisconnectAllUsers(msg.meetingID))
@ -149,9 +149,9 @@ class BigBlueButtonActor(val system: ActorSystem,
eventBus.subscribe(m.actorRef, m.mProps.deskshareBridge)
meetings += m.mProps.meetingID -> m
outGW.send(new MeetingCreated(m.mProps.meetingID, m.mProps.externalMeetingID, m.mProps.recorded, m.mProps.meetingName,
m.mProps.voiceBridge, msg.mProps.duration, msg.mProps.moderatorPass,
msg.mProps.viewerPass, msg.mProps.createTime, msg.mProps.createDate))
outGW.send(new MeetingCreated(m.mProps.meetingID, m.mProps.externalMeetingID, m.mProps.parentMeetingID,
m.mProps.recorded, m.mProps.meetingName, m.mProps.voiceBridge, msg.mProps.duration, msg.mProps.moderatorPass,
msg.mProps.viewerPass, msg.mProps.createTime, msg.mProps.createDate, msg.mProps.isBreakout))
m.actorRef ! new InitializeMeeting(m.mProps.meetingID, m.mProps.recorded)
}

View File

@ -57,6 +57,7 @@ class BigBlueButtonInGW(
val mProps = new MeetingProperties(
msg.payload.id,
msg.payload.externalId,
msg.payload.parentId,
msg.payload.name,
msg.payload.record,
msg.payload.voiceConfId,
@ -69,7 +70,8 @@ class BigBlueButtonInGW(
msg.payload.createTime,
msg.payload.createDate,
red5DeskShareIP, red5DeskShareApp,
msg.payload.isBreakout)
msg.payload.isBreakout,
msg.payload.sequence)
eventBus.publish(BigBlueButtonEvent("meeting-manager", new CreateMeeting(msg.payload.id, mProps)))
}

View File

@ -51,7 +51,7 @@ object JsonMessageDecoder {
def decode(json: String): Option[InMessage] = {
unmarshall(json) match {
case Success(validMsg) => Some(validMsg)
case Failure(ex) => None
case Failure(ex) => None
}
}
@ -75,4 +75,4 @@ object JsonMessageDecoder {
case _ => throw MessageProcessException("Cannot parse JSON message: [" + msg + "]")
}
}
}
}

View File

@ -38,27 +38,27 @@ class JsonMessageSenderActor(val service: MessageSender)
def receive = {
// Breakout
case msg: CreateBreakoutRoom => handleCreateBreakoutRoom(msg)
case msg: EndBreakoutRoom => handleEndBreakoutRoom(msg)
case msg: BreakoutRoomsListOutMessage => handleBreakoutRoomsList(msg)
case msg: CreateBreakoutRoom => handleCreateBreakoutRoom(msg)
case msg: EndBreakoutRoom => handleEndBreakoutRoom(msg)
case msg: BreakoutRoomsListOutMessage => handleBreakoutRoomsList(msg)
case msg: BreakoutRoomJoinURLOutMessage => handleBreakoutRoomJoinURL(msg)
case msg: BreakoutRoomStartedOutMessage => handleBreakoutRoomStarted(msg)
case msg: BreakoutRoomEndedOutMessage => handleBreakoutRoomEnded(msg)
case msg: BreakoutRoomEndedOutMessage => handleBreakoutRoomEnded(msg)
case msg: UpdateBreakoutUsersOutMessage => handleUpdateBreakoutUsers(msg)
case msg: MeetingTimeRemainingUpdate => handleMeetingTimeRemainingUpdate(msg)
case msg: MeetingTimeRemainingUpdate => handleMeetingTimeRemainingUpdate(msg)
case _ => // do nothing
case _ => // do nothing
}
// Breakout
private def handleBreakoutRoomStarted(msg: BreakoutRoomStartedOutMessage) {
val payload = new BreakoutRoomPayload(msg.meetingId, msg.breakout.breakoutId, msg.breakout.name)
val payload = new BreakoutRoomPayload(msg.parentMeetingId, msg.breakout.meetingId, msg.breakout.externalMeetingId, msg.breakout.name, msg.breakout.sequence)
val request = new BreakoutRoomStarted(payload)
service.send(MessagingConstants.FROM_MEETING_CHANNEL, request.toJson)
}
private def handleBreakoutRoomEnded(msg: BreakoutRoomEndedOutMessage) {
val payload = new BreakoutRoomPayload(msg.meetingId, msg.breakoutId, "")
val payload = new BreakoutRoomPayload(msg.parentMeetingId, msg.meetingId, "", "", 0)
val request = new BreakoutRoomClosed(payload)
service.send(MessagingConstants.FROM_MEETING_CHANNEL, request.toJson)
}
@ -66,7 +66,7 @@ class JsonMessageSenderActor(val service: MessageSender)
private def handleUpdateBreakoutUsers(msg: UpdateBreakoutUsersOutMessage) {
val users = new java.util.ArrayList[BreakoutUserPayload]()
msg.users.foreach(x => users.add(new BreakoutUserPayload(x.id, x.name)))
val payload = new UpdateBreakoutUsersPayload(msg.meetingId, msg.breakoutId, users)
val payload = new UpdateBreakoutUsersPayload(msg.parentMeetingId, msg.breakoutMeetingId, users)
val request = new UpdateBreakoutUsers(payload)
service.send(MessagingConstants.FROM_MEETING_CHANNEL, request.toJson())
}
@ -85,29 +85,29 @@ class JsonMessageSenderActor(val service: MessageSender)
private def handleBreakoutRoomsList(msg: BreakoutRoomsListOutMessage) {
val rooms = new java.util.ArrayList[BreakoutRoomPayload]()
msg.rooms.foreach(r => rooms.add(new BreakoutRoomPayload(msg.meetingId, r.breakoutId, r.name)))
msg.rooms.foreach(r => rooms.add(new BreakoutRoomPayload(msg.meetingId, r.meetingId, r.externalMeetingId, r.name, r.sequence)))
val payload = new BreakoutRoomsListPayload(msg.meetingId, rooms, msg.roomsReady)
val request = new BreakoutRoomsList(payload)
service.send(MessagingConstants.FROM_MEETING_CHANNEL, request.toJson())
}
private def handleCreateBreakoutRoom(msg: CreateBreakoutRoom) {
val payload = new CreateBreakoutRoomRequestPayload(msg.room.breakoutId, msg.room.parentId, msg.room.name,
msg.room.voiceConfId, msg.room.viewerPassword, msg.room.moderatorPassword,
val payload = new CreateBreakoutRoomRequestPayload(msg.room.breakoutMeetingId, msg.room.parentId, msg.room.name,
msg.room.sequence, msg.room.voiceConfId, msg.room.viewerPassword, msg.room.moderatorPassword,
msg.room.durationInMinutes, msg.room.sourcePresentationId, msg.room.sourcePresentationSlide, msg.room.record)
val request = new CreateBreakoutRoomRequest(payload)
service.send(MessagingConstants.FROM_MEETING_CHANNEL, request.toJson())
}
private def handleEndBreakoutRoom(msg: EndBreakoutRoom) {
val payload = new EndBreakoutRoomRequestPayload(msg.breakoutId)
val payload = new EndBreakoutRoomRequestPayload(msg.breakoutMeetingId)
val request = new EndBreakoutRoomRequest(payload)
service.send(MessagingConstants.FROM_MEETING_CHANNEL, request.toJson())
}
def handleBreakoutRoomJoinURL(msg: BreakoutRoomJoinURLOutMessage) {
val payload = new BreakoutRoomJoinURLPayload(msg.meetingId,
msg.breakoutId, msg.userId, msg.joinURL)
val payload = new BreakoutRoomJoinURLPayload(msg.parentMeetingId,
msg.breakoutMeetingId, msg.userId, msg.joinURL)
val request = new BreakoutRoomJoinURL(payload)
service.send(MessagingConstants.FROM_MEETING_CHANNEL, request.toJson)
}

View File

@ -12,17 +12,17 @@ import org.bigbluebutton.core.apps.CaptionApp
import org.bigbluebutton.core.apps.CaptionModel
class LiveMeeting(val mProps: MeetingProperties,
val eventBus: IncomingEventBus,
val outGW: OutMessageGateway,
val chatModel: ChatModel,
val layoutModel: LayoutModel,
val meetingModel: MeetingModel,
val usersModel: UsersModel,
val pollModel: PollModel,
val wbModel: WhiteboardModel,
val presModel: PresentationModel,
val breakoutModel: BreakoutRoomModel,
val captionModel: CaptionModel)(implicit val context: ActorContext)
val eventBus: IncomingEventBus,
val outGW: OutMessageGateway,
val chatModel: ChatModel,
val layoutModel: LayoutModel,
val meetingModel: MeetingModel,
val usersModel: UsersModel,
val pollModel: PollModel,
val wbModel: WhiteboardModel,
val presModel: PresentationModel,
val breakoutModel: BreakoutRoomModel,
val captionModel: CaptionModel)(implicit val context: ActorContext)
extends UsersApp with PresentationApp
with LayoutApp with ChatApp with WhiteboardApp with PollApp
with BreakoutRoomApp with CaptionApp {
@ -56,7 +56,7 @@ class LiveMeeting(val mProps: MeetingProperties,
}
def startCheckingIfWeNeedToEndVoiceConf() {
if (usersModel.numWebUsers == 0) {
if (usersModel.numWebUsers == 0 && !mProps.isBreakout) {
meetingModel.lastWebUserLeft()
log.debug("MonitorNumberOfWebUsers started for meeting [" + mProps.meetingID + "]")
}

View File

@ -37,8 +37,8 @@ class MeetingActorInternal(val mProps: MeetingProperties,
if (mProps.isBreakout) {
// This is a breakout room. Inform our parent meeting that we have been successfully created.
eventBus.publish(BigBlueButtonEvent(
mProps.externalMeetingID,
BreakoutRoomCreated(mProps.externalMeetingID, mProps.meetingID)))
mProps.parentMeetingID,
BreakoutRoomCreated(mProps.parentMeetingID, mProps.meetingID)))
}
def receive = {

View File

@ -4,14 +4,15 @@ import org.bigbluebutton.core.api.Permissions
import java.util.concurrent.TimeUnit
case object StopMeetingActor
case class MeetingProperties(meetingID: String, externalMeetingID: String, meetingName: String, recorded: Boolean,
voiceBridge: String, deskshareBridge: String, duration: Int, autoStartRecording: Boolean,
allowStartStopRecording: Boolean, moderatorPass: String, viewerPass: String, createTime: Long,
createDate: String, red5DeskShareIP: String, red5DeskShareApp: String, isBreakout: Boolean)
case class MeetingProperties(meetingID: String, externalMeetingID: String, parentMeetingID: String, meetingName: String,
recorded: Boolean, voiceBridge: String, deskshareBridge: String, duration: Int,
autoStartRecording: Boolean, allowStartStopRecording: Boolean, moderatorPass: String,
viewerPass: String, createTime: Long, createDate: String,
red5DeskShareIP: String, red5DeskShareApp: String, isBreakout: Boolean, sequence: Int)
case class MeetingExtensionProp(maxExtensions: Int = 2, numExtensions: Int = 0, extendByMinutes: Int = 20,
sendNotice: Boolean = true, sent15MinNotice: Boolean = false,
sent10MinNotice: Boolean = false, sent5MinNotice: Boolean = false)
sendNotice: Boolean = true, sent15MinNotice: Boolean = false,
sent10MinNotice: Boolean = false, sent5MinNotice: Boolean = false)
class MeetingModel {
private var audioSettingsInited = false

View File

@ -44,7 +44,6 @@ class MessageSenderActor(val service: MessageSender)
extends Actor with ActorLogging {
val encoder = new ToJsonEncoder()
def receive = {
case msg: UserEjectedFromMeeting => handleUserEjectedFromMeeting(msg)
case msg: GetChatHistoryReply => handleGetChatHistoryReply(msg)

View File

@ -4,7 +4,7 @@ import spray.json.{ DefaultJsonProtocol, JsValue, JsString, DeserializationExcep
import org.bigbluebutton.core.api._
object UserMessagesProtocol extends DefaultJsonProtocol {
/*
/*
implicit object RoleJsonFormat extends JsonFormat[Role.RoleType] {
def write(obj: Role.RoleType): JsValue = JsString(obj.toString)
@ -20,21 +20,20 @@ object UserMessagesProtocol extends DefaultJsonProtocol {
def read(json: JsValue): MessageType.MessageType = json match {
case JsString(str) => MessageType.withName(str)
case _ => throw new DeserializationException("Enum string expected")
case _ => throw new DeserializationException("Enum string expected")
}
}
implicit val breakoutRoomInPayloadFormat = jsonFormat2(BreakoutRoomInPayload)
implicit val createBreakoutRoomsFormat = jsonFormat4(CreateBreakoutRooms)
implicit val breakoutRoomInPayloadFormat = jsonFormat3(BreakoutRoomInPayload)
implicit val createBreakoutRoomsFormat = jsonFormat5(CreateBreakoutRooms)
implicit val breakoutRoomsListMessageFormat = jsonFormat1(BreakoutRoomsListMessage)
implicit val requestBreakoutJoinURLInMessageFormat = jsonFormat3(RequestBreakoutJoinURLInMessage)
implicit val requestBreakoutJoinURLInMessageFormat = jsonFormat4(RequestBreakoutJoinURLInMessage)
implicit val transferUserToMeetingRequestFormat = jsonFormat3(TransferUserToMeetingRequest)
implicit val endBreakoutRoomsFormat = jsonFormat1(EndAllBreakoutRooms)
implicit val inMsgHeaderFormat = jsonFormat1(InMessageHeader)
implicit val outMsgHeaderFormat = jsonFormat1(OutMsgHeader)
implicit val outMsgEnvelopeHeaderFormat = jsonFormat2(OutMsgEnvelopeHeader)
implicit val createBreakoutRoomOutMsgPayloadFormat = jsonFormat10(CreateBreakoutRoomOutMsgPayload)
implicit val createBreakoutRoomOutMsgPayloadFormat = jsonFormat11(CreateBreakoutRoomOutMsgPayload)
implicit val createBreakoutRoomOutMsgEnvelopePayloadFormat = jsonFormat2(CreateBreakoutRoomOutMsgEnvelopePayload)
implicit val createBreakoutRoomOutMsgEnvelopeFormat = jsonFormat2(CreateBreakoutRoomOutMsgEnvelope)
}

View File

@ -7,8 +7,7 @@ trait OutMessage
case class CreateBreakoutRoomOutMsgEnvelope(header: OutMsgEnvelopeHeader, payload: CreateBreakoutRoomOutMsgEnvelopePayload)
case class CreateBreakoutRoomOutMsgEnvelopePayload(header: OutMsgHeader, payload: CreateBreakoutRoomOutMsgPayload)
case class CreateBreakoutRoomOutMsgPayload(breakoutId: String, name: String, parentId: String,
voiceConfId: String, durationInMinutes: Int,
moderatorPassword: String, viewerPassword: String,
sourcePresentationId: String, sourcePresentationSlide: Int, record: Boolean)
case class CreateBreakoutRoomOutMsgPayload(meetingId: String, parentId: String, name: String,
voiceConfId: String, moderatorPassword: String, viewerPassword: String,
durationInMinutes: Int, sourcePresentationId: String, sourcePresentationSlide: Int,
record: Boolean, sequence: Int)

View File

@ -6,6 +6,8 @@ object Constants {
val PAYLOAD = "payload"
val MEETING_ID = "meeting_id"
val EXTERNAL_MEETING_ID = "external_meeting_id"
val PARENT_MEETING_ID = "parent_meeting_id"
val IS_BREAKOUT = "is_breakout";
val TIMESTAMP = "timestamp"
val CURRENT_TIME = "current_time"
val USER_ID = "userid"

View File

@ -42,17 +42,14 @@ case class LockSetting(meetingID: String, locked: Boolean, settings: Map[String,
// Sent by user to request the breakout rooms list of a room
case class BreakoutRoomsListMessage(meetingId: String) extends InMessage
// Sent by user to request creation of breakout rooms
case class CreateBreakoutRooms(meetingId: String, durationInMinutes: Int, record: Boolean,
rooms: Vector[BreakoutRoomInPayload]) extends InMessage
case class BreakoutRoomInPayload(name: String, users: Vector[String])
case class CreateBreakoutRooms(meetingId: String, durationInMinutes: Int, record: Boolean, redirectOnJoin: Boolean, rooms: Vector[BreakoutRoomInPayload]) extends InMessage
case class BreakoutRoomInPayload(name: String, sequence: Int, users: Vector[String])
// Sent by user to request for a join URL in order to be able to join a breakout room
case class RequestBreakoutJoinURLInMessage(meetingId: String, breakoutId: String,
userId: String) extends InMessage
case class RequestBreakoutJoinURLInMessage(meetingId: String, breakoutMeetingId: String, userId: String, redirect: Boolean) extends InMessage
// Sent by breakout actor to tell meeting actor that breakout room has been created.
case class BreakoutRoomCreated(meetingId: String, breakoutRoomId: String) extends InMessage
// Sent by breakout actor to tell meeting actor the list of users in the breakout room.
case class BreakoutRoomUsersUpdate(meetingId: String, breakoutId: String,
users: Vector[BreakoutUser]) extends InMessage
case class BreakoutRoomUsersUpdate(meetingId: String, breakoutMeetingId: String, users: Vector[BreakoutUser]) extends InMessage
// Send by internal actor to tell the breakout actor to send it's list of users to the main meeting actor.
case class SendBreakoutUsersUpdate(meetingId: String) extends InMessage
// Sent by user to request ending all the breakout rooms
@ -76,9 +73,9 @@ case class GetLockSettings(meetingID: String, userId: String) extends InMessage
/////////////////////////////////////////////////////////////////////////////////
case class ValidateAuthToken(meetingID: String, userId: String, token: String,
correlationId: String, sessionId: String) extends InMessage
correlationId: String, sessionId: String) extends InMessage
case class RegisterUser(meetingID: String, userID: String, name: String, role: Role,
extUserID: String, authToken: String, avatarURL: String) extends InMessage
extUserID: String, authToken: String, avatarURL: String) extends InMessage
case class UserJoining(meetingID: String, userID: String, authToken: String) extends InMessage
case class UserLeaving(meetingID: String, userID: String, sessionId: String) extends InMessage
case class GetUsers(meetingID: String, requesterID: String) extends InMessage
@ -100,9 +97,9 @@ case class GetChatHistoryRequest(meetingID: String, requesterID: String, replyTo
case class SendPublicMessageRequest(meetingID: String, requesterID: String, message: Map[String, String]) extends InMessage
case class SendPrivateMessageRequest(meetingID: String, requesterID: String, message: Map[String, String]) extends InMessage
case class UserConnectedToGlobalAudio(meetingID: String, /** Not used. Just to satisfy trait **/ voiceConf: String,
userid: String, name: String) extends InMessage
userid: String, name: String) extends InMessage
case class UserDisconnectedFromGlobalAudio(meetingID: String, /** Not used. Just to satisfy trait **/ voiceConf: String,
userid: String, name: String) extends InMessage
userid: String, name: String) extends InMessage
///////////////////////////////////////////////////////////////////////////////////////
// Layout
@ -111,7 +108,7 @@ case class UserDisconnectedFromGlobalAudio(meetingID: String, /** Not used. Just
case class GetCurrentLayoutRequest(meetingID: String, requesterID: String) extends InMessage
case class SetLayoutRequest(meetingID: String, requesterID: String, layoutID: String) extends InMessage
case class LockLayoutRequest(meetingID: String, setById: String, lock: Boolean, viewersOnly: Boolean,
layout: Option[String]) extends InMessage
layout: Option[String]) extends InMessage
case class BroadcastLayoutRequest(meetingID: String, requesterID: String, layout: String) extends InMessage
//////////////////////////////////////////////////////////////////////////////////////
@ -123,19 +120,19 @@ case class RemovePresentation(meetingID: String, presentationID: String) extends
case class GetPresentationInfo(meetingID: String, requesterID: String, replyTo: String) extends InMessage
case class SendCursorUpdate(meetingID: String, xPercent: Double, yPercent: Double) extends InMessage
case class ResizeAndMoveSlide(meetingID: String, xOffset: Double, yOffset: Double,
widthRatio: Double, heightRatio: Double) extends InMessage
widthRatio: Double, heightRatio: Double) extends InMessage
case class GotoSlide(meetingID: String, page: String) extends InMessage
case class SharePresentation(meetingID: String, presentationID: String, share: Boolean) extends InMessage
case class GetSlideInfo(meetingID: String, requesterID: String, replyTo: String) extends InMessage
case class PreuploadedPresentations(meetingID: String, presentations: Seq[Presentation]) extends InMessage
case class PresentationConversionUpdate(meetingID: String, messageKey: String, code: String,
presentationId: String, presName: String) extends InMessage
presentationId: String, presName: String) extends InMessage
case class PresentationPageCountError(meetingID: String, messageKey: String, code: String, presentationId: String,
numberOfPages: Int, maxNumberPages: Int, presName: String) extends InMessage
numberOfPages: Int, maxNumberPages: Int, presName: String) extends InMessage
case class PresentationSlideGenerated(meetingID: String, messageKey: String, code: String, presentationId: String,
numberOfPages: Int, pagesCompleted: Int, presName: String) extends InMessage
numberOfPages: Int, pagesCompleted: Int, presName: String) extends InMessage
case class PresentationConversionCompleted(meetingID: String, messageKey: String, code: String,
presentation: Presentation) extends InMessage
presentation: Presentation) extends InMessage
/////////////////////////////////////////////////////////////////////////////////////
// Polling
@ -164,9 +161,9 @@ case class MuteUserRequest(meetingID: String, requesterID: String, userID: Strin
case class LockUserRequest(meetingID: String, requesterID: String, userID: String, lock: Boolean) extends InMessage
case class EjectUserFromVoiceRequest(meetingID: String, userId: String, ejectedBy: String) extends InMessage
case class VoiceUserJoinedMessage(meetingID: String, user: String, voiceConfId: String,
callerIdNum: String, callerIdName: String, muted: Boolean, talking: Boolean) extends InMessage
callerIdNum: String, callerIdName: String, muted: Boolean, talking: Boolean) extends InMessage
case class UserJoinedVoiceConfMessage(voiceConfId: String, voiceUserId: String, userId: String, externUserId: String,
callerIdName: String, callerIdNum: String, muted: Boolean, talking: Boolean, avatarURL: String, listenOnly: Boolean) extends InMessage
callerIdName: String, callerIdNum: String, muted: Boolean, talking: Boolean, avatarURL: String, listenOnly: Boolean) extends InMessage
case class UserLeftVoiceConfMessage(voiceConfId: String, voiceUserId: String) extends InMessage
case class UserLockedInVoiceConfMessage(voiceConfId: String, voiceUserId: String, locked: Boolean) extends InMessage
case class UserMutedInVoiceConfMessage(voiceConfId: String, voiceUserId: String, muted: Boolean) extends InMessage

View File

@ -14,8 +14,8 @@ case class VoiceRecordingStarted(meetingID: String, recorded: Boolean, recording
case class VoiceRecordingStopped(meetingID: String, recorded: Boolean, recordingFile: String, timestamp: String, confNum: String) extends IOutMessage
case class RecordingStatusChanged(meetingID: String, recorded: Boolean, userId: String, recording: Boolean) extends IOutMessage
case class GetRecordingStatusReply(meetingID: String, recorded: Boolean, userId: String, recording: Boolean) extends IOutMessage
case class MeetingCreated(meetingID: String, externalMeetingID: String, recorded: Boolean, name: String,
voiceBridge: String, duration: Int, moderatorPass: String, viewerPass: String, createTime: Long, createDate: String) extends IOutMessage
case class MeetingCreated(meetingID: String, externalMeetingID: String, parentMeetingID: String, recorded: Boolean, name: String,
voiceBridge: String, duration: Int, moderatorPass: String, viewerPass: String, createTime: Long, createDate: String, isBreakout: Boolean) extends IOutMessage
case class MeetingMuted(meetingID: String, recorded: Boolean, meetingMuted: Boolean) extends IOutMessage
case class MeetingEnded(meetingID: String, recorded: Boolean, voiceBridge: String) extends IOutMessage
case class MeetingState(meetingID: String, recorded: Boolean, userId: String, permissions: Permissions, meetingMuted: Boolean) extends IOutMessage
@ -30,17 +30,17 @@ case object IsAliveMessage extends IOutMessage
// Breakout Rooms
case class BreakoutRoomsListOutMessage(meetingId: String, rooms: Vector[BreakoutRoomBody], roomsReady: Boolean) extends IOutMessage
case class CreateBreakoutRoom(meetingId: String, room: BreakoutRoomOutPayload) extends IOutMessage
case class EndBreakoutRoom(breakoutId: String) extends IOutMessage
case class BreakoutRoomOutPayload(breakoutId: String, name: String, parentId: String,
case class EndBreakoutRoom(breakoutMeetingId: String) extends IOutMessage
case class BreakoutRoomOutPayload(breakoutMeetingId: String, name: String, parentId: String, sequence: Integer,
voiceConfId: String, durationInMinutes: Int, moderatorPassword: String, viewerPassword: String,
sourcePresentationId: String, sourcePresentationSlide: Int, record: Boolean)
case class BreakoutRoomJoinURLOutMessage(meetingId: String, recorded: Boolean, breakoutId: String, userId: String, joinURL: String) extends IOutMessage
case class BreakoutRoomStartedOutMessage(meetingId: String, recorded: Boolean, breakout: BreakoutRoomBody) extends IOutMessage
case class BreakoutRoomBody(name: String, breakoutId: String)
case class UpdateBreakoutUsersOutMessage(meetingId: String, recorded: Boolean, breakoutId: String, users: Vector[BreakoutUser]) extends IOutMessage
case class BreakoutRoomJoinURLOutMessage(parentMeetingId: String, recorded: Boolean, breakoutMeetingId: String, userId: String, joinURL: String) extends IOutMessage
case class BreakoutRoomStartedOutMessage(parentMeetingId: String, recorded: Boolean, breakout: BreakoutRoomBody) extends IOutMessage
case class BreakoutRoomBody(name: String, externalMeetingId: String, meetingId: String, sequence: Int)
case class UpdateBreakoutUsersOutMessage(parentMeetingId: String, recorded: Boolean, breakoutMeetingId: String, users: Vector[BreakoutUser]) extends IOutMessage
case class MeetingTimeRemainingUpdate(meetingId: String, recorded: Boolean, timeRemaining: Int) extends IOutMessage
case class BreakoutRoomsTimeRemainingUpdateOutMessage(meetingId: String, recorded: Boolean, timeRemaining: Int) extends IOutMessage
case class BreakoutRoomEndedOutMessage(meetingId: String, breakoutId: String) extends IOutMessage
case class BreakoutRoomEndedOutMessage(parentMeetingId: String, meetingId: String) extends IOutMessage
// Permissions
case class PermissionsSettingInitialized(meetingID: String, permissions: Permissions, applyTo: Array[UserVO]) extends IOutMessage

View File

@ -21,8 +21,10 @@ trait BreakoutRoomApp extends SystemConfiguration {
val eventBus: IncomingEventBus
def handleBreakoutRoomsList(msg: BreakoutRoomsListMessage) {
val breakoutRooms = breakoutModel.getRooms().toVector map { r => new BreakoutRoomBody(r.name, r.id) }
outGW.send(new BreakoutRoomsListOutMessage(mProps.meetingID, breakoutRooms, breakoutModel.pendingRoomsNumber == 0 && breakoutRooms.length > 0));
val breakoutRooms = breakoutModel.getRooms().toVector map { r => new BreakoutRoomBody(r.name, r.externalMeetingId, r.id, r.sequence) }
val roomsReady = breakoutModel.pendingRoomsNumber == 0 && breakoutRooms.length > 0
log.info("Sending breakout rooms list to {} with containing {} room(s)", mProps.meetingID, breakoutRooms.length)
outGW.send(new BreakoutRoomsListOutMessage(mProps.meetingID, breakoutRooms, roomsReady))
}
def handleCreateBreakoutRooms(msg: CreateBreakoutRooms) {
@ -31,18 +33,26 @@ trait BreakoutRoomApp extends SystemConfiguration {
log.warning("CreateBreakoutRooms event received while {} are pending to be created for meeting {}", breakoutModel.pendingRoomsNumber, mProps.meetingID)
return
}
if (breakoutModel.getNumberOfRooms() > 0) {
log.warning("CreateBreakoutRooms event received while {} breakout rooms running for meeting {}", breakoutModel.getNumberOfRooms(), mProps.meetingID)
return
}
var i = 0
val sourcePresentationId = presModel.getCurrentPresentation().get.id
val sourcePresentationSlide = presModel.getCurrentPage().get.num
// in very rare cases the presentation conversion generates an error, what should we do?
// those cases where default.pdf is deleted from the whiteboard
val sourcePresentationId = if (!presModel.getCurrentPresentation().isEmpty) presModel.getCurrentPresentation().get.id else "blank"
val sourcePresentationSlide = if (!presModel.getCurrentPage().isEmpty) presModel.getCurrentPage().get.num else 0
breakoutModel.pendingRoomsNumber = msg.rooms.length;
breakoutModel.redirectOnJoin = msg.redirectOnJoin;
for (room <- msg.rooms) {
i += 1
val breakoutMeetingId = BreakoutRoomsUtil.createMeetingId(mProps.meetingID, i)
val breakoutMeetingId = BreakoutRoomsUtil.createMeetingIds(mProps.meetingID, i)
val voiceConfId = BreakoutRoomsUtil.createVoiceConfId(mProps.voiceBridge, i)
val r = breakoutModel.createBreakoutRoom(breakoutMeetingId, room.name, voiceConfId, room.users)
val p = new BreakoutRoomOutPayload(r.id, r.name, mProps.meetingID,
val r = breakoutModel.createBreakoutRoom(mProps.meetingID, breakoutMeetingId._1, breakoutMeetingId._2, room.name,
room.sequence, voiceConfId, room.users)
val p = new BreakoutRoomOutPayload(r.id, r.name, mProps.meetingID, r.sequence,
r.voiceConfId, msg.durationInMinutes, mProps.moderatorPass, mProps.viewerPass,
sourcePresentationId, sourcePresentationSlide, msg.record)
outGW.send(new CreateBreakoutRoom(mProps.meetingID, p))
@ -51,36 +61,37 @@ trait BreakoutRoomApp extends SystemConfiguration {
meetingModel.breakoutRoomsStartedOn = timeNowInSeconds;
}
def sendJoinURL(userId: String, breakoutId: String) {
def sendJoinURL(userId: String, externalMeetingId: String, redirect: Boolean) {
log.debug("Sending breakout meeting {} Join URL for user: {}", externalMeetingId, userId);
for {
user <- usersModel.getUser(userId)
apiCall = "join"
params = BreakoutRoomsUtil.joinParams(user.name, userId, true, breakoutId, mProps.moderatorPass, true)
params = BreakoutRoomsUtil.joinParams(user.name, userId, true, externalMeetingId, mProps.moderatorPass, redirect)
baseString = BreakoutRoomsUtil.createBaseString(params)
checksum = BreakoutRoomsUtil.calculateChecksum(apiCall, baseString, bbbWebSharedSecret)
joinURL = BreakoutRoomsUtil.createJoinURL(bbbWebAPI, apiCall, baseString, checksum)
} yield outGW.send(new BreakoutRoomJoinURLOutMessage(mProps.meetingID, mProps.recorded, breakoutId, userId, joinURL))
} yield outGW.send(new BreakoutRoomJoinURLOutMessage(mProps.meetingID, mProps.recorded, externalMeetingId, userId, joinURL))
}
def handleRequestBreakoutJoinURL(msg: RequestBreakoutJoinURLInMessage) {
sendJoinURL(msg.userId, msg.breakoutId)
sendJoinURL(msg.userId, msg.breakoutMeetingId, msg.redirect)
}
def handleBreakoutRoomCreated(msg: BreakoutRoomCreated) {
breakoutModel.pendingRoomsNumber -= 1
val room = breakoutModel.getBreakoutRoom(msg.breakoutRoomId)
room foreach { room =>
sendBreakoutRoomStarted(mProps.meetingID, room.name, room.id, room.voiceConfId)
sendBreakoutRoomStarted(room.parentRoomId, room.name, room.externalMeetingId, room.id, room.sequence, room.voiceConfId)
}
// We avoid sending invitation
// We postpone sending invitation until all breakout rooms have been created
if (breakoutModel.pendingRoomsNumber == 0) {
log.info("All breakout rooms created for meetingId={}", mProps.meetingID)
breakoutModel.getRooms().foreach { room =>
breakoutModel.getAssignedUsers(room.id) foreach { users =>
users.foreach { u =>
log.debug("Sending Join URL for users: {}", u);
sendJoinURL(u, room.id)
log.debug("Sending Join URL for users");
sendJoinURL(u, room.externalMeetingId, breakoutModel.redirectOnJoin)
}
}
}
@ -88,8 +99,9 @@ trait BreakoutRoomApp extends SystemConfiguration {
}
}
def sendBreakoutRoomStarted(meetingId: String, breakoutName: String, breakoutId: String, voiceConfId: String) {
outGW.send(new BreakoutRoomStartedOutMessage(meetingId, mProps.recorded, new BreakoutRoomBody(breakoutName, breakoutId)))
def sendBreakoutRoomStarted(meetingId: String, breakoutName: String, externalMeetingId: String, breakoutMeetingId: String, sequence: Int, voiceConfId: String) {
log.info("Sending breakout room started {} for parent meeting {} ", breakoutMeetingId, meetingId);
outGW.send(new BreakoutRoomStartedOutMessage(meetingId, mProps.recorded, new BreakoutRoomBody(breakoutName, externalMeetingId, breakoutMeetingId, sequence)))
}
def handleBreakoutRoomEnded(msg: BreakoutRoomEnded) {
@ -98,16 +110,16 @@ trait BreakoutRoomApp extends SystemConfiguration {
}
def handleBreakoutRoomUsersUpdate(msg: BreakoutRoomUsersUpdate) {
breakoutModel.updateBreakoutUsers(msg.breakoutId, msg.users) foreach { room =>
outGW.send(new UpdateBreakoutUsersOutMessage(mProps.meetingID, mProps.recorded, msg.breakoutId, room.users))
breakoutModel.updateBreakoutUsers(msg.breakoutMeetingId, msg.users) foreach { room =>
outGW.send(new UpdateBreakoutUsersOutMessage(mProps.meetingID, mProps.recorded, msg.breakoutMeetingId, room.users))
}
}
def handleSendBreakoutUsersUpdate(msg: SendBreakoutUsersUpdate) {
val users = usersModel.getUsers().toVector
val breakoutUsers = users map { u => new BreakoutUser(u.externUserID, u.name) }
eventBus.publish(BigBlueButtonEvent(mProps.externalMeetingID,
new BreakoutRoomUsersUpdate(mProps.externalMeetingID, mProps.meetingID, breakoutUsers)))
eventBus.publish(BigBlueButtonEvent(mProps.parentMeetingID,
new BreakoutRoomUsersUpdate(mProps.parentMeetingID, mProps.meetingID, breakoutUsers)))
}
def handleTransferUserToMeeting(msg: TransferUserToMeetingRequest) {
@ -146,18 +158,18 @@ trait BreakoutRoomApp extends SystemConfiguration {
}
object BreakoutRoomsUtil {
def createMeetingId(id: String, index: Int): String = {
id.concat("-").concat(index.toString())
def createMeetingIds(id: String, index: Int): (String, String) = {
val timeStamp = System.currentTimeMillis()
val externalHash = DigestUtils.sha1Hex(id.concat("-").concat(timeStamp.toString()).concat("-").concat(index.toString()))
val externalId = externalHash.concat("-").concat(timeStamp.toString())
val internalId = DigestUtils.sha1Hex(externalId).concat("-").concat(timeStamp.toString())
(internalId, externalId)
}
def createVoiceConfId(id: String, index: Int): String = {
id.concat(index.toString())
}
def fromSWFtoPDF(swfURL: String): String = {
swfURL.replace("swf", "pdf")
}
def createJoinURL(webAPI: String, apiCall: String, baseString: String, checksum: String): String = {
var apiURL = if (webAPI.endsWith("/")) webAPI else webAPI.concat("/")
apiURL.concat(apiCall).concat("?").concat(baseString).concat("&checksum=").concat(checksum)
@ -174,13 +186,13 @@ object BreakoutRoomsUtil {
checksum(apiCall.concat(baseString).concat(sharedSecret))
}
def joinParams(username: String, userId: String, isBreakout: Boolean, breakoutId: String,
def joinParams(username: String, userId: String, isBreakout: Boolean, breakoutMeetingId: String,
password: String, redirect: Boolean): mutable.Map[String, String] = {
val params = new collection.mutable.HashMap[String, String]
params += "fullName" -> urlEncode(username)
params += "userID" -> urlEncode(userId + "-" + breakoutId.substring(breakoutId.lastIndexOf("-") + 1));
params += "userID" -> urlEncode(userId + "-" + breakoutMeetingId.substring(breakoutMeetingId.lastIndexOf("-") + 1));
params += "isBreakout" -> urlEncode(isBreakout.toString())
params += "meetingID" -> urlEncode(breakoutId)
params += "meetingID" -> urlEncode(breakoutMeetingId)
params += "password" -> urlEncode(password)
params += "redirect" -> urlEncode(redirect.toString())
@ -218,18 +230,4 @@ object BreakoutRoomsUtil {
def urlEncode(s: String): String = {
URLEncoder.encode(s, "UTF-8");
}
//
//encodeURIComponent() -- Java encoding similiar to JavaScript encodeURIComponent
//
def encodeURIComponent(component: String): String = {
URLEncoder.encode(component, "UTF-8")
.replaceAll("\\%28", "(")
.replaceAll("\\%29", ")")
.replaceAll("\\+", "%20")
.replaceAll("\\%27", "'")
.replaceAll("\\%21", "!")
.replaceAll("\\%7E", "~")
}
}

View File

@ -1,16 +1,16 @@
package org.bigbluebutton.core.apps
import scala.collection.mutable.ArrayBuffer
import scala.collection.immutable.HashMap
import scala.Vector
case class BreakoutUser(id: String, name: String)
case class BreakoutRoom(id: String, name: String, voiceConfId: String,
assignedUsers: Vector[String], users: Vector[BreakoutUser])
case class BreakoutRoom(id: String, externalMeetingId: String, name: String, parentRoomId: String, sequence: Integer, voiceConfId: String,
assignedUsers: Vector[String], users: Vector[BreakoutUser])
class BreakoutRoomModel {
private var rooms = new collection.immutable.HashMap[String, BreakoutRoom]
var pendingRoomsNumber: Integer = 0
var redirectOnJoin: Boolean = false
def add(room: BreakoutRoom): BreakoutRoom = {
rooms += room.id -> room
@ -21,9 +21,9 @@ class BreakoutRoomModel {
rooms -= id
}
def createBreakoutRoom(id: String, name: String, voiceConfId: String,
assignedUsers: Vector[String]): BreakoutRoom = {
val room = new BreakoutRoom(id, name, voiceConfId, assignedUsers, Vector())
def createBreakoutRoom(parentRoomId: String, id: String, externalMeetingId: String, name: String, sequence: Integer, voiceConfId: String,
assignedUsers: Vector[String]): BreakoutRoom = {
val room = new BreakoutRoom(id, externalMeetingId, name, parentRoomId, sequence, voiceConfId, assignedUsers, Vector())
add(room)
}
@ -35,18 +35,21 @@ class BreakoutRoomModel {
rooms.values.toArray
}
def getAssignedUsers(breakoutId: String): Option[Vector[String]] = {
def getNumberOfRooms(): Int = {
rooms.size
}
def getAssignedUsers(breakoutMeetingId: String): Option[Vector[String]] = {
for {
room <- rooms.get(breakoutId)
room <- rooms.get(breakoutMeetingId)
} yield room.assignedUsers
}
def updateBreakoutUsers(breakoutId: String, users: Vector[BreakoutUser]): Option[BreakoutRoom] = {
def updateBreakoutUsers(breakoutMeetingId: String, users: Vector[BreakoutUser]): Option[BreakoutRoom] = {
for {
room <- rooms.get(breakoutId)
room <- rooms.get(breakoutMeetingId)
newroom = room.copy(users = users)
room2 = add(newroom)
} yield room2
}
}

View File

@ -29,6 +29,8 @@ object MeetingMessageToJsonConverter {
val payload = new java.util.HashMap[String, Any]()
payload.put(Constants.MEETING_ID, msg.meetingID)
payload.put(Constants.EXTERNAL_MEETING_ID, msg.externalMeetingID)
payload.put(Constants.PARENT_MEETING_ID, msg.parentMeetingID)
payload.put(Constants.IS_BREAKOUT, msg.isBreakout)
payload.put(Constants.NAME, msg.name)
payload.put(Constants.RECORDED, msg.recorded)
payload.put(Constants.VOICE_CONF, msg.voiceBridge)
@ -145,8 +147,10 @@ object MeetingMessageToJsonConverter {
def breakoutRoomStartedOutMessageToJson(msg: BreakoutRoomStartedOutMessage): String = {
val payload = new java.util.HashMap[String, Any]()
payload.put("meetingId", msg.meetingId)
payload.put("breakoutId", msg.breakout.breakoutId)
payload.put("meetingId", msg.breakout.meetingId)
payload.put("externalMeetingId", msg.breakout.externalMeetingId)
payload.put("parentMeetingId", msg.parentMeetingId)
payload.put("sequence", msg.breakout.sequence)
payload.put("name", msg.breakout.name)
val header = Util.buildHeader(BreakoutRoomStarted.NAME, None)
@ -155,8 +159,8 @@ object MeetingMessageToJsonConverter {
def breakoutRoomEndedOutMessageToJson(msg: BreakoutRoomEndedOutMessage): String = {
val payload = new java.util.HashMap[String, Any]()
payload.put("parentMeetingId", msg.parentMeetingId)
payload.put("meetingId", msg.meetingId)
payload.put("breakoutId", msg.breakoutId)
val header = Util.buildHeader(BreakoutRoomClosed.NAME, None)
Util.buildJson(header, payload)
@ -164,8 +168,8 @@ object MeetingMessageToJsonConverter {
def breakoutRoomJoinURLOutMessageToJson(msg: BreakoutRoomJoinURLOutMessage): String = {
val payload = new java.util.HashMap[String, Any]()
payload.put("meetingId", msg.meetingId)
payload.put("breakoutId", msg.breakoutId)
payload.put("parentMeetingId", msg.parentMeetingId)
payload.put("breakoutMeetingId", msg.breakoutMeetingId)
payload.put("userId", msg.userId)
payload.put("joinURL", msg.joinURL)
@ -175,8 +179,8 @@ object MeetingMessageToJsonConverter {
def updateBreakoutUsersOutMessageToJson(msg: UpdateBreakoutUsersOutMessage): String = {
val payload = new java.util.HashMap[String, Any]()
payload.put("meetingId", msg.meetingId)
payload.put("breakoutId", msg.breakoutId)
payload.put("parentMeetingId", msg.parentMeetingId)
payload.put("breakoutMeetingId", msg.breakoutMeetingId)
payload.put("recorded", msg.recorded)
payload.put("users", msg.users.toArray)

View File

@ -4,6 +4,8 @@ trait AppsTestFixtures {
val meetingId = "testMeetingId"
val externalMeetingId = "testExternalMeetingId"
val parentMeetingId = "testParentMeetingId"
val sequence = 4
val meetingName = "test meeting"
val record = false
val voiceConfId = "85115"
@ -16,13 +18,15 @@ trait AppsTestFixtures {
val createTime = System.currentTimeMillis
val createDate = "Oct 26, 2015"
val isBreakout = false
val red5DeskShareIP = "127.0.0.1"
val red5DeskShareApp = "red5App"
val mProps = new MeetingProperties(meetingId, externalMeetingId,
val mProps = new MeetingProperties(meetingId, externalMeetingId, parentMeetingId,
meetingName, record,
voiceConfId, deskshareConfId,
durationInMinutes,
autoStartRecording, allowStartStopRecording,
moderatorPassword, viewerPassword,
createTime, createDate, isBreakout)
}
createTime, createDate, red5DeskShareIP, red5DeskShareApp,
isBreakout, sequence)
}

View File

@ -6,18 +6,11 @@ import org.bigbluebutton.core.UnitSpec
class BreakoutRoomsUtilSpec extends UnitSpec {
it should "return a pdfURL" in {
val baseURL = "http://localhost/pre1/page1."
val swfURL = baseURL + "swf"
val pdfURL = BreakoutRoomsUtil.fromSWFtoPDF(swfURL)
assert(pdfURL == baseURL + "pdf")
}
it should "return a meetingId" in {
val mainMeetingId = "abc-123"
val index = 1
val result = mainMeetingId.concat("-").concat(index.toString())
val breakoutMeetingId = BreakoutRoomsUtil.createMeetingId(mainMeetingId, index)
val breakoutMeetingId = BreakoutRoomsUtil.createMeetingIds(mainMeetingId, index)
assert(breakoutMeetingId == result)
}

View File

@ -47,7 +47,7 @@ libraryDependencies ++= {
"org.pegdown" % "pegdown" % "1.4.0",
"junit" % "junit" % "4.11",
"com.etaty.rediscala" %% "rediscala" % "1.4.0",
"commons-codec" % "commons-codec" % "1.8",
"commons-codec" % "commons-codec" % "1.10",
"joda-time" % "joda-time" % "2.3",
"com.google.code.gson" % "gson" % "1.7.1",
"redis.clients" % "jedis" % "2.1.0",

View File

@ -3,48 +3,54 @@ package org.bigbluebutton.messages;
import org.bigbluebutton.common.messages.IBigBlueButtonMessage;
public class CreateMeetingRequest implements IBigBlueButtonMessage {
public final static String NAME = "CreateMeetingRequest";
public final Header header;
public final CreateMeetingRequestPayload payload;
public CreateMeetingRequest(CreateMeetingRequestPayload payload) {
this.header = new Header(NAME);
this.payload = payload;
}
public static class CreateMeetingRequestPayload {
public final String id;
public final String externalId;
public final String name;
public final Boolean record;
public final String voiceConfId;
public final Integer durationInMinutes;
public final Boolean autoStartRecording;
public final Boolean allowStartStopRecording;
public final String moderatorPassword;
public final String viewerPassword;
public final Long createTime;
public final String createDate;
public final Boolean isBreakout;
public CreateMeetingRequestPayload(String id, String externalId, String name, Boolean record, String voiceConfId,
Integer duration, Boolean autoStartRecording,
Boolean allowStartStopRecording, String moderatorPass,
String viewerPass, Long createTime, String createDate, Boolean isBreakout) {
this.id = id;
this.externalId = externalId;
this.name = name;
this.record = record;
this.voiceConfId = voiceConfId;
this.durationInMinutes = duration;
this.autoStartRecording = autoStartRecording;
this.allowStartStopRecording = allowStartStopRecording;
this.moderatorPassword = moderatorPass;
this.viewerPassword = viewerPass;
this.createTime = createTime;
this.createDate = createDate;
this.isBreakout = isBreakout;
public final static String NAME = "CreateMeetingRequest";
public final Header header;
public final CreateMeetingRequestPayload payload;
public CreateMeetingRequest(CreateMeetingRequestPayload payload) {
this.header = new Header(NAME);
this.payload = payload;
}
public static class CreateMeetingRequestPayload {
public final String id;
public final String externalId;
public final String parentId;
public final String name;
public final Boolean record;
public final String voiceConfId;
public final Integer durationInMinutes;
public final Boolean autoStartRecording;
public final Boolean allowStartStopRecording;
public final String moderatorPassword;
public final String viewerPassword;
public final Long createTime;
public final String createDate;
public final Boolean isBreakout;
public final Integer sequence;
public CreateMeetingRequestPayload(String id, String externalId,
String parentId, String name, Boolean record,
String voiceConfId, Integer duration,
Boolean autoStartRecording, Boolean allowStartStopRecording,
String moderatorPass, String viewerPass, Long createTime,
String createDate, Boolean isBreakout, Integer sequence) {
this.id = id;
this.externalId = externalId;
this.parentId = parentId;
this.name = name;
this.record = record;
this.voiceConfId = voiceConfId;
this.durationInMinutes = duration;
this.autoStartRecording = autoStartRecording;
this.allowStartStopRecording = allowStartStopRecording;
this.moderatorPassword = moderatorPass;
this.viewerPassword = viewerPass;
this.createTime = createTime;
this.createDate = createDate;
this.isBreakout = isBreakout;
this.sequence = sequence;
}
}
}
}

View File

@ -20,14 +20,14 @@ package org.bigbluebutton.messages.payload;
public class BreakoutRoomJoinURLPayload {
public final String meetingId;
public final String breakoutId;
public final String parentMeetingId;
public final String breakoutMeetingId;
public final String userId;
public final String joinURL;
public BreakoutRoomJoinURLPayload(String meetingId, String breakoutId, String userId, String joinURL) {
this.meetingId = meetingId;
this.breakoutId = breakoutId;
public BreakoutRoomJoinURLPayload(String parentMeetingId, String breakoutMeetingId, String userId, String joinURL) {
this.parentMeetingId = parentMeetingId;
this.breakoutMeetingId = breakoutMeetingId;
this.userId = userId;
this.joinURL = joinURL;
}

View File

@ -2,13 +2,18 @@ package org.bigbluebutton.messages.payload;
public class BreakoutRoomPayload {
public final String meetingId;
public final String breakoutId;
public final String name;
public final String parentMeetingId;
public final String meetingId;
public final String externalMeetingId;
public final String name;
public final Integer sequence;
public BreakoutRoomPayload(String meetingId, String breakoutId, String name) {
this.meetingId = meetingId;
this.breakoutId = breakoutId;
this.name = name;
}
public BreakoutRoomPayload(String parentMeetingId, String meetingId,
String externalMeetingId, String name, Integer sequence) {
this.parentMeetingId = parentMeetingId;
this.meetingId = meetingId;
this.externalMeetingId = externalMeetingId;
this.name = name;
this.sequence = sequence;
}
}

View File

@ -3,13 +3,16 @@ package org.bigbluebutton.messages.payload;
import java.util.ArrayList;
public class BreakoutRoomRequestPayload {
// Name of the breakout room
public final String name;
// List of user ids to assign to the breakout room
public final ArrayList<String> users;
public BreakoutRoomRequestPayload(String name, ArrayList<String> users) {
this.name = name;
this.users = users;
}
// Name of the breakout room
public final String name;
// Sequence of the breakout room
public final Integer sequence;
// List of user ids to assign to the breakout room
public final ArrayList<String> users;
public BreakoutRoomRequestPayload(String name, Integer sequence, ArrayList<String> users) {
this.name = name;
this.sequence = sequence;
this.users = users;
}
}

View File

@ -1,9 +1,10 @@
package org.bigbluebutton.messages.payload;
public class CreateBreakoutRoomRequestPayload {
public final String breakoutId;
public final String parentId; // The main meeting internal id
public final String breakoutMeetingId;
public final String parentMeetingId; // The main meeting internal id
public final String name; // The name of the breakout room
public final Integer sequence; // The sequnce number of the breakout room
public final String voiceConfId; // The voice conference id
public final String viewerPassword;
public final String moderatorPassword;
@ -12,14 +13,15 @@ public class CreateBreakoutRoomRequestPayload {
public final Integer sourcePresentationSlide;
public final Boolean record;
public CreateBreakoutRoomRequestPayload(String breakoutId, String parentId,
String name, String voiceConfId, String viewerPassword,
public CreateBreakoutRoomRequestPayload(String meetingMeetingId, String parentMeetingId,
String name, Integer sequence, String voiceConfId, String viewerPassword,
String moderatorPassword, Integer duration,
String sourcePresentationId, Integer sourcePresentationSlide,
Boolean record) {
this.breakoutId = breakoutId;
this.parentId = parentId;
this.breakoutMeetingId = meetingMeetingId;
this.parentMeetingId = parentMeetingId;
this.name = name;
this.sequence = sequence;
this.voiceConfId = voiceConfId;
this.viewerPassword = viewerPassword;
this.moderatorPassword = moderatorPassword;

View File

@ -3,21 +3,24 @@ package org.bigbluebutton.messages.payload;
import java.util.ArrayList;
public class CreateBreakoutRoomsRequestPayload {
// The main meeting internal id
public final String meetingId;
// The list of breakout rooms
public final ArrayList<BreakoutRoomRequestPayload> rooms;
// The duration of the breakout room
public final Integer durationInMinutes;
// Breakout rooms recording option
public final Boolean record;
// The main meeting internal id
public final String meetingId;
// The list of breakout rooms
public final ArrayList<BreakoutRoomRequestPayload> rooms;
// The duration of the breakout room
public final Integer durationInMinutes;
// Breakout rooms recording option
public final Boolean record;
// Creates join URL with redirect value true or false
public final Boolean redirectOnJoin;
public CreateBreakoutRoomsRequestPayload(String meetingId,
ArrayList<BreakoutRoomRequestPayload> breakoutRooms,
Integer duration, Boolean record) {
this.meetingId = meetingId;
this.rooms = breakoutRooms;
this.durationInMinutes = duration;
this.record = record;
}
public CreateBreakoutRoomsRequestPayload(String meetingId,
ArrayList<BreakoutRoomRequestPayload> breakoutRooms,
Integer duration, Boolean record, Boolean redirectOnJoin) {
this.meetingId = meetingId;
this.rooms = breakoutRooms;
this.durationInMinutes = duration;
this.record = record;
this.redirectOnJoin = redirectOnJoin;
}
}

View File

@ -2,14 +2,14 @@ package org.bigbluebutton.messages.payload;
public class ListenInOnBreakoutPayload {
public final String meetingId;
public final String targetMeetingId;
public final String userId;
public final String meetingId;
public final String targetMeetingId;
public final String userId;
public ListenInOnBreakoutPayload(String meetingId, String breakoutId,
String userId) {
this.meetingId = meetingId;
this.targetMeetingId = breakoutId;
this.userId = userId;
}
public ListenInOnBreakoutPayload(String meetingId, String targetMeetingId,
String userId) {
this.meetingId = meetingId;
this.targetMeetingId = targetMeetingId;
this.userId = userId;
}
}

View File

@ -2,13 +2,16 @@ package org.bigbluebutton.messages.payload;
public class RequestBreakoutJoinURLPayload {
public final String meetingId;
public final String breakoutId;
public final String userId;
public RequestBreakoutJoinURLPayload(String meetingId, String breakoutId, String userId) {
this.meetingId = meetingId;
this.breakoutId = breakoutId;
this.userId = userId;
}
public final String meetingId;
public final String breakoutMeetingId;
public final String userId;
public final Boolean redirect;
public RequestBreakoutJoinURLPayload(String meetingId,
String breakoutMeetingId, String userId, Boolean redirect) {
this.meetingId = meetingId;
this.breakoutMeetingId = breakoutMeetingId;
this.userId = userId;
this.redirect = redirect;
}
}

View File

@ -5,12 +5,12 @@ import java.util.ArrayList;
public class UpdateBreakoutUsersPayload {
public final ArrayList<BreakoutUserPayload> users;
public final String breakoutId;
public final String meetingId;
public final String breakoutMeetingId;
public final String parentMeetingId;
public UpdateBreakoutUsersPayload(String meetingId, String breakoutId, ArrayList<BreakoutUserPayload> users) {
this.meetingId = meetingId;
this.breakoutId = breakoutId;
public UpdateBreakoutUsersPayload(String meetingParentId, String breakoutMeetingId, ArrayList<BreakoutUserPayload> users) {
this.parentMeetingId = meetingParentId;
this.breakoutMeetingId = breakoutMeetingId;
this.users = users;
}
}

View File

@ -13,6 +13,7 @@ public class CreateBreakoutRoomRequestTest {
String parentId = "abc-123";
Integer durationInMinutes = 20;
String name = "Breakout room 1";
Integer sequence = 3;
String voiceConfId = "851153";
String viewerPassword = "vp";
String moderatorPassword = "mp";
@ -20,9 +21,10 @@ public class CreateBreakoutRoomRequestTest {
Integer sourePresentationSlide = 5;
Boolean record = false;
CreateBreakoutRoomRequestPayload payload =
new CreateBreakoutRoomRequestPayload(breakoutId, parentId, name, voiceConfId,
viewerPassword, moderatorPassword, durationInMinutes, sourcePresentationId, sourePresentationSlide, record);
CreateBreakoutRoomRequestPayload payload = new CreateBreakoutRoomRequestPayload(
breakoutId, parentId, name, sequence, voiceConfId,
viewerPassword, moderatorPassword, durationInMinutes,
sourcePresentationId, sourePresentationSlide, record);
CreateBreakoutRoomRequest msg = new CreateBreakoutRoomRequest(payload);
Gson gson = new Gson();
String json = gson.toJson(msg);
@ -31,8 +33,9 @@ public class CreateBreakoutRoomRequestTest {
CreateBreakoutRoomRequest rxMsg = gson.fromJson(json, CreateBreakoutRoomRequest.class);
Assert.assertEquals(rxMsg.header.name, CreateBreakoutRoomRequest.NAME);
Assert.assertEquals(rxMsg.payload.breakoutId, breakoutId);
Assert.assertEquals(rxMsg.payload.breakoutMeetingId, breakoutId);
Assert.assertEquals(rxMsg.payload.name, name);
Assert.assertEquals(rxMsg.payload.sequence, sequence);
Assert.assertEquals(rxMsg.payload.voiceConfId, voiceConfId);
Assert.assertEquals(rxMsg.payload.viewerPassword, viewerPassword);
Assert.assertEquals(rxMsg.payload.moderatorPassword, moderatorPassword);

View File

@ -15,23 +15,24 @@ public class CreateBreakoutRoomsRequestTest {
String meetingId = "abc123";
Integer durationInMinutes = 20;
Boolean record = true;
Boolean redirectOnJoin = false;
ArrayList<String> room1Users = new ArrayList<String>();
room1Users.add("Tidora"); room1Users.add("Nidora"); room1Users.add("Tinidora");
BreakoutRoomRequestPayload room1 = new BreakoutRoomRequestPayload("room1", room1Users);
BreakoutRoomRequestPayload room1 = new BreakoutRoomRequestPayload("room1", 1, room1Users);
ArrayList<String> room2Users = new ArrayList<String>();
room2Users.add("Jose"); room2Users.add("Wally"); room2Users.add("Paolo");
BreakoutRoomRequestPayload room2= new BreakoutRoomRequestPayload("room2", room2Users);
BreakoutRoomRequestPayload room2= new BreakoutRoomRequestPayload("room2", 2, room2Users);
ArrayList<String> room3Users = new ArrayList<String>();
room3Users.add("Alden"); room3Users.add("Yaya Dub");
BreakoutRoomRequestPayload room3= new BreakoutRoomRequestPayload("room3", room3Users);
BreakoutRoomRequestPayload room3= new BreakoutRoomRequestPayload("room3", 3, room3Users);
ArrayList<BreakoutRoomRequestPayload> rooms = new ArrayList<BreakoutRoomRequestPayload>();
rooms.add(room1); rooms.add(room2); rooms.add(room3);
CreateBreakoutRoomsRequestPayload payload = new CreateBreakoutRoomsRequestPayload(meetingId, rooms, durationInMinutes, record);
CreateBreakoutRoomsRequestPayload payload = new CreateBreakoutRoomsRequestPayload(meetingId, rooms, durationInMinutes, record, redirectOnJoin);
CreateBreakoutRoomsRequest msg = new CreateBreakoutRoomsRequest(payload);
Gson gson = new Gson();
String json = gson.toJson(msg);

View File

@ -12,6 +12,7 @@ public class CreateMeetingRequestTest {
public void testCreateMeetingRequest() {
String meetingId = "abc123";
String externalId = "extabc123";
String parentId = "";
Boolean record = false;
Integer durationInMinutes = 20;
String name = "Breakout room 1";
@ -19,16 +20,17 @@ public class CreateMeetingRequestTest {
Boolean autoStartRecording = false;
Boolean allowStartStopRecording = false;
Boolean isBreakout = true;
Integer sequence = 4;
String viewerPassword = "vp";
String moderatorPassword = "mp";
long createTime = System.currentTimeMillis();
String createDate = new Date(createTime).toString();
CreateMeetingRequestPayload payload =
new CreateMeetingRequestPayload(meetingId, externalId, name, record, voiceConfId,
durationInMinutes, autoStartRecording,
allowStartStopRecording, moderatorPassword,
viewerPassword, createTime, createDate, isBreakout);
CreateMeetingRequestPayload payload = new CreateMeetingRequestPayload(
meetingId, externalId, parentId, name, record, voiceConfId,
durationInMinutes, autoStartRecording, allowStartStopRecording,
moderatorPassword, viewerPassword, createTime, createDate,
isBreakout, sequence);
CreateMeetingRequest msg = new CreateMeetingRequest(payload);
Gson gson = new Gson();
String json = gson.toJson(msg);
@ -38,11 +40,14 @@ public class CreateMeetingRequestTest {
Assert.assertEquals(rxMsg.header.name, CreateMeetingRequest.NAME);
Assert.assertEquals(rxMsg.payload.id, meetingId);
Assert.assertEquals(rxMsg.payload.externalId, externalId);
Assert.assertEquals(rxMsg.payload.parentId, parentId);
Assert.assertEquals(rxMsg.payload.name, name);
Assert.assertEquals(rxMsg.payload.voiceConfId, voiceConfId);
Assert.assertEquals(rxMsg.payload.viewerPassword, viewerPassword);
Assert.assertEquals(rxMsg.payload.moderatorPassword, moderatorPassword);
Assert.assertEquals(rxMsg.payload.durationInMinutes, durationInMinutes);
Assert.assertEquals(rxMsg.payload.isBreakout, isBreakout);
Assert.assertEquals(rxMsg.payload.sequence, sequence);
}
}

View File

@ -44,7 +44,7 @@ libraryDependencies ++= {
// "org.pegdown" % "pegdown" % "1.4.0",
// "junit" % "junit" % "4.11",
// "com.etaty.rediscala" %% "rediscala" % "1.4.0",
"commons-codec" % "commons-codec" % "1.8",
"commons-codec" % "commons-codec" % "1.10",
"redis.clients" % "jedis" % "2.7.2",
// "org.apache.commons" % "commons-lang3" % "3.2",
"org.apache.commons" % "commons-pool2" % "2.3",

View File

@ -3,12 +3,11 @@ package org.bigbluebutton.red5.client;
import java.util.HashMap;
import java.util.Map;
import org.bigbluebutton.common.messages.ChatKeyUtil;
import org.bigbluebutton.common.messages.DeskShareNotifyViewersRTMPEventMessage;
import org.bigbluebutton.common.messages.DeskShareNotifyASingleViewerEventMessage;
import org.bigbluebutton.common.messages.DeskShareNotifyViewersRTMPEventMessage;
import org.bigbluebutton.red5.client.messaging.BroadcastClientMessage;
import org.bigbluebutton.red5.client.messaging.DirectClientMessage;
import org.bigbluebutton.red5.client.messaging.ConnectionInvokerService;
import org.bigbluebutton.red5.client.messaging.DirectClientMessage;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;

View File

@ -44,7 +44,6 @@ import com.google.gson.Gson;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
public class UserClientMessageSender {
private static Logger log = Red5LoggerFactory.getLogger(UserClientMessageSender.class, "bigbluebutton");
@ -543,8 +542,8 @@ public class UserClientMessageSender {
private void processBreakoutRoomJoinURL(BreakoutRoomJoinURL msg) {
Map<String, Object> args = new HashMap<String, Object>();
args.put("meetingId", msg.payload.meetingId);
args.put("breakoutId", msg.payload.breakoutId);
args.put("parentMeetingId", msg.payload.parentMeetingId);
args.put("breakoutMeetingId", msg.payload.breakoutMeetingId);
args.put("userId", msg.payload.userId);
args.put("joinURL", msg.payload.joinURL);
@ -552,7 +551,7 @@ public class UserClientMessageSender {
Gson gson = new Gson();
message.put("msg", gson.toJson(args));
DirectClientMessage m = new DirectClientMessage(msg.payload.meetingId, msg.payload.userId, "breakoutRoomJoinURL", message);
DirectClientMessage m = new DirectClientMessage(msg.payload.parentMeetingId, msg.payload.userId, "breakoutRoomJoinURL", message);
service.sendMessage(m);
}
@ -584,42 +583,44 @@ public class UserClientMessageSender {
private void processUpdateBreakoutUsers(UpdateBreakoutUsers msg) {
Map<String, Object> args = new HashMap<String, Object>();
args.put("meetingId", msg.payload.meetingId);
args.put("breakoutId", msg.payload.breakoutId);
args.put("parentMeetingId", msg.payload.parentMeetingId);
args.put("breakoutMeetingId", msg.payload.breakoutMeetingId);
args.put("users", msg.payload.users);
Map<String, Object> message = new HashMap<String, Object>();
Gson gson = new Gson();
message.put("msg", gson.toJson(args));
BroadcastClientMessage m = new BroadcastClientMessage(msg.payload.meetingId, "updateBreakoutUsers", message);
BroadcastClientMessage m = new BroadcastClientMessage(msg.payload.parentMeetingId, "updateBreakoutUsers", message);
service.sendMessage(m);
}
private void processBreakoutRoomStarted(BreakoutRoomStarted msg) {
Map<String, Object> args = new HashMap<String, Object>();
args.put("breakoutId", msg.payload.breakoutId);
args.put("meetingId", msg.payload.meetingId);
args.put("breakoutMeetingId", msg.payload.meetingId);
args.put("parentMeetingId", msg.payload.parentMeetingId);
args.put("externalMeetingId", msg.payload.externalMeetingId);
args.put("sequence", msg.payload.sequence);
args.put("name", msg.payload.name);
Map<String, Object> message = new HashMap<String, Object>();
Gson gson = new Gson();
message.put("msg", gson.toJson(args));
BroadcastClientMessage m = new BroadcastClientMessage(msg.payload.meetingId, "breakoutRoomStarted", message);
BroadcastClientMessage m = new BroadcastClientMessage(msg.payload.parentMeetingId, "breakoutRoomStarted", message);
service.sendMessage(m);
}
private void processBreakoutRoomClosed(BreakoutRoomClosed msg) {
Map<String, Object> args = new HashMap<String, Object>();
args.put("breakoutId", msg.payload.breakoutId);
args.put("meetingId", msg.payload.meetingId);
args.put("breakoutMeetingId", msg.payload.meetingId);
args.put("parentMeetingId", msg.payload.parentMeetingId);
Map<String, Object> message = new HashMap<String, Object>();
Gson gson = new Gson();
message.put("msg", gson.toJson(args));
BroadcastClientMessage m = new BroadcastClientMessage(msg.payload.meetingId, "breakoutRoomClosed", message);
BroadcastClientMessage m = new BroadcastClientMessage(msg.payload.parentMeetingId, "breakoutRoomClosed", message);
service.sendMessage(m);
}
}

View File

@ -1,6 +1,5 @@
package org.bigbluebutton.red5.client;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;

View File

@ -18,9 +18,6 @@
*/
package org.bigbluebutton.red5.service;
import java.util.HashMap;
import java.util.Map;
import org.bigbluebutton.red5.BigBlueButtonSession;
import org.bigbluebutton.red5.Constants;
import org.bigbluebutton.red5.pubsub.MessagePublisher;

View File

@ -39,7 +39,9 @@ package org.bigbluebutton.main.events {
public var meetingId:String;
public var breakoutId:String;
public var breakoutMeetingId:String;
public var breakoutMeetingSequence:int;
public var rooms:Array;

View File

@ -334,12 +334,11 @@ package org.bigbluebutton.main.model.users
private var _breakoutRooms : Array = [];
[Bindable("displayNameChange")]
public function get displayName() : String {
if (ArrayUtils.isEmpty(_breakoutRooms)){
public function get displayName():String {
if (ArrayUtils.isEmpty(_breakoutRooms)) {
return name;
}
else {
return "[" + _breakoutRooms.join(",") + "] " +name;
} else {
return "[" + _breakoutRooms.join(",") + "] " + name;
}
}
@ -352,14 +351,14 @@ package org.bigbluebutton.main.model.users
dispatchEvent(new Event("displayNameChange"));
}
public function addBreakoutRoom(roomNumber:String):void {
public function addBreakoutRoom(roomNumber:int):void {
if (!ArrayUtils.contains(_breakoutRooms, roomNumber)) {
_breakoutRooms.push(roomNumber);
dispatchEvent(new Event("displayNameChange"));
}
}
public function removeBreakoutRoom(roomNumber:String):void {
public function removeBreakoutRoom(roomNumber:int):void {
_breakoutRooms.splice(_breakoutRooms.indexOf(roomNumber), 1);
dispatchEvent(new Event("displayNameChange"));
}

View File

@ -28,7 +28,11 @@ package org.bigbluebutton.main.model.users {
public static const OTHER:String = "other";
public var breakoutId:String;
public var externalMeetingId:String;
public var meetingId:String;
public var sequence:int;
public var name:String;
@ -44,6 +48,5 @@ package org.bigbluebutton.main.model.users {
public function get numberOfUsers():int {
return users.length;
}
}
}

View File

@ -20,6 +20,7 @@ package org.bigbluebutton.main.model.users {
import mx.collections.ArrayCollection;
import mx.collections.Sort;
import mx.collections.SortField;
import org.as3commons.lang.ArrayUtils;
import org.as3commons.lang.StringUtils;
@ -534,59 +535,87 @@ package org.bigbluebutton.main.model.users {
/* Breakout room feature */
public function addBreakoutRoom(newRoom:BreakoutRoom):void {
if (hasBreakoutRoom(newRoom.breakoutId)) {
removeBreakoutRoom(newRoom.breakoutId);
if (hasBreakoutRoom(newRoom.meetingId)) {
removeBreakoutRoom(newRoom.meetingId);
}
breakoutRooms.addItem(newRoom);
sortBreakoutRooms();
}
private function sortBreakoutRooms() : void {
var sort:Sort = new Sort();
sort.fields = [new SortField("sequence", true, false, true)];
breakoutRooms.sort = sort;
breakoutRooms.refresh();
}
public function updateBreakoutRoomUsers(breakoutId:String, breakoutUsers:Array):void {
var room:Object = getBreakoutRoom(breakoutId);
public function updateBreakoutRoomUsers(breakoutMeetingId:String, breakoutUsers:Array):void {
var room:BreakoutRoom = getBreakoutRoom(breakoutMeetingId);
if (room != null) {
BreakoutRoom(room).users = new ArrayCollection(breakoutUsers);
var breakoutRoomNumber:String = StringUtils.substringAfterLast(breakoutId, "-");
room.users = new ArrayCollection(breakoutUsers);
var updateUsers:Array = [];
// Update users breakout rooms
var user : BBBUser;
var user:BBBUser;
for (var i:int = 0; i < breakoutUsers.length; i++) {
var userId:String = StringUtils.substringBeforeLast(breakoutUsers[i].id, "-");
user = getUser(userId);
if (user) {
user.addBreakoutRoom(breakoutRoomNumber)
user.addBreakoutRoom(room.sequence)
}
updateUsers.push(userId);
}
// Remove users breakout rooms if the users left the breakout rooms
for (var j:int = 0; j < users.length; j++) {
user = BBBUser(users.getItemAt(j));
if (updateUsers.indexOf(BBBUser(users.getItemAt(j)).userID) == -1 && ArrayUtils.contains(user.breakoutRooms, breakoutRoomNumber)) {
user.removeBreakoutRoom(breakoutRoomNumber);
if (updateUsers.indexOf(BBBUser(users.getItemAt(j)).userID) == -1 && ArrayUtils.contains(user.breakoutRooms, room.sequence)) {
user.removeBreakoutRoom(room.sequence);
}
}
users.refresh();
}
}
/**
* Returns a breakout room by its breakoutId
* Returns a breakout room by its internal meeting ID
*/
public function getBreakoutRoom(breakoutId:String):BreakoutRoom {
var r:Object = getBreakoutRoomIndex(breakoutId);
public function getBreakoutRoom(breakoutMeetingId:String):BreakoutRoom {
var r:Object = getBreakoutRoomIndex(breakoutMeetingId);
if (r != null) {
return r.room as BreakoutRoom;
}
return null;
}
/**
* Finds the index of a breakout room by its breakoutId
*/
public function getBreakoutRoomIndex(breakoutId:String):Object {
public function getBreakoutRoomByExternalId(externalId:String):BreakoutRoom {
var aRoom:BreakoutRoom;
for (var i:int = 0; i < breakoutRooms.length; i++) {
aRoom = breakoutRooms.getItemAt(i) as BreakoutRoom;
if (aRoom.breakoutId == breakoutId) {
if (aRoom.externalMeetingId == externalId) {
return aRoom;
}
}
return null;
}
public function getBreakoutRoomBySequence(sequence:int):BreakoutRoom {
var aRoom:BreakoutRoom;
for (var i:int = 0; i < breakoutRooms.length; i++) {
aRoom = breakoutRooms.getItemAt(i) as BreakoutRoom;
if (aRoom.sequence == sequence) {
return aRoom;
}
}
return null;
}
/**
* Finds the index of a breakout room by its internal meeting ID
*/
public function getBreakoutRoomIndex(breakoutMeetingId:String):Object {
var aRoom:BreakoutRoom;
for (var i:int = 0; i < breakoutRooms.length; i++) {
aRoom = breakoutRooms.getItemAt(i) as BreakoutRoom;
if (aRoom.meetingId == breakoutMeetingId) {
return {index: i, room: aRoom};
}
}
@ -594,39 +623,38 @@ package org.bigbluebutton.main.model.users {
return null;
}
public function removeBreakoutRoom(breakoutId:String):void {
var p:Object = getBreakoutRoomIndex(breakoutId);
if (p != null) {
breakoutRooms.removeItemAt(p.index);
breakoutRooms.refresh();
public function removeBreakoutRoom(breakoutMeetingId:String):void {
var room:Object = getBreakoutRoomIndex(breakoutMeetingId);
if (room != null) {
breakoutRooms.removeItemAt(room.index);
sortBreakoutRooms();
if (breakoutRooms.length == 0) {
breakoutRoomsReady = false;
}
// Remove breakout room number display from users
for (var i:int; i < users.length; i++) {
var breakoutRoomNumber:String = StringUtils.substringAfterLast(breakoutId, "-");
if (ArrayUtils.contains(users[i].breakoutRooms, breakoutRoomNumber)) {
users[i].removeBreakoutRoom(breakoutRoomNumber);
if (ArrayUtils.contains(users[i].breakoutRooms, room.room.sequence)) {
users[i].removeBreakoutRoom(room.room.sequence);
}
}
users.refresh();
}
}
public function hasBreakoutRoom(breakoutId:String):Boolean {
var p:Object = getBreakoutRoomIndex(breakoutId);
public function hasBreakoutRoom(breakoutMeetingId:String):Boolean {
var p:Object = getBreakoutRoomIndex(breakoutMeetingId);
if (p != null) {
return true;
}
return false;
}
public function setBreakoutRoomInListen(listen:Boolean, breakoutId:String):void {
public function setBreakoutRoomInListen(listen:Boolean, breakoutMeetingId:String):void {
for (var i:int = 0; i < breakoutRooms.length; i++) {
var br:BreakoutRoom = BreakoutRoom(breakoutRooms.getItemAt(i));
if (listen == false) {
br.listenStatus = BreakoutRoom.NONE;
} else if (listen == true && br.breakoutId == breakoutId) {
} else if (listen == true && br.meetingId == breakoutMeetingId) {
br.listenStatus = BreakoutRoom.SELF;
} else {
br.listenStatus = BreakoutRoom.OTHER;

View File

@ -199,20 +199,20 @@ package org.bigbluebutton.main.model.users
}
public function createBreakoutRooms(e:BreakoutRoomEvent):void{
sender.createBreakoutRooms(_conferenceParameters.meetingID, e.rooms, e.durationInMinutes, e.record);
sender.createBreakoutRooms(_conferenceParameters.meetingID, e.rooms, e.durationInMinutes, e.record, true);
}
public function requestBreakoutJoinUrl(e:BreakoutRoomEvent):void{
sender.requestBreakoutJoinUrl(_conferenceParameters.meetingID, e.breakoutId, e.userId);
sender.requestBreakoutJoinUrl(_conferenceParameters.meetingID, e.breakoutMeetingId, e.userId, true);
}
public function listenInOnBreakout(e:BreakoutRoomEvent):void {
if (e.listen) {
sender.listenInOnBreakout(_conferenceParameters.meetingID, e.breakoutId, _conferenceParameters.userid);
sender.listenInOnBreakout(_conferenceParameters.meetingID, e.breakoutMeetingId, _conferenceParameters.userid);
} else {
sender.listenInOnBreakout(e.breakoutId, _conferenceParameters.meetingID, _conferenceParameters.userid);
sender.listenInOnBreakout(e.breakoutMeetingId, _conferenceParameters.meetingID, _conferenceParameters.userid);
}
UserManager.getInstance().getConference().setBreakoutRoomInListen(e.listen, e.breakoutId);
UserManager.getInstance().getConference().setBreakoutRoomInListen(e.listen, e.breakoutMeetingId);
}
public function endAllBreakoutRooms(e:BreakoutRoomEvent):void {

View File

@ -46,16 +46,7 @@ package org.bigbluebutton.modules.users.services
import org.bigbluebutton.main.model.users.IMessageListener;
import org.bigbluebutton.main.model.users.events.StreamStoppedEvent;
import org.bigbluebutton.main.model.users.events.UsersConnectionEvent;
import org.bigbluebutton.modules.users.events.MeetingMutedEvent;
import org.bigbluebutton.modules.present.events.CursorEvent;
import org.bigbluebutton.modules.present.events.NavigationEvent;
import org.bigbluebutton.modules.present.events.RemovePresentationEvent;
import org.bigbluebutton.modules.present.events.UploadEvent;
import org.bigbluebutton.modules.users.events.MeetingMutedEvent;
import org.bigbluebutton.modules.screenshare.events.ViewStreamEvent;
import org.bigbluebutton.modules.screenshare.events.WebRTCViewStreamEvent;
import org.bigbluebutton.main.api.JSLog;
import org.bigbluebutton.modules.users.events.MeetingMutedEvent;
public class MessageReceiver implements IMessageListener
@ -156,8 +147,8 @@ package org.bigbluebutton.modules.users.services
handleTimeRemainingUpdate(message);
break;
case "breakoutRoomsTimeRemainingUpdate":
handleBreakoutRoomsTimeRemainingUpdate(message);
break;
handleBreakoutRoomsTimeRemainingUpdate(message);
break;
case "breakoutRoomStarted":
handleBreakoutRoomStarted(message);
break;
@ -639,8 +630,10 @@ package org.bigbluebutton.modules.users.services
for each(var room : Object in map.rooms)
{
var breakoutRoom : BreakoutRoom = new BreakoutRoom();
breakoutRoom.breakoutId = room.breakoutId;
breakoutRoom.meetingId = room.meetingId;
breakoutRoom.externalMeetingId = room.externalMeetingId;
breakoutRoom.name = room.name;
breakoutRoom.sequence = room.sequence;
UserManager.getInstance().getConference().addBreakoutRoom(breakoutRoom);
}
UserManager.getInstance().getConference().breakoutRoomsReady = map.roomsReady;
@ -650,13 +643,14 @@ package org.bigbluebutton.modules.users.services
var map:Object = JSON.parse(msg.msg);
var event : BreakoutRoomEvent = new BreakoutRoomEvent(BreakoutRoomEvent.BREAKOUT_JOIN_URL);
event.joinURL = map.joinURL;
event.breakoutId = StringUtils.substringBetween(event.joinURL, "meetingID=", "&");
var externalMeetingId : String = StringUtils.substringBetween(event.joinURL, "meetingID=", "&");
event.breakoutMeetingSequence = UserManager.getInstance().getConference().getBreakoutRoomByExternalId(externalMeetingId).sequence;
dispatcher.dispatchEvent(event);
}
private function handleUpdateBreakoutUsers(msg:Object):void{
var map:Object = JSON.parse(msg.msg);
UserManager.getInstance().getConference().updateBreakoutRoomUsers(map.breakoutId, map.users);
UserManager.getInstance().getConference().updateBreakoutRoomUsers(map.breakoutMeetingId, map.users);
}
private function handleTimeRemainingUpdate(msg:Object):void {
@ -676,14 +670,16 @@ package org.bigbluebutton.modules.users.services
private function handleBreakoutRoomStarted(msg:Object):void{
var map:Object = JSON.parse(msg.msg);
var breakoutRoom : BreakoutRoom = new BreakoutRoom();
breakoutRoom.breakoutId = map.breakoutId;
breakoutRoom.meetingId = map.breakoutMeetingId;
breakoutRoom.externalMeetingId = map.externalMeetingId;
breakoutRoom.name = map.name;
breakoutRoom.sequence = map.sequence;
UserManager.getInstance().getConference().addBreakoutRoom(breakoutRoom);
}
private function handleBreakoutRoomClosed(msg:Object):void{
var map:Object = JSON.parse(msg.msg);
UserManager.getInstance().getConference().removeBreakoutRoom(map.breakoutId);
UserManager.getInstance().getConference().removeBreakoutRoom(map.breakoutMeetingId);
}
}

View File

@ -87,12 +87,13 @@ package org.bigbluebutton.modules.users.services
);
}
public function createBreakoutRooms(meetingId:String, rooms:Array, durationInMinutes:int, record:Boolean):void {
public function createBreakoutRooms(meetingId:String, rooms:Array, durationInMinutes:int, record:Boolean, redirectOnJoin:Boolean):void {
var message:Object = new Object();
message["meetingId"] = meetingId;
message["rooms"] = rooms;
message["durationInMinutes"] = durationInMinutes;
message["record"] = record;
message["redirectOnJoin"] = redirectOnJoin;
var jsonMsg:String = JSON.stringify(message);
var _nc:ConnectionManager = BBB.initConnectionManager();
@ -107,24 +108,21 @@ package org.bigbluebutton.modules.users.services
);
}
public function requestBreakoutJoinUrl(meetingId:String, breakoutId:String, userId:String):void {
public function requestBreakoutJoinUrl(parentMeetingId:String, breakoutMeetingId:String, userId:String, redirect:Boolean):void {
var message:Object = new Object();
message["meetingId"] = meetingId;
message["breakoutId"] = breakoutId;
message["meetingId"] = parentMeetingId;
message["breakoutMeetingId"] = breakoutMeetingId;
message["userId"] = userId;
message["redirect"] = redirect;
var jsonMsg:String = JSON.stringify(message);
var _nc:ConnectionManager = BBB.initConnectionManager();
_nc.sendMessage("breakoutroom.requestBreakoutJoinUrl", function(result:String):void
{
_nc.sendMessage("breakoutroom.requestBreakoutJoinUrl", function(result:String):void {
// On successful result
}, function(status:String):void
{ // status - On error occurred
}, function(status:String):void { // status - On error occurred
LOGGER.error(status);
},
jsonMsg
);
}, jsonMsg);
}
public function listenInOnBreakout(meetingId:String, targetMeetingId:String, userId:String):void {

View File

@ -65,15 +65,15 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
var event:BreakoutRoomEvent = new BreakoutRoomEvent(BreakoutRoomEvent.CREATE_BREAKOUT_ROOMS);
// event.meetingId is filled in the event handler for BreakoutRoomEvent in UserService class
event.rooms = new Array();
var parentMeetingName:String = UserManager.getInstance().getConference().meetingName;
var roomResource:String = ResourceUtil.getInstance().getString('bbb.users.breakout.room');
for (var i:int = 0; i < (roomsCombo.selectedIndex + 2); i++) {
var users:Array = BreakoutList(roomsContainer.getChildAt(i)).users.source;
totalUsers += users.length;
var room:Object = new Object();
room.users = new Array();
room.name =
UserManager.getInstance().getConference().meetingName + " (" +
ResourceUtil.getInstance().getString('bbb.users.breakout.room')
+ " - " + (i + 1).toString() + ")";
room.sequence = i + 1;
room.name = parentMeetingName + " (" + roomResource + " - " + room.sequence.toString() + ")";
for (var j:int = 0; j < users.length; j++) {
room.users.push(users[j].userID);
}
@ -105,7 +105,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
usersInvited ||= true;
var event:BreakoutRoomEvent = new BreakoutRoomEvent(BreakoutRoomEvent.REQUEST_BREAKOUT_JOIN_URL);
event.userId = user.userID;
event.breakoutId = UserManager.getInstance().getConference().internalMeetingID + "-" + (i + 1);
event.breakoutMeetingId = UserManager.getInstance().getConference().getBreakoutRoomBySequence(i + 1).externalMeetingId;
dispatcher.dispatchEvent(event);
}
}
@ -294,8 +294,8 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
</mx:HBox>
<mx:HBox id="recordBox" width="100%" paddingTop="12">
<mx:Label text="{ResourceUtil.getInstance().getString('bbb.users.breakout.record')}" visible="false" />
<mx:CheckBox id="recordCheckbox" visible="false"
<mx:Label text="{ResourceUtil.getInstance().getString('bbb.users.breakout.record')}"/>
<mx:CheckBox id="recordCheckbox"
accessibilityName="{ResourceUtil.getInstance().getString('bbb.users.breakout.recordCheckbox.accessibilityName')}"/>
</mx:HBox>
<mx:Tile id="roomsContainer" styleName="roomsContainer" width="100%" height="100%"/>

View File

@ -37,14 +37,14 @@
protected function listenToBreakoutRoom(event:MouseEvent):void {
var e:BreakoutRoomEvent = new BreakoutRoomEvent(BreakoutRoomEvent.LISTEN_IN);
e.breakoutId = data.breakoutId as String;
e.breakoutMeetingId = data.meetingId as String;
e.listen = listenBtn.selected;
globalDispatch.dispatchEvent(e);
}
protected function requestBreakoutJoinUrl(event:MouseEvent):void {
var e:BreakoutRoomEvent = new BreakoutRoomEvent(BreakoutRoomEvent.REQUEST_BREAKOUT_JOIN_URL);
e.breakoutId = data.breakoutId as String;
e.breakoutMeetingId = data.externalMeetingId as String;
e.userId = UserManager.getInstance().getConference().getMyUserId();
globalDispatch.dispatchEvent(e);
}

View File

@ -55,7 +55,6 @@
import mx.events.MenuEvent;
import mx.managers.PopUpManager;
import org.as3commons.lang.StringUtils;
import org.as3commons.logging.api.ILogger;
import org.as3commons.logging.api.getClassLogger;
import org.bigbluebutton.common.IBbbModuleWindow;
@ -343,7 +342,7 @@
// We display only one alert
removeJoinAlert();
joinAlert = Alert.show(
ResourceUtil.getInstance().getString('bbb.users.breakout.openJoinURL', [StringUtils.substringAfterLast(event.breakoutId, "-")]),
ResourceUtil.getInstance().getString('bbb.users.breakout.openJoinURL', [event.breakoutMeetingSequence]),
ResourceUtil.getInstance().getString('bbb.users.breakout.confirm'),
Alert.YES | Alert.NO,
null,
@ -514,7 +513,7 @@
var br:BreakoutRoom = UserManager.getInstance().getConference().getBreakoutRoomInListen();
if (br != null) {
var e:BreakoutRoomEvent = new BreakoutRoomEvent(BreakoutRoomEvent.LISTEN_IN);
e.breakoutId = br.breakoutId;
e.breakoutMeetingId = br.meetingId;
e.listen = false;
dispatcher.dispatchEvent(e);
}
@ -595,7 +594,7 @@
}
private function breakoutRoomNameLabelFunction(item:Object, column:DataGridColumn) : String {
return ResourceUtil.getInstance().getString('bbb.users.roomsGrid.room') + " " + StringUtils.substringAfterLast(item.breakoutId, "-");
return ResourceUtil.getInstance().getString('bbb.users.roomsGrid.room') + " " + item.sequence;
}
]]>
</mx:Script>
@ -633,7 +632,7 @@
<mx:columns>
<mx:DataGridColumn labelFunction="breakoutRoomNameLabelFunction" headerText="{ResourceUtil.getInstance().getString('bbb.users.roomsGrid.room')}" />
<mx:DataGridColumn dataField="numberOfUsers" headerText="{ResourceUtil.getInstance().getString('bbb.users.roomsGrid.users')}"/>
<mx:DataGridColumn dataField="breakoutId" headerText="{ResourceUtil.getInstance().getString('bbb.users.roomsGrid.action')}"
<mx:DataGridColumn dataField="meetingId" headerText="{ResourceUtil.getInstance().getString('bbb.users.roomsGrid.action')}"
visible="{amIModerator}"
itemRenderer="org.bigbluebutton.modules.users.views.RoomActionsRenderer"/>
</mx:columns>

View File

@ -33,7 +33,7 @@
# 2013-04-05 GUG Description is optional in bbb-record --watch
# 2013-04-05 GUG Map internal meeting id with external meeting id
# 2016-07-02 FFD Updates for 1.1
# 2016-09-27 GTR Stricter recording directories names detection to cover breakout rooms recordings
# 2016-10-17 GTR Stricter rule for detection of recording directories names
#set -e
#set -x
@ -408,7 +408,7 @@ if [ $DELETEALL ]; then
rm -f /var/bigbluebutton/screenshare/*.flv
rm -f /var/freeswitch/meetings/*.wav
for meeting in $(ls /var/bigbluebutton | grep "^[0-9a-f]\{40\}-[[:digit:]]\{13\}-\?[[:digit:]]\{1\}\?$"); do
for meeting in $(ls /var/bigbluebutton | grep "^[0-9a-f]\{40\}-[[:digit:]]\{13\}$"); do
echo "deleting: $meeting"
rm -rf /var/bigbluebutton/$meeting
done
@ -448,10 +448,10 @@ if [ -z $HEAD ]; then
fi
tmp_file=$(mktemp)
ls -t /var/bigbluebutton | grep "^[0-9a-f]\{40\}-[[:digit:]]\{13\}-\?[[:digit:]]\{1\}\?$" | head -n $HEAD > $tmp_file
ls -t /var/bigbluebutton/recording/raw | grep "^[0-9a-f]\{40\}-[[:digit:]]\{13\}-\?[[:digit:]]\{1\}\?$" | head -n $HEAD >> $tmp_file
ls -t /var/bigbluebutton | grep "^[0-9a-f]\{40\}-[[:digit:]]\{13\}$" | head -n $HEAD > $tmp_file
ls -t /var/bigbluebutton/recording/raw | grep "^[0-9a-f]\{40\}-[[:digit:]]\{13\}$" | head -n $HEAD >> $tmp_file
#for meeting in $(ls -t /var/bigbluebutton | grep "^[0-9a-f]\{40\}-[[:digit:]]\{13\}-\?[[:digit:]]\{1\}\?$" | head -n $HEAD); do
#for meeting in $(ls -t /var/bigbluebutton | grep "^[0-9a-f]\{40\}-[[:digit:]]\{13\}$" | head -n $HEAD); do
for meeting in $(cat $tmp_file | sort -t - -k 2 -r| uniq); do
echo -n "$meeting"
timestamp=$(echo $meeting | sed s/.*-//g)

Binary file not shown.

View File

@ -3,5 +3,4 @@ import sendChat from './methods/sendChat';
Meteor.methods({
sendChat,
sendChatMessagetoServer: sendChat, // legacy
});

View File

@ -14,9 +14,9 @@ eventEmitter.on('get_whiteboard_shapes_reply', function (arg) {
let whiteboardId = shape.wb_id;
addShapeToCollection(meetingId, whiteboardId, shape);
}
return arg.callback();
}
return arg.callback();
});
eventEmitter.on('send_whiteboard_shape_message', function (arg) {

View File

@ -1,98 +1,96 @@
import React, { Component, PropTypes } from 'react';
import { defineMessages, injectIntl } from 'react-intl';
import Button from '/imports/ui/components/button/component';
import Dropdown from '/imports/ui/components/dropdown/component';
import DropdownTrigger from '/imports/ui/components/dropdown/trigger/component';
import DropdownContent from '/imports/ui/components/dropdown/content/component';
import DropdownList from '/imports/ui/components/dropdown/list/component';
import DropdownListItem from '/imports/ui/components/dropdown/list/item/component';
const intlMessages = defineMessages({
actionsLabel: {
id: 'app.actionsBar.actionsDropdown.actionsLabel',
//defaultMessage: 'Actions',
},
presentationLabel: {
id: 'app.actionsBar.actionsDropdown.presentationLabel',
defaultMessage: 'Upload a presentation',
},
initPollLabel: {
id: 'app.actionsBar.actionsDropdown.initPollLabel',
defaultMessage: 'Initiate a poll',
},
desktopShareLabel: {
id: 'app.actionsBar.actionsDropdown.desktopShareLabel',
defaultMessage: 'Share your screen',
},
presentationDesc: {
id: 'app.actionsBar.actionsDropdown.presentationDesc',
defaultMessage: 'Upload your presentation',
},
initPollDesc: {
id: 'app.actionsBar.actionsDropdown.initPollDesc',
defaultMessage: 'Initiate a poll',
},
desktopShareDesc: {
id: 'app.actionsBar.actionsDropdown.desktopShareDesc',
defaultMessage: 'Share your screen with others',
},
});
const presentation = () => {console.log('Should show the uploader component');};
const polling = () => {console.log('Should initiate a polling');};
const shareScreen = () => {console.log('Should start screen sharing');};
class ActionsDropdown extends Component {
constructor(props) {
super(props);
}
render() {
const { intl } = this.props;
return (
<Dropdown ref="dropdown">
<DropdownTrigger>
<Button
label={intl.formatMessage(intlMessages.actionsLabel)}
icon="circle-add"
color="primary"
size="lg"
circle={true}
onClick={() => null}
/>
</DropdownTrigger>
<DropdownContent placement="top left">
<DropdownList>
<DropdownListItem
icon="presentation"
label={intl.formatMessage(intlMessages.presentationLabel)}
description={intl.formatMessage(intlMessages.presentationDesc)}
onClick={presentation.bind(this)}
/>
{/* These icons are unaligned because of the font issue
Check it later */}
<DropdownListItem
icon="polling"
label={intl.formatMessage(intlMessages.initPollLabel)}
description={intl.formatMessage(intlMessages.initPollDesc)}
onClick={polling.bind(this)}
/>
<DropdownListItem
icon="desktop"
label={intl.formatMessage(intlMessages.desktopShareLabel)}
description={intl.formatMessage(intlMessages.desktopShareDesc)}
onClick={shareScreen.bind(this)}
/>
</DropdownList>
</DropdownContent>
</Dropdown>
);
}
}
export default injectIntl(ActionsDropdown);
import React, { Component, PropTypes } from 'react';
import { defineMessages, injectIntl } from 'react-intl';
import Button from '/imports/ui/components/button/component';
import Dropdown from '/imports/ui/components/dropdown/component';
import DropdownTrigger from '/imports/ui/components/dropdown/trigger/component';
import DropdownContent from '/imports/ui/components/dropdown/content/component';
import DropdownList from '/imports/ui/components/dropdown/list/component';
import DropdownListItem from '/imports/ui/components/dropdown/list/item/component';
const intlMessages = defineMessages({
actionsLabel: {
id: 'app.actionsBar.actionsDropdown.actionsLabel',
},
presentationLabel: {
id: 'app.actionsBar.actionsDropdown.presentationLabel',
defaultMessage: 'Upload a presentation',
},
initPollLabel: {
id: 'app.actionsBar.actionsDropdown.initPollLabel',
defaultMessage: 'Initiate a poll',
},
desktopShareLabel: {
id: 'app.actionsBar.actionsDropdown.desktopShareLabel',
defaultMessage: 'Share your screen',
},
presentationDesc: {
id: 'app.actionsBar.actionsDropdown.presentationDesc',
defaultMessage: 'Upload your presentation',
},
initPollDesc: {
id: 'app.actionsBar.actionsDropdown.initPollDesc',
defaultMessage: 'Initiate a poll',
},
desktopShareDesc: {
id: 'app.actionsBar.actionsDropdown.desktopShareDesc',
defaultMessage: 'Share your screen with others',
},
});
const presentation = () => {console.log('Should show the uploader component');};
const polling = () => {console.log('Should initiate a polling');};
const shareScreen = () => {console.log('Should start screen sharing');};
class ActionsDropdown extends Component {
constructor(props) {
super(props);
}
render() {
const { intl } = this.props;
return (
<Dropdown ref="dropdown">
<DropdownTrigger>
<Button
label={intl.formatMessage(intlMessages.actionsLabel)}
icon="circle-add"
color="primary"
size="lg"
circle={true}
onClick={() => null}
/>
</DropdownTrigger>
<DropdownContent placement="top left">
<DropdownList>
<DropdownListItem
icon="presentation"
label={intl.formatMessage(intlMessages.presentationLabel)}
description={intl.formatMessage(intlMessages.presentationDesc)}
onClick={presentation.bind(this)}
/>
{/* These icons are unaligned because of the font issue
Check it later */}
<DropdownListItem
icon="polling"
label={intl.formatMessage(intlMessages.initPollLabel)}
description={intl.formatMessage(intlMessages.initPollDesc)}
onClick={polling.bind(this)}
/>
<DropdownListItem
icon="desktop"
label={intl.formatMessage(intlMessages.desktopShareLabel)}
description={intl.formatMessage(intlMessages.desktopShareDesc)}
onClick={shareScreen.bind(this)}
/>
</DropdownList>
</DropdownContent>
</Dropdown>
);
}
}
export default injectIntl(ActionsDropdown);

View File

@ -179,7 +179,7 @@ const sendMessage = (receiverID, message) => {
from_color: 0,
};
callServer('sendChatMessagetoServer', messagePayload);
callServer('sendChat', messagePayload);
return messagePayload;
};

View File

@ -51,22 +51,32 @@ const toggleFullScreen = () => {
if (document.fullscreenEnabled
|| document.mozFullScreenEnabled
|| document.webkitFullscreenEnabled) {
if (element.requestFullscreen) {
element.requestFullscreen();
} else if (element.mozRequestFullScreen) {
element.mozRequestFullScreen();
} else if (element.webkitRequestFullscreen) {
element.webkitRequestFullscreen();
} else if (element.msRequestFullscreen) {
element.msRequestFullscreen();
}
} else {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
// If the page is already fullscreen, exit fullscreen
if (document.fullscreenElement
|| document.webkitFullscreenElement
|| document.mozFullScreenElement
|| document.msFullscreenElement) {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.mozCancelFullScreen) {
document.mozCancelFullScreen();
} else if (document.webkitExitFullscreen) {
document.webkitExitFullscreen();
}
// If the page is not currently fullscreen, make fullscreen
} else {
if (element.requestFullscreen) {
element.requestFullscreen();
} else if (element.mozRequestFullScreen) {
element.mozRequestFullScreen();
} else if (element.webkitRequestFullscreen) {
element.webkitRequestFullscreen();
} else if (element.msRequestFullscreen) {
element.msRequestFullscreen();
}
}
}
};

View File

@ -19,6 +19,7 @@ dependencies {
compile 'commons-lang:commons-lang:2.5'
compile 'commons-io:commons-io:2.4'
compile 'commons-codec:commons-codec:1.10'
compile 'com.google.code.gson:gson:1.7.1'
compile 'commons-httpclient:commons-httpclient:3.1'
compile 'com.zaxxer:nuprocess:1.1.0'

View File

@ -1,220 +1,221 @@
#
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
#
# Copyright (c) 2012 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/>.
#
#
# These are the default properites for BigBlueButton Web application
# Default loglevel.
appLogLevel=DEBUG
#----------------------------------------------------
# Directory where BigBlueButton stores uploaded slides
presentationDir=/var/bigbluebutton
#----------------------------------------------------
# Directory where SWFTOOLS (pdf2swf, jpeg2swf, png2swf) are located
swfToolsDir=/usr/bin
#----------------------------------------------------
# Directory where ImageMagick's convert executable is located
imageMagickDir=/usr/bin
#----------------------------------------------------
# Use fullpath to ghostscript executable since the exec names are different
# for each platform.
ghostScriptExec=/usr/bin/gs
#----------------------------------------------------
# Fonts directory passed into PDF2SWF to support highlighting of texts
# in the SWF slides.
fontsDir=/usr/share/fonts
#----------------------------------------------------
# This is a workaround for a problem converting PDF files, referenced at
# http://groups.google.com/group/comp.lang.postscript/browse_thread/thread/c2e264ca76534ce0?pli=1
noPdfMarkWorkaround=/etc/bigbluebutton/nopdfmark.ps
#----------------------------------------------------
# These will be copied in cases where the conversion process
# fails to generate a slide from the uploaded presentation
BLANK_SLIDE=/var/bigbluebutton/blank/blank-slide.swf
BLANK_THUMBNAIL=/var/bigbluebutton/blank/blank-thumb.png
#----------------------------------------------------
# Number of minutes the conversion should take. If it takes
# more than this time, cancel the conversion process.
maxConversionTime=5
#----------------------------------------------------
# Maximum number of pages allowed for an uploaded presentation (default 100).
maxNumPages=200
#----------------------------------------------------
# Maximum swf file size for load to the client (default 500000).
MAX_SWF_FILE_SIZE=500000
#----------------------------------------------------
# Maximum allowed number of place object tags in the converted SWF, if exceeded the conversion will fallback to full BMP (default 8000)
placementsThreshold=8000
# Maximum allowed number of bitmap images in the converted SWF, if exceeded the conversion will fallback to full BMP (default 8000)
imageTagThreshold=8000
# Maximum allowed number of define text tags in the converted SWF, if exceeded the conversion will fallback to full BMP (default 2500)
defineTextThreshold=2500
#------------------------------------
# Number of threads in the pool to do the presentation conversion.
#------------------------------------
numConversionThreads=2
#----------------------------------------------------
# Additional conversion of the presentation slides to SVG
# to be used in the HTML5 client
svgImagesRequired=false
# Default number of digits for voice conference users joining through the PSTN.
defaultNumDigitsForTelVoice=5
#----------------------------------------------------
# Default dial access number
defaultDialAccessNumber=613-555-1234
#----------------------------------------------------
# Default welcome message to display when the participant joins the web
# conference. This is only used for the old scheduling which will be
# removed in the future. Use the API to create a conference.
defaultWelcomeMessage=<br>Welcome to <b>%%CONFNAME%%</b>!<br><br>For help on using BigBlueButton see these (short) <a href="event:http://www.bigbluebutton.org/content/videos"><u>tutorial videos</u></a>.<br><br>To join the audio bridge click the headset icon (upper-left hand corner). Use a headset to avoid causing background noise for others.<br>
defaultWelcomeMessageFooter=This server is running <a href="http://docs.bigbluebutton.org/" target="_blank"><u>BigBlueButton</u></a>.
# Default maximum number of users a meeting can have.
# Current default is 0 (meeting doesn't have a user limit).
defaultMaxUsers=0
# Default duration of the meeting in minutes.
# Current default is 0 (meeting doesn't end).
defaultMeetingDuration=0
# Remove the meeting from memory when the end API is called.
# This allows 3rd-party apps to recycle the meeting right-away
# instead of waiting for the meeting to expire (see below).
removeMeetingWhenEnded=true
# The number of minutes before the system removes the meeting from memory.
defaultMeetingExpireDuration=1
# The number of minutes the system waits when a meeting is created and when
# a user joins. If after this period, a user hasn't joined, the meeting is
# removed from memory.
defaultMeetingCreateJoinDuration=5
# Disable recording by default.
# true - don't record even if record param in the api call is set to record
# false - when record param is passed from api, override this default
disableRecordingDefault=false
# Start recording when first user joins the meeting.
# For backward compatibility with 0.81 where whole meeting
# is recorded.
autoStartRecording=false
# Allow the user to start/stop recording.
allowStartStopRecording=true
#----------------------------------------------------
# This URL is where the BBB client is accessible. When a user sucessfully
# enters a name and password, she is redirected here to load the client.
bigbluebutton.web.serverURL=http://192.168.23.44
#----------------------------------------------------
# Assign URL where the logged-out participant will be redirected after sign-out.
# If "default", it returns to bigbluebutton.web.serverURL
bigbluebutton.web.logoutURL=default
# The url of the BigBlueButton client. User's will be redirected here when
# successfully joining the meeting.
defaultClientUrl=${bigbluebutton.web.serverURL}/client/BigBlueButton.html
#defaultClientUrl=http://192.168.0.235/3rd-party.html
# The default avatar image to display if nothing is passed on the JOIN API (avatarURL)
# call. This avatar is displayed if the user isn't sharing the webcam and
# the option (displayAvatar) is enabled in config.xml
defaultAvatarURL=${bigbluebutton.web.serverURL}/client/avatar.png
# The URL of the default configuration
defaultConfigURL=${bigbluebutton.web.serverURL}/client/conf/config.xml
apiVersion=1.0
# Salt which is used by 3rd-party apps to authenticate api calls
securitySalt=a820d30da2db356124fce5bd5d8054b4
# Directory where we drop the <meeting-id-recorded>.done file
recordStatusDir=/var/bigbluebutton/recording/status/recorded
redisHost=127.0.0.1
redisPort=6379
# The directory where the published/unpublised recordings are located. This is for
# the get recording* api calls
publishedDir=/var/bigbluebutton/published
unpublishedDir=/var/bigbluebutton/unpublished
# The directory where the pre-built configs are stored
configDir=/var/bigbluebutton/configs
# If the API is enabled.
serviceEnabled = true
# Test voiceBridge number
testVoiceBridge=99999
testConferenceMock=conference-mock-default
#------------------------------------------------------
# These properties are used to test the conversion process.
# Conference name folder in ${presentationDir} (see above)
beans.presentationService.testConferenceMock=${testConferenceMock}
# Conference room folder in ${presentationDir}/${testConferenceMock}
beans.presentationService.testRoomMock=conference-mock-default
# Uploaded presentation name
beans.presentationService.testPresentationName=appkonference
# Uploaded presentation file
beans.presentationService.testUploadedPresentation=appkonference.txt
# Default Uploaded presentation file
beans.presentationService.defaultUploadedPresentation=${bigbluebutton.web.serverURL}/default.pdf
presentationBaseURL=${bigbluebutton.web.serverURL}/bigbluebutton/presentation
#----------------------------------------------------
# The URL where the presentations will be loaded from.
#----------------------------------------------------
beans.presentationService.presentationBaseUrl=${presentationBaseURL}
#----------------------------------------------------
# Inject values into grails service beans
beans.presentationService.presentationDir=${presentationDir}
#----------------------------------------------------
# Specify which IPs can do cross domain requests
accessControlAllowOrigin=${bigbluebutton.web.serverURL}
#----------------------------------------------------
# The lapsus of seconds for polling the BBB Server in order to check if it's down.
# After 5 tries if there isn't response, it will be declared down
checkBBBServerEvery=10
#
# BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
#
# Copyright (c) 2012 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/>.
#
#
# These are the default properites for BigBlueButton Web application
# Default loglevel.
appLogLevel=DEBUG
#----------------------------------------------------
# Directory where BigBlueButton stores uploaded slides
presentationDir=/var/bigbluebutton
#----------------------------------------------------
# Directory where SWFTOOLS (pdf2swf, jpeg2swf, png2swf) are located
swfToolsDir=/usr/bin
#----------------------------------------------------
# Directory where ImageMagick's convert executable is located
imageMagickDir=/usr/bin
#----------------------------------------------------
# Use fullpath to ghostscript executable since the exec names are different
# for each platform.
ghostScriptExec=/usr/bin/gs
#----------------------------------------------------
# Fonts directory passed into PDF2SWF to support highlighting of texts
# in the SWF slides.
fontsDir=/usr/share/fonts
#----------------------------------------------------
# This is a workaround for a problem converting PDF files, referenced at
# http://groups.google.com/group/comp.lang.postscript/browse_thread/thread/c2e264ca76534ce0?pli=1
noPdfMarkWorkaround=/etc/bigbluebutton/nopdfmark.ps
#----------------------------------------------------
# These will be copied in cases where the conversion process
# fails to generate a slide from the uploaded presentation
BLANK_SLIDE=/var/bigbluebutton/blank/blank-slide.swf
BLANK_PRESENTATION=/var/bigbluebutton/blank/blank-presentation.pdf
BLANK_THUMBNAIL=/var/bigbluebutton/blank/blank-thumb.png
#----------------------------------------------------
# Number of minutes the conversion should take. If it takes
# more than this time, cancel the conversion process.
maxConversionTime=5
#----------------------------------------------------
# Maximum number of pages allowed for an uploaded presentation (default 100).
maxNumPages=200
#----------------------------------------------------
# Maximum swf file size for load to the client (default 500000).
MAX_SWF_FILE_SIZE=500000
#----------------------------------------------------
# Maximum allowed number of place object tags in the converted SWF, if exceeded the conversion will fallback to full BMP (default 8000)
placementsThreshold=8000
# Maximum allowed number of bitmap images in the converted SWF, if exceeded the conversion will fallback to full BMP (default 8000)
imageTagThreshold=8000
# Maximum allowed number of define text tags in the converted SWF, if exceeded the conversion will fallback to full BMP (default 2500)
defineTextThreshold=2500
#------------------------------------
# Number of threads in the pool to do the presentation conversion.
#------------------------------------
numConversionThreads=2
#----------------------------------------------------
# Additional conversion of the presentation slides to SVG
# to be used in the HTML5 client
svgImagesRequired=false
# Default number of digits for voice conference users joining through the PSTN.
defaultNumDigitsForTelVoice=5
#----------------------------------------------------
# Default dial access number
defaultDialAccessNumber=613-555-1234
#----------------------------------------------------
# Default welcome message to display when the participant joins the web
# conference. This is only used for the old scheduling which will be
# removed in the future. Use the API to create a conference.
defaultWelcomeMessage=<br>Welcome to <b>%%CONFNAME%%</b>!<br><br>For help on using BigBlueButton see these (short) <a href="event:http://www.bigbluebutton.org/content/videos"><u>tutorial videos</u></a>.<br><br>To join the audio bridge click the headset icon (upper-left hand corner). Use a headset to avoid causing background noise for others.<br>
defaultWelcomeMessageFooter=This server is running <a href="http://docs.bigbluebutton.org/" target="_blank"><u>BigBlueButton</u></a>.
# Default maximum number of users a meeting can have.
# Current default is 0 (meeting doesn't have a user limit).
defaultMaxUsers=0
# Default duration of the meeting in minutes.
# Current default is 0 (meeting doesn't end).
defaultMeetingDuration=0
# Remove the meeting from memory when the end API is called.
# This allows 3rd-party apps to recycle the meeting right-away
# instead of waiting for the meeting to expire (see below).
removeMeetingWhenEnded=true
# The number of minutes before the system removes the meeting from memory.
defaultMeetingExpireDuration=1
# The number of minutes the system waits when a meeting is created and when
# a user joins. If after this period, a user hasn't joined, the meeting is
# removed from memory.
defaultMeetingCreateJoinDuration=5
# Disable recording by default.
# true - don't record even if record param in the api call is set to record
# false - when record param is passed from api, override this default
disableRecordingDefault=false
# Start recording when first user joins the meeting.
# For backward compatibility with 0.81 where whole meeting
# is recorded.
autoStartRecording=false
# Allow the user to start/stop recording.
allowStartStopRecording=true
#----------------------------------------------------
# This URL is where the BBB client is accessible. When a user sucessfully
# enters a name and password, she is redirected here to load the client.
bigbluebutton.web.serverURL=http://192.168.23.44
#----------------------------------------------------
# Assign URL where the logged-out participant will be redirected after sign-out.
# If "default", it returns to bigbluebutton.web.serverURL
bigbluebutton.web.logoutURL=default
# The url of the BigBlueButton client. User's will be redirected here when
# successfully joining the meeting.
defaultClientUrl=${bigbluebutton.web.serverURL}/client/BigBlueButton.html
#defaultClientUrl=http://192.168.0.235/3rd-party.html
# The default avatar image to display if nothing is passed on the JOIN API (avatarURL)
# call. This avatar is displayed if the user isn't sharing the webcam and
# the option (displayAvatar) is enabled in config.xml
defaultAvatarURL=${bigbluebutton.web.serverURL}/client/avatar.png
# The URL of the default configuration
defaultConfigURL=${bigbluebutton.web.serverURL}/client/conf/config.xml
apiVersion=1.0
# Salt which is used by 3rd-party apps to authenticate api calls
securitySalt=a820d30da2db356124fce5bd5d8054b4
# Directory where we drop the <meeting-id-recorded>.done file
recordStatusDir=/var/bigbluebutton/recording/status/recorded
redisHost=127.0.0.1
redisPort=6379
# The directory where the published/unpublised recordings are located. This is for
# the get recording* api calls
publishedDir=/var/bigbluebutton/published
unpublishedDir=/var/bigbluebutton/unpublished
# The directory where the pre-built configs are stored
configDir=/var/bigbluebutton/configs
# If the API is enabled.
serviceEnabled = true
# Test voiceBridge number
testVoiceBridge=99999
testConferenceMock=conference-mock-default
#------------------------------------------------------
# These properties are used to test the conversion process.
# Conference name folder in ${presentationDir} (see above)
beans.presentationService.testConferenceMock=${testConferenceMock}
# Conference room folder in ${presentationDir}/${testConferenceMock}
beans.presentationService.testRoomMock=conference-mock-default
# Uploaded presentation name
beans.presentationService.testPresentationName=appkonference
# Uploaded presentation file
beans.presentationService.testUploadedPresentation=appkonference.txt
# Default Uploaded presentation file
beans.presentationService.defaultUploadedPresentation=${bigbluebutton.web.serverURL}/default.pdf
presentationBaseURL=${bigbluebutton.web.serverURL}/bigbluebutton/presentation
#----------------------------------------------------
# The URL where the presentations will be loaded from.
#----------------------------------------------------
beans.presentationService.presentationBaseUrl=${presentationBaseURL}
#----------------------------------------------------
# Inject values into grails service beans
beans.presentationService.presentationDir=${presentationDir}
#----------------------------------------------------
# Specify which IPs can do cross domain requests
accessControlAllowOrigin=${bigbluebutton.web.serverURL}
#----------------------------------------------------
# The lapsus of seconds for polling the BBB Server in order to check if it's down.
# After 5 tries if there isn't response, it will be declared down
checkBBBServerEvery=10

View File

@ -61,6 +61,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
<property name="presentationBaseURL" value="${presentationBaseURL}"/>
<property name="pageExtractor" ref="pageExtractor"/>
<property name="documentConversionService" ref="documentConversionService"/>
<property name="blankPresentation" value="${BLANK_PRESENTATION}"/>
</bean>
<bean id="recordingService" class="org.bigbluebutton.api.RecordingService" >

View File

@ -286,19 +286,9 @@ class ApiController {
return
}
Boolean isBreakoutRoom = false
if(!StringUtils.isEmpty(params.isBreakout)) {
isBreakoutRoom = new Boolean(StringUtils.strip(params.isBreakout))
}
// Everything is good so far. Translate the external meeting id to an internal meeting id. If
// we can't find the meeting, complain.
String internalMeetingId = paramsProcessorUtil.convertToInternalMeetingId(externalMeetingId);
if (isBreakoutRoom) {
// This is a join request for a breakout room. Use the passed meetingId to find the meeting.
internalMeetingId = externalMeetingId
log.info("Join request for breakout room " + internalMeetingId)
}
log.info("Retrieving meeting ${internalMeetingId}")
Meeting meeting = meetingService.getMeeting(internalMeetingId);
@ -869,6 +859,10 @@ class ApiController {
meeting {
meetingID() { mkp.yield(m.getExternalId()) }
internalMeetingID() { mkp.yield(m.getInternalId()) }
if (m.isBreakout()) {
parentMeetingID() { mkp.yield(m.getParentMeetingId()) }
sequence(m.getSequence())
}
isBreakout() { mkp.yield(m.isBreakout()) }
meetingName() { mkp.yield(m.getName()) }
createTime(m.getCreateTime())
@ -2136,6 +2130,10 @@ class ApiController {
isBreakout() { mkp.yield(meeting.isBreakout()) }
meetingID() { mkp.yield(meeting.getExternalId()) }
internalMeetingID(meeting.getInternalId())
if (m.isBreakout()) {
parentMeetingID() { mkp.yield(meeting.getParentMeetingId()) }
sequence(meeting.getSequence())
}
createTime(meeting.getCreateTime())
createDate(formatPrettyDate(meeting.getCreateTime()))
voiceBridge() { mkp.yield(meeting.getTelVoice()) }
@ -2196,6 +2194,7 @@ class ApiController {
returncode(RESP_CODE_SUCCESS)
meetingID() { mkp.yield(meeting.getExternalId()) }
internalMeetingID() { mkp.yield(meeting.getInternalId()) }
parentMeetingID() { mkp.yield(meeting.getParentMeetingId()) }
attendeePW() { mkp.yield(meeting.getViewerPassword()) }
moderatorPW() { mkp.yield(meeting.getModeratorPassword()) }
createTime(meeting.getCreateTime())

View File

@ -19,9 +19,6 @@
package org.bigbluebutton.api;
import java.io.File;
import java.io.FileFilter;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;

View File

@ -301,15 +301,26 @@ public class MeetingService implements MessageListener {
// TODO: Need a better way to store these values for recordings
metadata.put("meetingId", m.getExternalId());
metadata.put("meetingName", m.getName());
metadata.put("isBreakout", m.isBreakout().toString());
messagingService.recordMeetingInfo(m.getInternalId(), metadata);
Map<String, String> breakoutMetadata = new TreeMap<String, String>();
if (m.isBreakout()){
breakoutMetadata.put("sequence", m.getSequence().toString());
breakoutMetadata.put("parentMeetingId", m.getParentMeetingId());
}
messagingService.recordMeetingInfo(m.getInternalId(), metadata, breakoutMetadata);
}
Map<String, Object> logData = new HashMap<String, Object>();
logData.put("meetingId", m.getInternalId());
logData.put("externalMeetingId", m.getExternalId());
if (m.isBreakout()){
logData.put("sequence", m.getSequence());
logData.put("parentMeetingId", m.getParentMeetingId());
}
logData.put("name", m.getName());
logData.put("duration", m.getDuration());
logData.put("isBreakout", m.isBreakout());
logData.put("record", m.isRecord());
logData.put("event", "create_meeting");
logData.put("description", "Create meeting.");
@ -320,11 +331,11 @@ public class MeetingService implements MessageListener {
log.info("Create meeting: data={}", logStr);
messagingService.createMeeting(m.getInternalId(), m.getExternalId(),
m.getName(), m.isRecord(), m.getTelVoice(), m.getDuration(),
m.getAutoStartRecording(), m.getAllowStartStopRecording(),
m.getModeratorPassword(), m.getViewerPassword(),
m.getCreateTime(), formatPrettyDate(m.getCreateTime()),
m.isBreakout());
m.getParentMeetingId(), m.getName(), m.isRecord(),
m.getTelVoice(), m.getDuration(), m.getAutoStartRecording(),
m.getAllowStartStopRecording(), m.getModeratorPassword(),
m.getViewerPassword(), m.getCreateTime(),
formatPrettyDate(m.getCreateTime()), m.isBreakout(), m.getSequence());
}
private String formatPrettyDate(Long timestamp) {
@ -359,15 +370,9 @@ public class MeetingService implements MessageListener {
public Meeting getMeeting(String meetingId) {
if (meetingId == null)
return null;
int dashes = meetingId.split("-", -1).length - 1;
for (String key : meetings.keySet()) {
int keyDashes = key.split("-", -1).length - 1;
if (dashes == 2
&& key.equals(meetingId)
|| (dashes < 2 && keyDashes < 2 && key
.startsWith(meetingId))) {
if (key.startsWith(meetingId))
return (Meeting) meetings.get(key);
}
}
return null;
@ -515,20 +520,21 @@ public class MeetingService implements MessageListener {
}
private void processCreateBreakoutRoom(CreateBreakoutRoom message) {
Meeting parentMeeting = getMeeting(message.parentId);
Meeting parentMeeting = getMeeting(message.parentMeetingId);
if (parentMeeting != null) {
Map<String, String> params = new HashMap<String, String>();
params.put("name", message.name);
params.put("breakoutId", message.breakoutId);
params.put("meetingID", message.parentId);
params.put("meetingID", message.meetingId);
params.put("parentMeetingID", message.parentMeetingId);
params.put("isBreakout", "true");
params.put("sequence", message.sequence.toString());
params.put("attendeePW", message.viewerPassword);
params.put("moderatorPW", message.moderatorPassword);
params.put("voiceBridge", message.voiceConfId);
params.put("duration", message.durationInMinutes.toString());
params.put("record", message.record.toString());
params.put("welcome", getMeeting(message.parentId)
params.put("welcome", getMeeting(message.parentMeetingId)
.getWelcomeMessageTemplate());
Map<String, String> parentMeetingMetadata = parentMeeting
@ -545,17 +551,18 @@ public class MeetingService implements MessageListener {
handleCreateMeeting(breakout);
presDownloadService.extractPage(message.parentId,
presDownloadService.extractPage(message.parentMeetingId,
message.sourcePresentationId,
message.sourcePresentationSlide, breakout.getInternalId());
} else {
log.error("Failed to create breakout room " + message.breakoutId
+ ".Reason: Parent meeting not found.");
log.error(
"Failed to create breakout room {}.Reason: Parent meeting {} not found.",
message.meetingId, message.parentMeetingId);
}
}
private void processEndBreakoutRoom(EndBreakoutRoom message) {
processEndMeeting(new EndMeeting(message.breakoutId));
processEndMeeting(new EndMeeting(message.breakoutMeetingId));
}
private void processEndMeeting(EndMeeting message) {
@ -591,6 +598,9 @@ public class MeetingService implements MessageListener {
Map<String, Object> logData = new HashMap<String, Object>();
logData.put("meetingId", m.getInternalId());
logData.put("externalMeetingId", m.getExternalId());
if (m.isBreakout()) {
logData.put("parentMeetingId", m.getParentMeetingId());
}
logData.put("name", m.getName());
logData.put("duration", m.getDuration());
logData.put("record", m.isRecord());
@ -601,11 +611,14 @@ public class MeetingService implements MessageListener {
Gson gson = new Gson();
String logStr = gson.toJson(logData);
log.info("Meeting restarted: data={}", logStr);
log.info("Meeting started: data={}", logStr);
} else {
Map<String, Object> logData = new HashMap<String, Object>();
logData.put("meetingId", m.getInternalId());
logData.put("externalMeetingId", m.getExternalId());
if (m.isBreakout()) {
logData.put("parentMeetingId", m.getParentMeetingId());
}
logData.put("name", m.getName());
logData.put("duration", m.getDuration());
logData.put("record", m.isRecord());

View File

@ -288,84 +288,108 @@ public class ParamsProcessorUtil {
return metas;
}
public Meeting processCreateParams(Map<String, String> params) {
String meetingName = params.get("name");
if(meetingName == null){
meetingName = "";
}
String externalMeetingId = params.get("meetingID");
String viewerPass = processPassword(params.get("attendeePW"));
String modPass = processPassword(params.get("moderatorPW"));
// Get the digits for voice conference for users joining through the phone.
// If none is provided, generate one.
String telVoice = processTelVoice(params.get("voiceBridge"));
// Get the voice conference digits/chars for users joing through VOIP on the client.
// If none is provided, make it the same as the telVoice. If one has been provided,
// we expect that the users will be joined in the same voice conference.
String webVoice = params.get("webVoice");
if (StringUtils.isEmpty(webVoice)) {
webVoice = telVoice;
}
// Get all the other relevant parameters and generate defaults if none has been provided.
String dialNumber = processDialNumber(params.get("dialNumber"));
String logoutUrl = processLogoutUrl(params.get("logoutURL"));
boolean record = processRecordMeeting(params.get("record"));
int maxUsers = processMaxUser(params.get("maxParticipants"));
int meetingDuration = processMeetingDuration(params.get("duration"));
public Meeting processCreateParams(Map<String, String> params) {
String meetingName = params.get("name");
if (meetingName == null) {
meetingName = "";
}
String externalMeetingId = params.get("meetingID");
String viewerPass = processPassword(params.get("attendeePW"));
String modPass = processPassword(params.get("moderatorPW"));
// Get the digits for voice conference for users joining through the
// phone.
// If none is provided, generate one.
String telVoice = processTelVoice(params.get("voiceBridge"));
// Get the voice conference digits/chars for users joing through VOIP on
// the client.
// If none is provided, make it the same as the telVoice. If one has
// been provided,
// we expect that the users will be joined in the same voice conference.
String webVoice = params.get("webVoice");
if (StringUtils.isEmpty(webVoice)) {
webVoice = telVoice;
}
// Get all the other relevant parameters and generate defaults if none
// has been provided.
String dialNumber = processDialNumber(params.get("dialNumber"));
String logoutUrl = processLogoutUrl(params.get("logoutURL"));
boolean record = processRecordMeeting(params.get("record"));
int maxUsers = processMaxUser(params.get("maxParticipants"));
int meetingDuration = processMeetingDuration(params.get("duration"));
// set is breakout room property
boolean isBreakout = false;
if (!StringUtils.isEmpty(params.get("isBreakout"))) {
isBreakout = new Boolean(params.get("isBreakout"));
}
String welcomeMessageTemplate = processWelcomeMessage(params.get("welcome"), isBreakout);
String welcomeMessage = substituteKeywords(welcomeMessageTemplate, dialNumber, telVoice, meetingName);
String internalMeetingId = convertToInternalMeetingId(externalMeetingId);
// Check if this is a test meeting. NOTE: This should not belong here. Extract this out.
if (isTestMeeting(telVoice)) {
internalMeetingId = getIntMeetingIdForTestMeeting(telVoice);
}
boolean autoStartRec = autoStartRecording;
if (!StringUtils.isEmpty(params.get("autoStartRecording"))) {
try {
autoStartRec = Boolean.parseBoolean(params.get("autoStartRecording"));
} catch(Exception ex){
log.warn("Invalid param [autoStartRecording] for meeting=[" + internalMeetingId + "]");
}
}
String welcomeMessageTemplate = processWelcomeMessage(
params.get("welcome"), isBreakout);
String welcomeMessage = substituteKeywords(welcomeMessageTemplate,
dialNumber, telVoice, meetingName);
boolean allowStartStoptRec = allowStartStopRecording;
if (!StringUtils.isEmpty(params.get("allowStartStopRecording"))) {
try {
allowStartStoptRec = Boolean.parseBoolean(params.get("allowStartStopRecording"));
} catch(Exception ex){
log.warn("Invalid param [allowStartStopRecording] for meeting=[" + internalMeetingId + "]");
}
}
// Collect metadata for this meeting that the third-party app wants to store if meeting is recorded.
Map<String, String> meetingInfo = new HashMap<String, String>();
meetingInfo = processMetaParam(params);
// Create a unique internal id by appending the current time. This way, the 3rd-party
// app can reuse the external meeting id.
long createTime = System.currentTimeMillis();
internalMeetingId = internalMeetingId + '-' + new Long(createTime).toString();
String internalMeetingId = convertToInternalMeetingId(externalMeetingId);
// Check if this is a test meeting. NOTE: This should not belong here.
// Extract this out.
if (isTestMeeting(telVoice)) {
internalMeetingId = getIntMeetingIdForTestMeeting(telVoice);
}
boolean autoStartRec = autoStartRecording;
if (!StringUtils.isEmpty(params.get("autoStartRecording"))) {
try {
autoStartRec = Boolean.parseBoolean(params
.get("autoStartRecording"));
} catch (Exception ex) {
log.warn("Invalid param [autoStartRecording] for meeting=[{}]",
internalMeetingId);
}
}
boolean allowStartStoptRec = allowStartStopRecording;
if (!StringUtils.isEmpty(params.get("allowStartStopRecording"))) {
try {
allowStartStoptRec = Boolean.parseBoolean(params
.get("allowStartStopRecording"));
} catch (Exception ex) {
log.warn(
"Invalid param [allowStartStopRecording] for meeting=[{}]",
internalMeetingId);
}
}
// Collect metadata for this meeting that the third-party app wants to
// store if meeting is recorded.
Map<String, String> meetingInfo = new HashMap<String, String>();
meetingInfo = processMetaParam(params);
// Create a unique internal id by appending the current time. This way,
// the 3rd-party
// app can reuse the external meeting id.
long createTime = System.currentTimeMillis();
internalMeetingId = internalMeetingId.concat("-").concat(
new Long(createTime).toString());
// If this create meeting request is for a breakout room, we just used
// the passed in breakoutId as the internal meetingId so we can
// correlate
// the breakout meeting with it's parent meeting.
// we need to generate a unique internal and external id and keep
// tracks of the parent meeting id
String parentMeetingId = new String();
if (isBreakout) {
internalMeetingId = params.get("breakoutId");
internalMeetingId = params.get("meetingID");
parentMeetingId = params.get("parentMeetingID");
// We rebuild the the external meeting using the has of the parent
// meeting, the shared timestamp and the sequence number
String timeStamp = StringUtils.substringAfter(internalMeetingId,
"-");
String externalHash = DigestUtils.shaHex(parentMeetingId
.concat("-").concat(timeStamp.toString()).concat("-")
.concat(params.get("sequence")));
externalMeetingId = externalHash.concat("-").concat(timeStamp);
}
// Create the meeting with all passed in parameters.
@ -392,8 +416,14 @@ public class ParamsProcessorUtil {
meeting.setModeratorOnlyMessage(moderatorOnlyMessage);
}
// Add extra parameters for breakout room
if (isBreakout) {
meeting.setSequence(Integer.parseInt(params.get("sequence")));
meeting.setParentMeetingId(parentMeetingId);
}
return meeting;
}
}
public String getApiVersion() {
return apiVersion;

View File

@ -21,11 +21,11 @@ package org.bigbluebutton.api.domain;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import org.apache.commons.lang.RandomStringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -37,7 +37,9 @@ public class Meeting {
private String name;
private String extMeetingId;
private String intMeetingId;
private String intMeetingId;
private String parentMeetingId;
private Integer sequence = 0;
private Integer duration = 0;
private long createdTime = 0;
private long startTime = 0;
@ -152,7 +154,15 @@ public class Meeting {
public long getCreateTime() {
return createdTime;
}
public Integer setSequence(Integer s) {
return sequence = s;
}
public Integer getSequence() {
return sequence;
}
public Integer getDuration() {
return duration;
}
@ -201,6 +211,14 @@ public class Meeting {
return intMeetingId;
}
public String setParentMeetingId(String p) {
return parentMeetingId = p;
}
public String getParentMeetingId() {
return parentMeetingId;
}
public String getWebVoice() {
return webVoice;
}
@ -375,7 +393,7 @@ public class Meeting {
public static class Builder {
private String name;
private String externalId;
private String internalId;
private String internalId;
private int maxUsers;
private boolean record;
private boolean autoStartRecording;
@ -393,18 +411,18 @@ public class Meeting {
private String defaultAvatarURL;
private long createdTime;
private boolean isBreakout;
public Builder(String externalId, String internalId, long createTime) {
this.externalId = externalId;
this.internalId = internalId;
this.createdTime = createTime;
}
public Builder withName(String name) {
this.name = name;
return this;
}
public Builder withDuration(int minutes) {
duration = minutes;
return this;
@ -474,7 +492,7 @@ public class Meeting {
isBreakout = b;
return this;
}
public Builder withLogoutUrl(String l) {
logoutUrl = l;
return this;

View File

@ -72,13 +72,14 @@ public class MeetingMessageHandler implements MessageHandler {
CreateBreakoutRoomRequest msg = new Gson().fromJson(message, CreateBreakoutRoomRequest.class);
for (MessageListener listener : listeners) {
listener.handle(new CreateBreakoutRoom(
msg.payload.breakoutId,
msg.payload.parentId,
msg.payload.name,
msg.payload.voiceConfId,
msg.payload.viewerPassword,
msg.payload.moderatorPassword,
msg.payload.durationInMinutes,
msg.payload.breakoutMeetingId,
msg.payload.parentMeetingId,
msg.payload.name,
msg.payload.sequence,
msg.payload.voiceConfId,
msg.payload.viewerPassword,
msg.payload.moderatorPassword,
msg.payload.durationInMinutes,
msg.payload.sourcePresentationId,
msg.payload.sourcePresentationSlide,
msg.payload.record
@ -88,7 +89,7 @@ public class MeetingMessageHandler implements MessageHandler {
}
else if (EndBreakoutRoomRequest.NAME.equals(messageName)) {
EndBreakoutRoomRequest msg = new Gson().fromJson(message, EndBreakoutRoomRequest.class);
log.info("Received an end breakout room request message for breakout meeting id=[{}]", msg.payload.meetingId);
log.info("Received end breakout room request message for breakout meeting id=[{}]", msg.payload.meetingId);
for (MessageListener listener : listeners) {
listener.handle(new EndBreakoutRoom(msg.payload.meetingId));
}

View File

@ -7,7 +7,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPubSub;
import redis.clients.jedis.exceptions.JedisConnectionException;

View File

@ -27,12 +27,14 @@ import java.util.Map;
import java.util.Set;
public interface MessagingService {
void recordMeetingInfo(String meetingId, Map<String, String> info);
void recordMeetingInfo(String meetingId, Map<String, String> info, Map<String, String> breakoutInfo);
void destroyMeeting(String meetingID);
void createMeeting(String meetingID, String externalMeetingID, String meetingName, Boolean recorded,
String voiceBridge, Integer duration, Boolean autoStartRecording,
Boolean allowStartStopRecording, String moderatorPass, String viewerPass,
Long createTime, String createDate, Boolean isBreakout);
void createMeeting(String meetingID, String externalMeetingID,
String parentMeetingID, String meetingName, Boolean recorded,
String voiceBridge, Integer duration, Boolean autoStartRecording,
Boolean allowStartStopRecording, String moderatorPass,
String viewerPass, Long createTime, String createDate,
Boolean isBreakout, Integer sequence);
void endMeeting(String meetingId);
void send(String channel, String message);
void sendPolls(String meetingId, String title, String question, String questionType, List<String> answers);

View File

@ -19,28 +19,27 @@
package org.bigbluebutton.api.messaging;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import javax.imageio.ImageIO;
import java.util.Set;
import org.bigbluebutton.api.messaging.converters.messages.DestroyMeetingMessage;
import org.bigbluebutton.api.messaging.converters.messages.EndMeetingMessage;
import org.bigbluebutton.api.messaging.converters.messages.RegisterUserMessage;
import org.bigbluebutton.common.converters.ToJsonEncoder;
import org.bigbluebutton.common.messages.Constants;
import org.bigbluebutton.common.messages.MessagingConstants;
import org.bigbluebutton.common.messages.SendStunTurnInfoReplyMessage;
import org.bigbluebutton.messages.CreateMeetingRequest;
import org.bigbluebutton.messages.CreateMeetingRequest.CreateMeetingRequestPayload;
import org.bigbluebutton.common.messages.Constants;
import org.bigbluebutton.common.messages.PubSubPingMessage;
import org.bigbluebutton.common.messages.payload.PubSubPingMessagePayload;
import org.bigbluebutton.common.messages.SendStunTurnInfoReplyMessage;
import org.bigbluebutton.web.services.turn.StunServer;
import org.bigbluebutton.web.services.turn.TurnEntry;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.gson.Gson;
public class RedisMessagingService implements MessagingService {
@ -50,8 +49,8 @@ public class RedisMessagingService implements MessagingService {
private MessageSender sender;
private ToJsonEncoder encoder = new ToJsonEncoder();
public void recordMeetingInfo(String meetingId, Map<String, String> info) {
storeService.recordMeetingInfo(meetingId, info);
public void recordMeetingInfo(String meetingId, Map<String, String> info, Map<String, String> breakoutInfo) {
storeService.recordMeetingInfo(meetingId, info, breakoutInfo);
}
public void destroyMeeting(String meetingID) {
@ -68,22 +67,24 @@ public class RedisMessagingService implements MessagingService {
sender.send(MessagingConstants.TO_MEETING_CHANNEL, json);
}
public void createMeeting(String meetingID, String externalMeetingID, String meetingName, Boolean recorded,
String voiceBridge, Integer duration,
Boolean autoStartRecording, Boolean allowStartStopRecording,
String moderatorPass, String viewerPass, Long createTime,
String createDate, Boolean isBreakout) {
CreateMeetingRequestPayload payload = new CreateMeetingRequestPayload(meetingID, externalMeetingID, meetingName,
recorded, voiceBridge, duration,
autoStartRecording, allowStartStopRecording,
moderatorPass, viewerPass, createTime, createDate, isBreakout);
CreateMeetingRequest msg = new CreateMeetingRequest(payload);
Gson gson = new Gson();
String json = gson.toJson(msg);
log.info("Sending create meeting message to bbb-apps:[{}]", json);
sender.send(MessagingConstants.TO_MEETING_CHANNEL, json);
}
public void createMeeting(String meetingID, String externalMeetingID,
String parentMeetingID, String meetingName, Boolean recorded,
String voiceBridge, Integer duration, Boolean autoStartRecording,
Boolean allowStartStopRecording, String moderatorPass,
String viewerPass, Long createTime, String createDate,
Boolean isBreakout, Integer sequence) {
CreateMeetingRequestPayload payload = new CreateMeetingRequestPayload(
meetingID, externalMeetingID, parentMeetingID, meetingName,
recorded, voiceBridge, duration, autoStartRecording,
allowStartStopRecording, moderatorPass, viewerPass, createTime,
createDate, isBreakout, sequence);
CreateMeetingRequest msg = new CreateMeetingRequest(payload);
Gson gson = new Gson();
String json = gson.toJson(msg);
log.info("Sending create meeting message to bbb-apps:[{}]", json);
sender.send(MessagingConstants.TO_MEETING_CHANNEL, json);
}
public void endMeeting(String meetingId) {
EndMeetingMessage msg = new EndMeetingMessage(meetingId);

View File

@ -28,24 +28,32 @@ public class RedisStorageService {
// CLIENT LIST on redis-cli
redisPool = new JedisPool(new GenericObjectPoolConfig(), host, port, Protocol.DEFAULT_TIMEOUT, null,
Protocol.DEFAULT_DATABASE, "BbbRed5AppsPub");
}
public void recordMeetingInfo(String meetingId, Map<String, String> info) {
Jedis jedis = redisPool.getResource();
try {
for (String key: info.keySet()) {
log.debug("Storing metadata {} = {}", key, info.get(key));
}
public void recordMeetingInfo(String meetingId, Map<String, String> info,
Map<String, String> breakoutInfo) {
Jedis jedis = redisPool.getResource();
try {
for (String key : info.keySet()) {
log.debug("Storing metadata {} = {}", key, info.get(key));
}
log.debug("Saving metadata in {}", meetingId);
jedis.hmset("meeting:info:" + meetingId, info);
} catch (Exception e){
log.warn("Cannot record the info meeting:"+meetingId,e);
} finally {
jedis.close();
}
}
log.debug("Saving metadata in {}", meetingId);
jedis.hmset("meeting:info:" + meetingId, info);
for (String breakoutKey : breakoutInfo.keySet()) {
log.debug("Storing breakout metadata {} = {}", breakoutKey,
breakoutInfo.get(breakoutKey));
}
log.debug("Saving breakout metadata in {}", meetingId);
jedis.hmset("meeting:breakout:" + meetingId, breakoutInfo);
} catch (Exception e) {
log.warn("Cannot record the info meeting:" + meetingId, e);
} finally {
jedis.close();
}
}
public void removeMeeting(String meetingId){
Jedis jedis = redisPool.getResource();

View File

@ -2,9 +2,10 @@ package org.bigbluebutton.api.messaging.messages;
public class CreateBreakoutRoom implements IMessage {
public final String breakoutId;
public final String parentId; // The main meeting internal id
public final String meetingId;
public final String parentMeetingId; // The main meeting internal id
public final String name; // The name of the breakout room
public final Integer sequence; // The sequence number of the breakout room
public final String voiceConfId; // The voice conference id
public final String viewerPassword;
public final String moderatorPassword;
@ -13,14 +14,15 @@ public class CreateBreakoutRoom implements IMessage {
public final Integer sourcePresentationSlide;
public final Boolean record;
public CreateBreakoutRoom(String breakoutId, String parentId, String name,
String voiceConfId, String viewerPassword,
String moderatorPassword, Integer duration,
public CreateBreakoutRoom(String meetingId, String parentMeetingId,
String name, Integer sequence, String voiceConfId,
String viewerPassword, String moderatorPassword, Integer duration,
String sourcePresentationId, Integer sourcePresentationSlide,
Boolean record) {
this.breakoutId = breakoutId;
this.parentId = parentId;
this.meetingId = meetingId;
this.parentMeetingId = parentMeetingId;
this.name = name;
this.sequence = sequence;
this.voiceConfId = voiceConfId;
this.viewerPassword = viewerPassword;
this.moderatorPassword = moderatorPassword;

View File

@ -1,9 +1,9 @@
package org.bigbluebutton.api.messaging.messages;
public class EndBreakoutRoom implements IMessage {
public final String breakoutId;
public final String breakoutMeetingId;
public EndBreakoutRoom(String breakoutId) {
this.breakoutId = breakoutId;
public EndBreakoutRoom(String breakoutMeetingId) {
this.breakoutMeetingId = breakoutMeetingId;
}
}

View File

@ -27,6 +27,7 @@ public class PresentationUrlDownloadService {
private DocumentConversionService documentConversionService;
private String presentationBaseURL;
private String presentationDir;
private String BLANK_PRESENTATION;
public void processUploadedPresentation(UploadedPresentation uploadedPres) {
documentConversionService.processDocument(uploadedPres);
@ -43,11 +44,12 @@ public class PresentationUrlDownloadService {
public void extractPage(String sourceMeetingId, String presentationId,
Integer presentationSlide, String destinationMeetingId) {
// Construct the source meeting path
// Build the source meeting path
File sourceMeetingPath = new File(presentationDir + File.separator
+ sourceMeetingId + File.separator + sourceMeetingId
+ File.separator + presentationId);
// Find the source meeting presentation file
final String presentationFilter = presentationId;
FilenameFilter pdfFilter = new FilenameFilter() {
public boolean accept(File dir, String name) {
@ -57,7 +59,7 @@ public class PresentationUrlDownloadService {
};
File[] matches = sourceMeetingPath.listFiles(pdfFilter);
if (matches.length != 1) {
if (matches != null && matches.length != 1) {
// No PDF presentation was found, we look for an image presentation
FilenameFilter imgFlter = new FilenameFilter() {
public boolean accept(File dir, String name) {
@ -67,40 +69,44 @@ public class PresentationUrlDownloadService {
matches = sourceMeetingPath.listFiles(imgFlter);
}
if (matches.length != 1) {
log.info("Not matching PDF file with prefix {} found at {}",
File sourcePresentationFile;
if (matches == null || matches.length != 1) {
log.warn(
"Not matching PDF file with prefix {} found at {}. Using the default blank PDF",
sourceMeetingId, sourceMeetingPath);
sourcePresentationFile = new File(BLANK_PRESENTATION);
} else {
File sourcePresentationFile = matches[0];
String filenameExt = FilenameUtils
.getExtension(sourcePresentationFile.getName());
String presId = generatePresentationId(presentationId);
String newFilename = Util.createNewFilename(presId, filenameExt);
File uploadDir = createPresentationDirectory(destinationMeetingId,
presentationDir, presId);
String newFilePath = uploadDir.getAbsolutePath()
+ File.separatorChar + newFilename;
File newPresentation = new File(newFilePath);
if (sourcePresentationFile.getName().toLowerCase().endsWith("pdf")) {
pageExtractor.extractPage(sourcePresentationFile, new File(
newFilePath), presentationSlide);
} else {
try {
FileUtils.copyFile(sourcePresentationFile, newPresentation);
} catch (IOException e) {
log.error("Could not copy presentation {} to {}",
sourcePresentationFile.getAbsolutePath(),
newPresentation.getAbsolutePath());
e.printStackTrace();
}
}
processUploadedFile(destinationMeetingId, presId, "default-"
+ presentationSlide.toString() + "." + filenameExt,
newPresentation);
sourcePresentationFile = matches[0];
}
// Build the target meeting path
String filenameExt = FilenameUtils.getExtension(sourcePresentationFile
.getName());
String presId = generatePresentationId(presentationId);
String newFilename = Util.createNewFilename(presId, filenameExt);
File uploadDir = createPresentationDirectory(destinationMeetingId,
presentationDir, presId);
String newFilePath = uploadDir.getAbsolutePath() + File.separatorChar
+ newFilename;
File newPresentation = new File(newFilePath);
if (sourcePresentationFile.getName().toLowerCase().endsWith("pdf")) {
pageExtractor.extractPage(sourcePresentationFile, new File(
newFilePath), presentationSlide);
} else {
try {
FileUtils.copyFile(sourcePresentationFile, newPresentation);
} catch (IOException e) {
log.error("Could not copy presentation {} to {}",
sourcePresentationFile.getAbsolutePath(),
newPresentation.getAbsolutePath());
e.printStackTrace();
}
}
processUploadedFile(destinationMeetingId, presId, "default-"
+ presentationSlide.toString() + "." + filenameExt,
newPresentation);
}
public String generatePresentationId(String name) {
@ -220,4 +226,8 @@ public class PresentationUrlDownloadService {
this.documentConversionService = documentConversionService;
}
public void setBlankPresentation(String blankPresentation) {
this.BLANK_PRESENTATION = blankPresentation;
}
}

View File

@ -21,7 +21,6 @@ package org.bigbluebutton.presentation.imp;
import java.io.BufferedReader;
import java.io.File;
import java.io.IOException;
import java.io.InputStreamReader;
import java.util.Timer;
import java.util.TimerTask;
@ -29,7 +28,6 @@ import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.bigbluebutton.presentation.PageCounter;
import org.bigbluebutton.presentation.imp.ExternalProcessExecutor.InterruptTimerTask;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@ -30,7 +30,6 @@ import java.util.concurrent.ExecutorCompletionService;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;

View File

@ -1,13 +1,9 @@
package org.bigbluebutton.presentation.imp;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.Writer;
import org.bigbluebutton.presentation.SvgImageCreator;
import org.bigbluebutton.presentation.SupportedFileTypes;
import org.bigbluebutton.presentation.SvgImageCreator;
import org.bigbluebutton.presentation.UploadedPresentation;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@ -21,14 +21,13 @@ package org.bigbluebutton.presentation.imp;
import java.util.Map;
import org.apache.commons.lang.StringEscapeUtils;
import org.bigbluebutton.api.messaging.MessagingConstants;
import org.bigbluebutton.api.messaging.MessagingService;
import org.bigbluebutton.presentation.ConversionMessageConstants;
import org.bigbluebutton.presentation.ConversionUpdateMessage;
import org.bigbluebutton.presentation.ConversionUpdateMessage.MessageBuilder;
import org.bigbluebutton.presentation.GeneratedSlidesInfoHelper;
import org.bigbluebutton.presentation.UploadedPresentation;
import org.bigbluebutton.presentation.ConversionUpdateMessage.MessageBuilder;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@ -19,20 +19,6 @@
package org.bigbluebutton.web.services;
import org.bigbluebutton.api.messaging.MessageListener;
import org.bigbluebutton.api.messaging.MessagingService;
import org.bigbluebutton.api.messaging.MessagingConstants;
import org.bigbluebutton.api.messaging.RedisMessagingService;
import org.bigbluebutton.api.messaging.messages.IMessage;
import org.bigbluebutton.api.messaging.messages.KeepAliveReply;
import org.bigbluebutton.api.messaging.messages.MeetingDestroyed;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.Timer;
import java.util.TimerTask;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
@ -40,7 +26,12 @@ import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import com.google.gson.Gson;
import org.bigbluebutton.api.messaging.MessageListener;
import org.bigbluebutton.api.messaging.MessagingService;
import org.bigbluebutton.api.messaging.messages.IMessage;
import org.bigbluebutton.api.messaging.messages.KeepAliveReply;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class KeepAliveService implements MessageListener {
private static Logger log = LoggerFactory.getLogger(KeepAliveService.class);

View File

@ -50,6 +50,10 @@ module BigBlueButton
@redis.hgetall("meeting:info:#{meeting_id}")
end
def breakout_metadata_for(meeting_id)
@redis.hgetall("meeting:breakout:#{meeting_id}")
end
def num_events_for(meeting_id)
@redis.llen("meeting:#{meeting_id}:recordings")
end
@ -74,6 +78,10 @@ module BigBlueButton
@redis.del("meeting:info:#{meeting_id}")
end
def delete_breakout_metadata_for(meeting_id)
@redis.del("meeting:breakout:#{meeting_id}")
end
def build_header(message_type)
return {
"timestamp" => BigBlueButton.monotonic_clock, #
@ -178,11 +186,13 @@ module BigBlueButton
result = xml.instruct! :xml, :version => "1.0", :encoding=>"UTF-8"
meeting_metadata = @redis.metadata_for(meeting_id)
breakout_metadata = @redis.breakout_metadata_for(meeting_id)
version = YAML::load(File.open('../../core/scripts/bigbluebutton.yml'))["bbb_version"]
if (meeting_metadata != nil)
xml.recording(:meeting_id => meeting_id, :bbb_version => version) {
xml.metadata(meeting_metadata)
xml.breakout(breakout_metadata)
msgs = @redis.events_for(meeting_id)
msgs.each do |msg|
res = @redis.event_info_for(meeting_id, msg)
@ -220,6 +230,7 @@ module BigBlueButton
@redis.delete_events_for(meeting_id)
end
@redis.delete_metadata_for(meeting_id)
@redis.delete_breakout_metadata_for(meeting_id)
end
def save_events_to_file(directory, result)