Merged upstream develop branch

This commit is contained in:
Michael Zinsmeister 2021-05-01 16:59:20 +02:00
parent 2dfb9d6fa7
commit 5ca831ff86
544 changed files with 9425 additions and 20743 deletions

View File

@ -2,7 +2,7 @@
name: Server Core Issue
about: Template for creating an issue with bbb-web, akka-apps, or other server component
title: ''
labels: ''
labels: 'module: core'
assignees: ''
---

View File

@ -2,7 +2,7 @@
name: Feature request
about: Suggest an idea for this project
title: ''
labels: Enhancement
labels: 'type: enhancement'
assignees: ''
---

View File

@ -2,7 +2,7 @@
name: HTML5 Issue
about: Template for creating HTML5 Issue (frontend which you see during a session, not Greenlight).
title: ''
labels: HTML5 Client
labels: 'module: client'
assignees: ''
---

View File

@ -2,7 +2,7 @@
name: Installation issue (not a support question)
about: Template for issues encountered during installation
title: ''
labels: ''
labels: 'deploy: installation'
assignees: ''
---

View File

@ -2,7 +2,7 @@
name: Recording Issue
about: Template for creating a recording issue
title: ''
labels: Recording
labels: 'module: recording'
assignees: ''
---

View File

@ -13,7 +13,7 @@ jobs:
- name: Check for dirty pull requests
uses: eps1lon/actions-label-merge-conflict@releases/2.x
with:
dirtyLabel: has-conflicts
dirtyLabel: "status: conflict"
repoToken: "${{ secrets.GITHUB_TOKEN }}"
commentOnDirty: |
This pull request has conflicts ☹

View File

@ -2,5 +2,4 @@
rm -rf src/main/resources
cp -R src/universal/conf src/main/resources
sbt run
exec sbt run

View File

@ -3,4 +3,4 @@
sbt clean stage
sudo service bbb-apps-akka stop
cd target/universal/stage
./bin/bbb-apps-akka
exec ./bin/bbb-apps-akka

View File

