Merge branch 'master' of https://github.com/bigbluebutton/bigbluebutton into chat-improvements
Conflicts: bigbluebutton-html5/app/client/views/whiteboard/whiteboard.coffee
This commit is contained in:
commit
da7242737b
@ -390,7 +390,7 @@ trait UsersApp {
|
||||
* and is reconnecting. Make the user as joined only in the voice conference. If we get a
|
||||
* user left voice conference message, then we will remove the user from the users list.
|
||||
*/
|
||||
handleUserJoinedVoiceConfMessage((new UserJoinedVoiceConfMessage(mProps.voiceBridge,
|
||||
switchUserToPhoneUser((new UserJoinedVoiceConfMessage(mProps.voiceBridge,
|
||||
vu.userId, u.userID, u.externUserID, vu.callerName,
|
||||
vu.callerNum, vu.muted, vu.talking, u.listenOnly)));
|
||||
}
|
||||
@ -461,6 +461,34 @@ trait UsersApp {
|
||||
}
|
||||
}
|
||||
|
||||
def switchUserToPhoneUser(msg: UserJoinedVoiceConfMessage) = {
|
||||
log.info("User has been disconnected but still in voice conf. Switching to phone user. meetingId="
|
||||
+ mProps.meetingID + " callername=" + msg.callerIdName
|
||||
+ " userId=" + msg.userId + " extUserId=" + msg.externUserId)
|
||||
|
||||
usersModel.getUser(msg.userId) match {
|
||||
case Some(user) => {
|
||||
val vu = new VoiceUser(msg.voiceUserId, msg.userId, msg.callerIdName,
|
||||
msg.callerIdNum, joined = true, locked = false,
|
||||
msg.muted, msg.talking, msg.listenOnly)
|
||||
val nu = user.copy(voiceUser = vu, listenOnly = msg.listenOnly)
|
||||
usersModel.addUser(nu)
|
||||
|
||||
log.info("User joined voice. meetingId=" + mProps.meetingID + " userId=" + user.userID + " user=" + nu)
|
||||
outGW.send(new UserJoinedVoice(mProps.meetingID, mProps.recorded, mProps.voiceBridge, nu))
|
||||
|
||||
if (meetingModel.isMeetingMuted()) {
|
||||
outGW.send(new MuteVoiceUser(mProps.meetingID, mProps.recorded,
|
||||
nu.userID, nu.userID, mProps.voiceBridge,
|
||||
nu.voiceUser.userId, meetingModel.isMeetingMuted()))
|
||||
}
|
||||
}
|
||||
case None => {
|
||||
handleUserJoinedVoiceFromPhone(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def handleUserJoinedVoiceConfMessage(msg: UserJoinedVoiceConfMessage) = {
|
||||
log.info("Received user joined voice. meetingId=" + mProps.meetingID + " callername=" + msg.callerIdName
|
||||
+ " userId=" + msg.userId + " extUserId=" + msg.externUserId)
|
||||
|
@ -43,6 +43,8 @@ Author: Fred Dixon <ffdixon@bigbluebutton.org>
|
||||
|
||||
<%@ include file="bbb_api.jsp"%>
|
||||
<%@ page import="java.util.regex.*"%>
|
||||
<%@ page import="org.apache.commons.lang.StringEscapeUtils"%>
|
||||
|
||||
|
||||
<br>
|
||||
|
||||
@ -121,7 +123,7 @@ $(document).ready(function(){
|
||||
<tbody>
|
||||
<tr>
|
||||
<td width="50%">
|
||||
<center><strong> <%=username%>'s meeting</strong> has been
|
||||
<center><strong> <%=StringEscapeUtils.escapeXml(username)%>'s meeting</strong> has been
|
||||
created.</center>
|
||||
</td>
|
||||
|
||||
@ -213,7 +215,7 @@ function mycallback() {
|
||||
<tr>
|
||||
<td width="50%">
|
||||
|
||||
<p>Hi <%=username%>,</p>
|
||||
<p>Hi <%=StringEscapeUtils.escapeXml(username)%>,</p>
|
||||
<p>Now waiting for the moderator to start <strong><%=meetingID%></strong>.</p>
|
||||
<br />
|
||||
<p>(Your browser will automatically refresh and join the meeting
|
||||
|
@ -26,9 +26,6 @@
|
||||
color = colourToHex(color)
|
||||
color
|
||||
|
||||
@getCurrentSlideDoc = -> # returns only one document
|
||||
BBB.getCurrentSlide()
|
||||
|
||||
@getInSession = (k) -> SessionAmplify.get k
|
||||
|
||||
@getTime = -> # returns epoch in ms
|
||||
@ -60,7 +57,9 @@ Handlebars.registerHelper "getCurrentMeeting", ->
|
||||
Meteor.Meetings.findOne()
|
||||
|
||||
Handlebars.registerHelper "getCurrentSlide", ->
|
||||
getCurrentSlideDoc()
|
||||
result = BBB.getCurrentSlide("helper getCurrentSlide")
|
||||
# console.log "result=#{JSON.stringify result}"
|
||||
result
|
||||
|
||||
# Allow access through all templates
|
||||
Handlebars.registerHelper "getInSession", (k) -> SessionAmplify.get k
|
||||
@ -69,7 +68,7 @@ Handlebars.registerHelper "getMeetingName", ->
|
||||
BBB.getMeetingName()
|
||||
|
||||
Handlebars.registerHelper "getShapesForSlide", ->
|
||||
currentSlide = getCurrentSlideDoc()
|
||||
currentSlide = BBB.getCurrentSlide("helper getShapesForSlide")
|
||||
|
||||
# try to reuse the lines above
|
||||
Meteor.Shapes.find({whiteboardId: currentSlide?.slide?.id})
|
||||
@ -112,6 +111,9 @@ Handlebars.registerHelper "isCurrentUserTalking", ->
|
||||
Handlebars.registerHelper "isCurrentUserPresenter", ->
|
||||
BBB.isUserPresenter(getInSession('userId'))
|
||||
|
||||
Handlebars.registerHelper "isCurrentUserModerator", ->
|
||||
BBB.getMyRole() is "MODERATOR"
|
||||
|
||||
Handlebars.registerHelper "isDisconnected", ->
|
||||
return !Meteor.status().connected
|
||||
|
||||
@ -149,7 +151,7 @@ Handlebars.registerHelper "pointerLocation", ->
|
||||
currentPresentation = Meteor.Presentations.findOne({"presentation.current": true})
|
||||
presentationId = currentPresentation?.presentation?.id
|
||||
currentSlideDoc = Meteor.Slides.findOne({"presentationId": presentationId, "slide.current": true})
|
||||
pointer = currentPresentation?.pointer
|
||||
pointer = Meteor.Cursor.findOne()
|
||||
pointer.x = (- currentSlideDoc.slide.x_offset * 2 + currentSlideDoc.slide.width_ratio * pointer.x) / 100
|
||||
pointer.y = (- currentSlideDoc.slide.y_offset * 2 + currentSlideDoc.slide.height_ratio * pointer.y) / 100
|
||||
pointer
|
||||
@ -445,6 +447,12 @@ Handlebars.registerHelper "getPollQuestions", ->
|
||||
console.log "logging out"
|
||||
clearSessionVar(document.location = getInSession 'logoutURL') # navigate to logout
|
||||
|
||||
@kickUser = (meetingId, toKickUserId, requesterUserId, authToken) ->
|
||||
Meteor.call("kickUser", meetingId, toKickUserId, requesterUserId, authToken)
|
||||
|
||||
@setUserPresenter = (meetingId, newPresenterId, requesterSetPresenter, newPresenterName, authToken) ->
|
||||
Meteor.call("setUserPresenter", meetingId, newPresenterId, requesterSetPresenter, newPresenterName, authToken)
|
||||
|
||||
# Clear the local user session
|
||||
@clearSessionVar = (callback) ->
|
||||
amplify.store('authToken', null)
|
||||
|
@ -123,10 +123,12 @@ https://github.com/bigbluebutton/bigbluebutton/blob/master/bigbluebutton-client/
|
||||
|
||||
return lockedMicForRoom and BBB.amILocked()
|
||||
|
||||
BBB.getCurrentSlide = ->
|
||||
BBB.getCurrentSlide = (callingLocaton)->
|
||||
currentPresentation = Meteor.Presentations.findOne({"presentation.current": true})
|
||||
presentationId = currentPresentation?.presentation?.id
|
||||
currentSlide = Meteor.Slides.findOne({"presentationId": presentationId, "slide.current": true})
|
||||
# console.log "trigger:#{callingLocaton} currentSlideId=#{currentSlide?._id}"
|
||||
currentSlide
|
||||
|
||||
BBB.getMeetingName = ->
|
||||
Meteor.Meetings.findOne()?.meetingName or null
|
||||
|
@ -42,11 +42,11 @@
|
||||
// 120px is size of the icon list with all icons enabled (lock, cam, mic/listen)
|
||||
|
||||
@media @landscape {
|
||||
height: 27px;
|
||||
min-height: 30px;
|
||||
font-size: 4.5mm;
|
||||
}
|
||||
@media @desktop-portrait {
|
||||
height: 27px;
|
||||
min-height: 30px;
|
||||
font-size: 4.5mm;
|
||||
}
|
||||
@media @phone-portrait, @phone-portrait-with-keyboard {
|
||||
@ -104,9 +104,30 @@
|
||||
max-height: 100% !important;
|
||||
height: 100%;
|
||||
|
||||
#content{
|
||||
.setPresenter{
|
||||
color: #A0A0A0;
|
||||
}
|
||||
|
||||
@media @desktop-landscape, @desktop-portrait {
|
||||
.kickUser, .setPresenter{
|
||||
opacity:0;
|
||||
color: #fff;
|
||||
cursor: pointer;
|
||||
display: none;
|
||||
-webkit-animation: fadeInAnimation .5s;
|
||||
animation: fadeInAnimation .5s;
|
||||
}
|
||||
}
|
||||
}
|
||||
#content:hover {
|
||||
@media @landscape, @desktop-portrait {
|
||||
background-color: #2C4155;
|
||||
|
||||
.kickUser, .setPresenter{
|
||||
display: inline;
|
||||
opacity:1;
|
||||
}
|
||||
}
|
||||
}
|
||||
#content {
|
||||
@ -195,3 +216,12 @@
|
||||
padding-left: 5px;
|
||||
}
|
||||
}
|
||||
|
||||
@-webkit-keyframes fadeInAnimation {
|
||||
0% {
|
||||
opacity: 0;
|
||||
}
|
||||
100% {
|
||||
opacity: 1;
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,9 @@ Template.displayUserIcons.events
|
||||
# the userId of the person who is lowering the hand
|
||||
BBB.lowerHand(getInSession("meetingId"), @userId, getInSession("userId"), getInSession("authToken"))
|
||||
|
||||
'click .kickUser': (event) ->
|
||||
kickUser BBB.getMeetingId(), @.userId, getInSession("userId"), getInSession("authToken")
|
||||
|
||||
Template.displayUserIcons.helpers
|
||||
userLockedIconApplicable: (userId) ->
|
||||
# the lock settings affect the user (and requiire a lock icon) if
|
||||
@ -54,6 +57,9 @@ Template.usernameEntry.events
|
||||
break
|
||||
setInSession 'chats', chats
|
||||
|
||||
'click .setPresenter': (event) ->
|
||||
setUserPresenter BBB.getMeetingId(), @.userId, getInSession('userId'), @.user.name, getInSession('authToken')
|
||||
|
||||
Template.usernameEntry.helpers
|
||||
hasGotUnreadMailClass: (userId) ->
|
||||
chats = getInSession('chats')
|
||||
|
@ -43,6 +43,14 @@
|
||||
{{/if}}
|
||||
{{/if}}
|
||||
|
||||
{{#unless isCurrentUser userId}}
|
||||
{{#if isCurrentUserModerator}}
|
||||
<span class="kickUser" rel="tooltip" data-placement="bottom" title="Kick {{user.name}}">
|
||||
<i class="icon fi-x-circle usericon"></i>
|
||||
</span>
|
||||
{{/if}}
|
||||
{{/unless}}
|
||||
|
||||
{{#if isUserSharingVideo userId}}
|
||||
<span rel="tooltip" data-placement="bottom" title="{{user.name}} is sharing their webcam">
|
||||
<i class="icon fi-video usericon"></i>
|
||||
@ -58,6 +66,13 @@
|
||||
|
||||
<template name="usernameEntry">
|
||||
<div class="status">
|
||||
{{#if isCurrentUserModerator}}
|
||||
{{#unless user.presenter}}
|
||||
<span class="setPresenter" rel="tooltip" data-placement="bottom" title="set {{user.name}} as presenter">
|
||||
<i class="icon fi-projection-screen statusIcon"></i>
|
||||
</span>
|
||||
{{/unless}}
|
||||
{{/if}}
|
||||
{{#if equals user.emoji_status "happy"}}
|
||||
{{> icon name="happy-face" size="25"}}
|
||||
{{else}}
|
||||
|
@ -1,10 +1,13 @@
|
||||
Template.slide.rendered = ->
|
||||
currentSlide = getCurrentSlideDoc()
|
||||
reactOnSlideChange(@)
|
||||
|
||||
@reactOnSlideChange = =>
|
||||
currentSlide = BBB.getCurrentSlide("slide.rendered")
|
||||
|
||||
pic = new Image()
|
||||
pic.onload = ->
|
||||
setInSession 'slideOriginalWidth', this.width
|
||||
setInSession 'slideOriginalHeight', this.height
|
||||
setInSession 'slideOriginalWidth', @width
|
||||
setInSession 'slideOriginalHeight', @height
|
||||
$(window).resize( ->
|
||||
# redraw the whiteboard to adapt to the resized window
|
||||
if !$('.panel-footer').hasClass('ui-resizable-resizing') # not in the middle of resizing the message input
|
||||
@ -14,13 +17,15 @@ Template.slide.rendered = ->
|
||||
createWhiteboardPaper (wpm) ->
|
||||
displaySlide wpm
|
||||
pic.src = currentSlide?.slide?.img_uri
|
||||
return ""
|
||||
|
||||
@createWhiteboardPaper = (callback) =>
|
||||
# console.log "CREATING WPM"
|
||||
@whiteboardPaperModel = new Meteor.WhiteboardPaperModel('whiteboard-paper')
|
||||
callback(@whiteboardPaperModel)
|
||||
|
||||
@displaySlide = (wpm) ->
|
||||
currentSlide = getCurrentSlideDoc()
|
||||
currentSlide = BBB.getCurrentSlide("displaySlide")
|
||||
|
||||
wpm.create()
|
||||
adjustedDimensions = scaleSlide(getInSession('slideOriginalWidth'), getInSession('slideOriginalHeight'))
|
||||
@ -32,7 +37,7 @@ Template.slide.rendered = ->
|
||||
|
||||
return if Meteor.WhiteboardCleanStatus.findOne({in_progress: true})?
|
||||
|
||||
currentSlide = getCurrentSlideDoc()
|
||||
currentSlide = BBB.getCurrentSlide("manuallyDisplayShapes")
|
||||
wpm = @whiteboardPaperModel
|
||||
shapes = Meteor.Shapes.find({whiteboardId: currentSlide?.slide?.id}).fetch()
|
||||
for s in shapes
|
||||
@ -90,9 +95,7 @@ Template.slide.rendered = ->
|
||||
|
||||
Template.slide.helpers
|
||||
updatePointerLocation: (pointer) ->
|
||||
if whiteboardPaperModel?
|
||||
wpm = whiteboardPaperModel
|
||||
wpm?.moveCursor(pointer.x, pointer.y)
|
||||
whiteboardPaperModel?.moveCursor(pointer.x, pointer.y)
|
||||
|
||||
#### SHAPE ####
|
||||
Template.shape.rendered = ->
|
||||
|
@ -1,12 +1,12 @@
|
||||
# scale the whiteboard to adapt to the resized window
|
||||
@scaleWhiteboard = (callback) ->
|
||||
adjustedDimensions = scaleSlide(getInSession('slideOriginalWidth'), getInSession('slideOriginalHeight'))
|
||||
wpm = whiteboardPaperModel
|
||||
wpm.scale(adjustedDimensions.width, adjustedDimensions.height)
|
||||
if whiteboardPaperModel?
|
||||
whiteboardPaperModel.scale(adjustedDimensions.width, adjustedDimensions.height)
|
||||
|
||||
if callback
|
||||
callback()
|
||||
|
||||
Template.whiteboard.helpers
|
||||
isPollStarted: ->
|
||||
if BBB.isPollGoing(getInSession('userId'))
|
||||
return true
|
||||
@ -16,6 +16,17 @@ Template.whiteboard.helpers
|
||||
hasNoPresentation: ->
|
||||
Meteor.Presentations.findOne({'presentation.current':true})
|
||||
|
||||
forceSlideShow: ->
|
||||
reactOnSlideChange()
|
||||
|
||||
clearSlide: ->
|
||||
#clear the slide
|
||||
whiteboardPaperModel?.removeAllImagesFromPaper()
|
||||
|
||||
# hide the cursor
|
||||
whiteboardPaperModel?.cursor?.remove()
|
||||
|
||||
|
||||
Template.whiteboard.events
|
||||
'click .whiteboardFullscreenButton': (event, template) ->
|
||||
enterWhiteboardFullscreen()
|
||||
@ -76,16 +87,13 @@ Template.whiteboard.events
|
||||
|
||||
Template.whiteboardControls.helpers
|
||||
presentationProgress: ->
|
||||
console.log "test"
|
||||
currentPresentation = Meteor.Presentations.findOne({'presentation.current':true})
|
||||
currentSlideNum = Meteor.Slides.findOne({'presentationId': currentPresentation?.presentation.id, 'slide.current':true})?.slide.num
|
||||
totalSlideNum = Meteor.Slides.find({'presentationId': currentPresentation?.presentation.id})?.count()
|
||||
console.log('slide', currentSlideNum)
|
||||
if currentSlideNum isnt undefined
|
||||
console.log currentSlideNum
|
||||
return "#{currentSlideNum}/#{totalSlideNum}"
|
||||
else
|
||||
console.log currentSlideNum
|
||||
return ''
|
||||
|
||||
Template.whiteboardControls.events
|
||||
|
@ -1,8 +1,12 @@
|
||||
<template name="whiteboard">
|
||||
<div id="{{id}}" {{visibility name}} class="component">
|
||||
{{#if getCurrentSlide}}
|
||||
{{> slide}}
|
||||
{{>slide}}
|
||||
{{forceSlideShow}}
|
||||
{{else}}
|
||||
{{clearSlide}}
|
||||
{{/if}}
|
||||
|
||||
<div id="whiteboard-container" class="{{whiteboardSize}}">
|
||||
<div id="whiteboard-paper">
|
||||
</div>
|
||||
|
@ -33,6 +33,6 @@ Meteor.methods
|
||||
|
||||
# applies zooming to the stroke thickness
|
||||
@zoomStroke = (thickness) ->
|
||||
currentSlide = @getCurrentSlideDoc()
|
||||
currentSlide = BBB.getCurrentSlide("zoomStroke")
|
||||
ratio = (currentSlide?.slide.width_ratio + currentSlide?.slide.height_ratio) / 2
|
||||
thickness * 100 / ratio
|
||||
|
@ -240,9 +240,9 @@ class Meteor.WhiteboardPaperModel
|
||||
@containerOffsetTop = $container.offset()?.top
|
||||
|
||||
_updateZoomRatios: ->
|
||||
currentSlideDoc = getCurrentSlideDoc()
|
||||
@widthRatio = currentSlideDoc.slide.width_ratio
|
||||
@heightRatio = currentSlideDoc.slide.height_ratio
|
||||
currentSlideDoc = BBB.getCurrentSlide("_updateZoomRatios")
|
||||
@widthRatio = currentSlideDoc?.slide.width_ratio
|
||||
@heightRatio = currentSlideDoc?.slide.height_ratio
|
||||
|
||||
# Retrieves an image element from the paper.
|
||||
# The url must be in the slides array.
|
||||
@ -312,7 +312,7 @@ class Meteor.WhiteboardPaperModel
|
||||
boardWidth = @containerWidth
|
||||
boardHeight = @containerHeight
|
||||
|
||||
currentSlide = getCurrentSlideDoc()
|
||||
currentSlide = BBB.getCurrentSlide("_displayPage")
|
||||
currentPresentation = Meteor.Presentations.findOne({"presentation.current": true})
|
||||
presentationId = currentPresentation?.presentation?.id
|
||||
currentSlideCursor = Meteor.Slides.find({"presentationId": presentationId, "slide.current": true})
|
||||
@ -355,5 +355,6 @@ class Meteor.WhiteboardPaperModel
|
||||
@addImageToPaper(data, boardWidth, @adjustedHeight)
|
||||
@adjustedWidth = boardWidth
|
||||
|
||||
@zoomAndPan(currentSlide.slide.width_ratio, currentSlide.slide.height_ratio,
|
||||
currentSlide.slide.x_offset, currentSlide.slide.y_offset)
|
||||
if currentSlide?
|
||||
@zoomAndPan(currentSlide.slide.width_ratio, currentSlide.slide.height_ratio,
|
||||
currentSlide.slide.x_offset, currentSlide.slide.y_offset)
|
||||
|
@ -2,8 +2,9 @@ Meteor.Users = new Meteor.Collection("bbb_users")
|
||||
Meteor.Chat = new Meteor.Collection("bbb_chat")
|
||||
Meteor.Meetings = new Meteor.Collection("meetings")
|
||||
Meteor.Presentations = new Meteor.Collection("presentations")
|
||||
Meteor.Cursor = new Meteor.Collection("bbb_cursor")
|
||||
Meteor.Shapes = new Meteor.Collection("shapes")
|
||||
Meteor.Slides = new Meteor.Collection("slides")
|
||||
Meteor.Polls = new Meteor.Collection("bbb_poll")
|
||||
|
||||
Meteor.WhiteboardCleanStatus = new Meteor.Collection("whiteboard-clean-status")
|
||||
Meteor.WhiteboardCleanStatus = new Meteor.Collection("whiteboard-clean-status")
|
||||
|
@ -61,30 +61,31 @@
|
||||
Meteor.subscribe 'users', meetingId, userId, authToken, onError: onErrorFunction, onReady: =>
|
||||
Meteor.subscribe 'whiteboard-clean-status', meetingId, onReady: =>
|
||||
Meteor.subscribe 'bbb_poll', meetingId, userId, authToken, onReady: =>
|
||||
# done subscribing, start rendering the client and set default settings
|
||||
@render('main')
|
||||
onLoadComplete()
|
||||
Meteor.subscribe 'bbb_cursor', meetingId, onReady: =>
|
||||
# done subscribing, start rendering the client and set default settings
|
||||
@render('main')
|
||||
onLoadComplete()
|
||||
|
||||
handleLogourUrlError = () ->
|
||||
alert "Error: could not find the logoutURL"
|
||||
setInSession("logoutURL", document.location.hostname)
|
||||
return
|
||||
|
||||
# obtain the logoutURL
|
||||
a = $.ajax({dataType: 'json', url: '/bigbluebutton/api/enter'})
|
||||
a.done (data) ->
|
||||
if data.response.logoutURL? # for a meeting with 0 users
|
||||
setInSession("logoutURL", data.response.logoutURL)
|
||||
return
|
||||
else
|
||||
if data.response.logoutUrl? # for a running meeting
|
||||
setInSession("logoutURL", data.response.logoutUrl)
|
||||
handleLogourUrlError = () ->
|
||||
alert "Error: could not find the logoutURL"
|
||||
setInSession("logoutURL", document.location.hostname)
|
||||
return
|
||||
else
|
||||
handleLogourUrlError()
|
||||
|
||||
a.fail (data, textStatus, errorThrown) ->
|
||||
handleLogourUrlError()
|
||||
# obtain the logoutURL
|
||||
a = $.ajax({dataType: 'json', url: '/bigbluebutton/api/enter'})
|
||||
a.done (data) ->
|
||||
if data.response.logoutURL? # for a meeting with 0 users
|
||||
setInSession("logoutURL", data.response.logoutURL)
|
||||
return
|
||||
else
|
||||
if data.response.logoutUrl? # for a running meeting
|
||||
setInSession("logoutURL", data.response.logoutUrl)
|
||||
return
|
||||
else
|
||||
handleLogourUrlError()
|
||||
|
||||
a.fail (data, textStatus, errorThrown) ->
|
||||
handleLogourUrlError()
|
||||
|
||||
@render('loading')
|
||||
|
||||
|
@ -0,0 +1,39 @@
|
||||
# --------------------------------------------------------------------------------------------
|
||||
# Private methods on server
|
||||
# --------------------------------------------------------------------------------------------
|
||||
@initializeCursor = (meetingId) ->
|
||||
Meteor.Cursor.upsert({meetingId:meetingId}, {
|
||||
meetingId:meetingId
|
||||
x:0
|
||||
y:0
|
||||
}, (err, numChanged) ->
|
||||
if err
|
||||
Meteor.log.error "err upserting cursor for #{meetingId}"
|
||||
else
|
||||
# Meteor.log.info "ok upserting cursor for #{meetingId}"
|
||||
)
|
||||
|
||||
@updateCursorLocation = (meetingId, cursorObject) ->
|
||||
Meteor.Cursor.update({meetingId:meetingId}, {$set:{
|
||||
x:cursorObject.x
|
||||
y:cursorObject.y
|
||||
}}, (err, numChanged) ->
|
||||
if err?
|
||||
Meteor.log.error "_unsucc update of cursor for #{meetingId} #{JSON.stringify cursorObject}
|
||||
err=#{JSON.stringify err}"
|
||||
else
|
||||
# Meteor.log.info "updated cursor for #{meetingId} #{JSON.stringify cursorObject}"
|
||||
)
|
||||
|
||||
# called on server start and meeting end
|
||||
@clearCursorCollection = (meetingId) ->
|
||||
if meetingId?
|
||||
Meteor.Cursor.remove {meetingId: meetingId}, ->
|
||||
Meteor.log.info "cleared Cursor Collection (meetingId: #{meetingId})!"
|
||||
else
|
||||
Meteor.Cursor.remove {}, ->
|
||||
Meteor.log.info "cleared Cursor Collection (all meetings)!"
|
||||
|
||||
# --------------------------------------------------------------------------------------------
|
||||
# end Private methods on server
|
||||
# --------------------------------------------------------------------------------------------
|
@ -5,7 +5,7 @@
|
||||
@addMeetingToCollection = (meetingId, name, intendedForRecording, voiceConf, duration, callback) ->
|
||||
#check if the meeting is already in the collection
|
||||
|
||||
obj = Meteor.Meetings.upsert({meetingId:meetingId}, {$set: {
|
||||
Meteor.Meetings.upsert({meetingId:meetingId}, {$set: {
|
||||
meetingName:name
|
||||
intendedForRecording: intendedForRecording
|
||||
currentlyBeingRecorded: false # default value
|
||||
@ -31,6 +31,9 @@
|
||||
callback()
|
||||
)
|
||||
|
||||
# initialize the cursor in the meeting
|
||||
initializeCursor(meetingId)
|
||||
|
||||
|
||||
|
||||
@clearMeetingsCollection = (meetingId) ->
|
||||
@ -63,6 +66,9 @@
|
||||
# delete the meeting
|
||||
clearMeetingsCollection(meetingId)
|
||||
|
||||
# delete the cursor for the meeting
|
||||
clearCursorCollection(meetingId)
|
||||
|
||||
callback()
|
||||
else
|
||||
funct = (localCallback) ->
|
||||
|
@ -61,10 +61,6 @@ Meteor.methods
|
||||
name: presentationObject.name
|
||||
current: presentationObject.current
|
||||
|
||||
pointer: #initially we have no data about the cursor
|
||||
x: 0.0
|
||||
y: 0.0
|
||||
|
||||
id = Meteor.Presentations.insert(entry)
|
||||
#Meteor.log.info "presentation added id =[#{id}]:#{presentationObject.id} in #{meetingId}. Presentations.size is now #{Meteor.Presentations.find({meetingId: meetingId}).count()}"
|
||||
|
||||
|
@ -131,6 +131,41 @@ Meteor.methods
|
||||
Meteor.log.info "a user is logging out from #{meetingId}:" + userId
|
||||
requestUserLeaving meetingId, userId
|
||||
|
||||
#meetingId: the meeting where the user is
|
||||
#toKickUserId: the userid of the user to kick
|
||||
#requesterUserId: the userid of the user that wants to kick
|
||||
#authToken: the authToken of the user that wants to kick
|
||||
kickUser: (meetingId, toKickUserId, requesterUserId, authToken) ->
|
||||
if isAllowedTo('kickUser', meetingId, requesterUserId, authToken)
|
||||
message =
|
||||
"payload":
|
||||
"userid": toKickUserId
|
||||
"ejected_by": requesterUserId
|
||||
"meeting_id": meetingId
|
||||
"header":
|
||||
"name": "eject_user_from_meeting_request_message"
|
||||
|
||||
publish Meteor.config.redis.channels.toBBBApps.users, message
|
||||
|
||||
#meetingId: the meeting where the user is
|
||||
#newPresenterId: the userid of the new presenter
|
||||
#requesterSetPresenter: the userid of the user that wants to change the presenter
|
||||
#newPresenterName: user name of the new presenter
|
||||
#authToken: the authToken of the user that wants to kick
|
||||
setUserPresenter: (meetingId, newPresenterId, requesterSetPresenter, newPresenterName, authToken) ->
|
||||
if isAllowedTo('setPresenter', meetingId, requesterSetPresenter, authToken)
|
||||
message =
|
||||
"payload":
|
||||
"new_presenter_id": newPresenterId
|
||||
"new_presenter_name": newPresenterName
|
||||
"meeting_id": meetingId
|
||||
"assigned_by": requesterSetPresenter
|
||||
"header":
|
||||
"name": "assign_presenter_request_message"
|
||||
|
||||
publish Meteor.config.redis.channels.toBBBApps.users, message
|
||||
|
||||
|
||||
# --------------------------------------------------------------------------------------------
|
||||
# Private methods on server
|
||||
# --------------------------------------------------------------------------------------------
|
||||
|
@ -44,7 +44,7 @@ Meteor.publish 'users', (meetingId, userid, authToken) ->
|
||||
|
||||
Meteor.publish 'chat', (meetingId, userid, authToken) ->
|
||||
if isAllowedTo('subscribeChat', meetingId, userid, authToken)
|
||||
|
||||
|
||||
Meteor.log.info "publishing chat for #{meetingId} #{userid} #{authToken}"
|
||||
return Meteor.Chat.find({$or: [
|
||||
{'message.chat_type': 'PUBLIC_CHAT', 'meetingId': meetingId},
|
||||
@ -86,6 +86,10 @@ Meteor.publish 'presentations', (meetingId) ->
|
||||
Meteor.log.info "publishing presentations for #{meetingId}"
|
||||
Meteor.Presentations.find({meetingId: meetingId})
|
||||
|
||||
Meteor.publish 'bbb_cursor', (meetingId) ->
|
||||
Meteor.log.info "publishing cursor for #{meetingId}"
|
||||
Meteor.Cursor.find({meetingId: meetingId})
|
||||
|
||||
Meteor.publish 'whiteboard-clean-status', (meetingId) ->
|
||||
Meteor.log.info "whiteboard clean status #{meetingId}"
|
||||
Meteor.WhiteboardCleanStatus.find({meetingId: meetingId})
|
||||
|
@ -10,16 +10,17 @@ Meteor.startup ->
|
||||
clearSlidesCollection()
|
||||
clearPresentationsCollection()
|
||||
clearPollCollection()
|
||||
clearCursorCollection()
|
||||
|
||||
# create create a PubSub connection, start listening
|
||||
Meteor.redisPubSub = new Meteor.RedisPubSub(->
|
||||
Meteor.log.info "created pubsub")
|
||||
|
||||
|
||||
Meteor.myQueue = new PowerQueue({
|
||||
# autoStart:true
|
||||
# isPaused: true
|
||||
})
|
||||
|
||||
Meteor.myQueue.taskHandler = (data, next, failures) ->
|
||||
eventName = JSON.parse(data.jsonMsg)?.header.name
|
||||
if failures > 0
|
||||
@ -61,15 +62,15 @@ Meteor.startup ->
|
||||
"presentation_page_resized_message"
|
||||
"presentation_cursor_updated_message"
|
||||
"get_presentation_info_reply"
|
||||
# "get_users_reply"
|
||||
#"get_users_reply"
|
||||
"get_chat_history_reply"
|
||||
# "get_all_meetings_reply"
|
||||
#"get_all_meetings_reply"
|
||||
"get_whiteboard_shapes_reply"
|
||||
"presentation_shared_message"
|
||||
"presentation_conversion_done_message"
|
||||
"presentation_conversion_progress_message"
|
||||
"presentation_page_generated_message"
|
||||
# "presentation_page_changed_message"
|
||||
#"presentation_page_changed_message"
|
||||
"BbbPubSubPongMessage"
|
||||
"bbb_apps_is_alive_message"
|
||||
"user_voice_talking_message"
|
||||
@ -382,10 +383,12 @@ Meteor.startup ->
|
||||
|
||||
# for now not handling this serially #TODO
|
||||
else if eventName is "presentation_cursor_updated_message"
|
||||
x = message.payload.x_percent
|
||||
y = message.payload.y_percent
|
||||
Meteor.Presentations.update({"presentation.current": true, meetingId: meetingId},
|
||||
{$set: {"pointer.x": x, "pointer.y": y}})
|
||||
cursor =
|
||||
x: message.payload.x_percent
|
||||
y: message.payload.y_percent
|
||||
|
||||
# update the location of the cursor on the whiteboard
|
||||
updateCursorLocation(meetingId, cursor)
|
||||
callback()
|
||||
|
||||
# for now not handling this serially #TODO
|
||||
|
@ -42,6 +42,10 @@ moderator =
|
||||
setEmojiStatus: true
|
||||
clearEmojiStatus: true
|
||||
|
||||
#user control
|
||||
kickUser: true
|
||||
setPresenter: true
|
||||
|
||||
# holds the values for whether the viewer user is allowed to perform an action (true)
|
||||
# or false if not allowed. Some actions have dynamic values depending on the current lock settings
|
||||
viewer = (meetingId, userId) ->
|
||||
|
@ -43,18 +43,19 @@ repositories {
|
||||
|
||||
dependencies {
|
||||
//redis
|
||||
compile "redis.clients:jedis:2.7.2"
|
||||
compile 'org.apache.commons:commons-pool2:2.3'
|
||||
compile 'redis.clients:jedis:2.7.2'
|
||||
compile 'org.apache.commons:commons-pool2:2.3'
|
||||
|
||||
compile 'commons-lang:commons-lang:2.5'
|
||||
compile 'commons-io:commons-io:2.4'
|
||||
compile 'com.google.code.gson:gson:1.7.1'
|
||||
compile 'commons-httpclient:commons-httpclient:3.1'
|
||||
|
||||
compile 'com.zaxxer:nuprocess:1.0.4'
|
||||
|
||||
compile 'org.bigbluebutton:bbb-common-message:0.0.13'
|
||||
|
||||
// Logging
|
||||
// Commenting out as it results in build failure (ralam - may 11, 2014)
|
||||
// Commenting out as it results in build failure (ralam - may 11, 2014)
|
||||
//compile 'ch.qos.logback:logback-core:1.0.9@jar'
|
||||
//compile 'ch.qos.logback:logback-classic:1.0.9@jar'
|
||||
//compile 'org.slf4j:log4j-over-slf4j:1.7.2@jar'
|
||||
@ -64,7 +65,7 @@ dependencies {
|
||||
|
||||
//junit
|
||||
compile 'junit:junit:4.8.2'
|
||||
|
||||
|
||||
// Logging
|
||||
/**** UNCOMMENT WHEN you want to run gradle test
|
||||
compile 'ch.qos.logback:logback-core:1.0.13@jar'
|
||||
|
@ -68,6 +68,16 @@ 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
|
||||
|
||||
#----------------------------------------------------
|
||||
# Additional conversion of the presentation slides to SVG
|
||||
# to be used in the HTML5 client
|
||||
|
@ -62,6 +62,9 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
<bean id="pdf2SwfPageConverter" class="org.bigbluebutton.presentation.imp.Pdf2SwfPageConverter">
|
||||
<property name="swfToolsDir" value="${swfToolsDir}"/>
|
||||
<property name="fontsDir" value="${fontsDir}"/>
|
||||
<property name="placementsThreshold" value="${placementsThreshold}"/>
|
||||
<property name="defineTextThreshold" value="${defineTextThreshold}"/>
|
||||
<property name="imageTagThreshold" value="${imageTagThreshold}"/>
|
||||
</bean>
|
||||
|
||||
<bean id="imageConvSvc" class="org.bigbluebutton.presentation.imp.PdfPageToImageConversionService">
|
||||
@ -79,9 +82,9 @@ with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
<property name="imageMagickDir" value="${imageMagickDir}"/>
|
||||
</bean>
|
||||
|
||||
<bean id="svgImageCreator" class="org.bigbluebutton.presentation.imp.SvgImageCreatorImp">
|
||||
<property name="imageMagickDir" value="${imageMagickDir}"/>
|
||||
</bean>
|
||||
<bean id="svgImageCreator" class="org.bigbluebutton.presentation.imp.SvgImageCreatorImp">
|
||||
<property name="imageMagickDir" value="${imageMagickDir}"/>
|
||||
</bean>
|
||||
|
||||
<bean id="generatedSlidesInfoHelper" class="org.bigbluebutton.presentation.GeneratedSlidesInfoHelperImp"/>
|
||||
|
||||
|
@ -0,0 +1,32 @@
|
||||
/**
|
||||
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
|
||||
*
|
||||
* Copyright (c) 2015 BigBlueButton Inc. and by respective authors (see below).
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free Software
|
||||
* Foundation; either version 3.0 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License along
|
||||
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.bigbluebutton.presentation;
|
||||
|
||||
import java.io.File;
|
||||
|
||||
public interface PageAnalyser {
|
||||
/**
|
||||
*
|
||||
* @param output
|
||||
* a {@link File} to analyse
|
||||
* @return true if the file has been parsed without any error
|
||||
*/
|
||||
public boolean analyse(File output);
|
||||
}
|
@ -0,0 +1,90 @@
|
||||
/**
|
||||
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
|
||||
*
|
||||
* Copyright (c) 2015 BigBlueButton Inc. and by respective authors (see below).
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free Software
|
||||
* Foundation; either version 3.0 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License along
|
||||
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package org.bigbluebutton.presentation.handlers;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.CharBuffer;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.zaxxer.nuprocess.NuAbstractProcessHandler;
|
||||
import com.zaxxer.nuprocess.NuProcess;
|
||||
|
||||
public abstract class AbstractPageConverterHandler extends
|
||||
NuAbstractProcessHandler {
|
||||
|
||||
private static Logger log = LoggerFactory
|
||||
.getLogger(AbstractPageConverterHandler.class);
|
||||
|
||||
protected NuProcess nuProcess;
|
||||
protected int exitCode;
|
||||
final protected StringBuilder stdoutBuilder = new StringBuilder();
|
||||
final protected StringBuilder stderrBuilder = new StringBuilder();
|
||||
|
||||
@Override
|
||||
public void onPreStart(NuProcess nuProcess) {
|
||||
this.nuProcess = nuProcess;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStart(NuProcess nuProcess) {
|
||||
super.onStart(nuProcess);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStdout(ByteBuffer buffer, boolean closed) {
|
||||
if (buffer != null) {
|
||||
CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer);
|
||||
stdoutBuilder.append(charBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onStderr(ByteBuffer buffer, boolean closed) {
|
||||
if (buffer != null) {
|
||||
CharBuffer charBuffer = StandardCharsets.UTF_8.decode(buffer);
|
||||
stderrBuilder.append(charBuffer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onExit(int statusCode) {
|
||||
exitCode = statusCode;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return true if the exit code of the process is different from 0
|
||||
*/
|
||||
public Boolean exitedWithError() {
|
||||
return exitCode != 0;
|
||||
}
|
||||
|
||||
protected Boolean stdoutContains(String value) {
|
||||
return stdoutBuilder.indexOf(value) > -1;
|
||||
}
|
||||
|
||||
protected Boolean stderrContains(String value) {
|
||||
return stderrBuilder.indexOf(value) > -1;
|
||||
}
|
||||
|
||||
public abstract Boolean isConversionSuccessfull();
|
||||
}
|
@ -0,0 +1,109 @@
|
||||
/**
|
||||
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
|
||||
*
|
||||
* Copyright (c) 2015 BigBlueButton Inc. and by respective authors (see below).
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free Software
|
||||
* Foundation; either version 3.0 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License along
|
||||
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
package org.bigbluebutton.presentation.handlers;
|
||||
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
/**
|
||||
*
|
||||
* The default command output the anlayse looks like the following: </br> 20
|
||||
* DEBUG Using</br> 60 VERBOSE Updating font</br> 80 VERBOSE Drawing
|
||||
*
|
||||
*/
|
||||
public class Pdf2SwfPageConverterHandler extends AbstractPageConverterHandler {
|
||||
|
||||
private static Logger log = LoggerFactory
|
||||
.getLogger(Pdf2SwfPageConverterHandler.class);
|
||||
|
||||
private static String PLACEMENT_OUTPUT = "DEBUG Using";
|
||||
private static String TEXT_TAG_OUTPUT = "VERBOSE Updating";
|
||||
private static String IMAGE_TAG_OUTPUT = "VERBOSE Drawing";
|
||||
private static String PLACEMENT_PATTERN = "\\d+\\s" + PLACEMENT_OUTPUT;
|
||||
private static String TEXT_TAG_PATTERN = "\\d+\\s" + TEXT_TAG_OUTPUT;
|
||||
private static String IMAGE_TAG_PATTERN = "\\d+\\s" + IMAGE_TAG_OUTPUT;
|
||||
|
||||
@Override
|
||||
public Boolean isConversionSuccessfull() {
|
||||
return !exitedWithError();
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return The number of PlaceObject2 tags in the generated SWF
|
||||
*/
|
||||
public long numberOfPlacements() {
|
||||
if (stdoutContains(PLACEMENT_OUTPUT)) {
|
||||
try {
|
||||
String out = stdoutBuilder.toString();
|
||||
Pattern r = Pattern.compile(PLACEMENT_PATTERN);
|
||||
Matcher m = r.matcher(out);
|
||||
m.find();
|
||||
return Integer
|
||||
.parseInt(m.group(0).replace(PLACEMENT_OUTPUT, "").trim());
|
||||
} catch (Exception e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return The number of text tags in the generated SWF.
|
||||
*/
|
||||
public long numberOfTextTags() {
|
||||
if (stdoutContains(TEXT_TAG_OUTPUT)) {
|
||||
try {
|
||||
String out = stdoutBuilder.toString();
|
||||
Pattern r = Pattern.compile(TEXT_TAG_PATTERN);
|
||||
Matcher m = r.matcher(out);
|
||||
m.find();
|
||||
return Integer.parseInt(m.group(0).replace(TEXT_TAG_OUTPUT, "").trim());
|
||||
} catch (Exception e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @return The number of image tags in the generated SWF.
|
||||
*/
|
||||
public long numberOfImageTags() {
|
||||
if (stdoutContains(IMAGE_TAG_OUTPUT)) {
|
||||
try {
|
||||
String out = stdoutBuilder.toString();
|
||||
Pattern r = Pattern.compile(IMAGE_TAG_PATTERN);
|
||||
Matcher m = r.matcher(out);
|
||||
m.find();
|
||||
return Integer
|
||||
.parseInt(m.group(0).replace(IMAGE_TAG_OUTPUT, "").trim());
|
||||
} catch (Exception e) {
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
}
|
@ -1,65 +1,132 @@
|
||||
/**
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
* 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/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.bigbluebutton.presentation.imp;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.Arrays;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.bigbluebutton.presentation.PageConverter;
|
||||
import org.bigbluebutton.presentation.handlers.Pdf2SwfPageConverterHandler;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.zaxxer.nuprocess.NuProcess;
|
||||
import com.zaxxer.nuprocess.NuProcessBuilder;
|
||||
|
||||
public class Pdf2SwfPageConverter implements PageConverter {
|
||||
private static Logger log = LoggerFactory.getLogger(Pdf2SwfPageConverter.class);
|
||||
|
||||
private String SWFTOOLS_DIR;
|
||||
private String fontsDir;
|
||||
|
||||
public boolean convert(File presentation, File output, int page) {
|
||||
String source = presentation.getAbsolutePath();
|
||||
String dest = output.getAbsolutePath();
|
||||
String AVM2SWF = "-T9";
|
||||
|
||||
String COMMAND = SWFTOOLS_DIR + File.separator + "pdf2swf " + AVM2SWF + " -F " + fontsDir + " -p " + page + " " + source + " -o " + dest;
|
||||
private static Logger log = LoggerFactory
|
||||
.getLogger(Pdf2SwfPageConverter.class);
|
||||
|
||||
boolean done = new ExternalProcessExecutor().exec(COMMAND, 60000);
|
||||
|
||||
File destFile = new File(dest);
|
||||
if (done && destFile.exists()) {
|
||||
return true;
|
||||
} else {
|
||||
COMMAND = SWFTOOLS_DIR + File.separator + "pdf2swf " + AVM2SWF + " -s poly2bitmap -F " + fontsDir + " -p " + page + " " + source + " -o " + dest;
|
||||
done = new ExternalProcessExecutor().exec(COMMAND, 60000);
|
||||
if (done && destFile.exists()){
|
||||
return true;
|
||||
} else {
|
||||
log.warn("Failed to convert: " + dest + " does not exist.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
private String SWFTOOLS_DIR;
|
||||
private String fontsDir;
|
||||
private long placementsThreshold;
|
||||
private long defineTextThreshold;
|
||||
private long imageTagThreshold;
|
||||
|
||||
public boolean convert(File presentation, File output, int page) {
|
||||
String source = presentation.getAbsolutePath();
|
||||
String dest = output.getAbsolutePath();
|
||||
String AVM2SWF = "-T9";
|
||||
|
||||
// Building the command line wrapped in shell to be able to use shell
|
||||
// feature like the pipe
|
||||
NuProcessBuilder pb = new NuProcessBuilder(
|
||||
Arrays.asList(
|
||||
"/bin/sh",
|
||||
"-c",
|
||||
SWFTOOLS_DIR
|
||||
+ File.separator
|
||||
+ "pdf2swf"
|
||||
+ " -vv "
|
||||
+ AVM2SWF
|
||||
+ " -F "
|
||||
+ fontsDir
|
||||
+ " -p "
|
||||
+ String.valueOf(page)
|
||||
+ " "
|
||||
+ source
|
||||
+ " -o "
|
||||
+ dest
|
||||
+ " | egrep 'shape id|Updating font|Drawing' | sed 's/ / /g' | cut -d' ' -f 1-3 | sort | uniq -cw 15"));
|
||||
|
||||
Pdf2SwfPageConverterHandler pHandler = new Pdf2SwfPageConverterHandler();
|
||||
pb.setProcessListener(pHandler);
|
||||
NuProcess process = pb.start();
|
||||
try {
|
||||
process.waitFor(60, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
log.error(e.getMessage());
|
||||
}
|
||||
|
||||
File destFile = new File(dest);
|
||||
if (pHandler.isConversionSuccessfull() && destFile.exists()
|
||||
&& pHandler.numberOfPlacements() < placementsThreshold
|
||||
&& pHandler.numberOfTextTags() < defineTextThreshold
|
||||
&& pHandler.numberOfImageTags() < imageTagThreshold) {
|
||||
return true;
|
||||
} else {
|
||||
log.debug(
|
||||
"Previous conversion generated {} PlaceObject tags, {} DefineText tags and {} Images. Falling back to 'poly2bitmap' option for pdf2swf.",
|
||||
pHandler.numberOfPlacements(), pHandler.numberOfTextTags(),
|
||||
pHandler.numberOfImageTags());
|
||||
NuProcessBuilder pbBmp = new NuProcessBuilder(Arrays.asList(SWFTOOLS_DIR
|
||||
+ File.separator + "pdf2swf", AVM2SWF, "-s", "poly2bitmap", "-F",
|
||||
fontsDir, "-p", String.valueOf(page), source, "-o", dest));
|
||||
Pdf2SwfPageConverterHandler pBmpHandler = new Pdf2SwfPageConverterHandler();
|
||||
pbBmp.setProcessListener(pBmpHandler);
|
||||
NuProcess processBmp = pbBmp.start();
|
||||
try {
|
||||
processBmp.waitFor(60, TimeUnit.SECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
log.error(e.getMessage());
|
||||
}
|
||||
boolean doneBmp = pBmpHandler.isConversionSuccessfull();
|
||||
|
||||
if (doneBmp && destFile.exists()) {
|
||||
return true;
|
||||
} else {
|
||||
log.warn("Failed to convert: " + dest + " does not exist.");
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public void setSwfToolsDir(String dir) {
|
||||
SWFTOOLS_DIR = dir;
|
||||
}
|
||||
|
||||
public void setFontsDir(String dir) {
|
||||
fontsDir = dir;
|
||||
}
|
||||
|
||||
public void setPlacementsThreshold(long threshold) {
|
||||
placementsThreshold = threshold;
|
||||
}
|
||||
|
||||
public void setDefineTextThreshold(long threshold) {
|
||||
defineTextThreshold = threshold;
|
||||
}
|
||||
|
||||
public void setImageTagThreshold(long threshold) {
|
||||
imageTagThreshold = threshold;
|
||||
}
|
||||
|
||||
public void setSwfToolsDir(String dir) {
|
||||
SWFTOOLS_DIR = dir;
|
||||
}
|
||||
|
||||
public void setFontsDir(String dir) {
|
||||
fontsDir = dir;
|
||||
}
|
||||
}
|
||||
|
@ -33,13 +33,13 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.bigbluebutton.presentation.ConversionMessageConstants;
|
||||
import org.bigbluebutton.presentation.ConversionUpdateMessage;
|
||||
import org.bigbluebutton.presentation.ConversionUpdateMessage.MessageBuilder;
|
||||
import org.bigbluebutton.presentation.PageConverter;
|
||||
import org.bigbluebutton.presentation.PdfToSwfSlide;
|
||||
import org.bigbluebutton.presentation.SvgImageCreator;
|
||||
import org.bigbluebutton.presentation.TextFileCreator;
|
||||
import org.bigbluebutton.presentation.ThumbnailCreator;
|
||||
import org.bigbluebutton.presentation.UploadedPresentation;
|
||||
import org.bigbluebutton.presentation.ConversionUpdateMessage.MessageBuilder;
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
@ -152,12 +152,12 @@ public class PdfToSwfSlidesGenerationService {
|
||||
slidesCompleted++;
|
||||
notifier.sendConversionUpdateMessage(slidesCompleted, pres);
|
||||
} else {
|
||||
log.warn("Timedout waiting for page to finish conversion. meetingId=" + pres.getMeetingId() + " presId=" + pres.getId() + " presName=" + pres.getName() );
|
||||
log.warn("Timedout waiting for page to finish conversion. meetingId=" + pres.getMeetingId() + " presId=" + pres.getId() + " presName=[" + pres.getName() + "]");
|
||||
}
|
||||
} catch (InterruptedException e) {
|
||||
log.error("InterruptedException while creating slide. meetingId=" + pres.getMeetingId() + " presId=" + pres.getId() + " name=[" + pres.getName());
|
||||
log.error("InterruptedException while creating slide. meetingId=" + pres.getMeetingId() + " presId=" + pres.getId() + " presName=[" + pres.getName() + "]");
|
||||
} catch (ExecutionException e) {
|
||||
log.error("ExecutionException while creating slide. meetingId=" + pres.getMeetingId() + " presId=" + pres.getId() + " name=[" + pres.getName());
|
||||
log.error("ExecutionException while creating slide. meetingId=" + pres.getMeetingId() + " presId=" + pres.getId() + " presName=[" + pres.getName() + "]");
|
||||
}
|
||||
}
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user