Merge remote-tracking branch 'upstream/develop' into PR-11359
This commit is contained in:
commit
0ca1b7896e
2
.github/ISSUE_TEMPLATE/html5-issue.md
vendored
2
.github/ISSUE_TEMPLATE/html5-issue.md
vendored
@ -29,7 +29,7 @@ A clear and concise description of what you expected to happen.
|
||||
**Screenshots**
|
||||
If applicable, add screenshots to help explain your problem.
|
||||
|
||||
**BBB version (optional):**
|
||||
**BBB version:**
|
||||
BigBlueButton continually evolves. Providing the version/build helps us to pinpoint when an issue was introduced.
|
||||
Example:
|
||||
$ sudo bbb-conf --check | grep BigBlueButton
|
||||
|
19
.github/stale.yml
vendored
Normal file
19
.github/stale.yml
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
# Number of days of inactivity before an issue becomes stale
|
||||
daysUntilStale: 270
|
||||
# Number of days of inactivity before a stale issue is closed
|
||||
daysUntilClose: 90
|
||||
# Issues with these labels will never be considered stale
|
||||
exemptLabels:
|
||||
- "status: accepted"
|
||||
- "status: verify"
|
||||
- "target: security"
|
||||
- "type: discussion"
|
||||
# Label to use when marking an issue as stale
|
||||
staleLabel: "status: stale"
|
||||
# Comment to post when marking an issue as stale. Set to `false` to disable
|
||||
markComment: >
|
||||
This issue has been automatically marked as stale because it has not had
|
||||
recent activity. It will be closed if no further activity occurs. Thank you
|
||||
for your contributions.
|
||||
# Comment to post when closing a stale issue. Set to `false` to disable
|
||||
closeComment: false
|
188
.gitlab-ci.yml
Normal file
188
.gitlab-ci.yml
Normal file
@ -0,0 +1,188 @@
|
||||
# set up stages
|
||||
|
||||
variables:
|
||||
GIT_STRATEGY: fetch
|
||||
|
||||
stages:
|
||||
- change detection
|
||||
- get external dependencies
|
||||
- build
|
||||
- push packages
|
||||
|
||||
# define which docker image to use for builds
|
||||
default:
|
||||
image: gitlab.senfcall.de:5050/senfcall-public/docker-bbb-build:v2021-08-10
|
||||
|
||||
# This stage uses git to find out since when each package has been unmodified.
|
||||
# it then checks an API endpoint on the package server to find out for which of
|
||||
# these versions a build exists. If a viable build (from a commit where the
|
||||
# package is identical) is found, that package name and .deb-filename are
|
||||
# written to a file `packages_to_skip.txt` the root of the repo. This file is
|
||||
# passed to the subsequent stages:
|
||||
# - The jobs in the build stage check whether "their" package is listed in
|
||||
# `packages_to_skip.txt` and don't build a new one if it is.
|
||||
# - The bigbluebutton-build job includes the package versions listed in that
|
||||
# file as version-pinned dependencies of the `bigbluebutton` package (instead
|
||||
# of the current commit version)
|
||||
# - The push_packages job sends the filenames of the packages that can be reused
|
||||
# to the server, so they are included with the current branch. (Relevant for
|
||||
# commits that start a new branch and don't change all packages)
|
||||
change_detection:
|
||||
stage: change detection
|
||||
script: build/change_detection.sh
|
||||
artifacts:
|
||||
paths:
|
||||
- packages_to_skip.txt
|
||||
|
||||
# replace placeholder files with actual external repos
|
||||
# (for source and version of the package see the placeholder file)
|
||||
# this step will be obsolete once dependencies can be tracked as
|
||||
# git submodules
|
||||
get_external_dependencies:
|
||||
stage: get external dependencies
|
||||
script: build/get_external_dependencies.sh
|
||||
artifacts:
|
||||
paths:
|
||||
- bbb-etherpad
|
||||
- bbb-webrtc-sfu
|
||||
- freeswitch
|
||||
- bbb-playback
|
||||
expire_in: 1h 30min
|
||||
|
||||
# template job for build step
|
||||
.build_job:
|
||||
stage: build
|
||||
artifacts:
|
||||
paths:
|
||||
- artifacts/*.deb
|
||||
expire_in: 1h 30min
|
||||
cache:
|
||||
key: $CI_COMMIT_REF_SLUG
|
||||
paths:
|
||||
- cache/.gradle
|
||||
|
||||
# jobs for all packages in the "build" stage (templated from above)
|
||||
bbb-apps-akka-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-apps-akka
|
||||
|
||||
bbb-config-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-config
|
||||
|
||||
bbb-demo-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-demo
|
||||
|
||||
bbb-etherpad-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-etherpad
|
||||
|
||||
bbb-freeswitch-core-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-freeswitch-core
|
||||
|
||||
bbb-freeswitch-sounds-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-freeswitch-sounds
|
||||
|
||||
bbb-fsesl-akka-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-fsesl-akka
|
||||
|
||||
bbb-html5-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-html5
|
||||
|
||||
bbb-learning-dashboard:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-learning-dashboard
|
||||
|
||||
bbb-libreoffice-docker-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-libreoffice-docker
|
||||
|
||||
bbb-lti-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-lti
|
||||
|
||||
bbb-mkclean-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-mkclean
|
||||
|
||||
bbb-playback-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-playback
|
||||
|
||||
bbb-playback-notes-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-playback-notes
|
||||
|
||||
bbb-playback-podcast-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-playback-podcast
|
||||
|
||||
bbb-playback-presentation-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-playback-presentation
|
||||
|
||||
bbb-playback-screenshare-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-playback-screenshare
|
||||
|
||||
bbb-record-core-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-record-core
|
||||
|
||||
bbb-web-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-web
|
||||
|
||||
bbb-webhooks-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-webhooks
|
||||
|
||||
bbb-webrtc-sfu-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bbb-webrtc-sfu
|
||||
|
||||
bigbluebutton-build:
|
||||
extends: .build_job
|
||||
script:
|
||||
- build/setup-inside-docker.sh bigbluebutton
|
||||
|
||||
# upload packages to repo server
|
||||
push_packages:
|
||||
stage: push packages
|
||||
script: build/push_packages.sh
|
||||
resource_group: push_packages
|
||||
|
||||
# uncomment the lines below if you want one final
|
||||
# "artifacts" dir with all packages (increases runtime, fills up space on gitlab server)
|
||||
#artifacts:
|
||||
# paths:
|
||||
# - artifacts/*
|
||||
# expire_in: 2 days
|
||||
|
||||
|
435
DEVELOPMENT.md
435
DEVELOPMENT.md
@ -1,435 +0,0 @@
|
||||
This document provides instructions for developers to setup their
|
||||
environment and work on the upcoming BBB 2.0 (tentative release version).
|
||||
|
||||
## Install BBB 1.1
|
||||
|
||||
Follow the [install instructions](http://docs.bigbluebutton.org/install/install.html) for 1.1.
|
||||
|
||||
Make sure you have a working BBB 1.1 before you proceed with the instructions below.
|
||||
|
||||
## Setup development environment
|
||||
|
||||
Setup your development environment following these [instructions](http://docs.bigbluebutton.org/dev/setup.html)
|
||||
|
||||
## Checkout development branch
|
||||
|
||||
Checkout the development branch `move-java-classes-from-bbb-web-to-bbb-common-web` from this [repository](https://github.com/ritzalam/bigbluebutton)
|
||||
|
||||
Open nine (9) terminal windows so you will dedicate one window for each bbb-component.
|
||||
You can name them client, bbb-apps, apps-common, red5, akka-apps, akka-fsesl, bbb-web, common-web, and messages.
|
||||
|
||||
|
||||
## Building the client
|
||||
|
||||
On you bbb-client terminal, run the following commands.
|
||||
|
||||
```
|
||||
cd ~/dev/bigbluebutton/bigbluebutton-client
|
||||
```
|
||||
|
||||
Build build a specific locale (en_US default)
|
||||
|
||||
```
|
||||
ant locale -DLOCALE=en_US
|
||||
```
|
||||
|
||||
To build all locales
|
||||
|
||||
```
|
||||
ant locales
|
||||
```
|
||||
|
||||
This will take about 10 minutes (depending on the speed of your computer). Next, let's build the client
|
||||
|
||||
```
|
||||
ant
|
||||
```
|
||||
|
||||
This will create a build of the BigBlueButton client in the `/home/firstuser/dev/bigbluebutton/bigbluebutton-client/client` directory.
|
||||
|
||||
|
||||
## Build BBB Red5 Applications
|
||||
|
||||
On your red5 terminal, turn off red5 service
|
||||
|
||||
```
|
||||
sudo systemctl stop red5
|
||||
```
|
||||
|
||||
You need to make `red5/webapps` writeable. Otherwise, you will get a permission error when you try to deploy into Red5.
|
||||
|
||||
```
|
||||
sudo chmod -R 777 /usr/share/red5/webapps
|
||||
```
|
||||
|
||||
### Build common-message
|
||||
|
||||
On your message terminal, run the following commands. Other components depends on this, so build this first.
|
||||
|
||||
|
||||
```
|
||||
cd ~/dev/bigbluebutton/bbb-common-message/
|
||||
sbt clean
|
||||
|
||||
sbt publish
|
||||
sbt publishLocal
|
||||
```
|
||||
|
||||
### Build bbb-apps
|
||||
|
||||
We've split bbb-apps into bbb-apps-common and bigbluebutton-apps. We need to build bbb-apps-common first.
|
||||
|
||||
On your apps-common terminal, build the bbb-apps-common component.
|
||||
|
||||
```
|
||||
cd ~/dev/bigbluebutton/bbb-apps-common/
|
||||
|
||||
# Force updating of bbb-commons-message
|
||||
sbt clean
|
||||
|
||||
# Build and share library
|
||||
sbt publish publishLocal
|
||||
```
|
||||
|
||||
On your bbb-apps terminal, run the following commands.
|
||||
|
||||
```
|
||||
cd ~/dev/bigbluebutton/bigbluebutton-apps/
|
||||
|
||||
# To make sure the lib folder is clean of old dependencies especially if you've used this
|
||||
# dev environment for BBB 1.1, delete the contents of the lib directory. You can only to
|
||||
# do once.
|
||||
rm lib/*
|
||||
|
||||
# Force updating dependencies (bbb-apps-common)
|
||||
gradle clean
|
||||
|
||||
gradle resolveDeps
|
||||
gradle war deploy
|
||||
```
|
||||
|
||||
## Manually start services
|
||||
|
||||
### Run Red5
|
||||
|
||||
On your red5 terminal, start red5.
|
||||
|
||||
```
|
||||
cd /usr/share/red5
|
||||
sudo -u red5 ./red5.sh
|
||||
```
|
||||
|
||||
### Run Akka Apps
|
||||
|
||||
On your akka-apps terminal, start akka-apps
|
||||
|
||||
```
|
||||
cd ~/dev/bigbluebutton/akka-bbb-apps
|
||||
|
||||
# To make sure the lib folder is clean of old dependencies especially if you've used this
|
||||
# dev environment for BBB 1.1, delete the contents of the lib directory. You can only to
|
||||
# do once.
|
||||
rm lib_managed/*
|
||||
|
||||
# We need to stop the existing packaged akka-apps
|
||||
sudo systemctl stop bbb-apps-akka
|
||||
|
||||
# Now we can run our own
|
||||
sbt clean
|
||||
sbt run
|
||||
```
|
||||
|
||||
### Run Akka FSESL App
|
||||
|
||||
On your akka-fsesl terminal, start akka-fsesl
|
||||
|
||||
```
|
||||
cd ~/dev/bigbluebutton/akka-bbb-fsesl
|
||||
|
||||
# To make sure the lib folder is clean of old dependencies especially if you've used this
|
||||
# dev environment for BBB 1.1, delete the contents of the lib directory. You can only to
|
||||
# do once.
|
||||
rm lib_managed/*
|
||||
|
||||
# We need to stop the existing packaged akka-fsesl
|
||||
sudo systemctl stop bbb-fsesl-akka
|
||||
|
||||
# Now we can run our own
|
||||
sbt clean
|
||||
sbt run
|
||||
```
|
||||
|
||||
### Build bbb-web
|
||||
|
||||
We've split up bbb-web into bbb-common-web and bigbluebutton-web. We need to build
|
||||
bbb-common-web first.
|
||||
|
||||
On your common-web terminal, run these commands
|
||||
|
||||
|
||||
```
|
||||
cd ~/dev/bigbluebutton/bbb-common-web/
|
||||
|
||||
# To make sure the lib folder is clean of old dependencies especially if you've used this
|
||||
# dev environment for BBB 1.1, delete the contents of the lib directory. You can only to
|
||||
# do once.
|
||||
rm lib_managed/*
|
||||
|
||||
# Force updating of dependencies especially bbb-commons-message
|
||||
sbt clean
|
||||
sbt publish publishLocal
|
||||
```
|
||||
|
||||
|
||||
### Run bbb-web
|
||||
|
||||
|
||||
First we need to remove the old `bbb-web` app from tomcat to avoid duplicate messages
|
||||
|
||||
```
|
||||
sudo cp /var/lib/tomcat7/webapps/bigbluebutton.war /var/lib/tomcat7/webapps/bigbluebutton.war-packaged
|
||||
sudo rm -r /var/lib/tomcat7/webapps/bigbluebutton
|
||||
```
|
||||
|
||||
On your bbb-web terminal, start bbb-web
|
||||
|
||||
```
|
||||
cd ~/dev/bigbluebutton/bigbluebutton-web
|
||||
```
|
||||
|
||||
Get the salt and BBB URL from `/var/lib/tomcat7/webapps/demo/bbb_api_conf.jsp`
|
||||
|
||||
Edit `grails-app/conf/bigbluebutton.properties` and change the following with
|
||||
the salt and IP you got from above.
|
||||
|
||||
```
|
||||
bigbluebutton.web.serverURL=http://192.168.74.128
|
||||
securitySalt=856d5e0197b1aa0cf79897841142a5f6
|
||||
```
|
||||
|
||||
Start bbb-web
|
||||
|
||||
```
|
||||
|
||||
# To make sure the lib folder is clean of old dependencies especially if you've used this
|
||||
# dev environment for BBB 1.1, delete the contents of the lib directory. You can only to
|
||||
# do once.
|
||||
rm lib/*
|
||||
|
||||
gradle clean
|
||||
gradle resolveDeps
|
||||
grails clean
|
||||
sudo chmod -R ugo+rwx /var/log/bigbluebutton
|
||||
grails -Dserver.port=8888 run-war
|
||||
```
|
||||
|
||||
If things started without errors, congrats!
|
||||
|
||||
|
||||
## Converting and Adding new messages
|
||||
|
||||
In bigbluebutton-apps, from [InMessages.scala](https://github.com/bigbluebutton/bigbluebutton/blob/bbb-2x-mconf/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/api/InMessages.scala) choose the message to convert.
|
||||
|
||||
```
|
||||
case class UserShareWebcam(meetingID: String, userId: String, stream: String) extends InMessage
|
||||
```
|
||||
|
||||
In bbb-apps-common, add new message in [BbbCoreEnvelope.scala](https://github.com/bigbluebutton/bigbluebutton/blob/bbb-2x-mconf/bbb-common-message/src/main/scala/org/bigbluebutton/common2/messages/BbbCoreEnvelope.scala)
|
||||
|
||||
```
|
||||
object UserShareWebcamMsg { val NAME = "UserShareWebcamMsg" }
|
||||
case class UserShareWebcamMsg(header: BbbClientMsgHeader, body: UserShareWebcamMsgBody)
|
||||
```
|
||||
|
||||
Define `UserShareWebcamMsgBody` in `MessageBody.scala`
|
||||
|
||||
```
|
||||
case class UserShareWebcamMsgBody(userId: String, stream: String)
|
||||
```
|
||||
|
||||
From the client, send message as
|
||||
|
||||
```
|
||||
{
|
||||
"header": {
|
||||
"name": "UserShareWebcamMsg",
|
||||
"meetingId": "foo-meetingId",
|
||||
"userId": "bar-userId"
|
||||
},
|
||||
"body": {
|
||||
"streamId": "my-webcam-stream"
|
||||
}
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
In [ReceivedJsonMsgHandlerActor](https://github.com/bigbluebutton/bigbluebutton/blob/bbb-2x-mconf/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/pubsub/senders/ReceivedJsonMsgHandlerActor.scala), deserialize the message with implementation in [ReceivedJsonMsgDeserializer](https://github.com/bigbluebutton/bigbluebutton/blob/bbb-2x-mconf/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/pubsub/senders/ReceivedJsonMsgDeserializer.scala).
|
||||
|
||||
|
||||
```
|
||||
|
||||
case UserShareWebcamMsg.NAME =>
|
||||
for {
|
||||
m <- routeUserShareWebcamMsg(jsonNode)
|
||||
} yield {
|
||||
send(envelope, m)
|
||||
}
|
||||
```
|
||||
|
||||
Route the message in [ReceivedMessageRouter](https://github.com/bigbluebutton/bigbluebutton/blob/bbb-2x-mconf/akka-bbb-apps/src/main/scala/org/bigbluebutton/core2/ReceivedMessageRouter.scala).
|
||||
|
||||
```
|
||||
def send(envelope: BbbCoreEnvelope, msg: UserShareWebcamMsg): Unit = {
|
||||
val event = BbbMsgEvent(msg.header.meetingId, BbbCommonEnvCoreMsg(envelope, msg))
|
||||
publish(event)
|
||||
}
|
||||
|
||||
```
|
||||
|
||||
Handle the message in [MeestingActor](https://github.com/bigbluebutton/bigbluebutton/blob/bbb-2x-mconf/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/running/MeetingActor.scala) replacing the old implementation.
|
||||
|
||||
A complete example would be the `ValidateAuthTokenReqMsg`.
|
||||
|
||||
## Installing Flex SDK 4.16.0 and Playerglobal 23.0 for version 2.1 development
|
||||
|
||||
In the next step, you need to get the Apache Flex 4.16.0 SDK package.
|
||||
|
||||
Next, you need to make a directory to hold the tools needed for BigBlueButton development.
|
||||
|
||||
~~~
|
||||
mkdir -p ~/dev/tools
|
||||
cd ~/dev/tools
|
||||
~~~
|
||||
|
||||
First, you need to download the SDK tarball from an Apache mirror site and then unpack it.
|
||||
|
||||
~~~
|
||||
wget https://archive.apache.org/dist/flex/4.16.0/binaries/apache-flex-sdk-4.16.0-bin.tar.gz
|
||||
tar xvfz apache-flex-sdk-4.16.0-bin.tar.gz
|
||||
~~~
|
||||
|
||||
Next, create a linked directory with a shortened name for easier referencing.
|
||||
|
||||
~~~
|
||||
ln -s ~/dev/tools/apache-flex-sdk-4.16.0-bin ~/dev/tools/flex
|
||||
~~~
|
||||
|
||||
Once the Apache Flex SDK is unpacked, you need to download one of the dependencies manually because the file was moved from its original URL.
|
||||
|
||||
~~~
|
||||
wget --content-disposition https://github.com/swfobject/swfobject/archive/2.2.tar.gz
|
||||
tar xvfz swfobject-2.2.tar.gz
|
||||
cp -r swfobject-2.2/swfobject flex/templates/
|
||||
~~~
|
||||
|
||||
Now that we've finished with the first dependency we need to download the Adobe Flex SDK. We'll do this step manually in case the download fails (if it does, remove the incomplete file and issue the `wget` command again).
|
||||
|
||||
~~~
|
||||
cd flex/
|
||||
mkdir -p in/
|
||||
wget http://download.macromedia.com/pub/flex/sdk/builds/flex4.6/flex_sdk_4.6.0.23201B.zip -P in/
|
||||
~~~
|
||||
|
||||
Once the supplementary SDK has downloaded, we can use its `build.xml` script to automatically download the remaining third-party tools.
|
||||
|
||||
~~~
|
||||
ant -f frameworks/build.xml thirdparty-downloads
|
||||
~~~
|
||||
|
||||
After Flex downloads the remaining third-party tools, you need to modify their permissions.
|
||||
|
||||
~~~
|
||||
find ~/dev/tools/flex -type d -exec chmod o+rx '{}' \;
|
||||
chmod 755 ~/dev/tools/flex/bin/*
|
||||
chmod -R +r ~/dev/tools/flex
|
||||
~~~
|
||||
|
||||
The next step in setting up the Flex SDK environment is to download a Flex library for video.
|
||||
|
||||
~~~
|
||||
mkdir -p ~/dev/tools/flex/frameworks/libs/player/23.0
|
||||
cd ~/dev/tools/flex/frameworks/libs/player/23.0
|
||||
wget http://fpdownload.macromedia.com/get/flashplayer/installers/archive/playerglobal/playerglobal23_0.swc
|
||||
mv -f playerglobal23_0.swc playerglobal.swc
|
||||
~~~
|
||||
|
||||
The last step to have a working Flex SDK is to configure it to work with playerglobal 23.0
|
||||
|
||||
~~~
|
||||
cd ~/dev/tools/flex
|
||||
sed -i "s/11.1/23.0/g" frameworks/flex-config.xml
|
||||
sed -i "s/<swf-version>14<\/swf-version>/<swf-version>34<\/swf-version>/g" frameworks/flex-config.xml
|
||||
sed -i "s/{playerglobalHome}\/{targetPlayerMajorVersion}.{targetPlayerMinorVersion}/libs\/player\/23.0/g" frameworks/flex-config.xml
|
||||
~~~
|
||||
|
||||
With the tools installed, you need to add a set of environment variables to your `.profile` to access these tools.
|
||||
|
||||
~~~
|
||||
vi ~/.profile
|
||||
~~~
|
||||
|
||||
Copy-and-paste the following text at bottom of `.profile`.
|
||||
|
||||
~~~
|
||||
|
||||
export FLEX_HOME=$HOME/dev/tools/flex
|
||||
export PATH=$PATH:$FLEX_HOME/bin
|
||||
|
||||
export ANT_OPTS="-Xmx512m -XX:MaxPermSize=512m"
|
||||
|
||||
~~~
|
||||
|
||||
Reload your profile to use these tools (this will happen automatically when you next login).
|
||||
|
||||
~~~
|
||||
source ~/.profile
|
||||
~~~
|
||||
|
||||
Check that the tools are now in your path by running the following command.
|
||||
|
||||
~~~
|
||||
$ mxmlc -version
|
||||
Version 4.16.0 build 20170305
|
||||
~~~
|
||||
|
||||
## Configure the client to use CDN
|
||||
|
||||
It is possible to load the SWF application, modules and and locales from an external server; a CDN as a example. Let's suppose that our eternal domain is `cdn.company.org`
|
||||
|
||||
1.First create a `crossdomain.xml` file into `/var/www/bigbluebutton-default/`
|
||||
|
||||
~~~xml
|
||||
<?xml version="1.0"?>
|
||||
<!DOCTYPE cross-domain-policy SYSTEM "http://www.adobe.com/xml/dtds/cross-domain-policy.dtd">
|
||||
<cross-domain-policy>
|
||||
<site-control permitted-cross-domain-policies="master-only"/>
|
||||
<allow-access-from domain="cdn.company.org"/>
|
||||
</cross-domain-policy>
|
||||
~~~
|
||||
|
||||
For more information about crossdomain policy please visit this link http://www.adobe.com/devnet/articles/crossdomain_policy_file_spec.html
|
||||
|
||||
2.In config.xml you can configure some assets to be loaded from an external server. The same can also be done for every `url` property for `module` tag. Please check the example below
|
||||
|
||||
~~~xml
|
||||
<language ... localesConfig="http://cdn.company.org/client/conf/locales.xml"
|
||||
localesDirectory="http://cdn.company.org/client/locale/" />
|
||||
<skinning url="http://cdn.company.org/client/branding/css/V2Theme.css.swf" />
|
||||
<branding logo="http://cdn.company.org/client/logo.swf" ...
|
||||
background="http://cdn.company.org/client/background.png" />
|
||||
|
||||
...
|
||||
|
||||
<module name="ChatModule" url="http://cdn.company.org/client/ChatModule.swf" ... />
|
||||
~~~
|
||||
|
||||
3.Then in `BigBlueButton.html` make sure you load the main swf from your remote URL. You can make the same with all other assets.
|
||||
|
||||
~~~js
|
||||
content.innerHTML = '<object type="application/x-shockwave-flash" id="BigBlueButton" name="BigBlueButton" tabindex="0" data="http://cdn.company.org/client/BigBlueButton.swf" style="position: relative; top: 0.5px;" width="100%" height="100%" align="middle"><param name="quality" value="high"><param name="bgcolor" value="#FFFFFF"><param name="allowfullscreen" value="true"><param name="allowfullscreeninteractive" value="true"><param name="wmode" value="window"><param name="allowscriptaccess" value="always"><param name="seamlesstabbing" value="true"></object>';
|
||||
}
|
||||
};
|
||||
} else {
|
||||
swfobject.embedSWF("http://cdn.company.org/client/BigBlueButton.swf", "altFlash", "100%", "100%", "11.0.0", "http://cdn.company.org/client/expressInstall.swf", flashvars, params, attributes, embedCallback);
|
||||
}
|
||||
~~~
|
@ -13,7 +13,7 @@ We designed BigBlueButton for online learning (though it can be used for many [o
|
||||
* Group collaboration (many-to-many)
|
||||
* Online classes (one-to-many)
|
||||
|
||||
You can install on a Ubuntu 16.04 64-bit server. We provide [bbb-install.sh](https://github.com/bigbluebutton/bbb-install) to let you have a server up and running within 30 minutes (or your money back 😉).
|
||||
You can install on a Ubuntu 18.04 64-bit server. We provide [bbb-install.sh](https://github.com/bigbluebutton/bbb-install) to let you have a server up and running within 30 minutes (or your money back 😉).
|
||||
|
||||
For full technical documentation BigBlueButton -- including architecture, features, API, and GreenLight (the default front-end) -- see [https://docs.bigbluebutton.org/](https://docs.bigbluebutton.org/).
|
||||
|
||||
|
@ -7,8 +7,12 @@ We actively support BigBlueButton through the community forums and through secur
|
||||
| Version | Supported |
|
||||
| ------- | ------------------ |
|
||||
| 2.0.x (or earlier) | :x: |
|
||||
| 2.2.x | :white_check_mark: |
|
||||
| 2.3-dev | :white_check_mark: |
|
||||
| 2.2.x | :x: |
|
||||
| 2.3.x | :white_check_mark: |
|
||||
|
||||
We have released 2.3 to the community and all our support efforts are now transitioned to 2.3. Also, BigBlueButton 2.2 is running on Ubuntu 16.04 which is now end of life.
|
||||
|
||||
As such, we highly recommend that all administrators deploy 2.3 going forward as it is built upon Ubuntu 18.04. You'll find [many improvements](/2.3/new.html) in this newer version.
|
||||
|
||||
## Reporting a Vulnerability
|
||||
|
||||
|
@ -3,8 +3,11 @@ package org.bigbluebutton
|
||||
import akka.http.scaladsl.model._
|
||||
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
|
||||
import akka.http.scaladsl.server.Directives._
|
||||
import org.bigbluebutton.service.{ HealthzService, PubSubReceiveStatus, PubSubSendStatus, RecordingDBSendStatus }
|
||||
import spray.json.DefaultJsonProtocol
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.service.{ HealthzService, MeetingInfoService, PubSubReceiveStatus, PubSubSendStatus, RecordingDBSendStatus }
|
||||
import spray.json._
|
||||
import scala.concurrent._
|
||||
import ExecutionContext.Implicits.global
|
||||
|
||||
case class HealthResponse(
|
||||
isHealthy: Boolean,
|
||||
@ -13,14 +16,56 @@ case class HealthResponse(
|
||||
recordingDbStatus: RecordingDBSendStatus
|
||||
)
|
||||
|
||||
trait JsonSupportProtocol extends SprayJsonSupport with DefaultJsonProtocol {
|
||||
case class MeetingInfoResponse(
|
||||
meetingInfoResponse: Option[MeetingInfoAnalytics]
|
||||
)
|
||||
|
||||
case class MeetingInfoAnalytics(
|
||||
name: String,
|
||||
externalId: String,
|
||||
internalId: String,
|
||||
hasUserJoined: Boolean,
|
||||
isMeetingRecorded: Boolean,
|
||||
webcams: Webcam,
|
||||
audio: Audio,
|
||||
screenshare: Screenshare,
|
||||
users: List[Participant],
|
||||
presentation: PresentationInfo,
|
||||
breakoutRoom: BreakoutRoom
|
||||
)
|
||||
|
||||
trait JsonSupportProtocolHealthResponse extends SprayJsonSupport with DefaultJsonProtocol {
|
||||
implicit val pubSubSendStatusJsonFormat = jsonFormat2(PubSubSendStatus)
|
||||
implicit val pubSubReceiveStatusJsonFormat = jsonFormat2(PubSubReceiveStatus)
|
||||
implicit val recordingDbStatusJsonFormat = jsonFormat2(RecordingDBSendStatus)
|
||||
implicit val healthServiceJsonFormat = jsonFormat4(HealthResponse)
|
||||
}
|
||||
|
||||
class ApiService(healthz: HealthzService) extends JsonSupportProtocol {
|
||||
trait JsonSupportProtocolMeetingInfoResponse extends SprayJsonSupport with DefaultJsonProtocol {
|
||||
implicit val meetingInfoUserJsonFormat = jsonFormat2(User)
|
||||
implicit val meetingInfoBroadcastJsonFormat = jsonFormat3(Broadcast)
|
||||
implicit val meetingInfoWebcamStreamJsonFormat = jsonFormat2(WebcamStream)
|
||||
implicit val meetingInfoWebcamJsonFormat = jsonFormat2(Webcam)
|
||||
|
||||
implicit val meetingInfoListenOnlyAudioJsonFormat = jsonFormat2(ListenOnlyAudio)
|
||||
implicit val meetingInfoTwoWayAudioJsonFormat = jsonFormat2(TwoWayAudio)
|
||||
implicit val meetingInfoPhoneAudioJsonFormat = jsonFormat2(PhoneAudio)
|
||||
implicit val meetingInfoAudioJsonFormat = jsonFormat4(Audio)
|
||||
|
||||
implicit val meetingInfoScreenshareStreamJsonFormat = jsonFormat2(ScreenshareStream)
|
||||
implicit val meetingInfoScreenshareJsonFormat = jsonFormat1(Screenshare)
|
||||
|
||||
implicit val meetingInfoPresentationInfoJsonFormat = jsonFormat2(PresentationInfo)
|
||||
implicit val meetingInfoBreakoutRoomJsonFormat = jsonFormat2(BreakoutRoom)
|
||||
|
||||
implicit val meetingInfoParticipantJsonFormat = jsonFormat3(Participant)
|
||||
implicit val meetingInfoAnalyticsJsonFormat = jsonFormat11(MeetingInfoAnalytics)
|
||||
implicit val meetingInfoResponseJsonFormat = jsonFormat1(MeetingInfoResponse)
|
||||
}
|
||||
|
||||
class ApiService(healthz: HealthzService, meetingInfoz: MeetingInfoService)
|
||||
extends JsonSupportProtocolHealthResponse
|
||||
with JsonSupportProtocolMeetingInfoResponse {
|
||||
|
||||
def routes =
|
||||
path("healthz") {
|
||||
@ -51,5 +96,33 @@ class ApiService(healthz: HealthzService) extends JsonSupportProtocol {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} ~
|
||||
path("analytics") {
|
||||
parameter('meetingId.as[String]) { meetingId =>
|
||||
get {
|
||||
val meetingAnalyticsFuture = meetingInfoz.getAnalytics(meetingId)
|
||||
val entityFuture = meetingAnalyticsFuture.map { resp =>
|
||||
resp.optionMeetingInfoAnalytics match {
|
||||
case Some(_) =>
|
||||
HttpEntity(ContentTypes.`application/json`, resp.optionMeetingInfoAnalytics.get.toJson.prettyPrint)
|
||||
case None =>
|
||||
HttpEntity(ContentTypes.`application/json`, s"""{ "message": "No active meeting with ID $meetingId"}""".parseJson.prettyPrint)
|
||||
}
|
||||
}
|
||||
complete(entityFuture)
|
||||
}
|
||||
} ~
|
||||
get {
|
||||
val future = meetingInfoz.getAnalytics()
|
||||
val entityFuture = future.map { res =>
|
||||
res.optionMeetingsInfoAnalytics match {
|
||||
case Some(_) =>
|
||||
HttpEntity(ContentTypes.`application/json`, res.optionMeetingsInfoAnalytics.get.toJson.prettyPrint)
|
||||
case None =>
|
||||
HttpEntity(ContentTypes.`application/json`, """{ "message": "No active meetings"}""".parseJson.prettyPrint)
|
||||
}
|
||||
}
|
||||
complete(entityFuture)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -12,8 +12,9 @@ import org.bigbluebutton.core2.AnalyticsActor
|
||||
import org.bigbluebutton.core2.FromAkkaAppsMsgSenderActor
|
||||
import org.bigbluebutton.endpoint.redis.AppsRedisSubscriberActor
|
||||
import org.bigbluebutton.endpoint.redis.RedisRecorderActor
|
||||
import org.bigbluebutton.endpoint.redis.LearningDashboardActor
|
||||
import org.bigbluebutton.common2.bus.IncomingJsonMessageBus
|
||||
import org.bigbluebutton.service.HealthzService
|
||||
import org.bigbluebutton.service.{ HealthzService, MeetingInfoActor, MeetingInfoService }
|
||||
|
||||
object Boot extends App with SystemConfiguration {
|
||||
|
||||
@ -40,21 +41,32 @@ object Boot extends App with SystemConfiguration {
|
||||
)
|
||||
|
||||
val msgSender = new MessageSender(redisPublisher)
|
||||
val bbbMsgBus = new BbbMsgRouterEventBus
|
||||
|
||||
val healthzService = HealthzService(system)
|
||||
|
||||
val apiService = new ApiService(healthzService)
|
||||
val meetingInfoActorRef = system.actorOf(MeetingInfoActor.props())
|
||||
|
||||
outBus2.subscribe(meetingInfoActorRef, outBbbMsgMsgChannel)
|
||||
bbbMsgBus.subscribe(meetingInfoActorRef, analyticsChannel)
|
||||
|
||||
val meetingInfoService = MeetingInfoService(system, meetingInfoActorRef)
|
||||
|
||||
val apiService = new ApiService(healthzService, meetingInfoService)
|
||||
|
||||
val redisRecorderActor = system.actorOf(
|
||||
RedisRecorderActor.props(system, redisConfig, healthzService),
|
||||
"redisRecorderActor"
|
||||
)
|
||||
|
||||
val learningDashboardActor = system.actorOf(
|
||||
LearningDashboardActor.props(system, outGW),
|
||||
"LearningDashboardActor"
|
||||
)
|
||||
|
||||
recordingEventBus.subscribe(redisRecorderActor, outMessageChannel)
|
||||
val incomingJsonMessageBus = new IncomingJsonMessageBus
|
||||
|
||||
val bbbMsgBus = new BbbMsgRouterEventBus
|
||||
|
||||
val fromAkkaAppsMsgSenderActorRef = system.actorOf(FromAkkaAppsMsgSenderActor.props(msgSender))
|
||||
|
||||
val analyticsActorRef = system.actorOf(AnalyticsActor.props(analyticsIncludeChat))
|
||||
@ -64,6 +76,9 @@ object Boot extends App with SystemConfiguration {
|
||||
outBus2.subscribe(analyticsActorRef, outBbbMsgMsgChannel)
|
||||
bbbMsgBus.subscribe(analyticsActorRef, analyticsChannel)
|
||||
|
||||
outBus2.subscribe(learningDashboardActor, outBbbMsgMsgChannel)
|
||||
bbbMsgBus.subscribe(learningDashboardActor, analyticsChannel)
|
||||
|
||||
val bbbActor = system.actorOf(BigBlueButtonActor.props(system, eventBus, bbbMsgBus, outGW, healthzService), "bigbluebutton-actor")
|
||||
eventBus.subscribe(bbbActor, meetingManagerChannel)
|
||||
|
||||
|
@ -75,6 +75,14 @@ case class BreakoutRoomUsersUpdateInternalMsg(parentId: String, breakoutId: Stri
|
||||
*/
|
||||
case class EndBreakoutRoomInternalMsg(parentId: String, breakoutId: String, reason: String) extends InMessage
|
||||
|
||||
/**
|
||||
* Sent by parent meeting to breakout room to extend time.
|
||||
* @param parentId
|
||||
* @param breakoutId
|
||||
* @param extendTimeInMinutes
|
||||
*/
|
||||
case class ExtendBreakoutRoomTimeInternalMsg(parentId: String, breakoutId: String, extendTimeInMinutes: Int) extends InMessage
|
||||
|
||||
// DeskShare
|
||||
case class DeskShareStartedRequest(conferenceName: String, callerId: String, callerIdName: String) extends InMessage
|
||||
case class DeskShareStoppedRequest(conferenceName: String, callerId: String, callerIdName: String) extends InMessage
|
||||
|
@ -9,11 +9,13 @@ object BreakoutModel {
|
||||
externalId: String,
|
||||
name: String,
|
||||
sequence: Integer,
|
||||
shortName: String,
|
||||
isDefaultName: Boolean,
|
||||
freeJoin: Boolean,
|
||||
voiceConf: String,
|
||||
assignedUsers: Vector[String]
|
||||
): BreakoutRoom2x = {
|
||||
new BreakoutRoom2x(id, externalId, name, parentId, sequence, freeJoin, voiceConf, assignedUsers, Vector(), Vector(), None, false)
|
||||
new BreakoutRoom2x(id, externalId, name, parentId, sequence, shortName, isDefaultName, freeJoin, voiceConf, assignedUsers, Vector(), Vector(), None, false)
|
||||
}
|
||||
|
||||
}
|
||||
@ -75,4 +77,9 @@ case class BreakoutModel(
|
||||
def removeRoom(id: String): BreakoutModel = {
|
||||
copy(rooms = rooms - id)
|
||||
}
|
||||
|
||||
def extendTime(timeToExtendInMinutes: Int): BreakoutModel = {
|
||||
copy(durationInMinutes = durationInMinutes + timeToExtendInMinutes)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,27 @@
|
||||
package org.bigbluebutton.core.apps
|
||||
|
||||
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
|
||||
import org.bigbluebutton.core2.message.senders.{ MsgBuilder }
|
||||
|
||||
object ExternalVideoModel {
|
||||
def setURL(externalVideoModel: ExternalVideoModel, externalVideoUrl: String) {
|
||||
externalVideoModel.externalVideoUrl = externalVideoUrl
|
||||
}
|
||||
|
||||
def clear(externalVideoModel: ExternalVideoModel) {
|
||||
externalVideoModel.externalVideoUrl = ""
|
||||
}
|
||||
|
||||
def stop(outGW: OutMsgRouter, liveMeeting: LiveMeeting) {
|
||||
if (!liveMeeting.externalVideoModel.externalVideoUrl.isEmpty) {
|
||||
liveMeeting.externalVideoModel.externalVideoUrl = ""
|
||||
|
||||
val event = MsgBuilder.buildStopExternalVideoEvtMsg(liveMeeting.props.meetingProp.intId)
|
||||
outGW.send(event)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class ExternalVideoModel {
|
||||
private var externalVideoUrl = ""
|
||||
}
|
@ -10,10 +10,12 @@ trait BreakoutApp2x extends BreakoutRoomCreatedMsgHdlr
|
||||
with BreakoutRoomUsersUpdateMsgHdlr
|
||||
with CreateBreakoutRoomsCmdMsgHdlr
|
||||
with EndAllBreakoutRoomsMsgHdlr
|
||||
with ExtendBreakoutRoomsTimeMsgHdlr
|
||||
with RequestBreakoutJoinURLReqMsgHdlr
|
||||
with SendBreakoutUsersUpdateMsgHdlr
|
||||
with TransferUserToMeetingRequestHdlr
|
||||
with EndBreakoutRoomInternalMsgHdlr
|
||||
with ExtendBreakoutRoomTimeInternalMsgHdlr
|
||||
with BreakoutRoomEndedInternalMsgHdlr {
|
||||
|
||||
this: MeetingActor =>
|
||||
|
@ -68,7 +68,7 @@ trait BreakoutRoomCreatedMsgHdlr {
|
||||
|
||||
def sendBreakoutRoomsList(breakoutModel: BreakoutModel): BreakoutModel = {
|
||||
val breakoutRooms = breakoutModel.rooms.values.toVector map { r =>
|
||||
new BreakoutRoomInfo(r.name, r.externalId, r.id, r.sequence, r.freeJoin)
|
||||
new BreakoutRoomInfo(r.name, r.externalId, r.id, r.sequence, r.shortName, r.isDefaultName, r.freeJoin)
|
||||
}
|
||||
|
||||
log.info("Sending breakout rooms list to {} with containing {} room(s)", liveMeeting.props.meetingProp.intId, breakoutRooms.length)
|
||||
@ -95,7 +95,7 @@ trait BreakoutRoomCreatedMsgHdlr {
|
||||
BbbCommonEnvCoreMsg(envelope, event)
|
||||
}
|
||||
|
||||
val breakoutInfo = BreakoutRoomInfo(room.name, room.externalId, room.id, room.sequence, room.freeJoin)
|
||||
val breakoutInfo = BreakoutRoomInfo(room.name, room.externalId, room.id, room.sequence, room.shortName, room.isDefaultName, room.freeJoin)
|
||||
val event = build(liveMeeting.props.meetingProp.intId, breakoutInfo)
|
||||
outGW.send(event)
|
||||
|
||||
|
@ -3,6 +3,7 @@ package org.bigbluebutton.core.apps.breakout
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.api.BreakoutRoomUsersUpdateInternalMsg
|
||||
import org.bigbluebutton.core.domain.{ BreakoutRoom2x, MeetingState2x }
|
||||
import org.bigbluebutton.core.models.Users2x
|
||||
import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter }
|
||||
|
||||
trait BreakoutRoomUsersUpdateMsgHdlr {
|
||||
@ -30,6 +31,13 @@ trait BreakoutRoomUsersUpdateMsgHdlr {
|
||||
val updatedRoom = room.copy(users = msg.users, voiceUsers = msg.voiceUsers)
|
||||
val msgEvent = broadcastEvent(updatedRoom)
|
||||
outGW.send(msgEvent)
|
||||
|
||||
//Update user lastActivityTime in parent room (to avoid be ejected while is in Breakout room)
|
||||
for {
|
||||
breakoutRoomUser <- updatedRoom.users
|
||||
user <- Users2x.findWithBreakoutRoomId(liveMeeting.users2x, breakoutRoomUser.id)
|
||||
} yield Users2x.updateLastUserActivity(liveMeeting.users2x, user)
|
||||
|
||||
model.update(updatedRoom)
|
||||
}
|
||||
|
||||
|
@ -28,7 +28,7 @@ trait BreakoutRoomsListMsgHdlr {
|
||||
breakoutModel <- state.breakout
|
||||
} yield {
|
||||
val rooms = breakoutModel.rooms.values.toVector map { r =>
|
||||
new BreakoutRoomInfo(r.name, r.externalId, r.id, r.sequence, r.freeJoin)
|
||||
new BreakoutRoomInfo(r.name, r.externalId, r.id, r.sequence, r.shortName, r.isDefaultName, r.freeJoin)
|
||||
}
|
||||
val ready = breakoutModel.hasAllStarted()
|
||||
broadcastEvent(rooms, ready)
|
||||
|
@ -47,7 +47,7 @@ trait CreateBreakoutRoomsCmdMsgHdlr extends RightsManagementTrait {
|
||||
val (internalId, externalId) = BreakoutRoomsUtil.createMeetingIds(liveMeeting.props.meetingProp.intId, i)
|
||||
val voiceConf = BreakoutRoomsUtil.createVoiceConfId(liveMeeting.props.voiceProp.voiceConf, i)
|
||||
|
||||
val breakout = BreakoutModel.create(parentId, internalId, externalId, room.name, room.sequence, room.freeJoin, voiceConf, room.users)
|
||||
val breakout = BreakoutModel.create(parentId, internalId, externalId, room.name, room.sequence, room.shortName, room.isDefaultName, room.freeJoin, voiceConf, room.users)
|
||||
rooms = rooms + (breakout.id -> breakout)
|
||||
}
|
||||
|
||||
@ -56,6 +56,8 @@ trait CreateBreakoutRoomsCmdMsgHdlr extends RightsManagementTrait {
|
||||
breakout.id, breakout.name,
|
||||
liveMeeting.props.meetingProp.intId,
|
||||
breakout.sequence,
|
||||
breakout.shortName,
|
||||
breakout.isDefaultName,
|
||||
breakout.freeJoin,
|
||||
liveMeeting.props.voiceProp.dialNumber,
|
||||
breakout.voiceConf,
|
||||
|
@ -0,0 +1,27 @@
|
||||
package org.bigbluebutton.core.apps.breakout
|
||||
|
||||
import org.bigbluebutton.core.api.{ ExtendBreakoutRoomTimeInternalMsg }
|
||||
import org.bigbluebutton.core.domain.{ MeetingState2x }
|
||||
import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter }
|
||||
|
||||
trait ExtendBreakoutRoomTimeInternalMsgHdlr {
|
||||
this: MeetingActor =>
|
||||
|
||||
val outGW: OutMsgRouter
|
||||
|
||||
def handleExtendBreakoutRoomTimeInternalMsgHdlr(msg: ExtendBreakoutRoomTimeInternalMsg, state: MeetingState2x): MeetingState2x = {
|
||||
|
||||
val breakoutModel = for {
|
||||
model <- state.breakout
|
||||
} yield {
|
||||
val updatedBreakoutModel = model.extendTime(msg.extendTimeInMinutes)
|
||||
updatedBreakoutModel
|
||||
}
|
||||
|
||||
breakoutModel match {
|
||||
case Some(model) => state.update(Some(model))
|
||||
case None => state
|
||||
}
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,83 @@
|
||||
package org.bigbluebutton.core.apps.breakout
|
||||
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.api.{ ExtendBreakoutRoomTimeInternalMsg, SendTimeRemainingAuditInternalMsg }
|
||||
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
|
||||
import org.bigbluebutton.core.bus.BigBlueButtonEvent
|
||||
import org.bigbluebutton.core.domain.MeetingState2x
|
||||
import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter }
|
||||
import org.bigbluebutton.core.util.TimeUtil
|
||||
|
||||
trait ExtendBreakoutRoomsTimeMsgHdlr extends RightsManagementTrait {
|
||||
this: MeetingActor =>
|
||||
|
||||
val outGW: OutMsgRouter
|
||||
|
||||
def handleExtendBreakoutRoomsTimeMsg(msg: ExtendBreakoutRoomsTimeReqMsg, state: MeetingState2x): MeetingState2x = {
|
||||
|
||||
if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "No permission to extend time for breakout rooms for meeting."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
|
||||
state
|
||||
} else if (msg.body.extendTimeInMinutes <= 0) {
|
||||
log.error("Error while trying to extend {} minutes for breakout rooms time in meeting {}. Only positive values are allowed!", msg.body.extendTimeInMinutes, props.meetingProp.intId)
|
||||
state
|
||||
} else {
|
||||
val updatedModel = for {
|
||||
breakoutModel <- state.breakout
|
||||
startedOn <- breakoutModel.startedOn
|
||||
} yield {
|
||||
val breakoutRoomEndTime = TimeUtil.millisToSeconds(startedOn) + TimeUtil.minutesToSeconds(breakoutModel.durationInMinutes)
|
||||
val breakoutRoomSecsRemaining = breakoutRoomEndTime - TimeUtil.millisToSeconds(System.currentTimeMillis())
|
||||
|
||||
var isExtendTimeHigherThanMeetingRemaining = false
|
||||
|
||||
if (state.expiryTracker.durationInMs > 0) {
|
||||
val mainRoomEndTime = state.expiryTracker.startedOnInMs + state.expiryTracker.durationInMs
|
||||
val mainRoomSecsRemaining = TimeUtil.millisToSeconds(mainRoomEndTime - TimeUtil.timeNowInMs())
|
||||
val mainRoomTimeRemainingInMinutes = mainRoomSecsRemaining / 60
|
||||
|
||||
//Avoid breakout room end later than main room
|
||||
//Keep 5 seconds of margin to finish breakout room and send informations to parent meeting
|
||||
if (breakoutRoomSecsRemaining + TimeUtil.minutesToSeconds(msg.body.extendTimeInMinutes) > (mainRoomSecsRemaining - 5)) {
|
||||
log.error("Error while trying to extend {} minutes for breakout rooms time in meeting {}. Parent meeting will end up in {} minutes!", msg.body.extendTimeInMinutes, props.meetingProp.intId, mainRoomTimeRemainingInMinutes)
|
||||
isExtendTimeHigherThanMeetingRemaining = true
|
||||
}
|
||||
}
|
||||
|
||||
if (isExtendTimeHigherThanMeetingRemaining) {
|
||||
breakoutModel
|
||||
} else {
|
||||
breakoutModel.rooms.values.foreach { room =>
|
||||
eventBus.publish(BigBlueButtonEvent(room.id, ExtendBreakoutRoomTimeInternalMsg(props.breakoutProps.parentId, room.id, msg.body.extendTimeInMinutes)))
|
||||
}
|
||||
log.debug("Extending {} minutes for breakout rooms time in meeting {}", msg.body.extendTimeInMinutes, props.meetingProp.intId)
|
||||
breakoutModel.extendTime(msg.body.extendTimeInMinutes)
|
||||
}
|
||||
}
|
||||
|
||||
val event = buildExtendBreakoutRoomsTimeEvtMsg(msg.body.extendTimeInMinutes)
|
||||
outGW.send(event)
|
||||
|
||||
//Force Update time remaining in the clients
|
||||
eventBus.publish(BigBlueButtonEvent(props.meetingProp.intId, SendTimeRemainingAuditInternalMsg(props.meetingProp.intId)))
|
||||
|
||||
updatedModel match {
|
||||
case Some(model) => state.update(Some(model))
|
||||
case None => state
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def buildExtendBreakoutRoomsTimeEvtMsg(extendTimeInMinutes: Int): BbbCommonEnvCoreMsg = {
|
||||
val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
|
||||
val envelope = BbbCoreEnvelope(ExtendBreakoutRoomsTimeEvtMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(ExtendBreakoutRoomsTimeEvtMsg.NAME, liveMeeting.props.meetingProp.intId, "not-used")
|
||||
|
||||
val body = ExtendBreakoutRoomsTimeEvtMsgBody(props.meetingProp.intId, extendTimeInMinutes)
|
||||
val event = ExtendBreakoutRoomsTimeEvtMsg(header, body)
|
||||
BbbCommonEnvCoreMsg(envelope, event)
|
||||
}
|
||||
|
||||
}
|
@ -4,6 +4,7 @@ import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.domain.MeetingState2x
|
||||
import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter }
|
||||
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
|
||||
import org.bigbluebutton.core.models.{ Users2x, Roles }
|
||||
|
||||
trait RequestBreakoutJoinURLReqMsgHdlr extends RightsManagementTrait {
|
||||
this: MeetingActor =>
|
||||
@ -19,15 +20,22 @@ trait RequestBreakoutJoinURLReqMsgHdlr extends RightsManagementTrait {
|
||||
for {
|
||||
model <- state.breakout
|
||||
room <- model.find(msg.body.breakoutId)
|
||||
requesterUser <- Users2x.findWithIntId(liveMeeting.users2x, msg.header.userId)
|
||||
} yield {
|
||||
BreakoutHdlrHelpers.sendJoinURL(
|
||||
liveMeeting,
|
||||
outGW,
|
||||
msg.body.userId,
|
||||
room.externalId,
|
||||
room.sequence.toString(),
|
||||
room.id
|
||||
)
|
||||
if (requesterUser.role == Roles.MODERATOR_ROLE || room.freeJoin) {
|
||||
BreakoutHdlrHelpers.sendJoinURL(
|
||||
liveMeeting,
|
||||
outGW,
|
||||
msg.body.userId,
|
||||
room.externalId,
|
||||
room.sequence.toString(),
|
||||
room.id
|
||||
)
|
||||
} else {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "No permission to request breakout room URL for meeting."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,10 +1,11 @@
|
||||
package org.bigbluebutton.core.apps.externalvideo
|
||||
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait, ExternalVideoModel }
|
||||
import org.bigbluebutton.core.bus.MessageBus
|
||||
import org.bigbluebutton.core.running.{ LiveMeeting }
|
||||
|
||||
trait StartExternalVideoPubMsgHdlr {
|
||||
trait StartExternalVideoPubMsgHdlr extends RightsManagementTrait {
|
||||
this: ExternalVideoApp2x =>
|
||||
|
||||
def handle(msg: StartExternalVideoPubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
|
||||
@ -22,6 +23,13 @@ trait StartExternalVideoPubMsgHdlr {
|
||||
bus.outGW.send(msgEvent)
|
||||
}
|
||||
|
||||
broadcastEvent(msg)
|
||||
if (permissionFailed(PermissionCheck.GUEST_LEVEL, PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "You need to be the presenter to start external videos"
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
|
||||
} else {
|
||||
ExternalVideoModel.setURL(liveMeeting.externalVideoModel, msg.body.externalVideoUrl)
|
||||
broadcastEvent(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,11 @@
|
||||
package org.bigbluebutton.core.apps.externalvideo
|
||||
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait, ExternalVideoModel }
|
||||
import org.bigbluebutton.core.bus.MessageBus
|
||||
import org.bigbluebutton.core.running.{ LiveMeeting }
|
||||
|
||||
trait StopExternalVideoPubMsgHdlr {
|
||||
trait StopExternalVideoPubMsgHdlr extends RightsManagementTrait {
|
||||
this: ExternalVideoApp2x =>
|
||||
|
||||
def handle(msg: StopExternalVideoPubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
|
||||
@ -22,6 +23,13 @@ trait StopExternalVideoPubMsgHdlr {
|
||||
bus.outGW.send(msgEvent)
|
||||
}
|
||||
|
||||
broadcastEvent()
|
||||
if (permissionFailed(PermissionCheck.GUEST_LEVEL, PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "You need to be the presenter to stop external video"
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
|
||||
} else {
|
||||
ExternalVideoModel.clear(liveMeeting.externalVideoModel)
|
||||
broadcastEvent()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,11 @@
|
||||
package org.bigbluebutton.core.apps.externalvideo
|
||||
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
|
||||
import org.bigbluebutton.core.bus.MessageBus
|
||||
import org.bigbluebutton.core.running.{ LiveMeeting }
|
||||
|
||||
trait UpdateExternalVideoPubMsgHdlr {
|
||||
trait UpdateExternalVideoPubMsgHdlr extends RightsManagementTrait {
|
||||
|
||||
def handle(msg: UpdateExternalVideoPubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
|
||||
def broadcastEvent(msg: UpdateExternalVideoPubMsg) {
|
||||
@ -18,6 +19,12 @@ trait UpdateExternalVideoPubMsgHdlr {
|
||||
bus.outGW.send(msgEvent)
|
||||
}
|
||||
|
||||
broadcastEvent(msg)
|
||||
if (permissionFailed(PermissionCheck.GUEST_LEVEL, PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "You need to be the presenter to update external video"
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
|
||||
} else {
|
||||
broadcastEvent(msg)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package org.bigbluebutton.core.apps.layout
|
||||
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.models.Layouts
|
||||
import org.bigbluebutton.core.models.{ Layouts, LayoutsType }
|
||||
import org.bigbluebutton.core.running.OutMsgRouter
|
||||
import org.bigbluebutton.core2.MeetingStatus2x
|
||||
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
|
||||
@ -17,9 +17,13 @@ trait BroadcastLayoutMsgHdlr extends RightsManagementTrait {
|
||||
val reason = "No permission to broadcast layout to meeting."
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
|
||||
} else {
|
||||
Layouts.setCurrentLayout(liveMeeting.layouts, msg.body.layout, msg.header.userId)
|
||||
if (LayoutsType.layoutsType.contains(msg.body.layout)) {
|
||||
val newlayout = LayoutsType.layoutsType.getOrElse(msg.body.layout, "")
|
||||
|
||||
sendBroadcastLayoutEvtMsg(msg.header.userId)
|
||||
Layouts.setCurrentLayout(liveMeeting.layouts, newlayout, msg.header.userId)
|
||||
|
||||
sendBroadcastLayoutEvtMsg(msg.header.userId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -23,12 +23,12 @@ trait RespondToPollReqMsgHdlr {
|
||||
bus.outGW.send(msgEvent)
|
||||
}
|
||||
|
||||
def broadcastUserRespondedToPollRecordMsg(msg: RespondToPollReqMsg, pollId: String, answerIds: Seq[Int]): Unit = {
|
||||
def broadcastUserRespondedToPollRecordMsg(msg: RespondToPollReqMsg, pollId: String, answerIds: Seq[Int], answer: String, isSecret: Boolean): Unit = {
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, msg.header.userId)
|
||||
val envelope = BbbCoreEnvelope(UserRespondedToPollRecordMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(UserRespondedToPollRecordMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
|
||||
|
||||
val body = UserRespondedToPollRecordMsgBody(pollId, answerIds)
|
||||
val body = UserRespondedToPollRecordMsgBody(pollId, answerIds, answer, isSecret)
|
||||
val event = UserRespondedToPollRecordMsg(header, body)
|
||||
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||
bus.outGW.send(msgEvent)
|
||||
@ -50,7 +50,12 @@ trait RespondToPollReqMsgHdlr {
|
||||
msg.body.questionId, msg.body.answerIds, liveMeeting)
|
||||
} yield {
|
||||
broadcastPollUpdatedEvent(msg, pollId, updatedPoll)
|
||||
broadcastUserRespondedToPollRecordMsg(msg, pollId, msg.body.answerIds)
|
||||
for {
|
||||
poll <- Polls.getPoll(pollId, liveMeeting.polls)
|
||||
} yield {
|
||||
val answerText = poll.questions(0).answers.get(msg.body.answerId).key
|
||||
broadcastUserRespondedToPollRecordMsg(msg, pollId, msg.body.answerId, answerText, poll.isSecret)
|
||||
}
|
||||
|
||||
for {
|
||||
presenter <- Users2x.findPresenter(liveMeeting.users2x)
|
||||
|
@ -17,7 +17,7 @@ trait StartCustomPollReqMsgHdlr extends RightsManagementTrait {
|
||||
val envelope = BbbCoreEnvelope(PollStartedEvtMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(PollStartedEvtMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
|
||||
|
||||
val body = PollStartedEvtMsgBody(msg.header.userId, poll.id, msg.body.pollType, msg.body.question, poll)
|
||||
val body = PollStartedEvtMsgBody(msg.header.userId, poll.id, msg.body.pollType, msg.body.secretPoll, msg.body.question, poll)
|
||||
val event = PollStartedEvtMsg(header, body)
|
||||
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||
bus.outGW.send(msgEvent)
|
||||
@ -29,7 +29,7 @@ trait StartCustomPollReqMsgHdlr extends RightsManagementTrait {
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
|
||||
} else {
|
||||
for {
|
||||
pvo <- Polls.handleStartCustomPollReqMsg(state, msg.header.userId, msg.body.pollId, msg.body.pollType, msg.body.isMultipleResponse, msg.body.answers, msg.body.question, liveMeeting)
|
||||
pvo <- Polls.handleStartCustomPollReqMsg(state, msg.header.userId, msg.body.pollId, msg.body.pollType, msg.body.secretPoll, msg.body.isMultipleResponse, msg.body.answers, msg.body.question, liveMeeting)
|
||||
} yield {
|
||||
broadcastEvent(msg, pvo)
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ trait StartPollReqMsgHdlr extends RightsManagementTrait {
|
||||
val envelope = BbbCoreEnvelope(PollStartedEvtMsg.NAME, routing)
|
||||
val header = BbbClientMsgHeader(PollStartedEvtMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
|
||||
|
||||
val body = PollStartedEvtMsgBody(msg.header.userId, poll.id, msg.body.pollType, msg.body.question, poll)
|
||||
val body = PollStartedEvtMsgBody(msg.header.userId, poll.id, msg.body.pollType, msg.body.secretPoll, msg.body.question, poll)
|
||||
val event = PollStartedEvtMsg(header, body)
|
||||
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
|
||||
bus.outGW.send(msgEvent)
|
||||
@ -30,7 +30,7 @@ trait StartPollReqMsgHdlr extends RightsManagementTrait {
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
|
||||
} else {
|
||||
for {
|
||||
pvo <- Polls.handleStartPollReqMsg(state, msg.header.userId, msg.body.pollId, msg.body.pollType, msg.body.question, msg.body.isMultipleResponse, liveMeeting)
|
||||
pvo <- Polls.handleStartPollReqMsg(state, msg.header.userId, msg.body.pollId, msg.body.pollType, msg.body.secretPoll, msg.body.isMultipleResponse, msg.body.question, liveMeeting)
|
||||
} yield {
|
||||
broadcastEvent(msg, pvo)
|
||||
}
|
||||
|
@ -17,6 +17,7 @@ trait SetPresenterInPodReqMsgHdlr {
|
||||
): MeetingState2x = {
|
||||
if (msg.body.podId == PresentationPod.DEFAULT_PRESENTATION_POD) {
|
||||
// Swith presenter as default presenter pod has changed.
|
||||
log.info("Presenter pod change will trigger a presenter change")
|
||||
AssignPresenterActionHandler.handleAction(liveMeeting, bus.outGW, msg.header.userId, msg.body.nextPresenterId)
|
||||
}
|
||||
SetPresenterInPodActionHandler.handleAction(state, liveMeeting, bus.outGW, msg.header.userId, msg.body.podId, msg.body.nextPresenterId)
|
||||
@ -74,4 +75,4 @@ object SetPresenterInPodActionHandler extends RightsManagementTrait {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,7 @@
|
||||
package org.bigbluebutton.core.apps.screenshare
|
||||
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.apps.ScreenshareModel
|
||||
import org.bigbluebutton.core.apps.{ ScreenshareModel, ExternalVideoModel }
|
||||
import org.bigbluebutton.core.bus.MessageBus
|
||||
import org.bigbluebutton.core.running.LiveMeeting
|
||||
|
||||
@ -34,6 +34,9 @@ trait ScreenshareRtmpBroadcastStartedVoiceConfEvtMsgHdlr {
|
||||
|
||||
// only valid if not broadcasting yet
|
||||
if (!ScreenshareModel.isBroadcastingRTMP(liveMeeting.screenshareModel)) {
|
||||
// Stop external video if it's running
|
||||
ExternalVideoModel.stop(bus.outGW, liveMeeting)
|
||||
|
||||
ScreenshareModel.setRTMPBroadcastingUrl(liveMeeting.screenshareModel, msg.body.stream)
|
||||
ScreenshareModel.broadcastingRTMPStarted(liveMeeting.screenshareModel)
|
||||
ScreenshareModel.setScreenshareVideoWidth(liveMeeting.screenshareModel, msg.body.vidWidth)
|
||||
|
@ -2,6 +2,7 @@ package org.bigbluebutton.core.apps.users
|
||||
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.apps.presentationpod.SetPresenterInPodActionHandler
|
||||
import org.bigbluebutton.core.apps.{ ExternalVideoModel }
|
||||
import org.bigbluebutton.core.models.{ PresentationPod, UserState, Users2x }
|
||||
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
|
||||
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
|
||||
@ -14,6 +15,7 @@ trait AssignPresenterReqMsgHdlr extends RightsManagementTrait {
|
||||
val outGW: OutMsgRouter
|
||||
|
||||
def handleAssignPresenterReqMsg(msg: AssignPresenterReqMsg, state: MeetingState2x): MeetingState2x = {
|
||||
log.info("handleAssignPresenterReqMsg: assignedBy={} newPresenterId={}", msg.body.assignedBy, msg.body.newPresenterId)
|
||||
AssignPresenterActionHandler.handleAction(liveMeeting, outGW, msg.body.assignedBy, msg.body.newPresenterId)
|
||||
|
||||
// Change presenter of default presentation pod
|
||||
@ -67,8 +69,13 @@ object AssignPresenterActionHandler extends RightsManagementTrait {
|
||||
for {
|
||||
oldPres <- Users2x.findPresenter(liveMeeting.users2x)
|
||||
} yield {
|
||||
Users2x.makeNotPresenter(liveMeeting.users2x, oldPres.intId)
|
||||
broadcastOldPresenterChange(oldPres)
|
||||
if (oldPres.intId != newPresenterId) {
|
||||
// Stop external video if it's running
|
||||
ExternalVideoModel.stop(outGW, liveMeeting)
|
||||
|
||||
Users2x.makeNotPresenter(liveMeeting.users2x, oldPres.intId)
|
||||
broadcastOldPresenterChange(oldPres)
|
||||
}
|
||||
}
|
||||
|
||||
for {
|
||||
@ -79,4 +86,4 @@ object AssignPresenterActionHandler extends RightsManagementTrait {
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,22 +3,29 @@ package org.bigbluebutton.core.apps.users
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
|
||||
import org.bigbluebutton.core2.MeetingStatus2x
|
||||
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
|
||||
|
||||
trait UpdateWebcamsOnlyForModeratorCmdMsgHdlr {
|
||||
trait UpdateWebcamsOnlyForModeratorCmdMsgHdlr extends RightsManagementTrait {
|
||||
this: UsersApp =>
|
||||
|
||||
val liveMeeting: LiveMeeting
|
||||
val outGW: OutMsgRouter
|
||||
|
||||
def handleUpdateWebcamsOnlyForModeratorCmdMsg(msg: UpdateWebcamsOnlyForModeratorCmdMsg) {
|
||||
log.info("Change webcams only for moderator status. meetingId=" + liveMeeting.props.meetingProp.intId + " webcamsOnlyForModeratorrecording=" + msg.body.webcamsOnlyForModerator)
|
||||
if (MeetingStatus2x.webcamsOnlyForModeratorEnabled(liveMeeting.status) != msg.body.webcamsOnlyForModerator) {
|
||||
MeetingStatus2x.setWebcamsOnlyForModerator(liveMeeting.status, msg.body.webcamsOnlyForModerator)
|
||||
|
||||
val event = buildWebcamsOnlyForModeratorChangedEvtMsg(liveMeeting.props.meetingProp.intId, msg.body.setBy, msg.body.webcamsOnlyForModerator)
|
||||
outGW.send(event)
|
||||
if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
val reason = "No permission to change lock settings"
|
||||
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
|
||||
} else {
|
||||
log.info("Change webcams only for moderator status. meetingId=" + liveMeeting.props.meetingProp.intId + " webcamsOnlyForModeratorrecording=" + msg.body.webcamsOnlyForModerator)
|
||||
if (MeetingStatus2x.webcamsOnlyForModeratorEnabled(liveMeeting.status) != msg.body.webcamsOnlyForModerator) {
|
||||
MeetingStatus2x.setWebcamsOnlyForModerator(liveMeeting.status, msg.body.webcamsOnlyForModerator)
|
||||
|
||||
val event = buildWebcamsOnlyForModeratorChangedEvtMsg(liveMeeting.props.meetingProp.intId, msg.body.setBy, msg.body.webcamsOnlyForModerator)
|
||||
outGW.send(event)
|
||||
}
|
||||
}
|
||||
|
||||
def buildWebcamsOnlyForModeratorChangedEvtMsg(meetingId: String, userId: String, webcamsOnlyForModerator: Boolean): BbbCommonEnvCoreMsg = {
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, meetingId, userId)
|
||||
val envelope = BbbCoreEnvelope(WebcamsOnlyForModeratorChangedEvtMsg.NAME, routing)
|
||||
@ -29,4 +36,4 @@ trait UpdateWebcamsOnlyForModeratorCmdMsgHdlr {
|
||||
BbbCommonEnvCoreMsg(envelope, event)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -3,6 +3,7 @@ package org.bigbluebutton.core.apps.users
|
||||
import akka.actor.ActorContext
|
||||
import akka.event.Logging
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.apps.{ ExternalVideoModel }
|
||||
import org.bigbluebutton.core.bus.InternalEventBus
|
||||
import org.bigbluebutton.core.models._
|
||||
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
|
||||
@ -53,11 +54,15 @@ object UsersApp {
|
||||
}
|
||||
|
||||
def automaticallyAssignPresenter(outGW: OutMsgRouter, liveMeeting: LiveMeeting): Unit = {
|
||||
// Stop external video if it's running
|
||||
ExternalVideoModel.stop(outGW, liveMeeting)
|
||||
|
||||
val meetingId = liveMeeting.props.meetingProp.intId
|
||||
for {
|
||||
moderator <- Users2x.findModerator(liveMeeting.users2x)
|
||||
newPresenter <- Users2x.makePresenter(liveMeeting.users2x, moderator.intId)
|
||||
} yield {
|
||||
// println(s"automaticallyAssignPresenter: moderator=${moderator} newPresenter=${newPresenter.intId}");
|
||||
sendPresenterAssigned(outGW, meetingId, newPresenter.intId, newPresenter.name, newPresenter.intId)
|
||||
}
|
||||
}
|
||||
@ -111,6 +116,7 @@ object UsersApp {
|
||||
sendUserEjectedMessageToClient(outGW, meetingId, userId, ejectedBy, reason, reasonCode)
|
||||
sendUserLeftMeetingToAllClients(outGW, meetingId, userId)
|
||||
if (user.presenter) {
|
||||
// println(s"ejectUserFromMeeting will cause a automaticallyAssignPresenter for user=${user}")
|
||||
automaticallyAssignPresenter(outGW, liveMeeting)
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,8 @@ case class BreakoutRoom2x(
|
||||
name: String,
|
||||
parentId: String,
|
||||
sequence: Int,
|
||||
shortName: String,
|
||||
isDefaultName: Boolean,
|
||||
freeJoin: Boolean,
|
||||
voiceConf: String,
|
||||
assignedUsers: Vector[String],
|
||||
|
@ -32,4 +32,5 @@ object MeetingEndReason {
|
||||
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"
|
||||
val ENDED_DUE_TO_NO_MODERATOR = "ENDED_DUE_TO_NO_MODERATOR"
|
||||
}
|
||||
|
@ -3,14 +3,18 @@ package org.bigbluebutton.core.domain
|
||||
case class MeetingExpiryTracker(
|
||||
startedOnInMs: Long,
|
||||
userHasJoined: Boolean,
|
||||
moderatorHasJoined: Boolean,
|
||||
isBreakout: Boolean,
|
||||
lastUserLeftOnInMs: Option[Long],
|
||||
lastModeratorLeftOnInMs: Long,
|
||||
durationInMs: Long,
|
||||
meetingExpireIfNoUserJoinedInMs: Long,
|
||||
meetingExpireWhenLastUserLeftInMs: Long,
|
||||
userInactivityInspectTimerInMs: Long,
|
||||
userInactivityThresholdInMs: Long,
|
||||
userActivitySignResponseDelayInMs: Long
|
||||
userActivitySignResponseDelayInMs: Long,
|
||||
endWhenNoModerator: Boolean,
|
||||
endWhenNoModeratorDelayInMs: Long
|
||||
) {
|
||||
def setUserHasJoined(): MeetingExpiryTracker = {
|
||||
if (!userHasJoined) {
|
||||
@ -24,6 +28,18 @@ case class MeetingExpiryTracker(
|
||||
copy(lastUserLeftOnInMs = Some(timestampInMs))
|
||||
}
|
||||
|
||||
def setModeratorHasJoined(): MeetingExpiryTracker = {
|
||||
if (!moderatorHasJoined) {
|
||||
copy(moderatorHasJoined = true, lastModeratorLeftOnInMs = 0)
|
||||
} else {
|
||||
copy(lastModeratorLeftOnInMs = 0)
|
||||
}
|
||||
}
|
||||
|
||||
def setLastModeratorLeftOn(timestampInMs: Long): MeetingExpiryTracker = {
|
||||
copy(lastModeratorLeftOnInMs = timestampInMs)
|
||||
}
|
||||
|
||||
def hasMeetingExpiredAfterLastUserLeft(timestampInMs: Long): Boolean = {
|
||||
val expire = for {
|
||||
lastUserLeftOn <- lastUserLeftOnInMs
|
||||
|
@ -1,5 +1,7 @@
|
||||
package org.bigbluebutton.core.models
|
||||
|
||||
import scala.collection.mutable.Map
|
||||
|
||||
object Layouts {
|
||||
def setCurrentLayout(instance: Layouts, layout: String, setBy: String) {
|
||||
instance.currentLayout = layout
|
||||
@ -29,3 +31,12 @@ class Layouts {
|
||||
// this is not being set by the client, and we need to apply the layouts to all users, not just viewers, so will keep the default value of this as false
|
||||
private var affectViewersOnly = true
|
||||
}
|
||||
|
||||
object LayoutsType {
|
||||
val layoutsType = Map(
|
||||
"custom" -> "CUSTOM_LAYOUT",
|
||||
"smart" -> "SMART_LAYOUT",
|
||||
"presentationFocus" -> "PRESENTATION_FOCUS",
|
||||
"videoFocus" -> "VIDEO_FOCUS"
|
||||
)
|
||||
}
|
||||
|
@ -11,14 +11,14 @@ import org.bigbluebutton.core.running.LiveMeeting
|
||||
|
||||
object Polls {
|
||||
|
||||
def handleStartPollReqMsg(state: MeetingState2x, userId: String, pollId: String, pollType: String, question: String,
|
||||
multiResponse: Boolean, lm: LiveMeeting): Option[SimplePollOutVO] = {
|
||||
def handleStartPollReqMsg(state: MeetingState2x, userId: String, pollId: String, pollType: String, secretPoll: Boolean, multiResponse: Boolean, questionText: String,
|
||||
lm: LiveMeeting): Option[SimplePollOutVO] = {
|
||||
|
||||
def createPoll(stampedPollId: String): Option[Poll] = {
|
||||
val numRespondents: Int = Users2x.numUsers(lm.users2x) - 1 // subtract the presenter
|
||||
|
||||
for {
|
||||
poll <- PollFactory.createPoll(stampedPollId, pollType, multiResponse, numRespondents, None)
|
||||
poll <- PollFactory.createPoll(stampedPollId, pollType, multiResponse, numRespondents, None, Some(questionText), secretPoll)
|
||||
} yield {
|
||||
lm.polls.save(poll)
|
||||
poll
|
||||
@ -68,7 +68,9 @@ object Polls {
|
||||
stoppedPoll match {
|
||||
case None => {
|
||||
for {
|
||||
curPoll <- getRunningPollThatStartsWith("public", lm.polls)
|
||||
// Assuming there's only one running poll at a time, fallback to the
|
||||
// current running poll without indexing by a presentation page.
|
||||
curPoll <- getRunningPoll(lm.polls)
|
||||
} yield {
|
||||
stopPoll(curPoll.id, lm.polls)
|
||||
curPoll.id
|
||||
@ -166,13 +168,13 @@ object Polls {
|
||||
}
|
||||
}
|
||||
|
||||
def handleStartCustomPollReqMsg(state: MeetingState2x, requesterId: String, pollId: String, pollType: String,
|
||||
multiResponse: Boolean, answers: Seq[String], question: String, lm: LiveMeeting): Option[SimplePollOutVO] = {
|
||||
def handleStartCustomPollReqMsg(state: MeetingState2x, requesterId: String, pollId: String, pollType: String, secretPoll: Boolean, multiResponse: Boolean,
|
||||
answers: Seq[String], questionText: String, lm: LiveMeeting): Option[SimplePollOutVO] = {
|
||||
|
||||
def createPoll(stampedPollId: String): Option[Poll] = {
|
||||
val numRespondents: Int = Users2x.numUsers(lm.users2x) - 1 // subtract the presenter
|
||||
for {
|
||||
poll <- PollFactory.createPoll(stampedPollId, pollType, multiResponse, numRespondents, Some(answers))
|
||||
poll <- PollFactory.createPoll(stampedPollId, pollType, numRespondents, Some(answers), Some(questionText), secretPoll, multiResponse)
|
||||
} yield {
|
||||
lm.polls.save(poll)
|
||||
poll
|
||||
@ -255,6 +257,7 @@ object Polls {
|
||||
shape += "numRespondents" -> new Integer(result.numRespondents)
|
||||
shape += "numResponders" -> new Integer(result.numResponders)
|
||||
shape += "type" -> WhiteboardKeyUtil.POLL_RESULT_TYPE
|
||||
shape += "pollType" -> result.questionType
|
||||
shape += "id" -> result.id
|
||||
shape += "status" -> WhiteboardKeyUtil.DRAW_END_STATUS
|
||||
|
||||
@ -268,7 +271,7 @@ object Polls {
|
||||
|
||||
// Limit the number of answers displayed to minimize
|
||||
// squishing the display.
|
||||
if (sorted_answers.length < 7) {
|
||||
if (sorted_answers.length <= 7) {
|
||||
sorted_answers.foreach(ans => {
|
||||
answers += SimpleVoteOutVO(ans.id, ans.key, ans.numVotes)
|
||||
})
|
||||
@ -306,6 +309,12 @@ object Polls {
|
||||
shape.toMap
|
||||
}
|
||||
|
||||
def getRunningPoll(polls: Polls): Option[PollVO] = {
|
||||
for {
|
||||
poll <- polls.polls.values find { poll => poll.isRunning() }
|
||||
} yield poll.toPollVO()
|
||||
}
|
||||
|
||||
def getRunningPollThatStartsWith(pollId: String, polls: Polls): Option[PollVO] = {
|
||||
for {
|
||||
poll <- polls.polls.values find { poll => poll.id.startsWith(pollId) && poll.isRunning() }
|
||||
@ -435,7 +444,7 @@ object PollType {
|
||||
val CustomPollType = "CUSTOM"
|
||||
val LetterPollType = "A-"
|
||||
val NumberPollType = "1-"
|
||||
val ResponsePollType = "RP"
|
||||
val ResponsePollType = "R-"
|
||||
}
|
||||
|
||||
object PollFactory {
|
||||
@ -443,35 +452,35 @@ object PollFactory {
|
||||
val LetterArray = Array("A", "B", "C", "D", "E", "F")
|
||||
val NumberArray = Array("1", "2", "3", "4", "5", "6")
|
||||
|
||||
private def processYesNoPollType(qType: String, multiResponse: Boolean): Question = {
|
||||
private def processYesNoPollType(qType: String, text: Option[String], multiResponse: Boolean): Question = {
|
||||
val answers = new ArrayBuffer[Answer];
|
||||
|
||||
answers += new Answer(0, "Yes", Some("Yes"))
|
||||
answers += new Answer(1, "No", Some("No"))
|
||||
|
||||
new Question(0, PollType.YesNoPollType, multiResponse, None, answers)
|
||||
new Question(0, PollType.YesNoPollType, false, text, answers, multiResponse)
|
||||
}
|
||||
|
||||
private def processYesNoAbstentionPollType(qType: String, multiResponse: Boolean): Question = {
|
||||
private def processYesNoAbstentionPollType(qType: String, text: Option[String], multiResponse: Boolean): Question = {
|
||||
val answers = new ArrayBuffer[Answer]
|
||||
|
||||
answers += new Answer(0, "Yes", Some("Yes"))
|
||||
answers += new Answer(1, "No", Some("No"))
|
||||
answers += new Answer(2, "Abstention", Some("Abstention"))
|
||||
|
||||
new Question(0, PollType.YesNoAbstentionPollType, multiResponse, None, answers)
|
||||
new Question(0, PollType.YesNoAbstentionPollType, false, text, answers, multiResponse)
|
||||
}
|
||||
|
||||
private def processTrueFalsePollType(qType: String, multiResponse: Boolean): Question = {
|
||||
private def processTrueFalsePollType(qType: String, text: Option[String], multiResponse: Boolean): Question = {
|
||||
val answers = new ArrayBuffer[Answer];
|
||||
|
||||
answers += new Answer(0, "True", Some("True"))
|
||||
answers += new Answer(1, "False", Some("False"))
|
||||
|
||||
new Question(0, PollType.TrueFalsePollType, multiResponse, None, answers)
|
||||
new Question(0, PollType.TrueFalsePollType, false, text, answers, multiResponse)
|
||||
}
|
||||
|
||||
private def processLetterPollType(qType: String, multiResponse: Boolean): Option[Question] = {
|
||||
private def processLetterPollType(qType: String, multiResponse: Boolean, text: Option[String]): Option[Question] = {
|
||||
val q = qType.split('-')
|
||||
val numQs = q(1).toInt
|
||||
|
||||
@ -481,7 +490,7 @@ object PollFactory {
|
||||
val answers = new ArrayBuffer[Answer];
|
||||
for (i <- 0 until numQs) {
|
||||
answers += new Answer(i, LetterArray(i), Some(LetterArray(i)))
|
||||
val question = new Question(0, PollType.LetterPollType, multiResponse, None, answers)
|
||||
val question = new Question(0, PollType.LetterPollType, multiResponse, text, answers)
|
||||
questionOption = Some(question)
|
||||
}
|
||||
}
|
||||
@ -489,7 +498,7 @@ object PollFactory {
|
||||
questionOption
|
||||
}
|
||||
|
||||
private def processNumberPollType(qType: String, multiResponse: Boolean): Option[Question] = {
|
||||
private def processNumberPollType(qType: String, multiResponse: Boolean, text: Option[String]): Option[Question] = {
|
||||
val q = qType.split('-')
|
||||
val numQs = q(1).toInt
|
||||
|
||||
@ -499,7 +508,7 @@ object PollFactory {
|
||||
val answers = new ArrayBuffer[Answer];
|
||||
for (i <- 0 until numQs) {
|
||||
answers += new Answer(i, NumberArray(i), Some(NumberArray(i)))
|
||||
val question = new Question(0, PollType.NumberPollType, multiResponse, None, answers)
|
||||
val question = new Question(0, PollType.NumberPollType, multiResponse, text, answers)
|
||||
questionOption = Some(question)
|
||||
}
|
||||
}
|
||||
@ -515,58 +524,58 @@ object PollFactory {
|
||||
ans
|
||||
}
|
||||
|
||||
private def processCustomPollType(qType: String, multiResponse: Boolean, answers: Option[Seq[String]]): Option[Question] = {
|
||||
private def processCustomPollType(qType: String, multiResponse: Boolean, text: Option[String], answers: Option[Seq[String]]): Option[Question] = {
|
||||
var questionOption: Option[Question] = None
|
||||
|
||||
answers.foreach { ans =>
|
||||
val someAnswers = buildAnswers(ans)
|
||||
val question = new Question(0, PollType.CustomPollType, multiResponse, None, someAnswers)
|
||||
val question = new Question(0, PollType.CustomPollType, multiResponse, text, someAnswers)
|
||||
questionOption = Some(question)
|
||||
}
|
||||
|
||||
questionOption
|
||||
}
|
||||
|
||||
private def processResponsePollType(qType: String): Option[Question] = {
|
||||
private def processResponsePollType(qType: String, text: Option[String]): Option[Question] = {
|
||||
var questionOption: Option[Question] = None
|
||||
|
||||
val answers = new ArrayBuffer[Answer]
|
||||
val question = new Question(0, PollType.ResponsePollType, false, None, answers)
|
||||
val question = new Question(0, PollType.ResponsePollType, false, text, answers)
|
||||
questionOption = Some(question)
|
||||
|
||||
questionOption
|
||||
}
|
||||
|
||||
private def createQuestion(qType: String, multiResponse: Boolean, answers: Option[Seq[String]]): Option[Question] = {
|
||||
private def createQuestion(qType: String, multiResponse: Boolean, answers: Option[Seq[String]], text: Option[String]): Option[Question] = {
|
||||
|
||||
val qt = qType.toUpperCase()
|
||||
var questionOption: Option[Question] = None
|
||||
|
||||
if (qt.matches(PollType.YesNoPollType)) {
|
||||
questionOption = Some(processYesNoPollType(qt, multiResponse))
|
||||
questionOption = Some(processYesNoPollType(qt, text, multiResponse))
|
||||
} else if (qt.matches(PollType.YesNoAbstentionPollType)) {
|
||||
questionOption = Some(processYesNoAbstentionPollType(qt, multiResponse))
|
||||
questionOption = Some(processYesNoAbstentionPollType(qt, text, multiResponse))
|
||||
} else if (qt.matches(PollType.TrueFalsePollType)) {
|
||||
questionOption = Some(processTrueFalsePollType(qt, multiResponse))
|
||||
questionOption = Some(processTrueFalsePollType(qt, text, multiResponse))
|
||||
} else if (qt.matches(PollType.CustomPollType)) {
|
||||
questionOption = processCustomPollType(qt, multiResponse, answers)
|
||||
questionOption = processCustomPollType(qt, false, text, answers, multiResponse)
|
||||
} else if (qt.startsWith(PollType.LetterPollType)) {
|
||||
questionOption = processLetterPollType(qt, multiResponse)
|
||||
questionOption = processLetterPollType(qt, false, text, multiResponse)
|
||||
} else if (qt.startsWith(PollType.NumberPollType)) {
|
||||
questionOption = processNumberPollType(qt, multiResponse)
|
||||
questionOption = processNumberPollType(qt, false, text, multiResponse)
|
||||
} else if (qt.startsWith(PollType.ResponsePollType)) {
|
||||
questionOption = processResponsePollType(qt)
|
||||
questionOption = processResponsePollType(qt, text, multiResponse)
|
||||
}
|
||||
|
||||
questionOption
|
||||
}
|
||||
|
||||
def createPoll(id: String, pollType: String, multiResponse: Boolean, numRespondents: Int, answers: Option[Seq[String]]): Option[Poll] = {
|
||||
def createPoll(id: String, pollType: String, multiResponse: Boolean, numRespondents: Int, answers: Option[Seq[String]], questionText: Option[String], isSecret: Boolean): Option[Poll] = {
|
||||
var poll: Option[Poll] = None
|
||||
|
||||
createQuestion(pollType, multiResponse, answers) match {
|
||||
createQuestion(pollType, multiResponse, answers, questionText) match {
|
||||
case Some(question) => {
|
||||
poll = Some(new Poll(id, Array(question), numRespondents, None))
|
||||
poll = Some(new Poll(id, Array(question), numRespondents, None, isSecret))
|
||||
}
|
||||
case None => poll = None
|
||||
}
|
||||
@ -582,7 +591,7 @@ case class ResponderVO(responseID: String, user: Responder)
|
||||
case class ResponseOutVO(id: String, text: String, responders: Array[Responder] = Array[Responder]())
|
||||
case class QuestionOutVO(id: String, multiResponse: Boolean, question: String, responses: Array[ResponseOutVO])
|
||||
|
||||
class Poll(val id: String, val questions: Array[Question], val numRespondents: Int, val title: Option[String]) {
|
||||
class Poll(val id: String, val questions: Array[Question], val numRespondents: Int, val title: Option[String], val isSecret: Boolean) {
|
||||
private var _started: Boolean = false
|
||||
private var _stopped: Boolean = false
|
||||
private var _showResult: Boolean = false
|
||||
@ -636,7 +645,7 @@ class Poll(val id: String, val questions: Array[Question], val numRespondents: I
|
||||
qvos += q.toQuestionVO
|
||||
})
|
||||
|
||||
new PollVO(id, qvos.toArray, title, _started, _stopped, _showResult)
|
||||
new PollVO(id, qvos.toArray, title, _started, _stopped, _showResult, isSecret)
|
||||
}
|
||||
|
||||
def toSimplePollOutVO(): SimplePollOutVO = {
|
||||
@ -644,7 +653,7 @@ class Poll(val id: String, val questions: Array[Question], val numRespondents: I
|
||||
}
|
||||
|
||||
def toSimplePollResultOutVO(): SimplePollResultOutVO = {
|
||||
new SimplePollResultOutVO(id, questions(0).toSimpleVotesOutVO(), numRespondents, _numResponders)
|
||||
new SimplePollResultOutVO(id, questions(0).questionType, questions(0).text, questions(0).toSimpleVotesOutVO(), numRespondents, _numResponders)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -8,6 +8,14 @@ object Users2x {
|
||||
users.toVector find (u => u.intId == intId)
|
||||
}
|
||||
|
||||
def findWithBreakoutRoomId(users: Users2x, breakoutRoomId: String): Option[UserState] = {
|
||||
//userId + "-" + roomSequence
|
||||
val userIdParts = breakoutRoomId.split("-")
|
||||
val userExtId = userIdParts(0)
|
||||
|
||||
users.toVector find (u => u.extId == userExtId)
|
||||
}
|
||||
|
||||
def findAll(users: Users2x): Vector[UserState] = users.toVector
|
||||
|
||||
def add(users: Users2x, user: UserState): Option[UserState] = {
|
||||
@ -55,6 +63,10 @@ object Users2x {
|
||||
users.toVector.length
|
||||
}
|
||||
|
||||
def numActiveModerators(users: Users2x): Int = {
|
||||
users.toVector.filter(u => u.role == Roles.MODERATOR_ROLE && !u.userLeftFlag.left).length
|
||||
}
|
||||
|
||||
def findNotPresenters(users: Users2x): Vector[UserState] = {
|
||||
users.toVector.filter(u => !u.presenter)
|
||||
}
|
||||
@ -68,7 +80,13 @@ object Users2x {
|
||||
}
|
||||
|
||||
def updateLastUserActivity(users: Users2x, u: UserState): UserState = {
|
||||
val newUserState = modify(u)(_.lastActivityTime).setTo(TimeUtil.timeNowInMs())
|
||||
val newUserState = modify(u)(_.lastActivityTime).setTo(System.currentTimeMillis())
|
||||
users.save(newUserState)
|
||||
newUserState
|
||||
}
|
||||
|
||||
def updateLastInactivityInspect(users: Users2x, u: UserState): UserState = {
|
||||
val newUserState = modify(u)(_.lastInactivityInspect).setTo(System.currentTimeMillis())
|
||||
users.save(newUserState)
|
||||
newUserState
|
||||
}
|
||||
@ -257,21 +275,22 @@ case class OldPresenter(userId: String, changedPresenterOn: Long)
|
||||
case class UserLeftFlag(left: Boolean, leftOn: Long)
|
||||
|
||||
case class UserState(
|
||||
intId: String,
|
||||
extId: String,
|
||||
name: String,
|
||||
role: String,
|
||||
guest: Boolean,
|
||||
authed: Boolean,
|
||||
guestStatus: String,
|
||||
emoji: String,
|
||||
locked: Boolean,
|
||||
presenter: Boolean,
|
||||
avatar: String,
|
||||
roleChangedOn: Long = System.currentTimeMillis(),
|
||||
lastActivityTime: Long = TimeUtil.timeNowInMs(),
|
||||
clientType: String,
|
||||
userLeftFlag: UserLeftFlag
|
||||
intId: String,
|
||||
extId: String,
|
||||
name: String,
|
||||
role: String,
|
||||
guest: Boolean,
|
||||
authed: Boolean,
|
||||
guestStatus: String,
|
||||
emoji: String,
|
||||
locked: Boolean,
|
||||
presenter: Boolean,
|
||||
avatar: String,
|
||||
roleChangedOn: Long = System.currentTimeMillis(),
|
||||
lastActivityTime: Long = System.currentTimeMillis(),
|
||||
lastInactivityInspect: Long = 0,
|
||||
clientType: String,
|
||||
userLeftFlag: UserLeftFlag
|
||||
)
|
||||
|
||||
case class UserIdAndName(id: String, name: String)
|
||||
|
@ -14,6 +14,7 @@ object VoiceUsers {
|
||||
def findAll(users: VoiceUsers): Vector[VoiceUserState] = users.toVector
|
||||
|
||||
def findAllNonListenOnlyVoiceUsers(users: VoiceUsers): Vector[VoiceUserState] = users.toVector.filter(u => u.listenOnly == false)
|
||||
def findAllListenOnlyVoiceUsers(users: VoiceUsers): Vector[VoiceUserState] = users.toVector.filter(u => u.listenOnly == true)
|
||||
def findAllFreeswitchCallers(users: VoiceUsers): Vector[VoiceUserState] = users.toVector.filter(u => u.calledInto == "freeswitch")
|
||||
def findAllKurentoCallers(users: VoiceUsers): Vector[VoiceUserState] = users.toVector.filter(u => u.calledInto == "kms")
|
||||
|
||||
|
@ -30,6 +30,21 @@ object Webcams {
|
||||
removedStream <- webcams.remove(streamId)
|
||||
} yield removedStream
|
||||
}
|
||||
|
||||
def updateWebcamStream(webcams: Webcams, streamId: String, userId: String): Option[WebcamStream] = {
|
||||
findWithStreamId(webcams, streamId) match {
|
||||
case Some(value) => {
|
||||
val mediaStream: MediaStream = MediaStream(value.stream.id, value.stream.url, userId, value.stream.attributes,
|
||||
value.stream.viewers)
|
||||
val webcamStream: WebcamStream = WebcamStream(streamId, mediaStream)
|
||||
webcams.update(streamId, webcamStream)
|
||||
Some(webcamStream)
|
||||
}
|
||||
case None => {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
class Webcams {
|
||||
@ -47,6 +62,12 @@ class Webcams {
|
||||
webcam foreach (u => webcams -= streamId)
|
||||
webcam
|
||||
}
|
||||
|
||||
private def update(streamId: String, webcamStream: WebcamStream): WebcamStream = {
|
||||
val webcam = remove(streamId)
|
||||
|
||||
save(webcamStream)
|
||||
}
|
||||
}
|
||||
|
||||
case class WebcamStream(streamId: String, stream: MediaStream)
|
||||
|
@ -189,6 +189,8 @@ class ReceivedJsonMsgHandlerActor(
|
||||
routeGenericMsg[EndAllBreakoutRoomsMsg](envelope, jsonNode)
|
||||
case TransferUserToMeetingRequestMsg.NAME =>
|
||||
routeGenericMsg[TransferUserToMeetingRequestMsg](envelope, jsonNode)
|
||||
case ExtendBreakoutRoomsTimeReqMsg.NAME =>
|
||||
routeGenericMsg[ExtendBreakoutRoomsTimeReqMsg](envelope, jsonNode)
|
||||
|
||||
// Layout
|
||||
case GetCurrentLayoutReqMsg.NAME =>
|
||||
|
@ -0,0 +1,53 @@
|
||||
/**
|
||||
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
|
||||
*
|
||||
* Copyright (c) 2017 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.core.record.events
|
||||
|
||||
import org.bigbluebutton.common2.domain.SimpleVoteOutVO
|
||||
import org.bigbluebutton.common2.util.JsonUtil
|
||||
|
||||
class PollPublishedRecordEvent extends AbstractPollRecordEvent {
|
||||
import PollPublishedRecordEvent._
|
||||
|
||||
setEvent("PollPublishedRecordEvent")
|
||||
|
||||
def setQuestion(question: String) {
|
||||
eventMap.put(QUESTION, question)
|
||||
}
|
||||
|
||||
def setAnswers(answers: Array[SimpleVoteOutVO]) {
|
||||
eventMap.put(ANSWERS, JsonUtil.toJson(answers))
|
||||
}
|
||||
|
||||
def setNumRespondents(numRespondents: Int) {
|
||||
eventMap.put(NUM_RESPONDENTS, Integer.toString(numRespondents))
|
||||
}
|
||||
|
||||
def setNumResponders(numResponders: Int) {
|
||||
eventMap.put(NUM_RESPONDERS, Integer.toString(numResponders))
|
||||
}
|
||||
}
|
||||
|
||||
object PollPublishedRecordEvent {
|
||||
protected final val USER_ID = "userId"
|
||||
protected final val QUESTION = "question"
|
||||
protected final val ANSWERS = "answers"
|
||||
protected final val NUM_RESPONDENTS = "numRespondents"
|
||||
protected final val NUM_RESPONDERS = "numResponders"
|
||||
}
|
@ -31,12 +31,27 @@ class PollStartedRecordEvent extends AbstractPollRecordEvent {
|
||||
eventMap.put(USER_ID, userId)
|
||||
}
|
||||
|
||||
def setQuestion(question: String) {
|
||||
eventMap.put(QUESTION, question)
|
||||
}
|
||||
|
||||
def setAnswers(answers: Array[SimpleAnswerOutVO]) {
|
||||
eventMap.put(ANSWERS, JsonUtil.toJson(answers))
|
||||
}
|
||||
|
||||
def setType(pollType: String) {
|
||||
eventMap.put(TYPE, pollType)
|
||||
}
|
||||
|
||||
def setSecretPoll(secretPoll: Boolean) {
|
||||
eventMap.put(SECRET_POLL, secretPoll.toString)
|
||||
}
|
||||
}
|
||||
|
||||
object PollStartedRecordEvent {
|
||||
protected final val USER_ID = "userId"
|
||||
protected final val QUESTION = "question"
|
||||
protected final val ANSWERS = "answers"
|
||||
protected final val TYPE = "type"
|
||||
protected final val SECRET_POLL = "secretPoll"
|
||||
}
|
@ -36,7 +36,7 @@ class UpdateExternalVideoRecordEvent extends AbstractExternalVideoRecordEvent {
|
||||
eventMap.put(TIME, time.toString)
|
||||
}
|
||||
|
||||
def setState(state: Boolean) {
|
||||
def setState(state: Int) {
|
||||
eventMap.put(STATE, state.toString)
|
||||
}
|
||||
}
|
||||
|
@ -33,9 +33,14 @@ class UserRespondedToPollRecordEvent extends AbstractPollRecordEvent {
|
||||
def setAnswerId(answerIds: Array[Int]) {
|
||||
eventMap.put(ANSWER_IDS, JsonUtil.toJson(answerIds))
|
||||
}
|
||||
|
||||
def setAnswer(answer: String) {
|
||||
eventMap.put(ANSWER, answer)
|
||||
}
|
||||
}
|
||||
|
||||
object UserRespondedToPollRecordEvent {
|
||||
protected final val USER_ID = "userId"
|
||||
protected final val ANSWER_IDS = "answerIds"
|
||||
protected final val ANSWER = "answer"
|
||||
}
|
@ -35,5 +35,5 @@ class WebcamsOnlyForModeratorRecordEvent extends AbstractParticipantRecordEvent
|
||||
|
||||
object WebcamsOnlyForModeratorRecordEvent {
|
||||
protected final val USER_ID = "userId"
|
||||
protected final val WEBCAMS_ONLY_FOR_MODERATOR = "webacmsOnlyForModerator"
|
||||
protected final val WEBCAMS_ONLY_FOR_MODERATOR = "webcamsOnlyForModerator"
|
||||
}
|
||||
|
@ -84,6 +84,7 @@ trait HandlerHelpers extends SystemConfiguration {
|
||||
outGW.send(event)
|
||||
val newState = startRecordingIfAutoStart2x(outGW, liveMeeting, state)
|
||||
if (!Users2x.hasPresenter(liveMeeting.users2x)) {
|
||||
// println(s"userJoinMeeting will trigger an automaticallyAssignPresenter for user=${newUser}")
|
||||
UsersApp.automaticallyAssignPresenter(outGW, liveMeeting)
|
||||
}
|
||||
newState.update(newState.expiryTracker.setUserHasJoined())
|
||||
|
@ -6,18 +6,19 @@ import org.bigbluebutton.core.models._
|
||||
import org.bigbluebutton.core2.MeetingStatus2x
|
||||
|
||||
class LiveMeeting(
|
||||
val props: DefaultProps,
|
||||
val status: MeetingStatus2x,
|
||||
val screenshareModel: ScreenshareModel,
|
||||
val chatModel: ChatModel,
|
||||
val layouts: Layouts,
|
||||
val registeredUsers: RegisteredUsers,
|
||||
val polls: Polls, // 2x
|
||||
val wbModel: WhiteboardModel,
|
||||
val presModel: PresentationModel,
|
||||
val captionModel: CaptionModel,
|
||||
val webcams: Webcams,
|
||||
val voiceUsers: VoiceUsers,
|
||||
val users2x: Users2x,
|
||||
val guestsWaiting: GuestsWaiting
|
||||
val props: DefaultProps,
|
||||
val status: MeetingStatus2x,
|
||||
val screenshareModel: ScreenshareModel,
|
||||
val chatModel: ChatModel,
|
||||
val externalVideoModel: ExternalVideoModel,
|
||||
val layouts: Layouts,
|
||||
val registeredUsers: RegisteredUsers,
|
||||
val polls: Polls, // 2x
|
||||
val wbModel: WhiteboardModel,
|
||||
val presModel: PresentationModel,
|
||||
val captionModel: CaptionModel,
|
||||
val webcams: Webcams,
|
||||
val voiceUsers: VoiceUsers,
|
||||
val users2x: Users2x,
|
||||
val guestsWaiting: GuestsWaiting
|
||||
)
|
||||
|
@ -1,7 +1,6 @@
|
||||
package org.bigbluebutton.core.running
|
||||
|
||||
import java.io.{ PrintWriter, StringWriter }
|
||||
|
||||
import akka.actor._
|
||||
import akka.actor.SupervisorStrategy.Resume
|
||||
import org.bigbluebutton.SystemConfiguration
|
||||
@ -22,7 +21,7 @@ import org.bigbluebutton.core.apps.presentation.PresentationApp2x
|
||||
import org.bigbluebutton.core.apps.users.UsersApp2x
|
||||
import org.bigbluebutton.core.apps.whiteboard.WhiteboardApp2x
|
||||
import org.bigbluebutton.core.bus._
|
||||
import org.bigbluebutton.core.models._
|
||||
import org.bigbluebutton.core.models.{ Users2x, VoiceUsers, _ }
|
||||
import org.bigbluebutton.core2.{ MeetingStatus2x, Permissions }
|
||||
import org.bigbluebutton.core2.message.handlers._
|
||||
import org.bigbluebutton.core2.message.handlers.meeting._
|
||||
@ -31,15 +30,21 @@ import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.core.apps.breakout._
|
||||
import org.bigbluebutton.core.apps.polls._
|
||||
import org.bigbluebutton.core.apps.voice._
|
||||
import akka.actor._
|
||||
import akka.actor.Props
|
||||
import akka.actor.OneForOneStrategy
|
||||
import akka.actor.SupervisorStrategy.Resume
|
||||
import org.bigbluebutton.common2.msgs
|
||||
|
||||
import scala.concurrent.duration._
|
||||
import org.bigbluebutton.core.apps.layout.LayoutApp2x
|
||||
import org.bigbluebutton.core.apps.meeting.{ SyncGetMeetingInfoRespMsgHdlr, ValidateConnAuthTokenSysMsgHdlr }
|
||||
import org.bigbluebutton.core.apps.users.ChangeLockSettingsInMeetingCmdMsgHdlr
|
||||
import org.bigbluebutton.core.models.VoiceUsers.{ findAllFreeswitchCallers, findAllListenOnlyVoiceUsers }
|
||||
import org.bigbluebutton.core.models.Webcams.{ findAll, updateWebcamStream }
|
||||
import org.bigbluebutton.core2.MeetingStatus2x.{ hasAuthedUserJoined, isVoiceRecording }
|
||||
import org.bigbluebutton.core2.message.senders.{ MsgBuilder, Sender }
|
||||
|
||||
import java.util.concurrent.TimeUnit
|
||||
import scala.concurrent.ExecutionContext.Implicits.global
|
||||
|
||||
object MeetingActor {
|
||||
@ -97,6 +102,8 @@ class MeetingActor(
|
||||
|
||||
object CheckVoiceRecordingInternalMsg
|
||||
object SyncVoiceUserStatusInternalMsg
|
||||
object MeetingInfoAnalyticsMsg
|
||||
object MeetingInfoAnalyticsLogMsg
|
||||
|
||||
override val supervisorStrategy = OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1 minute) {
|
||||
case e: Exception => {
|
||||
@ -135,14 +142,18 @@ class MeetingActor(
|
||||
val expiryTracker = new MeetingExpiryTracker(
|
||||
startedOnInMs = TimeUtil.timeNowInMs(),
|
||||
userHasJoined = false,
|
||||
moderatorHasJoined = false,
|
||||
isBreakout = props.meetingProp.isBreakout,
|
||||
lastUserLeftOnInMs = None,
|
||||
lastModeratorLeftOnInMs = 0,
|
||||
durationInMs = TimeUtil.minutesToMillis(props.durationProps.duration),
|
||||
meetingExpireIfNoUserJoinedInMs = TimeUtil.minutesToMillis(props.durationProps.meetingExpireIfNoUserJoinedInMinutes),
|
||||
meetingExpireWhenLastUserLeftInMs = TimeUtil.minutesToMillis(props.durationProps.meetingExpireWhenLastUserLeftInMinutes),
|
||||
userInactivityInspectTimerInMs = TimeUtil.minutesToMillis(props.durationProps.userInactivityInspectTimerInMinutes),
|
||||
userInactivityThresholdInMs = TimeUtil.minutesToMillis(props.durationProps.userInactivityThresholdInMinutes),
|
||||
userActivitySignResponseDelayInMs = TimeUtil.minutesToMillis(props.durationProps.userActivitySignResponseDelayInMinutes)
|
||||
userActivitySignResponseDelayInMs = TimeUtil.minutesToMillis(props.durationProps.userActivitySignResponseDelayInMinutes),
|
||||
endWhenNoModerator = props.durationProps.endWhenNoModerator,
|
||||
endWhenNoModeratorDelayInMs = TimeUtil.minutesToMillis(props.durationProps.endWhenNoModeratorDelayInMinutes)
|
||||
)
|
||||
|
||||
val recordingTracker = new MeetingRecordingTracker(startedOnInMs = 0L, previousDurationInMs = 0L, currentDurationInMs = 0L)
|
||||
@ -208,12 +219,32 @@ class MeetingActor(
|
||||
CheckVoiceRecordingInternalMsg
|
||||
)
|
||||
|
||||
context.system.scheduler.scheduleOnce(
|
||||
10 seconds,
|
||||
self,
|
||||
MeetingInfoAnalyticsLogMsg
|
||||
)
|
||||
|
||||
context.system.scheduler.schedule(
|
||||
10 seconds,
|
||||
30 seconds,
|
||||
self,
|
||||
MeetingInfoAnalyticsMsg
|
||||
)
|
||||
|
||||
def receive = {
|
||||
case SyncVoiceUserStatusInternalMsg =>
|
||||
checkVoiceConfUsersStatus()
|
||||
case CheckVoiceRecordingInternalMsg =>
|
||||
checkVoiceConfIsRunningAndRecording()
|
||||
|
||||
case MeetingInfoAnalyticsLogMsg =>
|
||||
handleMeetingInfoAnalyticsLogging()
|
||||
case MeetingInfoAnalyticsMsg =>
|
||||
handleMeetingInfoAnalyticsService()
|
||||
case msg: CamStreamSubscribeSysMsg =>
|
||||
handleCamStreamSubscribeSysMsg(msg)
|
||||
case msg: ScreenStreamSubscribeSysMsg =>
|
||||
handleScreenStreamSubscribeSysMsg(msg)
|
||||
//=============================
|
||||
|
||||
// 2x messages
|
||||
@ -250,6 +281,7 @@ class MeetingActor(
|
||||
case msg: SendBreakoutUsersAuditInternalMsg => handleSendBreakoutUsersUpdateInternalMsg(msg)
|
||||
case msg: BreakoutRoomUsersUpdateInternalMsg => state = handleBreakoutRoomUsersUpdateInternalMsg(msg, state)
|
||||
case msg: EndBreakoutRoomInternalMsg => handleEndBreakoutRoomInternalMsg(msg)
|
||||
case msg: ExtendBreakoutRoomTimeInternalMsg => state = handleExtendBreakoutRoomTimeInternalMsgHdlr(msg, state)
|
||||
case msg: BreakoutRoomEndedInternalMsg => state = handleBreakoutRoomEndedInternalMsg(msg, state)
|
||||
case msg: SendBreakoutTimeRemainingInternalMsg =>
|
||||
handleSendBreakoutTimeRemainingInternalMsg(msg)
|
||||
@ -298,6 +330,31 @@ class MeetingActor(
|
||||
}
|
||||
}
|
||||
|
||||
private def updateModeratorsPresence() {
|
||||
if (Users2x.numActiveModerators(liveMeeting.users2x) > 0) {
|
||||
if (state.expiryTracker.moderatorHasJoined == false ||
|
||||
state.expiryTracker.lastModeratorLeftOnInMs != 0) {
|
||||
log.info("A moderator has joined. Setting setModeratorHasJoined(). meetingId=" + props.meetingProp.intId)
|
||||
val tracker = state.expiryTracker.setModeratorHasJoined()
|
||||
state = state.update(tracker)
|
||||
}
|
||||
} else {
|
||||
if (state.expiryTracker.moderatorHasJoined == true) {
|
||||
log.info("All moderators have left. Setting setLastModeratorLeftOn(). meetingId=" + props.meetingProp.intId)
|
||||
val tracker = state.expiryTracker.setLastModeratorLeftOn(TimeUtil.timeNowInMs())
|
||||
state = state.update(tracker)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def updateUserLastInactivityInspect(userId: String) {
|
||||
for {
|
||||
user <- Users2x.findWithIntId(liveMeeting.users2x, userId)
|
||||
} yield {
|
||||
Users2x.updateLastInactivityInspect(liveMeeting.users2x, user)
|
||||
}
|
||||
}
|
||||
|
||||
private def handleBbbCommonEnvCoreMsg(msg: BbbCommonEnvCoreMsg): Unit = {
|
||||
msg.core match {
|
||||
case m: ClientToServerLatencyTracerMsg => handleClientToServerLatencyTracerMsg(m)
|
||||
@ -309,20 +366,26 @@ class MeetingActor(
|
||||
private def handleMessageThatAffectsInactivity(msg: BbbCommonEnvCoreMsg): Unit = {
|
||||
|
||||
msg.core match {
|
||||
case m: EndMeetingSysCmdMsg => handleEndMeeting(m, state)
|
||||
case m: EndMeetingSysCmdMsg => handleEndMeeting(m, state)
|
||||
|
||||
// Users
|
||||
case m: ValidateAuthTokenReqMsg => state = usersApp.handleValidateAuthTokenReqMsg(m, state)
|
||||
case m: UserJoinMeetingReqMsg => state = handleUserJoinMeetingReqMsg(m, state)
|
||||
case m: UserJoinMeetingAfterReconnectReqMsg => state = handleUserJoinMeetingAfterReconnectReqMsg(m, state)
|
||||
case m: UserLeaveReqMsg => state = handleUserLeaveReqMsg(m, state)
|
||||
case m: UserBroadcastCamStartMsg => handleUserBroadcastCamStartMsg(m)
|
||||
case m: UserBroadcastCamStopMsg => handleUserBroadcastCamStopMsg(m)
|
||||
case m: GetCamBroadcastPermissionReqMsg => handleGetCamBroadcastPermissionReqMsg(m)
|
||||
case m: GetCamSubscribePermissionReqMsg => handleGetCamSubscribePermissionReqMsg(m)
|
||||
case m: ValidateAuthTokenReqMsg => state = usersApp.handleValidateAuthTokenReqMsg(m, state)
|
||||
case m: UserJoinMeetingReqMsg =>
|
||||
state = handleUserJoinMeetingReqMsg(m, state)
|
||||
updateModeratorsPresence()
|
||||
case m: UserJoinMeetingAfterReconnectReqMsg =>
|
||||
state = handleUserJoinMeetingAfterReconnectReqMsg(m, state)
|
||||
updateModeratorsPresence()
|
||||
case m: UserLeaveReqMsg =>
|
||||
state = handleUserLeaveReqMsg(m, state)
|
||||
updateModeratorsPresence()
|
||||
case m: UserBroadcastCamStartMsg => handleUserBroadcastCamStartMsg(m)
|
||||
case m: UserBroadcastCamStopMsg => handleUserBroadcastCamStopMsg(m)
|
||||
case m: GetCamBroadcastPermissionReqMsg => handleGetCamBroadcastPermissionReqMsg(m)
|
||||
case m: GetCamSubscribePermissionReqMsg => handleGetCamSubscribePermissionReqMsg(m)
|
||||
|
||||
case m: UserJoinedVoiceConfEvtMsg => handleUserJoinedVoiceConfEvtMsg(m)
|
||||
case m: LogoutAndEndMeetingCmdMsg => usersApp.handleLogoutAndEndMeetingCmdMsg(m, state)
|
||||
case m: UserJoinedVoiceConfEvtMsg => handleUserJoinedVoiceConfEvtMsg(m)
|
||||
case m: LogoutAndEndMeetingCmdMsg => usersApp.handleLogoutAndEndMeetingCmdMsg(m, state)
|
||||
case m: SetRecordingStatusCmdMsg =>
|
||||
state = usersApp.handleSetRecordingStatusCmdMsg(m, state)
|
||||
updateUserLastActivity(m.body.setBy)
|
||||
@ -346,6 +409,7 @@ class MeetingActor(
|
||||
case m: ChangeUserRoleCmdMsg =>
|
||||
usersApp.handleChangeUserRoleCmdMsg(m)
|
||||
updateUserLastActivity(m.body.changedBy)
|
||||
updateModeratorsPresence()
|
||||
|
||||
// Whiteboard
|
||||
case m: SendCursorPositionPubMsg => wbApp.handle(m, liveMeeting, msgBus)
|
||||
@ -382,6 +446,7 @@ class MeetingActor(
|
||||
case m: EndAllBreakoutRoomsMsg => state = handleEndAllBreakoutRoomsMsg(m, state)
|
||||
case m: RequestBreakoutJoinURLReqMsg => state = handleRequestBreakoutJoinURLReqMsg(m, state)
|
||||
case m: TransferUserToMeetingRequestMsg => state = handleTransferUserToMeetingRequestMsg(m, state)
|
||||
case m: ExtendBreakoutRoomsTimeReqMsg => state = handleExtendBreakoutRoomsTimeMsg(m, state)
|
||||
|
||||
// Voice
|
||||
case m: UserLeftVoiceConfEvtMsg => handleUserLeftVoiceConfEvtMsg(m)
|
||||
@ -508,6 +573,98 @@ class MeetingActor(
|
||||
}
|
||||
}
|
||||
|
||||
private def handleCamStreamSubscribeSysMsg(msg: CamStreamSubscribeSysMsg): Unit = {
|
||||
updateWebcamStream(liveMeeting.webcams, msg.body.streamId, msg.body.userId)
|
||||
}
|
||||
|
||||
private def handleScreenStreamSubscribeSysMsg(msg: ScreenStreamSubscribeSysMsg): Unit = ???
|
||||
|
||||
private def handleMeetingInfoAnalyticsLogging(): Unit = {
|
||||
val meetingInfoAnalyticsLogMsg: MeetingInfoAnalytics = prepareMeetingInfo()
|
||||
val event = MsgBuilder.buildMeetingInfoAnalyticsMsg(meetingInfoAnalyticsLogMsg)
|
||||
outGW.send(event)
|
||||
}
|
||||
|
||||
private def handleMeetingInfoAnalyticsService(): Unit = {
|
||||
val meetingInfoAnalyticsLogMsg: MeetingInfoAnalytics = prepareMeetingInfo()
|
||||
val event2 = MsgBuilder.buildMeetingInfoAnalyticsServiceMsg(meetingInfoAnalyticsLogMsg)
|
||||
outGW.send(event2)
|
||||
}
|
||||
|
||||
private def prepareMeetingInfo(): MeetingInfoAnalytics = {
|
||||
val meetingName: String = liveMeeting.props.meetingProp.name
|
||||
val externalId: String = liveMeeting.props.meetingProp.extId
|
||||
val internalId: String = liveMeeting.props.meetingProp.intId
|
||||
val hasUserJoined: Boolean = hasAuthedUserJoined(liveMeeting.status)
|
||||
val isMeetingRecorded = MeetingStatus2x.isRecording(liveMeeting.status)
|
||||
|
||||
// TODO: Placeholder values as required values not available
|
||||
val screenshareStream: ScreenshareStream = ScreenshareStream(new User("", ""), List())
|
||||
val screenshare: Screenshare = Screenshare(screenshareStream)
|
||||
|
||||
val listOfUsers: List[UserState] = Users2x.findAll(liveMeeting.users2x).toList
|
||||
val breakoutRoomNames: List[String] = {
|
||||
if (state.breakout.isDefined)
|
||||
state.breakout.get.getRooms.map(_.name).toList
|
||||
else
|
||||
List()
|
||||
}
|
||||
val breakoutRoom: BreakoutRoom = BreakoutRoom(liveMeeting.props.breakoutProps.parentId, breakoutRoomNames)
|
||||
MeetingInfoAnalytics(
|
||||
meetingName, externalId, internalId, hasUserJoined, isMeetingRecorded, getMeetingInfoWebcamDetails, getMeetingInfoAudioDetails,
|
||||
screenshare, listOfUsers.map(u => Participant(u.intId, u.name, u.role)), getMeetingInfoPresentationDetails, breakoutRoom
|
||||
)
|
||||
}
|
||||
|
||||
private def resolveUserName(userId: String): String = {
|
||||
val userName: String = Users2x.findWithIntId(liveMeeting.users2x, userId).map(_.name).getOrElse("")
|
||||
if (userName.isEmpty) log.error(s"Failed to map username for id $userId")
|
||||
userName
|
||||
}
|
||||
|
||||
private def getMeetingInfoWebcamDetails(): Webcam = {
|
||||
val liveWebcams: Vector[org.bigbluebutton.core.models.WebcamStream] = findAll(liveMeeting.webcams)
|
||||
val numOfLiveWebcams: Int = liveWebcams.length
|
||||
val broadcasts: List[Broadcast] = liveWebcams.map(webcam => Broadcast(
|
||||
webcam.stream.id,
|
||||
User(webcam.stream.userId, resolveUserName(webcam.stream.userId)), 0L
|
||||
)).toList
|
||||
val viewers: Set[String] = liveWebcams.flatMap(_.stream.viewers).toSet
|
||||
val webcamStream: msgs.WebcamStream = msgs.WebcamStream(broadcasts, viewers)
|
||||
Webcam(numOfLiveWebcams, webcamStream)
|
||||
}
|
||||
|
||||
private def getMeetingInfoAudioDetails(): Audio = {
|
||||
val voiceUsers: Vector[VoiceUserState] = VoiceUsers.findAll(liveMeeting.voiceUsers)
|
||||
val numOfVoiceUsers: Int = voiceUsers.length
|
||||
|
||||
val listenOnlyUsers: Vector[VoiceUserState] = findAllListenOnlyVoiceUsers(liveMeeting.voiceUsers)
|
||||
val numOfListenOnlyUsers: Int = listenOnlyUsers.length
|
||||
val listenOnlyAudio = ListenOnlyAudio(
|
||||
numOfListenOnlyUsers,
|
||||
listenOnlyUsers.map(voiceUserState => User(voiceUserState.voiceUserId, resolveUserName(voiceUserState.intId))).toList
|
||||
)
|
||||
|
||||
val freeswitchUsers: Vector[VoiceUserState] = findAllFreeswitchCallers(liveMeeting.voiceUsers)
|
||||
val numOfFreeswitchUsers: Int = freeswitchUsers.length
|
||||
val twoWayAudio = TwoWayAudio(
|
||||
numOfFreeswitchUsers,
|
||||
freeswitchUsers.map(voiceUserState => User(voiceUserState.voiceUserId, resolveUserName(voiceUserState.intId))).toList
|
||||
)
|
||||
|
||||
// TODO: Placeholder values
|
||||
val phoneAudio = PhoneAudio(0, List())
|
||||
|
||||
Audio(numOfVoiceUsers, listenOnlyAudio, twoWayAudio, phoneAudio)
|
||||
}
|
||||
|
||||
private def getMeetingInfoPresentationDetails(): PresentationInfo = {
|
||||
val presentationPods: Vector[PresentationPod] = state.presentationPodManager.getAllPresentationPodsInMeeting()
|
||||
val presentationId: String = presentationPods.flatMap(_.getCurrentPresentation.map(_.id)).mkString
|
||||
val presentationName: String = presentationPods.flatMap(_.getCurrentPresentation.map(_.name)).mkString
|
||||
PresentationInfo(presentationId, presentationName)
|
||||
}
|
||||
|
||||
def handleGetRunningMeetingStateReqMsg(msg: GetRunningMeetingStateReqMsg): Unit = {
|
||||
processGetRunningMeetingStateReqMsg()
|
||||
}
|
||||
@ -558,7 +715,6 @@ class MeetingActor(
|
||||
}
|
||||
|
||||
def handleDeskShareGetDeskShareInfoRequest(msg: DeskShareGetDeskShareInfoRequest): Unit = {
|
||||
|
||||
log.info("handleDeskShareGetDeskShareInfoRequest: " + msg.conferenceName + "isBroadcasting="
|
||||
+ ScreenshareModel.isBroadcastingRTMP(liveMeeting.screenshareModel) + " URL:" +
|
||||
ScreenshareModel.getRTMPBroadcastingUrl(liveMeeting.screenshareModel))
|
||||
@ -588,7 +744,8 @@ class MeetingActor(
|
||||
|
||||
processUserInactivityAudit()
|
||||
flagRegisteredUsersWhoHasNotJoined()
|
||||
checkIfNeetToEndMeetingWhenNoAuthedUsers(liveMeeting)
|
||||
checkIfNeedToEndMeetingWhenNoAuthedUsers(liveMeeting)
|
||||
checkIfNeedToEndMeetingWhenNoModerators(liveMeeting)
|
||||
}
|
||||
|
||||
def checkVoiceConfUsersStatus(): Unit = {
|
||||
@ -610,7 +767,7 @@ class MeetingActor(
|
||||
var lastRecBreakSentOn = expiryTracker.startedOnInMs
|
||||
|
||||
def setRecordingChapterBreak(): Unit = {
|
||||
val now = TimeUtil.timeNowInMs()
|
||||
val now = System.currentTimeMillis()
|
||||
val elapsedInMs = now - lastRecBreakSentOn
|
||||
val elapsedInMin = TimeUtil.millisToMinutes(elapsedInMs)
|
||||
|
||||
@ -655,7 +812,7 @@ class MeetingActor(
|
||||
|
||||
}
|
||||
|
||||
private def checkIfNeetToEndMeetingWhenNoAuthedUsers(liveMeeting: LiveMeeting): Unit = {
|
||||
private def checkIfNeedToEndMeetingWhenNoAuthedUsers(liveMeeting: LiveMeeting): Unit = {
|
||||
val authUserJoined = MeetingStatus2x.hasAuthedUserJoined(liveMeeting.status)
|
||||
|
||||
if (endMeetingWhenNoMoreAuthedUsers &&
|
||||
@ -676,10 +833,30 @@ class MeetingActor(
|
||||
}
|
||||
}
|
||||
|
||||
def handleExtendMeetingDuration(msg: ExtendMeetingDuration) {
|
||||
|
||||
private def checkIfNeedToEndMeetingWhenNoModerators(liveMeeting: LiveMeeting): Unit = {
|
||||
if (state.expiryTracker.endWhenNoModerator &&
|
||||
!liveMeeting.props.meetingProp.isBreakout &&
|
||||
state.expiryTracker.moderatorHasJoined &&
|
||||
state.expiryTracker.lastModeratorLeftOnInMs != 0 &&
|
||||
//Check if has moderator with leftFlag
|
||||
Users2x.findModerator(liveMeeting.users2x).toVector.length == 0) {
|
||||
val hasModeratorLeftRecently = (TimeUtil.timeNowInMs() - state.expiryTracker.endWhenNoModeratorDelayInMs) < state.expiryTracker.lastModeratorLeftOnInMs
|
||||
if (!hasModeratorLeftRecently) {
|
||||
log.info("Meeting will end due option endWhenNoModerator is enabled and all moderators have left the meeting. meetingId=" + props.meetingProp.intId)
|
||||
sendEndMeetingDueToExpiry(
|
||||
MeetingEndReason.ENDED_DUE_TO_NO_MODERATOR,
|
||||
eventBus, outGW, liveMeeting,
|
||||
"system"
|
||||
)
|
||||
} else {
|
||||
val msToEndMeeting = state.expiryTracker.lastModeratorLeftOnInMs - (TimeUtil.timeNowInMs() - state.expiryTracker.endWhenNoModeratorDelayInMs)
|
||||
log.info("All moderators have left. Meeting will end in " + TimeUtil.millisToSeconds(msToEndMeeting) + " seconds. meetingId=" + props.meetingProp.intId)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def handleExtendMeetingDuration(msg: ExtendMeetingDuration) = ???
|
||||
|
||||
def removeUsersWithExpiredUserLeftFlag(liveMeeting: LiveMeeting, state: MeetingState2x): MeetingState2x = {
|
||||
val leftUsers = Users2x.findAllExpiredUserLeftFlags(liveMeeting.users2x, expiryTracker.meetingExpireWhenLastUserLeftInMs)
|
||||
leftUsers foreach { leftUser =>
|
||||
@ -695,6 +872,7 @@ class MeetingActor(
|
||||
outGW.send(userLeftMeetingEvent)
|
||||
|
||||
if (u.presenter) {
|
||||
log.info("removeUsersWithExpiredUserLeftFlag will cause an automaticallyAssignPresenter because user={} left", u)
|
||||
UsersApp.automaticallyAssignPresenter(outGW, liveMeeting)
|
||||
|
||||
// request screenshare to end
|
||||
@ -723,34 +901,37 @@ class MeetingActor(
|
||||
}
|
||||
}
|
||||
|
||||
var lastUserInactivityInspectSentOn = TimeUtil.timeNowInMs()
|
||||
var checkInactiveUsers = false
|
||||
var lastUsersInactivityInspection = System.currentTimeMillis()
|
||||
|
||||
def processUserInactivityAudit(): Unit = {
|
||||
val now = TimeUtil.timeNowInMs()
|
||||
|
||||
val now = System.currentTimeMillis()
|
||||
|
||||
// Check if user is inactive. We only do the check is user inactivity
|
||||
// is not disabled (0).
|
||||
if ((expiryTracker.userInactivityInspectTimerInMs > 0) &&
|
||||
(now > lastUserInactivityInspectSentOn + expiryTracker.userInactivityInspectTimerInMs)) {
|
||||
lastUserInactivityInspectSentOn = now
|
||||
checkInactiveUsers = true
|
||||
warnPotentiallyInactiveUsers()
|
||||
}
|
||||
(now > lastUsersInactivityInspection + expiryTracker.userInactivityInspectTimerInMs)) {
|
||||
lastUsersInactivityInspection = now
|
||||
|
||||
if (checkInactiveUsers && now > lastUserInactivityInspectSentOn + expiryTracker.userActivitySignResponseDelayInMs) {
|
||||
checkInactiveUsers = false
|
||||
warnPotentiallyInactiveUsers()
|
||||
disconnectInactiveUsers()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
def warnPotentiallyInactiveUsers(): Unit = {
|
||||
log.info("Checking for inactive users.")
|
||||
val users = Users2x.findAll(liveMeeting.users2x)
|
||||
users foreach { u =>
|
||||
val active = (lastUserInactivityInspectSentOn - expiryTracker.userInactivityThresholdInMs) < u.lastActivityTime
|
||||
if (!active) {
|
||||
Sender.sendUserInactivityInspectMsg(liveMeeting.props.meetingProp.intId, u.intId, TimeUtil.minutesToSeconds(props.durationProps.userActivitySignResponseDelayInMinutes), outGW)
|
||||
val hasActivityAfterWarning = u.lastInactivityInspect < u.lastActivityTime
|
||||
val hasActivityRecently = (lastUsersInactivityInspection - expiryTracker.userInactivityThresholdInMs) < u.lastActivityTime
|
||||
|
||||
if (hasActivityAfterWarning && !hasActivityRecently) {
|
||||
log.info("User has been inactive for " + TimeUnit.MILLISECONDS.toMinutes(expiryTracker.userInactivityThresholdInMs) + " minutes. Sending inactivity warning. meetingId=" + props.meetingProp.intId + " userId=" + u.intId + " user=" + u)
|
||||
|
||||
val secsToDisconnect = TimeUnit.MILLISECONDS.toSeconds(expiryTracker.userActivitySignResponseDelayInMs);
|
||||
Sender.sendUserInactivityInspectMsg(liveMeeting.props.meetingProp.intId, u.intId, secsToDisconnect, outGW)
|
||||
updateUserLastInactivityInspect(u.intId)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -759,8 +940,13 @@ class MeetingActor(
|
||||
log.info("Check for users who haven't responded to user inactivity warning.")
|
||||
val users = Users2x.findAll(liveMeeting.users2x)
|
||||
users foreach { u =>
|
||||
val respondedOnTime = (lastUserInactivityInspectSentOn - expiryTracker.userInactivityThresholdInMs) < u.lastActivityTime && (lastUserInactivityInspectSentOn + expiryTracker.userActivitySignResponseDelayInMs) > u.lastActivityTime
|
||||
if (!respondedOnTime) {
|
||||
val hasInactivityWarningSent = u.lastInactivityInspect != 0
|
||||
val hasActivityAfterWarning = u.lastInactivityInspect < u.lastActivityTime
|
||||
val respondedOnTime = (lastUsersInactivityInspection - expiryTracker.userActivitySignResponseDelayInMs) < u.lastInactivityInspect
|
||||
|
||||
if (hasInactivityWarningSent && !hasActivityAfterWarning && !respondedOnTime) {
|
||||
log.info("User didn't response the inactivity warning within " + TimeUnit.MILLISECONDS.toSeconds(expiryTracker.userActivitySignResponseDelayInMs) + " seconds. Ejecting from meeting. meetingId=" + props.meetingProp.intId + " userId=" + u.intId + " user=" + u)
|
||||
|
||||
UsersApp.ejectUserFromMeeting(
|
||||
outGW,
|
||||
liveMeeting,
|
||||
|
@ -17,6 +17,7 @@ object RunningMeeting {
|
||||
class RunningMeeting(val props: DefaultProps, outGW: OutMessageGateway,
|
||||
eventBus: InternalEventBus)(implicit val context: ActorContext) {
|
||||
|
||||
private val externalVideoModel = new ExternalVideoModel()
|
||||
private val chatModel = new ChatModel()
|
||||
private val layouts = new Layouts()
|
||||
private val wbModel = new WhiteboardModel()
|
||||
@ -35,8 +36,8 @@ class RunningMeeting(val props: DefaultProps, outGW: OutMessageGateway,
|
||||
|
||||
// We extract the meeting handlers into this class so it is
|
||||
// easy to test.
|
||||
private val liveMeeting = new LiveMeeting(props, meetingStatux2x, deskshareModel, chatModel, layouts,
|
||||
registeredUsers, polls2x, wbModel, presModel, captionModel,
|
||||
private val liveMeeting = new LiveMeeting(props, meetingStatux2x, deskshareModel, chatModel, externalVideoModel,
|
||||
layouts, registeredUsers, polls2x, wbModel, presModel, captionModel,
|
||||
webcams, voiceUsers, users2x, guestsWaiting)
|
||||
|
||||
GuestsWaiting.setGuestPolicy(
|
||||
@ -44,6 +45,12 @@ class RunningMeeting(val props: DefaultProps, outGW: OutMessageGateway,
|
||||
GuestPolicy(props.usersProp.guestPolicy, SystemUser.ID)
|
||||
)
|
||||
|
||||
Layouts.setCurrentLayout(
|
||||
liveMeeting.layouts,
|
||||
props.usersProp.meetingLayout,
|
||||
SystemUser.ID
|
||||
)
|
||||
|
||||
private val recordEvents = props.recordProp.record || props.recordProp.keepEvents
|
||||
val outMsgRouter = new OutMsgRouter(recordEvents, outGW)
|
||||
|
||||
|
@ -3,7 +3,6 @@ package org.bigbluebutton.core2
|
||||
import akka.actor.{ Actor, ActorLogging, Props }
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.common2.util.JsonUtil
|
||||
|
||||
object AnalyticsActor {
|
||||
def props(includeChat: Boolean): Props = Props(classOf[AnalyticsActor], includeChat)
|
||||
}
|
||||
@ -61,6 +60,7 @@ class AnalyticsActor(val includeChat: Boolean) extends Actor with ActorLogging {
|
||||
case m: RequestBreakoutJoinURLReqMsg => logMessage(msg)
|
||||
case m: EndAllBreakoutRoomsMsg => logMessage(msg)
|
||||
case m: TransferUserToMeetingRequestMsg => logMessage(msg)
|
||||
case m: ExtendBreakoutRoomsTimeReqMsg => logMessage(msg)
|
||||
case m: UserLeftVoiceConfToClientEvtMsg => logMessage(msg)
|
||||
case m: UserLeftVoiceConfEvtMsg => logMessage(msg)
|
||||
case m: RecordingStartedVoiceConfEvtMsg => logMessage(msg)
|
||||
@ -167,6 +167,11 @@ class AnalyticsActor(val includeChat: Boolean) extends Actor with ActorLogging {
|
||||
case m: ChangeLockSettingsInMeetingCmdMsg => logMessage(msg)
|
||||
case m: GetLockSettingsReqMsg => logMessage(msg)
|
||||
case m: LockSettingsNotInitializedRespMsg => logMessage(msg)
|
||||
case m: MeetingInfoAnalyticsMsg => logMessage(msg)
|
||||
|
||||
// Layout
|
||||
case m: BroadcastLayoutMsg => logMessage(msg)
|
||||
case m: BroadcastLayoutEvtMsg => logMessage(msg)
|
||||
|
||||
case _ => // ignore message
|
||||
}
|
||||
|
@ -160,6 +160,17 @@ object MsgBuilder {
|
||||
BbbCommonEnvCoreMsg(envelope, event)
|
||||
}
|
||||
|
||||
def buildStopExternalVideoEvtMsg(meetingId: String): BbbCommonEnvCoreMsg = {
|
||||
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, meetingId, "nodeJSapp")
|
||||
val envelope = BbbCoreEnvelope(StopExternalVideoEvtMsg.NAME, routing)
|
||||
|
||||
val body = StopExternalVideoEvtMsgBody()
|
||||
val header = BbbClientMsgHeader(StopExternalVideoEvtMsg.NAME, meetingId, "not-used")
|
||||
val event = StopExternalVideoEvtMsg(header, body)
|
||||
|
||||
BbbCommonEnvCoreMsg(envelope, event)
|
||||
}
|
||||
|
||||
def buildMeetingCreatedEvtMsg(meetingId: String, props: DefaultProps): BbbCommonEnvCoreMsg = {
|
||||
val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
|
||||
val envelope = BbbCoreEnvelope(MeetingCreatedEvtMsg.NAME, routing)
|
||||
@ -169,6 +180,35 @@ object MsgBuilder {
|
||||
BbbCommonEnvCoreMsg(envelope, event)
|
||||
}
|
||||
|
||||
def buildMeetingInfoAnalyticsMsg(analytics: MeetingInfoAnalytics): BbbCommonEnvCoreMsg = {
|
||||
val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
|
||||
val envelope = BbbCoreEnvelope(MeetingInfoAnalyticsMsg.NAME, routing)
|
||||
val header = BbbCoreBaseHeader(MeetingInfoAnalyticsMsg.NAME)
|
||||
val body = MeetingInfoAnalyticsMsgBody(analytics)
|
||||
val event = MeetingInfoAnalyticsMsg(header, body)
|
||||
|
||||
BbbCommonEnvCoreMsg(envelope, event)
|
||||
}
|
||||
|
||||
def buildMeetingInfoAnalyticsServiceMsg(analytics: MeetingInfoAnalytics): BbbCommonEnvCoreMsg = {
|
||||
val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
|
||||
val envelope = BbbCoreEnvelope(MeetingInfoAnalyticsServiceMsg.NAME, routing)
|
||||
val header = BbbCoreBaseHeader(MeetingInfoAnalyticsServiceMsg.NAME)
|
||||
val body = MeetingInfoAnalyticsMsgBody(analytics)
|
||||
val event = MeetingInfoAnalyticsServiceMsg(header, body)
|
||||
BbbCommonEnvCoreMsg(envelope, event)
|
||||
}
|
||||
|
||||
def buildCamStreamSubscribeSysMsg(meetingId: String, userId: String, streamId: String, sfuSessionId: String): BbbCommonEnvCoreMsg = {
|
||||
val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
|
||||
val envelope = BbbCoreEnvelope(CamStreamSubscribeSysMsg.NAME, routing)
|
||||
val header = BbbCoreBaseHeader(CamStreamSubscribeSysMsg.NAME)
|
||||
val body = CamStreamSubscribeSysMsgBody(meetingId, userId, streamId, sfuSessionId)
|
||||
val event = CamStreamSubscribeSysMsg(header, body)
|
||||
|
||||
BbbCommonEnvCoreMsg(envelope, event)
|
||||
}
|
||||
|
||||
def buildMeetingDestroyedEvtMsg(meetingId: String): BbbCommonEnvCoreMsg = {
|
||||
val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
|
||||
val envelope = BbbCoreEnvelope(MeetingDestroyedEvtMsg.NAME, routing)
|
||||
@ -504,4 +544,15 @@ object MsgBuilder {
|
||||
|
||||
BbbCommonEnvCoreMsg(envelope, event)
|
||||
}
|
||||
|
||||
def buildLearningDashboardEvtMsg(meetingId: String, activityJson: String): BbbCommonEnvCoreMsg = {
|
||||
val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
|
||||
val envelope = BbbCoreEnvelope(LearningDashboardEvtMsg.NAME, routing)
|
||||
val body = LearningDashboardEvtMsgBody(activityJson)
|
||||
val header = BbbCoreHeaderWithMeetingId(LearningDashboardEvtMsg.NAME, meetingId)
|
||||
val event = LearningDashboardEvtMsg(header, body)
|
||||
|
||||
BbbCommonEnvCoreMsg(envelope, event)
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -0,0 +1,433 @@
|
||||
package org.bigbluebutton.endpoint.redis
|
||||
|
||||
import akka.actor.{Actor, ActorLogging, ActorSystem, Props}
|
||||
import org.bigbluebutton.common2.msgs._
|
||||
import org.bigbluebutton.common2.util.JsonUtil
|
||||
import org.bigbluebutton.core.OutMessageGateway
|
||||
import org.bigbluebutton.core.apps.groupchats.GroupChatApp
|
||||
import org.bigbluebutton.core.models.Roles
|
||||
import org.bigbluebutton.core2.message.senders.MsgBuilder
|
||||
|
||||
import java.security.MessageDigest
|
||||
import scala.concurrent.duration._
|
||||
import scala.concurrent._
|
||||
import ExecutionContext.Implicits.global
|
||||
|
||||
case object SendPeriodicReport
|
||||
|
||||
case class Meeting(
|
||||
intId: String,
|
||||
extId: String,
|
||||
name: String,
|
||||
learningDashboardAccessToken: String,
|
||||
users: Map[String, User] = Map(),
|
||||
polls: Map[String, Poll] = Map(),
|
||||
screenshares: Vector[Screenshare] = Vector(),
|
||||
createdOn: Long = System.currentTimeMillis(),
|
||||
endedOn: Long = 0,
|
||||
)
|
||||
|
||||
case class User(
|
||||
intId: String,
|
||||
extId: String,
|
||||
name: String,
|
||||
isModerator: Boolean,
|
||||
isDialIn: Boolean = false,
|
||||
answers: Map[String,String] = Map(),
|
||||
talk: Talk = Talk(),
|
||||
emojis: Vector[Emoji] = Vector(),
|
||||
webcams: Vector[Webcam] = Vector(),
|
||||
totalOfMessages: Long = 0,
|
||||
registeredOn: Long = System.currentTimeMillis(),
|
||||
leftOn: Long = 0,
|
||||
)
|
||||
|
||||
case class Poll(
|
||||
pollId: String,
|
||||
pollType: String,
|
||||
anonymous: Boolean,
|
||||
question: String,
|
||||
options: Vector[String] = Vector(),
|
||||
anonymousAnswers: Vector[String] = Vector(),
|
||||
createdOn: Long = System.currentTimeMillis(),
|
||||
)
|
||||
|
||||
case class Talk(
|
||||
totalTime: Long = 0,
|
||||
lastTalkStartedOn: Long = 0,
|
||||
)
|
||||
|
||||
case class Emoji(
|
||||
name: String,
|
||||
sentOn: Long = System.currentTimeMillis(),
|
||||
)
|
||||
|
||||
case class Webcam(
|
||||
startedOn: Long = System.currentTimeMillis(),
|
||||
stoppedOn: Long = 0,
|
||||
)
|
||||
|
||||
case class Screenshare(
|
||||
startedOn: Long = System.currentTimeMillis(),
|
||||
stoppedOn: Long = 0,
|
||||
)
|
||||
|
||||
|
||||
object LearningDashboardActor {
|
||||
def props(
|
||||
system: ActorSystem,
|
||||
outGW: OutMessageGateway,
|
||||
): Props =
|
||||
Props(
|
||||
classOf[LearningDashboardActor],
|
||||
system,
|
||||
outGW
|
||||
)
|
||||
}
|
||||
|
||||
class LearningDashboardActor(
|
||||
system: ActorSystem,
|
||||
val outGW: OutMessageGateway,
|
||||
) extends Actor with ActorLogging {
|
||||
|
||||
private var meetings: Map[String, Meeting] = Map()
|
||||
private var meetingsLastJsonHash : Map[String,String] = Map()
|
||||
|
||||
system.scheduler.schedule(10.seconds, 10.seconds, self, SendPeriodicReport)
|
||||
|
||||
def receive = {
|
||||
//=============================
|
||||
// 2x messages
|
||||
case msg: BbbCommonEnvCoreMsg => handleBbbCommonEnvCoreMsg(msg)
|
||||
case SendPeriodicReport => sendPeriodicReport()
|
||||
case _ => // do nothing
|
||||
}
|
||||
|
||||
private def handleBbbCommonEnvCoreMsg(msg: BbbCommonEnvCoreMsg): Unit = {
|
||||
msg.core match {
|
||||
// Chat
|
||||
case m: GroupChatMessageBroadcastEvtMsg => handleGroupChatMessageBroadcastEvtMsg(m)
|
||||
|
||||
// User
|
||||
case m: UserJoinedMeetingEvtMsg => handleUserJoinedMeetingEvtMsg(m)
|
||||
case m: UserLeftMeetingEvtMsg => handleUserLeftMeetingEvtMsg(m)
|
||||
case m: UserEmojiChangedEvtMsg => handleUserEmojiChangedEvtMsg(m)
|
||||
case m: UserRoleChangedEvtMsg => handleUserRoleChangedEvtMsg(m)
|
||||
case m: UserBroadcastCamStartedEvtMsg => handleUserBroadcastCamStartedEvtMsg(m)
|
||||
case m: UserBroadcastCamStoppedEvtMsg => handleUserBroadcastCamStoppedEvtMsg(m)
|
||||
|
||||
// Voice
|
||||
case m: UserJoinedVoiceConfToClientEvtMsg => handleUserJoinedVoiceConfToClientEvtMsg(m)
|
||||
case m: UserLeftVoiceConfToClientEvtMsg => handleUserLeftVoiceConfToClientEvtMsg(m)
|
||||
case m: UserMutedVoiceEvtMsg => handleUserMutedVoiceEvtMsg(m)
|
||||
case m: UserTalkingVoiceEvtMsg => handleUserTalkingVoiceEvtMsg(m)
|
||||
|
||||
// Screenshare
|
||||
case m: ScreenshareRtmpBroadcastStartedEvtMsg => handleScreenshareRtmpBroadcastStartedEvtMsg(m)
|
||||
case m: ScreenshareRtmpBroadcastStoppedEvtMsg => handleScreenshareRtmpBroadcastStoppedEvtMsg(m)
|
||||
|
||||
// Meeting
|
||||
case m: CreateMeetingReqMsg => handleCreateMeetingReqMsg(m)
|
||||
case m: MeetingEndingEvtMsg => handleMeetingEndingEvtMsg(m)
|
||||
|
||||
// Poll
|
||||
case m: PollStartedEvtMsg => handlePollStartedEvtMsg(m)
|
||||
case m: UserRespondedToPollRecordMsg => handleUserRespondedToPollRecordMsg(m)
|
||||
|
||||
case _ => // message not to be handled.
|
||||
}
|
||||
}
|
||||
|
||||
private def handleGroupChatMessageBroadcastEvtMsg(msg: GroupChatMessageBroadcastEvtMsg) {
|
||||
if (msg.body.chatId == GroupChatApp.MAIN_PUBLIC_CHAT) {
|
||||
for {
|
||||
meeting <- meetings.values.find(m => m.intId == msg.header.meetingId)
|
||||
user <- meeting.users.values.find(u => u.intId == msg.header.userId)
|
||||
} yield {
|
||||
val updatedUser = user.copy(totalOfMessages = user.totalOfMessages + 1)
|
||||
val updatedMeeting = meeting.copy(users = meeting.users + (updatedUser.intId -> updatedUser))
|
||||
meetings += (updatedMeeting.intId -> updatedMeeting)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def handleUserJoinedMeetingEvtMsg(msg: UserJoinedMeetingEvtMsg): Unit = {
|
||||
for {
|
||||
meeting <- meetings.values.find(m => m.intId == msg.header.meetingId)
|
||||
} yield {
|
||||
val user: User = meeting.users.values.find(u => u.intId == msg.body.intId).getOrElse({
|
||||
User(
|
||||
msg.body.intId, msg.body.extId, msg.body.name, (msg.body.role == Roles.MODERATOR_ROLE)
|
||||
)
|
||||
})
|
||||
|
||||
meetings += (meeting.intId -> meeting.copy(users = meeting.users + (user.intId -> user.copy(leftOn = 0))))
|
||||
}
|
||||
}
|
||||
|
||||
private def handleUserLeftMeetingEvtMsg(msg: UserLeftMeetingEvtMsg): Unit = {
|
||||
for {
|
||||
meeting <- meetings.values.find(m => m.intId == msg.header.meetingId)
|
||||
user <- meeting.users.values.find(u => u.intId == msg.body.intId)
|
||||
} yield {
|
||||
val updatedUser = user.copy(leftOn = System.currentTimeMillis())
|
||||
val updatedMeeting = meeting.copy(users = meeting.users + (updatedUser.intId -> updatedUser))
|
||||
|
||||
meetings += (updatedMeeting.intId -> updatedMeeting)
|
||||
}
|
||||
}
|
||||
|
||||
private def handleUserEmojiChangedEvtMsg(msg: UserEmojiChangedEvtMsg) {
|
||||
for {
|
||||
meeting <- meetings.values.find(m => m.intId == msg.header.meetingId)
|
||||
user <- meeting.users.values.find(u => u.intId == msg.body.userId)
|
||||
} yield {
|
||||
if (msg.body.emoji != "none") {
|
||||
val updatedUser = user.copy(emojis = user.emojis :+ Emoji(msg.body.emoji))
|
||||
val updatedMeeting = meeting.copy(users = meeting.users + (updatedUser.intId -> updatedUser))
|
||||
|
||||
meetings += (updatedMeeting.intId -> updatedMeeting)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def handleUserRoleChangedEvtMsg(msg: UserRoleChangedEvtMsg) {
|
||||
if(msg.body.role == Roles.MODERATOR_ROLE) {
|
||||
for {
|
||||
meeting <- meetings.values.find(m => m.intId == msg.header.meetingId)
|
||||
user <- meeting.users.values.find(u => u.intId == msg.body.userId)
|
||||
} yield {
|
||||
val updatedUser = user.copy(isModerator = true)
|
||||
val updatedMeeting = meeting.copy(users = meeting.users + (updatedUser.intId -> updatedUser))
|
||||
|
||||
meetings += (updatedMeeting.intId -> updatedMeeting)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def handleUserBroadcastCamStartedEvtMsg(msg: UserBroadcastCamStartedEvtMsg) {
|
||||
for {
|
||||
meeting <- meetings.values.find(m => m.intId == msg.header.meetingId)
|
||||
user <- meeting.users.values.find(u => u.intId == msg.body.userId)
|
||||
} yield {
|
||||
|
||||
val updatedUser = user.copy(webcams = user.webcams :+ Webcam())
|
||||
val updatedMeeting = meeting.copy(users = meeting.users + (updatedUser.intId -> updatedUser))
|
||||
meetings += (updatedMeeting.intId -> updatedMeeting)
|
||||
}
|
||||
}
|
||||
|
||||
private def handleUserBroadcastCamStoppedEvtMsg(msg: UserBroadcastCamStoppedEvtMsg) {
|
||||
for {
|
||||
meeting <- meetings.values.find(m => m.intId == msg.header.meetingId)
|
||||
user <- meeting.users.values.find(u => u.intId == msg.body.userId)
|
||||
} yield {
|
||||
val lastWebcam: Webcam = user.webcams.last.copy(stoppedOn = System.currentTimeMillis())
|
||||
val updatedUser = user.copy(webcams = user.webcams.dropRight(1) :+ lastWebcam)
|
||||
val updatedMeeting = meeting.copy(users = meeting.users + (updatedUser.intId -> updatedUser))
|
||||
meetings += (updatedMeeting.intId -> updatedMeeting)
|
||||
}
|
||||
}
|
||||
|
||||
private def handleUserJoinedVoiceConfToClientEvtMsg(msg: UserJoinedVoiceConfToClientEvtMsg): Unit = {
|
||||
//Create users for Dial-in connections
|
||||
if(msg.body.intId.startsWith("v_")) {
|
||||
for {
|
||||
meeting <- meetings.values.find(m => m.intId == msg.header.meetingId)
|
||||
} yield {
|
||||
val user: User = meeting.users.values.find(u => u.intId == msg.body.intId).getOrElse({
|
||||
User(
|
||||
msg.body.intId, msg.body.callerNum, msg.body.callerName, false, true
|
||||
)
|
||||
})
|
||||
|
||||
meetings += (meeting.intId -> meeting.copy(users = meeting.users + (user.intId -> user.copy(leftOn = 0))))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def handleUserLeftVoiceConfToClientEvtMsg(msg: UserLeftVoiceConfToClientEvtMsg) {
|
||||
for {
|
||||
meeting <- meetings.values.find(m => m.intId == msg.header.meetingId)
|
||||
user <- meeting.users.values.find(u => u.intId == msg.body.intId)
|
||||
} yield {
|
||||
endUserTalk(meeting, user)
|
||||
|
||||
if(user.isDialIn) {
|
||||
val updatedUser = user.copy(leftOn = System.currentTimeMillis())
|
||||
val updatedMeeting = meeting.copy(users = meeting.users + (updatedUser.intId -> updatedUser))
|
||||
|
||||
meetings += (updatedMeeting.intId -> updatedMeeting)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def handleUserMutedVoiceEvtMsg(msg: UserMutedVoiceEvtMsg) {
|
||||
for {
|
||||
meeting <- meetings.values.find(m => m.intId == msg.header.meetingId)
|
||||
user <- meeting.users.values.find(u => u.intId == msg.body.intId)
|
||||
} yield {
|
||||
endUserTalk(meeting, user)
|
||||
}
|
||||
}
|
||||
|
||||
private def handleUserTalkingVoiceEvtMsg(msg: UserTalkingVoiceEvtMsg) {
|
||||
for {
|
||||
meeting <- meetings.values.find(m => m.intId == msg.header.meetingId)
|
||||
user <- meeting.users.values.find(u => u.intId == msg.body.intId)
|
||||
} yield {
|
||||
if(msg.body.talking) {
|
||||
val updatedUser = user.copy(talk = user.talk.copy(lastTalkStartedOn = System.currentTimeMillis()))
|
||||
val updatedMeeting = meeting.copy(users = meeting.users + (updatedUser.intId -> updatedUser))
|
||||
meetings += (updatedMeeting.intId -> updatedMeeting)
|
||||
} else {
|
||||
endUserTalk(meeting, user)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def endUserTalk(meeting: Meeting, user: User): Unit = {
|
||||
if(user.talk.lastTalkStartedOn > 0) {
|
||||
val updatedUser = user.copy(
|
||||
talk = user.talk.copy(
|
||||
lastTalkStartedOn = 0,
|
||||
totalTime = user.talk.totalTime + (System.currentTimeMillis() - user.talk.lastTalkStartedOn)
|
||||
)
|
||||
)
|
||||
val updatedMeeting = meeting.copy(users = meeting.users + (updatedUser.intId -> updatedUser))
|
||||
meetings += (updatedMeeting.intId -> updatedMeeting)
|
||||
}
|
||||
}
|
||||
|
||||
private def handlePollStartedEvtMsg(msg: PollStartedEvtMsg): Unit = {
|
||||
for {
|
||||
meeting <- meetings.values.find(m => m.intId == msg.header.meetingId)
|
||||
} yield {
|
||||
val options = msg.body.poll.answers.map(answer => answer.key)
|
||||
val newPoll = Poll(msg.body.pollId, msg.body.pollType, msg.body.secretPoll, msg.body.question, options.toVector)
|
||||
|
||||
val updatedMeeting = meeting.copy(polls = meeting.polls + (newPoll.pollId -> newPoll))
|
||||
meetings += (updatedMeeting.intId -> updatedMeeting)
|
||||
}
|
||||
}
|
||||
|
||||
private def handleUserRespondedToPollRecordMsg(msg: UserRespondedToPollRecordMsg): Unit = {
|
||||
for {
|
||||
meeting <- meetings.values.find(m => m.intId == msg.header.meetingId)
|
||||
user <- meeting.users.values.find(u => u.intId == msg.header.userId)
|
||||
} yield {
|
||||
if(msg.body.isSecret) {
|
||||
//Store Anonymous Poll in `poll.anonymousAnswers`
|
||||
for {
|
||||
poll <- meeting.polls.find(p => p._1 == msg.body.pollId)
|
||||
} yield {
|
||||
val updatedPoll = poll._2.copy(anonymousAnswers = poll._2.anonymousAnswers :+ msg.body.answer)
|
||||
val updatedMeeting = meeting.copy(polls = meeting.polls + (poll._1 -> updatedPoll))
|
||||
meetings += (updatedMeeting.intId -> updatedMeeting)
|
||||
}
|
||||
} else {
|
||||
//Store Public Poll in `user.answers`
|
||||
val updatedUser = user.copy(answers = user.answers + (msg.body.pollId -> msg.body.answer))
|
||||
val updatedMeeting = meeting.copy(users = meeting.users + (updatedUser.intId -> updatedUser))
|
||||
meetings += (updatedMeeting.intId -> updatedMeeting)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private def handleScreenshareRtmpBroadcastStartedEvtMsg(msg: ScreenshareRtmpBroadcastStartedEvtMsg) {
|
||||
for {
|
||||
meeting <- meetings.values.find(m => m.intId == msg.header.meetingId)
|
||||
} yield {
|
||||
val updatedMeeting = meeting.copy(screenshares = meeting.screenshares :+ Screenshare())
|
||||
meetings += (updatedMeeting.intId -> updatedMeeting)
|
||||
}
|
||||
}
|
||||
|
||||
private def handleScreenshareRtmpBroadcastStoppedEvtMsg(msg: ScreenshareRtmpBroadcastStoppedEvtMsg) {
|
||||
for {
|
||||
meeting <- meetings.values.find(m => m.intId == msg.header.meetingId)
|
||||
} yield {
|
||||
val lastScreenshare: Screenshare = meeting.screenshares.last.copy(stoppedOn = System.currentTimeMillis())
|
||||
val updatedMeeting = meeting.copy(screenshares = meeting.screenshares.dropRight(1) :+ lastScreenshare)
|
||||
meetings += (updatedMeeting.intId -> updatedMeeting)
|
||||
}
|
||||
}
|
||||
|
||||
private def handleCreateMeetingReqMsg(msg: CreateMeetingReqMsg): Unit = {
|
||||
if(msg.body.props.meetingProp.learningDashboardEnabled) {
|
||||
val newMeeting = Meeting(
|
||||
msg.body.props.meetingProp.intId,
|
||||
msg.body.props.meetingProp.extId,
|
||||
msg.body.props.meetingProp.name,
|
||||
msg.body.props.password.learningDashboardAccessToken,
|
||||
)
|
||||
|
||||
meetings += (newMeeting.intId -> newMeeting)
|
||||
|
||||
log.info(" created for meeting {}.",msg.body.props.meetingProp.intId)
|
||||
} else {
|
||||
log.info(" disabled for meeting {}.",msg.body.props.meetingProp.intId)
|
||||
}
|
||||
}
|
||||
|
||||
private def handleMeetingEndingEvtMsg(msg: MeetingEndingEvtMsg): Unit = {
|
||||
for {
|
||||
meeting <- meetings.values.find(m => m.intId == msg.body.meetingId)
|
||||
} yield {
|
||||
//Update endedOn and screenshares.stoppedOn, user.totalTime talks, webcams.stoppedOn
|
||||
val endedOn : Long = System.currentTimeMillis()
|
||||
val updatedMeeting = meeting.copy(
|
||||
endedOn = endedOn,
|
||||
screenshares = meeting.screenshares.map(screenshare => {
|
||||
if(screenshare.stoppedOn > 0) screenshare;
|
||||
else screenshare.copy(stoppedOn = endedOn)
|
||||
}),
|
||||
users = meeting.users.map(user => {
|
||||
(user._1 ->
|
||||
user._2.copy(
|
||||
leftOn = if(user._2.leftOn > 0) user._2.leftOn else endedOn,
|
||||
talk = user._2.talk.copy(
|
||||
totalTime = user._2.talk.totalTime + (if (user._2.talk.lastTalkStartedOn > 0) (endedOn - user._2.talk.lastTalkStartedOn) else 0),
|
||||
lastTalkStartedOn = 0
|
||||
),
|
||||
webcams = user._2.webcams.map(webcam => {
|
||||
if(webcam.stoppedOn > 0) webcam
|
||||
else webcam.copy(stoppedOn = endedOn)
|
||||
})
|
||||
))
|
||||
})
|
||||
)
|
||||
|
||||
meetings += (updatedMeeting.intId -> updatedMeeting)
|
||||
|
||||
//Send report one last time
|
||||
sendReport(updatedMeeting)
|
||||
|
||||
meetings = meetings.-(updatedMeeting.intId)
|
||||
log.info(" removed for meeting {}.",updatedMeeting.intId)
|
||||
}
|
||||
}
|
||||
|
||||
private def sendPeriodicReport(): Unit = {
|
||||
meetings.map(meeting => {
|
||||
sendReport(meeting._2)
|
||||
})
|
||||
}
|
||||
|
||||
private def sendReport(meeting : Meeting): Unit = {
|
||||
val activityJson: String = JsonUtil.toJson(meeting)
|
||||
|
||||
//Avoid send repeated activity jsons
|
||||
val activityJsonHash : String = MessageDigest.getInstance("MD5").digest(activityJson.getBytes).mkString
|
||||
if(!meetingsLastJsonHash.contains(meeting.intId) || meetingsLastJsonHash.get(meeting.intId).getOrElse("") != activityJsonHash) {
|
||||
|
||||
val event = MsgBuilder.buildLearningDashboardEvtMsg(meeting.intId, activityJson)
|
||||
outGW.send(event)
|
||||
|
||||
meetingsLastJsonHash += (meeting.intId -> activityJsonHash)
|
||||
|
||||
log.info("Activity Report sent for meeting {}",meeting.intId)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -85,6 +85,7 @@ class RedisRecorderActor(
|
||||
case m: UserLeftMeetingEvtMsg => handleUserLeftMeetingEvtMsg(m)
|
||||
case m: PresenterAssignedEvtMsg => handlePresenterAssignedEvtMsg(m)
|
||||
case m: UserEmojiChangedEvtMsg => handleUserEmojiChangedEvtMsg(m)
|
||||
case m: UserRoleChangedEvtMsg => handleUserRoleChangedEvtMsg(m)
|
||||
case m: UserBroadcastCamStartedEvtMsg => handleUserBroadcastCamStartedEvtMsg(m)
|
||||
case m: UserBroadcastCamStoppedEvtMsg => handleUserBroadcastCamStoppedEvtMsg(m)
|
||||
|
||||
@ -357,6 +358,10 @@ class RedisRecorderActor(
|
||||
handleUserStatusChange(msg.header.meetingId, msg.body.userId, "emojiStatus", msg.body.emoji)
|
||||
}
|
||||
|
||||
private def handleUserRoleChangedEvtMsg(msg: UserRoleChangedEvtMsg) {
|
||||
handleUserStatusChange(msg.header.meetingId, msg.body.userId, "role", msg.body.role)
|
||||
}
|
||||
|
||||
private def handleUserBroadcastCamStartedEvtMsg(msg: UserBroadcastCamStartedEvtMsg) {
|
||||
handleUserStatusChange(msg.header.meetingId, msg.body.userId, "hasStream", "true,stream=" + msg.body.stream)
|
||||
}
|
||||
@ -555,7 +560,10 @@ class RedisRecorderActor(
|
||||
private def handlePollStartedEvtMsg(msg: PollStartedEvtMsg): Unit = {
|
||||
val ev = new PollStartedRecordEvent()
|
||||
ev.setPollId(msg.body.pollId)
|
||||
ev.setQuestion(msg.body.question)
|
||||
ev.setAnswers(msg.body.poll.answers)
|
||||
ev.setType(msg.body.pollType)
|
||||
ev.setSecretPoll(msg.body.secretPoll)
|
||||
|
||||
record(msg.header.meetingId, ev.toMap.asJava)
|
||||
}
|
||||
@ -563,25 +571,33 @@ class RedisRecorderActor(
|
||||
private def handleUserRespondedToPollRecordMsg(msg: UserRespondedToPollRecordMsg): Unit = {
|
||||
val ev = new UserRespondedToPollRecordEvent()
|
||||
ev.setPollId(msg.body.pollId)
|
||||
ev.setUserId(msg.header.userId)
|
||||
if (msg.body.isSecret) {
|
||||
ev.setUserId("")
|
||||
} else {
|
||||
ev.setUserId(msg.header.userId)
|
||||
}
|
||||
ev.setAnswerId(msg.body.answerIds.toArray)
|
||||
ev.setAnswer(msg.body.answer)
|
||||
|
||||
record(msg.header.meetingId, ev.toMap.asJava)
|
||||
}
|
||||
|
||||
private def handlePollStoppedEvtMsg(msg: PollStoppedEvtMsg): Unit = {
|
||||
pollStoppedRecordHelper(msg.header.meetingId, msg.body.pollId)
|
||||
val ev = new PollStoppedRecordEvent()
|
||||
ev.setPollId(msg.body.pollId)
|
||||
|
||||
record(msg.header.meetingId, ev.toMap.asJava)
|
||||
}
|
||||
|
||||
private def handlePollShowResultEvtMsg(msg: PollShowResultEvtMsg): Unit = {
|
||||
pollStoppedRecordHelper(msg.header.meetingId, msg.body.pollId)
|
||||
}
|
||||
val ev = new PollPublishedRecordEvent()
|
||||
ev.setPollId(msg.body.pollId)
|
||||
ev.setQuestion(msg.body.poll.questionText.getOrElse(""))
|
||||
ev.setAnswers(msg.body.poll.answers)
|
||||
ev.setNumRespondents(msg.body.poll.numRespondents)
|
||||
ev.setNumResponders(msg.body.poll.numResponders)
|
||||
|
||||
private def pollStoppedRecordHelper(meetingId: String, pollId: String): Unit = {
|
||||
val ev = new PollStoppedRecordEvent()
|
||||
ev.setPollId(pollId)
|
||||
|
||||
record(meetingId, ev.toMap.asJava)
|
||||
record(msg.header.meetingId, ev.toMap.asJava)
|
||||
}
|
||||
|
||||
private def checkRecordingDBStatus(): Unit = {
|
||||
|
@ -0,0 +1,95 @@
|
||||
package org.bigbluebutton.service
|
||||
|
||||
import akka.actor.{ Actor, ActorLogging, ActorRef, ActorSystem, Props }
|
||||
import akka.pattern.ask
|
||||
import akka.pattern.AskTimeoutException
|
||||
import akka.util.Timeout
|
||||
import org.bigbluebutton.MeetingInfoAnalytics
|
||||
import org.bigbluebutton.common2.msgs.{ BbbCommonEnvCoreMsg, MeetingEndingEvtMsg, MeetingInfoAnalyticsServiceMsg }
|
||||
|
||||
import scala.collection.mutable
|
||||
import scala.concurrent.duration.DurationInt
|
||||
import scala.concurrent.{ ExecutionContextExecutor, Future }
|
||||
|
||||
sealed trait MeetingInfoMessage
|
||||
|
||||
case class GetMeetingInfoMessage(meetingId: String) extends MeetingInfoMessage
|
||||
case object GetMeetingsInfoMessage extends MeetingInfoMessage
|
||||
case class MeetingInfoResponseMsg(optionMeetingInfoAnalytics: Option[MeetingInfoAnalytics]) extends MeetingInfoMessage
|
||||
case class MeetingInfoListResponseMsg(optionMeetingsInfoAnalytics: Option[List[MeetingInfoAnalytics]]) extends MeetingInfoMessage
|
||||
|
||||
object MeetingInfoService {
|
||||
def apply(system: ActorSystem, meetingInfoActor: ActorRef) = new MeetingInfoService(system, meetingInfoActor)
|
||||
}
|
||||
|
||||
class MeetingInfoService(system: ActorSystem, meetingInfoActor: ActorRef) {
|
||||
implicit def executionContext: ExecutionContextExecutor = system.dispatcher
|
||||
implicit val timeout: Timeout = 2 seconds
|
||||
|
||||
def getAnalytics(): Future[MeetingInfoListResponseMsg] = {
|
||||
val future = meetingInfoActor.ask(GetMeetingsInfoMessage).mapTo[MeetingInfoListResponseMsg]
|
||||
|
||||
future.recover {
|
||||
case e: AskTimeoutException => {
|
||||
MeetingInfoListResponseMsg(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
def getAnalytics(meetingId: String): Future[MeetingInfoResponseMsg] = {
|
||||
val future = meetingInfoActor.ask(GetMeetingInfoMessage(meetingId)).mapTo[MeetingInfoResponseMsg]
|
||||
|
||||
future.recover {
|
||||
case e: AskTimeoutException => {
|
||||
MeetingInfoResponseMsg(None)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
object MeetingInfoActor {
|
||||
def props(): Props = Props(classOf[MeetingInfoActor])
|
||||
}
|
||||
|
||||
class MeetingInfoActor extends Actor with ActorLogging {
|
||||
var optionMeetingInfo: Option[MeetingInfoAnalytics] = None
|
||||
var meetingInfoMap: mutable.HashMap[String, MeetingInfoAnalytics] = mutable.HashMap.empty[String, MeetingInfoAnalytics]
|
||||
|
||||
override def receive: Receive = {
|
||||
case msg: BbbCommonEnvCoreMsg => handle(msg)
|
||||
case GetMeetingsInfoMessage =>
|
||||
if (meetingInfoMap.size > 0) {
|
||||
sender ! MeetingInfoListResponseMsg(Option(meetingInfoMap.values.toList))
|
||||
} else {
|
||||
sender ! MeetingInfoListResponseMsg(None)
|
||||
}
|
||||
case GetMeetingInfoMessage(meetingId) =>
|
||||
meetingInfoMap.get(meetingId) match {
|
||||
case Some(meetingInfoAnalytics) =>
|
||||
sender ! MeetingInfoResponseMsg(Option(meetingInfoAnalytics))
|
||||
case None => sender ! MeetingInfoResponseMsg(None)
|
||||
}
|
||||
case _ => // ignore other messages
|
||||
}
|
||||
|
||||
def handle(msg: BbbCommonEnvCoreMsg): Unit = {
|
||||
msg.core match {
|
||||
case m: MeetingInfoAnalyticsServiceMsg =>
|
||||
val meetingInternalId = m.body.meetingInfo.internalId
|
||||
|
||||
optionMeetingInfo = Option.apply(MeetingInfoAnalytics(m.body.meetingInfo.name, m.body.meetingInfo.externalId,
|
||||
meetingInternalId, m.body.meetingInfo.hasUserJoined, m.body.meetingInfo.isMeetingRecorded, m.body.meetingInfo.webcams,
|
||||
m.body.meetingInfo.audio, m.body.meetingInfo.screenshare, m.body.meetingInfo.users, m.body.meetingInfo.presentation,
|
||||
m.body.meetingInfo.breakoutRoom))
|
||||
|
||||
meetingInfoMap.get(meetingInternalId) match {
|
||||
case Some(_) => {
|
||||
meetingInfoMap(meetingInternalId) = optionMeetingInfo.get
|
||||
}
|
||||
case None => meetingInfoMap += (meetingInternalId -> optionMeetingInfo.get)
|
||||
}
|
||||
case m: MeetingEndingEvtMsg => meetingInfoMap -= m.body.meetingId
|
||||
case _ => // ignore
|
||||
}
|
||||
}
|
||||
}
|
@ -29,6 +29,7 @@ trait AppsTestFixtures {
|
||||
val webcamsOnlyForModerator = false;
|
||||
val moderatorPassword = "modpass"
|
||||
val viewerPassword = "viewpass"
|
||||
val learningDashboardAccessToken = "ldToken"
|
||||
val createTime = System.currentTimeMillis
|
||||
val createDate = "Oct 26, 2015"
|
||||
val isBreakout = false
|
||||
@ -52,7 +53,7 @@ trait AppsTestFixtures {
|
||||
val durationProps = DurationProps(duration = durationInMinutes, createdTime = createTime, createdDate = createDate,
|
||||
meetingExpireIfNoUserJoinedInMinutes = meetingExpireIfNoUserJoinedInMinutes, meetingExpireWhenLastUserLeftInMinutes = meetingExpireWhenLastUserLeftInMinutes,
|
||||
userInactivityInspectTimerInMinutes = userInactivityInspectTimerInMinutes, userInactivityThresholdInMinutes = userInactivityInspectTimerInMinutes, userActivitySignResponseDelayInMinutes = userActivitySignResponseDelayInMinutes)
|
||||
val password = PasswordProp(moderatorPass = moderatorPassword, viewerPass = viewerPassword)
|
||||
val password = PasswordProp(moderatorPass = moderatorPassword, viewerPass = viewerPassword, learningDashboardAccessToken = learningDashboardAccessToken)
|
||||
val recordProp = RecordProp(record = record, autoStartRecording = autoStartRecording,
|
||||
allowStartStopRecording = allowStartStopRecording, keepEvents = keepEvents )
|
||||
val welcomeProp = WelcomeProp(welcomeMsgTemplate = welcomeMsgTemplate, welcomeMsg = welcomeMsg,
|
||||
|
@ -4,9 +4,11 @@ case class ConfigProps(defaultConfigToken: String, config: String)
|
||||
|
||||
case class DurationProps(duration: Int, createdTime: Long, createdDate: String,
|
||||
meetingExpireIfNoUserJoinedInMinutes: Int, meetingExpireWhenLastUserLeftInMinutes: Int,
|
||||
userInactivityInspectTimerInMinutes: Int, userInactivityThresholdInMinutes: Int, userActivitySignResponseDelayInMinutes: Int)
|
||||
userInactivityInspectTimerInMinutes: Int, userInactivityThresholdInMinutes: Int,
|
||||
userActivitySignResponseDelayInMinutes: Int,
|
||||
endWhenNoModerator: Boolean, endWhenNoModeratorDelayInMinutes: Int)
|
||||
|
||||
case class MeetingProp(name: String, extId: String, intId: String, isBreakout: Boolean)
|
||||
case class MeetingProp(name: String, extId: String, intId: String, isBreakout: Boolean, learningDashboardEnabled: Boolean)
|
||||
|
||||
case class BreakoutProps(
|
||||
parentId: String,
|
||||
@ -18,7 +20,7 @@ case class BreakoutProps(
|
||||
privateChatEnabled: Boolean
|
||||
)
|
||||
|
||||
case class PasswordProp(moderatorPass: String, viewerPass: String)
|
||||
case class PasswordProp(moderatorPass: String, viewerPass: String, learningDashboardAccessToken: String)
|
||||
|
||||
case class RecordProp(record: Boolean, autoStartRecording: Boolean, allowStartStopRecording: Boolean, keepEvents: Boolean)
|
||||
|
||||
@ -26,7 +28,7 @@ case class WelcomeProp(welcomeMsgTemplate: String, welcomeMsg: String, modOnlyMe
|
||||
|
||||
case class VoiceProp(telVoice: String, voiceConf: String, dialNumber: String, muteOnStart: Boolean)
|
||||
|
||||
case class UsersProp(maxUsers: Int, webcamsOnlyForModerator: Boolean, guestPolicy: String, allowModsToUnmuteUsers: Boolean, authenticatedGuest: Boolean)
|
||||
case class UsersProp(maxUsers: Int, webcamsOnlyForModerator: Boolean, guestPolicy: String, meetingLayout: String, allowModsToUnmuteUsers: Boolean, authenticatedGuest: Boolean)
|
||||
|
||||
case class MetadataProp(metadata: collection.immutable.Map[String, String])
|
||||
|
||||
@ -73,13 +75,13 @@ case class MeetingStatus(startEndTimeStatus: StartEndTimeStatus, recordingStatus
|
||||
case class Meeting2x(defaultProps: DefaultProps, meetingStatus: MeetingStatus)
|
||||
|
||||
case class SimpleAnswerOutVO(id: Int, key: String)
|
||||
case class SimplePollOutVO(id: String, isMultipleResponse: Boolean, answers: Array[SimpleAnswerOutVO])
|
||||
case class SimplePollOutVO(id: String, answers: Array[SimpleAnswerOutVO])
|
||||
case class SimpleVoteOutVO(id: Int, key: String, numVotes: Int)
|
||||
case class SimplePollResultOutVO(id: String, answers: Array[SimpleVoteOutVO], numRespondents: Int, numResponders: Int)
|
||||
case class SimplePollResultOutVO(id: String, questionType: String, questionText: Option[String], answers: Array[SimpleVoteOutVO], numRespondents: Int, numResponders: Int)
|
||||
case class Responder(userId: String, name: String)
|
||||
case class AnswerVO(id: Int, key: String, text: Option[String], responders: Option[Array[Responder]])
|
||||
case class QuestionVO(id: Int, questionType: String, isMultipleResponse: Boolean, questionText: Option[String], answers: Option[Array[AnswerVO]])
|
||||
case class PollVO(id: String, questions: Array[QuestionVO], title: Option[String], started: Boolean, stopped: Boolean, showResult: Boolean)
|
||||
case class QuestionVO(id: Int, questionType: String, multiResponse: Boolean, questionText: Option[String], answers: Option[Array[AnswerVO]])
|
||||
case class PollVO(id: String, questions: Array[QuestionVO], title: Option[String], started: Boolean, stopped: Boolean, showResult: Boolean, isSecret: Boolean)
|
||||
|
||||
case class UserVO(id: String, externalId: String, name: String, role: String,
|
||||
guest: Boolean, authed: Boolean, guestStatus: String, emojiStatus: String,
|
||||
|
@ -13,7 +13,7 @@ case class BreakoutRoomJoinURLEvtMsgBody(parentId: String, breakoutId: String, e
|
||||
object BreakoutRoomsListEvtMsg { val NAME = "BreakoutRoomsListEvtMsg" }
|
||||
case class BreakoutRoomsListEvtMsg(header: BbbClientMsgHeader, body: BreakoutRoomsListEvtMsgBody) extends BbbCoreMsg
|
||||
case class BreakoutRoomsListEvtMsgBody(meetingId: String, rooms: Vector[BreakoutRoomInfo], roomsReady: Boolean)
|
||||
case class BreakoutRoomInfo(name: String, externalId: String, breakoutId: String, sequence: Int, freeJoin: Boolean)
|
||||
case class BreakoutRoomInfo(name: String, externalId: String, breakoutId: String, sequence: Int, shortName: String, isDefaultName: Boolean, freeJoin: Boolean)
|
||||
|
||||
object BreakoutRoomsListMsg { val NAME = "BreakoutRoomsListMsg" }
|
||||
case class BreakoutRoomsListMsg(header: BbbClientMsgHeader, body: BreakoutRoomsListMsgBody) extends StandardMsg
|
||||
@ -47,6 +47,8 @@ case class BreakoutRoomDetail(
|
||||
name: String,
|
||||
parentId: String,
|
||||
sequence: Integer,
|
||||
shortName: String,
|
||||
isDefaultName: Boolean,
|
||||
freeJoin: Boolean,
|
||||
dialNumber: String,
|
||||
voiceConfId: String,
|
||||
@ -65,7 +67,7 @@ case class BreakoutRoomDetail(
|
||||
object CreateBreakoutRoomsCmdMsg { val NAME = "CreateBreakoutRoomsCmdMsg" }
|
||||
case class CreateBreakoutRoomsCmdMsg(header: BbbClientMsgHeader, body: CreateBreakoutRoomsCmdMsgBody) extends StandardMsg
|
||||
case class CreateBreakoutRoomsCmdMsgBody(meetingId: String, durationInMinutes: Int, record: Boolean, rooms: Vector[BreakoutRoomMsgBody])
|
||||
case class BreakoutRoomMsgBody(name: String, sequence: Int, freeJoin: Boolean, users: Vector[String])
|
||||
case class BreakoutRoomMsgBody(name: String, sequence: Int, shortName: String, isDefaultName: Boolean, freeJoin: Boolean, users: Vector[String])
|
||||
|
||||
// Sent by user to request ending all the breakout rooms
|
||||
object EndAllBreakoutRoomsMsg { val NAME = "EndAllBreakoutRoomsMsg" }
|
||||
@ -100,6 +102,14 @@ object UpdateBreakoutUsersEvtMsg { val NAME = "UpdateBreakoutUsersEvtMsg" }
|
||||
case class UpdateBreakoutUsersEvtMsg(header: BbbClientMsgHeader, body: UpdateBreakoutUsersEvtMsgBody) extends BbbCoreMsg
|
||||
case class UpdateBreakoutUsersEvtMsgBody(parentId: String, breakoutId: String, users: Vector[BreakoutUserVO])
|
||||
|
||||
object ExtendBreakoutRoomsTimeReqMsg { val NAME = "ExtendBreakoutRoomsTimeReqMsg" }
|
||||
case class ExtendBreakoutRoomsTimeReqMsg(header: BbbClientMsgHeader, body: ExtendBreakoutRoomsTimeReqMsgBody) extends StandardMsg
|
||||
case class ExtendBreakoutRoomsTimeReqMsgBody(meetingId: String, extendTimeInMinutes: Int)
|
||||
|
||||
object ExtendBreakoutRoomsTimeEvtMsg { val NAME = "ExtendBreakoutRoomsTimeEvtMsg" }
|
||||
case class ExtendBreakoutRoomsTimeEvtMsg(header: BbbClientMsgHeader, body: ExtendBreakoutRoomsTimeEvtMsgBody) extends BbbCoreMsg
|
||||
case class ExtendBreakoutRoomsTimeEvtMsgBody(meetingId: String, extendTimeInMinutes: Int)
|
||||
|
||||
// Common Value objects
|
||||
case class BreakoutUserVO(id: String, name: String)
|
||||
|
||||
|
@ -0,0 +1,14 @@
|
||||
package org.bigbluebutton.common2.msgs
|
||||
|
||||
object CamStreamSubscribeSysMsg { val NAME = "CamStreamSubscribeSysMsg" }
|
||||
case class CamStreamSubscribeSysMsg(
|
||||
header: BbbCoreBaseHeader,
|
||||
body: CamStreamSubscribeSysMsgBody
|
||||
) extends BbbCoreMsg
|
||||
|
||||
case class CamStreamSubscribeSysMsgBody(
|
||||
meetingId: String,
|
||||
userId: String,
|
||||
streamId: String,
|
||||
sfuSessionId: String
|
||||
)
|
@ -7,7 +7,7 @@ case class StartExternalVideoPubMsgBody(externalVideoUrl: String)
|
||||
|
||||
object UpdateExternalVideoPubMsg { val NAME = "UpdateExternalVideoPubMsg" }
|
||||
case class UpdateExternalVideoPubMsg(header: BbbClientMsgHeader, body: UpdateExternalVideoPubMsgBody) extends StandardMsg
|
||||
case class UpdateExternalVideoPubMsgBody(status: String, rate: Double, time: Double, state: Boolean)
|
||||
case class UpdateExternalVideoPubMsgBody(status: String, rate: Double, time: Double, state: Int)
|
||||
|
||||
object StopExternalVideoPubMsg { val NAME = "StopExternalVideoPubMsg" }
|
||||
case class StopExternalVideoPubMsg(header: BbbClientMsgHeader, body: StopExternalVideoPubMsgBody) extends StandardMsg
|
||||
@ -20,7 +20,7 @@ case class StartExternalVideoEvtMsgBody(externalVideoUrl: String)
|
||||
|
||||
object UpdateExternalVideoEvtMsg { val NAME = "UpdateExternalVideoEvtMsg" }
|
||||
case class UpdateExternalVideoEvtMsg(header: BbbClientMsgHeader, body: UpdateExternalVideoEvtMsgBody) extends BbbCoreMsg
|
||||
case class UpdateExternalVideoEvtMsgBody(status: String, rate: Double, time: Double, state: Boolean)
|
||||
case class UpdateExternalVideoEvtMsgBody(status: String, rate: Double, time: Double, state: Int)
|
||||
|
||||
object StopExternalVideoEvtMsg { val NAME = "StopExternalVideoEvtMsg" }
|
||||
case class StopExternalVideoEvtMsg(header: BbbClientMsgHeader, body: StopExternalVideoEvtMsgBody) extends BbbCoreMsg
|
||||
|
@ -0,0 +1,47 @@
|
||||
package org.bigbluebutton.common2.msgs
|
||||
|
||||
object MeetingInfoAnalyticsMsg { val NAME = "MeetingInfoAnalyticsMsg" }
|
||||
case class MeetingInfoAnalyticsMsg(
|
||||
header: BbbCoreBaseHeader,
|
||||
body: MeetingInfoAnalyticsMsgBody
|
||||
) extends BbbCoreMsg
|
||||
case class MeetingInfoAnalyticsMsgBody(meetingInfo: MeetingInfoAnalytics)
|
||||
|
||||
object MeetingInfoAnalytics {
|
||||
def apply(name: String, externalId: String, internalId: String, hasUserJoined: Boolean, isMeetingRecorded: Boolean,
|
||||
webcam: Webcam, audio: Audio, screenshare: Screenshare, users: List[Participant], presentation: PresentationInfo,
|
||||
breakoutRooms: BreakoutRoom): MeetingInfoAnalytics =
|
||||
new MeetingInfoAnalytics(name, externalId, internalId, hasUserJoined, isMeetingRecorded, webcam, audio, screenshare, users,
|
||||
presentation, breakoutRooms)
|
||||
}
|
||||
|
||||
case class MeetingInfoAnalytics(
|
||||
name: String,
|
||||
externalId: String,
|
||||
internalId: String,
|
||||
hasUserJoined: Boolean,
|
||||
isMeetingRecorded: Boolean,
|
||||
webcams: Webcam,
|
||||
audio: Audio,
|
||||
screenshare: Screenshare,
|
||||
users: List[Participant],
|
||||
presentation: PresentationInfo,
|
||||
breakoutRoom: BreakoutRoom
|
||||
)
|
||||
|
||||
case class Webcam(total: Int, streams: WebcamStream)
|
||||
case class WebcamStream(broadcasts: List[Broadcast], viewers: Set[String])
|
||||
case class User(id: String, name: String)
|
||||
case class Broadcast(id: String, user: User, startedOn: Long)
|
||||
|
||||
case class Audio(total: Int, listenOnly: ListenOnlyAudio, twoWay: TwoWayAudio, phone: PhoneAudio)
|
||||
case class ListenOnlyAudio(total: Int, users: List[User])
|
||||
case class TwoWayAudio(total: Int, users: List[User])
|
||||
case class PhoneAudio(total: Int, users: List[User])
|
||||
|
||||
case class Screenshare(stream: ScreenshareStream)
|
||||
case class ScreenshareStream(user: User, viewers: List[User])
|
||||
|
||||
case class Participant(id: String, name: String, role: String)
|
||||
case class PresentationInfo(id: String, name: String)
|
||||
case class BreakoutRoom(id: String, names: List[String])
|
@ -0,0 +1,7 @@
|
||||
package org.bigbluebutton.common2.msgs
|
||||
|
||||
object MeetingInfoAnalyticsServiceMsg { val NAME = "MeetingInfoAnalyticsServiceMsg" }
|
||||
case class MeetingInfoAnalyticsServiceMsg(
|
||||
header: BbbCoreBaseHeader,
|
||||
body: MeetingInfoAnalyticsMsgBody
|
||||
) extends BbbCoreMsg
|
@ -16,7 +16,7 @@ case class PollShowResultEvtMsgBody(userId: String, pollId: String, poll: Simple
|
||||
|
||||
object PollStartedEvtMsg { val NAME = "PollStartedEvtMsg" }
|
||||
case class PollStartedEvtMsg(header: BbbClientMsgHeader, body: PollStartedEvtMsgBody) extends BbbCoreMsg
|
||||
case class PollStartedEvtMsgBody(userId: String, pollId: String, pollType: String, question: String, poll: SimplePollOutVO)
|
||||
case class PollStartedEvtMsgBody(userId: String, pollId: String, pollType: String, secretPoll: Boolean, question: String, poll: SimplePollOutVO)
|
||||
|
||||
object PollStoppedEvtMsg { val NAME = "PollStoppedEvtMsg" }
|
||||
case class PollStoppedEvtMsg(header: BbbClientMsgHeader, body: PollStoppedEvtMsgBody) extends BbbCoreMsg
|
||||
@ -28,11 +28,11 @@ case class PollUpdatedEvtMsgBody(pollId: String, poll: SimplePollResultOutVO)
|
||||
|
||||
object UserRespondedToPollRecordMsg { val NAME = "UserRespondedToPollRecordMsg" }
|
||||
case class UserRespondedToPollRecordMsg(header: BbbClientMsgHeader, body: UserRespondedToPollRecordMsgBody) extends BbbCoreMsg
|
||||
case class UserRespondedToPollRecordMsgBody(pollId: String, answerIds: Seq[Int])
|
||||
case class UserRespondedToPollRecordMsgBody(pollId: String, answerId: Int, answer: String, isSecret: Boolean)
|
||||
|
||||
object RespondToPollReqMsg { val NAME = "RespondToPollReqMsg" }
|
||||
case class RespondToPollReqMsg(header: BbbClientMsgHeader, body: RespondToPollReqMsgBody) extends StandardMsg
|
||||
case class RespondToPollReqMsgBody(requesterId: String, pollId: String, questionId: Int, answerIds: Seq[Int])
|
||||
case class RespondToPollReqMsgBody(requesterId: String, pollId: String, questionId: Int, answerId: Int)
|
||||
|
||||
object RespondToTypedPollReqMsg { val NAME = "RespondToTypedPollReqMsg" }
|
||||
case class RespondToTypedPollReqMsg(header: BbbClientMsgHeader, body: RespondToTypedPollReqMsgBody) extends StandardMsg
|
||||
@ -40,7 +40,7 @@ case class RespondToTypedPollReqMsgBody(requesterId: String, pollId: String, que
|
||||
|
||||
object UserRespondedToPollRespMsg { val NAME = "UserRespondedToPollRespMsg" }
|
||||
case class UserRespondedToPollRespMsg(header: BbbClientMsgHeader, body: UserRespondedToPollRespMsgBody) extends BbbCoreMsg
|
||||
case class UserRespondedToPollRespMsgBody(pollId: String, userId: String, answerIds: Seq[Int])
|
||||
case class UserRespondedToPollRespMsgBody(pollId: String, userId: String, answerId: Int)
|
||||
|
||||
object UserRespondedToTypedPollRespMsg { val NAME = "UserRespondedToTypedPollRespMsg" }
|
||||
case class UserRespondedToTypedPollRespMsg(header: BbbClientMsgHeader, body: UserRespondedToTypedPollRespMsgBody) extends BbbCoreMsg
|
||||
@ -52,12 +52,12 @@ case class ShowPollResultReqMsgBody(requesterId: String, pollId: String)
|
||||
|
||||
object StartCustomPollReqMsg { val NAME = "StartCustomPollReqMsg" }
|
||||
case class StartCustomPollReqMsg(header: BbbClientMsgHeader, body: StartCustomPollReqMsgBody) extends StandardMsg
|
||||
case class StartCustomPollReqMsgBody(requesterId: String, pollId: String, pollType: String, isMultipleResponse: Boolean, answers: Seq[String], question: String)
|
||||
case class StartCustomPollReqMsgBody(requesterId: String, pollId: String, pollType: String, secretPoll: Boolean, answers: Seq[String], question: String)
|
||||
|
||||
object StartPollReqMsg { val NAME = "StartPollReqMsg" }
|
||||
case class StartPollReqMsg(header: BbbClientMsgHeader, body: StartPollReqMsgBody) extends StandardMsg
|
||||
case class StartPollReqMsgBody(requesterId: String, pollId: String, pollType: String, question: String, isMultipleResponse: Boolean)
|
||||
case class StartPollReqMsgBody(requesterId: String, pollId: String, pollType: String, secretPoll: Boolean, question: String)
|
||||
|
||||
object StopPollReqMsg { val NAME = "StopPollReqMsg" }
|
||||
case class StopPollReqMsg(header: BbbClientMsgHeader, body: StopPollReqMsgBody) extends StandardMsg
|
||||
case class StopPollReqMsgBody(requesterId: String)
|
||||
case class StopPollReqMsgBody(requesterId: String)
|
||||
|
@ -0,0 +1,14 @@
|
||||
package org.bigbluebutton.common2.msgs
|
||||
|
||||
object ScreenStreamSubscribeSysMsg { val NAME = "ScreenStreamSubscribeSysMsg" }
|
||||
case class ScreenStreamSubscribeSysMsg(
|
||||
header: BbbCoreBaseHeader,
|
||||
body: ScreenStreamSubscribeSysMsg
|
||||
) extends BbbCoreMsg
|
||||
|
||||
case class ScreenStreamSubscribeSysMsgBody(
|
||||
meetingId: String,
|
||||
userId: String,
|
||||
streamId: String,
|
||||
sfuSessionId: String
|
||||
)
|
@ -223,3 +223,13 @@ case class UnpublishedRecordingSysMsgBody(recordId: String)
|
||||
object DeletedRecordingSysMsg { val NAME = "DeletedRecordingSysMsg" }
|
||||
case class DeletedRecordingSysMsg(header: BbbCoreBaseHeader, body: DeletedRecordingSysMsgBody) extends BbbCoreMsg
|
||||
case class DeletedRecordingSysMsgBody(recordId: String)
|
||||
|
||||
/**
|
||||
* Sent from akka-apps to bbb-web to inform a summary of the meeting activities
|
||||
*/
|
||||
object LearningDashboardEvtMsg { val NAME = "LearningDashboardEvtMsg" }
|
||||
case class LearningDashboardEvtMsg(
|
||||
header: BbbCoreHeaderWithMeetingId,
|
||||
body: LearningDashboardEvtMsgBody
|
||||
) extends BbbCoreMsg
|
||||
case class LearningDashboardEvtMsgBody(activityJson: String)
|
||||
|
@ -17,12 +17,15 @@ trait TestFixtures {
|
||||
val userInactivityInspectTimerInMinutes = 60
|
||||
val userInactivityThresholdInMinutes = 10
|
||||
val userActivitySignResponseDelayInMinutes = 5
|
||||
val endWhenNoModerator = false
|
||||
val endWhenNoModeratorDelayInMinutes = 1
|
||||
|
||||
val autoStartRecording = false
|
||||
val allowStartStopRecording = false
|
||||
val webcamsOnlyForModerator = false
|
||||
val moderatorPassword = "modpass"
|
||||
val viewerPassword = "viewpass"
|
||||
val learningDashboardAccessToken = "ldToken"
|
||||
val createTime = System.currentTimeMillis
|
||||
val createDate = "Oct 26, 2015"
|
||||
val isBreakout = false
|
||||
@ -45,7 +48,7 @@ trait TestFixtures {
|
||||
val durationProps = DurationProps(duration = durationInMinutes, createdTime = createTime, createdDate = createDate,
|
||||
meetingExpireIfNoUserJoinedInMinutes = meetingExpireIfNoUserJoinedInMinutes, meetingExpireWhenLastUserLeftInMinutes = meetingExpireWhenLastUserLeftInMinutes,
|
||||
userInactivityInspectTimerInMinutes = userInactivityInspectTimerInMinutes, userInactivityThresholdInMinutes = userInactivityInspectTimerInMinutes, userActivitySignResponseDelayInMinutes = userActivitySignResponseDelayInMinutes)
|
||||
val password = PasswordProp(moderatorPass = moderatorPassword, viewerPass = viewerPassword)
|
||||
val password = PasswordProp(moderatorPass = moderatorPassword, viewerPass = viewerPassword, learningDashboardAccessToken = learningDashboardAccessToken)
|
||||
val recordProp = RecordProp(record = record, autoStartRecording = autoStartRecording,
|
||||
allowStartStopRecording = allowStartStopRecording, keepEvents = keepEvents)
|
||||
val welcomeProp = WelcomeProp(welcomeMsgTemplate = welcomeMsgTemplate, welcomeMsg = welcomeMsg,
|
||||
|
@ -95,3 +95,8 @@ pomExtra := (
|
||||
licenses := Seq("LGPL-3.0" -> url("http://opensource.org/licenses/LGPL-3.0"))
|
||||
|
||||
homepage := Some(url("http://www.bigbluebutton.org"))
|
||||
|
||||
libraryDependencies += "javax.validation" % "validation-api" % "2.0.1.Final"
|
||||
libraryDependencies += "org.springframework.boot" % "spring-boot-starter-validation" % "2.5.1"
|
||||
libraryDependencies += "org.glassfish" % "javax.el" % "3.0.1-b12"
|
||||
libraryDependencies += "org.apache.httpcomponents" % "httpclient" % "4.5.13"
|
@ -26,11 +26,12 @@ object Dependencies {
|
||||
|
||||
// Server
|
||||
val servlet = "3.1.0"
|
||||
|
||||
|
||||
// Apache Commons
|
||||
val lang = "3.9"
|
||||
val io = "2.6"
|
||||
val pool = "2.8.0"
|
||||
val text = "1.9"
|
||||
|
||||
// BigBlueButton
|
||||
val bbbCommons = "0.0.20-SNAPSHOT"
|
||||
@ -57,10 +58,11 @@ object Dependencies {
|
||||
val nuProcess = "com.zaxxer" % "nuprocess" % Versions.nuProcess
|
||||
|
||||
val servletApi = "javax.servlet" % "javax.servlet-api" % Versions.servlet
|
||||
|
||||
|
||||
val apacheLang = "org.apache.commons" % "commons-lang3" % Versions.lang
|
||||
val apacheIo = "commons-io" % "commons-io" % Versions.io
|
||||
val apachePool2 = "org.apache.commons" % "commons-pool2" % Versions.pool
|
||||
val apacheText = "org.apache.commons" % "commons-text" % Versions.text
|
||||
|
||||
val bbbCommons = "org.bigbluebutton" % "bbb-common-message_2.12" % Versions.bbbCommons excludeAll (
|
||||
ExclusionRule(organization = "org.red5"))
|
||||
@ -96,5 +98,6 @@ object Dependencies {
|
||||
Compile.apacheLang,
|
||||
Compile.apacheIo,
|
||||
Compile.apachePool2,
|
||||
Compile.apacheText,
|
||||
Compile.bbbCommons) ++ testing
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
/**
|
||||
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
|
||||
*
|
||||
*
|
||||
* Copyright (c) 2017 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.
|
||||
@ -33,6 +33,7 @@ public class ApiParams {
|
||||
public static final String FREE_JOIN = "freeJoin";
|
||||
public static final String FULL_NAME = "fullName";
|
||||
public static final String GUEST_POLICY = "guestPolicy";
|
||||
public static final String MEETING_LAYOUT = "meetingLayout";
|
||||
public static final String IS_BREAKOUT = "isBreakout";
|
||||
public static final String LOGO = "logo";
|
||||
public static final String LOGOUT_TIMER = "logoutTimer";
|
||||
@ -54,6 +55,8 @@ public class ApiParams {
|
||||
public static final String SEQUENCE = "sequence";
|
||||
public static final String VOICE_BRIDGE = "voiceBridge";
|
||||
public static final String WEB_VOICE = "webVoice";
|
||||
public static final String LEARNING_DASHBOARD_ENABLED = "learningDashboardEnabled";
|
||||
public static final String LEARNING_DASHBOARD_CLEANUP_DELAY_IN_MINUTES = "learningDashboardCleanupDelayInMinutes";
|
||||
public static final String WEBCAMS_ONLY_FOR_MODERATOR = "webcamsOnlyForModerator";
|
||||
public static final String WELCOME = "welcome";
|
||||
public static final String HTML5_INSTANCE_ID = "html5InstanceId";
|
||||
@ -81,6 +84,7 @@ public class ApiParams {
|
||||
// Needed for classes where teacher gets disconnected and can't get back in. Prevents
|
||||
// students from running amok.
|
||||
public static final String END_WHEN_NO_MODERATOR = "endWhenNoModerator";
|
||||
public static final String END_WHEN_NO_MODERATOR_DELAY_IN_MINUTES = "endWhenNoModeratorDelayInMinutes";
|
||||
|
||||
private ApiParams() {
|
||||
throw new IllegalStateException("ApiParams is a utility class. Instanciation is forbidden.");
|
||||
|
@ -0,0 +1,94 @@
|
||||
/**
|
||||
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
|
||||
*
|
||||
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free Software
|
||||
* Foundation; either version 3.0 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU Lesser General Public License along
|
||||
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
|
||||
*
|
||||
*/
|
||||
|
||||
package org.bigbluebutton.api;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
|
||||
public class LearningDashboardService {
|
||||
private static Logger log = LoggerFactory.getLogger(LearningDashboardService.class);
|
||||
private static String learningDashboardFilesDir = "/var/bigbluebutton/learning-dashboard";
|
||||
|
||||
public void writeJsonDataFile(String meetingId, String learningDashboardAccessToken, String activityJson) {
|
||||
|
||||
try {
|
||||
if(learningDashboardAccessToken.length() == 0) {
|
||||
log.error("LearningDashboard AccessToken not found. JSON file will not be saved for meeting {}.",meetingId);
|
||||
return;
|
||||
}
|
||||
|
||||
File baseDir = new File(this.getDestinationBaseDirectoryName(meetingId,learningDashboardAccessToken));
|
||||
if (!baseDir.exists()) baseDir.mkdirs();
|
||||
|
||||
File jsonFile = new File(baseDir.getAbsolutePath() + File.separatorChar + "learning_dashboard_data.json");
|
||||
|
||||
FileOutputStream fileOutput = new FileOutputStream(jsonFile);
|
||||
fileOutput.write(activityJson.getBytes());
|
||||
|
||||
fileOutput.close();
|
||||
|
||||
log.info("Learning Dashboard ({}) updated for meeting {}.",jsonFile.getAbsolutePath(),meetingId);
|
||||
} catch(Exception e) {
|
||||
System.out.println(e);
|
||||
}
|
||||
}
|
||||
|
||||
public void removeJsonDataFile(String meetingId, int cleanUpDelayMinutes) {
|
||||
//Delay `cleanUpDelayMinutes` then moderators can open the Dashboard before files has been removed
|
||||
new java.util.Timer().schedule(
|
||||
new java.util.TimerTask() {
|
||||
@Override
|
||||
public void run() {
|
||||
File ldMeetingFilesDir = new File(learningDashboardFilesDir + File.separatorChar + meetingId);
|
||||
LearningDashboardService.deleteDirectory(ldMeetingFilesDir);
|
||||
log.info("Learning Dashboard files removed for meeting {}.",meetingId);
|
||||
}
|
||||
},
|
||||
(cleanUpDelayMinutes * 60) * 1000
|
||||
);
|
||||
}
|
||||
|
||||
private String getDestinationBaseDirectoryName(String meetingId, String learningDashboardAccessToken) {
|
||||
return learningDashboardFilesDir + File.separatorChar + meetingId + File.separatorChar + learningDashboardAccessToken;
|
||||
}
|
||||
|
||||
private static void deleteDirectory(File directory) {
|
||||
/**
|
||||
* Go through each directory and check if it's not empty. We need to
|
||||
* delete files inside a directory before a directory can be deleted.
|
||||
**/
|
||||
File[] files = directory.listFiles();
|
||||
for (File file : files) {
|
||||
if (file.isDirectory()) {
|
||||
deleteDirectory(file);
|
||||
} else {
|
||||
file.delete();
|
||||
}
|
||||
}
|
||||
// Now that the directory is empty. Delete it.
|
||||
directory.delete();
|
||||
}
|
||||
|
||||
public void setLearningDashboardFilesDir(String dir) {
|
||||
learningDashboardFilesDir = dir;
|
||||
}
|
||||
}
|
@ -40,6 +40,7 @@ import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
|
||||
import com.google.gson.JsonObject;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.http.client.utils.URIBuilder;
|
||||
import org.bigbluebutton.api.HTML5LoadBalancingService;
|
||||
@ -49,39 +50,14 @@ import org.bigbluebutton.api.domain.Recording;
|
||||
import org.bigbluebutton.api.domain.RegisteredUser;
|
||||
import org.bigbluebutton.api.domain.User;
|
||||
import org.bigbluebutton.api.domain.UserSession;
|
||||
import org.bigbluebutton.api.domain.MeetingLayout;
|
||||
import org.bigbluebutton.api.messaging.MessageListener;
|
||||
import org.bigbluebutton.api.messaging.converters.messages.DestroyMeetingMessage;
|
||||
import org.bigbluebutton.api.messaging.converters.messages.EndMeetingMessage;
|
||||
import org.bigbluebutton.api.messaging.converters.messages.PublishedRecordingMessage;
|
||||
import org.bigbluebutton.api.messaging.converters.messages.UnpublishedRecordingMessage;
|
||||
import org.bigbluebutton.api.messaging.converters.messages.DeletedRecordingMessage;
|
||||
import org.bigbluebutton.api.messaging.messages.AddPad;
|
||||
import org.bigbluebutton.api.messaging.messages.AddCaptionsPads;
|
||||
import org.bigbluebutton.api.messaging.messages.CreateBreakoutRoom;
|
||||
import org.bigbluebutton.api.messaging.messages.CreateMeeting;
|
||||
import org.bigbluebutton.api.messaging.messages.EndMeeting;
|
||||
import org.bigbluebutton.api.messaging.messages.GuestPolicyChanged;
|
||||
import org.bigbluebutton.api.messaging.messages.GuestLobbyMessageChanged;
|
||||
import org.bigbluebutton.api.messaging.messages.GuestStatusChangedEventMsg;
|
||||
import org.bigbluebutton.api.messaging.messages.GuestsStatus;
|
||||
import org.bigbluebutton.api.messaging.messages.IMessage;
|
||||
import org.bigbluebutton.api.messaging.messages.MakePresentationDownloadableMsg;
|
||||
import org.bigbluebutton.api.messaging.messages.MeetingDestroyed;
|
||||
import org.bigbluebutton.api.messaging.messages.MeetingEnded;
|
||||
import org.bigbluebutton.api.messaging.messages.MeetingStarted;
|
||||
import org.bigbluebutton.api.messaging.messages.PresentationUploadToken;
|
||||
import org.bigbluebutton.api.messaging.messages.RecordChapterBreak;
|
||||
import org.bigbluebutton.api.messaging.messages.RegisterUser;
|
||||
import org.bigbluebutton.api.messaging.messages.UpdateRecordingStatus;
|
||||
import org.bigbluebutton.api.messaging.messages.UserJoined;
|
||||
import org.bigbluebutton.api.messaging.messages.UserJoinedVoice;
|
||||
import org.bigbluebutton.api.messaging.messages.UserLeft;
|
||||
import org.bigbluebutton.api.messaging.messages.UserLeftVoice;
|
||||
import org.bigbluebutton.api.messaging.messages.UserListeningOnly;
|
||||
import org.bigbluebutton.api.messaging.messages.UserRoleChanged;
|
||||
import org.bigbluebutton.api.messaging.messages.UserSharedWebcam;
|
||||
import org.bigbluebutton.api.messaging.messages.UserStatusChanged;
|
||||
import org.bigbluebutton.api.messaging.messages.UserUnsharedWebcam;
|
||||
import org.bigbluebutton.api.messaging.messages.*;
|
||||
import org.bigbluebutton.api2.IBbbWebApiGWApp;
|
||||
import org.bigbluebutton.api2.domain.UploadedTrack;
|
||||
import org.bigbluebutton.common2.redis.RedisStorageService;
|
||||
@ -114,6 +90,7 @@ public class MeetingService implements MessageListener {
|
||||
private final ConcurrentMap<String, UserSession> sessions;
|
||||
|
||||
private RecordingService recordingService;
|
||||
private LearningDashboardService learningDashboardService;
|
||||
private WaitingGuestCleanupTimerTask waitingGuestCleaner;
|
||||
private UserCleanupTimerTask userCleaner;
|
||||
private EnteredUserCleanupTimerTask enteredUserCleaner;
|
||||
@ -142,7 +119,7 @@ public class MeetingService implements MessageListener {
|
||||
public void addUserSession(String token, UserSession user) {
|
||||
sessions.put(token, user);
|
||||
}
|
||||
|
||||
|
||||
public String getTokenByUserId(String internalUserId) {
|
||||
String result = null;
|
||||
for (Entry<String, UserSession> e : sessions.entrySet()) {
|
||||
@ -418,6 +395,7 @@ public class MeetingService implements MessageListener {
|
||||
logData.put("description", "Create meeting.");
|
||||
|
||||
logData.put("meetingKeepEvents", m.getMeetingKeepEvents());
|
||||
logData.put("meetingLayout", m.getMeetingLayout());
|
||||
|
||||
Gson gson = new Gson();
|
||||
String logStr = gson.toJson(logData);
|
||||
@ -426,13 +404,15 @@ public class MeetingService implements MessageListener {
|
||||
|
||||
gw.createMeeting(m.getInternalId(), m.getExternalId(), m.getParentMeetingId(), m.getName(), m.isRecord(),
|
||||
m.getTelVoice(), m.getDuration(), m.getAutoStartRecording(), m.getAllowStartStopRecording(),
|
||||
m.getWebcamsOnlyForModerator(), m.getModeratorPassword(), m.getViewerPassword(), m.getCreateTime(),
|
||||
m.getWebcamsOnlyForModerator(), m.getModeratorPassword(), m.getViewerPassword(),
|
||||
m.getLearningDashboardEnabled(), m.getLearningDashboardAccessToken(), m.getCreateTime(),
|
||||
formatPrettyDate(m.getCreateTime()), m.isBreakout(), m.getSequence(), m.isFreeJoin(), m.getMetadata(),
|
||||
m.getGuestPolicy(), m.getAuthenticatedGuest(), m.getWelcomeMessageTemplate(), m.getWelcomeMessage(), m.getModeratorOnlyMessage(),
|
||||
m.getGuestPolicy(), m.getAuthenticatedGuest(), m.getMeetingLayout(), m.getWelcomeMessageTemplate(), m.getWelcomeMessage(), m.getModeratorOnlyMessage(),
|
||||
m.getDialNumber(), m.getMaxUsers(),
|
||||
m.getMeetingExpireIfNoUserJoinedInMinutes(), m.getmeetingExpireWhenLastUserLeftInMinutes(),
|
||||
m.getUserInactivityInspectTimerInMinutes(), m.getUserInactivityThresholdInMinutes(),
|
||||
m.getUserActivitySignResponseDelayInMinutes(), m.getMuteOnStart(), m.getAllowModsToUnmuteUsers(), m.getMeetingKeepEvents(),
|
||||
m.getUserActivitySignResponseDelayInMinutes(), m.getEndWhenNoModerator(), m.getEndWhenNoModeratorDelayInMinutes(),
|
||||
m.getMuteOnStart(), m.getAllowModsToUnmuteUsers(), m.getMeetingKeepEvents(),
|
||||
m.breakoutRoomsParams,
|
||||
m.lockSettingsParams, m.getHtml5InstanceId());
|
||||
}
|
||||
@ -580,7 +560,7 @@ public class MeetingService implements MessageListener {
|
||||
public String getCaptionTrackInboxDir() {
|
||||
return recordingService.getCaptionTrackInboxDir();
|
||||
}
|
||||
|
||||
|
||||
public String getCaptionsDir() {
|
||||
return recordingService.getCaptionsDir();
|
||||
}
|
||||
@ -694,7 +674,7 @@ public class MeetingService implements MessageListener {
|
||||
log.error(" --analytics-- data={}", logStr);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private void processUpdateRecordingStatus(UpdateRecordingStatus message) {
|
||||
Meeting m = getMeeting(message.meetingId);
|
||||
// Set only once
|
||||
@ -879,6 +859,11 @@ public class MeetingService implements MessageListener {
|
||||
}
|
||||
}
|
||||
|
||||
//Remove Learning Dashboard files
|
||||
if(m.getLearningDashboardCleanupDelayInMinutes() > 0) {
|
||||
learningDashboardService.removeJsonDataFile(message.meetingId, m.getLearningDashboardCleanupDelayInMinutes());
|
||||
}
|
||||
|
||||
processRemoveEndedMeeting(message);
|
||||
}
|
||||
}
|
||||
@ -973,6 +958,26 @@ public class MeetingService implements MessageListener {
|
||||
}
|
||||
}
|
||||
|
||||
public void processLearningDashboard(LearningDashboard message) {
|
||||
//Get all data from Json instead of getMeeting(message.meetingId), to process messages received even after meeting ended
|
||||
JsonObject activityJsonObject = new Gson().fromJson(message.activityJson, JsonObject.class).getAsJsonObject();
|
||||
String learningDashboardAccessToken = activityJsonObject.get("learningDashboardAccessToken").getAsString();
|
||||
|
||||
Map<String, Object> logData = new HashMap<String, Object>();
|
||||
logData.put("meetingId", activityJsonObject.get("intId").getAsString());
|
||||
logData.put("externalMeetingId", activityJsonObject.get("extId").getAsString());
|
||||
logData.put("name", activityJsonObject.get("name").getAsString());
|
||||
logData.put("logCode", "update_activity_json");
|
||||
logData.put("description", "Updating activities json.");
|
||||
|
||||
Gson gson = new Gson();
|
||||
String logStr = gson.toJson(logData);
|
||||
|
||||
log.info(" --analytics-- data={}", logStr);
|
||||
|
||||
learningDashboardService.writeJsonDataFile(message.meetingId, learningDashboardAccessToken, message.activityJson);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void handle(IMessage message) {
|
||||
receivedMessages.add(message);
|
||||
@ -1129,6 +1134,8 @@ public class MeetingService implements MessageListener {
|
||||
processMakePresentationDownloadableMsg((MakePresentationDownloadableMsg) message);
|
||||
} else if (message instanceof UpdateRecordingStatus) {
|
||||
processUpdateRecordingStatus((UpdateRecordingStatus) message);
|
||||
} else if (message instanceof LearningDashboard) {
|
||||
processLearningDashboard((LearningDashboard) message);
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -1214,6 +1221,10 @@ public class MeetingService implements MessageListener {
|
||||
recordingService = s;
|
||||
}
|
||||
|
||||
public void setLearningDashboardService(LearningDashboardService s) {
|
||||
learningDashboardService = s;
|
||||
}
|
||||
|
||||
public void setRedisStorageService(RedisStorageService mess) {
|
||||
storeService = mess;
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
/**
|
||||
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
|
||||
*
|
||||
*
|
||||
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free Software
|
||||
* Foundation; either version 3.0 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
*
|
||||
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
@ -38,8 +38,6 @@ import org.apache.http.HttpResponse;
|
||||
import org.apache.http.client.ClientProtocolException;
|
||||
import org.apache.http.client.ResponseHandler;
|
||||
import org.apache.http.client.methods.HttpGet;
|
||||
import org.apache.http.impl.client.CloseableHttpClient;
|
||||
import org.apache.http.impl.client.HttpClients;
|
||||
import org.apache.http.util.EntityUtils;
|
||||
import org.bigbluebutton.api.domain.BreakoutRoomsParams;
|
||||
import org.bigbluebutton.api.domain.LockSettingsParams;
|
||||
@ -54,7 +52,7 @@ public class ParamsProcessorUtil {
|
||||
private static final String URLDECODER_SEPARATOR=",";
|
||||
private static final String FILTERDECODER_SEPARATOR_ELEMENTS=":";
|
||||
private static final String FILTERDECODER_SEPARATOR_OPERATORS="\\|";
|
||||
|
||||
|
||||
private static final String SERVER_URL = "%%SERVERURL%%";
|
||||
private static final String DIAL_NUM = "%%DIALNUM%%";
|
||||
private static final String CONF_NUM = "%%CONFNUM%%";
|
||||
@ -77,17 +75,21 @@ public class ParamsProcessorUtil {
|
||||
private Boolean allowRequestsWithoutSession;
|
||||
private Boolean useDefaultAvatar = false;
|
||||
private String defaultAvatarURL;
|
||||
private String defaultConfigURL;
|
||||
private String defaultGuestPolicy;
|
||||
private Boolean authenticatedGuest;
|
||||
private String defaultMeetingLayout;
|
||||
private int defaultMeetingDuration;
|
||||
private boolean disableRecordingDefault;
|
||||
private boolean autoStartRecording;
|
||||
private boolean allowStartStopRecording;
|
||||
private boolean learningDashboardEnabled;
|
||||
private int learningDashboardCleanupDelayInMinutes;
|
||||
private boolean webcamsOnlyForModerator;
|
||||
private boolean defaultMuteOnStart = false;
|
||||
private boolean defaultAllowModsToUnmuteUsers = false;
|
||||
private boolean defaultKeepEvents = false;
|
||||
private Boolean useDefaultLogo;
|
||||
private String defaultLogoURL;
|
||||
|
||||
private boolean defaultBreakoutRoomsEnabled;
|
||||
private boolean defaultBreakoutRoomsRecord;
|
||||
@ -103,8 +105,6 @@ public class ParamsProcessorUtil {
|
||||
private boolean defaultLockSettingsLockOnJoin;
|
||||
private boolean defaultLockSettingsLockOnJoinConfigurable;
|
||||
|
||||
private String defaultConfigXML = null;
|
||||
|
||||
private Long maxPresentationFileUpload = 30000000L; // 30MB
|
||||
|
||||
private Integer clientLogoutTimerInMinutes = 0;
|
||||
@ -115,6 +115,7 @@ public class ParamsProcessorUtil {
|
||||
private Integer userActivitySignResponseDelayInMinutes = 5;
|
||||
private Boolean defaultAllowDuplicateExtUserid = true;
|
||||
private Boolean defaultEndWhenNoModerator = false;
|
||||
private Integer defaultEndWhenNoModeratorDelayInMinutes = 1;
|
||||
private Integer defaultHtml5InstanceId = 1;
|
||||
|
||||
private String formatConfNum(String s) {
|
||||
@ -154,7 +155,7 @@ public class ParamsProcessorUtil {
|
||||
} else if (keyword.equals(CONF_NAME)) {
|
||||
welcomeMessage = welcomeMessage.replaceAll(
|
||||
Pattern.quote(CONF_NAME),
|
||||
Matcher.quoteReplacement(meetingName));
|
||||
Matcher.quoteReplacement(ParamsUtil.escapeHTMLTags(meetingName)));
|
||||
} else if (keyword.equals(SERVER_URL)) {
|
||||
welcomeMessage = welcomeMessage.replaceAll(
|
||||
Pattern.quote(SERVER_URL),
|
||||
@ -184,10 +185,10 @@ public class ParamsProcessorUtil {
|
||||
errors.missingParamError(ApiParams.MEETING_ID);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Map<String, Object> processUpdateCreateParams(Map<String, String> params) {
|
||||
Map<String, Object> newParams = new HashMap<>();
|
||||
|
||||
|
||||
String[] createParams = { ApiParams.NAME, ApiParams.ATTENDEE_PW, ApiParams.MODERATOR_PW, ApiParams.VOICE_BRIDGE,
|
||||
ApiParams.WEB_VOICE, ApiParams.DIAL_NUMBER, ApiParams.LOGOUT_URL, ApiParams.RECORD,
|
||||
ApiParams.MAX_PARTICIPANTS, ApiParams.DURATION, ApiParams.WELCOME };
|
||||
@ -198,7 +199,7 @@ public class ParamsProcessorUtil {
|
||||
newParams.put(paramName, parameter);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Collect metadata for this meeting that the third-party application wants to store if meeting is recorded.
|
||||
Map<String, String> meetingInfo = new HashMap<>();
|
||||
for (Map.Entry<String, String> entry : params.entrySet()) {
|
||||
@ -207,17 +208,17 @@ public class ParamsProcessorUtil {
|
||||
if(meta.length == 2){
|
||||
meetingInfo.put(meta[1], entry.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (!meetingInfo.isEmpty()) {
|
||||
newParams.put("metadata", meetingInfo);
|
||||
}
|
||||
|
||||
|
||||
return newParams;
|
||||
}
|
||||
|
||||
private static final Pattern META_VAR_PATTERN = Pattern.compile("meta_[a-zA-Z][a-zA-Z0-9-]*$");
|
||||
|
||||
private static final Pattern META_VAR_PATTERN = Pattern.compile("meta_[a-zA-Z][a-zA-Z0-9-]*$");
|
||||
public static Boolean isMetaValid(String param) {
|
||||
Matcher metaMatcher = META_VAR_PATTERN.matcher(param);
|
||||
if (metaMatcher.matches()) {
|
||||
@ -225,11 +226,11 @@ public class ParamsProcessorUtil {
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
public static String removeMetaString(String param) {
|
||||
return StringUtils.removeStart(param, "meta_");
|
||||
}
|
||||
|
||||
|
||||
public static Map<String, String> processMetaParam(Map<String, String> params) {
|
||||
Map<String, String> metas = new HashMap<>();
|
||||
for (Map.Entry<String, String> entry : params.entrySet()) {
|
||||
@ -341,7 +342,7 @@ public class ParamsProcessorUtil {
|
||||
meetingName = "";
|
||||
}
|
||||
|
||||
meetingName = ParamsUtil.stripHTMLTags(ParamsUtil.stripControlChars(meetingName));
|
||||
meetingName = ParamsUtil.stripControlChars(meetingName);
|
||||
|
||||
String externalMeetingId = params.get(ApiParams.MEETING_ID);
|
||||
|
||||
@ -371,11 +372,11 @@ public class ParamsProcessorUtil {
|
||||
int maxUsers = processMaxUser(params.get(ApiParams.MAX_PARTICIPANTS));
|
||||
int meetingDuration = processMeetingDuration(params.get(ApiParams.DURATION));
|
||||
int logoutTimer = processLogoutTimer(params.get(ApiParams.LOGOUT_TIMER));
|
||||
|
||||
|
||||
// Banner parameters
|
||||
String bannerText = params.get(ApiParams.BANNER_TEXT);
|
||||
String bannerColor = params.get(ApiParams.BANNER_COLOR);
|
||||
|
||||
|
||||
// set is breakout room property
|
||||
boolean isBreakout = false;
|
||||
if (!StringUtils.isEmpty(params.get(ApiParams.IS_BREAKOUT))) {
|
||||
@ -418,6 +419,36 @@ public class ParamsProcessorUtil {
|
||||
}
|
||||
}
|
||||
|
||||
boolean learningDashboardEn = learningDashboardEnabled;
|
||||
if (!StringUtils.isEmpty(params.get(ApiParams.LEARNING_DASHBOARD_ENABLED))) {
|
||||
try {
|
||||
learningDashboardEn = Boolean.parseBoolean(params
|
||||
.get(ApiParams.LEARNING_DASHBOARD_ENABLED));
|
||||
} catch (Exception ex) {
|
||||
log.warn(
|
||||
"Invalid param [learningDashboardEnabled] for meeting=[{}]",
|
||||
internalMeetingId);
|
||||
}
|
||||
}
|
||||
|
||||
int learningDashboardCleanupMins = learningDashboardCleanupDelayInMinutes;
|
||||
if (!StringUtils.isEmpty(params.get(ApiParams.LEARNING_DASHBOARD_CLEANUP_DELAY_IN_MINUTES))) {
|
||||
try {
|
||||
learningDashboardCleanupMins = Integer.parseInt(params
|
||||
.get(ApiParams.LEARNING_DASHBOARD_CLEANUP_DELAY_IN_MINUTES));
|
||||
} catch (Exception ex) {
|
||||
log.warn(
|
||||
"Invalid param [learningDashboardCleanupDelayInMinutes] for meeting=[{}]",
|
||||
internalMeetingId);
|
||||
}
|
||||
}
|
||||
|
||||
//Generate token to access Activity Report
|
||||
String learningDashboardAccessToken = "";
|
||||
if(learningDashboardEn == true) {
|
||||
learningDashboardAccessToken = RandomStringUtils.randomAlphanumeric(12).toLowerCase();
|
||||
}
|
||||
|
||||
boolean webcamsOnlyForMod = webcamsOnlyForModerator;
|
||||
if (!StringUtils.isEmpty(params.get(ApiParams.WEBCAMS_ONLY_FOR_MODERATOR))) {
|
||||
try {
|
||||
@ -439,15 +470,29 @@ public class ParamsProcessorUtil {
|
||||
}
|
||||
}
|
||||
|
||||
int endWhenNoModeratorDelayInMinutes = defaultEndWhenNoModeratorDelayInMinutes;
|
||||
if (!StringUtils.isEmpty(params.get(ApiParams.END_WHEN_NO_MODERATOR_DELAY_IN_MINUTES))) {
|
||||
try {
|
||||
endWhenNoModeratorDelayInMinutes = Integer.parseInt(params.get(ApiParams.END_WHEN_NO_MODERATOR_DELAY_IN_MINUTES));
|
||||
} catch (Exception ex) {
|
||||
log.warn("Invalid param [endWhenNoModeratorDelayInMinutes] for meeting=[{}]", internalMeetingId);
|
||||
}
|
||||
}
|
||||
|
||||
String guestPolicy = defaultGuestPolicy;
|
||||
if (!StringUtils.isEmpty(params.get(ApiParams.GUEST_POLICY))) {
|
||||
guestPolicy = params.get(ApiParams.GUEST_POLICY);
|
||||
}
|
||||
}
|
||||
|
||||
String meetingLayout = defaultMeetingLayout;
|
||||
|
||||
if (!StringUtils.isEmpty(params.get(ApiParams.MEETING_LAYOUT))) {
|
||||
meetingLayout = params.get(ApiParams.MEETING_LAYOUT);
|
||||
}
|
||||
|
||||
BreakoutRoomsParams breakoutParams = processBreakoutRoomsParams(params);
|
||||
LockSettingsParams lockSettingsParams = processLockSettingsParams(params);
|
||||
|
||||
|
||||
|
||||
// Collect metadata for this meeting that the third-party app wants to
|
||||
// store if meeting is recorded.
|
||||
Map<String, String> meetingInfo = processMetaParam(params);
|
||||
@ -496,15 +541,16 @@ public class ParamsProcessorUtil {
|
||||
.withWelcomeMessage(welcomeMessage).isBreakout(isBreakout)
|
||||
.withGuestPolicy(guestPolicy)
|
||||
.withAuthenticatedGuest(authenticatedGuest)
|
||||
.withMeetingLayout(meetingLayout)
|
||||
.withBreakoutRoomsParams(breakoutParams)
|
||||
.withLockSettingsParams(lockSettingsParams)
|
||||
.withAllowDuplicateExtUserid(defaultAllowDuplicateExtUserid)
|
||||
.withHTML5InstanceId(html5InstanceId)
|
||||
.withLearningDashboardEnabled(learningDashboardEn)
|
||||
.withLearningDashboardCleanupDelayInMinutes(learningDashboardCleanupMins)
|
||||
.withLearningDashboardAccessToken(learningDashboardAccessToken)
|
||||
.build();
|
||||
|
||||
String configXML = getDefaultConfigXML();
|
||||
meeting.storeConfig(true, configXML);
|
||||
|
||||
if (!StringUtils.isEmpty(params.get(ApiParams.MODERATOR_ONLY_MESSAGE))) {
|
||||
String moderatorOnlyMessageTemplate = params.get(ApiParams.MODERATOR_ONLY_MESSAGE);
|
||||
String moderatorOnlyMessage = substituteKeywords(moderatorOnlyMessageTemplate,
|
||||
@ -523,6 +569,8 @@ public class ParamsProcessorUtil {
|
||||
meeting.setUserActivitySignResponseDelayInMinutes(userActivitySignResponseDelayInMinutes);
|
||||
meeting.setUserInactivityThresholdInMinutes(userInactivityThresholdInMinutes);
|
||||
// meeting.setHtml5InstanceId(html5InstanceId);
|
||||
meeting.setEndWhenNoModerator(endWhenNoModerator);
|
||||
meeting.setEndWhenNoModeratorDelayInMinutes(endWhenNoModeratorDelayInMinutes);
|
||||
|
||||
// Add extra parameters for breakout room
|
||||
if (isBreakout) {
|
||||
@ -533,6 +581,8 @@ public class ParamsProcessorUtil {
|
||||
|
||||
if (!StringUtils.isEmpty(params.get(ApiParams.LOGO))) {
|
||||
meeting.setCustomLogoURL(params.get(ApiParams.LOGO));
|
||||
} else if (this.getUseDefaultLogo()) {
|
||||
meeting.setCustomLogoURL(this.getDefaultLogoURL());
|
||||
}
|
||||
|
||||
if (!StringUtils.isEmpty(params.get(ApiParams.COPYRIGHT))) {
|
||||
@ -543,6 +593,12 @@ public class ParamsProcessorUtil {
|
||||
muteOnStart = Boolean.parseBoolean(params.get(ApiParams.MUTE_ON_START));
|
||||
}
|
||||
|
||||
// when a moderator joins in a breakout room only with the audio, and the muteOnStart is set to true,
|
||||
// the moderator is unable to unmute himself, because they don't have an icon to do so
|
||||
if (isBreakout) {
|
||||
muteOnStart = false;
|
||||
}
|
||||
|
||||
meeting.setMuteOnStart(muteOnStart);
|
||||
|
||||
Boolean meetingKeepEvents = defaultKeepEvents;
|
||||
@ -559,15 +615,15 @@ public class ParamsProcessorUtil {
|
||||
|
||||
return meeting;
|
||||
}
|
||||
|
||||
|
||||
public String getApiVersion() {
|
||||
return apiVersion;
|
||||
}
|
||||
|
||||
|
||||
public boolean isServiceEnabled() {
|
||||
return serviceEnabled;
|
||||
}
|
||||
|
||||
|
||||
public String getDefaultHTML5ClientUrl() {
|
||||
return defaultHTML5ClientUrl;
|
||||
}
|
||||
@ -576,58 +632,18 @@ public class ParamsProcessorUtil {
|
||||
return defaultGuestWaitURL;
|
||||
}
|
||||
|
||||
public Boolean getUseDefaultLogo() {
|
||||
return useDefaultLogo;
|
||||
}
|
||||
|
||||
public String getDefaultLogoURL() {
|
||||
return defaultLogoURL;
|
||||
}
|
||||
|
||||
public Boolean getAllowRequestsWithoutSession() {
|
||||
return allowRequestsWithoutSession;
|
||||
}
|
||||
|
||||
public String getDefaultConfigXML() {
|
||||
defaultConfigXML = getConfig(defaultConfigURL);
|
||||
|
||||
return defaultConfigXML;
|
||||
}
|
||||
|
||||
private String getConfig(String url) {
|
||||
String configXML = "";
|
||||
|
||||
CloseableHttpClient httpclient = HttpClients.createDefault();
|
||||
try {
|
||||
HttpGet httpget = new HttpGet(url);
|
||||
|
||||
// Create a custom response handler
|
||||
ResponseHandler<String> responseHandler = new ResponseHandler<String>() {
|
||||
|
||||
@Override
|
||||
public String handleResponse(
|
||||
final HttpResponse response) throws IOException {
|
||||
int status = response.getStatusLine().getStatusCode();
|
||||
if (status >= 200 && status < 300) {
|
||||
HttpEntity entity = response.getEntity();
|
||||
return entity != null ? EntityUtils.toString(entity, StandardCharsets.UTF_8) : null;
|
||||
} else {
|
||||
throw new ClientProtocolException("Unexpected response status: " + status);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
String responseBody = httpclient.execute(httpget, responseHandler);
|
||||
configXML = responseBody;
|
||||
} catch(IOException ex) {
|
||||
// IOException
|
||||
} finally {
|
||||
try {
|
||||
httpclient.close();
|
||||
} catch(IOException ex) {
|
||||
// do nothing
|
||||
}
|
||||
}
|
||||
|
||||
return configXML;
|
||||
}
|
||||
|
||||
public String getDefaultConfigURL() {
|
||||
return defaultConfigURL;
|
||||
}
|
||||
|
||||
public String getDefaultLogoutUrl() {
|
||||
if ((StringUtils.isEmpty(defaultLogoutUrl)) || "default".equalsIgnoreCase(defaultLogoutUrl)) {
|
||||
return defaultServerUrl;
|
||||
@ -635,7 +651,7 @@ public class ParamsProcessorUtil {
|
||||
return defaultLogoutUrl;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public String processWelcomeMessage(String message, Boolean isBreakout) {
|
||||
String welcomeMessage = message;
|
||||
if (StringUtils.isEmpty(message)) {
|
||||
@ -649,7 +665,7 @@ public class ParamsProcessorUtil {
|
||||
public String convertToInternalMeetingId(String extMeetingId) {
|
||||
return DigestUtils.sha1Hex(extMeetingId);
|
||||
}
|
||||
|
||||
|
||||
public String processPassword(String pass) {
|
||||
return StringUtils.isEmpty(pass) ? RandomStringUtils.randomAlphanumeric(8) : pass;
|
||||
}
|
||||
@ -661,39 +677,39 @@ public class ParamsProcessorUtil {
|
||||
public String processTelVoice(String telNum) {
|
||||
return StringUtils.isEmpty(telNum) ? RandomStringUtils.randomNumeric(defaultNumDigitsForTelVoice) : telNum;
|
||||
}
|
||||
|
||||
|
||||
public String processDialNumber(String dial) {
|
||||
return StringUtils.isEmpty(dial) ? defaultDialAccessNumber : dial;
|
||||
return StringUtils.isEmpty(dial) ? defaultDialAccessNumber : dial;
|
||||
}
|
||||
|
||||
|
||||
public String processLogoutUrl(String logoutUrl) {
|
||||
if (StringUtils.isEmpty(logoutUrl)) {
|
||||
if ((StringUtils.isEmpty(defaultLogoutUrl)) || "default".equalsIgnoreCase(defaultLogoutUrl)) {
|
||||
return defaultServerUrl;
|
||||
} else {
|
||||
return defaultLogoutUrl;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return logoutUrl;
|
||||
}
|
||||
|
||||
|
||||
public boolean processRecordMeeting(String record) {
|
||||
// The administrator has turned off recording for all meetings.
|
||||
if (disableRecordingDefault) {
|
||||
log.info("Recording is turned OFF by default.");
|
||||
return false;
|
||||
}
|
||||
|
||||
boolean rec = false;
|
||||
|
||||
boolean rec = false;
|
||||
if(! StringUtils.isEmpty(record)){
|
||||
try {
|
||||
rec = Boolean.parseBoolean(record);
|
||||
} catch(Exception ex){
|
||||
} catch(Exception ex){
|
||||
rec = false;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return rec;
|
||||
}
|
||||
|
||||
@ -707,28 +723,28 @@ public class ParamsProcessorUtil {
|
||||
|
||||
return html5InstanceId;
|
||||
}
|
||||
|
||||
|
||||
public int processMaxUser(String maxUsers) {
|
||||
int mUsers = -1;
|
||||
|
||||
|
||||
try {
|
||||
mUsers = Integer.parseInt(maxUsers);
|
||||
} catch(Exception ex) {
|
||||
} catch(Exception ex) {
|
||||
mUsers = defaultMaxUsers;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return mUsers;
|
||||
}
|
||||
}
|
||||
|
||||
public int processMeetingDuration(String duration) {
|
||||
int mDuration = -1;
|
||||
|
||||
|
||||
try {
|
||||
mDuration = Integer.parseInt(duration);
|
||||
} catch(Exception ex) {
|
||||
} catch(Exception ex) {
|
||||
mDuration = defaultMeetingDuration;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
return mDuration;
|
||||
}
|
||||
|
||||
@ -748,7 +764,7 @@ public class ParamsProcessorUtil {
|
||||
return ((!StringUtils.isEmpty(telVoice)) && (!StringUtils.isEmpty(testVoiceBridge))
|
||||
&& (telVoice.equals(testVoiceBridge)));
|
||||
}
|
||||
|
||||
|
||||
public String getIntMeetingIdForTestMeeting(String telVoice) {
|
||||
if ((testVoiceBridge != null) && (testVoiceBridge.equals(telVoice))
|
||||
&& StringUtils.isEmpty(testConferenceMock)) {
|
||||
@ -757,29 +773,8 @@ public class ParamsProcessorUtil {
|
||||
|
||||
return "";
|
||||
}
|
||||
|
||||
public boolean isConfigXMLChecksumSame(String meetingID, String configXML, String checksum) {
|
||||
if (StringUtils.isEmpty(securitySalt)) {
|
||||
log.warn("Security is disabled in this service. Make sure this is intentional.");
|
||||
return true;
|
||||
}
|
||||
|
||||
log.info("CONFIGXML CHECKSUM={} length={}", checksum, checksum.length());
|
||||
|
||||
String data = meetingID + configXML + securitySalt;
|
||||
String cs = DigestUtils.sha1Hex(data);
|
||||
if (checksum.length() == 64) {
|
||||
cs = DigestUtils.sha256Hex(data);
|
||||
log.info("CONFIGXML SHA256 {}", cs);
|
||||
}
|
||||
|
||||
if (cs == null || !cs.equals(checksum)) {
|
||||
log.info("checksumError: configXML checksum. our: [{}], client: [{}]", cs, checksum);
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Can be removed. Checksum validation is performed by the ChecksumValidator
|
||||
public boolean isChecksumSame(String apiCall, String checksum, String queryString) {
|
||||
if (StringUtils.isEmpty(securitySalt)) {
|
||||
log.warn("Security is disabled in this service. Make sure this is intentional.");
|
||||
@ -810,9 +805,9 @@ public class ParamsProcessorUtil {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
return true;
|
||||
}
|
||||
|
||||
|
||||
public boolean isPostChecksumSame(String apiCall, Map<String, String[]> params) {
|
||||
if (StringUtils.isEmpty(securitySalt)) {
|
||||
log.warn("Security is disabled in this service. Make sure this is intentional.");
|
||||
@ -821,9 +816,9 @@ public class ParamsProcessorUtil {
|
||||
|
||||
StringBuilder csbuf = new StringBuilder();
|
||||
csbuf.append(apiCall);
|
||||
|
||||
|
||||
SortedSet<String> keys = new TreeSet<>(params.keySet());
|
||||
|
||||
|
||||
boolean first = true;
|
||||
String checksum = null;
|
||||
for (String key: keys) {
|
||||
@ -832,7 +827,7 @@ public class ParamsProcessorUtil {
|
||||
checksum = params.get(key)[0];
|
||||
continue;
|
||||
}
|
||||
|
||||
|
||||
for (String value: params.get(key)) {
|
||||
if (first) {
|
||||
first = false;
|
||||
@ -844,26 +839,26 @@ public class ParamsProcessorUtil {
|
||||
String encResult;
|
||||
|
||||
encResult = value;
|
||||
|
||||
|
||||
/*****
|
||||
* Seems like Grails 2.3.6 decodes the string. So we need to re-encode it.
|
||||
* We'll remove this later. richard (aug 5, 2014)
|
||||
*/ try {
|
||||
* We'll remove this later. richard (aug 5, 2014)
|
||||
*/ try {
|
||||
// we need to re-encode the values because Grails unencoded it
|
||||
// when it received the 'POST'ed data. Might not need to do in a GET request.
|
||||
encResult = URLEncoder.encode(value, StandardCharsets.UTF_8.name());
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
encResult = value;
|
||||
}
|
||||
encResult = URLEncoder.encode(value, StandardCharsets.UTF_8.name());
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
encResult = value;
|
||||
}
|
||||
|
||||
csbuf.append(encResult);
|
||||
}
|
||||
}
|
||||
csbuf.append(securitySalt);
|
||||
|
||||
String baseString = csbuf.toString();
|
||||
String baseString = csbuf.toString();
|
||||
String cs = DigestUtils.sha1Hex(baseString);
|
||||
|
||||
|
||||
if (cs == null || !cs.equals(checksum)) {
|
||||
log.info("POST basestring = {}", baseString);
|
||||
log.info("checksumError: failed checksum. our checksum: [{}], client: [{}]", cs, checksum);
|
||||
@ -876,7 +871,7 @@ public class ParamsProcessorUtil {
|
||||
/*************************************************
|
||||
* Setters
|
||||
************************************************/
|
||||
|
||||
|
||||
public void setApiVersion(String apiVersion) {
|
||||
this.apiVersion = apiVersion;
|
||||
}
|
||||
@ -884,7 +879,7 @@ public class ParamsProcessorUtil {
|
||||
public void setServiceEnabled(boolean e) {
|
||||
serviceEnabled = e;
|
||||
}
|
||||
|
||||
|
||||
public void setSecuritySalt(String securitySalt) {
|
||||
this.securitySalt = securitySalt;
|
||||
}
|
||||
@ -896,7 +891,7 @@ public class ParamsProcessorUtil {
|
||||
public void setDefaultWelcomeMessage(String defaultWelcomeMessage) {
|
||||
this.defaultWelcomeMessage = defaultWelcomeMessage;
|
||||
}
|
||||
|
||||
|
||||
public void setDefaultWelcomeMessageFooter(String defaultWelcomeMessageFooter) {
|
||||
this.defaultWelcomeMessageFooter = defaultWelcomeMessageFooter;
|
||||
}
|
||||
@ -917,10 +912,6 @@ public class ParamsProcessorUtil {
|
||||
this.defaultLogoutUrl = defaultLogoutUrl;
|
||||
}
|
||||
|
||||
public void setDefaultConfigURL(String defaultConfigUrl) {
|
||||
this.defaultConfigURL = defaultConfigUrl;
|
||||
}
|
||||
|
||||
public void setDefaultServerUrl(String defaultServerUrl) {
|
||||
this.defaultServerUrl = defaultServerUrl;
|
||||
}
|
||||
@ -937,6 +928,14 @@ public class ParamsProcessorUtil {
|
||||
this.defaultGuestWaitURL = url;
|
||||
}
|
||||
|
||||
public void setUseDefaultLogo(Boolean value) {
|
||||
this.useDefaultLogo = value;
|
||||
}
|
||||
|
||||
public void setDefaultLogoURL(String url) {
|
||||
this.defaultLogoURL = url;
|
||||
}
|
||||
|
||||
public void setAllowRequestsWithoutSession(Boolean allowRequestsWithoutSession) {
|
||||
this.allowRequestsWithoutSession = allowRequestsWithoutSession;
|
||||
}
|
||||
@ -948,7 +947,7 @@ public class ParamsProcessorUtil {
|
||||
public void setDisableRecordingDefault(boolean disabled) {
|
||||
this.disableRecordingDefault = disabled;
|
||||
}
|
||||
|
||||
|
||||
public void setAutoStartRecording(boolean start) {
|
||||
this.autoStartRecording = start;
|
||||
}
|
||||
@ -956,11 +955,19 @@ public class ParamsProcessorUtil {
|
||||
public void setAllowStartStopRecording(boolean allowStartStopRecording) {
|
||||
this.allowStartStopRecording = allowStartStopRecording;
|
||||
}
|
||||
|
||||
|
||||
public void setLearningDashboardEnabled(boolean learningDashboardEnabled) {
|
||||
this.learningDashboardEnabled = learningDashboardEnabled;
|
||||
}
|
||||
|
||||
public void setlearningDashboardCleanupDelayInMinutes(int learningDashboardCleanupDelayInMinutes) {
|
||||
this.learningDashboardCleanupDelayInMinutes = learningDashboardCleanupDelayInMinutes;
|
||||
}
|
||||
|
||||
public void setWebcamsOnlyForModerator(boolean webcamsOnlyForModerator) {
|
||||
this.webcamsOnlyForModerator = webcamsOnlyForModerator;
|
||||
}
|
||||
|
||||
|
||||
public void setUseDefaultAvatar(Boolean value) {
|
||||
this.useDefaultAvatar = value;
|
||||
}
|
||||
@ -977,6 +984,10 @@ public class ParamsProcessorUtil {
|
||||
this.authenticatedGuest = value;
|
||||
}
|
||||
|
||||
public void setDefaultMeetingLayout(String meetingLayout) {
|
||||
this.defaultMeetingLayout = meetingLayout;
|
||||
}
|
||||
|
||||
public void setClientLogoutTimerInMinutes(Integer value) {
|
||||
clientLogoutTimerInMinutes = value;
|
||||
}
|
||||
@ -992,7 +1003,7 @@ public class ParamsProcessorUtil {
|
||||
public void setMeetingExpireIfNoUserJoinedInMinutes(Integer value) {
|
||||
meetingExpireIfNoUserJoinedInMinutes = value;
|
||||
}
|
||||
|
||||
|
||||
public Integer getUserInactivityInspectTimerInMinutes() {
|
||||
return userInactivityInspectTimerInMinutes;
|
||||
}
|
||||
@ -1000,7 +1011,7 @@ public class ParamsProcessorUtil {
|
||||
public void setUserInactivityInspectTimerInMinutes(Integer userInactivityInspectTimerInMinutes) {
|
||||
this.userInactivityInspectTimerInMinutes = userInactivityInspectTimerInMinutes;
|
||||
}
|
||||
|
||||
|
||||
public Integer getUserInactivityThresholdInMinutes() {
|
||||
return userInactivityThresholdInMinutes;
|
||||
}
|
||||
@ -1052,7 +1063,7 @@ public class ParamsProcessorUtil {
|
||||
} catch (UnsupportedEncodingException e) {
|
||||
log.error("Couldn't decode the IDs");
|
||||
}
|
||||
|
||||
|
||||
return ids;
|
||||
}
|
||||
|
||||
@ -1063,7 +1074,7 @@ public class ParamsProcessorUtil {
|
||||
}
|
||||
return internalMeetingIds;
|
||||
}
|
||||
|
||||
|
||||
public Map<String, String> getUserCustomData(Map<String, String> params) {
|
||||
Map<String, String> resp = new HashMap<>();
|
||||
|
||||
@ -1156,5 +1167,8 @@ public class ParamsProcessorUtil {
|
||||
this.defaultEndWhenNoModerator = val;
|
||||
}
|
||||
|
||||
public void setEndWhenNoModeratorDelayInMinutes(Integer value) {
|
||||
this.defaultEndWhenNoModeratorDelayInMinutes = value;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,13 +1,13 @@
|
||||
/**
|
||||
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
|
||||
*
|
||||
*
|
||||
* Copyright (c) 2012 BigBlueButton Inc. and by respective authors (see below).
|
||||
*
|
||||
* This program is free software; you can redistribute it and/or modify it under the
|
||||
* terms of the GNU Lesser General Public License as published by the Free Software
|
||||
* Foundation; either version 3.0 of the License, or (at your option) any later
|
||||
* version.
|
||||
*
|
||||
*
|
||||
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
|
||||
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
|
||||
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
|
||||
@ -42,7 +42,7 @@ public class Meeting {
|
||||
private String parentMeetingId = "bbb-none"; // Initialize so we don't send null in the json message.
|
||||
private Integer sequence = 0;
|
||||
private Boolean freeJoin = false;
|
||||
private Integer duration = 0;
|
||||
private Integer duration = 0;
|
||||
private long createdTime = 0;
|
||||
private long startTime = 0;
|
||||
private long endTime = 0;
|
||||
@ -51,6 +51,9 @@ public class Meeting {
|
||||
private String webVoice;
|
||||
private String moderatorPass;
|
||||
private String viewerPass;
|
||||
private Boolean learningDashboardEnabled;
|
||||
private int learningDashboardCleanupDelayInMinutes;
|
||||
private String learningDashboardAccessToken;
|
||||
private String welcomeMsgTemplate;
|
||||
private String welcomeMsg;
|
||||
private String modOnlyMessage = "";
|
||||
@ -66,10 +69,10 @@ public class Meeting {
|
||||
private boolean webcamsOnlyForModerator = false;
|
||||
private String dialNumber;
|
||||
private String defaultAvatarURL;
|
||||
private String defaultConfigToken;
|
||||
private String guestPolicy = GuestPolicy.ASK_MODERATOR;
|
||||
private String guestLobbyMessage = "";
|
||||
private Boolean authenticatedGuest = false;
|
||||
private String meetingLayout = MeetingLayout.SMART_LAYOUT;
|
||||
private boolean userHasJoined = false;
|
||||
private Map<String, String> pads;
|
||||
private Map<String, String> metadata;
|
||||
@ -77,7 +80,6 @@ public class Meeting {
|
||||
private final ConcurrentMap<String, User> users;
|
||||
private final ConcurrentMap<String, RegisteredUser> registeredUsers;
|
||||
private final ConcurrentMap<String, Long> enteredUsers;
|
||||
private final ConcurrentMap<String, Config> configs;
|
||||
private final Boolean isBreakout;
|
||||
private final List<String> breakoutRooms = new ArrayList<>();
|
||||
private String customLogoURL = "";
|
||||
@ -91,6 +93,8 @@ public class Meeting {
|
||||
private Integer userInactivityInspectTimerInMinutes = 120;
|
||||
private Integer userInactivityThresholdInMinutes = 30;
|
||||
private Integer userActivitySignResponseDelayInMinutes = 5;
|
||||
private Boolean endWhenNoModerator = false;
|
||||
private Integer endWhenNoModeratorDelayInMinutes = 1;
|
||||
|
||||
public final BreakoutRoomsParams breakoutRoomsParams;
|
||||
public final LockSettingsParams lockSettingsParams;
|
||||
@ -99,8 +103,6 @@ public class Meeting {
|
||||
|
||||
private String meetingEndedCallbackURL = "";
|
||||
|
||||
public final Boolean endWhenNoModerator;
|
||||
|
||||
private Integer html5InstanceId;
|
||||
|
||||
public Meeting(Meeting.Builder builder) {
|
||||
@ -109,6 +111,9 @@ public class Meeting {
|
||||
intMeetingId = builder.internalId;
|
||||
viewerPass = builder.viewerPass;
|
||||
moderatorPass = builder.moderatorPass;
|
||||
learningDashboardEnabled = builder.learningDashboardEnabled;
|
||||
learningDashboardCleanupDelayInMinutes = builder.learningDashboardCleanupDelayInMinutes;
|
||||
learningDashboardAccessToken = builder.learningDashboardAccessToken;
|
||||
maxUsers = builder.maxUsers;
|
||||
bannerColor = builder.bannerColor;
|
||||
bannerText = builder.bannerText;
|
||||
@ -130,10 +135,12 @@ public class Meeting {
|
||||
isBreakout = builder.isBreakout;
|
||||
guestPolicy = builder.guestPolicy;
|
||||
authenticatedGuest = builder.authenticatedGuest;
|
||||
meetingLayout = builder.meetingLayout;
|
||||
breakoutRoomsParams = builder.breakoutRoomsParams;
|
||||
lockSettingsParams = builder.lockSettingsParams;
|
||||
allowDuplicateExtUserid = builder.allowDuplicateExtUserid;
|
||||
endWhenNoModerator = builder.endWhenNoModerator;
|
||||
endWhenNoModeratorDelayInMinutes = builder.endWhenNoModeratorDelayInMinutes;
|
||||
html5InstanceId = builder.html5InstanceId;
|
||||
|
||||
/*
|
||||
@ -147,9 +154,7 @@ public class Meeting {
|
||||
|
||||
users = new ConcurrentHashMap<>();
|
||||
registeredUsers = new ConcurrentHashMap<>();
|
||||
enteredUsers = new ConcurrentHashMap<>();;
|
||||
|
||||
configs = new ConcurrentHashMap<>();
|
||||
enteredUsers = new ConcurrentHashMap<>();
|
||||
}
|
||||
|
||||
public void addBreakoutRoom(String meetingId) {
|
||||
@ -160,37 +165,6 @@ public class Meeting {
|
||||
return breakoutRooms;
|
||||
}
|
||||
|
||||
public String storeConfig(boolean defaultConfig, String config) {
|
||||
String token = RandomStringUtils.randomAlphanumeric(8);
|
||||
while (configs.containsKey(token)) {
|
||||
token = RandomStringUtils.randomAlphanumeric(8);
|
||||
}
|
||||
|
||||
configs.put(token, new Config(token, System.currentTimeMillis(), config));
|
||||
|
||||
if (defaultConfig) {
|
||||
defaultConfigToken = token;
|
||||
}
|
||||
|
||||
return token;
|
||||
}
|
||||
|
||||
public Config getDefaultConfig() {
|
||||
if (defaultConfigToken != null) {
|
||||
return getConfig(defaultConfigToken);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Config getConfig(String token) {
|
||||
return configs.get(token);
|
||||
}
|
||||
|
||||
public Config removeConfig(String token) {
|
||||
return configs.remove(token);
|
||||
}
|
||||
|
||||
public Map<String, String> getPads() {
|
||||
return pads;
|
||||
}
|
||||
@ -253,11 +227,11 @@ public class Meeting {
|
||||
public long getStartTime() {
|
||||
return startTime;
|
||||
}
|
||||
|
||||
|
||||
public void setStartTime(long t) {
|
||||
startTime = t;
|
||||
}
|
||||
|
||||
|
||||
public long getCreateTime() {
|
||||
return createdTime;
|
||||
}
|
||||
@ -277,35 +251,35 @@ public class Meeting {
|
||||
public void setFreeJoin(Boolean freeJoin) {
|
||||
this.freeJoin = freeJoin;
|
||||
}
|
||||
|
||||
|
||||
public Integer getDuration() {
|
||||
return duration;
|
||||
}
|
||||
|
||||
|
||||
public long getEndTime() {
|
||||
return endTime;
|
||||
}
|
||||
|
||||
|
||||
public void setModeratorOnlyMessage(String msg) {
|
||||
modOnlyMessage = msg;
|
||||
}
|
||||
|
||||
|
||||
public String getModeratorOnlyMessage() {
|
||||
return modOnlyMessage;
|
||||
}
|
||||
|
||||
|
||||
public void setEndTime(long t) {
|
||||
endTime = t;
|
||||
}
|
||||
|
||||
|
||||
public boolean isRunning() {
|
||||
return ! users.isEmpty();
|
||||
}
|
||||
|
||||
|
||||
public Boolean isBreakout() {
|
||||
return isBreakout;
|
||||
}
|
||||
|
||||
|
||||
public void setHaveRecordingMarks(boolean marks) {
|
||||
haveRecordingMarks = marks;
|
||||
}
|
||||
@ -313,7 +287,7 @@ public class Meeting {
|
||||
public boolean haveRecordingMarks() {
|
||||
return haveRecordingMarks;
|
||||
}
|
||||
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
@ -329,7 +303,7 @@ public class Meeting {
|
||||
public String getExternalId() {
|
||||
return extMeetingId;
|
||||
}
|
||||
|
||||
|
||||
public String getInternalId() {
|
||||
return intMeetingId;
|
||||
}
|
||||
@ -357,10 +331,22 @@ public class Meeting {
|
||||
public String getViewerPassword() {
|
||||
return viewerPass;
|
||||
}
|
||||
|
||||
public String getWelcomeMessageTemplate() {
|
||||
return welcomeMsgTemplate;
|
||||
}
|
||||
|
||||
public Boolean getLearningDashboardEnabled() {
|
||||
return learningDashboardEnabled;
|
||||
}
|
||||
|
||||
public int getLearningDashboardCleanupDelayInMinutes() {
|
||||
return learningDashboardCleanupDelayInMinutes;
|
||||
}
|
||||
|
||||
public String getLearningDashboardAccessToken() {
|
||||
return learningDashboardAccessToken;
|
||||
}
|
||||
|
||||
public String getWelcomeMessageTemplate() {
|
||||
return welcomeMsgTemplate;
|
||||
}
|
||||
|
||||
public String getWelcomeMessage() {
|
||||
return welcomeMsg;
|
||||
@ -394,6 +380,14 @@ public class Meeting {
|
||||
return authenticatedGuest;
|
||||
}
|
||||
|
||||
public void setMeetingLayout(String layout) {
|
||||
meetingLayout = layout;
|
||||
}
|
||||
|
||||
public String getMeetingLayout() {
|
||||
return meetingLayout;
|
||||
}
|
||||
|
||||
private String getUnauthenticatedGuestStatus(Boolean guest) {
|
||||
if (guest) {
|
||||
switch(guestPolicy) {
|
||||
@ -447,15 +441,15 @@ public class Meeting {
|
||||
public int getMaxUsers() {
|
||||
return maxUsers;
|
||||
}
|
||||
|
||||
|
||||
public int getLogoutTimer() {
|
||||
return logoutTimer;
|
||||
}
|
||||
|
||||
|
||||
public String getBannerColor() {
|
||||
return bannerColor;
|
||||
}
|
||||
|
||||
|
||||
public String getBannerText() {
|
||||
return bannerText;
|
||||
}
|
||||
@ -463,19 +457,19 @@ public class Meeting {
|
||||
public boolean isRecord() {
|
||||
return record;
|
||||
}
|
||||
|
||||
|
||||
public boolean getAutoStartRecording() {
|
||||
return autoStartRecording;
|
||||
}
|
||||
|
||||
|
||||
public boolean getAllowStartStopRecording() {
|
||||
return allowStartStopRecording;
|
||||
}
|
||||
|
||||
|
||||
public boolean getWebcamsOnlyForModerator() {
|
||||
return webcamsOnlyForModerator;
|
||||
}
|
||||
|
||||
|
||||
public boolean hasUserJoined() {
|
||||
return userHasJoined;
|
||||
}
|
||||
@ -559,11 +553,11 @@ public class Meeting {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
|
||||
public int getNumUsers(){
|
||||
return this.users.size();
|
||||
}
|
||||
|
||||
|
||||
public int getNumModerators() {
|
||||
int sum = 0;
|
||||
for (Map.Entry<String, User> entry : users.entrySet()) {
|
||||
@ -573,7 +567,7 @@ public class Meeting {
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
|
||||
public String getDialNumber() {
|
||||
return dialNumber;
|
||||
}
|
||||
@ -587,7 +581,7 @@ public class Meeting {
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
|
||||
public int getNumVoiceJoined() {
|
||||
int sum = 0;
|
||||
for (Map.Entry<String, User> entry : users.entrySet()) {
|
||||
@ -606,7 +600,7 @@ public class Meeting {
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
|
||||
public void addPad(String padId, String readOnlyId) {
|
||||
pads.put(padId, readOnlyId);
|
||||
}
|
||||
@ -635,7 +629,7 @@ public class Meeting {
|
||||
public Integer getMeetingExpireIfNoUserJoinedInMinutes() {
|
||||
return meetingExpireIfNoUserJoinedInMinutes;
|
||||
}
|
||||
|
||||
|
||||
public Integer getUserInactivityInspectTimerInMinutes() {
|
||||
return userInactivityInspectTimerInMinutes;
|
||||
}
|
||||
@ -643,7 +637,7 @@ public class Meeting {
|
||||
public void setUserInactivityInspectTimerInMinutes(Integer userInactivityInjspectTimerInMinutes) {
|
||||
this.userInactivityInspectTimerInMinutes = userInactivityInjspectTimerInMinutes;
|
||||
}
|
||||
|
||||
|
||||
public Integer getUserInactivityThresholdInMinutes() {
|
||||
return userInactivityThresholdInMinutes;
|
||||
}
|
||||
@ -660,6 +654,22 @@ public class Meeting {
|
||||
this.userActivitySignResponseDelayInMinutes = userActivitySignResponseDelayInMinutes;
|
||||
}
|
||||
|
||||
public Boolean getEndWhenNoModerator() {
|
||||
return endWhenNoModerator;
|
||||
}
|
||||
|
||||
public void setEndWhenNoModerator(Boolean endWhenNoModerator) {
|
||||
this.endWhenNoModerator = endWhenNoModerator;
|
||||
}
|
||||
|
||||
public Integer getEndWhenNoModeratorDelayInMinutes() {
|
||||
return endWhenNoModeratorDelayInMinutes;
|
||||
}
|
||||
|
||||
public void setEndWhenNoModeratorDelayInMinutes(Integer endWhenNoModeratorDelayInMinutes) {
|
||||
this.endWhenNoModeratorDelayInMinutes = endWhenNoModeratorDelayInMinutes;
|
||||
}
|
||||
|
||||
public String getMeetingEndedCallbackURL() {
|
||||
return meetingEndedCallbackURL;
|
||||
}
|
||||
@ -722,6 +732,9 @@ public class Meeting {
|
||||
private boolean webcamsOnlyForModerator;
|
||||
private String moderatorPass;
|
||||
private String viewerPass;
|
||||
private Boolean learningDashboardEnabled;
|
||||
private int learningDashboardCleanupDelayInMinutes;
|
||||
private String learningDashboardAccessToken;
|
||||
private int duration;
|
||||
private String webVoice;
|
||||
private String telVoice;
|
||||
@ -738,10 +751,12 @@ public class Meeting {
|
||||
private boolean isBreakout;
|
||||
private String guestPolicy;
|
||||
private Boolean authenticatedGuest;
|
||||
private String meetingLayout;
|
||||
private BreakoutRoomsParams breakoutRoomsParams;
|
||||
private LockSettingsParams lockSettingsParams;
|
||||
private Boolean allowDuplicateExtUserid;
|
||||
private Boolean endWhenNoModerator;
|
||||
private Integer endWhenNoModeratorDelayInMinutes;
|
||||
private int html5InstanceId;
|
||||
|
||||
public Builder(String externalId, String internalId, long createTime) {
|
||||
@ -749,7 +764,7 @@ public class Meeting {
|
||||
this.internalId = internalId;
|
||||
this.createdTime = createTime;
|
||||
}
|
||||
|
||||
|
||||
public Builder withName(String name) {
|
||||
this.name = name;
|
||||
return this;
|
||||
@ -759,17 +774,17 @@ public class Meeting {
|
||||
duration = minutes;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder withMaxUsers(int n) {
|
||||
maxUsers = n;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder withRecording(boolean record) {
|
||||
this.record = record;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder withAutoStartRecording(boolean start) {
|
||||
this.autoStartRecording = start;
|
||||
return this;
|
||||
@ -779,37 +794,52 @@ public class Meeting {
|
||||
this.allowStartStopRecording = allow;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder withWebcamsOnlyForModerator(boolean only) {
|
||||
this.webcamsOnlyForModerator = only;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder withWebVoice(String w) {
|
||||
this.webVoice = w;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder withTelVoice(String t) {
|
||||
this.telVoice = t;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder withDialNumber(String d) {
|
||||
this.dialNumber = d;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder withModeratorPass(String p) {
|
||||
this.moderatorPass = p;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withViewerPass(String p) {
|
||||
|
||||
public Builder withViewerPass(String p) {
|
||||
this.viewerPass = p;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withLearningDashboardEnabled(Boolean e) {
|
||||
this.learningDashboardEnabled = e;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withLearningDashboardCleanupDelayInMinutes(int m) {
|
||||
this.learningDashboardCleanupDelayInMinutes = m;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withLearningDashboardAccessToken(String t) {
|
||||
this.learningDashboardAccessToken = t;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withWelcomeMessage(String w) {
|
||||
welcomeMsg = w;
|
||||
return this;
|
||||
@ -819,12 +849,12 @@ public class Meeting {
|
||||
welcomeMsgTemplate = w;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder withDefaultAvatarURL(String w) {
|
||||
defaultAvatarURL = w;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder isBreakout(Boolean b) {
|
||||
isBreakout = b;
|
||||
return this;
|
||||
@ -834,22 +864,22 @@ public class Meeting {
|
||||
logoutUrl = l;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder withLogoutTimer(int l) {
|
||||
logoutTimer = l;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder withBannerColor(String c) {
|
||||
bannerColor = c;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder withBannerText(String t) {
|
||||
bannerText = t;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Builder withMetadata(Map<String, String> m) {
|
||||
metadata = m;
|
||||
return this;
|
||||
@ -858,13 +888,18 @@ public class Meeting {
|
||||
public Builder withGuestPolicy(String policy) {
|
||||
guestPolicy = policy;
|
||||
return this;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public Builder withAuthenticatedGuest(Boolean authGuest) {
|
||||
authenticatedGuest = authGuest;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withMeetingLayout(String layout) {
|
||||
meetingLayout = layout;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withBreakoutRoomsParams(BreakoutRoomsParams params) {
|
||||
breakoutRoomsParams = params;
|
||||
return this;
|
||||
@ -885,11 +920,16 @@ public class Meeting {
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withEndWhenNoModeratorDelayInMinutes(Integer endWhenNoModeratorDelayInMinutes) {
|
||||
this.endWhenNoModeratorDelayInMinutes = endWhenNoModeratorDelayInMinutes;
|
||||
return this;
|
||||
}
|
||||
|
||||
public Builder withHTML5InstanceId(int instanceId) {
|
||||
html5InstanceId = instanceId;
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
public Meeting build() {
|
||||
return new Meeting(this);
|
||||
}
|
||||
|
@ -0,0 +1,7 @@
|
||||
package org.bigbluebutton.api.domain;
|
||||
|
||||
public class MeetingLayout {
|
||||
public static final String FOCUS_ON_PRESENTATION = "FOCUS_ON_PRESENTATION";
|
||||
public static final String FOCUS_ON_WEBCAMS = "FOCUS_ON_WEBCAMS";
|
||||
public static final String SMART_LAYOUT = "SMART_LAYOUT";
|
||||
}
|
@ -42,7 +42,6 @@ public class UserSession {
|
||||
public String logoutUrl = null;
|
||||
public String defaultLayout = "NOLAYOUT";
|
||||
public String avatarURL;
|
||||
public String configXML;
|
||||
public String guestStatus = GuestPolicy.ALLOW;
|
||||
public String clientUrl = null;
|
||||
|
||||
@ -52,6 +51,96 @@ public class UserSession {
|
||||
public synchronized int incrementConnectionNum() {
|
||||
return connections.incrementAndGet();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public String getAuthToken() {
|
||||
return authToken;
|
||||
}
|
||||
|
||||
public String getInternalUserId() {
|
||||
return internalUserId;
|
||||
}
|
||||
|
||||
public String getConferencename() {
|
||||
return conferencename;
|
||||
}
|
||||
|
||||
public String getMeetingID() {
|
||||
return meetingID;
|
||||
}
|
||||
|
||||
public String getExternMeetingID() {
|
||||
return externMeetingID;
|
||||
}
|
||||
|
||||
public String getExternUserID() {
|
||||
return externUserID;
|
||||
}
|
||||
|
||||
public String getFullname() {
|
||||
return fullname;
|
||||
}
|
||||
|
||||
public String getRole() {
|
||||
return role;
|
||||
}
|
||||
|
||||
public String getConference() {
|
||||
return conference;
|
||||
}
|
||||
|
||||
public String getRoom() {
|
||||
return room;
|
||||
}
|
||||
|
||||
public Boolean getGuest() {
|
||||
return guest;
|
||||
}
|
||||
|
||||
public Boolean getAuthed() {
|
||||
return authed;
|
||||
}
|
||||
|
||||
public String getVoicebridge() {
|
||||
return voicebridge;
|
||||
}
|
||||
|
||||
public String getWebvoiceconf() {
|
||||
return webvoiceconf;
|
||||
}
|
||||
|
||||
public String getMode() {
|
||||
return mode;
|
||||
}
|
||||
|
||||
public String getRecord() {
|
||||
return record;
|
||||
}
|
||||
|
||||
public String getWelcome() {
|
||||
return welcome;
|
||||
}
|
||||
|
||||
public String getLogoutUrl() {
|
||||
return logoutUrl;
|
||||
}
|
||||
|
||||
public String getDefaultLayout() {
|
||||
return defaultLayout;
|
||||
}
|
||||
|
||||
public String getAvatarURL() {
|
||||
return avatarURL;
|
||||
}
|
||||
|
||||
public String getGuestStatus() {
|
||||
return guestStatus;
|
||||
}
|
||||
|
||||
public String getClientUrl() {
|
||||
return clientUrl;
|
||||
}
|
||||
|
||||
public String toString() {
|
||||
return fullname + " " + meetingID + " " + conferencename;
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,8 @@ public class CreateMeetingMessage {
|
||||
public boolean webcamsOnlyForModerator;
|
||||
public final String moderatorPass;
|
||||
public final String viewerPass;
|
||||
public final String learningDashboardAccessToken;
|
||||
public final Boolean learningDashboardEnabled;
|
||||
public final Long createTime;
|
||||
public final String createDate;
|
||||
public final Map<String, String> metadata;
|
||||
@ -25,7 +27,8 @@ public class CreateMeetingMessage {
|
||||
String voiceBridge, Long duration,
|
||||
Boolean autoStartRecording, Boolean allowStartStopRecording,
|
||||
Boolean webcamsOnlyForModerator, String moderatorPass,
|
||||
String viewerPass, Long createTime, String createDate, Map<String, String> metadata) {
|
||||
String viewerPass, String learningDashboardAccessToken, Boolean learningDashboardEnabled,
|
||||
Long createTime, String createDate, Map<String, String> metadata) {
|
||||
this.id = id;
|
||||
this.externalId = externalId;
|
||||
this.name = name;
|
||||
@ -37,6 +40,8 @@ public class CreateMeetingMessage {
|
||||
this.webcamsOnlyForModerator = webcamsOnlyForModerator;
|
||||
this.moderatorPass = moderatorPass;
|
||||
this.viewerPass = viewerPass;
|
||||
this.learningDashboardAccessToken = learningDashboardAccessToken;
|
||||
this.learningDashboardEnabled = learningDashboardEnabled;
|
||||
this.createTime = createTime;
|
||||
this.createDate = createDate;
|
||||
this.metadata = metadata;
|
||||
|
@ -6,6 +6,8 @@ public class CreateBreakoutRoom implements IMessage {
|
||||
public final String parentMeetingId; // The main meeting internal id
|
||||
public final String name; // The name of the breakout room
|
||||
public final Integer sequence; // The sequence number of the breakout room
|
||||
public final String shortName; // Name used in breakout rooms list
|
||||
public final Boolean isDefaultName; // Inform if using default name or changed by moderator
|
||||
public final Boolean freeJoin; // Allow users to freely join the conference
|
||||
// in the client
|
||||
public final String dialNumber;
|
||||
@ -22,6 +24,8 @@ public class CreateBreakoutRoom implements IMessage {
|
||||
String parentMeetingId,
|
||||
String name,
|
||||
Integer sequence,
|
||||
String shortName,
|
||||
Boolean isDefaultName,
|
||||
Boolean freeJoin,
|
||||
String dialNumber,
|
||||
String voiceConfId,
|
||||
@ -36,6 +40,8 @@ public class CreateBreakoutRoom implements IMessage {
|
||||
this.parentMeetingId = parentMeetingId;
|
||||
this.name = name;
|
||||
this.sequence = sequence;
|
||||
this.shortName = shortName;
|
||||
this.isDefaultName = isDefaultName;
|
||||
this.freeJoin = freeJoin;
|
||||
this.dialNumber = dialNumber;
|
||||
this.voiceConfId = voiceConfId;
|
||||
|
@ -0,0 +1,11 @@
|
||||
package org.bigbluebutton.api.messaging.messages;
|
||||
|
||||
public class LearningDashboard implements IMessage {
|
||||
public final String meetingId;
|
||||
public final String activityJson;
|
||||
|
||||
public LearningDashboard(String meetingId, String activityJson) {
|
||||
this.meetingId = meetingId;
|
||||
this.activityJson = activityJson;
|
||||
}
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package org.bigbluebutton.api.model.constraint;
|
||||
|
||||
import org.bigbluebutton.api.model.validator.GetChecksumValidator;
|
||||
|
||||
import javax.validation.Constraint;
|
||||
import javax.validation.Payload;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.TYPE;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
@Constraint(validatedBy = GetChecksumValidator.class)
|
||||
@Target(TYPE)
|
||||
@Retention(RUNTIME)
|
||||
public @interface GetChecksumConstraint {
|
||||
|
||||
String message() default "Invalid checksum: checksums do not match";
|
||||
Class<?>[] groups() default {};
|
||||
Class<? extends Payload>[] payload() default {};
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package org.bigbluebutton.api.model.constraint;
|
||||
|
||||
import org.bigbluebutton.api.model.validator.GuestPolicyValidator;
|
||||
|
||||
import javax.validation.Constraint;
|
||||
import javax.validation.Payload;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
@Constraint(validatedBy = { GuestPolicyValidator.class })
|
||||
@Target(FIELD)
|
||||
@Retention(RUNTIME)
|
||||
public @interface GuestPolicyConstraint {
|
||||
|
||||
String message() default "User denied access for this session";
|
||||
Class<?>[] groups() default {};
|
||||
Class<? extends Payload>[] payload() default {};
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package org.bigbluebutton.api.model.constraint;
|
||||
|
||||
import org.bigbluebutton.api.model.validator.IsBooleanValidator;
|
||||
|
||||
import javax.validation.Constraint;
|
||||
import javax.validation.Payload;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
@Constraint(validatedBy = IsBooleanValidator.class)
|
||||
@Target(FIELD)
|
||||
@Retention(RUNTIME)
|
||||
public @interface IsBooleanConstraint {
|
||||
|
||||
String message() default "Validation error: value must be a boolean";
|
||||
Class<?>[] groups() default {};
|
||||
Class<? extends Payload>[] payload() default {};
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package org.bigbluebutton.api.model.constraint;
|
||||
|
||||
import org.bigbluebutton.api.model.validator.IsIntegralValidator;
|
||||
|
||||
import javax.validation.Constraint;
|
||||
import javax.validation.Payload;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
@Constraint(validatedBy = IsIntegralValidator.class)
|
||||
@Target(FIELD)
|
||||
@Retention(RUNTIME)
|
||||
public @interface IsIntegralConstraint {
|
||||
|
||||
String message() default "Validation error: value must be an integral number";
|
||||
Class<?>[] groups() default {};
|
||||
Class<? extends Payload>[] payload() default {};
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package org.bigbluebutton.api.model.constraint;
|
||||
|
||||
import org.bigbluebutton.api.model.validator.MaxParticipantsValidator;
|
||||
|
||||
import javax.validation.Constraint;
|
||||
import javax.validation.Payload;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
@Constraint(validatedBy = { MaxParticipantsValidator.class })
|
||||
@Target(FIELD)
|
||||
@Retention(RUNTIME)
|
||||
public @interface MaxParticipantsConstraint {
|
||||
|
||||
String message() default "The maximum number of participants for the meeting has been reached";
|
||||
Class<?>[] groups() default {};
|
||||
Class<? extends Payload>[] payload() default {};
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package org.bigbluebutton.api.model.constraint;
|
||||
|
||||
import org.bigbluebutton.api.model.validator.MeetingEndedValidator;
|
||||
|
||||
import javax.validation.Constraint;
|
||||
import javax.validation.Payload;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
@Constraint(validatedBy = MeetingEndedValidator.class)
|
||||
@Target(FIELD)
|
||||
@Retention(RUNTIME)
|
||||
public @interface MeetingEndedConstraint {
|
||||
|
||||
String message() default "You can not re-join a meeting that has already been forcibly ended";
|
||||
Class<?>[] groups() default {};
|
||||
Class<? extends Payload>[] payload() default {};
|
||||
}
|
@ -0,0 +1,22 @@
|
||||
package org.bigbluebutton.api.model.constraint;
|
||||
|
||||
import org.bigbluebutton.api.model.validator.MeetingExistsValidator;
|
||||
|
||||
import javax.validation.Constraint;
|
||||
import javax.validation.Payload;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
@Constraint(validatedBy = MeetingExistsValidator.class)
|
||||
@Target(FIELD)
|
||||
@Retention(RUNTIME)
|
||||
public @interface MeetingExistsConstraint {
|
||||
|
||||
String message() default "Invalid meeting ID: A meeting with that ID does not exist";
|
||||
Class<?>[] groups() default {};
|
||||
Class<? extends Payload>[] payload() default {};
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package org.bigbluebutton.api.model.constraint;
|
||||
|
||||
import javax.validation.Constraint;
|
||||
import javax.validation.Payload;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.Pattern;
|
||||
import javax.validation.constraints.Size;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
@NotEmpty(message = "You must provide a meeting ID")
|
||||
@Size(min = 2, max = 256, message = "Meeting ID must be between 2 and 256 characters")
|
||||
@Pattern(regexp = "^[^,]+$", message = "Meeting ID cannot contain ','")
|
||||
@Constraint(validatedBy = {})
|
||||
@Target(FIELD)
|
||||
@Retention(RUNTIME)
|
||||
public @interface MeetingIDConstraint {
|
||||
|
||||
String message() default "Invalid meeting ID";
|
||||
Class<?>[] groups() default {};
|
||||
Class<? extends Payload>[] payload() default {};
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
package org.bigbluebutton.api.model.constraint;
|
||||
|
||||
import javax.validation.Constraint;
|
||||
import javax.validation.Payload;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.Pattern;
|
||||
import javax.validation.constraints.Size;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
@NotEmpty(message = "You must provide a meeting name")
|
||||
@Size(min = 2, max = 256, message = "Meeting name must be between 2 and 256 characters")
|
||||
//@Pattern(regexp = "^[^,]+$", message = "Meeting name cannot contain ','")
|
||||
@Constraint(validatedBy = {})
|
||||
@Target(FIELD)
|
||||
@Retention(RUNTIME)
|
||||
public @interface MeetingNameConstraint {
|
||||
|
||||
String message() default "Invalid meeting name";
|
||||
Class<?>[] groups() default {};
|
||||
Class<? extends Payload>[] payload() default {};
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package org.bigbluebutton.api.model.constraint;
|
||||
|
||||
import org.bigbluebutton.api.model.validator.ModeratorPasswordValidator;
|
||||
|
||||
import javax.validation.Constraint;
|
||||
import javax.validation.Payload;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.TYPE;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
@Constraint(validatedBy = ModeratorPasswordValidator.class)
|
||||
@Target(TYPE)
|
||||
@Retention(RUNTIME)
|
||||
public @interface ModeratorPasswordConstraint {
|
||||
|
||||
String message() default "Invalid password: The supplied moderator password is incorrect";
|
||||
Class<?>[] groups() default {};
|
||||
Class<? extends Payload>[] payload() default {};
|
||||
}
|
@ -0,0 +1,23 @@
|
||||
package org.bigbluebutton.api.model.constraint;
|
||||
|
||||
import javax.validation.Constraint;
|
||||
import javax.validation.Payload;
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.Size;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
@NotEmpty(message = "You must provide your password")
|
||||
@Size(min = 2, max = 64, message = "Password must be between 8 and 20 characters")
|
||||
@Constraint(validatedBy = {})
|
||||
@Target(FIELD)
|
||||
@Retention(RUNTIME)
|
||||
public @interface PasswordConstraint {
|
||||
|
||||
String message() default "Invalid password";
|
||||
Class<?>[] groups() default {};
|
||||
Class<? extends Payload>[] payload() default {};
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package org.bigbluebutton.api.model.constraint;
|
||||
|
||||
import org.bigbluebutton.api.model.validator.PostChecksumValidator;
|
||||
|
||||
import javax.validation.Constraint;
|
||||
import javax.validation.Payload;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.TYPE;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
@Constraint(validatedBy = PostChecksumValidator.class)
|
||||
@Target(TYPE)
|
||||
@Retention(RUNTIME)
|
||||
public @interface PostChecksumConstraint {
|
||||
|
||||
String message() default "Invalid checksum: checksums do not match";
|
||||
Class<?>[] groups() default {};
|
||||
Class<? extends Payload>[] payload() default {};
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
package org.bigbluebutton.api.model.constraint;
|
||||
|
||||
import org.bigbluebutton.api.model.validator.UserSessionValidator;
|
||||
|
||||
import javax.validation.Constraint;
|
||||
import javax.validation.Payload;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import static java.lang.annotation.ElementType.FIELD;
|
||||
import static java.lang.annotation.RetentionPolicy.RUNTIME;
|
||||
|
||||
@Constraint(validatedBy = { UserSessionValidator.class })
|
||||
@Target(FIELD)
|
||||
@Retention(RUNTIME)
|
||||
public @interface UserSessionConstraint {
|
||||
|
||||
String message() default "Invalid session token";
|
||||
Class<?>[] groups() default {};
|
||||
Class<? extends Payload>[] payload() default {};
|
||||
}
|
@ -0,0 +1,142 @@
|
||||
package org.bigbluebutton.api.model.request;
|
||||
|
||||
import org.bigbluebutton.api.model.constraint.*;
|
||||
import org.bigbluebutton.api.model.shared.Checksum;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.Map;
|
||||
|
||||
public class CreateMeeting extends RequestWithChecksum<CreateMeeting.Params> {
|
||||
|
||||
public enum Params implements RequestParameters {
|
||||
NAME("name"),
|
||||
MEETING_ID("meetingID"),
|
||||
VOICE_BRIDGE("voiceBridge"),
|
||||
ATTENDEE_PW("attendeePW"),
|
||||
MODERATOR_PW("moderatorPW"),
|
||||
IS_BREAKOUT_ROOM("isBreakoutRoom"),
|
||||
RECORD("record");
|
||||
|
||||
private final String value;
|
||||
|
||||
Params(String value) { this.value = value; }
|
||||
|
||||
public String getValue() { return value; }
|
||||
}
|
||||
|
||||
@MeetingNameConstraint
|
||||
private String name;
|
||||
|
||||
@MeetingIDConstraint
|
||||
private String meetingID;
|
||||
|
||||
//@NotEmpty(message = "You must provide a voice bridge")
|
||||
@IsIntegralConstraint(message = "Voice bridge must be a 5-digit integral value")
|
||||
private String voiceBridgeString;
|
||||
private Integer voiceBridge;
|
||||
|
||||
@PasswordConstraint
|
||||
private String attendeePW;
|
||||
|
||||
@PasswordConstraint
|
||||
private String moderatorPW;
|
||||
|
||||
//@NotEmpty(message = "You must provide whether this meeting is breakout room")
|
||||
@IsBooleanConstraint(message = "You must provide a boolean value (true or false) for the breakout room")
|
||||
private String isBreakoutRoomString;
|
||||
private Boolean isBreakoutRoom;
|
||||
|
||||
//@NotEmpty(message = "You must provide whether to record this meeting")
|
||||
@IsBooleanConstraint(message = "Record must be a boolean value (true or false)")
|
||||
private String recordString;
|
||||
private Boolean record;
|
||||
|
||||
public CreateMeeting(Checksum checksum) {
|
||||
super(checksum);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public void setName(String name) {
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
public String getMeetingID() {
|
||||
return meetingID;
|
||||
}
|
||||
|
||||
public void setMeetingID(String meetingID) {
|
||||
this.meetingID = meetingID;
|
||||
}
|
||||
|
||||
public String getVoiceBridgeString() {
|
||||
return voiceBridgeString;
|
||||
}
|
||||
|
||||
public void setVoiceBridgeString(String voiceBridgeString) {
|
||||
this.voiceBridgeString = voiceBridgeString;
|
||||
}
|
||||
|
||||
public Integer getVoiceBridge() { return voiceBridge; }
|
||||
|
||||
public void setVoiceBridge(Integer voiceBridge) { this.voiceBridge = voiceBridge; }
|
||||
|
||||
public String getAttendeePW() {
|
||||
return attendeePW;
|
||||
}
|
||||
|
||||
public void setAttendeePW(String attendeePW) {
|
||||
this.attendeePW = attendeePW;
|
||||
}
|
||||
|
||||
public String getModeratorPW() {
|
||||
return moderatorPW;
|
||||
}
|
||||
|
||||
public void setModeratorPW(String moderatorPW) {
|
||||
this.moderatorPW = moderatorPW;
|
||||
}
|
||||
|
||||
public void setBreakoutRoomString(String breakoutRoomString) { isBreakoutRoomString = breakoutRoomString; }
|
||||
|
||||
public Boolean isBreakoutRoom() {
|
||||
return isBreakoutRoom;
|
||||
}
|
||||
|
||||
public void setBreakoutRoom(boolean breakoutRoom) {
|
||||
isBreakoutRoom = breakoutRoom;
|
||||
}
|
||||
|
||||
public void setRecordString(String recordString) { this.recordString = recordString; }
|
||||
|
||||
public Boolean isRecord() {
|
||||
return record;
|
||||
}
|
||||
|
||||
public void setRecord(boolean record) {
|
||||
this.record = record;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void populateFromParamsMap(Map<String, String[]> params) {
|
||||
if(params.containsKey(Params.NAME.getValue())) setName(params.get(Params.NAME.getValue())[0]);
|
||||
if(params.containsKey(Params.MEETING_ID.getValue())) setMeetingID(params.get(Params.MEETING_ID.getValue())[0]);
|
||||
if(params.containsKey(Params.VOICE_BRIDGE.getValue())) setVoiceBridgeString(params.get(Params.VOICE_BRIDGE.getValue())[0]);
|
||||
if(params.containsKey(Params.ATTENDEE_PW.getValue())) setAttendeePW(params.get(Params.ATTENDEE_PW.getValue())[0]);
|
||||
if(params.containsKey(Params.MODERATOR_PW.getValue())) setModeratorPW(params.get(Params.MODERATOR_PW.getValue())[0]);
|
||||
if(params.containsKey(Params.IS_BREAKOUT_ROOM.getValue())) setBreakoutRoomString(params.get(Params.IS_BREAKOUT_ROOM.value)[0]);
|
||||
if(params.containsKey(Params.RECORD.getValue())) setRecordString(params.get(Params.RECORD.getValue())[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void convertParamsFromString() {
|
||||
if (voiceBridge != null) {
|
||||
voiceBridge = Integer.parseInt(voiceBridgeString);
|
||||
}
|
||||
isBreakoutRoom = Boolean.parseBoolean(isBreakoutRoomString);
|
||||
record = Boolean.parseBoolean(recordString);
|
||||
}
|
||||
}
|
@ -0,0 +1,68 @@
|
||||
package org.bigbluebutton.api.model.request;
|
||||
|
||||
import org.bigbluebutton.api.model.constraint.MeetingExistsConstraint;
|
||||
import org.bigbluebutton.api.model.constraint.MeetingIDConstraint;
|
||||
import org.bigbluebutton.api.model.constraint.PasswordConstraint;
|
||||
import org.bigbluebutton.api.model.shared.Checksum;
|
||||
import org.bigbluebutton.api.model.shared.ModeratorPassword;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import java.util.Map;
|
||||
|
||||
public class EndMeeting extends RequestWithChecksum<EndMeeting.Params> {
|
||||
|
||||
public enum Params implements RequestParameters {
|
||||
MEETING_ID("meetingID"),
|
||||
PASSWORD("password");
|
||||
|
||||
private final String value;
|
||||
|
||||
Params(String value) { this.value = value; }
|
||||
|
||||
public String getValue() { return value; }
|
||||
}
|
||||
|
||||
@MeetingIDConstraint
|
||||
@MeetingExistsConstraint
|
||||
private String meetingID;
|
||||
|
||||
@PasswordConstraint
|
||||
private String password;
|
||||
|
||||
@Valid
|
||||
private ModeratorPassword moderatorPassword;
|
||||
|
||||
public EndMeeting(Checksum checksum) {
|
||||
super(checksum);
|
||||
moderatorPassword = new ModeratorPassword();
|
||||
}
|
||||
|
||||
public String getMeetingID() {
|
||||
return meetingID;
|
||||
}
|
||||
|
||||
public void setMeetingID(String meetingID) {
|
||||
this.meetingID = meetingID;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void populateFromParamsMap(Map<String, String[]> params) {
|
||||
if(params.containsKey(Params.MEETING_ID.getValue())) {
|
||||
setMeetingID(params.get(Params.MEETING_ID.getValue())[0]);
|
||||
moderatorPassword.setMeetingID(meetingID);
|
||||
}
|
||||
|
||||
if(params.containsKey(Params.PASSWORD.getValue())) {
|
||||
setPassword(params.get(Params.PASSWORD.getValue())[0]);
|
||||
moderatorPassword.setPassword(password);
|
||||
}
|
||||
}
|
||||
}
|
57
bbb-common-web/src/main/java/org/bigbluebutton/api/model/request/Enter.java
Executable file
57
bbb-common-web/src/main/java/org/bigbluebutton/api/model/request/Enter.java
Executable file
@ -0,0 +1,57 @@
|
||||
package org.bigbluebutton.api.model.request;
|
||||
|
||||
import org.bigbluebutton.api.model.constraint.*;
|
||||
import org.bigbluebutton.api.service.SessionService;
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.Map;
|
||||
|
||||
public class Enter implements Request<Enter.Params> {
|
||||
|
||||
public enum Params implements RequestParameters {
|
||||
SESSION_TOKEN("sessionToken");
|
||||
|
||||
private final String value;
|
||||
|
||||
Params(String value) { this.value = value; }
|
||||
|
||||
public String getValue() { return value; }
|
||||
}
|
||||
|
||||
@NotNull(message = "You must provide a session token")
|
||||
@UserSessionConstraint
|
||||
//@MaxParticipantsConstraint
|
||||
@GuestPolicyConstraint
|
||||
private String sessionToken;
|
||||
|
||||
@MeetingExistsConstraint
|
||||
@MeetingEndedConstraint
|
||||
private String meetingID;
|
||||
|
||||
private SessionService sessionService;
|
||||
|
||||
public Enter() {
|
||||
sessionService = new SessionService();
|
||||
}
|
||||
|
||||
public String getSessionToken() {
|
||||
return sessionToken;
|
||||
}
|
||||
|
||||
public void setSessionToken(String sessionToken) {
|
||||
this.sessionToken = sessionToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void populateFromParamsMap(Map<String, String[]> params) {
|
||||
if(params.containsKey(Params.SESSION_TOKEN.getValue())) {
|
||||
setSessionToken(params.get(Params.SESSION_TOKEN.getValue())[0]);
|
||||
sessionService.setSessionToken(sessionToken);
|
||||
meetingID = sessionService.getMeetingID();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void convertParamsFromString() {
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
package org.bigbluebutton.api.model.request;
|
||||
|
||||
import org.bigbluebutton.api.model.constraint.MaxParticipantsConstraint;
|
||||
import org.bigbluebutton.api.model.constraint.MeetingEndedConstraint;
|
||||
import org.bigbluebutton.api.model.constraint.MeetingExistsConstraint;
|
||||
import org.bigbluebutton.api.model.constraint.UserSessionConstraint;
|
||||
import org.bigbluebutton.api.service.SessionService;
|
||||
|
||||
import javax.validation.constraints.NotNull;
|
||||
import java.util.Map;
|
||||
|
||||
public class GuestWait implements Request<GuestWait.Params> {
|
||||
|
||||
public enum Params implements RequestParameters {
|
||||
SESSION_TOKEN("sessionToken");
|
||||
|
||||
private final String value;
|
||||
|
||||
Params(String value) { this.value = value; }
|
||||
|
||||
public String getValue() { return value; }
|
||||
}
|
||||
|
||||
@NotNull(message = "You must provide the session token")
|
||||
@UserSessionConstraint
|
||||
// @MaxParticipantsConstraint
|
||||
private String sessionToken;
|
||||
|
||||
@MeetingExistsConstraint
|
||||
@MeetingEndedConstraint
|
||||
private String meetingID;
|
||||
|
||||
private SessionService sessionService;
|
||||
|
||||
public GuestWait() {
|
||||
sessionService = new SessionService();
|
||||
}
|
||||
|
||||
public String getSessionToken() {
|
||||
return sessionToken;
|
||||
}
|
||||
|
||||
public void setSessionToken(String sessionToken) {
|
||||
this.sessionToken = sessionToken;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void populateFromParamsMap(Map<String, String[]> params) {
|
||||
if(params.containsKey(Params.SESSION_TOKEN.getValue())) {
|
||||
setSessionToken(params.get(Params.SESSION_TOKEN.getValue())[0]);
|
||||
sessionService.setSessionToken(sessionToken);
|
||||
meetingID = sessionService.getMeetingID();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void convertParamsFromString() {
|
||||
|
||||
}
|
||||
}
|
@ -0,0 +1,141 @@
|
||||
package org.bigbluebutton.api.model.request;
|
||||
|
||||
import org.bigbluebutton.api.model.constraint.*;
|
||||
import org.bigbluebutton.api.model.shared.Checksum;
|
||||
|
||||
import javax.validation.constraints.NotEmpty;
|
||||
import java.util.Map;
|
||||
|
||||
public class JoinMeeting extends RequestWithChecksum<JoinMeeting.Params> {
|
||||
|
||||
public enum Params implements RequestParameters {
|
||||
MEETING_ID("meetingID"),
|
||||
USER_ID("userID"),
|
||||
FULL_NAME("fullName"),
|
||||
PASSWORD("password"),
|
||||
GUEST("guest"),
|
||||
AUTH("auth"),
|
||||
CREATE_TIME("createTime");
|
||||
|
||||
private final String value;
|
||||
|
||||
Params(String value) { this.value = value; }
|
||||
|
||||
public String getValue() { return value; }
|
||||
}
|
||||
|
||||
@MeetingIDConstraint
|
||||
@MeetingExistsConstraint
|
||||
@MeetingEndedConstraint
|
||||
private String meetingID;
|
||||
|
||||
private String userID;
|
||||
|
||||
@NotEmpty(message = "You must provide your name")
|
||||
private String fullName;
|
||||
|
||||
@PasswordConstraint
|
||||
private String password;
|
||||
|
||||
@IsBooleanConstraint(message = "Guest must be a boolean value (true or false)")
|
||||
private String guestString;
|
||||
private Boolean guest;
|
||||
|
||||
@IsBooleanConstraint(message = "Auth must be a boolean value (true or false)")
|
||||
private String authString;
|
||||
private Boolean auth;
|
||||
|
||||
@IsIntegralConstraint
|
||||
private String createTimeString;
|
||||
private Long createTime;
|
||||
|
||||
public JoinMeeting(Checksum checksum) {
|
||||
super(checksum);
|
||||
}
|
||||
|
||||
public String getMeetingID() {
|
||||
return meetingID;
|
||||
}
|
||||
|
||||
public void setMeetingID(String meetingID) {
|
||||
this.meetingID = meetingID;
|
||||
}
|
||||
|
||||
public String getUserID() {
|
||||
return userID;
|
||||
}
|
||||
|
||||
public void setUserID(String userID) {
|
||||
this.userID = userID;
|
||||
}
|
||||
|
||||
public String getFullName() {
|
||||
return fullName;
|
||||
}
|
||||
|
||||
public void setFullName(String fullName) {
|
||||
this.fullName = fullName;
|
||||
}
|
||||
|
||||
public String getPassword() {
|
||||
return password;
|
||||
}
|
||||
|
||||
public void setPassword(String password) {
|
||||
this.password = password;
|
||||
}
|
||||
|
||||
public void setGuestString(String guestString) { this.guestString = guestString; }
|
||||
|
||||
public Boolean getGuest() {
|
||||
return guest;
|
||||
}
|
||||
|
||||
public void setGuest(Boolean guest) {
|
||||
this.guest = guest;
|
||||
}
|
||||
|
||||
public void setAuthString(String authString) { this.authString = authString; }
|
||||
|
||||
public Boolean getAuth() {
|
||||
return auth;
|
||||
}
|
||||
|
||||
public void setAuth(Boolean auth) {
|
||||
this.auth = auth;
|
||||
}
|
||||
|
||||
public void setCreateTimeString(String createTimeString) { this.createTimeString = createTimeString; }
|
||||
|
||||
public Long getCreateTime() {
|
||||
return createTime;
|
||||
}
|
||||
|
||||
public void setCreateTime(Long createTime) {
|
||||
this.createTime = createTime;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void populateFromParamsMap(Map<String, String[]> params) {
|
||||
if(params.containsKey(Params.MEETING_ID.getValue())) {
|
||||
setMeetingID(params.get(Params.MEETING_ID.getValue())[0]);
|
||||
}
|
||||
|
||||
if(params.containsKey(Params.USER_ID.getValue())) setUserID(params.get(Params.USER_ID.getValue())[0]);
|
||||
if(params.containsKey(Params.FULL_NAME.getValue())) setFullName(params.get(Params.FULL_NAME.getValue())[0]);
|
||||
if(params.containsKey(Params.PASSWORD.getValue())) setPassword(params.get(Params.PASSWORD.getValue())[0]);
|
||||
if(params.containsKey(Params.GUEST.getValue())) setGuestString(params.get(Params.GUEST.getValue())[0]);
|
||||
if(params.containsKey(Params.AUTH.getValue())) setAuthString(params.get(Params.AUTH.getValue())[0]);
|
||||
if(params.containsKey(Params.CREATE_TIME.getValue())) setCreateTimeString(params.get(Params.CREATE_TIME.getValue())[0]);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void convertParamsFromString() {
|
||||
guest = Boolean.parseBoolean(guestString);
|
||||
auth = Boolean.parseBoolean(authString);
|
||||
|
||||
if(createTimeString != null) {
|
||||
createTime = Long.parseLong(createTimeString);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
package org.bigbluebutton.api.model.request;
|
||||
|
||||
import org.bigbluebutton.api.model.constraint.MeetingExistsConstraint;
|
||||
import org.bigbluebutton.api.model.constraint.MeetingIDConstraint;
|
||||
import org.bigbluebutton.api.model.shared.Checksum;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class MeetingInfo extends RequestWithChecksum<MeetingInfo.Params> {
|
||||
|
||||
public enum Params implements RequestParameters {
|
||||
MEETING_ID("meetingID");
|
||||
|
||||
private final String value;
|
||||
|
||||
Params(String value) { this.value = value; }
|
||||
|
||||
public String getValue() { return value; }
|
||||
}
|
||||
|
||||
@MeetingIDConstraint
|
||||
@MeetingExistsConstraint
|
||||
private String meetingID;
|
||||
|
||||
public MeetingInfo(Checksum checksum) {
|
||||
super(checksum);
|
||||
}
|
||||
|
||||
public String getMeetingID() {
|
||||
return meetingID;
|
||||
}
|
||||
|
||||
public void setMeetingID(String meetingID) {
|
||||
this.meetingID = meetingID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void populateFromParamsMap(Map<String, String[]> params) {
|
||||
if(params.containsKey(Params.MEETING_ID.getValue())) {
|
||||
setMeetingID(params.get(Params.MEETING_ID.getValue())[0]);
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
package org.bigbluebutton.api.model.request;
|
||||
|
||||
import org.bigbluebutton.api.model.constraint.MeetingIDConstraint;
|
||||
import org.bigbluebutton.api.model.shared.Checksum;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class MeetingRunning extends RequestWithChecksum<MeetingRunning.Params> {
|
||||
|
||||
public enum Params implements RequestParameters {
|
||||
MEETING_ID("meetingID");
|
||||
|
||||
private final String value;
|
||||
|
||||
Params(String value) { this.value = value; }
|
||||
|
||||
public String getValue() { return value; }
|
||||
}
|
||||
|
||||
@MeetingIDConstraint
|
||||
private String meetingID;
|
||||
|
||||
public MeetingRunning(Checksum checksum) {
|
||||
super(checksum);
|
||||
}
|
||||
|
||||
public String getMeetingID() {
|
||||
return meetingID;
|
||||
}
|
||||
|
||||
public void setMeetingID(String meetingID) {
|
||||
this.meetingID = meetingID;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void populateFromParamsMap(Map<String, String[]> params) {
|
||||
if(params.containsKey(Params.MEETING_ID.getValue())) setMeetingID(params.get(Params.MEETING_ID.getValue())[0]);
|
||||
}
|
||||
}
|
@ -0,0 +1,9 @@
|
||||
package org.bigbluebutton.api.model.request;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public interface Request<P extends Enum<P> & RequestParameters> {
|
||||
|
||||
void populateFromParamsMap(Map<String, String[]> params);
|
||||
void convertParamsFromString();
|
||||
}
|
@ -0,0 +1,6 @@
|
||||
package org.bigbluebutton.api.model.request;
|
||||
|
||||
public interface RequestParameters {
|
||||
|
||||
String getValue();
|
||||
}
|
@ -0,0 +1,30 @@
|
||||
package org.bigbluebutton.api.model.request;
|
||||
|
||||
import org.bigbluebutton.api.model.shared.Checksum;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import java.util.Map;
|
||||
|
||||
public abstract class RequestWithChecksum<P extends Enum<P> & RequestParameters> implements Request<P> {
|
||||
|
||||
@Valid
|
||||
protected Checksum checksum;
|
||||
|
||||
protected RequestWithChecksum(Checksum checksum) {
|
||||
this.checksum = checksum;
|
||||
}
|
||||
|
||||
public Checksum getChecksum() {
|
||||
return checksum;
|
||||
}
|
||||
|
||||
public void setChecksum(Checksum checksum) {
|
||||
this.checksum = checksum;
|
||||
}
|
||||
|
||||
public abstract void populateFromParamsMap(Map<String, String[]> params);
|
||||
|
||||
public void convertParamsFromString() {
|
||||
|
||||
}
|
||||
}
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user