Make breakout rooms use the selected slide of the current presentation as default presentation.

This commit is contained in:
Ghazi Triki 2016-09-28 22:19:48 +01:00
parent 87b4b8ca07
commit 0e72d7523d
18 changed files with 301 additions and 339 deletions

View File

@ -43,7 +43,6 @@ http {
services { services {
bbbWebAPI = "http://192.168.23.33/bigbluebutton/api" bbbWebAPI = "http://192.168.23.33/bigbluebutton/api"
sharedSecret = "changeme" sharedSecret = "changeme"
defaultPresentationURL = "http://localhost/default.pdf"
} }
red5 { red5 {

View File

@ -18,7 +18,6 @@ trait SystemConfiguration {
lazy val bbbWebSharedSecret = Try(config.getString("services.sharedSecret")).getOrElse("changeme") lazy val bbbWebSharedSecret = Try(config.getString("services.sharedSecret")).getOrElse("changeme")
lazy val bbbWebModeratorPassword = Try(config.getString("services.moderatorPassword")).getOrElse("changeme") lazy val bbbWebModeratorPassword = Try(config.getString("services.moderatorPassword")).getOrElse("changeme")
lazy val bbbWebViewerPassword = Try(config.getString("services.viewerPassword")).getOrElse("changeme") lazy val bbbWebViewerPassword = Try(config.getString("services.viewerPassword")).getOrElse("changeme")
lazy val bbbWebDefaultPresentationURL = Try(config.getString("services.defaultPresentationURL")).getOrElse("changeme")
lazy val keysExpiresInSec = Try(config.getInt("redis.keyExpiry")).getOrElse(14 * 86400) // 14 days lazy val keysExpiresInSec = Try(config.getInt("redis.keyExpiry")).getOrElse(14 * 86400) // 14 days
lazy val red5DeskShareIP = Try(config.getString("red5.deskshareip")).getOrElse("127.0.0.1") lazy val red5DeskShareIP = Try(config.getString("red5.deskshareip")).getOrElse("127.0.0.1")
lazy val red5DeskShareApp = Try(config.getString("red5.deskshareapp")).getOrElse("") lazy val red5DeskShareApp = Try(config.getString("red5.deskshareapp")).getOrElse("")

View File

@ -94,7 +94,7 @@ class JsonMessageSenderActor(val service: MessageSender)
private def handleCreateBreakoutRoom(msg: CreateBreakoutRoom) { private def handleCreateBreakoutRoom(msg: CreateBreakoutRoom) {
val payload = new CreateBreakoutRoomRequestPayload(msg.room.breakoutId, msg.room.parentId, msg.room.name, val payload = new CreateBreakoutRoomRequestPayload(msg.room.breakoutId, msg.room.parentId, msg.room.name,
msg.room.voiceConfId, msg.room.viewerPassword, msg.room.moderatorPassword, msg.room.voiceConfId, msg.room.viewerPassword, msg.room.moderatorPassword,
msg.room.durationInMinutes, msg.room.defaultPresentationURL, msg.room.record) msg.room.durationInMinutes, msg.room.sourcePresentationId, msg.room.sourcePresentationSlide, msg.room.record)
val request = new CreateBreakoutRoomRequest(payload) val request = new CreateBreakoutRoomRequest(payload)
service.send(MessagingConstants.FROM_MEETING_CHANNEL, request.toJson()) service.send(MessagingConstants.FROM_MEETING_CHANNEL, request.toJson())
} }

View File

@ -117,7 +117,7 @@ class LiveMeeting(val mProps: MeetingProperties,
meetingModel.meetingHasEnded meetingModel.meetingHasEnded
/** /**
* See if this meeting has breakout rooms. If so, we also need to end them. * Check if this meeting has breakout rooms. If so, we also need to end them.
*/ */
handleEndAllBreakoutRooms(new EndAllBreakoutRooms(msg.meetingId)) handleEndAllBreakoutRooms(new EndAllBreakoutRooms(msg.meetingId))

View File

@ -33,7 +33,7 @@ object UserMessagesProtocol extends DefaultJsonProtocol {
implicit val inMsgHeaderFormat = jsonFormat1(InMessageHeader) implicit val inMsgHeaderFormat = jsonFormat1(InMessageHeader)
implicit val outMsgHeaderFormat = jsonFormat1(OutMsgHeader) implicit val outMsgHeaderFormat = jsonFormat1(OutMsgHeader)
implicit val outMsgEnvelopeHeaderFormat = jsonFormat2(OutMsgEnvelopeHeader) implicit val outMsgEnvelopeHeaderFormat = jsonFormat2(OutMsgEnvelopeHeader)
implicit val createBreakoutRoomOutMsgPayloadFormat = jsonFormat8(CreateBreakoutRoomOutMsgPayload) implicit val createBreakoutRoomOutMsgPayloadFormat = jsonFormat10(CreateBreakoutRoomOutMsgPayload)
implicit val createBreakoutRoomOutMsgEnvelopePayloadFormat = jsonFormat2(CreateBreakoutRoomOutMsgEnvelopePayload) implicit val createBreakoutRoomOutMsgEnvelopePayloadFormat = jsonFormat2(CreateBreakoutRoomOutMsgEnvelopePayload)
implicit val createBreakoutRoomOutMsgEnvelopeFormat = jsonFormat2(CreateBreakoutRoomOutMsgEnvelope) implicit val createBreakoutRoomOutMsgEnvelopeFormat = jsonFormat2(CreateBreakoutRoomOutMsgEnvelope)

View File

@ -5,12 +5,10 @@ case class OutMsgEnvelopeHeader(`type`: MessageType.MessageType, address: String
trait OutMessage trait OutMessage
case class CreateBreakoutRoomOutMsgEnvelope(header: OutMsgEnvelopeHeader, case class CreateBreakoutRoomOutMsgEnvelope(header: OutMsgEnvelopeHeader, payload: CreateBreakoutRoomOutMsgEnvelopePayload)
payload: CreateBreakoutRoomOutMsgEnvelopePayload) case class CreateBreakoutRoomOutMsgEnvelopePayload(header: OutMsgHeader, payload: CreateBreakoutRoomOutMsgPayload)
case class CreateBreakoutRoomOutMsgEnvelopePayload(header: OutMsgHeader,
payload: CreateBreakoutRoomOutMsgPayload)
case class CreateBreakoutRoomOutMsgPayload(breakoutId: String, name: String, parentId: String, case class CreateBreakoutRoomOutMsgPayload(breakoutId: String, name: String, parentId: String,
voiceConfId: String, durationInMinutes: Int, voiceConfId: String, durationInMinutes: Int,
moderatorPassword: String, viewerPassword: String, moderatorPassword: String, viewerPassword: String,
defaultPresentationUrl: String) sourcePresentationId: String, sourcePresentationSlide: Int, record: Boolean)

View File

@ -33,7 +33,7 @@ case class CreateBreakoutRoom(meetingId: String, room: BreakoutRoomOutPayload) e
case class EndBreakoutRoom(breakoutId: String) extends IOutMessage case class EndBreakoutRoom(breakoutId: String) extends IOutMessage
case class BreakoutRoomOutPayload(breakoutId: String, name: String, parentId: String, case class BreakoutRoomOutPayload(breakoutId: String, name: String, parentId: String,
voiceConfId: String, durationInMinutes: Int, moderatorPassword: String, viewerPassword: String, voiceConfId: String, durationInMinutes: Int, moderatorPassword: String, viewerPassword: String,
defaultPresentationURL: String, record: Boolean) sourcePresentationId: String, sourcePresentationSlide: Int, record: Boolean)
case class BreakoutRoomJoinURLOutMessage(meetingId: String, recorded: Boolean, breakoutId: String, userId: String, joinURL: String) extends IOutMessage 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 BreakoutRoomStartedOutMessage(meetingId: String, recorded: Boolean, breakout: BreakoutRoomBody) extends IOutMessage
case class BreakoutRoomBody(name: String, breakoutId: String) case class BreakoutRoomBody(name: String, breakoutId: String)

View File

@ -20,15 +20,6 @@ trait BreakoutRoomApp extends SystemConfiguration {
val outGW: OutMessageGateway val outGW: OutMessageGateway
val eventBus: IncomingEventBus val eventBus: IncomingEventBus
def getDefaultPresentationURL(): String = {
var presURL = bbbWebDefaultPresentationURL
val page = presModel.getCurrentPage()
page foreach { p =>
presURL = BreakoutRoomsUtil.fromSWFtoPDF(p.swfUri)
}
presURL
}
def handleBreakoutRoomsList(msg: BreakoutRoomsListMessage) { def handleBreakoutRoomsList(msg: BreakoutRoomsListMessage) {
val breakoutRooms = breakoutModel.getRooms().toVector map { r => new BreakoutRoomBody(r.name, r.id) } 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)); outGW.send(new BreakoutRoomsListOutMessage(mProps.meetingID, breakoutRooms, breakoutModel.pendingRoomsNumber == 0 && breakoutRooms.length > 0));
@ -40,17 +31,20 @@ trait BreakoutRoomApp extends SystemConfiguration {
log.warning("CreateBreakoutRooms event received while {} are pending to be created for meeting {}", breakoutModel.pendingRoomsNumber, mProps.meetingID) log.warning("CreateBreakoutRooms event received while {} are pending to be created for meeting {}", breakoutModel.pendingRoomsNumber, mProps.meetingID)
return return
} }
var i = 0 var i = 0
val sourcePresentationId = presModel.getCurrentPresentation().get.id
val sourcePresentationSlide = presModel.getCurrentPage().get.num
breakoutModel.pendingRoomsNumber = msg.rooms.length; breakoutModel.pendingRoomsNumber = msg.rooms.length;
for (room <- msg.rooms) { for (room <- msg.rooms) {
i += 1 i += 1
val presURL = bbbWebDefaultPresentationURL
val breakoutMeetingId = BreakoutRoomsUtil.createMeetingId(mProps.meetingID, i) val breakoutMeetingId = BreakoutRoomsUtil.createMeetingId(mProps.meetingID, i)
val voiceConfId = BreakoutRoomsUtil.createVoiceConfId(mProps.voiceBridge, i) val voiceConfId = BreakoutRoomsUtil.createVoiceConfId(mProps.voiceBridge, i)
val r = breakoutModel.createBreakoutRoom(breakoutMeetingId, room.name, voiceConfId, room.users, presURL) val r = breakoutModel.createBreakoutRoom(breakoutMeetingId, room.name, voiceConfId, room.users)
val p = new BreakoutRoomOutPayload(r.id, r.name, mProps.meetingID, val p = new BreakoutRoomOutPayload(r.id, r.name, mProps.meetingID,
r.voiceConfId, msg.durationInMinutes, mProps.moderatorPass, mProps.viewerPass, r.voiceConfId, msg.durationInMinutes, mProps.moderatorPass, mProps.viewerPass,
r.defaultPresentationURL, msg.record) sourcePresentationId, sourcePresentationSlide, msg.record)
outGW.send(new CreateBreakoutRoom(mProps.meetingID, p)) outGW.send(new CreateBreakoutRoom(mProps.meetingID, p))
} }
meetingModel.breakoutRoomsdurationInMinutes = msg.durationInMinutes; meetingModel.breakoutRoomsdurationInMinutes = msg.durationInMinutes;
@ -85,7 +79,7 @@ trait BreakoutRoomApp extends SystemConfiguration {
breakoutModel.getRooms().foreach { room => breakoutModel.getRooms().foreach { room =>
breakoutModel.getAssignedUsers(room.id) foreach { users => breakoutModel.getAssignedUsers(room.id) foreach { users =>
users.foreach { u => users.foreach { u =>
log.debug("## Sending Join URL for users: {}", u); log.debug("Sending Join URL for users: {}", u);
sendJoinURL(u, room.id) sendJoinURL(u, room.id)
} }
} }

View File

@ -5,7 +5,7 @@ import scala.collection.immutable.HashMap
case class BreakoutUser(id: String, name: String) case class BreakoutUser(id: String, name: String)
case class BreakoutRoom(id: String, name: String, voiceConfId: String, case class BreakoutRoom(id: String, name: String, voiceConfId: String,
assignedUsers: Vector[String], users: Vector[BreakoutUser], defaultPresentationURL: String) assignedUsers: Vector[String], users: Vector[BreakoutUser])
class BreakoutRoomModel { class BreakoutRoomModel {
private var rooms = new collection.immutable.HashMap[String, BreakoutRoom] private var rooms = new collection.immutable.HashMap[String, BreakoutRoom]
@ -22,8 +22,8 @@ class BreakoutRoomModel {
} }
def createBreakoutRoom(id: String, name: String, voiceConfId: String, def createBreakoutRoom(id: String, name: String, voiceConfId: String,
assignedUsers: Vector[String], defaultPresentationURL: String): BreakoutRoom = { assignedUsers: Vector[String]): BreakoutRoom = {
val room = new BreakoutRoom(id, name, voiceConfId, assignedUsers, Vector(), defaultPresentationURL) val room = new BreakoutRoom(id, name, voiceConfId, assignedUsers, Vector())
add(room) add(room)
} }

View File

@ -94,16 +94,10 @@ trait PresentationApp {
} }
def handleGotoSlide(msg: GotoSlide) { def handleGotoSlide(msg: GotoSlide) {
// println("Received GotoSlide for meeting=[" + msg.meetingID + "] page=[" + msg.page + "]")
// println("*** Before change page ****")
// printPresentations
presModel.changePage(msg.page) foreach { page => presModel.changePage(msg.page) foreach { page =>
// println("Switching page for meeting=[" + msg.meetingID + "] page=[" + page.id + "]") log.debug("Switching page for meeting=[{}] page=[{}]", msg.meetingID, page.num);
outGW.send(new GotoSlideOutMsg(mProps.meetingID, mProps.recorded, page)) outGW.send(new GotoSlideOutMsg(mProps.meetingID, mProps.recorded, page))
} }
// println("*** After change page ****")
// printPresentations
usersModel.getCurrentPresenter() foreach { pres => usersModel.getCurrentPresenter() foreach { pres =>
handleStopPollRequest(StopPollRequest(mProps.meetingID, pres.userID)) handleStopPollRequest(StopPollRequest(mProps.meetingID, pres.userID))

View File

@ -8,12 +8,15 @@ public class CreateBreakoutRoomRequestPayload {
public final String viewerPassword; public final String viewerPassword;
public final String moderatorPassword; public final String moderatorPassword;
public final Integer durationInMinutes; // The duration of the breakout room public final Integer durationInMinutes; // The duration of the breakout room
public final String defaultPresentationURL; public final String sourcePresentationId;
public final Integer sourcePresentationSlide;
public final Boolean record; public final Boolean record;
public CreateBreakoutRoomRequestPayload(String breakoutId, String parentId, String name, public CreateBreakoutRoomRequestPayload(String breakoutId, String parentId,
String voiceConfId, String viewerPassword, String moderatorPassword, String name, String voiceConfId, String viewerPassword,
Integer duration, String defaultPresentationURL, Boolean record) { String moderatorPassword, Integer duration,
String sourcePresentationId, Integer sourcePresentationSlide,
Boolean record) {
this.breakoutId = breakoutId; this.breakoutId = breakoutId;
this.parentId = parentId; this.parentId = parentId;
this.name = name; this.name = name;
@ -21,7 +24,8 @@ public class CreateBreakoutRoomRequestPayload {
this.viewerPassword = viewerPassword; this.viewerPassword = viewerPassword;
this.moderatorPassword = moderatorPassword; this.moderatorPassword = moderatorPassword;
this.durationInMinutes = duration; this.durationInMinutes = duration;
this.defaultPresentationURL = defaultPresentationURL; this.sourcePresentationId = sourcePresentationId;
this.sourcePresentationSlide = sourcePresentationSlide;
this.record = record; this.record = record;
} }
} }

View File

@ -9,19 +9,20 @@ import com.google.gson.Gson;
public class CreateBreakoutRoomRequestTest { public class CreateBreakoutRoomRequestTest {
@Test @Test
public void testCreateBreakoutRoomRequest() { public void testCreateBreakoutRoomRequest() {
String breakoutId = "abc123"; String breakoutId = "183f0bf3a0982a127bdb8161e0c44eb696b3e75c-1474984695664";
String parentId = "abc-123"; String parentId = "abc-123";
Integer durationInMinutes = 20; Integer durationInMinutes = 20;
String name = "Breakout room 1"; String name = "Breakout room 1";
String voiceConfId = "851153"; String voiceConfId = "851153";
String viewerPassword = "vp"; String viewerPassword = "vp";
String moderatorPassword = "mp"; String moderatorPassword = "mp";
String defaultPresentationURL = "http://localhost/foo.pdf"; String sourcePresentationId = "d2d9a672040fbde2a47a10bf6c37b6a4b5ae187f-1474984695907";
Integer sourePresentationSlide = 5;
Boolean record = false; Boolean record = false;
CreateBreakoutRoomRequestPayload payload = CreateBreakoutRoomRequestPayload payload =
new CreateBreakoutRoomRequestPayload(breakoutId, parentId, name, voiceConfId, new CreateBreakoutRoomRequestPayload(breakoutId, parentId, name, voiceConfId,
viewerPassword, moderatorPassword, durationInMinutes, defaultPresentationURL, record); viewerPassword, moderatorPassword, durationInMinutes, sourcePresentationId, sourePresentationSlide, record);
CreateBreakoutRoomRequest msg = new CreateBreakoutRoomRequest(payload); CreateBreakoutRoomRequest msg = new CreateBreakoutRoomRequest(payload);
Gson gson = new Gson(); Gson gson = new Gson();
String json = gson.toJson(msg); String json = gson.toJson(msg);
@ -36,7 +37,8 @@ public class CreateBreakoutRoomRequestTest {
Assert.assertEquals(rxMsg.payload.viewerPassword, viewerPassword); Assert.assertEquals(rxMsg.payload.viewerPassword, viewerPassword);
Assert.assertEquals(rxMsg.payload.moderatorPassword, moderatorPassword); Assert.assertEquals(rxMsg.payload.moderatorPassword, moderatorPassword);
Assert.assertEquals(rxMsg.payload.durationInMinutes, durationInMinutes); Assert.assertEquals(rxMsg.payload.durationInMinutes, durationInMinutes);
Assert.assertEquals(rxMsg.payload.defaultPresentationURL, defaultPresentationURL); Assert.assertEquals(rxMsg.payload.sourcePresentationId, sourcePresentationId);
Assert.assertEquals(rxMsg.payload.sourcePresentationSlide, sourePresentationSlide);
Assert.assertEquals(rxMsg.payload.record, record); Assert.assertEquals(rxMsg.payload.record, record);
} }
} }

View File

@ -59,6 +59,7 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
<bean id="presDownloadService" class="org.bigbluebutton.presentation.PresentationUrlDownloadService"> <bean id="presDownloadService" class="org.bigbluebutton.presentation.PresentationUrlDownloadService">
<property name="presentationDir" value="${presentationDir}"/> <property name="presentationDir" value="${presentationDir}"/>
<property name="presentationBaseURL" value="${presentationBaseURL}"/> <property name="presentationBaseURL" value="${presentationBaseURL}"/>
<property name="pageExtractor" ref="pageExtractor"/>
<property name="documentConversionService" ref="documentConversionService"/> <property name="documentConversionService" ref="documentConversionService"/>
</bean> </bean>

View File

@ -113,7 +113,8 @@ public class MeetingService implements MessageListener {
} }
public void registerUser(String meetingID, String internalUserId, public void registerUser(String meetingID, String internalUserId,
String fullname, String role, String externUserID, String authToken, String avatarURL) { String fullname, String role, String externUserID,
String authToken, String avatarURL) {
handle(new RegisterUser(meetingID, internalUserId, fullname, role, handle(new RegisterUser(meetingID, internalUserId, fullname, role,
externUserID, authToken, avatarURL)); externUserID, authToken, avatarURL));
} }
@ -142,19 +143,22 @@ public class MeetingService implements MessageListener {
* Remove registered users who did not successfully joined the meeting. * Remove registered users who did not successfully joined the meeting.
*/ */
public void purgeRegisteredUsers() { public void purgeRegisteredUsers() {
for (AbstractMap.Entry<String, Meeting> entry : this.meetings.entrySet()) { for (AbstractMap.Entry<String, Meeting> entry : this.meetings
.entrySet()) {
Long now = System.nanoTime(); Long now = System.nanoTime();
Meeting meeting = entry.getValue(); Meeting meeting = entry.getValue();
ConcurrentMap<String, User> users = meeting.getUsersMap(); ConcurrentMap<String, User> users = meeting.getUsersMap();
for (AbstractMap.Entry<String, Long> registeredUser : meeting.getRegisteredUsers().entrySet()) { for (AbstractMap.Entry<String, Long> registeredUser : meeting
.getRegisteredUsers().entrySet()) {
String registeredUserID = registeredUser.getKey(); String registeredUserID = registeredUser.getKey();
Long registeredUserDate = registeredUser.getValue(); Long registeredUserDate = registeredUser.getValue();
long registrationTime = registeredUserDate.longValue(); long registrationTime = registeredUserDate.longValue();
long elapsedTime = now - registrationTime; long elapsedTime = now - registrationTime;
if ( elapsedTime >= 60000 && !users.containsKey(registeredUserID)) { if (elapsedTime >= 60000
&& !users.containsKey(registeredUserID)) {
meeting.userUnregistered(registeredUserID); meeting.userUnregistered(registeredUserID);
} }
} }
@ -319,7 +323,8 @@ public class MeetingService implements MessageListener {
m.getName(), m.isRecord(), m.getTelVoice(), m.getDuration(), m.getName(), m.isRecord(), m.getTelVoice(), m.getDuration(),
m.getAutoStartRecording(), m.getAllowStartStopRecording(), m.getAutoStartRecording(), m.getAllowStartStopRecording(),
m.getModeratorPassword(), m.getViewerPassword(), m.getModeratorPassword(), m.getViewerPassword(),
m.getCreateTime(), formatPrettyDate(m.getCreateTime()), m.isBreakout()); m.getCreateTime(), formatPrettyDate(m.getCreateTime()),
m.isBreakout());
} }
private String formatPrettyDate(Long timestamp) { private String formatPrettyDate(Long timestamp) {
@ -357,8 +362,10 @@ public class MeetingService implements MessageListener {
int dashes = meetingId.split("-", -1).length - 1; int dashes = meetingId.split("-", -1).length - 1;
for (String key : meetings.keySet()) { for (String key : meetings.keySet()) {
int keyDashes = key.split("-", -1).length - 1; int keyDashes = key.split("-", -1).length - 1;
if (dashes == 2 && key.equals(meetingId) if (dashes == 2
|| (dashes < 2 && keyDashes < 2 && key.startsWith(meetingId))) { && key.equals(meetingId)
|| (dashes < 2 && keyDashes < 2 && key
.startsWith(meetingId))) {
return (Meeting) meetings.get(key); return (Meeting) meetings.get(key);
} }
} }
@ -484,7 +491,8 @@ public class MeetingService implements MessageListener {
} }
} }
public void updateRecordings(List<String> idList, Map<String, String> metaParams) { public void updateRecordings(List<String> idList,
Map<String, String> metaParams) {
recordingService.updateMetaParams(idList, metaParams); recordingService.updateMetaParams(idList, metaParams);
} }
@ -527,12 +535,14 @@ public class MeetingService implements MessageListener {
params.put("voiceBridge", message.voiceConfId); params.put("voiceBridge", message.voiceConfId);
params.put("duration", message.durationInMinutes.toString()); params.put("duration", message.durationInMinutes.toString());
params.put("record", message.record.toString()); params.put("record", message.record.toString());
params.put("welcome", getMeeting(message.parentId).getWelcomeMessageTemplate()); params.put("welcome", getMeeting(message.parentId)
.getWelcomeMessageTemplate());
Map<String, String> parentMeetingMetadata = parentMeeting.getMetadata(); Map<String, String> parentMeetingMetadata = parentMeeting
.getMetadata();
String metaPrefix = "meta_"; String metaPrefix = "meta_";
for (String key: parentMeetingMetadata.keySet()) { for (String key : parentMeetingMetadata.keySet()) {
String metaName = metaPrefix + key; String metaName = metaPrefix + key;
// Inject metadata from parent meeting into the breakout room. // Inject metadata from parent meeting into the breakout room.
params.put(metaName, parentMeetingMetadata.get(key)); params.put(metaName, parentMeetingMetadata.get(key));
@ -542,10 +552,12 @@ public class MeetingService implements MessageListener {
handleCreateMeeting(breakout); handleCreateMeeting(breakout);
presDownloadService.downloadAndProcessDocument( presDownloadService.extractPage(message.parentId,
message.defaultPresentationURL, breakout.getInternalId()); message.sourcePresentationId,
message.sourcePresentationSlide, breakout.getInternalId());
} else { } else {
log.error("Failed to create breakout room " + message.breakoutId + ". Parent meeting not found."); log.error("Failed to create breakout room " + message.breakoutId
+ ".Reason: Parent meeting not found.");
} }
} }
@ -595,6 +607,8 @@ public class MeetingService implements MessageListener {
Gson gson = new Gson(); Gson gson = new Gson();
String logStr = gson.toJson(logData); String logStr = gson.toJson(logData);
log.info("Meeting restarted: data={}", logStr);
} else { } else {
Map<String, Object> logData = new HashMap<String, Object>(); Map<String, Object> logData = new HashMap<String, Object>();
logData.put("meetingId", m.getInternalId()); logData.put("meetingId", m.getInternalId());
@ -742,7 +756,7 @@ public class MeetingService implements MessageListener {
Meeting m = getMeeting(message.meetingId); Meeting m = getMeeting(message.meetingId);
if (m != null) { if (m != null) {
User user = m.getUserById(message.userId); User user = m.getUserById(message.userId);
if(user != null){ if (user != null) {
user.setStatus(message.status, message.value); user.setStatus(message.status, message.value);
return; return;
} }
@ -750,36 +764,38 @@ public class MeetingService implements MessageListener {
} }
} }
@Override @Override
public void handle(IMessage message) { public void handle(IMessage message) {
receivedMessages.add(message); receivedMessages.add(message);
} }
public void setParamsProcessorUtil(ParamsProcessorUtil util) { public void setParamsProcessorUtil(ParamsProcessorUtil util) {
this.paramsProcessorUtil = util; this.paramsProcessorUtil = util;
} }
public void setPresDownloadService(PresentationUrlDownloadService presDownloadService) { public void setPresDownloadService(
PresentationUrlDownloadService presDownloadService) {
this.presDownloadService = presDownloadService; this.presDownloadService = presDownloadService;
} }
private void processStunTurnInfoRequested (StunTurnInfoRequested message) { private void processStunTurnInfoRequested(StunTurnInfoRequested message) {
Set<StunServer> stuns = stunTurnService.getStunServers(); Set<StunServer> stuns = stunTurnService.getStunServers();
log.info("\nhere are the stuns:"); log.info("\nhere are the stuns:");
for(StunServer s : stuns) { for (StunServer s : stuns) {
log.info("a stun: " + s.url); log.info("a stun: " + s.url);
} }
Set<TurnEntry> turns = stunTurnService.getStunAndTurnServersFor(message.internalUserId); Set<TurnEntry> turns = stunTurnService
log.info("\nhere are the (" + turns.size() +") turns for internalUserId:" + message.internalUserId); .getStunAndTurnServersFor(message.internalUserId);
for(TurnEntry t : turns) { log.info("\nhere are the (" + turns.size()
log.info("a turn: " + t.url + "username/pass=" + t.username + '/' + t.password); + ") turns for internalUserId:" + message.internalUserId);
for (TurnEntry t : turns) {
log.info("a turn: " + t.url + "username/pass=" + t.username + '/'
+ t.password);
} }
messagingService.sendStunTurnInfo(message.meetingId, message.internalUserId, stuns, turns); messagingService.sendStunTurnInfo(message.meetingId,
message.internalUserId, stuns, turns);
} }
public void userJoinedVoice(UserJoinedVoice message) { public void userJoinedVoice(UserJoinedVoice message) {
Meeting m = getMeeting(message.meetingId); Meeting m = getMeeting(message.meetingId);
if (m != null) { if (m != null) {
@ -886,7 +902,6 @@ public class MeetingService implements MessageListener {
runExec.execute(task); runExec.execute(task);
} }
public void start() { public void start() {
log.info("Starting Meeting Service."); log.info("Starting Meeting Service.");
try { try {
@ -936,7 +951,8 @@ public class MeetingService implements MessageListener {
messagingService = mess; messagingService = mess;
} }
public void setExpiredMeetingCleanupTimerTask(ExpiredMeetingCleanupTimerTask c) { public void setExpiredMeetingCleanupTimerTask(
ExpiredMeetingCleanupTimerTask c) {
cleaner = c; cleaner = c;
cleaner.setMeetingService(this); cleaner.setMeetingService(this);
cleaner.start(); cleaner.start();
@ -946,11 +962,14 @@ public class MeetingService implements MessageListener {
removeMeetingWhenEnded = s; removeMeetingWhenEnded = s;
} }
public void setRegisteredUserCleanupTimerTask(RegisteredUserCleanupTimerTask c) { public void setRegisteredUserCleanupTimerTask(
RegisteredUserCleanupTimerTask c) {
registeredUserCleaner = c; registeredUserCleaner = c;
registeredUserCleaner.setMeetingService(this); registeredUserCleaner.setMeetingService(this);
registeredUserCleaner.start(); registeredUserCleaner.start();
} }
public void setStunTurnService(StunTurnService s) { stunTurnService = s; } public void setStunTurnService(StunTurnService s) {
stunTurnService = s;
}
} }

View File

@ -79,7 +79,8 @@ public class MeetingMessageHandler implements MessageHandler {
msg.payload.viewerPassword, msg.payload.viewerPassword,
msg.payload.moderatorPassword, msg.payload.moderatorPassword,
msg.payload.durationInMinutes, msg.payload.durationInMinutes,
msg.payload.defaultPresentationURL, msg.payload.sourcePresentationId,
msg.payload.sourcePresentationSlide,
msg.payload.record msg.payload.record
) )
); );

View File

@ -9,13 +9,15 @@ public class CreateBreakoutRoom implements IMessage {
public final String viewerPassword; public final String viewerPassword;
public final String moderatorPassword; public final String moderatorPassword;
public final Integer durationInMinutes; // The duration of the breakout room public final Integer durationInMinutes; // The duration of the breakout room
public final String defaultPresentationURL; public final String sourcePresentationId;
public final Integer sourcePresentationSlide;
public final Boolean record; public final Boolean record;
public CreateBreakoutRoom(String breakoutId, String parentId, String name, public CreateBreakoutRoom(String breakoutId, String parentId, String name,
String voiceConfId, String viewerPassword, String voiceConfId, String viewerPassword,
String moderatorPassword, Integer duration, String moderatorPassword, Integer duration,
String defaultPresentationURL, Boolean record) { String sourcePresentationId, Integer sourcePresentationSlide,
Boolean record) {
this.breakoutId = breakoutId; this.breakoutId = breakoutId;
this.parentId = parentId; this.parentId = parentId;
this.name = name; this.name = name;
@ -23,7 +25,8 @@ public class CreateBreakoutRoom implements IMessage {
this.viewerPassword = viewerPassword; this.viewerPassword = viewerPassword;
this.moderatorPassword = moderatorPassword; this.moderatorPassword = moderatorPassword;
this.durationInMinutes = duration; this.durationInMinutes = duration;
this.defaultPresentationURL = defaultPresentationURL; this.sourcePresentationId = sourcePresentationId;
this.sourcePresentationSlide = sourcePresentationSlide;
this.record = record; this.record = record;
} }
} }

View File

@ -1,25 +1,18 @@
package org.bigbluebutton.presentation; package org.bigbluebutton.presentation;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.FilenameFilter;
import java.net.HttpURLConnection;
import java.net.MalformedURLException;
import java.net.URL;
import java.util.Timer;
import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.codec.digest.DigestUtils;
import org.apache.commons.httpclient.HttpClient; import org.apache.commons.io.FilenameUtils;
import org.apache.commons.httpclient.HttpException;
import org.apache.commons.httpclient.HttpStatus;
import org.apache.commons.httpclient.methods.GetMethod;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
public class PresentationUrlDownloadService { public class PresentationUrlDownloadService {
private static Logger log = LoggerFactory.getLogger(PresentationUrlDownloadService.class); private static Logger log = LoggerFactory
.getLogger(PresentationUrlDownloadService.class);
private final int maxRedirects = 5; private PageExtractor pageExtractor;
private DocumentConversionService documentConversionService; private DocumentConversionService documentConversionService;
private String presentationBaseURL; private String presentationBaseURL;
private String presentationDir; private String presentationDir;
@ -28,31 +21,53 @@ public class PresentationUrlDownloadService {
documentConversionService.processDocument(uploadedPres); documentConversionService.processDocument(uploadedPres);
} }
public void processUploadedFile(String meetingId, String presId, String filename, File presFile) { public void processUploadedFile(String meetingId, String presId,
UploadedPresentation uploadedPres = new UploadedPresentation(meetingId, presId, filename, presentationBaseURL); String filename, File presFile) {
UploadedPresentation uploadedPres = new UploadedPresentation(meetingId,
presId, filename, presentationBaseURL);
uploadedPres.setUploadedFile(presFile); uploadedPres.setUploadedFile(presFile);
processUploadedPresentation(uploadedPres); processUploadedPresentation(uploadedPres);
} }
public void downloadAndProcessDocument(String address, String meetingId) { public void extractPage(String sourceMeetingId, String presentationId,
log.debug("downloadAndProcessDocument [pres=" + address + ", meeting=" + meetingId + "]"); Integer presentationSlide, String destinationMeetingId) {
String presFilename = address.substring(address.lastIndexOf('/') + 1); // Construct the source meeting path
log.debug("downloadAndProcessDocument [filename=" + presFilename + "]"); File sourceMeetingPath = new File(presentationDir + File.separator
String filenameExt = getFilenameExt(presFilename); + sourceMeetingId + File.separator + sourceMeetingId
+ File.separator + presentationId);
String presId = generatePresentationId(presFilename); final String presentationFilter = presentationId;
File uploadDir = createPresentationDirectory(meetingId, presentationDir, presId); FilenameFilter filter = new FilenameFilter() {
if (uploadDir != null) { public boolean accept(File dir, String name) {
String newFilename = createNewFilename(presId, filenameExt); return name.startsWith(presentationFilter);
String newFilePath = uploadDir.getAbsolutePath() + File.separatorChar + newFilename;
if (savePresentation(meetingId, newFilePath, address)) {
File pres = new File(newFilePath);
processUploadedFile(meetingId, presId, presFilename, pres);
} else {
log.error("Failed to download presentation=[" + address + "], meeting=[" + meetingId + "]");
} }
};
File[] children = sourceMeetingPath.listFiles(filter);
if (children.length != 1) {
log.error("Not matching file with prefix {} found at {}",
sourceMeetingId, sourceMeetingPath);
return;
} else {
File sourcePresentationFile = children[0];
String filenameExt = FilenameUtils
.getExtension(sourcePresentationFile.getName());
String presId = generatePresentationId(presentationId);
String newFilename = createNewFilename(presId, filenameExt);
File uploadDir = createPresentationDirectory(destinationMeetingId,
presentationDir, presId);
String newFilePath = uploadDir.getAbsolutePath()
+ File.separatorChar + newFilename;
pageExtractor.extractPage(sourcePresentationFile, new File(
newFilePath), presentationSlide);
File pres = new File(newFilePath);
processUploadedFile(destinationMeetingId, presId, "default-"
+ presentationSlide.toString() + "." + filenameExt, pres);
} }
} }
@ -61,16 +76,14 @@ public class PresentationUrlDownloadService {
return DigestUtils.shaHex(name) + "-" + timestamp; return DigestUtils.shaHex(name) + "-" + timestamp;
} }
public String getFilenameExt(String filename) {
return filename.substring(filename.lastIndexOf("."));
}
public String createNewFilename(String presId, String fileExt) { public String createNewFilename(String presId, String fileExt) {
return presId + fileExt; return presId + "." + fileExt;
} }
public File createPresentationDirectory(String meetingId, String presentationDir, String presentationId) { public File createPresentationDirectory(String meetingId,
String meetingPath = presentationDir + File.separatorChar + meetingId + File.separatorChar + meetingId; String presentationDir, String presentationId) {
String meetingPath = presentationDir + File.separatorChar + meetingId
+ File.separatorChar + meetingId;
String presPath = meetingPath + File.separatorChar + presentationId; String presPath = meetingPath + File.separatorChar + presentationId;
File dir = new File(presPath); File dir = new File(presPath);
log.debug("Creating dir [{}]", presPath); log.debug("Creating dir [{}]", presPath);
@ -80,77 +93,8 @@ public class PresentationUrlDownloadService {
return null; return null;
} }
private String followRedirect(String meetingId, String redirectUrl, public void setPageExtractor(PageExtractor extractor) {
int redirectCount, String origUrl) { this.pageExtractor = extractor;
if (redirectCount > maxRedirects) {
log.error("Max redirect reached for meeting=[{}] with url=[{}]", meetingId, origUrl);
return null;
}
URL presUrl;
try {
presUrl = new URL(redirectUrl);
} catch (MalformedURLException e) {
log.error("Malformed url=[{}] for meeting=[{}]", redirectUrl, meetingId);
return null;
}
HttpURLConnection conn;
try {
conn = (HttpURLConnection) presUrl.openConnection();
conn.setReadTimeout(5000);
conn.addRequestProperty("Accept-Language", "en-US,en;q=0.8");
conn.addRequestProperty("User-Agent", "Mozilla");
// normally, 3xx is redirect
int status = conn.getResponseCode();
if (status != HttpURLConnection.HTTP_OK) {
if (status == HttpURLConnection.HTTP_MOVED_TEMP
|| status == HttpURLConnection.HTTP_MOVED_PERM
|| status == HttpURLConnection.HTTP_SEE_OTHER) {
String newUrl = conn.getHeaderField("Location");
return followRedirect(meetingId, newUrl, redirectCount + 1, origUrl);
} else {
log.error("Invalid HTTP response=[{}] for url=[{}] with meeting[{}]", status, redirectUrl, meetingId);
return null;
}
} else {
return redirectUrl;
}
} catch (IOException e) {
log.error("IOException for url=[{}] with meeting[{}]", redirectUrl, meetingId);
return null;
}
}
public boolean savePresentation(final String meetingId, final String filename,
final String urlString) {
String finalUrl = followRedirect(meetingId, urlString, 0, urlString);
if (finalUrl == null) return false;
boolean success = false;
GetMethod method = new GetMethod(finalUrl);
HttpClient client = new HttpClient();
try {
int statusCode = client.executeMethod(method);
if (statusCode == HttpStatus.SC_OK) {
FileUtils.copyInputStreamToFile(
method.getResponseBodyAsStream(), new File(filename));
log.info("Downloaded presentation at [{}]", finalUrl);
success = true;
}
} catch (HttpException e) {
log.error("HttpException while downloading presentation at [{}]", finalUrl);
} catch (IOException e) {
log.error("IOException while downloading presentation at [{}]", finalUrl);
} finally {
method.releaseConnection();
}
return success;
} }
public void setPresentationDir(String presDir) { public void setPresentationDir(String presDir) {
@ -161,9 +105,9 @@ public class PresentationUrlDownloadService {
presentationBaseURL = presentationBaseUrl; presentationBaseURL = presentationBaseUrl;
} }
public void setDocumentConversionService(DocumentConversionService documentConversionService) { public void setDocumentConversionService(
DocumentConversionService documentConversionService) {
this.documentConversionService = documentConversionService; this.documentConversionService = documentConversionService;
} }
} }

View File

@ -1,48 +1,52 @@
/** /**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/ * BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
* *
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below). * 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 * 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 * 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 * Foundation; either version 3.0 of the License, or (at your option) any later
* version. * version.
* *
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY * 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 * WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. * 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 * You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>. * with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
* *
*/ */
package org.bigbluebutton.presentation.imp; package org.bigbluebutton.presentation.imp;
import java.io.File; import java.io.File;
import org.bigbluebutton.presentation.PageExtractor; import org.bigbluebutton.presentation.PageExtractor;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
public class GhostscriptPageExtractor implements PageExtractor { public class GhostscriptPageExtractor implements PageExtractor {
private static Logger log = LoggerFactory.getLogger(GhostscriptPageExtractor.class); private static Logger log = LoggerFactory
.getLogger(GhostscriptPageExtractor.class);
private String GHOSTSCRIPT_EXEC; private String GHOSTSCRIPT_EXEC;
private String noPdfMarkWorkaround; private String noPdfMarkWorkaround;
private String SPACE = " "; private String SPACE = " ";
public boolean extractPage(File presentationFile, File output, int page){ public boolean extractPage(File presentationFile, File output, int page) {
String OPTIONS = "-sDEVICE=pdfwrite -dNOPAUSE -dQUIET -dBATCH"; String OPTIONS = "-sDEVICE=pdfwrite -dNOPAUSE -dQUIET -dBATCH";
String FIRST_PAGE = "-dFirstPage=" + page; String FIRST_PAGE = "-dFirstPage=" + page;
String LAST_PAGE = "-dLastPage=" + page; String LAST_PAGE = "-dLastPage=" + page;
String DESTINATION = output.getAbsolutePath(); String DESTINATION = output.getAbsolutePath();
String OUTPUT_FILE = "-sOutputFile=" + DESTINATION; String OUTPUT_FILE = "-sOutputFile=" + DESTINATION;
//extract that specific page and create a temp-pdf(only one page) with GhostScript // extract that specific page and create a temp-pdf(only one page) with
String COMMAND = GHOSTSCRIPT_EXEC + SPACE + OPTIONS + SPACE + FIRST_PAGE + SPACE + LAST_PAGE + SPACE // GhostScript
+ OUTPUT_FILE + SPACE + noPdfMarkWorkaround + SPACE + presentationFile.getAbsolutePath(); String COMMAND = GHOSTSCRIPT_EXEC + SPACE + OPTIONS + SPACE
+ FIRST_PAGE + SPACE + LAST_PAGE + SPACE + OUTPUT_FILE + SPACE
+ noPdfMarkWorkaround + SPACE
+ presentationFile.getAbsolutePath();
log.debug(COMMAND);
return new ExternalProcessExecutor().exec(COMMAND, 60000); return new ExternalProcessExecutor().exec(COMMAND, 60000);
} }