Store breakout room sequence in akka-apps and bbb-web.

This commit is contained in:
Ghazi Triki 2016-10-13 12:53:53 +01:00
parent 054037c6d6
commit 5e002d845e
15 changed files with 152 additions and 104 deletions

View File

@ -70,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

@ -8,7 +8,7 @@ case class MeetingProperties(meetingID: String, externalMeetingID: String, paren
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)
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,

View File

@ -33,7 +33,7 @@ object UserMessagesProtocol extends DefaultJsonProtocol {
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(meetingId: 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

@ -24,7 +24,7 @@ trait BreakoutRoomApp extends SystemConfiguration {
val breakoutRooms = breakoutModel.getRooms().toVector map { r => new BreakoutRoomBody(r.name, 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 ))
outGW.send(new BreakoutRoomsListOutMessage(mProps.meetingID, breakoutRooms, roomsReady))
}
def handleCreateBreakoutRooms(msg: CreateBreakoutRooms) {
@ -33,6 +33,10 @@ 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
// in very rare cases the presentation conversion generates an error, what should we do?
@ -56,6 +60,7 @@ trait BreakoutRoomApp extends SystemConfiguration {
}
def sendJoinURL(userId: String, breakoutMeetingId: String) {
log.debug("Sending breakout meeting {} Join URL for user: {}", breakoutMeetingId, userId);
for {
user <- usersModel.getUser(userId)
apiCall = "join"
@ -77,13 +82,13 @@ trait BreakoutRoomApp extends SystemConfiguration {
sendBreakoutRoomStarted(room.parentRoomId, room.name, 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);
log.debug("Sending Join URL for users");
sendJoinURL(u, room.id)
}
}
@ -92,7 +97,7 @@ trait BreakoutRoomApp extends SystemConfiguration {
}
}
def sendBreakoutRoomStarted(meetingId: String, breakoutName: String, breakoutMeetingId: String, sequence: Int,voiceConfId: String) {
def sendBreakoutRoomStarted(meetingId: String, breakoutName: String, breakoutMeetingId: String, sequence: Int, voiceConfId: String) {
log.info("Sending breakout room started for parent meeting {} and breakout meeting", meetingId, breakoutMeetingId);
outGW.send(new BreakoutRoomStartedOutMessage(meetingId, mProps.recorded, new BreakoutRoomBody(breakoutName, breakoutMeetingId, sequence)))
}

View File

@ -33,6 +33,10 @@ class BreakoutRoomModel {
def getRooms(): Array[BreakoutRoom] = {
rooms.values.toArray
}
def getNumberOfRooms(): Int = {
rooms.size
}
def getAssignedUsers(breakoutMeetingId: String): Option[Vector[String]] = {
for {

View File

@ -5,6 +5,7 @@ trait AppsTestFixtures {
val meetingId = "testMeetingId"
val externalMeetingId = "testExternalMeetingId"
val parentMeetingId = "testParentMeetingId"
val sequence = 4
val meetingName = "test meeting"
val record = false
val voiceConfId = "85115"
@ -27,5 +28,5 @@ trait AppsTestFixtures {
autoStartRecording, allowStartStopRecording,
moderatorPassword, viewerPassword,
createTime, createDate, red5DeskShareIP, red5DeskShareApp,
isBreakout)
isBreakout, sequence)
}

View File

@ -28,13 +28,14 @@ public class CreateMeetingRequest implements IBigBlueButtonMessage {
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) {
String createDate, Boolean isBreakout, Integer sequence) {
this.id = id;
this.externalId = externalId;
this.parentId = parentId;
@ -49,6 +50,7 @@ public class CreateMeetingRequest implements IBigBlueButtonMessage {
this.createTime = createTime;
this.createDate = createDate;
this.isBreakout = isBreakout;
this.sequence = sequence;
}
}
}

View File

@ -20,6 +20,7 @@ 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();
@ -29,7 +30,7 @@ public class CreateMeetingRequestTest {
meetingId, externalId, parentId, name, record, voiceConfId,
durationInMinutes, autoStartRecording, allowStartStopRecording,
moderatorPassword, viewerPassword, createTime, createDate,
isBreakout);
isBreakout, sequence);
CreateMeetingRequest msg = new CreateMeetingRequest(payload);
Gson gson = new Gson();
String json = gson.toJson(msg);
@ -47,5 +48,6 @@ public class CreateMeetingRequestTest {
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

@ -861,6 +861,7 @@ class ApiController {
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()) }
@ -2131,6 +2132,7 @@ class ApiController {
internalMeetingID(meeting.getInternalId())
if (m.isBreakout()) {
parentMeetingID() { mkp.yield(meeting.getParentMeetingId()) }
sequence(meeting.getSequence())
}
createTime(meeting.getCreateTime())
createDate(formatPrettyDate(meeting.getCreateTime()))

View File

@ -303,7 +303,8 @@ public class MeetingService implements MessageListener {
metadata.put("meetingName", m.getName());
metadata.put("isBreakout", m.isBreakout().toString());
if (m.isBreakout() != null){
metadata.put("parentMeetingId", m.getParentMeetingId());
metadata.put("sequence", m.getSequence().toString());
metadata.put("parentMeetingId", m.getParentMeetingId());
}
messagingService.recordMeetingInfo(m.getInternalId(), metadata);
@ -313,7 +314,8 @@ public class MeetingService implements MessageListener {
logData.put("meetingId", m.getInternalId());
logData.put("externalMeetingId", m.getExternalId());
if (m.isBreakout() != null){
logData.put("parentMeetingId", m.getParentMeetingId());
logData.put("sequence", m.getSequence());
logData.put("parentMeetingId", m.getParentMeetingId());
}
logData.put("name", m.getName());
logData.put("duration", m.getDuration());
@ -332,7 +334,7 @@ public class MeetingService implements MessageListener {
m.getTelVoice(), m.getDuration(), m.getAutoStartRecording(),
m.getAllowStartStopRecording(), m.getModeratorPassword(),
m.getViewerPassword(), m.getCreateTime(),
formatPrettyDate(m.getCreateTime()), m.isBreakout());
formatPrettyDate(m.getCreateTime()), m.isBreakout(), m.getSequence());
}
private String formatPrettyDate(Long timestamp) {
@ -552,8 +554,9 @@ public class MeetingService implements MessageListener {
message.sourcePresentationId,
message.sourcePresentationSlide, breakout.getInternalId());
} else {
log.error("Failed to create breakout room " + message.meetingId
+ ".Reason: Parent meeting not found.");
log.error(
"Failed to create breakout room {}.Reason: Parent meeting {} not found.",
message.meetingId, message.parentMeetingId);
}
}

View File

@ -288,89 +288,107 @@ 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.concat("-").concat(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
// we need to generate a unique internal and external id and keep
// tracks of the parent meeting id
String parentMeetingId = new String();
// tracks of the parent meeting id
String parentMeetingId = new String();
if (isBreakout) {
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")));
String timeStamp = StringUtils.substringAfter(internalMeetingId,
"-");
String externalHash = DigestUtils.shaHex(parentMeetingId
.concat("-").concat(timeStamp.toString()).concat("-")
.concat(params.get("sequence")));
externalMeetingId = externalHash.concat("-").concat(timeStamp);
}
@ -388,7 +406,6 @@ public class ParamsProcessorUtil {
.withMetadata(meetingInfo)
.withWelcomeMessageTemplate(welcomeMessageTemplate)
.withWelcomeMessage(welcomeMessage).isBreakout(isBreakout)
.withParentMeetingId(parentMeetingId)
.build();
String configXML = getDefaultConfigXML();
@ -399,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

@ -39,6 +39,7 @@ public class Meeting {
private String extMeetingId;
private String intMeetingId;
private String parentMeetingId;
private Integer sequence = 0;
private Integer duration = 0;
private long createdTime = 0;
private long startTime = 0;
@ -73,7 +74,6 @@ public class Meeting {
name = builder.name;
extMeetingId = builder.externalId;
intMeetingId = builder.internalId;
parentMeetingId = builder.parentMeetingId;
viewerPass = builder.viewerPass;
moderatorPass = builder.moderatorPass;
maxUsers = builder.maxUsers;
@ -154,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;
}
@ -202,7 +210,11 @@ public class Meeting {
public String getInternalId() {
return intMeetingId;
}
public String setParentMeetingId(String p) {
return parentMeetingId = p;
}
public String getParentMeetingId() {
return parentMeetingId;
}
@ -382,7 +394,6 @@ public class Meeting {
private String name;
private String externalId;
private String internalId;
private String parentMeetingId;
private int maxUsers;
private boolean record;
private boolean autoStartRecording;
@ -400,18 +411,13 @@ 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 withParentMeetingId(String parentMeetingId) {
this.parentMeetingId = parentMeetingId;
return this;
}
public Builder withName(String name) {
this.name = name;
return this;
@ -486,7 +492,7 @@ public class Meeting {
isBreakout = b;
return this;
}
public Builder withLogoutUrl(String l) {
logoutUrl = l;
return this;

View File

@ -34,7 +34,7 @@ public interface MessagingService {
String voiceBridge, Integer duration, Boolean autoStartRecording,
Boolean allowStartStopRecording, String moderatorPass,
String viewerPass, Long createTime, String createDate,
Boolean isBreakout);
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

@ -72,12 +72,12 @@ public class RedisMessagingService implements MessagingService {
String voiceBridge, Integer duration, Boolean autoStartRecording,
Boolean allowStartStopRecording, String moderatorPass,
String viewerPass, Long createTime, String createDate,
Boolean isBreakout) {
Boolean isBreakout, Integer sequence) {
CreateMeetingRequestPayload payload = new CreateMeetingRequestPayload(
meetingID, externalMeetingID, parentMeetingID, meetingName,
recorded, voiceBridge, duration, autoStartRecording,
allowStartStopRecording, moderatorPass, viewerPass, createTime,
createDate, isBreakout);
createDate, isBreakout, sequence);
CreateMeetingRequest msg = new CreateMeetingRequest(payload);
Gson gson = new Gson();