@ -57,7 +57,7 @@ object Boot extends App with SystemConfiguration {
val fromAkkaAppsMsgSenderActorRef = system.actorOf(FromAkkaAppsMsgSenderActor.props(msgSender))
val analyticsActorRef = system.actorOf(AnalyticsActor.props())
val analyticsActorRef = system.actorOf(AnalyticsActor.props(analyticsIncludeChat))
outBus2.subscribe(fromAkkaAppsMsgSenderActorRef, outBbbMsgMsgChannel)
outBus2.subscribe(redisRecorderActor, recordServiceMessageChannel)

View File

@ -74,6 +74,8 @@ trait SystemConfiguration {
lazy val toAkkaTranscodeJsonChannel = Try(config.getString("eventBus.toAkkaTranscodeJsonChannel")).getOrElse("to-akka-transcode-json-channel")
lazy val fromAkkaTranscodeJsonChannel = Try(config.getString("eventBus.fromAkkaTranscodeJsonChannel")).getOrElse("from-akka-transcode-json-channel")
lazy val analyticsIncludeChat = Try(config.getBoolean("analytics.includeChat")).getOrElse(true)
// Grab the "interface" parameter from the http config
val httpHost = config.getString("http.interface")
// Grab the "port" parameter from the http config

View File

@ -26,6 +26,13 @@ case class MonitorNumberOfUsersInternalMsg(meetingID: String) extends InMessage
*/
case class SendTimeRemainingAuditInternalMsg(meetingId: String) extends InMessage
/**
* Parent message sent to breakout rooms to trigger updating clients of meeting time remaining.
* @param meetingId
* @param timeLeftInSec
*/
case class SendBreakoutTimeRemainingInternalMsg(meetingId: String, timeLeftInSec: Long) extends InMessage
case class SendRecordingTimerInternalMsg(meetingId: String) extends InMessage
case class ExtendMeetingDuration(meetingId: String, userId: String) extends InMessage
@ -66,7 +73,7 @@ case class BreakoutRoomUsersUpdateInternalMsg(parentId: String, breakoutId: Stri
* @param parentId
* @param breakoutId
*/
case class EndBreakoutRoomInternalMsg(parentId: String, breakoutId: String) extends InMessage
case class EndBreakoutRoomInternalMsg(parentId: String, breakoutId: String, reason: String) extends InMessage
// DeskShare
case class DeskShareStartedRequest(conferenceName: String, callerId: String, callerIdName: String) extends InMessage

View File

@ -3,7 +3,7 @@ package org.bigbluebutton.core.apps.breakout
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.api.EndBreakoutRoomInternalMsg
import org.bigbluebutton.core.bus.BigBlueButtonEvent
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.domain.{ MeetingEndReason, MeetingState2x }
import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter }
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
@ -23,7 +23,7 @@ trait EndAllBreakoutRoomsMsgHdlr extends RightsManagementTrait {
model <- state.breakout
} yield {
model.rooms.values.foreach { room =>
eventBus.publish(BigBlueButtonEvent(room.id, EndBreakoutRoomInternalMsg(props.breakoutProps.parentId, room.id)))
eventBus.publish(BigBlueButtonEvent(room.id, EndBreakoutRoomInternalMsg(props.breakoutProps.parentId, room.id, MeetingEndReason.BREAKOUT_ENDED_BY_MOD)))
}
}

View File

@ -2,7 +2,6 @@ package org.bigbluebutton.core.apps.breakout
import org.bigbluebutton.core.api.EndBreakoutRoomInternalMsg
import org.bigbluebutton.core.bus.{ InternalEventBus }
import org.bigbluebutton.core.domain.MeetingEndReason
import org.bigbluebutton.core.running.{ BaseMeetingActor, HandlerHelpers, LiveMeeting, OutMsgRouter }
trait EndBreakoutRoomInternalMsgHdlr extends HandlerHelpers {
@ -14,6 +13,6 @@ trait EndBreakoutRoomInternalMsgHdlr extends HandlerHelpers {
def handleEndBreakoutRoomInternalMsg(msg: EndBreakoutRoomInternalMsg): Unit = {
log.info("Breakout room {} ended by parent meeting {}.", msg.breakoutId, msg.parentId)
sendEndMeetingDueToExpiry(MeetingEndReason.ENDED_BY_PARENT, eventBus, outGW, liveMeeting, "system")
sendEndMeetingDueToExpiry(msg.reason, eventBus, outGW, liveMeeting, "system")
}
}

View File

@ -0,0 +1,15 @@
package org.bigbluebutton.core.apps.breakout
import org.bigbluebutton.core.api.SendBreakoutTimeRemainingInternalMsg
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
import org.bigbluebutton.core2.message.senders.MsgBuilder
trait SendBreakoutTimeRemainingInternalMsgHdlr {
val liveMeeting: LiveMeeting
val outGW: OutMsgRouter
def handleSendBreakoutTimeRemainingInternalMsg(msg: SendBreakoutTimeRemainingInternalMsg): Unit = {
val event = MsgBuilder.buildMeetingTimeRemainingUpdateEvtMsg(liveMeeting.props.meetingProp.intId, msg.timeLeftInSec.toInt)
outGW.send(event)
}
}

View File

@ -16,10 +16,7 @@ trait SendGroupChatMessageMsgHdlr {
def handle(msg: SendGroupChatMessageMsg, state: MeetingState2x,
liveMeeting: LiveMeeting, bus: MessageBus): MeetingState2x = {
log.debug("RECEIVED PUBLIC CHAT MESSAGE")
log.debug("NUM GROUP CHATS = " + state.groupChats.findAllPublicChats().length)
var chatLocked: Boolean = false;
var chatLocked: Boolean = false
for {
user <- Users2x.findWithIntId(liveMeeting.users2x, msg.header.userId)
@ -72,20 +69,6 @@ trait SendGroupChatMessageMsgHdlr {
BbbCommonEnvCoreMsg(envelope, event)
}
GroupChatApp.findGroupChatUser(msg.header.userId, liveMeeting.users2x) match {
case Some(s) => log.debug("Found sender")
case None => log.debug("NOT FOUND sender")
}
state.groupChats.find(msg.body.chatId) match {
case Some(c) => log.debug("FOUND CHAT ID " + c.id)
case None => log.debug("NOT FOUND CHAT ID " + msg.body.chatId)
}
state.groupChats.chats.values.toVector foreach { ch =>
log.debug("CHAT = " + ch.id)
}
val newState = for {
sender <- GroupChatApp.findGroupChatUser(msg.header.userId, liveMeeting.users2x)
chat <- state.groupChats.find(msg.body.chatId)

View File

@ -11,7 +11,6 @@ trait RespondToPollReqMsgHdlr {
this: PollApp2x =>
def handle(msg: RespondToPollReqMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
log.debug("Received RespondToPollReqMsg {}", RespondToPollReqMsg)
def broadcastPollUpdatedEvent(msg: RespondToPollReqMsg, pollId: String, poll: SimplePollResultOutVO): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, msg.header.userId)

View File

@ -11,7 +11,6 @@ trait RespondToTypedPollReqMsgHdlr {
this: PollApp2x =>
def handle(msg: RespondToTypedPollReqMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
log.debug("Received RespondToPollReqMsg {}", RespondToTypedPollReqMsg)
def broadcastPollUpdatedEvent(msg: RespondToTypedPollReqMsg, pollId: String, poll: SimplePollResultOutVO): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, msg.header.userId)

View File

@ -17,6 +17,7 @@ class PresentationPodHdlrs(implicit val context: ActorContext)
with PresentationConversionUpdatePubMsgHdlr
with PresentationPageGeneratedPubMsgHdlr
with PresentationPageCountErrorPubMsgHdlr
with PresentationUploadedFileTooLargeErrorPubMsgHdlr
with PresentationUploadTokenReqMsgHdlr
with ResizeAndMovePagePubMsgHdlr
with SyncGetPresentationPodsMsgHdlr

View File

@ -43,7 +43,7 @@ trait PresentationUploadTokenReqMsgHdlr extends RightsManagementTrait {
val envelope = BbbCoreEnvelope(PresentationUploadTokenSysPubMsg.NAME, routing)
val header = BbbClientMsgHeader(PresentationUploadTokenSysPubMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
val body = PresentationUploadTokenSysPubMsgBody(msg.body.podId, token, msg.body.filename)
val body = PresentationUploadTokenSysPubMsgBody(msg.body.podId, token, msg.body.filename, liveMeeting.props.meetingProp.intId)
val event = PresentationUploadTokenSysPubMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)

View File

@ -0,0 +1,36 @@
package org.bigbluebutton.core.apps.presentationpod
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.running.LiveMeeting
trait PresentationUploadedFileTooLargeErrorPubMsgHdlr {
this: PresentationPodHdlrs =>
def handle(
msg: PresentationUploadedFileTooLargeErrorSysPubMsg, state: MeetingState2x,
liveMeeting: LiveMeeting, bus: MessageBus
): MeetingState2x = {
def broadcastEvent(msg: PresentationUploadedFileTooLargeErrorSysPubMsg): Unit = {
val routing = Routing.addMsgToClientRouting(
MessageTypes.BROADCAST_TO_MEETING,
liveMeeting.props.meetingProp.intId, msg.header.userId
)
val envelope = BbbCoreEnvelope(PresentationUploadedFileTooLargeErrorEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(
PresentationUploadedFileTooLargeErrorEvtMsg.NAME,
liveMeeting.props.meetingProp.intId, msg.header.userId
)
val body = PresentationUploadedFileTooLargeErrorEvtMsgBody(msg.body.podId, msg.body.messageKey, msg.body.code, msg.body.presentationName, msg.body.presentationToken, msg.body.fileSize, msg.body.maxFileSize)
val event = PresentationUploadedFileTooLargeErrorEvtMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(msgEvent)
}
broadcastEvent(msg)
state
}
}

View File

@ -24,7 +24,7 @@ trait LogoutAndEndMeetingCmdMsgHdlr extends RightsManagementTrait {
u <- Users2x.findWithIntId(liveMeeting.users2x, msg.body.userId)
} yield {
if (u.role == Roles.MODERATOR_ROLE) {
endAllBreakoutRooms(eventBus, liveMeeting, state)
endAllBreakoutRooms(eventBus, liveMeeting, state, MeetingEndReason.ENDED_AFTER_USER_LOGGED_OUT)
log.info("Meeting {} ended by user [{}, {}} when logging out.", liveMeeting.props.meetingProp.intId,
u.intId, u.name)
sendEndMeetingDueToExpiry(MeetingEndReason.ENDED_AFTER_USER_LOGGED_OUT, eventBus, outGW, liveMeeting, u.intId)

View File

@ -15,12 +15,12 @@ trait SelectRandomViewerReqMsgHdlr extends RightsManagementTrait {
def handleSelectRandomViewerReqMsg(msg: SelectRandomViewerReqMsg): Unit = {
log.debug("Received SelectRandomViewerReqMsg {}", SelectRandomViewerReqMsg)
def broadcastEvent(msg: SelectRandomViewerReqMsg, selectedUser: UserState): Unit = {
def broadcastEvent(msg: SelectRandomViewerReqMsg, users: Vector[String], choice: Integer): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, msg.header.userId)
val envelope = BbbCoreEnvelope(SelectRandomViewerRespMsg.NAME, routing)
val header = BbbClientMsgHeader(SelectRandomViewerRespMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
val body = SelectRandomViewerRespMsgBody(msg.header.userId, selectedUser.intId)
val body = SelectRandomViewerRespMsgBody(msg.header.userId, users, choice)
val event = SelectRandomViewerRespMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
outGW.send(msgEvent)
@ -34,9 +34,8 @@ trait SelectRandomViewerReqMsgHdlr extends RightsManagementTrait {
val users = Users2x.findNotPresentersNorModerators(liveMeeting.users2x)
val randNum = new scala.util.Random
if (users.size > 0) {
broadcastEvent(msg, users(randNum.nextInt(users.size)))
}
val userIds = users.map { case (v) => v.intId }
broadcastEvent(msg, userIds, if (users.size == 0) -1 else randNum.nextInt(users.size))
}
}
}

View File

@ -40,7 +40,9 @@ trait UserConnectedToGlobalAudioMsgHdlr {
talking = false,
listenOnly = true,
"kms",
System.currentTimeMillis()
System.currentTimeMillis(),
floor = false,
lastFloorTime = "0",
)
VoiceUsers.add(liveMeeting.voiceUsers, vu)

View File

@ -0,0 +1,61 @@
package org.bigbluebutton.core.apps.voice
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.models.{ VoiceUserState, VoiceUsers }
import org.bigbluebutton.core.running.{ BaseMeetingActor, LiveMeeting, OutMsgRouter }
trait AudioFloorChangedVoiceConfEvtMsgHdlr {
this: BaseMeetingActor =>
val liveMeeting: LiveMeeting
val outGW: OutMsgRouter
def handleAudioFloorChangedVoiceConfEvtMsg(msg: AudioFloorChangedVoiceConfEvtMsg): Unit = {
def broadcastEvent(vu: VoiceUserState): Unit = {
val routing = Routing.addMsgToClientRouting(
MessageTypes.BROADCAST_TO_MEETING,
liveMeeting.props.meetingProp.intId,
vu.intId
)
val envelope = BbbCoreEnvelope(AudioFloorChangedEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(
AudioFloorChangedEvtMsg.NAME,
liveMeeting.props.meetingProp.intId, vu.intId
)
val body = AudioFloorChangedEvtMsgBody(
voiceConf = msg.header.voiceConf,
intId = vu.intId,
voiceUserId = vu.voiceUserId,
floor = vu.floor,
lastFloorTime = msg.body.floorTimestamp
)
val event = AudioFloorChangedEvtMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
outGW.send(msgEvent)
}
for {
oldFloorUser <- VoiceUsers.releasedFloor(
liveMeeting.voiceUsers,
msg.body.oldVoiceUserId,
floor = false
)
} yield {
broadcastEvent(oldFloorUser)
}
for {
newFloorUser <- VoiceUsers.becameFloor(
liveMeeting.voiceUsers,
msg.body.voiceUserId,
true,
msg.body.floorTimestamp
)
} yield {
broadcastEvent(newFloorUser)
}
}
}

View File

@ -268,7 +268,9 @@ object VoiceApp extends SystemConfiguration {
talking,
listenOnly = isListenOnly,
callingInto,
System.currentTimeMillis()
System.currentTimeMillis(),
floor = false,
lastFloorTime = "0"
)
VoiceUsers.add(liveMeeting.voiceUsers, voiceUserState)

View File

@ -18,6 +18,7 @@ trait VoiceApp2x extends UserJoinedVoiceConfEvtMsgHdlr
with RecordingStartedVoiceConfEvtMsgHdlr
with VoiceConfRunningEvtMsgHdlr
with SyncGetVoiceUsersMsgHdlr
with AudioFloorChangedVoiceConfEvtMsgHdlr
with VoiceConfCallStateEvtMsgHdlr
with UserStatusVoiceConfEvtMsgHdlr {

View File

@ -29,6 +29,7 @@ object MeetingEndReason {
val ENDED_WHEN_LAST_USER_LEFT = "ENDED_WHEN_LAST_USER_LEFT"
val ENDED_AFTER_USER_LOGGED_OUT = "ENDED_AFTER_USER_LOGGED_OUT"
val ENDED_AFTER_EXCEEDING_DURATION = "ENDED_AFTER_EXCEEDING_DURATION"
val ENDED_BY_PARENT = "ENDED_BY_PARENT"
val BREAKOUT_ENDED_EXCEEDING_DURATION = "BREAKOUT_ENDED_EXCEEDING_DURATION"
val BREAKOUT_ENDED_BY_MOD = "BREAKOUT_ENDED_BY_MOD"
val ENDED_DUE_TO_NO_AUTHED_USER = "ENDED_DUE_TO_NO_AUTHED_USER"
}

View File

@ -259,9 +259,41 @@ object Polls {
shape += "status" -> WhiteboardKeyUtil.DRAW_END_STATUS
val answers = new ArrayBuffer[SimpleVoteOutVO]
result.answers.foreach(ans => {
answers += SimpleVoteOutVO(ans.id, ans.key, ans.numVotes)
})
def sortByNumVotes(s1: SimpleVoteOutVO, s2: SimpleVoteOutVO) = {
s1.numVotes > s2.numVotes
}
val sorted_answers = result.answers.sortWith(sortByNumVotes)
// Limit the number of answers displayed to minimize
// squishing the display.
if (sorted_answers.length < 7) {
sorted_answers.foreach(ans => {
answers += SimpleVoteOutVO(ans.id, ans.key, ans.numVotes)
})
} else {
var highestId = 0
for (i <- 0 until 7) {
val ans = sorted_answers(i)
answers += SimpleVoteOutVO(ans.id, ans.key, ans.numVotes)
if (ans.id > highestId) {
highestId = ans.id
}
}
var otherNumVotes = 0
for (i <- 7 until sorted_answers.length) {
val ans = sorted_answers(i)
otherNumVotes += ans.numVotes
if (ans.id > highestId) {
highestId = ans.id
}
}
answers += SimpleVoteOutVO(highestId + 1, "...", otherNumVotes)
}
shape += "result" -> answers

View File

@ -67,6 +67,29 @@ object VoiceUsers {
}
}
def becameFloor(users: VoiceUsers, voiceUserId: String, floor: Boolean, timestamp: String): Option[VoiceUserState] = {
for {
u <- findWithVoiceUserId(users, voiceUserId)
} yield {
val vu = u.modify(_.floor).setTo(floor)
.modify(_.lastFloorTime).setTo(timestamp)
.modify(_.lastStatusUpdateOn).setTo(System.currentTimeMillis())
users.save(vu)
vu
}
}
def releasedFloor(users: VoiceUsers, voiceUserId: String, floor: Boolean): Option[VoiceUserState] = {
for {
u <- findWithVoiceUserId(users, voiceUserId)
} yield {
val vu = u.modify(_.floor).setTo(floor)
.modify(_.lastStatusUpdateOn).setTo(System.currentTimeMillis())
users.save(vu)
vu
}
}
def setLastStatusUpdate(users: VoiceUsers, user: VoiceUserState): VoiceUserState = {
val vu = user.copy(lastStatusUpdateOn = System.currentTimeMillis())
users.save(vu)
@ -130,16 +153,18 @@ case class VoiceUser2x(
voiceUserId: String
)
case class VoiceUserVO2x(
intId: String,
voiceUserId: String,
callerName: String,
callerNum: String,
joined: Boolean,
locked: Boolean,
muted: Boolean,
talking: Boolean,
callingWith: String,
listenOnly: Boolean
intId: String,
voiceUserId: String,
callerName: String,
callerNum: String,
joined: Boolean,
locked: Boolean,
muted: Boolean,
talking: Boolean,
callingWith: String,
listenOnly: Boolean,
floor: Boolean,
lastFloorTime: String
)
case class VoiceUserState(
@ -152,5 +177,7 @@ case class VoiceUserState(
talking: Boolean,
listenOnly: Boolean,
calledInto: String,
lastStatusUpdateOn: Long
lastStatusUpdateOn: Long,
floor: Boolean,
lastFloorTime: String
)

View File

@ -167,6 +167,8 @@ class ReceivedJsonMsgHandlerActor(
routeGenericMsg[MuteMeetingCmdMsg](envelope, jsonNode)
case IsMeetingMutedReqMsg.NAME =>
routeGenericMsg[IsMeetingMutedReqMsg](envelope, jsonNode)
case AudioFloorChangedVoiceConfEvtMsg.NAME =>
routeVoiceMsg[AudioFloorChangedVoiceConfEvtMsg](envelope, jsonNode)
case CheckRunningAndRecordingVoiceConfEvtMsg.NAME =>
routeVoiceMsg[CheckRunningAndRecordingVoiceConfEvtMsg](envelope, jsonNode)
case UserStatusVoiceConfEvtMsg.NAME =>
@ -234,6 +236,8 @@ class ReceivedJsonMsgHandlerActor(
routeGenericMsg[GetAllPresentationPodsReqMsg](envelope, jsonNode)
case PreuploadedPresentationsSysPubMsg.NAME =>
routeGenericMsg[PreuploadedPresentationsSysPubMsg](envelope, jsonNode)
case PresentationUploadedFileTooLargeErrorSysPubMsg.NAME =>
routeGenericMsg[PresentationUploadedFileTooLargeErrorSysPubMsg](envelope, jsonNode)
case PresentationConversionUpdateSysPubMsg.NAME =>
routeGenericMsg[PresentationConversionUpdateSysPubMsg](envelope, jsonNode)
case PresentationPageCountErrorSysPubMsg.NAME =>

View File

@ -205,12 +205,12 @@ trait HandlerHelpers extends SystemConfiguration {
outGW.send(event)
}
def endAllBreakoutRooms(eventBus: InternalEventBus, liveMeeting: LiveMeeting, state: MeetingState2x): MeetingState2x = {
def endAllBreakoutRooms(eventBus: InternalEventBus, liveMeeting: LiveMeeting, state: MeetingState2x, reason: String): MeetingState2x = {
for {
model <- state.breakout
} yield {
model.rooms.values.foreach { room =>
eventBus.publish(BigBlueButtonEvent(room.id, EndBreakoutRoomInternalMsg(liveMeeting.props.breakoutProps.parentId, room.id)))
eventBus.publish(BigBlueButtonEvent(room.id, EndBreakoutRoomInternalMsg(liveMeeting.props.breakoutProps.parentId, room.id, reason)))
}
}

View File

@ -86,6 +86,7 @@ class MeetingActor(
with DestroyMeetingSysCmdMsgHdlr
with SendTimeRemainingUpdateHdlr
with SendBreakoutTimeRemainingMsgHdlr
with SendBreakoutTimeRemainingInternalMsgHdlr
with ChangeLockSettingsInMeetingCmdMsgHdlr
with SyncGetMeetingInfoRespMsgHdlr
with ClientToServerLatencyTracerMsgHdlr
@ -238,16 +239,23 @@ class MeetingActor(
case msg: ExtendMeetingDuration => handleExtendMeetingDuration(msg)
case msg: SendTimeRemainingAuditInternalMsg =>
state = handleSendTimeRemainingUpdate(msg, state)
if (!liveMeeting.props.meetingProp.isBreakout) {
// Update users of meeting remaining time.
state = handleSendTimeRemainingUpdate(msg, state)
}
// Update breakout rooms of remaining time
state = handleSendBreakoutTimeRemainingMsg(msg, state)
case msg: BreakoutRoomCreatedInternalMsg => state = handleBreakoutRoomCreatedInternalMsg(msg, state)
case msg: SendBreakoutUsersAuditInternalMsg => handleSendBreakoutUsersUpdateInternalMsg(msg)
case msg: BreakoutRoomUsersUpdateInternalMsg => state = handleBreakoutRoomUsersUpdateInternalMsg(msg, state)
case msg: EndBreakoutRoomInternalMsg => handleEndBreakoutRoomInternalMsg(msg)
case msg: BreakoutRoomEndedInternalMsg => state = handleBreakoutRoomEndedInternalMsg(msg, state)
case msg: SendBreakoutTimeRemainingInternalMsg =>
handleSendBreakoutTimeRemainingInternalMsg(msg)
// Screenshare
case msg: DeskShareGetDeskShareInfoRequest => handleDeskShareGetDeskShareInfoRequest(msg)
case msg: DeskShareGetDeskShareInfoRequest => handleDeskShareGetDeskShareInfoRequest(msg)
case msg: SendRecordingTimerInternalMsg =>
state = usersApp.handleSendRecordingTimerInternalMsg(msg, state)
@ -381,9 +389,10 @@ class MeetingActor(
case m: UserTalkingInVoiceConfEvtMsg =>
updateVoiceUserLastActivity(m.body.voiceUserId)
handleUserTalkingInVoiceConfEvtMsg(m)
case m: VoiceConfCallStateEvtMsg => handleVoiceConfCallStateEvtMsg(m)
case m: VoiceConfCallStateEvtMsg => handleVoiceConfCallStateEvtMsg(m)
case m: RecordingStartedVoiceConfEvtMsg => handleRecordingStartedVoiceConfEvtMsg(m)
case m: RecordingStartedVoiceConfEvtMsg => handleRecordingStartedVoiceConfEvtMsg(m)
case m: AudioFloorChangedVoiceConfEvtMsg => handleAudioFloorChangedVoiceConfEvtMsg(m)
case m: MuteUserCmdMsg =>
usersApp.handleMuteUserCmdMsg(m)
updateUserLastActivity(m.body.mutedBy)
@ -411,49 +420,50 @@ class MeetingActor(
case m: ChangeLockSettingsInMeetingCmdMsg =>
handleSetLockSettings(m)
updateUserLastActivity(m.body.setBy)
case m: LockUserInMeetingCmdMsg => handleLockUserInMeetingCmdMsg(m)
case m: LockUsersInMeetingCmdMsg => handleLockUsersInMeetingCmdMsg(m)
case m: GetLockSettingsReqMsg => handleGetLockSettingsReqMsg(m)
case m: LockUserInMeetingCmdMsg => handleLockUserInMeetingCmdMsg(m)
case m: LockUsersInMeetingCmdMsg => handleLockUsersInMeetingCmdMsg(m)
case m: GetLockSettingsReqMsg => handleGetLockSettingsReqMsg(m)
// Presentation
case m: PreuploadedPresentationsSysPubMsg => presentationApp2x.handle(m, liveMeeting, msgBus)
case m: AssignPresenterReqMsg => state = handlePresenterChange(m, state)
case m: PreuploadedPresentationsSysPubMsg => presentationApp2x.handle(m, liveMeeting, msgBus)
case m: AssignPresenterReqMsg => state = handlePresenterChange(m, state)
// Presentation Pods
case m: CreateNewPresentationPodPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: RemovePresentationPodPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: GetAllPresentationPodsReqMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: SetCurrentPresentationPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: PresentationConversionCompletedSysPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: PdfConversionInvalidErrorSysPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: SetCurrentPagePubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: SetPresenterInPodReqMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: RemovePresentationPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: SetPresentationDownloadablePubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: PresentationConversionUpdateSysPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: PresentationPageGeneratedSysPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: PresentationPageCountErrorSysPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: PresentationUploadTokenReqMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: ResizeAndMovePagePubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: PresentationPageConvertedSysMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: PresentationPageConversionStartedSysMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: PresentationConversionEndedSysMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: CreateNewPresentationPodPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: RemovePresentationPodPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: GetAllPresentationPodsReqMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: SetCurrentPresentationPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: PresentationConversionCompletedSysPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: PdfConversionInvalidErrorSysPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: SetCurrentPagePubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: SetPresenterInPodReqMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: RemovePresentationPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: SetPresentationDownloadablePubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: PresentationConversionUpdateSysPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: PresentationUploadedFileTooLargeErrorSysPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: PresentationPageGeneratedSysPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: PresentationPageCountErrorSysPubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: PresentationUploadTokenReqMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: ResizeAndMovePagePubMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: PresentationPageConvertedSysMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: PresentationPageConversionStartedSysMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
case m: PresentationConversionEndedSysMsg => state = presentationPodsApp.handle(m, state, liveMeeting, msgBus)
// Caption
case m: EditCaptionHistoryPubMsg => captionApp2x.handle(m, liveMeeting, msgBus)
case m: UpdateCaptionOwnerPubMsg => captionApp2x.handle(m, liveMeeting, msgBus)
case m: SendCaptionHistoryReqMsg => captionApp2x.handle(m, liveMeeting, msgBus)
case m: EditCaptionHistoryPubMsg => captionApp2x.handle(m, liveMeeting, msgBus)
case m: UpdateCaptionOwnerPubMsg => captionApp2x.handle(m, liveMeeting, msgBus)
case m: SendCaptionHistoryReqMsg => captionApp2x.handle(m, liveMeeting, msgBus)
// Guests
case m: GetGuestsWaitingApprovalReqMsg => handleGetGuestsWaitingApprovalReqMsg(m)
case m: SetGuestPolicyCmdMsg => handleSetGuestPolicyMsg(m)
case m: SetGuestLobbyMessageCmdMsg => handleSetGuestLobbyMessageMsg(m)
case m: GuestsWaitingApprovedMsg => handleGuestsWaitingApprovedMsg(m)
case m: GuestWaitingLeftMsg => handleGuestWaitingLeftMsg(m)
case m: GetGuestPolicyReqMsg => handleGetGuestPolicyReqMsg(m)
case m: GetGuestsWaitingApprovalReqMsg => handleGetGuestsWaitingApprovalReqMsg(m)
case m: SetGuestPolicyCmdMsg => handleSetGuestPolicyMsg(m)
case m: SetGuestLobbyMessageCmdMsg => handleSetGuestLobbyMessageMsg(m)
case m: GuestsWaitingApprovedMsg => handleGuestsWaitingApprovedMsg(m)
case m: GuestWaitingLeftMsg => handleGuestWaitingLeftMsg(m)
case m: GetGuestPolicyReqMsg => handleGetGuestPolicyReqMsg(m)
// Chat
case m: GetChatHistoryReqMsg => chatApp2x.handle(m, liveMeeting, msgBus)
case m: GetChatHistoryReqMsg => chatApp2x.handle(m, liveMeeting, msgBus)
case m: SendPublicMessagePubMsg =>
chatApp2x.handle(m, liveMeeting, msgBus)
updateUserLastActivity(m.body.message.fromUserId)
@ -565,9 +575,13 @@ class MeetingActor(
def handleMonitorNumberOfUsers(msg: MonitorNumberOfUsersInternalMsg) {
state = removeUsersWithExpiredUserLeftFlag(liveMeeting, state)
val (newState, expireReason) = ExpiryTrackerHelper.processMeetingExpiryAudit(outGW, eventBus, liveMeeting, state)
state = newState
expireReason foreach (reason => log.info("Meeting {} expired with reason {}", props.meetingProp.intId, reason))
if (!liveMeeting.props.meetingProp.isBreakout) {
// Track expiry only for non-breakout rooms. The breakout room lifecycle is
// driven by the parent meeting.
val (newState, expireReason) = ExpiryTrackerHelper.processMeetingExpiryAudit(outGW, eventBus, liveMeeting, state)
state = newState
expireReason foreach (reason => log.info("Meeting {} expired with reason {}", props.meetingProp.intId, reason))
}
sendRttTraceTest()
setRecordingChapterBreak()

View File

@ -75,11 +75,13 @@ class MeetingActorAudit(
// Trigger updating users of time remaining on meeting.
eventBus.publish(BigBlueButtonEvent(props.meetingProp.intId, SendTimeRemainingAuditInternalMsg(props.meetingProp.intId)))
// This is a breakout room. Update the main meeting with list of users in this breakout room.
eventBus.publish(BigBlueButtonEvent(
props.meetingProp.intId,
SendBreakoutUsersAuditInternalMsg(props.breakoutProps.parentId, props.meetingProp.intId)
))
if (props.meetingProp.isBreakout) {
// This is a breakout room. Update the main meeting with list of users in this breakout room.
eventBus.publish(BigBlueButtonEvent(
props.meetingProp.intId,
SendBreakoutUsersAuditInternalMsg(props.breakoutProps.parentId, props.meetingProp.intId)
))
}
// Trigger recording timer, only for meeting allowing recording
if (props.recordProp.record) {

View File

@ -20,7 +20,7 @@ trait MeetingExpiryTrackerHelper extends HandlerHelpers {
for {
expireReason <- reason
} yield {
endAllBreakoutRooms(eventBus, liveMeeting, state)
endAllBreakoutRooms(eventBus, liveMeeting, state, expireReason)
sendEndMeetingDueToExpiry(expireReason, eventBus, outGW, liveMeeting, "system")
}
}

View File

@ -5,10 +5,10 @@ import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.common2.util.JsonUtil
object AnalyticsActor {
def props(): Props = Props(classOf[AnalyticsActor])
def props(includeChat: Boolean): Props = Props(classOf[AnalyticsActor], includeChat)
}
class AnalyticsActor extends Actor with ActorLogging {
class AnalyticsActor(val includeChat: Boolean) extends Actor with ActorLogging {
val TAG = "-- analytics -- "
@ -22,6 +22,12 @@ class AnalyticsActor extends Actor with ActorLogging {
log.info(TAG + json)
}
def logChatMessage(msg: BbbCommonEnvCoreMsg): Unit = {
if (includeChat) {
logMessage(msg)
}
}
def traceMessage(msg: BbbCommonEnvCoreMsg): Unit = {
val json = JsonUtil.toJson(msg)
log.info(" -- trace -- " + json)
@ -120,16 +126,18 @@ class AnalyticsActor extends Actor with ActorLogging {
case m: PresentationConversionUpdateEvtMsgBody => logMessage(msg)
case m: PresentationPageCountErrorSysPubMsg => logMessage(msg)
case m: PresentationPageCountErrorEvtMsg => logMessage(msg)
case m: PresentationUploadedFileTooLargeErrorSysPubMsg => logMessage(msg)
case m: PresentationUploadedFileTooLargeErrorEvtMsg => logMessage(msg)
// Group Chats
case m: SendGroupChatMessageMsg => logMessage(msg)
case m: GroupChatMessageBroadcastEvtMsg => logMessage(msg)
case m: GetGroupChatMsgsReqMsg => logMessage(msg)
case m: GetGroupChatMsgsRespMsg => logMessage(msg)
case m: CreateGroupChatReqMsg => logMessage(msg)
case m: GroupChatCreatedEvtMsg => logMessage(msg)
case m: GetGroupChatsReqMsg => logMessage(msg)
case m: GetGroupChatsRespMsg => logMessage(msg)
case m: SendGroupChatMessageMsg => logChatMessage(msg)
case m: GroupChatMessageBroadcastEvtMsg => logChatMessage(msg)
case m: GetGroupChatMsgsReqMsg => logChatMessage(msg)
case m: GetGroupChatMsgsRespMsg => logChatMessage(msg)
case m: CreateGroupChatReqMsg => logChatMessage(msg)
case m: GroupChatCreatedEvtMsg => logChatMessage(msg)
case m: GetGroupChatsReqMsg => logChatMessage(msg)
case m: GetGroupChatsRespMsg => logChatMessage(msg)
// Guest Management
case m: GuestsWaitingApprovedMsg => logMessage(msg)

View File

@ -1,30 +1,41 @@
package org.bigbluebutton.core2.message.handlers
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.api.SendTimeRemainingAuditInternalMsg
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
import org.bigbluebutton.core.api.{ EndBreakoutRoomInternalMsg, SendBreakoutTimeRemainingInternalMsg, SendTimeRemainingAuditInternalMsg }
import org.bigbluebutton.core.bus.BigBlueButtonEvent
import org.bigbluebutton.core.domain.{ MeetingEndReason, MeetingState2x }
import org.bigbluebutton.core.running.{ LiveMeeting, MeetingActor, OutMsgRouter }
import org.bigbluebutton.core.util.TimeUtil
trait SendBreakoutTimeRemainingMsgHdlr {
this: MeetingActor =>
val liveMeeting: LiveMeeting
val outGW: OutMsgRouter
def handleSendBreakoutTimeRemainingMsg(msg: SendTimeRemainingAuditInternalMsg, state: MeetingState2x): MeetingState2x = {
for {
model <- state.breakout
startedOn <- model.startedOn
} yield {
val endMeetingTime = TimeUtil.millisToSeconds(startedOn) + TimeUtil.minutesToSeconds(model.durationInMinutes)
val timeRemaining = endMeetingTime - TimeUtil.millisToSeconds(System.currentTimeMillis())
if (!liveMeeting.props.meetingProp.isBreakout) {
val endMeetingTime = TimeUtil.millisToSeconds(startedOn) + TimeUtil.minutesToSeconds(model.durationInMinutes)
val timeRemaining = endMeetingTime - TimeUtil.millisToSeconds(System.currentTimeMillis())
// Notify parent meeting users of breakout rooms time remaining
val event = buildBreakoutRoomsTimeRemainingUpdateEvtMsg(liveMeeting.props.meetingProp.intId, timeRemaining.toInt)
outGW.send(event)
}
// Tell all breakout rooms of time remaining so they can notify their users.
// This syncs all rooms about time remaining.
model.rooms.values.foreach { room =>
eventBus.publish(BigBlueButtonEvent(room.id, SendBreakoutTimeRemainingInternalMsg(props.breakoutProps.parentId, timeRemaining.toInt)))
}
if (timeRemaining < 0) {
endAllBreakoutRooms(eventBus, liveMeeting, state, MeetingEndReason.BREAKOUT_ENDED_EXCEEDING_DURATION)
}
}
state

View File

@ -1,10 +1,10 @@
package org.bigbluebutton.core2.message.handlers
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.api.SendTimeRemainingAuditInternalMsg
import org.bigbluebutton.core.domain.{ MeetingState2x }
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.running.{ BaseMeetingActor, LiveMeeting, OutMsgRouter }
import org.bigbluebutton.core.util.TimeUtil
import org.bigbluebutton.core2.message.senders.MsgBuilder
trait SendTimeRemainingUpdateHdlr {
this: BaseMeetingActor =>
@ -13,26 +13,14 @@ trait SendTimeRemainingUpdateHdlr {
val outGW: OutMsgRouter
def handleSendTimeRemainingUpdate(msg: SendTimeRemainingAuditInternalMsg, state: MeetingState2x): MeetingState2x = {
if (state.expiryTracker.durationInMs > 0) {
val endMeetingTime = state.expiryTracker.endMeetingTime()
val timeRemaining = TimeUtil.millisToSeconds(endMeetingTime - TimeUtil.timeNowInMs())
def buildMeetingTimeRemainingUpdateEvtMsg(meetingId: String, timeLeftInSec: Long): BbbCommonEnvCoreMsg = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, meetingId, "not-used")
val envelope = BbbCoreEnvelope(MeetingTimeRemainingUpdateEvtMsg.NAME, routing)
val body = MeetingTimeRemainingUpdateEvtMsgBody(timeLeftInSec)
val header = BbbClientMsgHeader(MeetingTimeRemainingUpdateEvtMsg.NAME, meetingId, "not-used")
val event = MeetingTimeRemainingUpdateEvtMsg(header, body)
BbbCommonEnvCoreMsg(envelope, event)
}
if (timeRemaining > 0) {
val event = buildMeetingTimeRemainingUpdateEvtMsg(liveMeeting.props.meetingProp.intId, timeRemaining.toInt)
val event = MsgBuilder.buildMeetingTimeRemainingUpdateEvtMsg(liveMeeting.props.meetingProp.intId, timeRemaining.toInt)
outGW.send(event)
}
}
state

View File

@ -13,7 +13,7 @@ trait EndMeetingSysCmdMsgHdlr extends HandlerHelpers {
val eventBus: InternalEventBus
def handleEndMeeting(msg: EndMeetingSysCmdMsg, state: MeetingState2x): Unit = {
endAllBreakoutRooms(eventBus, liveMeeting, state)
endAllBreakoutRooms(eventBus, liveMeeting, state, MeetingEndReason.ENDED_FROM_API)
log.info("Meeting {} ended by from API.", msg.body.meetingId)
sendEndMeetingDueToExpiry(MeetingEndReason.ENDED_FROM_API, eventBus, outGW, liveMeeting, "system")
}

View File

@ -494,4 +494,14 @@ object MsgBuilder {
BbbCommonEnvCoreMsg(envelope, event)
}
def buildMeetingTimeRemainingUpdateEvtMsg(meetingId: String, timeLeftInSec: Long): BbbCommonEnvCoreMsg = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, meetingId, "not-used")
val envelope = BbbCoreEnvelope(MeetingTimeRemainingUpdateEvtMsg.NAME, routing)
val body = MeetingTimeRemainingUpdateEvtMsgBody(timeLeftInSec)
val header = BbbClientMsgHeader(MeetingTimeRemainingUpdateEvtMsg.NAME, meetingId, "not-used")
val event = MeetingTimeRemainingUpdateEvtMsg(header, body)
BbbCommonEnvCoreMsg(envelope, event)
}
}

View File

@ -60,19 +60,21 @@ object FakeUserGenerator {
}
def createFakeVoiceUser(user: RegisteredUser, callingWith: String, muted: Boolean, talking: Boolean,
listenOnly: Boolean): VoiceUserState = {
listenOnly: Boolean, floor: Boolean = false): VoiceUserState = {
val voiceUserId = RandomStringGenerator.randomAlphanumericString(8)
val lastFloorTime = System.currentTimeMillis().toString();
VoiceUserState(intId = user.id, voiceUserId = voiceUserId, callingWith, callerName = user.name,
callerNum = user.name, muted, talking, listenOnly, "freeswitch", System.currentTimeMillis())
callerNum = user.name, muted, talking, listenOnly, "freeswitch", System.currentTimeMillis(), floor, lastFloorTime)
}
def createFakeVoiceOnlyUser(callingWith: String, muted: Boolean, talking: Boolean,
listenOnly: Boolean): VoiceUserState = {
listenOnly: Boolean, floor: Boolean = false): VoiceUserState = {
val voiceUserId = RandomStringGenerator.randomAlphanumericString(8)
val intId = "v_" + RandomStringGenerator.randomAlphanumericString(16)
val name = getRandomElement(firstNames, random) + " " + getRandomElement(lastNames, random)
val lastFloorTime = System.currentTimeMillis().toString();
VoiceUserState(intId, voiceUserId = voiceUserId, callingWith, callerName = name,
callerNum = name, muted, talking, listenOnly, "freeswitch", System.currentTimeMillis())
callerNum = name, muted, talking, listenOnly, "freeswitch", System.currentTimeMillis(), floor, lastFloorTime)
}
def createFakeWebcamStreamFor(userId: String, viewers: Set[String]): WebcamStream = {

View File

@ -78,6 +78,10 @@ apps {
endMeetingWhenNoMoreAuthedUsersAfterMinutes = 2
}
analytics {
includeChat = true
}
voiceConf {
recordPath = "/var/freeswitch/meetings"
# Use ogg instead of wav to get smaller audio files.

View File

@ -1,73 +0,0 @@
<!-- http://wiki.freeswitch.org/wiki/Mod_conference -->
<!-- None of these paths are real if you want any of these options you need to really set them up -->
<configuration name="conference.conf" description="Audio Conference">
<!-- Advertise certain presence on startup . -->
<advertise>
<room name="3001@$${domain}" status="FreeSWITCH"/>
</advertise>
<!-- These are the default keys that map when you do not specify a caller control group -->
<!-- Note: none and default are reserved names for group names. Disabled if dist-dtmf member flag is set. -->
<caller-controls>
<group name="default">
<control action="mute" digits="0"/>
<control action="deaf mute" digits="*"/>
<control action="energy up" digits="9"/>
<control action="energy equ" digits="8"/>
<control action="energy dn" digits="7"/>
<control action="vol talk up" digits="3"/>
<control action="vol talk zero" digits="2"/>
<control action="vol talk dn" digits="1"/>
<control action="vol listen up" digits="6"/>
<control action="vol listen zero" digits="5"/>
<control action="vol listen dn" digits="4"/>
<control action="hangup" digits="#"/>
</group>
</caller-controls>
<!-- Profiles are collections of settings you can reference by name. -->
<profiles>
<!-- profile used for WebRTC Desktop Sharing -->
<profile name="video-mcu-stereo">
<param name="domain" value="$${domain}"/>
<param name="rate" value="48000"/>
<param name="channels" value="2"/>
<param name="interval" value="20"/>
<param name="energy-level" value="200"/>
<!-- <param name="tts-engine" value="flite"/> -->
<!-- <param name="tts-voice" value="kal16"/> -->
<!--remove audio for when user is alone since we hit this case every time-->
<!--with -DESKSHARE conference. It has a single user only by default-->
<!--<param name="muted-sound" value="conference/conf-muted.wav"/>-->
<!--<param name="unmuted-sound" value="conference/conf-unmuted.wav"/>-->
<!-- <param name="alone-sound" value="conference/conf-alone.wav"/> -->
<!-- <param name="moh-sound" value="local_stream://stereo"/> -->
<!--<param name="enter-sound" value="tone_stream://%(200,0,500,600,700)"/>-->
<!--<param name="exit-sound" value="tone_stream://%(500,0,300,200,100,50,25)"/>-->
<!--<param name="kicked-sound" value="conference/conf-kicked.wav"/>-->
<!--<param name="locked-sound" value="conference/conf-locked.wav"/>-->
<!--<param name="is-locked-sound" value="conference/conf-is-locked.wav"/>-->
<!--<param name="is-unlocked-sound" value="conference/conf-is-unlocked.wav"/>-->
<!--<param name="pin-sound" value="conference/conf-pin.wav"/>-->
<!--<param name="bad-pin-sound" value="conference/conf-bad-pin.wav"/>-->
<param name="caller-id-name" value="$${outbound_caller_name}"/>
<param name="caller-id-number" value="$${outbound_caller_id}"/>
<param name="comfort-noise" value="false"/>
<param name="conference-flags" value="video-floor-only|video-required-for-canvas|rfc-4579|livearray-sync|minimize-video-encoding"/>
<param name="video-mode" value="mux"/> <!-- other values for video-mode are transcode or passthrough -->
<param name="video-layout-name" value="1x1"/> <!-- 1x1 since we only have 1 video stream -->
<param name="video-layout-name" value="group:grid"/>
<param name="video-canvas-size" value="1920x1080"/>
<param name="video-canvas-bgcolor" value="#333333"/>
<param name="video-layout-bgcolor" value="#000000"/>
<param name="video-codec-bandwidth" value="1mb"/>
<param name="video-fps" value="15"/>
</profile>
</profiles>
</configuration>

View File

@ -1,30 +0,0 @@
<configuration name="verto.conf" description="HTML5 Verto Endpoint">
<settings>
<param name="debug" value="10"/>
</settings>
<profiles>
<profile name="mine">
<param name="bind-local" value="0.0.0.0:8081"/>
<param name="bind-local" value="0.0.0.0:8082" secure="true"/>
<param name="force-register-domain" value="$${domain}"/>
<param name="secure-combined" value="$${certs_dir}/wss.pem"/>
<param name="secure-chain" value="$${certs_dir}/wss.pem"/>
<param name="userauth" value="true"/>
<!-- setting this to true will allow anyone to register even with no account so use with care -->
<param name="blind-reg" value="false"/>
<param name="mcast-ip" value="224.1.1.1"/>
<param name="mcast-port" value="1337"/>
<param name="rtp-ip" value="$${local_ip_v4}"/>
<!-- <param name="ext-rtp-ip" value=""/> -->
<param name="local-network" value="localnet.auto"/>
<param name="outbound-codec-string" value="opus,vp8"/>
<param name="inbound-codec-string" value="opus,vp8"/>
<param name="apply-candidate-acl" value="wan.auto"/>
<param name="timer-name" value="soft"/>
</profile>
</profiles>
</configuration>

View File

@ -1,26 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
NOTICE:
This context is usually accessed via authenticated callers on the sip profile on port 5060
or transfered callers from the public context which arrived via the sip profile on port 5080.
Authenticated users will use the user_context variable on the user to determine what context
they can access. You can also add a user in the directory with the cidr= attribute acl.conf.xml
will build the domains ACL using this value.
-->
<!-- http://wiki.freeswitch.org/wiki/Dialplan_XML -->
<include>
<context name="default">
<extension name="public_extensions">
<condition field="destination_number" expression="^\d{5}-DESKSHARE$">
<action application="log" data="INFO AAAAAA transferring to $1 XML public!!"/>
<action application="transfer" data="${destination_number} XML public"/>
</condition>
</extension>
<!-- other extensions -->
</context>
</include>

View File

@ -1,32 +0,0 @@
<!--
NOTICE:
This context is usually accessed via the external sip profile listening on port 5080.
It is recommended to have separate inbound and outbound contexts. Not only for security
but clearing up why you would need to do such a thing. You don't want outside un-authenticated
callers hitting your default context which allows dialing calls thru your providers and results
in Toll Fraud.
-->
<!-- http://wiki.freeswitch.org/wiki/Dialplan_XML -->
<include>
<context name="public">
<!-- other extensions -->
<extension name="public_extensions">
<condition field="destination_number" expression="^\d{5}-DESKSHARE$">
<action application="log" data="INFO ************WEBRTC DESKSHARE EXT***********" />
<action application="answer"/>
<action application="conference" data="${destination_number}@video-mcu-stereo"/>
</condition>
</extension>
<!--
You can place files in the public directory to get included.
-->
<X-PRE-PROCESS cmd="include" data="public/*.xml"/>
</context>
</include>

View File

@ -2,5 +2,4 @@
rm -rf src/main/resources
cp -R src/universal/conf src/main/resources
sbt run
exec sbt run

View File

@ -3,4 +3,4 @@
sbt clean stage
sudo service bbb-fsesl-akka stop
cd target/universal/stage
./bin/bbb-fsesl-akka
exec ./bin/bbb-fsesl-akka

View File

@ -89,6 +89,14 @@ public class FreeswitchConferenceEventListener implements ConferenceEventListene
vcs.deskShareRTMPBroadcastStopped(evt.getRoom(), evt.getBroadcastingStreamUrl(),
evt.getVideoWidth(), evt.getVideoHeight(), evt.getTimestamp());
}
} else if (event instanceof AudioFloorChangedEvent) {
AudioFloorChangedEvent evt = (AudioFloorChangedEvent) event;
vcs.audioFloorChanged(
evt.getRoom(),
evt.getVoiceUserId(),
evt.getOldVoiceUserId(),
evt.getFloorTimestamp()
);
} else if (event instanceof VoiceConfRunningAndRecordingEvent) {
VoiceConfRunningAndRecordingEvent evt = (VoiceConfRunningAndRecordingEvent) event;
if (evt.running && ! evt.recording) {

View File

@ -64,6 +64,11 @@ public interface IVoiceConferenceService {
Integer videoHeight,
String timestamp);
void audioFloorChanged(String room,
String voiceUserId,
String oldVoiceUserId,
String floorTimestamp);
void voiceConfRunningAndRecording(String room,
Boolean isRunning,
Boolean isRecording,

View File

@ -0,0 +1,50 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2018 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.freeswitch.voice.events;
public class AudioFloorChangedEvent extends VoiceConferenceEvent {
private final String voiceUserId;
private final String oldVoiceUserId;
private final String floorTimestamp;
public AudioFloorChangedEvent(
String room,
String voiceUserId,
String oldVoiceUserId,
String floorTimestamp
) {
super(room);
this.voiceUserId = voiceUserId;
this.oldVoiceUserId = oldVoiceUserId;
this.floorTimestamp = floorTimestamp;
}
public String getVoiceUserId() {
return voiceUserId;
}
public String getOldVoiceUserId() {
return oldVoiceUserId;
}
public String getFloorTimestamp() {
return floorTimestamp;
}
}

View File

@ -26,6 +26,7 @@ public class ESLEventListener implements IEslEventListener {
private static final String STOP_RECORDING_EVENT = "stop-recording";
private static final String CONFERENCE_CREATED_EVENT = "conference-create";
private static final String CONFERENCE_DESTROYED_EVENT = "conference-destroy";
private static final String FLOOR_CHANGE_EVENT = "video-floor-change";
private static final String SCREENSHARE_CONFERENCE_NAME_SUFFIX = "-SCREENSHARE";
@ -197,6 +198,12 @@ public class ESLEventListener implements IEslEventListener {
} else if (action.equals(CONFERENCE_DESTROYED_EVENT)) {
VoiceConfRunningEvent pt = new VoiceConfRunningEvent(confName, false);
conferenceEventListener.handleConferenceEvent(pt);
} else if (action.equals(FLOOR_CHANGE_EVENT)) {
String holderMemberId = this.getNewFloorHolderMemberIdFromEvent(event);
String oldHolderMemberId = this.getOldFloorHolderMemberIdFromEvent(event);
String floorTimestamp = event.getEventHeaders().get("Event-Date-Timestamp");
AudioFloorChangedEvent vFloor= new AudioFloorChangedEvent(confName, holderMemberId, oldHolderMemberId, floorTimestamp);
conferenceEventListener.handleConferenceEvent(vFloor);
} else {
log.warn("Unknown conference Action [" + action + "]");
}
@ -507,6 +514,22 @@ public class ESLEventListener implements IEslEventListener {
return e.getEventHeaders().get("Path");
}
private String getOldFloorHolderMemberIdFromEvent(EslEvent e) {
String oldFloorHolder = e.getEventHeaders().get("Old-ID");
if(oldFloorHolder == null || oldFloorHolder.equalsIgnoreCase("none")) {
oldFloorHolder= "";
}
return oldFloorHolder;
}
private String getNewFloorHolderMemberIdFromEvent(EslEvent e) {
String newHolder = e.getEventHeaders().get("New-ID");
if(newHolder == null || newHolder.equalsIgnoreCase("none")) {
newHolder = "";
}
return newHolder;
}
// Distinguish between recording to a file:
// /path/to/a/file.mp4
// and broadcasting a stream:

View File

@ -276,6 +276,28 @@ class VoiceConferenceService(healthz: HealthzService,
sender.publish(fromVoiceConfRedisChannel, json)
}
def audioFloorChanged(
voiceConfId: String,
voiceUserId: String,
oldVoiceUserId: String,
floorTimestamp: String
) {
val header = BbbCoreVoiceConfHeader(AudioFloorChangedVoiceConfEvtMsg.NAME, voiceConfId)
val body = AudioFloorChangedVoiceConfEvtMsgBody(
voiceConfId,
voiceUserId,
oldVoiceUserId,
floorTimestamp
);
val envelope = BbbCoreEnvelope(AudioFloorChangedVoiceConfEvtMsg.NAME, Map("voiceConf" -> voiceConfId))
val msg = new AudioFloorChangedVoiceConfEvtMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, msg)
val json = JsonUtil.toJson(msgEvent)
sender.publish(fromVoiceConfRedisChannel, json)
}
def voiceCallStateEvent(
conf: String,
callSession: String,

View File

@ -22,7 +22,6 @@ package org.bigbluebutton.common2.redis;
import java.util.HashMap;
import java.util.Map;
import com.sun.org.apache.xpath.internal.operations.Bool;
import io.lettuce.core.api.sync.BaseRedisCommands;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

View File

@ -147,6 +147,21 @@ case class PresentationConversionEndedSysMsgBody(
presName: String
)
object PresentationUploadedFileTooLargeErrorSysPubMsg { val NAME = "PresentationUploadedFileTooLargeErrorSysPubMsg" }
case class PresentationUploadedFileTooLargeErrorSysPubMsg(
header: BbbClientMsgHeader,
body: PresentationUploadedFileTooLargeErrorSysPubMsgBody
) extends StandardMsg
case class PresentationUploadedFileTooLargeErrorSysPubMsgBody(
podId: String,
messageKey: String,
code: String,
presentationName: String,
presentationToken: String,
fileSize: Int,
maxFileSize: Int
)
// ------------ bbb-common-web to akka-apps ------------
// ------------ akka-apps to client ------------
@ -198,6 +213,10 @@ case class PresentationPageConvertedEventMsgBody(
page: PresentationPageVO
)
object PresentationUploadedFileTooLargeErrorEvtMsg { val NAME = "PresentationUploadedFileTooLargeErrorEvtMsg" }
case class PresentationUploadedFileTooLargeErrorEvtMsg(header: BbbClientMsgHeader, body: PresentationUploadedFileTooLargeErrorEvtMsgBody) extends BbbCoreMsg
case class PresentationUploadedFileTooLargeErrorEvtMsgBody(podId: String, messageKey: String, code: String, presentationName: String, presentationToken: String, fileSize: Int, maxFileSize: Int)
object PresentationConversionRequestReceivedEventMsg { val NAME = "PresentationConversionRequestReceivedEventMsg" }
case class PresentationConversionRequestReceivedEventMsg(
header: BbbClientMsgHeader,
@ -281,5 +300,5 @@ case class SyncGetPresentationPodsRespMsgBody(pods: Vector[PresentationPodVO])
// ------------ akka-apps to bbb-common-web ------------
object PresentationUploadTokenSysPubMsg { val NAME = "PresentationUploadTokenSysPubMsg" }
case class PresentationUploadTokenSysPubMsg(header: BbbClientMsgHeader, body: PresentationUploadTokenSysPubMsgBody) extends BbbCoreMsg
case class PresentationUploadTokenSysPubMsgBody(podId: String, authzToken: String, filename: String)
case class PresentationUploadTokenSysPubMsgBody(podId: String, authzToken: String, filename: String, meetingId: String)
// ------------ akka-apps to bbb-common-web ------------

View File

@ -408,4 +408,4 @@ case class SelectRandomViewerReqMsgBody(requestedBy: String)
*/
object SelectRandomViewerRespMsg { val NAME = "SelectRandomViewerRespMsg" }
case class SelectRandomViewerRespMsg(header: BbbClientMsgHeader, body: SelectRandomViewerRespMsgBody) extends StandardMsg
case class SelectRandomViewerRespMsgBody(requestedBy: String, selectedUserId: String)
case class SelectRandomViewerRespMsgBody(requestedBy: String, userIds: Vector[String], choice: Integer)

View File

@ -495,6 +495,24 @@ object SyncGetVoiceUsersRespMsg { val NAME = "SyncGetVoiceUsersRespMsg" }
case class SyncGetVoiceUsersRespMsg(header: BbbClientMsgHeader, body: SyncGetVoiceUsersRespMsgBody) extends BbbCoreMsg
case class SyncGetVoiceUsersRespMsgBody(voiceUsers: Vector[VoiceConfUser])
/**
* Received from FS that a user has become a floor holder
*/
object AudioFloorChangedVoiceConfEvtMsg { val NAME = "AudioFloorChangedVoiceConfEvtMsg" }
case class AudioFloorChangedVoiceConfEvtMsg(
header: BbbCoreVoiceConfHeader,
body: AudioFloorChangedVoiceConfEvtMsgBody
) extends VoiceStandardMsg
case class AudioFloorChangedVoiceConfEvtMsgBody(voiceConf: String, voiceUserId: String, oldVoiceUserId: String, floorTimestamp: String)
/**
* Sent to a client that an user has become a floor holder
*/
object AudioFloorChangedEvtMsg { val NAME = "AudioFloorChangedEvtMsg" }
case class AudioFloorChangedEvtMsg(header: BbbClientMsgHeader, body: AudioFloorChangedEvtMsgBody) extends BbbCoreMsg
case class AudioFloorChangedEvtMsgBody(voiceConf: String, intId: String, voiceUserId: String, floor: Boolean, lastFloorTime: String)
/**
* Received from FS call state events.
*/

View File

@ -86,6 +86,7 @@ import org.bigbluebutton.api2.IBbbWebApiGWApp;
import org.bigbluebutton.api2.domain.UploadedTrack;
import org.bigbluebutton.common2.redis.RedisStorageService;
import org.bigbluebutton.presentation.PresentationUrlDownloadService;
import org.bigbluebutton.presentation.imp.SwfSlidesGenerationProgressNotifier;
import org.bigbluebutton.web.services.WaitingGuestCleanupTimerTask;
import org.bigbluebutton.web.services.UserCleanupTimerTask;
import org.bigbluebutton.web.services.EnteredUserCleanupTimerTask;
@ -120,6 +121,7 @@ public class MeetingService implements MessageListener {
private RedisStorageService storeService;
private CallbackUrlService callbackUrlService;
private HTML5LoadBalancingService html5LoadBalancingService;
private SwfSlidesGenerationProgressNotifier notifier;
private long usersTimeout;
private long enteredUsersTimeout;
@ -314,6 +316,18 @@ public class MeetingService implements MessageListener {
return valid;
}
public PresentationUploadToken getPresentationUploadToken(String authzToken) {
if(uploadAuthzTokens.containsKey(authzToken)) {
return uploadAuthzTokens.get(authzToken);
} else {
return null;
}
}
public void sendPresentationUploadMaxFilesizeMessage(PresentationUploadToken presUploadToken, int uploadedFileSize, int maxUploadFileSize) {
notifier.sendUploadFileTooLargeMessage(presUploadToken, uploadedFileSize, maxUploadFileSize);
}
private void removeUserSessions(String meetingId) {
Iterator<Map.Entry<String, UserSession>> iterator = sessions.entrySet().iterator();
while (iterator.hasNext()) {
@ -1241,4 +1255,9 @@ public class MeetingService implements MessageListener {
public void setEnteredUsersTimeout(long value) {
enteredUsersTimeout = value;
}
public void setSwfSlidesGenerationProgressNotifier(SwfSlidesGenerationProgressNotifier notifier) {
this.notifier = notifier;
}
}

View File

@ -4,10 +4,12 @@ public class PresentationUploadToken implements IMessage {
public final String podId;
public final String authzToken;
public final String filename;
public final String meetingId;
public PresentationUploadToken(String podId, String authzToken, String filename) {
public PresentationUploadToken(String podId, String authzToken, String filename, String meetingId) {
this.podId = podId;
this.authzToken = authzToken;
this.filename = filename;
this.meetingId = meetingId;
}
}

View File

@ -23,6 +23,7 @@ public class ConversionMessageConstants {
public static final String OFFICE_DOC_CONVERSION_SUCCESS_KEY = "OFFICE_DOC_CONVERSION_SUCCESS";
public static final String OFFICE_DOC_CONVERSION_FAILED_KEY = "OFFICE_DOC_CONVERSION_FAILED";
public static final String OFFICE_DOC_CONVERSION_INVALID_KEY = "OFFICE_DOC_CONVERSION_INVALID";
public static final String FILE_TOO_LARGE = "FILE_TOO_LARGE";
public static final String SUPPORTED_DOCUMENT_KEY = "SUPPORTED_DOCUMENT";
public static final String UNSUPPORTED_DOCUMENT_KEY = "UNSUPPORTED_DOCUMENT";
public static final String PAGE_COUNT_FAILED_KEY = "PAGE_COUNT_FAILED";

View File

@ -89,4 +89,9 @@ public abstract class AbstractCommandHandler extends
public Boolean isCommandSuccessful() {
return !exitedWithError();
}
public Boolean isCommandTimeout() {
return exitCode == 124;
}
}

View File

@ -59,18 +59,22 @@ public abstract class Office2PdfPageConverter {
log.info(String.format("Calling conversion script %s.", presOfficeConversionExec));
NuProcessBuilder officeConverterExec = new NuProcessBuilder(Arrays.asList(presOfficeConversionExec, presentationFile.getAbsolutePath(), output.getAbsolutePath()));
NuProcessBuilder officeConverterExec = new NuProcessBuilder(Arrays.asList("timeout", conversionTimeout + "s", "/bin/sh", "-c",
"\""+presOfficeConversionExec + "\" \"" + presentationFile.getAbsolutePath() + "\" \"" + output.getAbsolutePath()+"\""));
Office2PdfConverterHandler office2PdfConverterHandler = new Office2PdfConverterHandler();
officeConverterExec.setProcessListener(office2PdfConverterHandler);
NuProcess process = officeConverterExec.start();
try {
process.waitFor(conversionTimeout, TimeUnit.SECONDS);
process.waitFor(conversionTimeout + 1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
log.error("InterruptedException while counting PDF pages {}", presentationFile.getName(), e);
}
if(office2PdfConverterHandler.isCommandTimeout()) {
log.error("Command execution ({}) exceeded the {} secs timeout for {}.",presOfficeConversionExec, conversionTimeout, presentationFile.getName());
}
if(!office2PdfConverterHandler.isCommandSuccessful()) {
throw new Exception(String.format("Error while executing conversion script %s.", presOfficeConversionExec));
}

View File

@ -29,8 +29,10 @@ public class SvgImageCreatorImp implements SvgImageCreator {
private SwfSlidesGenerationProgressNotifier notifier;
private long imageTagThreshold;
private long pathsThreshold;
private String convTimeout = "60s";
private int WAIT_FOR_SEC = 60;
private int convPdfToSvgTimeout = 60;
private int svgResolutionPpi = 300;
private boolean forceRasterizeSlides = false;
private int pngWidthRasterizedSlides = 2048;
private String BLANK_SVG;
@Override
@ -64,20 +66,24 @@ public class SvgImageCreatorImp implements SvgImageCreator {
dest = imagePresentationDir.getAbsolutePath() + File.separator + "slide-1.pdf";
NuProcessBuilder convertImgToSvg = new NuProcessBuilder(
Arrays.asList("timeout", convTimeout, "convert", source, "-auto-orient", dest));
Arrays.asList("timeout", convPdfToSvgTimeout + "s", "convert", source, "-auto-orient", dest));
Png2SvgConversionHandler pHandler = new Png2SvgConversionHandler();
convertImgToSvg.setProcessListener(pHandler);
NuProcess process = convertImgToSvg.start();
try {
process.waitFor(WAIT_FOR_SEC, TimeUnit.SECONDS);
process.waitFor(convPdfToSvgTimeout + 1, TimeUnit.SECONDS);
done = true;
} catch (InterruptedException e) {
done = false;
log.error("InterruptedException while converting to SVG {}", dest, e);
}
if(pHandler.isCommandTimeout()) {
log.error("Command execution (convertImgToSvg) exceeded the {} secs timeout for {} page {}.", convPdfToSvgTimeout, pres.getName(), page);
}
// Use the intermediate PDF file as source
source = dest;
}
@ -89,26 +95,37 @@ public class SvgImageCreatorImp implements SvgImageCreator {
File destsvg = new File(imagePresentationDir.getAbsolutePath() + File.separatorChar + "slide" + page + ".svg");
NuProcessBuilder convertPdfToSvg = createConversionProcess("-svg", page, source, destsvg.getAbsolutePath(),
SvgConversionHandler pHandler = new SvgConversionHandler();
if(this.forceRasterizeSlides == false) {
NuProcessBuilder convertPdfToSvg = createConversionProcess("-svg", page, source, destsvg.getAbsolutePath(),
true);
SvgConversionHandler pHandler = new SvgConversionHandler();
convertPdfToSvg.setProcessListener(pHandler);
convertPdfToSvg.setProcessListener(pHandler);
NuProcess process = convertPdfToSvg.start();
try {
process.waitFor(WAIT_FOR_SEC, TimeUnit.SECONDS);
done = true;
} catch (InterruptedException e) {
log.error("Interrupted Exception while generating SVG slides {}", pres.getName(), e);
NuProcess process = convertPdfToSvg.start();
try {
process.waitFor(convPdfToSvgTimeout + 1, TimeUnit.SECONDS);
done = true;
} catch (InterruptedException e) {
log.error("Interrupted Exception while generating SVG slides {}", pres.getName(), e);
}
if(pHandler.isCommandTimeout()) {
log.error("Command execution (convertPdfToSvg) exceeded the {} secs timeout for {} page {}.", convPdfToSvgTimeout, pres.getName(), page);
}
if (!done) {
return done;
}
}
if (!done) {
return done;
}
if (destsvg.length() == 0 || pHandler.numberOfImageTags() > imageTagThreshold
|| pHandler.numberOfPaths() > pathsThreshold) {
if (destsvg.length() == 0 ||
pHandler.numberOfImageTags() > imageTagThreshold ||
pHandler.numberOfPaths() > pathsThreshold ||
this.forceRasterizeSlides) {
// We need t delete the destination file as we are starting a
// new conversion process
if (destsvg.exists()) {
@ -117,21 +134,24 @@ public class SvgImageCreatorImp implements SvgImageCreator {
done = false;
Map<String, Object> logData = new HashMap<String, Object>();
logData.put("meetingId", pres.getMeetingId());
logData.put("presId", pres.getId());
logData.put("filename", pres.getName());
logData.put("page", page);
logData.put("convertSuccess", pHandler.isCommandSuccessful());
logData.put("fileExists", destsvg.exists());
logData.put("numberOfImages", pHandler.numberOfImageTags());
logData.put("numberOfPaths", pHandler.numberOfPaths());
logData.put("logCode", "potential_problem_with_svg");
logData.put("message", "Potential problem with generated SVG");
Gson gson = new Gson();
String logStr = gson.toJson(logData);
if(!this.forceRasterizeSlides) {
Map<String, Object> logData = new HashMap<String, Object>();
logData.put("meetingId", pres.getMeetingId());
logData.put("presId", pres.getId());
logData.put("filename", pres.getName());
logData.put("page", page);
logData.put("convertSuccess", pHandler.isCommandSuccessful());
logData.put("fileExists", destsvg.exists());
logData.put("numberOfImages", pHandler.numberOfImageTags());
logData.put("numberOfPaths", pHandler.numberOfPaths());
logData.put("logCode", "potential_problem_with_svg");
logData.put("message", "Potential problem with generated SVG");
Gson gson = new Gson();
String logStr = gson.toJson(logData);
log.warn(" --analytics-- data={}", logStr);
}
log.warn(" --analytics-- data={}", logStr);
File tempPng = null;
String basePresentationame = UUID.randomUUID().toString();
@ -140,14 +160,15 @@ public class SvgImageCreatorImp implements SvgImageCreator {
} catch (IOException ioException) {
// We should never fall into this if the server is correctly
// configured
Map<String, Object> logData = new HashMap<String, Object>();
logData = new HashMap<String, Object>();
logData.put("meetingId", pres.getMeetingId());
logData.put("presId", pres.getId());
logData.put("filename", pres.getName());
logData.put("logCode", "problem_with_creating_svg");
logData.put("message", "Unable to create temporary files");
gson = new Gson();
logStr = gson.toJson(logData);
Gson gson = new Gson();
String logStr = gson.toJson(logData);
log.error(" --analytics-- data={}", logStr, ioException);
}
@ -159,47 +180,66 @@ public class SvgImageCreatorImp implements SvgImageCreator {
convertPdfToPng.setProcessListener(pngHandler);
NuProcess pngProcess = convertPdfToPng.start();
try {
pngProcess.waitFor(WAIT_FOR_SEC, TimeUnit.SECONDS);
pngProcess.waitFor(convPdfToSvgTimeout + 1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
log.error("Interrupted Exception while generating PNG image {}", pres.getName(), e);
}
// Step 2: Convert a PNG image to SVG
NuProcessBuilder convertPngToSvg = new NuProcessBuilder(Arrays.asList("timeout", convTimeout, "convert",
tempPng.getAbsolutePath(), destsvg.getAbsolutePath()));
Png2SvgConversionHandler svgHandler = new Png2SvgConversionHandler();
convertPngToSvg.setProcessListener(svgHandler);
NuProcess svgProcess = convertPngToSvg.start();
try {
svgProcess.waitFor(WAIT_FOR_SEC, TimeUnit.SECONDS);
} catch (InterruptedException e) {
log.error("Interrupted Exception while generating SVG image {}", pres.getName(), e);
if(pngHandler.isCommandTimeout()) {
log.error("Command execution (convertPdfToPng) exceeded the {} secs timeout for {} page {}.", convPdfToSvgTimeout, pres.getName(), page);
}
done = svgHandler.isCommandSuccessful();
if(tempPng.length() > 0) {
// Step 2: Convert a PNG image to SVG
NuProcessBuilder convertPngToSvg = new NuProcessBuilder(Arrays.asList("timeout", convPdfToSvgTimeout + "s", "convert",
tempPng.getAbsolutePath(), destsvg.getAbsolutePath()));
Png2SvgConversionHandler svgHandler = new Png2SvgConversionHandler();
convertPngToSvg.setProcessListener(svgHandler);
NuProcess svgProcess = convertPngToSvg.start();
try {
svgProcess.waitFor(convPdfToSvgTimeout + 1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
log.error("Interrupted Exception while generating SVG image {}", pres.getName(), e);
}
if(svgHandler.isCommandTimeout()) {
log.error("Command execution (convertPngToSvg) exceeded the {} secs timeout for {} page {}.", convPdfToSvgTimeout, pres.getName(), page);
}
done = svgHandler.isCommandSuccessful();
if(destsvg.length() > 0) {
// Step 3: Add SVG namespace to the destionation file
// Check : https://phabricator.wikimedia.org/T43174
NuProcessBuilder addNameSpaceToSVG = new NuProcessBuilder(Arrays.asList("timeout", convPdfToSvgTimeout + "s",
"/bin/sh", "-c",
"sed -i "
+ "'4s|>| xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" version=\"1.2\">|' "
+ destsvg.getAbsolutePath()));
AddNamespaceToSvgHandler namespaceHandler = new AddNamespaceToSvgHandler();
addNameSpaceToSVG.setProcessListener(namespaceHandler);
NuProcess namespaceProcess = addNameSpaceToSVG.start();
try {
namespaceProcess.waitFor(convPdfToSvgTimeout + 1, TimeUnit.SECONDS);
} catch (InterruptedException e) {
log.error("Interrupted Exception while adding SVG namespace {}", pres.getName(), e);
}
if (namespaceHandler.isCommandTimeout()) {
log.error("Command execution (addNameSpaceToSVG) exceeded the {} secs timeout for {} page {}.", convPdfToSvgTimeout, pres.getName(), page);
}
}
}
// Delete the temporary PNG after finishing the image conversion
tempPng.delete();
// Step 3: Add SVG namespace to the destionation file
// Check : https://phabricator.wikimedia.org/T43174
NuProcessBuilder addNameSpaceToSVG = new NuProcessBuilder(Arrays.asList("timeout", convTimeout,
"/bin/sh", "-c",
"sed -i "
+ "'4s|>| xmlns=\"http://www.w3.org/2000/svg\" xmlns:xlink=\"http://www.w3.org/1999/xlink\" version=\"1.2\">|' "
+ destsvg.getAbsolutePath()));
AddNamespaceToSvgHandler namespaceHandler = new AddNamespaceToSvgHandler();
addNameSpaceToSVG.setProcessListener(namespaceHandler);
NuProcess namespaceProcess = addNameSpaceToSVG.start();
try {
namespaceProcess.waitFor(WAIT_FOR_SEC, TimeUnit.SECONDS);
} catch (InterruptedException e) {
log.error("Interrupted Exception while adding SVG namespace {}", pres.getName(), e);
if(tempPng.exists()) {
tempPng.delete();
}
}
long endConv = System.currentTimeMillis();
//System.out.println("******** CREATING SVG page " + page + " " + (endConv - startConv));
@ -226,13 +266,20 @@ public class SvgImageCreatorImp implements SvgImageCreator {
private NuProcessBuilder createConversionProcess(String format, int page, String source, String destFile,
boolean analyze) {
String rawCommand = "pdftocairo -r " + (analyze ? " 300 " : " 150 ") + format + (analyze ? "" : " -singlefile")
+ " -q -f " + String.valueOf(page) + " -l " + String.valueOf(page) + " " + source + " " + destFile;
String rawCommand = "pdftocairo -r " + this.svgResolutionPpi + " " + format + (analyze ? "" : " -singlefile");
//Resize png resolution to avoid too large files
if(format.equals("-png") && this.pngWidthRasterizedSlides != 0) {
rawCommand += " -scale-to-x " + this.pngWidthRasterizedSlides + " -scale-to-y -1 ";
}
rawCommand += " -q -f " + String.valueOf(page) + " -l " + String.valueOf(page) + " " + source + " " + destFile;
if (analyze) {
rawCommand += " && cat " + destFile;
rawCommand += " | egrep 'data:image/png;base64|<path' | sed 's/ / /g' | cut -d' ' -f 1 | sort | uniq -cw 2";
}
return new NuProcessBuilder(Arrays.asList("timeout", convTimeout, "/bin/sh", "-c", rawCommand));
return new NuProcessBuilder(Arrays.asList("timeout", convPdfToSvgTimeout + "s", "/bin/sh", "-c", rawCommand));
}
private File determineSvgImagesDirectory(File presentationFile) {
@ -278,4 +325,20 @@ public class SvgImageCreatorImp implements SvgImageCreator {
SwfSlidesGenerationProgressNotifier notifier) {
this.notifier = notifier;
}
public void setConvPdfToSvgTimeout(int convPdfToSvgTimeout) {
this.convPdfToSvgTimeout = convPdfToSvgTimeout;
}
public void setSvgResolutionPpi(int svgResolutionPpi) {
this.svgResolutionPpi = svgResolutionPpi;
}
public void setForceRasterizeSlides(boolean forceRasterizeSlides) {
this.forceRasterizeSlides = forceRasterizeSlides;
}
public void setPngWidthRasterizedSlides(int pngWidthRasterizedSlides) {
this.pngWidthRasterizedSlides = pngWidthRasterizedSlides;
}
}

View File

@ -18,14 +18,12 @@
package org.bigbluebutton.presentation.imp;
import org.bigbluebutton.api.messaging.messages.PresentationUploadToken;
import org.bigbluebutton.api2.IBbbWebApiGWApp;
import org.bigbluebutton.presentation.ConversionMessageConstants;
import org.bigbluebutton.presentation.GeneratedSlidesInfoHelper;
import org.bigbluebutton.presentation.UploadedPresentation;
import org.bigbluebutton.presentation.messages.DocPageCompletedProgress;
import org.bigbluebutton.presentation.messages.DocPageGeneratedProgress;
import org.bigbluebutton.presentation.messages.IDocConversionMsg;
import org.bigbluebutton.presentation.messages.OfficeDocConversionProgress;
import org.bigbluebutton.presentation.messages.*;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -41,6 +39,17 @@ public class SwfSlidesGenerationProgressNotifier {
messagingService.sendDocConversionMsg(msg);
}
public void sendUploadFileTooLargeMessage(PresentationUploadToken pres, int uploadedFileSize, int maxUploadFileSize) {
UploadFileTooLargeMessage progress = new UploadFileTooLargeMessage(
pres.podId,
pres.meetingId,
pres.filename,
pres.authzToken,
ConversionMessageConstants.FILE_TOO_LARGE,
uploadedFileSize,
maxUploadFileSize);
messagingService.sendDocConversionMsg(progress);
}
public void sendConversionUpdateMessage(int slidesCompleted, UploadedPresentation pres, int pageGenerated) {
DocPageGeneratedProgress progress = new DocPageGeneratedProgress(pres.getPodId(),

View File

@ -0,0 +1,27 @@
package org.bigbluebutton.presentation.messages;
public class UploadFileTooLargeMessage implements IDocConversionMsg {
public final String podId;
public final String meetingId;
public final String filename;
public final String authzToken;
public final String key;
public final Integer uploadedFileSize;
public final Integer maxUploadFileSize;
public UploadFileTooLargeMessage(String podId,
String meetingId,
String filename,
String authzToken,
String key,
Integer uploadedFileSize,
Integer maxUploadFileSize) {
this.podId = podId;
this.meetingId = meetingId;
this.filename = filename;
this.authzToken = authzToken;
this.key = key;
this.uploadedFileSize = uploadedFileSize;
this.maxUploadFileSize = maxUploadFileSize;
}
}

View File

@ -312,6 +312,9 @@ class BbbWebApiGWApp(
} else if (msg.isInstanceOf[DocPageConversionStarted]) {
val event = MsgBuilder.buildPresentationPageConversionStartedSysMsg(msg.asInstanceOf[DocPageConversionStarted])
msgToAkkaAppsEventBus.publish(MsgToAkkaApps(toAkkaAppsChannel, event))
} else if (msg.isInstanceOf[UploadFileTooLargeMessage]) {
val event = MsgBuilder.buildPresentationUploadedFileTooLargeErrorSysMsg(msg.asInstanceOf[UploadFileTooLargeMessage])
msgToAkkaAppsEventBus.publish(MsgToAkkaApps(toAkkaAppsChannel, event))
}
}

View File

@ -280,4 +280,17 @@ object MsgBuilder {
val req = DeletedRecordingSysMsg(header, body)
BbbCommonEnvCoreMsg(envelope, req)
}
def buildPresentationUploadedFileTooLargeErrorSysMsg(msg: UploadFileTooLargeMessage): BbbCommonEnvCoreMsg = {
val routing = collection.immutable.HashMap("sender" -> "bbb-web")
val envelope = BbbCoreEnvelope(PresentationUploadedFileTooLargeErrorSysPubMsg.NAME, routing)
val header = BbbClientMsgHeader(PresentationUploadedFileTooLargeErrorSysPubMsg.NAME, msg.meetingId, msg.authzToken)
val body = PresentationUploadedFileTooLargeErrorSysPubMsgBody(podId = msg.podId, messageKey = msg.key,
code = msg.key, presentationName = msg.filename, presentationToken = msg.authzToken, fileSize = msg.uploadedFileSize.intValue(), maxFileSize = msg.maxUploadFileSize)
val req = PresentationUploadedFileTooLargeErrorSysPubMsg(header, body)
BbbCommonEnvCoreMsg(envelope, req)
}
}

View File

@ -159,7 +159,7 @@ class OldMeetingMsgHdlrActor(val olgMsgGW: OldMessageReceivedGW)
}
def handlePresentationUploadTokenSysPubMsg(msg: PresentationUploadTokenSysPubMsg): Unit = {
olgMsgGW.handle(new PresentationUploadToken(msg.body.podId, msg.body.authzToken, msg.body.filename))
olgMsgGW.handle(new PresentationUploadToken(msg.body.podId, msg.body.authzToken, msg.body.filename, msg.body.meetingId))
}
def handleGuestsWaitingApprovedEvtMsg(msg: GuestsWaitingApprovedEvtMsg): Unit = {

View File

@ -519,6 +519,12 @@ public class Client
} else if (eventFunc.equals("conference_loop_input")) {
listener.conferenceEventAction(uniqueId, confName, confSize, eventAction, event);
return;
} else if (eventFunc.equals("conference_member_set_floor_holder")) {
listener.conferenceEventAction(uniqueId, confName, confSize, eventAction, event);
return;
} else if (eventFunc.equals("conference_video_set_floor_holder")) {
listener.conferenceEventAction(uniqueId, confName, confSize, eventAction, event);
return;
} else if (eventFunc.equals("stop_talking_handler")) {
listener.conferenceEventAction(uniqueId, confName, confSize, eventAction, event);
return;

View File

@ -1,19 +1,44 @@
#!/bin/bash
set -e
set -u
PATH="/bin/:/usr/bin/"
# Conversion of office files to Pdf using local docker bbb-soffice
# This script receives two params
# This script receives three params
# Param 1: Input office file path (e.g. "/tmp/test.odt")
# Param 2: Output pdf file path (e.g. "/tmp/test.pdf")
# Param 3: Output format (pdf default)
while [ -z "$randomDirectoryName" -o -d "/tmp/bbb-libreoffice-conversion/$randomDirectoryName" ]; do
randomDirectoryName=$(shuf -i 100000000-999999999 -n 1)
done
if (( $# == 0 )); then
echo "Missing parameter 1 (Input office file path)";
exit 1
elif (( $# == 1 )); then
echo "Missing parameter 2 (Output pdf file path)";
exit 1
fi;
mkdir -p "/tmp/bbb-libreoffice-conversion/"
mkdir "/tmp/bbb-libreoffice-conversion/$randomDirectoryName/"
cp "$1" "/tmp/bbb-libreoffice-conversion/$randomDirectoryName/file"
sudo /usr/bin/docker run --rm --network none --env="HOME=/tmp/" -w /tmp/ --user=$(printf %05d `id -u`) -v "/tmp/bbb-libreoffice-conversion/$randomDirectoryName/":/data/ --rm bbb-soffice sh -c "/usr/bin/soffice -env:UserInstallation=file:///tmp/ --convert-to pdf --outdir /data /data/file"
cp "/tmp/bbb-libreoffice-conversion/$randomDirectoryName/file.pdf" "$2"
rm -r "/tmp/bbb-libreoffice-conversion/$randomDirectoryName/"
#Create tmp dir for conversion
mkdir -p "/tmp/bbb-soffice-$(whoami)/"
tempDir="$(mktemp -d -p /tmp/bbb-soffice-$(whoami)/)"
source="$1"
dest="$2"
#If output format is missing, define PDF
convertTo="${3:-pdf}"
convertToParam="--convert-to $convertTo"
#If output is html, include param --writer to avoid blank page
if [ ${1: -5} == ".html" ]
then
convertToParam="$convertToParam --writer"
fi
cp "${source}" "$tempDir/file"
sudo /usr/bin/docker run --rm --network none --env="HOME=/tmp/" -w /tmp/ --user=$(printf %05d `id -u`) -v "$tempDir/":/data/ -v /usr/share/fonts/:/usr/share/fonts/:ro --rm bbb-soffice sh -c "/usr/bin/soffice -env:UserInstallation=file:///tmp/ $convertToParam --outdir /data /data/file"
cp "$tempDir/file.$convertTo" "${dest}"
rm -r "$tempDir/"
exit 0

View File

@ -1,12 +1,32 @@
#/bin/bash
#!/bin/bash
set -e
set -u
PATH="/bin/:/usr/bin/"
# This is a sample script - adjust it per your need
# 1 - setup a server with JOD-CONVERTER-REST ( docker run --memory 512m --rm -p 8080:8080 eugenmayer/jodconverter:rest )
# 2 - replace the HOST information in below command with your server host
# This script receives two params
# This script receives three params
# Param 1: Input office file path (e.g. "/tmp/test.odt")
# Param 2: Output pdf file path (e.g. "/tmp/test.pdf")
# Param 3: Destination Format (pdf default)
curl -X POST "http://127.0.0.1:8080/lool/convert-to/pdf" -H "accept: application/octet-stream" -H "Content-Type: multipart/form-data" -F "data=@$1" > $2
if (( $# == 0 )); then
echo "Missing parameter 1 (Input office file path)";
exit 1
elif (( $# == 1 )); then
echo "Missing parameter 2 (Output pdf file path)";
exit 1
fi;
source="$1"
dest="$2"
#If output format is missing, define PDF
convertTo="${3:-pdf}"
curl -X POST "http://127.0.0.1:8080/lool/convert-to/$convertTo" -H "accept: application/octet-stream" -H "Content-Type: multipart/form-data" -F "data=@${source}" > "${dest}"
exit 0

View File

@ -0,0 +1,9 @@
#!/bin/bash
#This script is used to enable Etherpad to export to PDF/ODT
# 1- Edit /usr/share/etherpad-lite/settings.json
# 2- Set "soffice" config to this script path (default "/usr/share/bbb-libreoffice-conversion/etherpad-export.sh")
/usr/share/bbb-libreoffice-conversion/convert.sh "$8" "$(echo $8 | sed -E -e 's/html|odt/'$7'/')" $7
exit 0

View File

@ -1 +1,4 @@
bigbluebutton ALL=(ALL) NOPASSWD: /usr/bin/docker run --rm --network none --env=HOME=/tmp/ -w /tmp/ --user=[0-9][0-9][0-9][0-9][0-9] -v /tmp/bbb-libreoffice-conversion/[0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9][0-9]/\:/data/ --rm bbb-soffice sh -c /usr/bin/soffice -env\:UserInstallation=file\:///tmp/ --convert-to pdf --outdir /data /data/file
bigbluebutton ALL=(ALL) NOPASSWD: /usr/bin/docker run --rm --network none --env=HOME=/tmp/ -w /tmp/ --user=[0-9][0-9][0-9][0-9][0-9] -v /tmp/bbb-soffice-bigbluebutton/tmp.[0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z]/\:/data/ -v /usr/share/fonts/\:/usr/share/fonts/\:ro --rm bbb-soffice sh -c /usr/bin/soffice -env\:UserInstallation=file\:///tmp/ --convert-to pdf --outdir /data /data/file
etherpad ALL=(ALL) NOPASSWD: /usr/bin/docker run --rm --network none --env=HOME=/tmp/ -w /tmp/ --user=[0-9][0-9][0-9][0-9][0-9] -v /tmp/bbb-soffice-etherpad/tmp.[0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z]/\:/data/ -v /usr/share/fonts/\:/usr/share/fonts/\:ro --rm bbb-soffice sh -c /usr/bin/soffice -env\:UserInstallation=file\:///tmp/ --convert-to pdf --writer --outdir /data /data/file
etherpad ALL=(ALL) NOPASSWD: /usr/bin/docker run --rm --network none --env=HOME=/tmp/ -w /tmp/ --user=[0-9][0-9][0-9][0-9][0-9] -v /tmp/bbb-soffice-etherpad/tmp.[0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z]/\:/data/ -v /usr/share/fonts/\:/usr/share/fonts/\:ro --rm bbb-soffice sh -c /usr/bin/soffice -env\:UserInstallation=file\:///tmp/ --convert-to odt --writer --outdir /data /data/file
etherpad ALL=(ALL) NOPASSWD: /usr/bin/docker run --rm --network none --env=HOME=/tmp/ -w /tmp/ --user=[0-9][0-9][0-9][0-9][0-9] -v /tmp/bbb-soffice-etherpad/tmp.[0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z][0-9a-zA-Z]/\:/data/ -v /usr/share/fonts/\:/usr/share/fonts/\:ro --rm bbb-soffice sh -c /usr/bin/soffice -env\:UserInstallation=file\:///tmp/ --convert-to doc --outdir /data /data/file

View File

@ -2,12 +2,12 @@ FROM openjdk:11-jre
ENV DEBIAN_FRONTEND noninteractive
#Required to install Libreoffice 7
RUN echo "deb http://deb.debian.org/debian buster-backports main" >> /etc/apt/sources.list
RUN apt update
RUN apt -y install locales-all fontconfig libxt6 libxrender1
RUN apt -y install --no-install-recommends libreoffice fonts-crosextra-carlito fonts-crosextra-caladea fonts-noto fonts-noto-cjk
RUN dpkg-reconfigure fontconfig && fc-cache -f -s -v
RUN apt install -y -t buster-backports libreoffice

View File

@ -37,6 +37,8 @@ if [ "$FOLDER_CHECK" = "0" ]; then
mkdir -m 755 /usr/share/bbb-libreoffice-conversion/
cp assets/convert-local.sh /usr/share/bbb-libreoffice-conversion/convert.sh
chmod 755 /usr/share/bbb-libreoffice-conversion/convert.sh
cp assets/etherpad-export.sh /usr/share/bbb-libreoffice-conversion/etherpad-export.sh
chmod 755 /usr/share/bbb-libreoffice-conversion/etherpad-export.sh
chown -R root /usr/share/bbb-libreoffice-conversion/
else
echo "Install folder already exists"
@ -50,3 +52,20 @@ else
echo "Sudoers file already exists"
fi;
aptInstalledList=$(apt list --installed 2>&1)
fontInstalled=0
for font in fonts-arkpandora fonts-crosextra-carlito fonts-crosextra-caladea fonts-noto fonts-noto-cjk fonts-liberation fonts-arkpandora
do
if [[ $(echo $aptInstalledList | grep $font | wc -l) = "0" ]]; then
echo "Font $font doesn't exists, installing"
apt-get install -y --no-install-recommends $font
fontInstalled=1
else
echo "Font $font already installed"
fi
done
if [ $fontInstalled = "1" ]; then
dpkg-reconfigure fontconfig && fc-cache -f -s -v
fi

View File

@ -12,6 +12,8 @@ if [ "$FOLDER_CHECK" = "0" ]; then
mkdir -m 755 /usr/share/bbb-libreoffice-conversion/
cp assets/convert-remote.sh /usr/share/bbb-libreoffice-conversion/convert.sh
chmod 755 /usr/share/bbb-libreoffice-conversion/convert.sh
cp assets/etherpad-export.sh /usr/share/bbb-libreoffice-conversion/etherpad-export.sh
chmod 755 /usr/share/bbb-libreoffice-conversion/etherpad-export.sh
chown -R root /usr/share/bbb-libreoffice-conversion/
else
echo "Install folder already exists"

View File

@ -9,5 +9,4 @@ if [ -f webapps/lti.war ]; then
rm webapps/lti.war
fi
catalina.sh run
exec catalina.sh run

View File

@ -2,4 +2,4 @@
rm -rf libs
grails clean
grails compile
grails prod run-app --port 8181
exec grails prod run-app --port 8181

View File

@ -1,5 +0,0 @@
<configuration name="abstraction.conf" description="Abstraction">
<apis>
<api name="user_name" description="Return Name for extension" syntax="&lt;exten&gt;" parse="(.*)" destination="user_data" argument="$1@default var effective_caller_id_name"/>
</apis>
</configuration>

View File

@ -1,12 +0,0 @@
<configuration name="alsa.conf" description="Soundcard Endpoint">
<settings>
<!--Default dialplan and caller-id info -->
<param name="dialplan" value="XML"/>
<param name="cid-name" value="N800 Alsa"/>
<param name="cid-num" value="5555551212"/>
<!--audio sample rate and interval -->
<param name="sample-rate" value="8000"/>
<param name="codec-ms" value="20"/>
</settings>
</configuration>

View File

@ -1,87 +0,0 @@
<configuration name="amqp.conf" description="mod_amqp">
<producers>
<profile name="default">
<connections>
<connection name="primary">
<param name="hostname" value="localhost"/>
<param name="virtualhost" value="/"/>
<param name="username" value="guest"/>
<param name="password" value="guest"/>
<param name="port" value="5673"/>
<param name="heartbeat" value="0"/>
</connection>
<connection name="secondary">
<param name="hostname" value="localhost"/>
<param name="virtualhost" value="/"/>
<param name="username" value="guest"/>
<param name="password" value="guest"/>
<param name="port" value="5672"/>
<param name="heartbeat" value="0"/>
</connection>
</connections>
<params>
<param name="exchange-name" value="TAP.Events"/>
<param name="exchange-type" value="topic"/>
<param name="circuit_breaker_ms" value="10000"/>
<param name="reconnect_interval_ms" value="1000"/>
<param name="send_queue_size" value="5000"/>
<param name="enable_fallback_format_fields" value="1"/>
<!-- The routing key is made from the format string, using the header values in the event specified in the format_fields.-->
<!-- Fields that are prefixed with a # are treated as literals rather than doing a header lookup -->
<param name="format_fields" value="#FreeSWITCH,FreeSWITCH-Hostname,Event-Name,Event-Subclass,Unique-ID"/>
<!-- If enable_fallback_format_fields is enabled, then you can | separate event headers, and if the first does not exist
then the system will check additional configured header values.
-->
<!-- <param name="format_fields" value="#FreeSWITCH,FreeSWITCH-Hostname|#Unknown,Event-Name,Event-Subclass,Unique-ID"/> -->
<!-- <param name="event_filter" value="SWITCH_EVENT_ALL"/> -->
<param name="event_filter" value="SWITCH_EVENT_CHANNEL_CREATE,SWITCH_EVENT_CHANNEL_DESTROY,SWITCH_EVENT_HEARTBEAT,SWITCH_EVENT_DTMF"/>
</params>
</profile>
</producers>
<commands>
<profile name="default">
<connections>
<connection name="primary">
<param name="hostname" value="localhost"/>
<param name="virtualhost" value="/"/>
<param name="username" value="guest"/>
<param name="password" value="guest"/>
<param name="port" value="5672"/>
<param name="heartbeat" value="0"/>
</connection>
</connections>
<params>
<param name="exchange-name" value="TAP.Commands"/>
<param name="binding_key" value="commandBindingKey"/>
<param name="reconnect_interval_ms" value="1000"/>
<param name="queue-passive" value="false"/>
<param name="queue-durable" value="false"/>
<param name="queue-exclusive" value="false"/>
<param name="queue-auto-delete" value="true"/>
</params>
</profile>
</commands>
<logging>
<profile name="default">
<connections>
<connection name="primary">
<param name="hostname" value="localhost"/>
<param name="virtualhost" value="/"/>
<param name="username" value="guest"/>
<param name="password" value="guest"/>
<param name="port" value="5672"/>
<param name="heartbeat" value="0"/>
</connection>
</connections>
<params>
<param name="exchange-name" value="TAP.Logging"/>
<param name="send_queue_size" value="5000"/>
<param name="reconnect_interval_ms" value="1000"/>
<param name="log-levels" value="debug,info,notice,warning,err,crit,alert"/>
</params>
</profile>
</logging>
</configuration>

View File

@ -1,19 +0,0 @@
<configuration name="amr.conf">
<settings>
<!-- AMR modes (supported bitrates) :
mode 0 AMR 4.75 kbps
mode 1 AMR 5.15 kbps
mode 2 AMR 5.9 kbps
mode 3 AMR 6.7 kbps
mode 4 AMR 7.4 kbps
mode 5 AMR 7.95 kbps
mode 6 AMR 10.2 kbps
mode 7 AMR 12.2 kbps
-->
<param name="default-bitrate" value="7"/>
<!-- Enable VoLTE specific FMTP -->
<param name="volte" value="0"/>
<!-- Enable automatic bitrate variation during the call based on RTCP feedback -->
<param name="adjust-bitrate" value="0"/>
</settings>
</configuration>

View File

@ -1,7 +0,0 @@
<configuration name="amrwb.conf">
<settings>
<param name="default-bitrate" value="8"/>
<param name="volte" value="1"/>
<param name="adjust-bitrate" value="0"/>
</settings>
</configuration>

View File

@ -1,167 +0,0 @@
<configuration name="avcodec.conf" description="AVCodec Config">
<settings>
<!-- max bitrate the system support, truncate if over limit -->
<!-- <param name="max-bitrate" value="5mb"/> -->
<!-- <param name="rtp-slice-size" value="1200"/> -->
<!-- minimum time to generate a new key frame in ms /> -->
<!-- <param name="key-frame-min-freq" value="250"/> -->
<!-- integer of cpus, or 'auto', or 'cpu/<divisor>/<max> -->
<param name="dec-threads" value="1"/>
<param name="enc-threads" value="cpu/2/4"/>
</settings>
<profiles>
<profile name="H263">
</profile>
<profile name="H263+">
</profile>
<profile name="H264">
<!-- <param name="dec-threads" value="1"/> -->
<!-- <param name="enc-threads" value="cpu/2/4"/> -->
<!-- <param name="profile" value="baseline"/> -->
<!-- <param name="level" value="41"/> -->
<!-- <param name="timebase" value="1/90"/> -->
<!--
#define AV_CODEC_FLAG_UNALIGNED (1 << 0)
#define AV_CODEC_FLAG_QSCALE (1 << 1)
#define AV_CODEC_FLAG_4MV (1 << 2)
#define AV_CODEC_FLAG_OUTPUT_CORRUPT (1 << 3)
#define AV_CODEC_FLAG_QPEL (1 << 4)
#define AV_CODEC_FLAG_PASS1 (1 << 9)
#define AV_CODEC_FLAG_PASS2 (1 << 10)
#define AV_CODEC_FLAG_LOOP_FILTER (1 << 11)
#define AV_CODEC_FLAG_GRAY (1 << 13)
#define AV_CODEC_FLAG_PSNR (1 << 15)
#define AV_CODEC_FLAG_TRUNCATED (1 << 16)
#define AV_CODEC_FLAG_INTERLACED_DCT (1 << 18)
#define AV_CODEC_FLAG_LOW_DELAY (1 << 19)
#define AV_CODEC_FLAG_GLOBAL_HEADER (1 << 22)
#define AV_CODEC_FLAG_BITEXACT (1 << 23)
#define AV_CODEC_FLAG_AC_PRED (1 << 24)
#define AV_CODEC_FLAG_INTERLACED_ME (1 << 29)
#define AV_CODEC_FLAG_CLOSED_GOP (1U << 31)
-->
<param name="flags" value="LOOP_FILTER|PSNR"/>
<!--
#define FF_CMP_SAD 0
#define FF_CMP_SSE 1
#define FF_CMP_SATD 2
#define FF_CMP_DCT 3
#define FF_CMP_PSNR 4
#define FF_CMP_BIT 5
#define FF_CMP_RD 6
#define FF_CMP_ZERO 7
#define FF_CMP_VSAD 8
#define FF_CMP_VSSE 9
#define FF_CMP_NSSE 10
#define FF_CMP_W53 11
#define FF_CMP_W97 12
#define FF_CMP_DCTMAX 13
#define FF_CMP_DCT264 14
#define FF_CMP_MEDIAN_SAD 15
#define FF_CMP_CHROMA 256
-->
<!-- <param name="me-cmp" value="1"/> -->
<!-- <param name="me-range" value="16"/> -->
<!-- <param name="max-b-frames" value="3"/> -->
<!-- <param name="refs" value="3"/> -->
<!-- <param name="gop-size" value="250"/> -->
<!-- <param name="keyint-min" value="25"/> -->
<!-- <param name="i-quant-factor" value="0.71"/> -->
<!-- <param name="b-quant-factor" value="0.76923078"/> -->
<!-- <param name="qcompress" value="0.6"/> -->
<!-- <param name="qmin" value="10"/> -->
<!-- <param name="qmax" value="51"/> -->
<!-- <param name="max-qdiff" value="4"/> -->
<!--
enum AVColorSpace {
AVCOL_SPC_RGB = 0, ///< order of coefficients is actually GBR, also IEC 61966-2-1 (sRGB)
AVCOL_SPC_BT709 = 1, ///< also ITU-R BT1361 / IEC 61966-2-4 xvYCC709 / SMPTE RP177 Annex B
AVCOL_SPC_UNSPECIFIED = 2,
AVCOL_SPC_RESERVED = 3,
AVCOL_SPC_FCC = 4, ///< FCC Title 47 Code of Federal Regulations 73.682 (a)(20)
AVCOL_SPC_BT470BG = 5, ///< also ITU-R BT601-6 625 / ITU-R BT1358 625 / ITU-R BT1700 625 PAL & SECAM / IEC 61966-2-4 xvYCC601
AVCOL_SPC_SMPTE170M = 6, ///< also ITU-R BT601-6 525 / ITU-R BT1358 525 / ITU-R BT1700 NTSC
AVCOL_SPC_SMPTE240M = 7, ///< functionally identical to above
AVCOL_SPC_YCGCO = 8, ///< Used by Dirac / VC-2 and H.264 FRext, see ITU-T SG16
AVCOL_SPC_YCOCG = AVCOL_SPC_YCGCO,
AVCOL_SPC_BT2020_NCL = 9, ///< ITU-R BT2020 non-constant luminance system
AVCOL_SPC_BT2020_CL = 10, ///< ITU-R BT2020 constant luminance system
AVCOL_SPC_SMPTE2085 = 11, ///< SMPTE 2085, Y'D'zD'x
AVCOL_SPC_CHROMA_DERIVED_NCL = 12, ///< Chromaticity-derived non-constant luminance system
AVCOL_SPC_CHROMA_DERIVED_CL = 13, ///< Chromaticity-derived constant luminance system
AVCOL_SPC_ICTCP = 14, ///< ITU-R BT.2100-0, ICtCp
AVCOL_SPC_NB ///< Not part of ABI
};
-->
<param name="colorspace" value="0"/>
<!--
enum AVColorRange {
AVCOL_RANGE_UNSPECIFIED = 0,
AVCOL_RANGE_MPEG = 1, ///< the normal 219*2^(n-8) "MPEG" YUV ranges
AVCOL_RANGE_JPEG = 2, ///< the normal 2^n-1 "JPEG" YUV ranges
AVCOL_RANGE_NB ///< Not part of ABI
};
-->
<param name="color-range" value="2"/>
<!-- x264 private options-->
<options>
<option name="preset" value="veryfast"/>
<option name="intra_refresh" value="1"/>
<option name="tune" value="animation+zerolatency"/>
<option name="sc_threshold" value="40"/>
<option name="b_strategy" value="1"/>
<option name="crf" value="18"/>
</options>
</profile>
<profile name="H265">
</profile>
<profile name="conference">
<param name="dec-threads" value="1"/>
<param name="enc-threads" value="cpu/2/4"/>
<codecs>
<!-- profiles will be parsed at runtime
to overwrite this profile params if codec matches -->
<codec name="H263" profile="H263"/>
<codec name="H264" profile="H264"/>
<codec name="H264" profile="conference-H264"/>
</codecs>
</profile>
<profile name="conference-H264">
<options>
<option name="preset" value="veryfast"/>
<option name="intra_refresh" value="1"/>
<option name="tune" value="animation+zerolatency"/>
<option name="sc_threshold" value="40"/>
<option name="b_strategy" value="1"/>
<option name="crf" value="10"/>
</options>
</profile>
</profiles>
</configuration>
<configuration name="avformat.conf" description="AVFormat Config">
<settings>
<param name="colorspace" value="1"/>
</settings>
</configuration>

View File

@ -1,74 +0,0 @@
<configuration name="avmd.conf" description="AVMD config">
<settings>
<!-- Edit these settings to change default behaviour
of each avmd session. Settings can be overwritten
by values passed dynamically per each session -->
<!-- Global settings -->
<!-- define/undefine this to enable/disable logging of avmd
intermediate computations to log -->
<param name="debug" value="0"/>
<!-- define/undef this to enable/disable verbose logging (and reporting to the console)
of detection status and other diagnostics like parameters avmd session has been started with,
change of configuration parameters, beep detection status after session ended
(stop event is fired independently of this setting and beep status included there) -->
<param name="report_status" value="1"/>
<!-- define/undefine this to enable/disable faster computation
of arcus cosine - table will be created mapping floats
to integers and returning arc cos values given these integer
indices into table -->
<param name="fast_math" value="0"/>
<!-- Global settings end -->
<!-- Per call (session) settings. These settings can be overwritten
with custom/different values per each avmd session -->
<!-- define/undefine this to classify avmd beep detection as valid
only when there is required number of consecutive elements
in the SMA buffer without reset -->
<param name="require_continuous_streak" value="1"/>
<!-- required number of consecutive elements in the SMA buffer
without reset. This parameter helps to avoid false beeps, bigger this value is
smaller the probability of getting false detection -->
<param name="sample_n_continuous_streak" value="3"/>
<!-- define number of samples to skip starting from the beginning
of the frame and/or after reset has happened. This serves the purpose of skipping first few
estimations on each frame, as these estimations may be inaccurate. This parameter also helps
to give more robust detections when it's value is increased (up to scertain limit of about 60). -->
<param name="sample_n_to_skip" value="0"/>
<param name="require_continuous_streak_amp" value="1"/>
<param name="sample_n_continuous_streak_amp" value="3"/>
<!-- define/undefine this to enable/disable simplified estimation
of frequency based on approximation of sin(x) with (x)
in the range x=[0,PI/2] -->
<param name="simplified_estimation" value="1"/>
<!-- define/undefine to enable/disable avmd on internal channel -->
<param name="inbound_channel" value="0"/>
<!-- define/undefine to enable/disable avmd on external channel -->
<param name="outbound_channel" value="1"/>
<!-- determines the mode of detection, default is both amplitude and frequency -->
<param name="detection_mode" value="2"/>
<!-- number of detection threads running per each avmd session -->
<param name="detectors_n" value="36"/>
<!-- number of lagged detection threads running per each avmd session -->
<param name="detectors_lagged_n" value="1"/>
<!-- Per call settings end -->
</settings>
</configuration>

View File

@ -1,11 +0,0 @@
<configuration name="mod_blacklist.conf" description="Blacklist module">
<lists>
<!--
Example blacklist, the referenced file contains blacklisted items, one entry per line
NOTE: make sure the file exists and is readable by FreeSWITCH.
<list name="example" filename="$${conf_dir}/blacklists/example.list"/>
-->
</lists>
</configuration>

View File

@ -1,39 +0,0 @@
<configuration name="callcenter.conf" description="CallCenter">
<settings>
<!--<param name="odbc-dsn" value="dsn:user:pass"/>-->
<!--<param name="dbname" value="/dev/shm/callcenter.db"/>-->
<!--<param name="cc-instance-id" value="single_box"/>-->
</settings>
<queues>
<queue name="support@default">
<param name="strategy" value="longest-idle-agent"/>
<param name="moh-sound" value="$${hold_music}"/>
<!--<param name="record-template" value="$${recordings_dir}/${strftime(%Y-%m-%d-%H-%M-%S)}.${destination_number}.${caller_id_number}.${uuid}.wav"/>-->
<param name="time-base-score" value="system"/>
<param name="max-wait-time" value="0"/>
<param name="max-wait-time-with-no-agent" value="0"/>
<param name="max-wait-time-with-no-agent-time-reached" value="5"/>
<param name="tier-rules-apply" value="false"/>
<param name="tier-rule-wait-second" value="300"/>
<param name="tier-rule-wait-multiply-level" value="true"/>
<param name="tier-rule-no-agent-no-wait" value="false"/>
<param name="discard-abandoned-after" value="60"/>
<param name="abandoned-resume-allowed" value="false"/>
</queue>
</queues>
<!-- WARNING: Configuration of XML Agents will be updated into the DB upon restart. -->
<!-- WARNING: Configuration of XML Tiers will reset the level and position if those were supplied. -->
<!-- WARNING: Agents and Tiers XML config shouldn't be used in a multi FS shared DB setup (Not currently supported anyway) -->
<agents>
<!--<agent name="1000@default" type="callback" contact="[leg_timeout=10]user/1000@default" status="Available" max-no-answer="3" wrap-up-time="10" reject-delay-time="10" busy-delay-time="60" />-->
</agents>
<tiers>
<!-- If no level or position is provided, they will default to 1. You should do this to keep db value on restart. -->
<!-- <tier agent="1000@default" queue="support@default" level="1" position="1"/> -->
</tiers>
</configuration>

View File

@ -1,23 +0,0 @@
<configuration name="cdr_csv.conf" description="CDR CSV Format">
<settings>
<!-- 'cdr-csv' will always be appended to log-base -->
<!--<param name="log-base" value="/var/log"/>-->
<param name="default-template" value="example"/>
<!-- This is like the info app but after the call is hung up -->
<!--<param name="debug" value="true"/>-->
<param name="rotate-on-hup" value="true"/>
<!-- may be a b or ab -->
<param name="legs" value="a"/>
<!-- Only log in Master.csv -->
<!-- <param name="master-file-only" value="true"/> -->
</settings>
<templates>
<template name="sql">INSERT INTO cdr VALUES ("${caller_id_name}","${caller_id_number}","${destination_number}","${context}","${start_stamp}","${answer_stamp}","${end_stamp}","${duration}","${billsec}","${hangup_cause}","${uuid}","${bleg_uuid}", "${accountcode}");</template>
<template name="example">"${caller_id_name}","${caller_id_number}","${destination_number}","${context}","${start_stamp}","${answer_stamp}","${end_stamp}","${duration}","${billsec}","${hangup_cause}","${uuid}","${bleg_uuid}","${accountcode}","${read_codec}","${write_codec}"</template>
<template name="snom">"${caller_id_name}","${caller_id_number}","${destination_number}","${context}","${start_stamp}","${answer_stamp}","${end_stamp}","${duration}","${billsec}","${hangup_cause}","${uuid}","${bleg_uuid}", "${accountcode}","${read_codec}","${write_codec}","${sip_user_agent}","${call_clientcode}","${sip_rtp_rxstat}","${sip_rtp_txstat}","${sofia_record_file}"</template>
<template name="linksys">"${caller_id_name}","${caller_id_number}","${destination_number}","${context}","${start_stamp}","${answer_stamp}","${end_stamp}","${duration}","${billsec}","${hangup_cause}","${uuid}","${bleg_uuid}","${accountcode}","${read_codec}","${write_codec}","${sip_user_agent}","${sip_p_rtp_stat}"</template>
<template name="asterisk">"${accountcode}","${caller_id_number}","${destination_number}","${context}","${caller_id}","${channel_name}","${bridge_channel}","${last_app}","${last_arg}","${start_stamp}","${answer_stamp}","${end_stamp}","${duration}","${billsec}","${hangup_cause}","${amaflags}","${uuid}","${userfield}"</template>
<template name="opencdrrate">"${uuid}","${signal_bond}","${direction}","${ani}","${destination_number}","${answer_stamp}","${end_stamp}","${billsec}","${accountcode}","${userfield}","${network_addr}","${regex('${original_caller_id_name}'|^.)}","${sip_gateway_name}"</template>
</templates>
</configuration>

View File

@ -1,13 +0,0 @@
<configuration name="cdr_mongodb.conf" description="MongoDB CDR logger">
<settings>
<!-- Hostnames and IPv6 addrs not supported (yet) -->
<param name="host" value="127.0.0.1"/>
<param name="port" value="27017"/>
<!-- Namespace format is database.collection -->
<param name="namespace" value="test.cdr"/>
<!-- If true, create CDR for B-leg of call (default: true) -->
<param name="log-b-leg" value="false"/>
</settings>
</configuration>

View File

@ -1,40 +0,0 @@
<configuration name="cdr_pg_csv.conf" description="CDR PG CSV Format">
<settings>
<!-- See parameters for PQconnectdb() at http://www.postgresql.org/docs/8.4/static/libpq-connect.html -->
<param name="db-info" value="host=localhost dbname=cdr connect_timeout=10" />
<!-- CDR table name -->
<!--<param name="db-table" value="cdr"/>-->
<!-- Log a-leg (a), b-leg (b) or both (ab) -->
<param name="legs" value="a"/>
<!-- Directory in which to spool failed SQL inserts -->
<!-- <param name="spool-dir" value="$${log_dir}/cdr-pg-csv"/> -->
<!-- Disk spool format if DB connection/insert fails - csv (default) or sql -->
<param name="spool-format" value="csv"/>
<param name="rotate-on-hup" value="true"/>
<!-- This is like the info app but after the call is hung up -->
<!--<param name="debug" value="true"/>-->
</settings>
<schema>
<field var="local_ip_v4"/>
<field var="caller_id_name"/>
<field var="caller_id_number"/>
<field var="destination_number"/>
<field var="context"/>
<field var="start_stamp"/>
<field var="answer_stamp"/>
<field var="end_stamp"/>
<field var="duration" quote="false"/>
<field var="billsec" quote="false"/>
<field var="hangup_cause"/>
<field var="uuid"/>
<field var="bleg_uuid"/>
<field var="accountcode"/>
<field var="read_codec"/>
<field var="write_codec"/>
<!-- <field var="sip_hangup_disposition"/> -->
<!-- <field var="ani"/> -->
</schema>
</configuration>

View File

@ -1,18 +0,0 @@
<configuration name="cdr_sqlite.conf" description="SQLite CDR">
<settings>
<!-- SQLite database name (.db suffix will be automatically appended) -->
<!-- <param name="db-name" value="cdr"/> -->
<!-- CDR table name -->
<!-- <param name="db-table" value="cdr"/> -->
<!-- Log a-leg (a), b-leg (b) or both (ab) -->
<param name="legs" value="a"/>
<!-- Default template to use when inserting records -->
<param name="default-template" value="example"/>
<!-- This is like the info app but after the call is hung up -->
<!--<param name="debug" value="true"/>-->
</settings>
<templates>
<!-- Note that field order must match SQL table schema, otherwise insert will fail -->
<template name="example">"${caller_id_name}","${caller_id_number}","${destination_number}","${context}","${start_stamp}","${answer_stamp}","${end_stamp}",${duration},${billsec},"${hangup_cause}","${uuid}","${bleg_uuid}","${accountcode}"</template>
</templates>
</configuration>

View File

@ -1,12 +0,0 @@
<configuration name="cepstral.conf" description="Cepstral TTS configuration">
<settings>
<!--
Possible encodings:
* utf-8
* us-ascii
* iso8859-1 (default)
* iso8859-15
-->
<param name="encoding" value="utf-8"/>
</settings>
</configuration>

View File

@ -1,33 +0,0 @@
<configuration name="cidlookup.conf" description="cidlookup Configuration">
<settings>
<!-- comment out url to not setup a url based lookup -->
<param name="url" value="http://query.voipcnam.com/query.php?api_key=MYAPIKEY&amp;number=${caller_id_number}"/>
<!-- comment out whitepages-apikey to not use whitepages.com, you must
get an API key from http://developer.whitepages.com/ -->
<param name="whitepages-apikey" value="MYAPIKEY"/>
<!-- set to false to not cache (in memcache) results from the url query -->
<param name="cache" value="true"/>
<!-- expire is in seconds -->
<param name="cache-expire" value="86400"/>
<param name="odbc-dsn" value="phone:phone:phone"/>
<!-- comment out sql to not setup a database (directory) lookup -->
<param name="sql" value="
SELECT name||' ('||type||')' AS name
FROM phonebook p JOIN numbers n ON p.id = n.phonebook_id
WHERE n.number='${caller_id_number}'
LIMIT 1
"/>
<!-- comment out citystate-sql to not setup a database (city/state)
lookup -->
<param name="citystate-sql" value="
SELECT ratecenter||' '||state as name
FROM npa_nxx_company_ocn
WHERE npa = ${caller_id_number:1:3} AND nxx = ${caller_id_number:4:3}
LIMIT 1
"/>
</settings>
</configuration>

View File

@ -216,6 +216,7 @@
<param name="caller-id-number" value="$${outbound_caller_id}"/>
<!-- param name="comfort-noise" value="true"/ -->
<param name="comfort-noise" value="1400"/>
<param name="video-auto-floor-msec" value="2000"/>
<!-- <param name="conference-flags" value="video-floor-only|rfc-4579|livearray-sync|auto-3d-position|minimize-video-encoding"/> -->

View File

@ -1,393 +0,0 @@
<configuration name="conference_layouts.conf" description="Audio Conference">
<layout-settings>
<layouts>
<layout name="1x1">
<image x="0" y="0" scale="360" floor="true"/>
</layout>
<layout name="1x2" auto-3d-position="true">
<image x="90" y="0" scale="180"/>
<image x="90" y="180" scale="180"/>
</layout>
<layout name="2x1" auto-3d-position="true">
<image x="0" y="90" scale="180"/>
<image x="180" y="90" scale="180"/>
</layout>
<layout name="2x1-zoom" auto-3d-position="true">
<image x="0" y="0" scale="180" hscale="360" zoom="true"/>
<image x="180" y="0" scale="180" hscale="360" zoom="true"/>
</layout>
<layout name="3x1-zoom" auto-3d-position="true">
<image x="0" y="0" scale="120" hscale="360" zoom="true"/>
<image x="120" y="0" scale="120" hscale="360" zoom="true"/>
<image x="240" y="0" scale="120" hscale="360" zoom="true"/>
</layout>
<layout name="5-grid-zoom" auto-3d-position="true">
<image x="0" y="0" scale="180"/>
<image x="180" y="0" scale="180"/>
<image x="0" y="180" scale="120" hscale="180" zoom="true"/>
<image x="120" y="180" scale="120" hscale="180" zoom="true"/>
<image x="240" y="180" scale="120" hscale="180" zoom="true"/>
</layout>
<layout name="3x2-zoom" auto-3d-position="true">
<image x="0" y="0" scale="120" hscale="180" zoom="true"/>
<image x="120" y="0" scale="120" hscale="180" zoom="true"/>
<image x="240" y="0" scale="120" hscale="180" zoom="true"/>
<image x="0" y="180" scale="120" hscale="180" zoom="true"/>
<image x="120" y="180" scale="120" hscale="180" zoom="true"/>
<image x="240" y="180" scale="120" hscale="180" zoom="true"/>
</layout>
<layout name="7-grid-zoom" auto-3d-position="true">
<image x="0" y="0" scale="120" hscale="180" zoom="true"/>
<image x="120" y="0" scale="120" hscale="180" zoom="true"/>
<image x="240" y="0" scale="120" hscale="180" zoom="true"/>
<image x="0" y="180" scale="90" hscale="180" zoom="true"/>
<image x="90" y="180" scale="90" hscale="180" zoom="true"/>
<image x="180" y="180" scale="90" hscale="180" zoom="true"/>
<image x="270" y="180" scale="90" hscale="180" zoom="true"/>
</layout>
<layout name="4x2-zoom" auto-3d-position="true">
<image x="0" y="0" scale="90" hscale="180" zoom="true"/>
<image x="90" y="0" scale="90" hscale="180" zoom="true"/>
<image x="180" y="0" scale="90" hscale="180" zoom="true"/>
<image x="270" y="0" scale="90" hscale="180" zoom="true"/>
<image x="0" y="180" scale="90" hscale="180" zoom="true"/>
<image x="90" y="180" scale="90" hscale="180" zoom="true"/>
<image x="180" y="180" scale="90" hscale="180" zoom="true"/>
<image x="270" y="180" scale="90" hscale="180" zoom="true"/>
</layout>
<layout name="1x1+2x1" auto-3d-position="true">
<image x="90" y="0" scale="180"/>
<image x="0" y="180" scale="180"/>
<image x="180" y="180" scale="180"/>
</layout>
<layout name="2x2" auto-3d-position="true">
<image x="0" y="0" scale="180"/>
<image x="180" y="0" scale="180"/>
<image x="0" y="180" scale="180"/>
<image x="180" y="180" scale="180"/>
</layout>
<layout name="3x3" auto-3d-position="true">
<image x="0" y="0" scale="120"/>
<image x="120" y="0" scale="120"/>
<image x="240" y="0" scale="120"/>
<image x="0" y="120" scale="120"/>
<image x="120" y="120" scale="120"/>
<image x="240" y="120" scale="120"/>
<image x="0" y="240" scale="120"/>
<image x="120" y="240" scale="120"/>
<image x="240" y="240" scale="120"/>
</layout>
<layout name="4x4" auto-3d-position="true">
<image x="0" y="0" scale="90"/>
<image x="90" y="0" scale="90"/>
<image x="180" y="0" scale="90"/>
<image x="270" y="0" scale="90"/>
<image x="0" y="90" scale="90"/>
<image x="90" y="90" scale="90"/>
<image x="180" y="90" scale="90"/>
<image x="270" y="90" scale="90"/>
<image x="0" y="180" scale="90"/>
<image x="90" y="180" scale="90"/>
<image x="180" y="180" scale="90"/>
<image x="270" y="180" scale="90"/>
<image x="0" y="270" scale="90"/>
<image x="90" y="270" scale="90"/>
<image x="180" y="270" scale="90"/>
<image x="270" y="270" scale="90"/>
</layout>
<layout name="5x5" auto-3d-position="true">
<image x="0" y="0" scale="72"/>
<image x="72" y="0" scale="72"/>
<image x="144" y="0" scale="72"/>
<image x="216" y="0" scale="72"/>
<image x="288" y="0" scale="72"/>
<image x="0" y="72" scale="72"/>
<image x="72" y="72" scale="72"/>
<image x="144" y="72" scale="72"/>
<image x="216" y="72" scale="72"/>
<image x="288" y="72" scale="72"/>
<image x="0" y="144" scale="72"/>
<image x="72" y="144" scale="72"/>
<image x="144" y="144" scale="72"/>
<image x="216" y="144" scale="72"/>
<image x="288" y="144" scale="72"/>
<image x="0" y="216" scale="72"/>
<image x="72" y="216" scale="72"/>
<image x="144" y="216" scale="72"/>
<image x="216" y="216" scale="72"/>
<image x="288" y="216" scale="72"/>
<image x="0" y="288" scale="72"/>
<image x="72" y="288" scale="72"/>
<image x="144" y="288" scale="72"/>
<image x="216" y="288" scale="72"/>
<image x="288" y="288" scale="72"/>
</layout>
<layout name="6x6" auto-3d-position="true">
<image x="0" y="0" scale="60"/>
<image x="60" y="0" scale="60"/>
<image x="120" y="0" scale="60"/>
<image x="180" y="0" scale="60"/>
<image x="240" y="0" scale="60"/>
<image x="300" y="0" scale="60"/>
<image x="0" y="60" scale="60"/>
<image x="60" y="60" scale="60"/>
<image x="120" y="60" scale="60"/>
<image x="180" y="60" scale="60"/>
<image x="240" y="60" scale="60"/>
<image x="300" y="60" scale="60"/>
<image x="0" y="120" scale="60"/>
<image x="60" y="120" scale="60"/>
<image x="120" y="120" scale="60"/>
<image x="180" y="120" scale="60"/>
<image x="240" y="120" scale="60"/>
<image x="300" y="120" scale="60"/>
<image x="0" y="180" scale="60"/>
<image x="60" y="180" scale="60"/>
<image x="120" y="180" scale="60"/>
<image x="180" y="180" scale="60"/>
<image x="240" y="180" scale="60"/>
<image x="300" y="180" scale="60"/>
<image x="0" y="240" scale="60"/>
<image x="60" y="240" scale="60"/>
<image x="120" y="240" scale="60"/>
<image x="180" y="240" scale="60"/>
<image x="240" y="240" scale="60"/>
<image x="300" y="240" scale="60"/>
<image x="0" y="300" scale="60"/>
<image x="60" y="300" scale="60"/>
<image x="120" y="300" scale="60"/>
<image x="180" y="300" scale="60"/>
<image x="240" y="300" scale="60"/>
<image x="300" y="300" scale="60"/>
</layout>
<layout name="8x8" auto-3d-position="true">
<image x="0" y="0" scale="45"/>
<image x="45" y="0" scale="45"/>
<image x="90" y="0" scale="45"/>
<image x="135" y="0" scale="45"/>
<image x="180" y="0" scale="45"/>
<image x="225" y="0" scale="45"/>
<image x="270" y="0" scale="45"/>
<image x="315" y="0" scale="45"/>
<image x="0" y="45" scale="45"/>
<image x="45" y="45" scale="45"/>
<image x="90" y="45" scale="45"/>
<image x="135" y="45" scale="45"/>
<image x="180" y="45" scale="45"/>
<image x="225" y="45" scale="45"/>
<image x="270" y="45" scale="45"/>
<image x="315" y="45" scale="45"/>
<image x="0" y="90" scale="45"/>
<image x="45" y="90" scale="45"/>
<image x="90" y="90" scale="45"/>
<image x="135" y="90" scale="45"/>
<image x="180" y="90" scale="45"/>
<image x="225" y="90" scale="45"/>
<image x="270" y="90" scale="45"/>
<image x="315" y="90" scale="45"/>
<image x="0" y="135" scale="45"/>
<image x="45" y="135" scale="45"/>
<image x="90" y="135" scale="45"/>
<image x="135" y="135" scale="45"/>
<image x="180" y="135" scale="45"/>
<image x="225" y="135" scale="45"/>
<image x="270" y="135" scale="45"/>
<image x="315" y="135" scale="45"/>
<image x="0" y="180" scale="45"/>
<image x="45" y="180" scale="45"/>
<image x="90" y="180" scale="45"/>
<image x="135" y="180" scale="45"/>
<image x="180" y="180" scale="45"/>
<image x="225" y="180" scale="45"/>
<image x="270" y="180" scale="45"/>
<image x="315" y="180" scale="45"/>
<image x="0" y="225" scale="45"/>
<image x="45" y="225" scale="45"/>
<image x="90" y="225" scale="45"/>
<image x="135" y="225" scale="45"/>
<image x="180" y="225" scale="45"/>
<image x="225" y="225" scale="45"/>
<image x="270" y="225" scale="45"/>
<image x="315" y="225" scale="45"/>
<image x="0" y="270" scale="45"/>
<image x="45" y="270" scale="45"/>
<image x="90" y="270" scale="45"/>
<image x="135" y="270" scale="45"/>
<image x="180" y="270" scale="45"/>
<image x="225" y="270" scale="45"/>
<image x="270" y="270" scale="45"/>
<image x="315" y="270" scale="45"/>
<image x="0" y="315" scale="45"/>
<image x="45" y="315" scale="45"/>
<image x="90" y="315" scale="45"/>
<image x="135" y="315" scale="45"/>
<image x="180" y="315" scale="45"/>
<image x="225" y="315" scale="45"/>
<image x="270" y="315" scale="45"/>
<image x="315" y="315" scale="45"/>
</layout>
<layout name="1up_top_left+5" auto-3d-position="true">
<image x="0" y="0" scale="240" floor="true"/>
<image x="240" y="0" scale="120"/>
<image x="240" y="120" scale="120"/>
<image x="0" y="240" scale="120"/>
<image x="120" y="240" scale="120"/>
<image x="240" y="240" scale="120"/>
</layout>
<layout name="1up_top_left+7" auto-3d-position="true">
<image x="0" y="0" scale="270" floor="true"/>
<image x="270" y="0" scale="90"/>
<image x="270" y="90" scale="90"/>
<image x="270" y="180" scale="90"/>
<image x="0" y="270" scale="90"/>
<image x="90" y="270" scale="90"/>
<image x="180" y="270" scale="90"/>
<image x="270" y="270" scale="90"/>
</layout>
<layout name="1up_top_left+9" auto-3d-position="true">
<image x="0" y="0" scale="288" floor="true"/>
<image x="288" y="0" scale="72"/>
<image x="288" y="72" scale="72"/>
<image x="288" y="144" scale="72"/>
<image x="288" y="216" scale="72"/>
<image x="0" y="288" scale="72"/>
<image x="72" y="288" scale="72"/>
<image x="144" y="288" scale="72"/>
<image x="216" y="288" scale="72"/>
<image x="288" y="288" scale="72"/>
</layout>
<layout name="2up_top+8" auto-3d-position="true">
<image x="0" y="0" scale="180" floor="true"/>
<image x="180" y="0" scale="180" reservation_id="secondary"/>
<image x="0" y="180" scale="90"/>
<image x="90" y="180" scale="90"/>
<image x="180" y="180" scale="90"/>
<image x="270" y="180" scale="90"/>
<image x="0" y="270" scale="90"/>
<image x="90" y="270" scale="90"/>
<image x="180" y="270" scale="90"/>
<image x="270" y="270" scale="90"/>
</layout>
<layout name="2up_middle+8" auto-3d-position="true">
<image x="0" y="90" scale="180" floor="true"/>
<image x="180" y="90" scale="180" reservation_id="secondary"/>
<image x="0" y="0" scale="90"/>
<image x="90" y="0" scale="90"/>
<image x="180" y="0" scale="90"/>
<image x="270" y="0" scale="90"/>
<image x="0" y="270" scale="90"/>
<image x="90" y="270" scale="90"/>
<image x="180" y="270" scale="90"/>
<image x="270" y="270" scale="90"/>
</layout>
<layout name="2up_bottom+8" auto-3d-position="true">
<image x="0" y="180" scale="180" floor="true"/>
<image x="180" y="180" scale="180" reservation_id="secondary"/>
<image x="0" y="0" scale="90"/>
<image x="90" y="0" scale="90"/>
<image x="180" y="0" scale="90"/>
<image x="270" y="0" scale="90"/>
<image x="0" y="90" scale="90"/>
<image x="90" y="90" scale="90"/>
<image x="180" y="90" scale="90"/>
<image x="270" y="90" scale="90"/>
</layout>
<layout name="3up+4" auto-3d-position="true">
<image x="0" y="0" scale="180" floor="true"/>
<image x="180" y="0" scale="180" reservation_id="secondary"/>
<image x="0" y="180" scale="180" reservation_id="third"/>
<image x="180" y="180" scale="90"/>
<image x="270" y="180" scale="90"/>
<image x="180" y="270" scale="90"/>
<image x="270" y="270" scale="90"/>
</layout>
<layout name="3up+9" auto-3d-position="true">
<image x="0" y="0" scale="180" floor="true"/>
<image x="180" y="0" scale="180" reservation_id="secondary"/>
<image x="0" y="180" scale="180" reservation_id="third"/>
<image x="180" y="180" scale="60"/>
<image x="240" y="180" scale="60"/>
<image x="300" y="180" scale="60"/>
<image x="180" y="240" scale="60"/>
<image x="240" y="240" scale="60"/>
<image x="300" y="240" scale="60"/>
<image x="180" y="300" scale="60"/>
<image x="240" y="300" scale="60"/>
<image x="300" y="300" scale="60"/>
</layout>
<layout name="2x1-presenter-zoom" auto-3d-position="true">
<image x="0" y="0" scale="180" hscale="360" zoom="true" floor="true"/>
<image x="180" y="0" scale="180" hscale="360" zoom="true" reservation_id="presenter"/>
</layout>
<layout name="presenter-dual-vertical">
<image x="90" y="0" scale="180" floor-only="true"/>
<image x="90" y="180" scale="180" reservation_id="presenter"/>
</layout>
<layout name="presenter-dual-horizontal">
<image x="0" y="90" scale="180" floor-only="true"/>
<image x="180" y="90" scale="180" reservation_id="presenter"/>
</layout>
<layout name="presenter-overlap-small-top-right">
<image x="0" y="0" scale="360" floor-only="true"/>
<image x="300" y="0" scale="60" overlap="true" reservation_id="presenter"/>
</layout>
<layout name="presenter-overlap-small-bot-right">
<image x="0" y="0" scale="360" floor-only="true"/>
<image x="300" y="300" scale="60" overlap="true" reservation_id="presenter"/>
</layout>
<layout name="presenter-overlap-large-top-right">
<image x="0" y="0" scale="360" floor-only="true"/>
<image x="180" y="0" scale="180" overlap="true" reservation_id="presenter"/>
</layout>
<layout name="presenter-overlap-large-bot-right">
<image x="0" y="0" scale="360" floor-only="true"/>
<image x="180" y="180" scale="180" overlap="true" reservation_id="presenter"/>
</layout>
<layout name="overlaps" auto-3d-position="true">
<image x="0" y="0" scale="360" floor-only="true"/>
<image x="300" y="300" scale="60" overlap="true"/>
<image x="240" y="300" scale="60" overlap="true"/>
<image x="180" y="300" scale="60" overlap="true"/>
<image x="120" y="300" scale="60" overlap="true"/>
<image x="60" y="300" scale="60" overlap="true"/>
<image x="0" y="300" scale="60" overlap="true"/>
</layout>
</layouts>
<groups>
<group name="grid">
<layout>1x1</layout>
<layout>2x1</layout>
<layout>1x1+2x1</layout>
<layout>2x2</layout>
<layout>3x3</layout>
<layout>4x4</layout>
<layout>5x5</layout>
<layout>6x6</layout>
<layout>8x8</layout>
</group>
<group name="grid-zoom">
<layout>1x1</layout>
<layout>2x1-zoom</layout>
<layout>3x1-zoom</layout>
<layout>2x2</layout>
<layout>5-grid-zoom</layout>
<layout>3x2-zoom</layout>
<layout>7-grid-zoom</layout>
<layout>4x2-zoom</layout>
<layout>3x3</layout>
</group>
<group name="1up_top_left_plus">
<layout>1up_top_left+5</layout>
<layout>1up_top_left+7</layout>
<layout>1up_top_left+9</layout>
</group>
<group name="3up_plus">
<layout>3up+4</layout>
<layout>3up+9</layout>
</group>
</groups>
</layout-settings>
</configuration>

View File

@ -1,5 +0,0 @@
<configuration name="curl.conf" description="cURL module">
<settings>
<param name="max-bytes" value="64000"/>
</settings>
</configuration>

View File

@ -1,5 +0,0 @@
<configuration name="db.conf" description="LIMIT DB Configuration">
<settings>
<!--<param name="odbc-dsn" value="dsn:user:pass"/>-->
</settings>
</configuration>

View File

@ -1,9 +0,0 @@
<configuration name="dialplan_directory.conf" description="Dialplan Directory">
<settings>
<param name="directory-name" value="ldap"/>
<param name="host" value="ldap.freeswitch.org"/>
<param name="dn" value="cn=Manager,dc=freeswitch,dc=org"/>
<param name="pass" value="test"/>
<param name="base" value="dc=freeswitch,dc=org"/>
</settings>
</configuration>

View File

@ -1,9 +0,0 @@
<configuration name="dingaling.conf" description="XMPP Jingle Endpoint">
<settings>
<param name="debug" value="0"/>
<param name="codec-prefs" value="H264,PCMU"/>
</settings>
<X-PRE-PROCESS cmd="include" data="../jingle_profiles/*.xml"/>
</configuration>

View File

@ -1,21 +0,0 @@
<configuration name="directory.conf" description="Directory">
<settings>
<!--<param name="odbc-dsn" value="dsn:user:pass"/>-->
<!--<param name="dbname" value="directory"/>-->
</settings>
<profiles>
<profile name="default">
<param name="max-menu-attempts" value="3"/>
<param name="min-search-digits" value="3"/>
<param name="terminator-key" value="#"/>
<param name="digit-timeout" value="3000"/>
<param name="max-result" value="5"/>
<param name="next-key" value="6"/>
<param name="prev-key" value="4"/>
<param name="switch-order-key" value="*"/>
<param name="select-name-key" value="1"/>
<param name="new-search-key" value="3"/>
<param name="search-order" value="last_name"/>
</profile>
</profiles>
</configuration>

View File

@ -1,10 +0,0 @@
<configuration name="distributor.conf" description="Distributor Configuration">
<lists>
<!-- every 10 calls to test you will get foo1 once and foo2 9 times...yes NINE TIMES! -->
<!-- this is not the same as 100 with 10 and 90 that would do foo1 10 times in a row then foo2 90 times in a row -->
<list name="test">
<node name="foo1" weight="1"/>
<node name="foo2" weight="9"/>
</list>
</lists>
</configuration>

Some files were not shown because too many files have changed in this diff Show More