first commit

This commit is contained in:
zhongjin 2023-01-16 14:25:39 +08:00
commit 8ca0e4f293
3340 changed files with 427357 additions and 0 deletions

17
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@ -0,0 +1,17 @@
blank_issues_enabled: false
contact_links:
- name: BigBlueButton Setup
url: https://groups.google.com/forum/#!forum/bigbluebutton-setup
about: Get help with installation, setup and configuration of a BigBlueButton server.
- name: BigBlueButton Users
url: https://groups.google.com/forum/#!forum/bigbluebutton-users
about: Get help and give feedback about using BigBlueButton.
- name: BigBlueButton Developers
url: https://groups.google.com/forum/#!forum/bigbluebutton-dev
about: Ask questions and share experiences with other BigBlueButton developers.
- name: Greenlight Frontend
url: https://github.com/bigbluebutton/greenlight/issues/
about: Issue tracker for the Greenlight frontend
- name: Commercial Support
url: https://bigbluebutton.org/commercial-support
about: List of companies offering commercial BigBlueButton support

44
.github/ISSUE_TEMPLATE/core-issue.md vendored Normal file
View File

@ -0,0 +1,44 @@
---
name: Server Core Issue
about: Template for creating an issue with bbb-web, akka-apps, or other server component
title: ''
labels: 'module: core'
assignees: ''
---
<!--PLEASE DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS.
This issue tracker is only for bbb development related issues.-->
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Actual behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. Windows, Mac]
- Browser [e.g. Chrome, Safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, Safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

View File

@ -0,0 +1,24 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: 'type: enhancement'
assignees: ''
---
<!--PLEASE DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS.
This issue tracker is only for bbb development related issues.
Search for existing feature requests to avoid creating duplicates.-->
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

50
.github/ISSUE_TEMPLATE/html5-issue.md vendored Normal file
View File

@ -0,0 +1,50 @@
---
name: HTML5 Issue
about: Template for creating HTML5 Issue (frontend which you see during a session, not Greenlight).
title: ''
labels: 'module: client'
assignees: ''
---
<!--PLEASE DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS.
This issue tracker is only for bbb development related issues.-->
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Actual behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**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
BigBlueButton Server 2.2.2 (1816)
**Desktop (please complete the following information):**
- OS: [e.g. Windows, Mac]
- Browser [e.g. Chrome, Safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, Safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

View File

@ -0,0 +1,28 @@
---
name: Installation issue (not a support question)
about: Template for issues encountered during installation
title: ''
labels: 'deploy: installation'
assignees: ''
---
<!--PLEASE DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS.
This issue tracker is only for bbb development related issues.
For support of BBB installation problems ask in the forum:
https://groups.google.com/forum/#!forum/bigbluebutton-setup -->
**Describe the bug**
A clear and concise description of what the bug is.
**Installation type**
Did you install manually or did you use the bbb-install script?
**Firewall and IP address type**
Is your server behind a NAT or firewall? Does your BBB server have its own IPv4 addres?
**Console output**
Please include the full console output from the install.
**Additional context**
Add any other context about the problem here.

View File

@ -0,0 +1,44 @@
---
name: Recording Issue
about: Template for creating a recording issue
title: ''
labels: 'module: recording'
assignees: ''
---
<!--PLEASE DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS.
This issue tracker is only for bbb development related issues.-->
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Actual behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Desktop (please complete the following information):**
- OS: [e.g. Windows, Mac]
- Browser [e.g. Chrome, Safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, Safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

26
.github/ISSUE_TEMPLATE/test-scenario.md vendored Normal file
View File

@ -0,0 +1,26 @@
---
name: New Test Case
about: Specify a [HTML5 client] test scenario to be added to the automated test coverage suite
title: ''
labels: 'type: test'
assignees: ''
---
<!--PLEASE DO NOT FILE ISSUES FOR GENERAL SUPPORT QUESTIONS.
This issue tracker is only for bbb development related issues.-->
**Describe the test scenario, be specific**
A clear and concise description of what the test case is.
Steps to reproduce:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Edge cases**
Any specific edge cases to keep in mind?
**Additional context**
Add any other context about the problem here.

36
.github/PULL_REQUEST_TEMPLATE.md vendored Normal file
View File

@ -0,0 +1,36 @@
<!--
PLEASE READ THIS MESSAGE.
HOW TO WRITE A GOOD PULL REQUEST?
- Make it small.
- Do only one thing.
- Avoid re-formatting.
- Make sure the code builds and works.
- Write useful descriptions and titles.
- Address review comments in terms of additional commits.
- Do not amend/squash existing ones unless the PR is trivial.
- Read the contributing guide: https://docs.bigbluebutton.org/support/faq.html#bigbluebutton-development-process
- Sign and send the Contributor License Agreement: https://docs.bigbluebutton.org/support/faq.html#why-do-i-need-to-sign-a-contributor-license-agreement-to-contribute-source-code
-->
### What does this PR do?
<!-- A brief description of each change being made with this pull request. -->
### Closes Issue(s)
<!-- List here all the issues closed by this pull request. Use keyword `closes` before each issue number
Closes #123456
-->
Closes #
### Motivation
<!-- What inspired you to submit this pull request? -->
### More
<!-- Anything else we should know when reviewing? -->
- [ ] Added/updated documentation

19
.github/stale.yml vendored Normal file
View 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

159
.github/workflows/automated-tests.yml vendored Normal file
View File

@ -0,0 +1,159 @@
name: 'Automated tests'
on:
push:
branches:
- 'develop'
- 'v2.[5-9].x-release'
- 'v[3-9].*.x-release'
pull_request:
types: [opened, synchronize, reopened]
permissions:
contents: read
jobs:
build-install-and-test:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v3
- run: ./build/get_external_dependencies.sh
- run: ./build/setup.sh bbb-apps-akka
- run: ./build/setup.sh bbb-config
- run: ./build/setup.sh bbb-etherpad
- run: ./build/setup.sh bbb-export-annotations
- run: ./build/setup.sh bbb-freeswitch-core
- run: ./build/setup.sh bbb-freeswitch-sounds
- run: ./build/setup.sh bbb-fsesl-akka
- run: ./build/setup.sh bbb-html5-nodejs
- run: ./build/setup.sh bbb-html5
- run: ./build/setup.sh bbb-learning-dashboard
- run: ./build/setup.sh bbb-libreoffice-docker
- run: ./build/setup.sh bbb-mkclean
- run: ./build/setup.sh bbb-pads
- run: ./build/setup.sh bbb-playback
- run: ./build/setup.sh bbb-playback-notes
- run: ./build/setup.sh bbb-playback-podcast
- run: ./build/setup.sh bbb-playback-presentation
- run: ./build/setup.sh bbb-playback-screenshare
- run: ./build/setup.sh bbb-record-core
- run: ./build/setup.sh bbb-web
- run: ./build/setup.sh bbb-webrtc-sfu
- run: ./build/setup.sh bigbluebutton
- run: tar cvf artifacts.tar artifacts/
- name: Archive packages
uses: actions/upload-artifact@v3
with:
name: artifacts.tar
path: |
artifacts.tar
# - name: Fake package build
# run: |
# sudo sh -c '
# echo "Faking a package build (to speed up installation test)"
# cd /
# wget -q "http://ci.bbbvm.imdt.com.br/artifacts.tar"
# tar xf artifacts.tar
# '
- name: Generate CA
run: |
sudo sh -c '
mkdir /root/bbb-ci-ssl/
cd /root/bbb-ci-ssl/
openssl rand -base64 48 > /root/bbb-ci-ssl/bbb-dev-ca.pass ;
chmod 600 /root/bbb-ci-ssl/bbb-dev-ca.pass ;
openssl genrsa -des3 -out bbb-dev-ca.key -passout file:/root/bbb-ci-ssl/bbb-dev-ca.pass 2048 ;
openssl req -x509 -new -nodes -key bbb-dev-ca.key -sha256 -days 1460 -passin file:/root/bbb-ci-ssl/bbb-dev-ca.pass -out bbb-dev-ca.crt -subj "/C=CA/ST=BBB/L=BBB/O=BBB/OU=BBB/CN=BBB-DEV" ;
'
- name: Trust CA
run: |
sudo sh -c '
sudo mkdir /usr/local/share/ca-certificates/bbb-dev/
sudo cp /root/bbb-ci-ssl/bbb-dev-ca.crt /usr/local/share/ca-certificates/bbb-dev/
sudo chmod 644 /usr/local/share/ca-certificates/bbb-dev/bbb-dev-ca.crt
sudo update-ca-certificates
'
- name: Generate certificate
run: |
sudo sh -c '
cd /root/bbb-ci-ssl/
echo "127.0.0.1 bbb-ci.test" >> /etc/hosts
openssl genrsa -out bbb-ci.test.key 2048
rm bbb-ci.test.csr bbb-ci.test.crt bbb-ci.test.key
cat > bbb-ci.test.ext << EOF
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, nonRepudiation, keyEncipherment, dataEncipherment
subjectAltName = @alt_names
[alt_names]
DNS.1 = bbb-ci.test
EOF
openssl req -nodes -newkey rsa:2048 -keyout bbb-ci.test.key -out bbb-ci.test.csr -subj "/C=CA/ST=BBB/L=BBB/O=BBB/OU=BBB/CN=bbb-ci.test" -addext "subjectAltName = DNS:bbb-ci.test"
openssl x509 -req -in bbb-ci.test.csr -CA bbb-dev-ca.crt -CAkey bbb-dev-ca.key -CAcreateserial -out bbb-ci.test.crt -days 825 -sha256 -passin file:/root/bbb-ci-ssl/bbb-dev-ca.pass -extfile bbb-ci.test.ext
cd
mkdir -p /local/certs/
cp /root/bbb-ci-ssl/bbb-dev-ca.crt /local/certs/
cat /root/bbb-ci-ssl/bbb-ci.test.crt > /local/certs/fullchain.pem
cat /root/bbb-ci-ssl/bbb-dev-ca.crt >> /local/certs/fullchain.pem
cat /root/bbb-ci-ssl/bbb-ci.test.key > /local/certs/privkey.pem
'
- name: Setup local repository
run: |
sudo sh -c '
apt install -yq dpkg-dev
cd /root && wget -q http://ci.bbbvm.imdt.com.br/cache-3rd-part-packages.tar
cp -r /home/runner/work/bigbluebutton/bigbluebutton/artifacts/ /artifacts/
cd /artifacts && tar xf /root/cache-3rd-part-packages.tar
cd /artifacts && dpkg-scanpackages . /dev/null | gzip -9c > Packages.gz
echo "deb [trusted=yes] file:/artifacts/ ./" >> /etc/apt/sources.list
'
- name: Prepare for install
run: |
sudo sh -c '
apt --purge -y remove apache2-bin
'
- name: Install BBB
run: |
sudo sh -c '
cd /root/ && wget -q https://ubuntu.bigbluebutton.org/bbb-install-2.6.sh -O bbb-install.sh
cat bbb-install.sh | sed "s|> /etc/apt/sources.list.d/bigbluebutton.list||g" | bash -s -- -v focal-26-dev -s bbb-ci.test -j -d /certs/
bbb-conf --salt bbbci
echo "NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/bbb-dev/bbb-dev-ca.crt" >> /usr/share/meteor/bundle/bbb-html5-with-roles.conf
bbb-conf --restart
'
- name: Install test dependencies
working-directory: ./bigbluebutton-tests/playwright
run: |
sh -c '
npm install
npx playwright install-deps
npx playwright install
'
- name: Run tests
working-directory: ./bigbluebutton-tests/playwright
env:
NODE_EXTRA_CA_CERTS: /usr/local/share/ca-certificates/bbb-dev/bbb-dev-ca.crt
ACTIONS_RUNNER_DEBUG: true
BBB_URL: https://bbb-ci.test/bigbluebutton/api
BBB_SECRET: bbbci
run: npm run test-chromium-ci
- name: Run Firefox tests
working-directory: ./bigbluebutton-tests/playwright
if: ${{ contains(github.event.pull_request.labels.*.name, 'test Firefox')
|| contains(github.event.pull_request.labels.*.name, 'Test Firefox') }}
env:
NODE_EXTRA_CA_CERTS: /usr/local/share/ca-certificates/bbb-dev/bbb-dev-ca.crt
ACTIONS_RUNNER_DEBUG: true
BBB_URL: https://bbb-ci.test/bigbluebutton/api
BBB_SECRET: bbbci
# patch playwright's firefox so that it uses the system's root certificate authority
run: |
sh -c '
find $HOME/.cache/ms-playwright -name libnssckbi.so -exec rm {} \; -exec ln -s /usr/lib/x86_64-linux-gnu/pkcs11/p11-kit-trust.so {} \;
npm run test-firefox-ci
'
- if: always()
uses: actions/upload-artifact@v3
with:
name: tests-report
path: |
bigbluebutton-tests/playwright/playwright-report
bigbluebutton-tests/playwright/test-results

View File

@ -0,0 +1,26 @@
name: Merge conflict check
on:
push:
pull_request_target:
types:
- opened
- synchronize
permissions:
contents: read
jobs:
main:
permissions:
pull-requests: write # for eps1lon/actions-label-merge-conflict to label PRs
runs-on: ubuntu-latest
steps:
- name: Check for dirty pull requests
uses: eps1lon/actions-label-merge-conflict@releases/2.x
with:
dirtyLabel: "status: conflict"
repoToken: "${{ secrets.GITHUB_TOKEN }}"
commentOnDirty: |
This pull request has conflicts ☹
Please resolve those so we can review the pull request.
Thanks.

23
.gitignore vendored Normal file
View File

@ -0,0 +1,23 @@
.DS_Store
._.DS_Store*
.metadata/
record-and-playback/playback-client-js/testmeeting/
record-and-playback/playback-web/playback-web-0.1.war
record-and-playback/.project
**/target/*
**/.cache-main
**/.cache-tests
.vagrant/*
**/.settings/*
*/.gradle
bigbluebutton-web/.settings*
bigbluebutton-web/.classpath
bigbluebutton-web/.project
akka-bbb-apps/akka-bbb-apps*.log
bigbluebutton-web/target-eclipse*
record-and-playback/.loadpath
**/.idea/*
*.iml
*~
cache/*
artifacts/*

191
.gitlab-ci.yml Normal file
View File

@ -0,0 +1,191 @@
# 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:v2022-12-29-grails-524
# 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-webhooks
- bbb-webrtc-sfu
- freeswitch
- bbb-pads
- 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-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-build:
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-pads-build:
extends: .build_job
script:
- build/setup-inside-docker.sh bbb-pads
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

1
.nvmrc Normal file
View File

@ -0,0 +1 @@
node

165
LICENSE Normal file
View File

@ -0,0 +1,165 @@
GNU LESSER GENERAL PUBLIC LICENSE
Version 3, 29 June 2007
Copyright (C) 2007 Free Software Foundation, Inc. <https://fsf.org/>
Everyone is permitted to copy and distribute verbatim copies
of this license document, but changing it is not allowed.
This version of the GNU Lesser General Public License incorporates
the terms and conditions of version 3 of the GNU General Public
License, supplemented by the additional permissions listed below.
0. Additional Definitions.
As used herein, "this License" refers to version 3 of the GNU Lesser
General Public License, and the "GNU GPL" refers to version 3 of the GNU
General Public License.
"The Library" refers to a covered work governed by this License,
other than an Application or a Combined Work as defined below.
An "Application" is any work that makes use of an interface provided
by the Library, but which is not otherwise based on the Library.
Defining a subclass of a class defined by the Library is deemed a mode
of using an interface provided by the Library.
A "Combined Work" is a work produced by combining or linking an
Application with the Library. The particular version of the Library
with which the Combined Work was made is also called the "Linked
Version".
The "Minimal Corresponding Source" for a Combined Work means the
Corresponding Source for the Combined Work, excluding any source code
for portions of the Combined Work that, considered in isolation, are
based on the Application, and not on the Linked Version.
The "Corresponding Application Code" for a Combined Work means the
object code and/or source code for the Application, including any data
and utility programs needed for reproducing the Combined Work from the
Application, but excluding the System Libraries of the Combined Work.
1. Exception to Section 3 of the GNU GPL.
You may convey a covered work under sections 3 and 4 of this License
without being bound by section 3 of the GNU GPL.
2. Conveying Modified Versions.
If you modify a copy of the Library, and, in your modifications, a
facility refers to a function or data to be supplied by an Application
that uses the facility (other than as an argument passed when the
facility is invoked), then you may convey a copy of the modified
version:
a) under this License, provided that you make a good faith effort to
ensure that, in the event an Application does not supply the
function or data, the facility still operates, and performs
whatever part of its purpose remains meaningful, or
b) under the GNU GPL, with none of the additional permissions of
this License applicable to that copy.
3. Object Code Incorporating Material from Library Header Files.
The object code form of an Application may incorporate material from
a header file that is part of the Library. You may convey such object
code under terms of your choice, provided that, if the incorporated
material is not limited to numerical parameters, data structure
layouts and accessors, or small macros, inline functions and templates
(ten or fewer lines in length), you do both of the following:
a) Give prominent notice with each copy of the object code that the
Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the object code with a copy of the GNU GPL and this license
document.
4. Combined Works.
You may convey a Combined Work under terms of your choice that,
taken together, effectively do not restrict modification of the
portions of the Library contained in the Combined Work and reverse
engineering for debugging such modifications, if you also do each of
the following:
a) Give prominent notice with each copy of the Combined Work that
the Library is used in it and that the Library and its use are
covered by this License.
b) Accompany the Combined Work with a copy of the GNU GPL and this license
document.
c) For a Combined Work that displays copyright notices during
execution, include the copyright notice for the Library among
these notices, as well as a reference directing the user to the
copies of the GNU GPL and this license document.
d) Do one of the following:
0) Convey the Minimal Corresponding Source under the terms of this
License, and the Corresponding Application Code in a form
suitable for, and under terms that permit, the user to
recombine or relink the Application with a modified version of
the Linked Version to produce a modified Combined Work, in the
manner specified by section 6 of the GNU GPL for conveying
Corresponding Source.
1) Use a suitable shared library mechanism for linking with the
Library. A suitable mechanism is one that (a) uses at run time
a copy of the Library already present on the user's computer
system, and (b) will operate properly with a modified version
of the Library that is interface-compatible with the Linked
Version.
e) Provide Installation Information, but only if you would otherwise
be required to provide such information under section 6 of the
GNU GPL, and only to the extent that such information is
necessary to install and execute a modified version of the
Combined Work produced by recombining or relinking the
Application with a modified version of the Linked Version. (If
you use option 4d0, the Installation Information must accompany
the Minimal Corresponding Source and Corresponding Application
Code. If you use option 4d1, you must provide the Installation
Information in the manner specified by section 6 of the GNU GPL
for conveying Corresponding Source.)
5. Combined Libraries.
You may place library facilities that are a work based on the
Library side by side in a single library together with other library
facilities that are not Applications and are not covered by this
License, and convey such a combined library under terms of your
choice, if you do both of the following:
a) Accompany the combined library with a copy of the same work based
on the Library, uncombined with any other library facilities,
conveyed under the terms of this License.
b) Give prominent notice with the combined library that part of it
is a work based on the Library, and explaining where to find the
accompanying uncombined form of the same work.
6. Revised Versions of the GNU Lesser General Public License.
The Free Software Foundation may publish revised and/or new versions
of the GNU Lesser General Public License from time to time. Such new
versions will be similar in spirit to the present version, but may
differ in detail to address new problems or concerns.
Each version is given a distinguishing version number. If the
Library as you received it specifies that a certain numbered version
of the GNU Lesser General Public License "or any later version"
applies to it, you have the option of following the terms and
conditions either of that published version or of any later version
published by the Free Software Foundation. If the Library as you
received it does not specify a version number of the GNU Lesser
General Public License, you may choose any version of the GNU Lesser
General Public License ever published by the Free Software Foundation.
If the Library as you received it specifies that a proxy can decide
whether future versions of the GNU Lesser General Public License shall
apply, that proxy's public statement of acceptance of any version is
permanent authorization for you to choose that version for the
Library.

20
README.md Normal file
View File

@ -0,0 +1,20 @@
BigBlueButton
=============
BigBlueButton is an open source web conferencing system.
BigBlueButton supports real-time sharing of audio, video, slides (with whiteboard controls), chat, and the screen. Instructors can engage remote students with polling, emojis, multi-user whiteboard, and breakout rooms.
Presenters can record and playback content for later sharing with others.
We designed BigBlueButton for online learning, (though it can be used for many [other applications](https://www.c4isrnet.com/it-networks/2015/02/11/disa-to-replace-dco-with-new-collaboration-services-tool/) as well). The educational use cases for BigBlueButton are
* Online tutoring (one-to-one)
* Flipped classrooms (recording content ahead of your session)
* Group collaboration (many-to-many)
* Online classes (one-to-many)
You can install on a Ubuntu 20.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/).
BigBlueButton and the BigBlueButton Logo are trademarks of [BigBlueButton Inc](https://bigbluebutton.org) .

22
SECURITY.md Normal file
View File

@ -0,0 +1,22 @@
# Security Policy
## Supported Versions
We actively support BigBlueButton through the community forums and through security updates.
| Version | Supported |
| ------- | ------------------ |
| 2.3.x (or earlier) | :x: |
| 2.4.x   | :white_check_mark: |
| 2.5.x   | :white_check_mark: |
| 2.6.x   | :x: |
We have released 2.5 to the community and are going to support both 2.4 and 2.5 together for the coming months (while we're actively developing the next release). Also, BigBlueButton 2.3 is now end of life.
As such, we recommend that all administrators deploy 2.5 going forward. You'll find [many improvements](https://docs.bigbluebutton.org/2.5/new.html) in this newer version.
## Reporting a Vulnerability
If you believe you have found a security vunerability in BigBlueButton please let us know directly by e-mailing security@bigbluebutton.org with as much detail as possible.
Regards,... [BigBlueButton Team](https://docs.bigbluebutton.org/support/faq.html#bigbluebutton-committer)

52
akka-bbb-apps/.gitignore vendored Normal file
View File

@ -0,0 +1,52 @@
.metadata
.project
.classpath
.settings
.history
.worksheet
gen
**/*.swp
**/*~.nib
**/build/
**/*.pbxuser
**/*.perspective
**/*.perspectivev3
*.xcworkspace
*.xcuserdatad
*.iml
project/*.ipr
project/*.iml
project/*.iws
project/out
project/*/target
project/target
project/*/bin
project/*/build
project/*.iml
project/*/*.iml
project/.idea
project/.idea/*
.idea/
.DS_Store
project/.DS_Store
project/*/.DS_Store
tm.out
tmlog*.log
*.tm*.epoch
out/
provisioning/.vagrant
provisioning/*/.vagrant
provisioning/*/*.known
/sbt/akka-patterns-store/
/daemon/src/build/
*.lock
logs/
tmp/
build/
akka-patterns-store/
lib_managed/
.cache
bin/
src/main/resources/
.bsp/

View File

@ -0,0 +1,31 @@
#alignArguments=false
alignParameters=true
alignSingleLineCaseStatements=true
#alignSingleLineCaseStatements.maxArrowIndent=40
#allowParamGroupsOnNewlines=false
#compactControlReadability=false
#compactStringConcatenation=false
danglingCloseParenthesis=Force
#doubleIndentClassDeclaration=false
doubleIndentConstructorArguments=true
doubleIndentMethodDeclaration=true
firstArgumentOnNewline=Force
firstParameterOnNewline=Force
#formatXml=true
#indentLocalDefs=false
#indentPackageBlocks=true
#indentSpaces=2
#indentWithTabs=false
#multilineScaladocCommentsStartOnFirstLine=false
#newlineAtEndOfFile=false
#placeScaladocAsterisksBeneathSecondAsterisk=false
#preserveSpaceBeforeArguments=false
#rewriteArrowSymbols=false
singleCasePatternOnNewline=false
#spaceBeforeColon=false
#spaceBeforeContextColon=false
#spaceInsideBrackets=false
#spaceInsideParentheses=false
#spacesAroundMultiImports=true
#spacesWithinPatternBinders=true

79
akka-bbb-apps/build.sbt Executable file
View File

@ -0,0 +1,79 @@
import org.bigbluebutton.build._
import NativePackagerHelper._
import com.typesafe.sbt.SbtNativePackager.autoImport._
enablePlugins(JavaServerAppPackaging)
enablePlugins(UniversalPlugin)
enablePlugins(DebianPlugin)
version := "0.0.4"
val compileSettings = Seq(
organization := "org.bigbluebutton",
scalacOptions ++= List(
"-unchecked",
"-deprecation",
"-Xlint",
"-Ywarn-dead-code",
"-language:_",
"-target:11",
"-encoding", "UTF-8"
),
javacOptions ++= List(
"-Xlint:unchecked",
"-Xlint:deprecation"
)
)
publishTo := Some(Resolver.file("file", new File(Path.userHome.absolutePath + "/dev/repo/maven-repo/releases")))
// We want to have our jar files in lib_managed dir.
// This way we'll have the right path when we import
// into eclipse.
retrieveManaged := true
libraryDependencies += "org.scalatest" %% "scalatest" % "3.0.8" % "test"
libraryDependencies += "org.scala-lang.modules" %% "scala-xml" % "2.0.0"
testOptions in Test += Tests.Argument(TestFrameworks.Specs2, "html", "console", "junitxml")
testOptions in Test += Tests.Argument(TestFrameworks.ScalaTest, "-h", "target/scalatest-reports")
Seq(Revolver.settings: _*)
lazy val bbbAppsAkka = (project in file(".")).settings(name := "bbb-apps-akka", libraryDependencies ++= Dependencies.runtime).settings(compileSettings)
// See https://github.com/scala-ide/scalariform
// Config file is in ./.scalariform.conf
scalariformAutoformat := true
scalaVersion := "2.13.9"
//-----------
// Packaging
//
// Reference:
// https://github.com/muuki88/sbt-native-packager-examples/tree/master/akka-server-app
// http://www.scala-sbt.org/sbt-native-packager/index.html
//-----------
mainClass := Some("org.bigbluebutton.Boot")
maintainer in Linux := "Richard Alam <ritzalam@gmail.com>"
packageSummary in Linux := "BigBlueButton Apps (Akka)"
packageDescription := """BigBlueButton Core Apps in Akka."""
val user = "bigbluebutton"
val group = "bigbluebutton"
// user which will execute the application
daemonUser in Linux := user
// group which will execute the application
daemonGroup in Linux := group
javaOptions in Universal ++= Seq("-J-Xms130m", "-J-Xmx256m", "-Dconfig.file=/etc/bigbluebutton/bbb-apps-akka.conf", "-Dlogback.configurationFile=conf/logback.xml")
debianPackageDependencies in Debian ++= Seq("java11-runtime-headless", "bash")

13
akka-bbb-apps/deploy.sh Executable file
View File

@ -0,0 +1,13 @@
#!/usr/bin/env bash
cd "$(dirname "$0")"
sudo service bbb-apps-akka stop
sbt debian:packageBin
sudo dpkg -i target/bbb-apps-akka_*.deb
echo ''
echo ''
echo '----------------'
echo 'bbb-apps-akka updated'
sudo service bbb-apps-akka start
echo 'starting service bbb-apps-akka'

View File

@ -0,0 +1,91 @@
package org.bigbluebutton.build
import sbt._
import Keys._
object Dependencies {
object Versions {
// Scala
val scala = "2.13.9"
val junit = "4.12"
val junitInterface = "0.11"
val scalactic = "3.0.8"
// Libraries
val akkaVersion = "2.6.17"
val akkaHttpVersion = "10.2.7"
val gson = "2.8.9"
val jackson = "2.13.0"
val logback = "1.2.10"
val quicklens = "1.7.5"
val spray = "1.3.6"
// Apache Commons
val lang = "3.12.0"
val codec = "1.15"
// BigBlueButton
val bbbCommons = "0.0.22-SNAPSHOT"
// Test
val scalaTest = "3.2.11"
val mockito = "2.23.0"
val akkaTestKit = "2.6.0"
}
object Compile {
val scalaLibrary = "org.scala-lang" % "scala-library" % Versions.scala
val scalaCompiler = "org.scala-lang" % "scala-compiler" % Versions.scala
val akkaActor = "com.typesafe.akka" % "akka-actor_2.13" % Versions.akkaVersion
val akkaSl4fj = "com.typesafe.akka" % "akka-slf4j_2.13" % Versions.akkaVersion
val googleGson = "com.google.code.gson" % "gson" % Versions.gson
val jacksonModule = "com.fasterxml.jackson.module" %% "jackson-module-scala" % Versions.jackson
val quicklens = "com.softwaremill.quicklens" %% "quicklens" % Versions.quicklens
val logback = "ch.qos.logback" % "logback-classic" % Versions.logback
val commonsCodec = "commons-codec" % "commons-codec" % Versions.codec
val sprayJson = "io.spray" % "spray-json_2.13" % Versions.spray
val akkaStream = "com.typesafe.akka" %% "akka-stream" % Versions.akkaVersion
val akkaHttp = "com.typesafe.akka" %% "akka-http" % Versions.akkaHttpVersion
val akkaHttpSprayJson = "com.typesafe.akka" %% "akka-http-spray-json" % Versions.akkaHttpVersion
val apacheLang = "org.apache.commons" % "commons-lang3" % Versions.lang
val bbbCommons = "org.bigbluebutton" % "bbb-common-message_2.13" % Versions.bbbCommons
}
object Test {
val scalaTest = "org.scalatest" %% "scalatest" % Versions.scalaTest % "test"
// val junit = "junit" % "junit" % Versions.junit % "test"
val mockitoCore = "org.mockito" % "mockito-core" % Versions.mockito % "test"
val scalactic = "org.scalactic" % "scalactic_2.13" % Versions.scalactic % "test"
val akkaTestKit = "com.typesafe.akka" %% "akka-testkit" % Versions.akkaTestKit % "test"
}
val testing = Seq(
Test.scalaTest,
// Test.junit,
Test.mockitoCore,
Test.scalactic,
Test.akkaTestKit)
val runtime = Seq(
Compile.scalaLibrary,
Compile.scalaCompiler,
Compile.akkaActor,
Compile.akkaSl4fj,
Compile.akkaStream,
Compile.googleGson,
Compile.jacksonModule,
Compile.quicklens,
Compile.logback,
Compile.commonsCodec,
Compile.sprayJson,
Compile.apacheLang,
Compile.akkaHttp,
Compile.akkaHttpSprayJson,
Compile.bbbCommons) ++ testing
}

View File

@ -0,0 +1 @@
sbt.version=1.6.2

View File

@ -0,0 +1 @@

View File

@ -0,0 +1,11 @@
addSbtPlugin("io.spray" % "sbt-revolver" % "0.9.1")
addSbtPlugin("com.typesafe.sbteclipse" % "sbteclipse-plugin" % "5.2.4")
addSbtPlugin("org.scalariform" % "sbt-scalariform" % "1.8.2")
addSbtPlugin("com.typesafe.sbt" % "sbt-native-packager" % "1.3.15")
addSbtPlugin("net.vonbuchholtz" % "sbt-dependency-check" % "0.2.9")
addSbtPlugin("org.scalastyle" %% "scalastyle-sbt-plugin" % "1.0.0")

14
akka-bbb-apps/run-dev.sh Executable file
View File

@ -0,0 +1,14 @@
#!/usr/bin/env bash
sudo service bbb-apps-akka stop
rm -rf src/main/resources
cp -R src/universal/conf src/main/resources
#Set correct sharedSecret and bbbWebAPI
sudo sed -i "s/sharedSecret = \"changeme\"/sharedSecret = \"$(sudo bbb-conf --salt | grep Secret: | cut -d ' ' -f 6)\"/g" src/main/resources/application.conf
sudo sed -i "s/bbbWebAPI = \"https:\/\/192.168.23.33\/bigbluebutton\/api\"/bbbWebAPI = \"https:\/\/$(hostname -f)\/bigbluebutton\/api\"/g" src/main/resources/application.conf
#sbt update - Resolves and retrieves external dependencies, more details in https://www.scala-sbt.org/1.x/docs/Command-Line-Reference.html
#sbt ~reStart (instead of run) - run with "triggered restart" mode, more details in #https://github.com/spray/sbt-revolver
exec sbt update ~reStart

6
akka-bbb-apps/run.sh Executable file
View File

@ -0,0 +1,6 @@
#!/usr/bin/env bash
sbt clean stage
sudo service bbb-apps-akka stop
cd target/universal/stage
exec ./bin/bbb-apps-akka

View File

@ -0,0 +1,39 @@
import com.google.gson.Gson
import org.bigbluebutton.messages.BreakoutRoomJoinURL
object BreakoutRoom {
val gson = new Gson //> gson : com.google.gson.Gson = {serializeNulls:falsefactories:[Factory[typeH
//| ierarchy=com.google.gson.JsonElement,adapter=com.google.gson.internal.bind.T
//| ypeAdapters$29@6979e8cb], com.google.gson.internal.bind.ObjectTypeAdapter$1@
//| 763d9750, com.google.gson.internal.Excluder@5c0369c4, Factory[type=java.lang
//| .String,adapter=com.google.gson.internal.bind.TypeAdapters$16@2be94b0f], Fac
//| tory[type=java.lang.Integer+int,adapter=com.google.gson.internal.bind.TypeAd
//| apters$7@d70c109], Factory[type=java.lang.Boolean+boolean,adapter=com.google
//| .gson.internal.bind.TypeAdapters$3@17ed40e0], Factory[type=java.lang.Byte+by
//| te,adapter=com.google.gson.internal.bind.TypeAdapters$5@50675690], Factory[t
//| ype=java.lang.Short+short,adapter=com.google.gson.internal.bind.TypeAdapters
//| $6@31b7dea0], Factory[type=java.lang.Long+long,adapter=com.google.gson.inter
//| nal.bind.TypeAdapters$11@3ac42916], Factory[type=java.lang.Double+double,ada
//| pter=com.google.gson.Gso
//| Output exceeds cutoff limit.
val string = "{\"payload\":{\"redirectJoinUrl\":\"alink\",\"breakoutMeetingId\":\"4455e780b6f62cd5fcf09367aef62d9bc5108375-1479728671031\",\"redirectToHtml5JoinUrl\":\"http://bbb.riadvice.com/bigbluebutton/api/join?fullName\u003dOpera\u0026isBreakout\u003dtrue\u0026meetingID\u003d4455e780b6f62cd5fcf09367aef62d9bc5108375-1479728671031\u0026password\u003dmp\u0026redirect\u003dfalse\u0026userID\u003d6pt0vfeaxdze_1-1\u0026checksum\u003d51c4a1398b88170c25f1a71521bca604e784ab23\",\"parentMeetingId\":\"183f0bf3a0982a127bdb8161e0c44eb696b3e75c-1479728593178\",\"userId\":\"6pt0vfeaxdze_1\"},\"header\":{\"name\":\"BreakoutRoomJoinURL\",\"version\":\"0.0.1\",\"current_time\":1479728673586,\"timestamp\":8549632}}"
//> string : String = {"payload":{"redirectJoinUrl":"alink","breakoutMeetingId"
//| :"4455e780b6f62cd5fcf09367aef62d9bc5108375-1479728671031","noRedirectJoinUrl
//| ":"http://bbb.riadvice.com/bigbluebutton/api/join?fullName=Opera&isBreakout=
//| true&meetingID=4455e780b6f62cd5fcf09367aef62d9bc5108375-1479728671031&passwo
//| rd=mp&redirect=false&userID=6pt0vfeaxdze_1-1&checksum=51c4a1398b88170c25f1a7
//| 1521bca604e784ab23","parentMeetingId":"183f0bf3a0982a127bdb8161e0c44eb696b3e
//| 75c-1479728593178","userId":"6pt0vfeaxdze_1"},"header":{"name":"BreakoutRoom
//| JoinURL","version":"0.0.1","current_time":1479728673586,"timestamp":8549632}
//| }
val brjum: BreakoutRoomJoinURL = gson.fromJson(string, classOf[BreakoutRoomJoinURL])
//> brjum : org.bigbluebutton.messages.BreakoutRoomJoinURL = org.bigbluebutton.
//| messages.BreakoutRoomJoinURL@3327bd23
println(brjum.payload.userId) //> 6pt0vfeaxdze_1
println(brjum.payload.parentMeetingId) //> 183f0bf3a0982a127bdb8161e0c44eb696b3e75c-1479728593178
println(brjum.payload.breakoutMeetingId) //> 4455e780b6f62cd5fcf09367aef62d9bc5108375-1479728671031
println(brjum.payload.redirectJoinURL) //> null
println(brjum.payload.redirectToHtml5JoinURL) //> null
}

View File

@ -0,0 +1,9 @@
import scala.collection.mutable
object Collections {
val messages = new mutable.Queue[String]()
messages += "foo"
messages += "bar"
messages += "baz"
messages.foreach(f => println(f))
}

122
akka-bbb-apps/scala/JsonTest.sc Executable file
View File

@ -0,0 +1,122 @@
import org.bigbluebutton.core.api._
import scala.util.{ Try, Success, Failure }
import org.bigbluebutton.core.JsonMessageDecoder
import org.bigbluebutton.messages.BreakoutRoomsList
import org.bigbluebutton.messages.payload.BreakoutRoomsListPayload
import java.util.ArrayList
import org.bigbluebutton.core.messaging.Util
import org.bigbluebutton.messages.payload.BreakoutRoomPayload
import org.bigbluebutton.core.pubsub.senders.MeetingMessageToJsonConverter
import spray.json._
import DefaultJsonProtocol._
import com.google.gson.JsonArray
import scala.collection.mutable.ListBuffer
object JsonTest {
import org.bigbluebutton.core.UserMessagesProtocol._
import spray.json._
println("Welcome to the Scala worksheet") //> Welcome to the Scala worksheet
val xroom1 = new BreakoutRoomInPayload("foo", Vector("a", "b", "c"))
//> xroom1 : org.bigbluebutton.core.api.BreakoutRoomInPayload = BreakoutRoomInP
//| ayload(foo,Vector(a, b, c))
val xroom2 = new BreakoutRoomInPayload("bar", Vector("x", "y", "z"))
//> xroom2 : org.bigbluebutton.core.api.BreakoutRoomInPayload = BreakoutRoomInP
//| ayload(bar,Vector(x, y, z))
val xroom3 = new BreakoutRoomInPayload("baz", Vector("q", "r", "s"))
//> xroom3 : org.bigbluebutton.core.api.BreakoutRoomInPayload = BreakoutRoomInP
//| ayload(baz,Vector(q, r, s))
val xmsg = new CreateBreakoutRooms("test-meeting", 10, false, Vector(xroom1, xroom2, xroom3))
//> xmsg : org.bigbluebutton.core.api.CreateBreakoutRooms = CreateBreakoutRoom
//| s(test-meeting,10,false,Vector(BreakoutRoomInPayload(foo,Vector(a, b, c)),
//| BreakoutRoomInPayload(bar,Vector(x, y, z)), BreakoutRoomInPayload(baz,Vecto
//| r(q, r, s))))
val xjsonAst = xmsg.toJson //> xjsonAst : spray.json.JsValue = {"meetingId":"test-meeting","durationInMin
//| utes":10,"record":false,"rooms":[{"name":"foo","users":["a","b","c"]},{"nam
//| e":"bar","users":["x","y","z"]},{"name":"baz","users":["q","r","s"]}]}
val xjson = xjsonAst.asJsObject //> xjson : spray.json.JsObject = {"meetingId":"test-meeting","durationInMinut
//| es":10,"record":false,"rooms":[{"name":"foo","users":["a","b","c"]},{"name"
//| :"bar","users":["x","y","z"]},{"name":"baz","users":["q","r","s"]}]}
val meetingId = for {
meetingId <- xjson.fields.get("meetingId")
} yield meetingId //> meetingId : Option[spray.json.JsValue] = Some("test-meeting")
val cbrm = """
{"header":{"name":"CreateBreakoutRoomsRequest"},"payload":{"meetingId":"abc123","rooms":[{"name":"room1","users":["Tidora","Nidora","Tinidora"]},{"name":"room2","users":["Jose","Wally","Paolo"]},{"name":"room3","users":["Alden","Yaya Dub"]}],"durationInMinutes":20}}
""" //> cbrm : String = "
//| {"header":{"name":"CreateBreakoutRoomsRequest"},"payload":{"meetingId":"a
//| bc123","rooms":[{"name":"room1","users":["Tidora","Nidora","Tinidora"]},{"n
//| ame":"room2","users":["Jose","Wally","Paolo"]},{"name":"room3","users":["Al
//| den","Yaya Dub"]}],"durationInMinutes":20}}
//| "
JsonMessageDecoder.decode(cbrm) //> res0: Option[org.bigbluebutton.core.api.InMessage] = None
val rbju = """
{"header":{"name":"RequestBreakoutJoinURL"},"payload":{"userId":"id6pa5t8m1c9_1","meetingId":"183f0bf3a0982a127bdb8161e0c44eb696b3e75c-1452692983357","breakoutId":"183f0bf3a0982a127bdb8161e0c44eb696b3e75c-1452692983357-2"}}
""" //> rbju : String = "
//| {"header":{"name":"RequestBreakoutJoinURL"},"payload":{"userId":"id6pa5t8
//| m1c9_1","meetingId":"183f0bf3a0982a127bdb8161e0c44eb696b3e75c-1452692983357
//| ","breakoutId":"183f0bf3a0982a127bdb8161e0c44eb696b3e75c-1452692983357-2"}}
//|
//| "
JsonMessageDecoder.decode(rbju) //> res1: Option[org.bigbluebutton.core.api.InMessage] = Some(RequestBreakoutJo
//| inURLInMessage(183f0bf3a0982a127bdb8161e0c44eb696b3e75c-1452692983357,183f0
//| bf3a0982a127bdb8161e0c44eb696b3e75c-1452692983357-2,id6pa5t8m1c9_1))
val brl = """
{"payload":{"meetingId":"183f0bf3a0982a127bdb8161e0c44eb696b3e75c-1452849036428","rooms":{"startIndex":0,"endIndex":0,"focus":0,"dirty":false,"depth":0}},"header":{"timestamp":33619724,"name":"BreakoutRoomsList","current_time":1452849043547,"version":"0.0.1"}}
""" //> brl : String = "
//| {"payload":{"meetingId":"183f0bf3a0982a127bdb8161e0c44eb696b3e75c-1452849
//| 036428","rooms":{"startIndex":0,"endIndex":0,"focus":0,"dirty":false,"depth
//| ":0}},"header":{"timestamp":33619724,"name":"BreakoutRoomsList","current_ti
//| me":1452849043547,"version":"0.0.1"}}
//| "
val brb = new BreakoutRoomBody("Breakout Room", "br-id-1");
//> brb : org.bigbluebutton.core.api.BreakoutRoomBody = BreakoutRoomBody(Break
//| out Room,br-id-1)
val jsObj = JsObject(
"name" -> JsString(brb.name),
"breakoutId" -> JsString(brb.breakoutId)) //> jsObj : spray.json.JsObject = {"name":"Breakout Room","breakoutId":"br-id-
//| 1"}
val vector = Vector(jsObj) //> vector : scala.collection.immutable.Vector[spray.json.JsObject] = Vector({
//| "name":"Breakout Room","breakoutId":"br-id-1"})
val brlum = new BreakoutRoomsListOutMessage("main-meeting-1", Vector(brb), false)
//> brlum : org.bigbluebutton.core.api.BreakoutRoomsListOutMessage = BreakoutR
//| oomsListOutMessage(main-meeting-1,Vector(BreakoutRoomBody(Breakout Room,br-
//| id-1)),false)
var roomsJsVector: ListBuffer[JsObject] = new ListBuffer[JsObject]()
//> roomsJsVector : scala.collection.mutable.ListBuffer[spray.json.JsObject] =
//| ListBuffer()
brlum.rooms.foreach { r =>
roomsJsVector.append(JsObject("name" -> JsString(r.name), "breakoutId" -> JsString(r.breakoutId)))
}
roomsJsVector.length //> res2: Int = 1
val jsonAst = List(1, 2, 3).toJson //> jsonAst : spray.json.JsValue = [1,2,3]
roomsJsVector.toList.toJson //> res3: spray.json.JsValue = [{"name":"Breakout Room","breakoutId":"br-id-1"}
//| ]
JsArray(roomsJsVector.toVector) //> res4: spray.json.JsArray = [{"name":"Breakout Room","breakoutId":"br-id-1"}
//| ]
MeetingMessageToJsonConverter.breakoutRoomsListOutMessageToJson(brlum);
//> res5: String = {"payload":{"meetingId":"main-meeting-1","roomsReady":false,
//| "rooms":[{"name":"Breakout Room","breakoutId":"br-id-1"}]},"header":{"times
//| tamp":9652644,"name":"BreakoutRoomsList","current_time":1471504366540,"vers
//| ion":"0.0.1"}}
// JsonMessageDecoder.unmarshall(cbrm) match {
// case Success(validMsg) => println(validMsg)
// case Failure(ex) => println("Unhandled message: [{}]")
// }
}

77
akka-bbb-apps/scala/Test2.sc Executable file
View File

@ -0,0 +1,77 @@
import scala.collection.immutable.StringOps
import java.net.URLEncoder
import scala.collection._
object Test2 {
println("Welcome to the Scala worksheet") //> Welcome to the Scala worksheet
val userId = new StringOps("abc_12") //> userId : scala.collection.immutable.StringOps = abc_12
val s2 = userId.split('_') //> s2 : Array[String] = Array(abc, 12)
val s1 = if (s2.length == 2) s2(0) else userId //> s1 : Comparable[String] = abc
def sortParam(params: mutable.Map[String, String]):SortedSet[String] = {
collection.immutable.SortedSet[String]() ++ params.keySet
} //> sortParam: (params: scala.collection.mutable.Map[String,String])scala.collec
//| tion.SortedSet[String]
def createBaseString(params: mutable.Map[String, String]): String = {
val csbuf = new StringBuffer()
var keys = sortParam(params)
var first = true;
for (key <- keys) {
for (value <- params.get(key)) {
if (first) {
first = false;
} else {
csbuf.append("&");
}
csbuf.append(key);
csbuf.append("=");
csbuf.append(value);
}
}
return csbuf.toString();
} //> createBaseString: (params: scala.collection.mutable.Map[String,String])Strin
//| g
def urlEncode(s: String): String = {
URLEncoder.encode(s, "UTF-8");
} //> urlEncode: (s: String)String
val baseString = "fullName=User+4621018&isBreakout=true&meetingID=random-1853792&password=mp&redirect=true"
//> baseString : String = fullName=User+4621018&isBreakout=true&meetingID=rand
//| om-1853792&password=mp&redirect=true
val params = new collection.mutable.HashMap[String, String]
//> params : scala.collection.mutable.HashMap[String,String] = Map()
params += "fullName" -> urlEncode("User 4621018")
//> res0: Test2.params.type = Map(fullName -> User+4621018)
params += "isBreakout" -> urlEncode("true") //> res1: Test2.params.type = Map(fullName -> User+4621018, isBreakout -> true)
//|
params += "meetingID" -> urlEncode("random-1853792")
//> res2: Test2.params.type = Map(fullName -> User+4621018, isBreakout -> true,
//| meetingID -> random-1853792)
params += "password" -> urlEncode("mp") //> res3: Test2.params.type = Map(fullName -> User+4621018, isBreakout -> true,
//| meetingID -> random-1853792, password -> mp)
params += "redirect" -> urlEncode("true") //> res4: Test2.params.type = Map(fullName -> User+4621018, isBreakout -> true,
//| meetingID -> random-1853792, redirect -> true, password -> mp)
val keys = sortParam(params) //> keys : scala.collection.SortedSet[String] = TreeSet(fullName, isBreakout,
//| meetingID, password, redirect)
val result = createBaseString(params) //> result : String = fullName=User+4621018&isBreakout=true&meetingID=random-1
//| 853792&password=mp&redirect=true
val between = Set("xab", "bc")
val u2 = Set("bc", "xab")
val u3 = u2 + "zxc"
val foo = between subsetOf(u2)
val id = between.toSeq.sorted.mkString("-")
}

View File

@ -0,0 +1,126 @@
import scala.collection.immutable.StringOps
import org.bigbluebutton.core.apps.Answer
import org.bigbluebutton.core.apps.Question
object TestWorksheet {
println("Welcome to the Scala worksheet") //> Welcome to the Scala worksheet
val YesNoPollType = "YN" //> YesNoPollType : String = YN
val TrueFalsePollType = "TF" //> TrueFalsePollType : String = TF
val LetterPollType = "A-" //> LetterPollType : String = A-
val NumberPollType = "1-" //> NumberPollType : String = 1-
val LetterArray = Array("A", "B", "C", "D", "E", "F")
//> LetterArray : Array[String] = Array(A, B, C, D, E, F)
val NumberArray = Array("1", "2", "3", "4", "5", "6")
//> NumberArray : Array[String] = Array(1, 2, 3, 4, 5, 6)
def processYesNoPollType(qType: String):Question = {
val answers = new Array[Answer](2)
answers(0) = new Answer(0, "N", Some("No"))
answers(1) = new Answer(1, "Y", Some("Yes"))
new Question(0, YesNoPollType, false, None, answers)
} //> processYesNoPollType: (qType: String)org.bigbluebutton.core.apps.Question
def processTrueFalsePollType(qType: String):Question = {
val answers = new Array[Answer](2)
answers(0) = new Answer(0, "F", Some("False"))
answers(1) = new Answer(1, "T", Some("True"))
new Question(0, TrueFalsePollType, false, None, answers)
} //> processTrueFalsePollType: (qType: String)org.bigbluebutton.core.apps.Questio
//| n
def processLetterPollType(qType: String, multiResponse: Boolean):Option[Question] = {
val q = qType.split('-')
val numQs = q(1).toInt
var questionOption: Option[Question] = None
if (numQs > 0 && numQs <= 7) {
val answers = new Array[Answer](numQs)
var i = 0
for ( i <- 0 until numQs ) {
answers(i) = new Answer(i, LetterArray(i), Some(LetterArray(i)))
val question = new Question(0, LetterPollType, multiResponse, None, answers)
questionOption = Some(question)
}
}
questionOption
} //> processLetterPollType: (qType: String, multiResponse: Boolean)Option[org.bi
//| gbluebutton.core.apps.Question]
def processNumberPollType(qType: String, multiResponse: Boolean):Option[Question] = {
val q = qType.split('-')
val numQs = q(1).toInt
var questionOption: Option[Question] = None
if (numQs > 0 && numQs <= 7) {
val answers = new Array[Answer](numQs)
var i = 0
for ( i <- 0 until numQs ) {
answers(i) = new Answer(i, NumberArray(i), Some(NumberArray(i)))
val question = new Question(0, NumberPollType, multiResponse, None, answers)
questionOption = Some(question)
}
}
questionOption
} //> processNumberPollType: (qType: String, multiResponse: Boolean)Option[org.bi
//| gbluebutton.core.apps.Question]
def createQuestion(qType: String):Option[Question] = {
val qt = qType.toUpperCase()
var questionOption: Option[Question] = None
if (qt.matches(YesNoPollType)) {
questionOption = Some(processYesNoPollType(qt))
} else if (qt.matches(TrueFalsePollType)) {
questionOption = Some(processTrueFalsePollType(qt))
} else if (qt.startsWith(LetterPollType)) {
questionOption = processLetterPollType(qt, false)
} else if (qt.startsWith(NumberPollType)) {
processNumberPollType(qt, false)
} else {
questionOption = None
}
questionOption
} //> createQuestion: (qType: String)Option[org.bigbluebutton.core.apps.Question]
//|
def determineQType3(qType: String) {
val qt = qType.toUpperCase()
if (qt.matches(YesNoPollType)) {
println("YN")
} else if (qt.matches("TF")) {
println(TrueFalsePollType)
} else if (qt.startsWith(LetterPollType)) {
println("A5")
processLetterPollType(qt, false)
} else if (qt.startsWith(NumberPollType)) {
println("1")
processNumberPollType(qt, false)
} else {
println("No Match for [" + qType + "]")
}
} //> determineQType3: (qType: String)Unit
determineQType3("YN") //> YN
determineQType3("YF") //> No Match for [YF]
determineQType3("TF") //> TF
determineQType3("A-5") //> A5
determineQType3("1-5") //> 1
val list = new java.util.ArrayList[String]() //> list : java.util.ArrayList[String] = []
list.add("Red") //> res0: Boolean = true
list.add("Green") //> res1: Boolean = true
list.add("Blue") //> res2: Boolean = true
}

View File

@ -0,0 +1,12 @@
#!/bin/sh
set -e
case "$1" in
purge)
# remove config file added/changed by preinst script
OP_CONFIG=/etc/bigbluebutton/bbb-apps-akka.conf
if [ -f "${OP_CONFIG}" ]; then
rm "${OP_CONFIG}"
fi
esac

View File

@ -0,0 +1,38 @@
#!/bin/sh
write_config_file() {
mkdir -p /etc/bigbluebutton
cat <<END > "${OP_CONFIG}"
// include default config from upstream
include "/usr/share/bbb-apps-akka/conf/application.conf"
// you can customize everything here. API endpoint and secret have to be changed
// This file will not be overridden by packages
services {
bbbWebAPI=${API}
sharedSecret=${SECRET}
}
END
}
case "$1" in
install|upgrade|1|2)
PACKAGE_CONFIG=/usr/share/bbb-apps-akka/conf/application.conf
OP_CONFIG=/etc/bigbluebutton/bbb-apps-akka.conf
# this is for upgrading packages from old packaging system where operators had
# to change the package config file
if [ ! -f "${OP_CONFIG}" -a -f "${PACKAGE_CONFIG}" ]; then
API=$(grep bbbWebAPI "${PACKAGE_CONFIG}" | sed 's/[^=]*=\s*//')
SECRET=$(grep sharedSecret "${PACKAGE_CONFIG}" | sed 's/[^=]*=\s*//')
write_config_file
fi
# this is for fresh installs, where no config file exists
if [ ! -f "${OP_CONFIG}" ]; then
SECRET='"changeme"'
API='"https://192.168.23.33/bigbluebutton/api"'
write_config_file
fi
esac

View File

@ -0,0 +1,128 @@
package org.bigbluebutton
import akka.http.scaladsl.model._
import akka.http.scaladsl.marshallers.sprayjson.SprayJsonSupport
import akka.http.scaladsl.server.Directives._
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,
pubsubSendStatus: PubSubSendStatus,
pubsubReceiveStatus: PubSubReceiveStatus,
recordingDbStatus: RecordingDBSendStatus
)
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)
}
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") {
get {
val future = healthz.getHealthz()
onSuccess(future) {
case response =>
if (response.isHealthy) {
complete(
StatusCodes.OK,
HealthResponse(
response.isHealthy,
response.pubSubSendStatus,
response.pubSubReceiveStatus,
response.recordingDBSendStatus
)
)
} else {
complete(
StatusCodes.ServiceUnavailable,
HealthResponse(
response.isHealthy,
response.pubSubSendStatus,
response.pubSubReceiveStatus,
response.recordingDBSendStatus
)
)
}
}
}
} ~
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)
}
}
}

View File

@ -0,0 +1,111 @@
package org.bigbluebutton
import akka.actor.ActorSystem
import akka.event.Logging
import akka.http.scaladsl.Http
import akka.stream.ActorMaterializer
import org.bigbluebutton.common2.redis.{ MessageSender, RedisConfig, RedisPublisher }
import org.bigbluebutton.core._
import org.bigbluebutton.core.bus._
import org.bigbluebutton.core.pubsub.senders.ReceivedJsonMsgHandlerActor
import org.bigbluebutton.core2.AnalyticsActor
import org.bigbluebutton.core2.FromAkkaAppsMsgSenderActor
import org.bigbluebutton.endpoint.redis.AppsRedisSubscriberActor
import org.bigbluebutton.endpoint.redis.{ RedisRecorderActor, ExportAnnotationsActor }
import org.bigbluebutton.endpoint.redis.LearningDashboardActor
import org.bigbluebutton.common2.bus.IncomingJsonMessageBus
import org.bigbluebutton.service.{ HealthzService, MeetingInfoActor, MeetingInfoService }
object Boot extends App with SystemConfiguration {
implicit val system = ActorSystem("bigbluebutton-apps-system")
implicit val materializer: ActorMaterializer = ActorMaterializer()
implicit val executor = system.dispatcher
val logger = Logging(system, getClass)
val eventBus = new InMsgBusGW(new IncomingEventBusImp())
val outBus2 = new OutEventBus2
val recordingEventBus = new RecordingEventBus
val outGW = new OutMessageGatewayImp(outBus2)
val redisPass = if (redisPassword != "") Some(redisPassword) else None
val redisConfig = RedisConfig(redisHost, redisPort, redisPass, redisExpireKey)
val redisPublisher = new RedisPublisher(
system,
"BbbAppsAkkaPub",
redisConfig
)
val msgSender = new MessageSender(redisPublisher)
val bbbMsgBus = new BbbMsgRouterEventBus
val healthzService = HealthzService(system)
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 exportAnnotationsActor = system.actorOf(
ExportAnnotationsActor.props(system, redisConfig, healthzService),
"exportAnnotationsActor"
)
val learningDashboardActor = system.actorOf(
LearningDashboardActor.props(system, outGW),
"LearningDashboardActor"
)
recordingEventBus.subscribe(redisRecorderActor, outMessageChannel)
val incomingJsonMessageBus = new IncomingJsonMessageBus
val fromAkkaAppsMsgSenderActorRef = system.actorOf(FromAkkaAppsMsgSenderActor.props(msgSender))
val analyticsActorRef = system.actorOf(AnalyticsActor.props(analyticsIncludeChat))
outBus2.subscribe(fromAkkaAppsMsgSenderActorRef, outBbbMsgMsgChannel)
outBus2.subscribe(redisRecorderActor, recordServiceMessageChannel)
outBus2.subscribe(exportAnnotationsActor, outBbbMsgMsgChannel)
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)
val redisMessageHandlerActor = system.actorOf(ReceivedJsonMsgHandlerActor.props(bbbMsgBus, incomingJsonMessageBus))
incomingJsonMessageBus.subscribe(redisMessageHandlerActor, toAkkaAppsJsonChannel)
val channelsToSubscribe = Seq(
toAkkaAppsRedisChannel, fromVoiceConfRedisChannel, fromSfuRedisChannel,
)
val redisSubscriberActor = system.actorOf(
AppsRedisSubscriberActor.props(
system,
incomingJsonMessageBus,
redisConfig,
channelsToSubscribe,
Nil,
toAkkaAppsJsonChannel
),
"redis-subscriber"
)
val bindingFuture = Http().bindAndHandle(apiService.routes, httpHost, httpPort)
}

View File

@ -0,0 +1,156 @@
package org.bigbluebutton
import org.bigbluebutton.common2.msgs.{ BbbCommonEnvCoreMsg, BbbCoreEnvelope, BbbCoreHeaderWithMeetingId, MessageTypes, MuteUserInVoiceConfSysMsg, MuteUserInVoiceConfSysMsgBody, Routing }
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
import org.bigbluebutton.core2.{ MeetingStatus2x }
import org.bigbluebutton.core.apps.webcam.CameraHdlrHelpers
import org.bigbluebutton.core.models.{
Roles,
Users2x,
UserState,
VoiceUserState,
VoiceUsers,
Webcams,
WebcamStream
}
object LockSettingsUtil {
private def muteUserInVoiceConf(liveMeeting: LiveMeeting, outGW: OutMsgRouter, vu: VoiceUserState, mute: Boolean): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, vu.intId)
val envelope = BbbCoreEnvelope(MuteUserInVoiceConfSysMsg.NAME, routing)
val header = BbbCoreHeaderWithMeetingId(MuteUserInVoiceConfSysMsg.NAME, liveMeeting.props.meetingProp.intId)
val body = MuteUserInVoiceConfSysMsgBody(liveMeeting.props.voiceProp.voiceConf, vu.voiceUserId, mute)
val event = MuteUserInVoiceConfSysMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
outGW.send(msgEvent)
}
private def applyMutingOfUsers(disableMic: Boolean, liveMeeting: LiveMeeting, outGW: OutMsgRouter): Unit = {
VoiceUsers.findAll(liveMeeting.voiceUsers) foreach { vu =>
Users2x.findWithIntId(liveMeeting.users2x, vu.intId).foreach { user =>
if (user.role == Roles.VIEWER_ROLE && !vu.listenOnly && user.locked) {
// Apply lock setting to users who are not listen only. (ralam dec 6, 2019)
muteUserInVoiceConf(liveMeeting, outGW, vu, disableMic)
}
// Make sure listen only users are muted. (ralam dec 6, 2019)
if (vu.listenOnly && !vu.muted) {
muteUserInVoiceConf(liveMeeting, outGW, vu, true)
}
}
}
}
def enforceLockSettingsForAllVoiceUsers(liveMeeting: LiveMeeting, outGW: OutMsgRouter): Unit = {
val permissions = MeetingStatus2x.getPermissions(liveMeeting.status)
applyMutingOfUsers(permissions.disableMic, liveMeeting, outGW)
}
def enforceLockSettingsForVoiceUser(voiceUser: VoiceUserState, liveMeeting: LiveMeeting, outGW: OutMsgRouter): Unit = {
val permissions = MeetingStatus2x.getPermissions(liveMeeting.status)
if (permissions.disableMic) {
Users2x.findWithIntId(liveMeeting.users2x, voiceUser.intId).foreach { user =>
if (user.role == Roles.VIEWER_ROLE && user.locked) {
// Make sure that user is muted when lock settings has mic disabled. (ralam dec 6, 2019
if (!voiceUser.muted) {
muteUserInVoiceConf(liveMeeting, outGW, voiceUser, true)
}
}
}
} else {
enforceListenOnlyUserIsMuted(voiceUser.intId, liveMeeting, outGW)
}
}
private def enforceListenOnlyUserIsMuted(intUserId: String, liveMeeting: LiveMeeting, outGW: OutMsgRouter): Unit = {
val voiceUser = VoiceUsers.findWithIntId(liveMeeting.voiceUsers, intUserId)
voiceUser.foreach { vu =>
// Make sure that listen only user is muted. (ralam dec 6, 2019
if (vu.listenOnly && !vu.muted) {
muteUserInVoiceConf(liveMeeting, outGW, vu, true)
}
}
}
def isMicrophoneSharingLocked(user: UserState, liveMeeting: LiveMeeting): Boolean = {
val permissions = MeetingStatus2x.getPermissions(liveMeeting.status)
user.role == Roles.VIEWER_ROLE && user.locked && permissions.disableMic
}
def isCameraBroadcastLocked(user: UserState, liveMeeting: LiveMeeting): Boolean = {
val permissions = MeetingStatus2x.getPermissions(liveMeeting.status)
user.role == Roles.VIEWER_ROLE && user.locked && permissions.disableCam
}
def isCameraSubscribeLocked(
user: UserState, stream: WebcamStream, liveMeeting: LiveMeeting
): Boolean = {
var locked = false
for {
publisher <- Users2x.findWithIntId(liveMeeting.users2x, stream.userId)
} yield {
if (MeetingStatus2x.webcamsOnlyForModeratorEnabled(liveMeeting.status)
&& publisher.role != Roles.MODERATOR_ROLE
&& user.role == Roles.VIEWER_ROLE
&& user.locked) {
locked = true
}
}
locked
}
private def enforceSeeOtherViewersForUser(
user: UserState, liveMeeting: LiveMeeting, outGW: OutMsgRouter
): Unit = {
if (MeetingStatus2x.webcamsOnlyForModeratorEnabled(liveMeeting.status)) {
Webcams.findAll(liveMeeting.webcams) foreach { webcam =>
if (isCameraSubscribeLocked(user, webcam, liveMeeting)
&& Webcams.isSubscriber(liveMeeting.webcams, user.intId, webcam.streamId)) {
CameraHdlrHelpers.requestCamSubscriptionEjection(
liveMeeting.props.meetingProp.intId,
user.intId,
webcam.streamId,
outGW
)
}
}
}
}
private def enforceDisableCamForUser(
user: UserState, liveMeeting: LiveMeeting, outGW: OutMsgRouter
): Unit = {
if (isCameraBroadcastLocked(user, liveMeeting)) {
val broadcastedWebcams = Webcams.findWebcamsForUser(liveMeeting.webcams, user.intId)
broadcastedWebcams foreach { webcam =>
CameraHdlrHelpers.requestBroadcastedCamEjection(
liveMeeting.props.meetingProp.intId,
user.intId,
webcam.streamId,
outGW
)
}
}
}
def enforceCamLockSettingsForUser(
user: UserState, liveMeeting: LiveMeeting, outGW: OutMsgRouter
): Unit = {
enforceDisableCamForUser(user, liveMeeting, outGW)
enforceSeeOtherViewersForUser(user, liveMeeting, outGW)
}
def enforceCamLockSettingsForAllUsers(liveMeeting: LiveMeeting, outGW: OutMsgRouter): Unit = {
Users2x.findLockedViewers(liveMeeting.users2x).foreach { user =>
enforceCamLockSettingsForUser(user, liveMeeting, outGW)
}
}
}

View File

@ -0,0 +1,83 @@
package org.bigbluebutton
import scala.util.Try
import com.typesafe.config.ConfigFactory
trait SystemConfiguration {
val config = ConfigFactory.load()
lazy val bbbWebHost = Try(config.getString("services.bbbWebHost")).getOrElse("localhost")
lazy val bbbWebPort = Try(config.getInt("services.bbbWebPort")).getOrElse(8888)
lazy val bbbWebAPI = Try(config.getString("services.bbbWebAPI")).getOrElse("localhost")
lazy val bbbWebSharedSecret = Try(config.getString("services.sharedSecret")).getOrElse("changeme")
lazy val bbbWebModeratorPassword = Try(config.getString("services.moderatorPassword")).getOrElse("changeme")
lazy val bbbWebViewerPassword = Try(config.getString("services.viewerPassword")).getOrElse("changeme")
lazy val keysExpiresInSec = Try(config.getInt("redis.keyExpiry")).getOrElse(14 * 86400) // 14 days
lazy val expireLastUserLeft = Try(config.getInt("expire.lastUserLeft")).getOrElse(60) // 1 minute
lazy val expireNeverJoined = Try(config.getInt("expire.neverJoined")).getOrElse(5 * 60) // 5 minutes
lazy val analyticsChannel = Try(config.getString("eventBus.analyticsChannel")).getOrElse("analytics-channel")
lazy val meetingManagerChannel = Try(config.getString("eventBus.meetingManagerChannel")).getOrElse("MeetingManagerChannel")
lazy val outMessageChannel = Try(config.getString("eventBus.outMessageChannel")).getOrElse("OutgoingMessageChannel")
lazy val incomingJsonMsgChannel = Try(config.getString("eventBus.incomingJsonMsgChannel")).getOrElse("IncomingJsonMsgChannel")
lazy val outBbbMsgMsgChannel = Try(config.getString("eventBus.outBbbMsgMsgChannel")).getOrElse("OutBbbMsgChannel")
lazy val recordServiceMessageChannel = Try(config.getString("eventBus.recordServiceMessageChannel")).getOrElse("RecordServiceMessageChannel")
lazy val toHTML5RedisChannel = Try(config.getString("redis.toHTML5RedisChannel")).getOrElse("to-html5-redis-channel")
lazy val fromAkkaAppsChannel = Try(config.getString("eventBus.fromAkkaAppsChannel")).getOrElse("from-akka-apps-channel")
lazy val toAkkaAppsChannel = Try(config.getString("eventBus.toAkkaAppsChannel")).getOrElse("to-akka-apps-channel")
lazy val fromClientChannel = Try(config.getString("eventBus.fromClientChannel")).getOrElse("from-client-channel")
lazy val toClientChannel = Try(config.getString("eventBus.toClientChannel")).getOrElse("to-client-channel")
lazy val toAkkaAppsJsonChannel = Try(config.getString("eventBus.toAkkaAppsChannel")).getOrElse("to-akka-apps-json-channel")
lazy val fromAkkaAppsJsonChannel = Try(config.getString("eventBus.fromAkkaAppsChannel")).getOrElse("from-akka-apps-json-channel")
lazy val applyPermissionCheck = Try(config.getBoolean("apps.checkPermissions")).getOrElse(false)
lazy val ejectOnViolation = Try(config.getBoolean("apps.ejectOnViolation")).getOrElse(false)
lazy val voiceConfRecordPath = Try(config.getString("voiceConf.recordPath")).getOrElse("/var/freeswitch/meetings")
lazy val voiceConfRecordCodec = Try(config.getString("voiceConf.recordCodec")).getOrElse("wav")
lazy val checkVoiceRecordingInterval = Try(config.getInt("voiceConf.checkRecordingInterval")).getOrElse(19)
lazy val syncVoiceUsersStatusInterval = Try(config.getInt("voiceConf.syncUserStatusInterval")).getOrElse(43)
lazy val ejectRogueVoiceUsers = Try(config.getBoolean("voiceConf.ejectRogueVoiceUsers")).getOrElse(true)
lazy val dialInApprovalAudioPath = Try(config.getString("voiceConf.dialInApprovalAudioPath")).getOrElse("ivr/ivr-please_hold_while_party_contacted.wav")
lazy val recordingChapterBreakLengthInMinutes = Try(config.getInt("recording.chapterBreakLengthInMinutes")).getOrElse(0)
lazy val endMeetingWhenNoMoreAuthedUsers = Try(config.getBoolean("apps.endMeetingWhenNoMoreAuthedUsers")).getOrElse(false)
lazy val endMeetingWhenNoMoreAuthedUsersAfterMinutes = Try(config.getInt("apps.endMeetingWhenNoMoreAuthedUsersAfterMinutes")).getOrElse(2)
lazy val transcriptWords = Try(config.getInt("transcript.words")).getOrElse(8)
lazy val transcriptLines = Try(config.getInt("transcript.lines")).getOrElse(2)
lazy val reduceDuplicatedPick = Try(config.getBoolean("apps.reduceDuplicatedPick")).getOrElse(false)
// Redis server configuration
lazy val redisHost = Try(config.getString("redis.host")).getOrElse("127.0.0.1")
lazy val redisPort = Try(config.getInt("redis.port")).getOrElse(6379)
lazy val redisPassword = Try(config.getString("redis.password")).getOrElse("")
lazy val redisExpireKey = Try(config.getInt("redis.keyExpiry")).getOrElse(1209600)
// Redis channels
lazy val toAkkaAppsRedisChannel = Try(config.getString("redis.toAkkaAppsRedisChannel")).getOrElse("to-akka-apps-redis-channel")
lazy val fromAkkaAppsRedisChannel = Try(config.getString("redis.fromAkkaAppsRedisChannel")).getOrElse("from-akka-apps-redis-channel")
lazy val toVoiceConfRedisChannel = Try(config.getString("redis.toVoiceConfRedisChannel")).getOrElse("to-voice-conf-redis-channel")
lazy val fromVoiceConfRedisChannel = Try(config.getString("redis.fromVoiceConfRedisChannel")).getOrElse("from-voice-conf-redis-channel")
lazy val toSfuRedisChannel = Try(config.getString("redis.toSfuRedisChannel")).getOrElse("to-sfu-redis-channel")
lazy val fromSfuRedisChannel = Try(config.getString("redis.fromSfuRedisChannel")).getOrElse("from-sfu-redis-channel")
lazy val fromAkkaAppsWbRedisChannel = Try(config.getString("redis.fromAkkaAppsWbRedisChannel")).getOrElse("from-akka-apps-wb-redis-channel")
lazy val fromAkkaAppsChatRedisChannel = Try(config.getString("redis.fromAkkaAppsChatRedisChannel")).getOrElse("from-akka-apps-chat-redis-channel")
lazy val fromAkkaAppsPresRedisChannel = Try(config.getString("redis.fromAkkaAppsPresRedisChannel")).getOrElse("from-akka-apps-pres-redis-channel")
lazy val fromBbbWebRedisChannel = Try(config.getString("redis.fromBbbWebRedisChannel")).getOrElse("from-bbb-web-redis-channel")
lazy val analyticsIncludeChat = Try(config.getBoolean("analytics.includeChat")).getOrElse(true)
// Grab the "interface" parameter from the http config
val httpHost = config.getString("http.interface")
// Grab the "port" parameter from the http config
val httpPort = config.getInt("http.port")
}

View File

@ -0,0 +1,189 @@
package org.bigbluebutton.core
import java.io.{ PrintWriter, StringWriter }
import akka.actor._
import akka.actor.ActorLogging
import akka.actor.SupervisorStrategy.Resume
import akka.util.Timeout
import scala.concurrent.duration._
import org.bigbluebutton.core.bus._
import org.bigbluebutton.core.api._
import org.bigbluebutton.SystemConfiguration
import java.util.concurrent.TimeUnit
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.running.RunningMeeting
import org.bigbluebutton.core2.RunningMeetings
import org.bigbluebutton.core2.message.senders.MsgBuilder
import org.bigbluebutton.service.HealthzService
object BigBlueButtonActor extends SystemConfiguration {
def props(
system: ActorSystem,
eventBus: InternalEventBus,
bbbMsgBus: BbbMsgRouterEventBus,
outGW: OutMessageGateway,
healthzService: HealthzService
): Props =
Props(classOf[BigBlueButtonActor], system, eventBus, bbbMsgBus, outGW, healthzService)
}
class BigBlueButtonActor(
val system: ActorSystem,
val eventBus: InternalEventBus, val bbbMsgBus: BbbMsgRouterEventBus,
val outGW: OutMessageGateway,
val healthzService: HealthzService
) extends Actor
with ActorLogging with SystemConfiguration {
implicit def executionContext = system.dispatcher
implicit val timeout = Timeout(5 seconds)
private val meetings = new RunningMeetings
override val supervisorStrategy = OneForOneStrategy(maxNrOfRetries = 10, withinTimeRange = 1 minute) {
case e: Exception => {
val sw: StringWriter = new StringWriter()
sw.write("An exception has been thrown on BigBlueButtonActor, exception message [" + e.getMessage() + "] (full stacktrace below)\n")
e.printStackTrace(new PrintWriter(sw))
log.error(sw.toString())
Resume
}
}
override def preStart() {
bbbMsgBus.subscribe(self, meetingManagerChannel)
}
override def postStop() {
bbbMsgBus.unsubscribe(self, meetingManagerChannel)
}
def receive = {
// Internal messages
case msg: DestroyMeetingInternalMsg => handleDestroyMeeting(msg)
// 2x messages
case msg: BbbCommonEnvCoreMsg => handleBbbCommonEnvCoreMsg(msg)
case _ => // do nothing
}
private def handleBbbCommonEnvCoreMsg(msg: BbbCommonEnvCoreMsg): Unit = {
msg.core match {
case m: CreateMeetingReqMsg => handleCreateMeetingReqMsg(m)
case m: RegisterUserReqMsg => handleRegisterUserReqMsg(m)
case m: GetAllMeetingsReqMsg => handleGetAllMeetingsReqMsg(m)
case m: GetRunningMeetingsReqMsg => handleGetRunningMeetingsReqMsg(m)
case m: CheckAlivePingSysMsg => handleCheckAlivePingSysMsg(m)
case m: ValidateConnAuthTokenSysMsg => handleValidateConnAuthTokenSysMsg(m)
case _ => log.warning("Cannot handle " + msg.envelope.name)
}
}
def handleValidateConnAuthTokenSysMsg(msg: ValidateConnAuthTokenSysMsg): Unit = {
RunningMeetings.findWithId(meetings, msg.body.meetingId) match {
case Some(meeting) =>
meeting.actorRef forward msg
case None =>
val event = MsgBuilder.buildValidateConnAuthTokenSysRespMsg(msg.body.meetingId, msg.body.userId,
false, msg.body.connId, msg.body.app)
outGW.send(event)
}
}
def handleRegisterUserReqMsg(msg: RegisterUserReqMsg): Unit = {
log.debug("RECEIVED RegisterUserReqMsg msg {}", msg)
for {
m <- RunningMeetings.findWithId(meetings, msg.header.meetingId)
} yield {
log.debug("FORWARDING Register user message")
m.actorRef forward (msg)
}
}
def handleCreateMeetingReqMsg(msg: CreateMeetingReqMsg): Unit = {
log.debug("RECEIVED CreateMeetingReqMsg msg {}", msg)
RunningMeetings.findWithId(meetings, msg.body.props.meetingProp.intId) match {
case None =>
log.info("Create meeting request. meetingId={}", msg.body.props.meetingProp.intId)
val m = RunningMeeting(msg.body.props, outGW, eventBus)
// Subscribe to meeting and voice events.
eventBus.subscribe(m.actorRef, m.props.meetingProp.intId)
eventBus.subscribe(m.actorRef, m.props.voiceProp.voiceConf)
bbbMsgBus.subscribe(m.actorRef, m.props.meetingProp.intId)
bbbMsgBus.subscribe(m.actorRef, m.props.voiceProp.voiceConf)
RunningMeetings.add(meetings, m)
case Some(m) =>
log.info("Meeting already created. meetingID={}", msg.body.props.meetingProp.intId)
// do nothing
}
}
private def handleGetRunningMeetingsReqMsg(msg: GetRunningMeetingsReqMsg): Unit = {
val liveMeetings = RunningMeetings.meetings(meetings)
val meetingIds = liveMeetings.map(m => m.props.meetingProp.intId)
val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
val envelope = BbbCoreEnvelope(GetRunningMeetingsRespMsg.NAME, routing)
val header = BbbCoreBaseHeader(GetRunningMeetingsRespMsg.NAME)
val body = GetRunningMeetingsRespMsgBody(meetingIds)
val event = GetRunningMeetingsRespMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
outGW.send(msgEvent)
}
private def handleGetAllMeetingsReqMsg(msg: GetAllMeetingsReqMsg): Unit = {
RunningMeetings.meetings(meetings).filter(_.props.systemProps.html5InstanceId == msg.body.html5InstanceId).foreach(m => {
m.actorRef ! msg
})
}
private def handleCheckAlivePingSysMsg(msg: CheckAlivePingSysMsg): Unit = {
val event = MsgBuilder.buildCheckAlivePingSysMsg(msg.body.system, msg.body.bbbWebTimestamp, System.currentTimeMillis())
healthzService.sendPubSubStatusMessage(msg.body.akkaAppsTimestamp, System.currentTimeMillis())
outGW.send(event)
}
private def handleDestroyMeeting(msg: DestroyMeetingInternalMsg): Unit = {
for {
m <- RunningMeetings.findWithId(meetings, msg.meetingId)
m2 <- RunningMeetings.remove(meetings, msg.meetingId)
} yield {
// Unsubscribe to meeting and voice events.
eventBus.unsubscribe(m.actorRef, m.props.meetingProp.intId)
eventBus.unsubscribe(m.actorRef, m.props.voiceProp.voiceConf)
bbbMsgBus.unsubscribe(m.actorRef, m.props.meetingProp.intId)
bbbMsgBus.unsubscribe(m.actorRef, m.props.voiceProp.voiceConf)
// Delay sending DisconnectAllUsers to allow messages to reach the client
// before the connections are closed.
context.system.scheduler.scheduleOnce(Duration.create(2500, TimeUnit.MILLISECONDS)) {
// Disconnect all clients
val disconnectEvnt = MsgBuilder.buildDisconnectAllClientsSysMsg(msg.meetingId, "meeting-destroyed")
m2.outMsgRouter.send(disconnectEvnt)
log.info("Destroyed meetingId={}", msg.meetingId)
val destroyedEvent = MsgBuilder.buildMeetingDestroyedEvtMsg(msg.meetingId)
m2.outMsgRouter.send(destroyedEvent)
// Stop the meeting actor.
context.stop(m.actorRef)
}
}
}
}

View File

@ -0,0 +1,9 @@
package org.bigbluebutton.core {
object MessageType extends scala.Enumeration {
type MessageType = Value
val SYSTEM = Value("system")
val BROADCAST = Value("broadcast")
val DIRECT = Value("direct")
}
}

View File

@ -0,0 +1,10 @@
package org.bigbluebutton.core
import org.bigbluebutton.common2.msgs.{ BbbCommonEnvCoreMsg }
trait OutMessageGateway {
def send(msg: BbbCommonEnvCoreMsg): Unit
def record(msg: BbbCommonEnvCoreMsg): Unit
}

View File

@ -0,0 +1,26 @@
package org.bigbluebutton.core
import org.bigbluebutton.SystemConfiguration
import org.bigbluebutton.common2.msgs.BbbCommonEnvCoreMsg
import org.bigbluebutton.core.bus._
object OutMessageGatewayImp {
def apply(
outBus2: OutEventBus2
) =
new OutMessageGatewayImp(outBus2)
}
class OutMessageGatewayImp(
outBus2: OutEventBus2
) extends OutMessageGateway
with SystemConfiguration {
def send(msg: BbbCommonEnvCoreMsg): Unit = {
outBus2.publish(BbbOutMessage(outBbbMsgMsgChannel, msg))
}
def record(msg: BbbCommonEnvCoreMsg): Unit = {
outBus2.publish(BbbOutMessage(recordServiceMessageChannel, msg))
}
}

View File

@ -0,0 +1,127 @@
package org.bigbluebutton.core.api
import org.bigbluebutton.core.domain.{ BreakoutUser, BreakoutVoiceUser }
import spray.json.JsObject
case class InMessageHeader(name: String)
case class InHeaderAndJsonPayload(header: InMessageHeader, payload: JsObject)
case class MessageProcessException(message: String) extends Exception(message)
trait InMessage
//////////////////////////////////////////////////////////////////////////////
// System
/////////////////////////////////////////////////////////////////////////////
case class IsMeetingActorAliveMessage(meetingId: String) extends InMessage
//////////////////////////////////////////////////////////////////////////////
// Internal Messages
/////////////////////////////////////////////////////////////////////////////
case class MonitorNumberOfUsersInternalMsg(meetingID: String) extends InMessage
/**
* Audit message sent to meeting to trigger updating clients of meeting time remaining.
* @param meetingId
*/
case class SendTimeRemainingAuditInternalMsg(meetingId: String, timeUpdatedInMinutes: Int) extends InMessage
/**
* Parent message sent to breakout rooms to trigger updating clients of meeting time remaining.
* @param meetingId
* @param timeLeftInSec
*/
case class SendBreakoutTimeRemainingInternalMsg(meetingId: String, timeLeftInSec: Long, timeUpdatedInMinutes: Int) extends InMessage
case class SendRecordingTimerInternalMsg(meetingId: String) extends InMessage
case class ExtendMeetingDuration(meetingId: String, userId: String) extends InMessage
case class DestroyMeetingInternalMsg(meetingId: String) extends InMessage
/**
* Sent by breakout room to parent meeting the breakout had ended.
* @param meetingId
*/
case class BreakoutRoomEndedInternalMsg(meetingId: String) extends InMessage
/**
* Sent by breakout room to parent meeting that breakout room has been created.
* @param parentId
* @param breakoutId
*/
case class BreakoutRoomCreatedInternalMsg(parentId: String, breakoutId: String) extends InMessage
/**
* Audit message to trigger breakout room to update parent meeting of list of users.
* @param parentId
* @param breakoutId
*/
case class SendBreakoutUsersAuditInternalMsg(parentId: String, breakoutId: String) extends InMessage
/**
* Send by breakout room to parent meeting with list of users in breakout room.
* @param parentId
* @param breakoutId
* @param users
*/
case class BreakoutRoomUsersUpdateInternalMsg(parentId: String, breakoutId: String,
users: Vector[BreakoutUser],
voiceUsers: Vector[BreakoutVoiceUser]) extends InMessage
/**
* Sent by parent meeting to breakout room to end breakout room.
* @param parentId
* @param breakoutId
*/
case class EndBreakoutRoomInternalMsg(parentId: String, breakoutId: String, reason: String) extends InMessage
/**
* Sent by parent meeting to breakout room to update time.
* @param parentId
* @param breakoutId
* @param durationInSeconds
*/
case class UpdateBreakoutRoomTimeInternalMsg(parentId: String, breakoutId: String, durationInSeconds: Int) extends InMessage
/**
* Sent by parent meeting to breakout room to extend time.
* @param parentId
* @param breakoutId
* @param senderName
* @param msg
*/
case class SendMessageToBreakoutRoomInternalMsg(parentId: String, breakoutId: String, senderName: String, msg: String) extends InMessage
/**
* Sent by parent meeting to breakout room to eject user.
* @param parentId
* @param breakoutId
* @param extUserId
* @param ejectedBy
* @param reason
* @param reasonCode
* @param ban
*/
case class EjectUserFromBreakoutInternalMsg(parentId: String, breakoutId: String, extUserId: String, ejectedBy: String, reason: String, reasonCode: String, ban: Boolean) extends InMessage
/**
* Sent by parent meeting to breakout room to import annotated slides.
* @param userId
* @param parentMeetingId
* @param allPages
*/
case class CapturePresentationReqInternalMsg(userId: String, parentMeetingId: String, allPages: Boolean = true) extends InMessage
/**
* Sent by parent meeting to breakout room to import shared notes.
* @param parentMeetingId
* @param meetingName
*/
case class CaptureSharedNotesReqInternalMsg(parentMeetingId: String, meetingName: String) 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
case class DeskShareRTMPBroadcastStartedRequest(conferenceName: String, streamname: String, videoWidth: Int, videoHeight: Int, timestamp: String) extends InMessage
case class DeskShareRTMPBroadcastStoppedRequest(conferenceName: String, streamname: String, videoWidth: Int, videoHeight: Int, timestamp: String) extends InMessage
case class DeskShareGetDeskShareInfoRequest(conferenceName: String, requesterID: String, replyTo: String) extends InMessage

View File

@ -0,0 +1,14 @@
package org.bigbluebutton.core.api
import java.util.concurrent.TimeUnit
object TimestampGenerator {
def generateTimestamp(): Long = {
TimeUnit.NANOSECONDS.toMillis(System.nanoTime())
}
def getCurrentTime(): Long = {
System.currentTimeMillis();
}
}

View File

@ -0,0 +1,87 @@
package org.bigbluebutton.core.apps
import org.bigbluebutton.core.domain.{ BreakoutRoom2x, BreakoutUser }
object BreakoutModel {
def create(
parentId: String,
id: String,
externalId: String,
name: String,
sequence: Integer,
shortName: String,
isDefaultName: Boolean,
freeJoin: Boolean,
voiceConf: String,
assignedUsers: Vector[String],
captureNotes: Boolean,
captureSlides: Boolean,
): BreakoutRoom2x = {
new BreakoutRoom2x(id, externalId, name, parentId, sequence, shortName, isDefaultName, freeJoin, voiceConf, assignedUsers, Vector(), Vector(), None, false, captureNotes, captureSlides)
}
}
case class BreakoutModel(
startedOn: Option[Long],
durationInSeconds: Int,
rooms: Map[String, BreakoutRoom2x]
) {
def find(id: String): Option[BreakoutRoom2x] = {
rooms.get(id)
}
def findWithExternalId(externalId: String): Option[BreakoutRoom2x] = {
rooms.values find (r => r.externalId == externalId)
}
def getRooms(): Vector[BreakoutRoom2x] = {
rooms.values.toVector
}
def getNumberOfRooms(): Int = {
rooms.size
}
def started(room: BreakoutRoom2x, startedOnInMillis: Long): BreakoutRoom2x = {
room.copy(started = true, startedOn = Some(startedOnInMillis))
}
def hasAllStarted(): Boolean = {
rooms.values.filter(r => r.started).size == rooms.values.size
}
def update(room: BreakoutRoom2x): BreakoutModel = {
copy(rooms = rooms + (room.id -> room))
}
def getAssignedUsers(breakoutMeetingId: String): Option[Vector[String]] = {
for {
room <- rooms.get(breakoutMeetingId)
} yield room.assignedUsers
}
def updateBreakoutUsers(breakoutMeetingId: String, users: Vector[BreakoutUser]): BreakoutModel = {
val model = for {
room <- rooms.get(breakoutMeetingId)
} yield {
val newRoom = room.copy(users = users)
copy(rooms = rooms + (newRoom.id -> newRoom))
}
model match {
case Some(m) => m
case None => copy()
}
}
def removeRoom(id: String): BreakoutModel = {
copy(rooms = rooms - id)
}
def setTime(newDurationInSeconds: Int): BreakoutModel = {
copy(durationInSeconds = newDurationInSeconds)
}
}

View File

@ -0,0 +1,123 @@
package org.bigbluebutton.core.apps
import org.bigbluebutton.common2.msgs.TranscriptVO
import scala.collection.immutable.HashMap
class CaptionModel {
private var transcripts = new HashMap[String, TranscriptVO]()
private def createTranscript(name: String, locale: String, ownerId: String): TranscriptVO = {
val transcript = TranscriptVO(ownerId, "", locale)
transcripts += name -> transcript
transcript
}
private def findTranscriptByOwnerId(userId: String): Option[(String, TranscriptVO)] = {
transcripts.find(_._2.ownerId == userId).foreach(t => {
return Some(t)
})
return None
}
def updateTranscriptOwner(name: String, locale: String, ownerId: String): Map[String, TranscriptVO] = {
var updatedTranscripts = new HashMap[String, TranscriptVO]
// clear owner from previous locale
if (ownerId.length > 0) {
findTranscriptByOwnerId(ownerId).foreach(t => {
val oldTranscript = t._2.copy(ownerId = "")
transcripts += t._1 -> oldTranscript
updatedTranscripts += t._1 -> oldTranscript
})
}
// change the owner if it does exist
if (transcripts contains name) {
val newTranscript = transcripts(name).copy(ownerId = ownerId)
transcripts += name -> newTranscript
updatedTranscripts += name -> newTranscript
} else { // create the locale if it doesn't exist
val addedTranscript = createTranscript(name, locale, ownerId)
updatedTranscripts += name -> addedTranscript
}
updatedTranscripts
}
def getHistory(): Map[String, TranscriptVO] = {
transcripts
}
def editHistory(userId: String, startIndex: Integer, endIndex: Integer, name: String, text: String): Boolean = {
var successfulEdit = false
//println("editHistory entered")
if (transcripts contains name) {
val oldTranscript = transcripts(name)
if (oldTranscript.ownerId == userId || userId == "system") {
//println("editHistory found name:" + name)
val oText: String = transcripts(name).text
if (startIndex >= 0 && endIndex <= oText.length && startIndex <= endIndex) {
//println("editHistory passed index test")
val sText: String = oText.substring(0, startIndex)
val eText: String = oText.substring(endIndex)
transcripts += name -> transcripts(name).copy(text = (sText + text + eText))
//println("editHistory new history is: " + transcripts(name).text)
successfulEdit = true
}
}
}
successfulEdit
}
def getTextTail(name: String): String = {
var tail = ""
if (transcripts contains name) {
val text = transcripts(name).text
if (text.size > 256) {
tail = text.slice(text.size - 256, text.size)
} else {
tail = text
}
}
tail
}
def getLocale(name: String): String = {
var locale = ""
if (transcripts contains name) {
locale = transcripts(name).locale
}
locale
}
def checkCaptionOwnerLogOut(userId: String): Option[(String, TranscriptVO)] = {
var rtnTranscript: Option[(String, TranscriptVO)] = None
if (userId.length > 0) {
findTranscriptByOwnerId(userId).foreach(t => {
val oldTranscript = t._2.copy(ownerId = "")
transcripts += t._1 -> oldTranscript
rtnTranscript = Some((t._1, oldTranscript))
})
}
rtnTranscript
}
def isUserCaptionOwner(userId: String, name: String): Boolean = {
var isOwner: Boolean = false;
if (transcripts.contains(name) && transcripts(name).ownerId == userId) {
isOwner = true;
}
isOwner
}
}

View File

@ -0,0 +1,22 @@
package org.bigbluebutton.core.apps
import org.bigbluebutton.common2.msgs.ChatMessageVO
import scala.collection.mutable.ArrayBuffer
object ChatModel {
def getChatHistory(chatModel: ChatModel): Array[ChatMessageVO] = {
chatModel.messages.toArray
}
def addNewChatMessage(chatModel: ChatModel, msg: ChatMessageVO) {
chatModel.messages.append(msg)
}
def clearPublicChatHistory(chatModel: ChatModel) {
chatModel.messages.clear()
}
}
class ChatModel {
private val messages = new ArrayBuffer[ChatMessageVO]()
}

View File

@ -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 = ""
}

View File

@ -0,0 +1,17 @@
package org.bigbluebutton.core.apps
import org.bigbluebutton.core.running.MeetingActor
import org.bigbluebutton.core2.message.handlers.guests._
trait GuestsApp extends GetGuestsWaitingApprovalReqMsgHdlr
with GuestsWaitingApprovedMsgHdlr
with GuestWaitingLeftMsgHdlr
with UpdatePositionInWaitingQueueReqMsgHdlr
with SetGuestPolicyMsgHdlr
with SetGuestLobbyMessageMsgHdlr
with SetPrivateGuestLobbyMessageCmdMsgHdlr
with GetGuestPolicyReqMsgHdlr {
this: MeetingActor =>
}

View File

@ -0,0 +1,107 @@
package org.bigbluebutton.core.apps
import org.bigbluebutton.SystemConfiguration
import org.bigbluebutton.core.apps.users.UsersApp
import org.bigbluebutton.core.models._
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
import org.bigbluebutton.core2.message.senders.Sender
trait RightsManagementTrait extends SystemConfiguration {
/**
* This method will check if the user that issued the command has the correct permissions.
*
* Example of the permissions level are "AUTHENTICATED", "MODERATOR" and "GUEST". Example of roles
* are "VIEWER" and "PRESENTER".
*
* @param permissionLevel Lowest permission needed to have access.
* @param roleLevel Lowest role needed to have access.
* @return true allows API to execute, false denies executing API
*/
def permissionFailed(permissionLevel: Int, roleLevel: Int, users: Users2x, userId: String): Boolean = {
if (applyPermissionCheck) {
!PermissionCheck.isAllowed(permissionLevel, roleLevel, users, userId)
} else {
false
}
}
def filterPresentationMessage(users: Users2x, userId: String): Boolean = {
// Check if the message are delayed presentation messages from the previous presenter
// after a switch presenter has been made. ralam nov 22, 2017
users.purgeOldPresenters()
val now = System.currentTimeMillis()
users.findOldPresenter(userId) match {
case Some(op) => now - op.changedPresenterOn < 5000
case None => false
}
}
}
object PermissionCheck extends SystemConfiguration {
val MOD_LEVEL = 100
val AUTHED_LEVEL = 50
val GUEST_LEVEL = 0
val PRESENTER_LEVEL = 100
val VIEWER_LEVEL = 0
private def permissionToLevel(user: UserState): Int = {
if (user.role == Roles.MODERATOR_ROLE) {
MOD_LEVEL
} else if (user.authed) {
AUTHED_LEVEL
} else {
GUEST_LEVEL
}
}
private def roleToLevel(users: Users2x, user: UserState): Int = {
if (Users2x.userIsInPresenterGroup(users, user.intId) || user.presenter) PRESENTER_LEVEL else VIEWER_LEVEL
}
/**
* This method will check if the user that issued the command has the correct permissions.
*
* Example of the permissions level are "AUTHENTICATED", "MODERATOR" and "GUEST". Example of roles
* are "VIEWER" and "PRESENTER".
*
* @param permissionLevel Lowest permission needed to have access.
* @param roleLevel Lowest role needed to have access.
* @return true allows API to execute, false denies executing API
*/
def isAllowed(permissionLevel: Int, roleLevel: Int, users: Users2x, userId: String): Boolean = {
Users2x.findWithIntId(users, userId) match {
case Some(user) =>
val permLevelCheck = permissionToLevel(user) >= permissionLevel
val roleLevelCheck = roleToLevel(users, user) >= roleLevel
permLevelCheck && roleLevelCheck
case None => false
}
}
def ejectUserForFailedPermission(meetingId: String, userId: String, reason: String,
outGW: OutMsgRouter, liveMeeting: LiveMeeting): Unit = {
if (ejectOnViolation) {
val ejectedBy = SystemUser.ID
UsersApp.ejectUserFromMeeting(outGW, liveMeeting, userId, ejectedBy, reason, EjectReasonCode.PERMISSION_FAILED, ban = false)
// send a system message to force disconnection
Sender.sendDisconnectClientSysMsg(meetingId, userId, ejectedBy, reason, outGW)
} else {
// TODO: get this object a context so it can use the akka logging system
println(s"Skipping violation ejection of ${userId} trying to ${reason} in ${meetingId}")
}
}
def addOldPresenter(users: Users2x, userId: String): OldPresenter = {
users.addOldPresenter(userId)
}
def removeOldPresenter(users: Users2x, userId: String): Option[OldPresenter] = {
users.removeOldPresenter(userId)
}
}

View File

@ -0,0 +1,57 @@
package org.bigbluebutton.core.apps
import org.bigbluebutton.common2.domain.PageVO
case class CurrentPresenter(userId: String, name: String, assignedBy: String)
case class CurrentPresentationInfo(presenter: CurrentPresenter, presentations: Seq[Presentation])
case class Presentation(id: String, name: String, current: Boolean = false,
pages: scala.collection.immutable.Map[String, PageVO], downloadable: Boolean, removable: Boolean)
class PresentationModel {
private var presentations = new scala.collection.immutable.HashMap[String, Presentation] // todo remove
def addPresentation(pres: Presentation) { // todo remove
savePresentation(pres)
}
def getPresentations(): Vector[Presentation] = { // todo remove
presentations.values.toVector
}
def getCurrentPresentation(): Option[Presentation] = { // todo remove
presentations.values find (p => p.current)
}
def getCurrentPage(pres: Presentation): Option[PageVO] = {
pres.pages.values find (p => p.current)
}
def getCurrentPage(): Option[PageVO] = {
for {
curPres <- getCurrentPresentation()
curPage <- getCurrentPage(curPres)
} yield curPage
}
def setCurrentPresentation(presId: String): Option[Presentation] = { // todo remove
getPresentations foreach (curPres => {
if (curPres.id != presId) {
val newPres = curPres.copy(current = false)
savePresentation(newPres)
}
})
presentations.get(presId) match {
case Some(pres) =>
val cp = pres.copy(current = true)
savePresentation(cp)
Some(cp)
case None => None
}
}
private def savePresentation(pres: Presentation) { // todo remove
presentations += pres.id -> pres
}
}

View File

@ -0,0 +1,106 @@
package org.bigbluebutton.core.apps
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
import org.bigbluebutton.core2.message.senders.MsgBuilder
object ScreenshareModel {
def resetDesktopSharingParams(status: ScreenshareModel) = {
status.broadcastingRTMP = false
status.screenshareStarted = false
status.rtmpBroadcastingUrl = ""
status.screenshareVideoWidth = 0
status.screenshareVideoHeight = 0
status.voiceConf = ""
status.screenshareConf = ""
status.timestamp = ""
status.hasAudio = false
}
def getScreenshareStarted(status: ScreenshareModel): Boolean = {
return status.screenshareStarted
}
def setScreenshareStarted(status: ScreenshareModel, b: Boolean) {
status.screenshareStarted = b
}
def setScreenshareVideoWidth(status: ScreenshareModel, videoWidth: Int) {
status.screenshareVideoWidth = videoWidth
}
def setScreenshareVideoHeight(status: ScreenshareModel, videoHeight: Int) {
status.screenshareVideoHeight = videoHeight
}
def getScreenshareVideoWidth(status: ScreenshareModel): Int = {
status.screenshareVideoWidth
}
def getScreenshareVideoHeight(status: ScreenshareModel): Int = {
status.screenshareVideoHeight
}
def broadcastingRTMPStarted(status: ScreenshareModel) {
status.broadcastingRTMP = true
}
def isBroadcastingRTMP(status: ScreenshareModel): Boolean = {
status.broadcastingRTMP
}
def broadcastingRTMPStopped(status: ScreenshareModel) {
status.broadcastingRTMP = false
}
def setRTMPBroadcastingUrl(status: ScreenshareModel, path: String) {
status.rtmpBroadcastingUrl = path
}
def getRTMPBroadcastingUrl(status: ScreenshareModel): String = {
status.rtmpBroadcastingUrl
}
def setVoiceConf(status: ScreenshareModel, voiceConf: String): Unit = {
status.voiceConf = voiceConf
}
def getVoiceConf(status: ScreenshareModel): String = {
status.voiceConf
}
def setScreenshareConf(status: ScreenshareModel, screenshareConf: String): Unit = {
status.screenshareConf = screenshareConf
}
def getScreenshareConf(status: ScreenshareModel): String = {
status.screenshareConf
}
def setTimestamp(status: ScreenshareModel, timestamp: String): Unit = {
status.timestamp = timestamp
}
def getTimestamp(status: ScreenshareModel): String = {
status.timestamp
}
def setHasAudio(status: ScreenshareModel, hasAudio: Boolean): Unit = {
status.hasAudio = hasAudio
}
def getHasAudio(status: ScreenshareModel): Boolean = {
status.hasAudio
}
}
class ScreenshareModel {
private var rtmpBroadcastingUrl: String = ""
private var screenshareStarted = false
private var screenshareVideoWidth = 0
private var screenshareVideoHeight = 0
private var broadcastingRTMP = false
private var voiceConf: String = ""
private var screenshareConf: String = ""
private var timestamp: String = ""
private var hasAudio = false
}

View File

@ -0,0 +1,126 @@
package org.bigbluebutton.core.apps
import scala.collection.immutable.HashMap
import org.bigbluebutton.common2.msgs.AnnotationVO
import org.bigbluebutton.core.apps.whiteboard.Whiteboard
import org.bigbluebutton.SystemConfiguration
class WhiteboardModel extends SystemConfiguration {
private var _whiteboards = new HashMap[String, Whiteboard]()
private def saveWhiteboard(wb: Whiteboard) {
_whiteboards += wb.id -> wb
}
def getWhiteboard(id: String): Whiteboard = {
_whiteboards.get(id).getOrElse(createWhiteboard(id))
}
def hasWhiteboard(id: String): Boolean = {
_whiteboards.contains(id)
}
private def createWhiteboard(wbId: String): Whiteboard = {
Whiteboard(
wbId,
Array.empty[String],
Array.empty[String],
System.currentTimeMillis(),
new HashMap[String, AnnotationVO]
)
}
private def deepMerge(test: Map[String, _], that: Map[String, _]): Map[String, _] =
(for (k <- test.keys ++ that.keys) yield {
val newValue =
(test.get(k), that.get(k)) match {
case (Some(v), None) => v
case (None, Some(v)) => v
case (Some(v1), Some(v2)) =>
if (v1.isInstanceOf[Map[String, _]] && v2.isInstanceOf[Map[String, _]])
deepMerge(v1.asInstanceOf[Map[String, _]], v2.asInstanceOf[Map[String, _]])
else v2
case (_, _) => ???
}
k -> newValue
}).toMap
def addAnnotations(wbId: String, userId: String, annotations: Array[AnnotationVO], isPresenter: Boolean, isModerator: Boolean): Array[AnnotationVO] = {
var annotationsAdded = Array[AnnotationVO]()
val wb = getWhiteboard(wbId)
var newAnnotationsMap = wb.annotationsMap
for (annotation <- annotations) {
val oldAnnotation = wb.annotationsMap.get(annotation.id)
if (!oldAnnotation.isEmpty) {
val hasPermission = isPresenter || isModerator || oldAnnotation.get.userId == userId
if (hasPermission) {
val newAnnotation = oldAnnotation.get.copy(annotationInfo = deepMerge(oldAnnotation.get.annotationInfo, annotation.annotationInfo))
newAnnotationsMap += (annotation.id -> newAnnotation)
annotationsAdded :+= annotation
println(s"Updated annotation onpage [${wb.id}]. After numAnnotations=[${newAnnotationsMap.size}].")
} else {
println(s"User $userId doesn't have permission to edit annotation ${annotation.id}, ignoring...")
}
} else if (annotation.annotationInfo.contains("type")) {
newAnnotationsMap += (annotation.id -> annotation)
annotationsAdded :+= annotation
println(s"Adding annotation to page [${wb.id}]. After numAnnotations=[${newAnnotationsMap.size}].")
} else {
println(s"New annotation [${annotation.id}] with no type, ignoring (probably received a remove message before and now the shape is incomplete, ignoring...")
}
}
val newWb = wb.copy(annotationsMap = newAnnotationsMap)
saveWhiteboard(newWb)
annotationsAdded
}
def getHistory(wbId: String): Array[AnnotationVO] = {
val wb = getWhiteboard(wbId)
wb.annotationsMap.values.toArray
}
def deleteAnnotations(wbId: String, userId: String, annotationsIds: Array[String], isPresenter: Boolean, isModerator: Boolean): Array[String] = {
var annotationsIdsRemoved = Array[String]()
val wb = getWhiteboard(wbId)
var newAnnotationsMap = wb.annotationsMap
for (annotationId <- annotationsIds) {
val annotation = wb.annotationsMap.get(annotationId)
if (!annotation.isEmpty) {
val hasPermission = isPresenter || isModerator || annotation.get.userId == userId
if (hasPermission) {
newAnnotationsMap -= annotationId
println("Removing annotation on page [" + wb.id + "]. After numAnnotations=[" + newAnnotationsMap.size + "].")
annotationsIdsRemoved :+= annotationId
} else {
println("User doesn't have permission to remove this annotation, ignoring...")
}
}
}
val newWb = wb.copy(annotationsMap = newAnnotationsMap)
saveWhiteboard(newWb)
annotationsIdsRemoved
}
def modifyWhiteboardAccess(wbId: String, multiUser: Array[String]) {
val wb = getWhiteboard(wbId)
val newWb = wb.copy(multiUser = multiUser, oldMultiUser = wb.multiUser, changedModeOn = System.currentTimeMillis())
saveWhiteboard(newWb)
}
def getWhiteboardAccess(wbId: String): Array[String] = getWhiteboard(wbId).multiUser
def isNonEjectionGracePeriodOver(wbId: String, userId: String): Boolean = {
val wb = getWhiteboard(wbId)
val lastChange = System.currentTimeMillis() - wb.changedModeOn
!(wb.oldMultiUser.contains(userId) && lastChange < 5000)
}
def hasWhiteboardAccess(wbId: String, userId: String): Boolean = {
val wb = getWhiteboard(wbId)
wb.multiUser.contains(userId)
}
def getChangedModeOn(wbId: String): Long = getWhiteboard(wbId).changedModeOn
}

View File

@ -0,0 +1,9 @@
package org.bigbluebutton.core.apps.audiocaptions
import akka.actor.ActorContext
class AudioCaptionsApp2x(implicit val context: ActorContext)
extends UpdateTranscriptPubMsgHdlr
with AudioFloorChangedVoiceConfEvtMsgHdlr {
}

View File

@ -0,0 +1,15 @@
package org.bigbluebutton.core.apps.audiocaptions
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.models.{ AudioCaptions, VoiceUsers }
import org.bigbluebutton.core.running.LiveMeeting
trait AudioFloorChangedVoiceConfEvtMsgHdlr {
this: AudioCaptionsApp2x =>
def handle(msg: AudioFloorChangedVoiceConfEvtMsg, liveMeeting: LiveMeeting): Unit = {
for {
vu <- VoiceUsers.findWithVoiceUserId(liveMeeting.voiceUsers, msg.body.voiceUserId)
} yield AudioCaptions.setFloor(liveMeeting.audioCaptions, vu.intId)
}
}

View File

@ -0,0 +1,72 @@
package org.bigbluebutton.core.apps.audiocaptions
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.models.AudioCaptions
import org.bigbluebutton.core.running.LiveMeeting
trait UpdateTranscriptPubMsgHdlr {
this: AudioCaptionsApp2x =>
def handle(msg: UpdateTranscriptPubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
val meetingId = liveMeeting.props.meetingProp.intId
def broadcastEvent(userId: String, transcriptId: String, transcript: String, locale: String): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, meetingId, "nodeJSapp")
val envelope = BbbCoreEnvelope(TranscriptUpdatedEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(TranscriptUpdatedEvtMsg.NAME, meetingId, userId)
val body = TranscriptUpdatedEvtMsgBody(transcriptId, transcript, locale)
val event = TranscriptUpdatedEvtMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(msgEvent)
}
// Adapt to the current captions' recording process
def editTranscript(
userId: String,
start: Int,
end: Int,
locale: String,
text: String
): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, meetingId, userId)
val envelope = BbbCoreEnvelope(EditCaptionHistoryEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(EditCaptionHistoryEvtMsg.NAME, meetingId, userId)
val body = EditCaptionHistoryEvtMsgBody(start, end, locale, locale, text)
val event = EditCaptionHistoryEvtMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(msgEvent)
}
if (AudioCaptions.isFloor(liveMeeting.audioCaptions, msg.header.userId)) {
val (start, end, text) = AudioCaptions.editTranscript(
liveMeeting.audioCaptions,
msg.body.transcriptId,
msg.body.start,
msg.body.end,
msg.body.text,
msg.body.transcript,
msg.body.locale
)
editTranscript(
msg.header.userId,
start,
end,
msg.body.locale,
text
)
val transcript = AudioCaptions.parseTranscript(msg.body.transcript)
broadcastEvent(
msg.header.userId,
msg.body.transcriptId,
transcript,
msg.body.locale
)
}
}
}

View File

@ -0,0 +1,103 @@
package org.bigbluebutton.core.apps.breakout
import org.bigbluebutton.core.running.MeetingActor
import java.net.URLEncoder
import scala.collection.SortedSet
import org.apache.commons.codec.digest.DigestUtils
trait BreakoutApp2x extends BreakoutRoomCreatedMsgHdlr
with BreakoutRoomsListMsgHdlr
with BreakoutRoomUsersUpdateMsgHdlr
with CreateBreakoutRoomsCmdMsgHdlr
with EndAllBreakoutRoomsMsgHdlr
with UpdateBreakoutRoomsTimeMsgHdlr
with ChangeUserBreakoutReqMsgHdlr
with SendMessageToAllBreakoutRoomsMsgHdlr
with SendMessageToBreakoutRoomInternalMsgHdlr
with RequestBreakoutJoinURLReqMsgHdlr
with SendBreakoutUsersUpdateMsgHdlr
with TransferUserToMeetingRequestHdlr
with EndBreakoutRoomInternalMsgHdlr
with UpdateBreakoutRoomTimeInternalMsgHdlr
with EjectUserFromBreakoutInternalMsgHdlr
with BreakoutRoomEndedInternalMsgHdlr {
this: MeetingActor =>
}
object BreakoutRoomsUtil {
def createMeetingIds(id: String, index: Int): (String, String) = {
val timeStamp = System.currentTimeMillis()
val externalHash = DigestUtils.sha1Hex(id.concat("-").concat(timeStamp.toString()).concat("-").concat(index.toString()))
val externalId = externalHash.concat("-").concat(timeStamp.toString())
val internalId = DigestUtils.sha1Hex(externalId).concat("-").concat(timeStamp.toString())
(internalId, externalId)
}
def createVoiceConfId(id: String, index: Int): String = {
id.concat(index.toString())
}
def createJoinURL(webAPI: String, apiCall: String, baseString: String, checksum: String): String = {
val apiURL = if (webAPI.endsWith("/")) webAPI else webAPI.concat("/")
apiURL.concat(apiCall).concat("?").concat(baseString).concat("&checksum=").concat(checksum)
}
//
//checksum() -- Return a checksum based on SHA-1 digest
//
def checksum(s: String): String = {
DigestUtils.sha256Hex(s);
}
def calculateChecksum(apiCall: String, baseString: String, sharedSecret: String): String = {
checksum(apiCall.concat(baseString).concat(sharedSecret))
}
def joinParams(username: String, userId: String, isBreakout: Boolean, breakoutMeetingId: String,
password: String): (collection.immutable.Map[String, String], collection.immutable.Map[String, String]) = {
val params = collection.immutable.HashMap(
"fullName" -> urlEncode(username),
"userID" -> urlEncode(userId),
"isBreakout" -> urlEncode(isBreakout.toString()),
"meetingID" -> urlEncode(breakoutMeetingId),
"password" -> urlEncode(password),
"redirect" -> urlEncode("true")
)
(params, params + ("joinViaHtml5" -> urlEncode("true")))
}
def sortParams(params: collection.immutable.Map[String, String]): SortedSet[String] = {
collection.immutable.SortedSet[String]() ++ params.keySet
}
//From the list of parameters we want to pass. Creates a base string with parameters
//sorted in alphabetical order for us to sign.
def createBaseString(params: collection.immutable.Map[String, String]): String = {
val csbuf = new StringBuffer()
val keys = sortParams(params)
var first = true;
for (key <- keys) {
for (value <- params.get(key)) {
if (first) {
first = false;
} else {
csbuf.append("&");
}
csbuf.append(key);
csbuf.append("=");
csbuf.append(value);
}
}
return csbuf.toString();
}
def urlEncode(s: String): String = {
URLEncoder.encode(s, "UTF-8");
}
}

View File

@ -0,0 +1,125 @@
package org.bigbluebutton.core.apps.breakout
import org.bigbluebutton.SystemConfiguration
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.api.{ BreakoutRoomUsersUpdateInternalMsg }
import org.bigbluebutton.core.bus.{ BigBlueButtonEvent, InternalEventBus }
import org.bigbluebutton.core.domain.{ BreakoutUser, BreakoutVoiceUser }
import org.bigbluebutton.core.models.{ Users2x, VoiceUsers }
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
object BreakoutHdlrHelpers extends SystemConfiguration {
def sendJoinURL(
liveMeeting: LiveMeeting,
outGW: OutMsgRouter,
userId: String,
externalMeetingId: String,
roomSequence: String,
breakoutId: String
) {
for {
(redirectToHtml5JoinURL, redirectJoinURL) <- getRedirectUrls(liveMeeting, userId, externalMeetingId, roomSequence)
} yield {
sendJoinURLMsg(
outGW,
liveMeeting.props.meetingProp.intId,
breakoutId,
externalMeetingId,
userId,
redirectJoinURL,
redirectToHtml5JoinURL
)
}
}
def getRedirectUrls(
liveMeeting: LiveMeeting,
userId: String,
externalMeetingId: String,
roomSequence: String
): Option[(String, String)] = {
for {
user <- Users2x.findWithIntId(liveMeeting.users2x, userId)
apiCall = "join"
(redirectParams, redirectToHtml5Params) = BreakoutRoomsUtil.joinParams(user.name, userId + "-" + roomSequence, true,
externalMeetingId, liveMeeting.props.password.moderatorPass)
// We generate a first url with redirect -> true
redirectBaseString = BreakoutRoomsUtil.createBaseString(redirectParams)
redirectJoinURL = BreakoutRoomsUtil.createJoinURL(bbbWebAPI, apiCall, redirectBaseString,
BreakoutRoomsUtil.calculateChecksum(apiCall, redirectBaseString, bbbWebSharedSecret))
// We generate a second url with redirect -> true and joinViaHtml5 -> true
redirectToHtml5BaseString = BreakoutRoomsUtil.createBaseString(redirectToHtml5Params)
redirectToHtml5JoinURL = BreakoutRoomsUtil.createJoinURL(bbbWebAPI, apiCall, redirectToHtml5BaseString,
BreakoutRoomsUtil.calculateChecksum(apiCall, redirectToHtml5BaseString, bbbWebSharedSecret))
} yield {
(redirectToHtml5JoinURL, redirectJoinURL)
}
}
def sendJoinURLMsg(
outGW: OutMsgRouter,
meetingId: String,
breakoutId: String,
externalId: String,
userId: String,
redirectJoinURL: String,
redirectToHtml5JoinURL: String
): Unit = {
def build(meetingId: String, breakoutId: String,
userId: String, redirectJoinURL: String, redirectToHtml5JoinURL: String): BbbCommonEnvCoreMsg = {
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, meetingId, userId)
val envelope = BbbCoreEnvelope(BreakoutRoomJoinURLEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(BreakoutRoomJoinURLEvtMsg.NAME, meetingId, userId)
val body = BreakoutRoomJoinURLEvtMsgBody(meetingId, breakoutId, externalId,
userId, redirectJoinURL, redirectToHtml5JoinURL)
val event = BreakoutRoomJoinURLEvtMsg(header, body)
BbbCommonEnvCoreMsg(envelope, event)
}
val msgEvent = build(meetingId, breakoutId, userId, redirectJoinURL, redirectToHtml5JoinURL)
outGW.send(msgEvent)
}
def sendChangeUserBreakoutMsg(
outGW: OutMsgRouter,
meetingId: String,
userId: String,
fromBreakoutId: String,
toBreakoutId: String,
redirectToHtml5JoinURL: String
): Unit = {
def build(meetingId: String, userId: String, fromBreakoutId: String, toBreakoutId: String, redirectToHtml5JoinURL: String): BbbCommonEnvCoreMsg = {
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, meetingId, userId)
val envelope = BbbCoreEnvelope(ChangeUserBreakoutEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(ChangeUserBreakoutEvtMsg.NAME, meetingId, userId)
val body = ChangeUserBreakoutEvtMsgBody(meetingId, userId, fromBreakoutId, toBreakoutId, redirectToHtml5JoinURL)
val event = ChangeUserBreakoutEvtMsg(header, body)
BbbCommonEnvCoreMsg(envelope, event)
}
val msgEvent = build(meetingId, userId, fromBreakoutId, toBreakoutId, redirectToHtml5JoinURL)
outGW.send(msgEvent)
}
def updateParentMeetingWithUsers(
liveMeeting: LiveMeeting,
eventBus: InternalEventBus
): Unit = {
val users = Users2x.findAll(liveMeeting.users2x)
val breakoutUsers = users map { u => new BreakoutUser(u.extId, u.name) }
val voiceUsers = VoiceUsers.findAll(liveMeeting.voiceUsers)
val breakoutVoiceUsers = voiceUsers map { vu => BreakoutVoiceUser(vu.intId, vu.intId, vu.voiceUserId) }
eventBus.publish(BigBlueButtonEvent(
liveMeeting.props.breakoutProps.parentId,
new BreakoutRoomUsersUpdateInternalMsg(liveMeeting.props.breakoutProps.parentId, liveMeeting.props.meetingProp.intId,
breakoutUsers, breakoutVoiceUsers)
))
}
}

View File

@ -0,0 +1,89 @@
package org.bigbluebutton.core.apps.breakout
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.api.BreakoutRoomCreatedInternalMsg
import org.bigbluebutton.core.apps.BreakoutModel
import org.bigbluebutton.core.domain.{ BreakoutRoom2x, MeetingState2x }
import org.bigbluebutton.core.running.{ LiveMeeting, MeetingActor, OutMsgRouter }
trait BreakoutRoomCreatedMsgHdlr {
this: MeetingActor =>
val liveMeeting: LiveMeeting
val outGW: OutMsgRouter
def handleBreakoutRoomCreatedInternalMsg(msg: BreakoutRoomCreatedInternalMsg, state: MeetingState2x): MeetingState2x = {
val updatedModel = for {
breakoutModel <- state.breakout
room <- breakoutModel.find(msg.breakoutId)
startedRoom = breakoutModel.started(room, System.currentTimeMillis())
} yield {
val updatedRoom = sendBreakoutRoomStarted(startedRoom)
var updatedModel = breakoutModel.update(updatedRoom)
// We postpone sending invitation until all breakout rooms have been created
if (updatedModel.hasAllStarted()) {
updatedModel = updatedModel.copy(startedOn = Some(System.currentTimeMillis()))
updatedModel = sendBreakoutRoomsList(updatedModel)
}
updatedModel
}
updatedModel match {
case Some(model) => state.update(Some(model))
case None => state
}
}
def buildBreakoutRoomsListEvtMsg(meetingId: String, rooms: Vector[BreakoutRoomInfo], roomsReady: Boolean): BbbCommonEnvCoreMsg = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, meetingId, "not-used")
val envelope = BbbCoreEnvelope(BreakoutRoomsListEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(BreakoutRoomsListEvtMsg.NAME, meetingId, "not-used")
val body = BreakoutRoomsListEvtMsgBody(meetingId, rooms, roomsReady)
val event = BreakoutRoomsListEvtMsg(header, body)
BbbCommonEnvCoreMsg(envelope, event)
}
def sendBreakoutRoomsList(breakoutModel: BreakoutModel): BreakoutModel = {
val breakoutRooms = breakoutModel.rooms.values.toVector map { r =>
val html5JoinUrls = for {
user <- r.assignedUsers
(redirectToHtml5JoinURL, redirectJoinURL) <- BreakoutHdlrHelpers.getRedirectUrls(liveMeeting, user, r.externalId, r.sequence.toString())
} yield (user -> redirectToHtml5JoinURL)
new BreakoutRoomInfo(r.name, r.externalId, r.id, r.sequence, r.shortName, r.isDefaultName, r.freeJoin, html5JoinUrls.toMap, r.captureNotes, r.captureSlides)
}
log.info("Sending breakout rooms list to {} with containing {} room(s)", liveMeeting.props.meetingProp.intId, breakoutRooms.length)
val msgEvent = buildBreakoutRoomsListEvtMsg(liveMeeting.props.meetingProp.intId, breakoutRooms, true)
outGW.send(msgEvent)
breakoutModel
}
def sendBreakoutRoomStarted(room: BreakoutRoom2x): BreakoutRoom2x = {
log.info("Sending breakout room started {} for parent meeting {} ", room.id, room.parentId)
def build(meetingId: String, breakout: BreakoutRoomInfo): BbbCommonEnvCoreMsg = {
val routing = Routing.addMsgToClientRouting(
MessageTypes.BROADCAST_TO_MEETING,
liveMeeting.props.meetingProp.intId, "not-used"
)
val envelope = BbbCoreEnvelope(BreakoutRoomStartedEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(BreakoutRoomStartedEvtMsg.NAME, liveMeeting.props.meetingProp.intId, "not-used")
val body = BreakoutRoomStartedEvtMsgBody(meetingId, breakout)
val event = BreakoutRoomStartedEvtMsg(header, body)
BbbCommonEnvCoreMsg(envelope, event)
}
val breakoutInfo = BreakoutRoomInfo(room.name, room.externalId, room.id, room.sequence, room.shortName, room.isDefaultName, room.freeJoin, Map(), room.captureNotes, room.captureSlides)
val event = build(liveMeeting.props.meetingProp.intId, breakoutInfo)
outGW.send(event)
room
}
}

View File

@ -0,0 +1,49 @@
package org.bigbluebutton.core.apps.breakout
import org.bigbluebutton.core.api.BreakoutRoomEndedInternalMsg
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter }
import org.bigbluebutton.core2.message.senders.MsgBuilder
trait BreakoutRoomEndedInternalMsgHdlr {
this: MeetingActor =>
val outGW: OutMsgRouter
def handleBreakoutRoomEndedInternalMsg(msg: BreakoutRoomEndedInternalMsg, state: MeetingState2x): MeetingState2x = {
log.info("Received breakout room ended. breakoutId={}", msg.meetingId)
// send out BreakoutRoomEndedEvtMsg to inform clients the breakout has ended
outGW.send(MsgBuilder.buildBreakoutRoomEndedEvtMsg(liveMeeting.props.meetingProp.intId, "not-used",
msg.meetingId))
val updatedModel = for {
breakoutModel <- state.breakout
} yield {
breakoutModel.removeRoom(msg.meetingId)
}
updatedModel match {
case Some(model) =>
if (model.rooms.isEmpty) {
// All breakout rooms have ended
val notifyEvent = MsgBuilder.buildNotifyAllInMeetingEvtMsg(
liveMeeting.props.meetingProp.intId,
"info",
"rooms",
"app.toast.breakoutRoomEnded",
"Message when the breakout room is ended",
Vector()
)
outGW.send(notifyEvent)
state.update(None)
} else {
state.update(Some(model))
}
case None =>
state
}
}
}

View File

@ -0,0 +1,60 @@
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.{ RegisteredUsers, Users2x }
import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter }
trait BreakoutRoomUsersUpdateMsgHdlr {
this: MeetingActor =>
val outGW: OutMsgRouter
def handleBreakoutRoomUsersUpdateInternalMsg(msg: BreakoutRoomUsersUpdateInternalMsg, state: MeetingState2x): MeetingState2x = {
def broadcastEvent(room: BreakoutRoom2x): BbbCommonEnvCoreMsg = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, props.meetingProp.intId, "not-used")
val envelope = BbbCoreEnvelope(UpdateBreakoutUsersEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(UpdateBreakoutUsersEvtMsg.NAME, props.meetingProp.intId, "not-used")
val users = room.users.map(u => BreakoutUserVO(u.id, u.name))
val body = UpdateBreakoutUsersEvtMsgBody(props.meetingProp.intId, msg.breakoutId, users)
val event = UpdateBreakoutUsersEvtMsg(header, body)
BbbCommonEnvCoreMsg(envelope, event)
}
val breakoutModel = for {
model <- state.breakout
room <- model.find(msg.breakoutId)
} yield {
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)
//Update lastBreakout in registeredUsers to avoid lose this info when the user leaves
for {
breakoutRoomUser <- updatedRoom.users
u <- RegisteredUsers.findWithBreakoutRoomId(breakoutRoomUser.id, liveMeeting.registeredUsers)
} yield {
if (room != null && (u.lastBreakoutRoom == null || u.lastBreakoutRoom.id != room.id)) {
RegisteredUsers.updateUserLastBreakoutRoom(liveMeeting.registeredUsers, u, room)
}
}
model.update(updatedRoom)
}
breakoutModel match {
case Some(model) => state.update(Some(model))
case None => state
}
}
}

View File

@ -0,0 +1,39 @@
package org.bigbluebutton.core.apps.breakout
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter }
trait BreakoutRoomsListMsgHdlr {
this: MeetingActor =>
val outGW: OutMsgRouter
def handleBreakoutRoomsListMsg(msg: BreakoutRoomsListMsg, state: MeetingState2x): MeetingState2x = {
def broadcastEvent(rooms: Vector[BreakoutRoomInfo], roomsReady: Boolean): Unit = {
log.info("Sending breakout rooms list to {} with containing {} room(s)", props.meetingProp.intId, rooms.length)
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, props.meetingProp.intId, msg.header.userId)
val envelope = BbbCoreEnvelope(BreakoutRoomsListEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(BreakoutRoomsListEvtMsg.NAME, props.meetingProp.intId, msg.header.userId)
val body = BreakoutRoomsListEvtMsgBody(msg.body.meetingId, rooms, roomsReady)
val event = BreakoutRoomsListEvtMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
outGW.send(msgEvent)
}
for {
breakoutModel <- state.breakout
} yield {
val rooms = breakoutModel.rooms.values.toVector map { r =>
new BreakoutRoomInfo(r.name, r.externalId, r.id, r.sequence, r.shortName, r.isDefaultName, r.freeJoin, Map(), r.captureNotes, r.captureSlides)
}
val ready = breakoutModel.hasAllStarted()
broadcastEvent(rooms, ready)
}
state
}
}

View File

@ -0,0 +1,81 @@
package org.bigbluebutton.core.apps.breakout
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.api.EjectUserFromBreakoutInternalMsg
import org.bigbluebutton.core.apps.breakout.BreakoutHdlrHelpers.{ getRedirectUrls }
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core.bus.BigBlueButtonEvent
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.models.{ EjectReasonCode }
import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter }
import org.bigbluebutton.core2.message.senders.{ MsgBuilder }
trait ChangeUserBreakoutReqMsgHdlr extends RightsManagementTrait {
this: MeetingActor =>
val outGW: OutMsgRouter
def handleChangeUserBreakoutReqMsg(msg: ChangeUserBreakoutReqMsg, 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 move user among breakout rooms."
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
state
} else {
val meetingId = liveMeeting.props.meetingProp.intId
for {
breakoutModel <- state.breakout
} yield {
//Eject user from room From
for {
roomFrom <- breakoutModel.rooms.get(msg.body.fromBreakoutId)
} yield {
roomFrom.users.filter(u => u.id == msg.body.userId + "-" + roomFrom.sequence).foreach(user => {
eventBus.publish(BigBlueButtonEvent(roomFrom.id, EjectUserFromBreakoutInternalMsg(meetingId, roomFrom.id, user.id, msg.header.userId, "User moved to another room", EjectReasonCode.EJECT_USER, false)))
})
}
//Get join URL for room To
val redirectToHtml5JoinURL = (
for {
roomTo <- breakoutModel.rooms.get(msg.body.toBreakoutId)
(redirectToHtml5JoinURL, redirectJoinURL) <- getRedirectUrls(liveMeeting, msg.body.userId, roomTo.externalId, roomTo.sequence.toString())
} yield redirectToHtml5JoinURL
).getOrElse("")
BreakoutHdlrHelpers.sendChangeUserBreakoutMsg(
outGW,
meetingId,
msg.body.userId,
msg.body.fromBreakoutId,
msg.body.toBreakoutId,
redirectToHtml5JoinURL,
)
//Send notification to moved User
for {
roomFrom <- breakoutModel.rooms.get(msg.body.fromBreakoutId)
roomTo <- breakoutModel.rooms.get(msg.body.toBreakoutId)
} yield {
val notifyUserEvent = MsgBuilder.buildNotifyUserInMeetingEvtMsg(
msg.body.userId,
liveMeeting.props.meetingProp.intId,
"info",
"promote",
"app.updateBreakoutRoom.userChangeRoomNotification",
"Notification to warn user was moved to another room",
Vector(roomTo.shortName)
)
outGW.send(notifyUserEvent)
}
}
state
}
}
}

View File

@ -0,0 +1,123 @@
package org.bigbluebutton.core.apps.breakout
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.apps.{ BreakoutModel, PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core.domain.{ BreakoutRoom2x, MeetingState2x }
import org.bigbluebutton.core.models.PresentationInPod
import org.bigbluebutton.core.running.{ LiveMeeting, OutMsgRouter }
import org.bigbluebutton.core.running.MeetingActor
trait CreateBreakoutRoomsCmdMsgHdlr extends RightsManagementTrait {
this: MeetingActor =>
val liveMeeting: LiveMeeting
val outGW: OutMsgRouter
def handleCreateBreakoutRoomsCmdMsg(msg: CreateBreakoutRoomsCmdMsg, state: MeetingState2x): MeetingState2x = {
if (liveMeeting.props.meetingProp.disabledFeatures.contains("breakoutRooms")) {
val meetingId = liveMeeting.props.meetingProp.intId
val reason = "Breakout rooms is disabled for this meeting."
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
state
} else if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId) || liveMeeting.props.meetingProp.isBreakout) {
val meetingId = liveMeeting.props.meetingProp.intId
val reason = "No permission to create breakout room for meeting."
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId,
reason, outGW, liveMeeting)
state
} else {
state.breakout match {
case Some(breakout) =>
log.warning(
"CreateBreakoutRooms event received while breakout created for meeting {}", liveMeeting.props.meetingProp.intId
)
state
case None =>
processRequest(msg, state)
}
}
}
def processRequest(msg: CreateBreakoutRoomsCmdMsg, state: MeetingState2x): MeetingState2x = {
val presId = getPresentationId(state)
val presSlide = getPresentationSlide(state)
val parentId = liveMeeting.props.meetingProp.intId
var rooms = new collection.immutable.HashMap[String, BreakoutRoom2x]
var i = 0
for (room <- msg.body.rooms) {
i += 1
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.shortName, room.isDefaultName, room.freeJoin, voiceConf, room.users, msg.body.captureNotes, msg.body.captureSlides)
rooms = rooms + (breakout.id -> breakout)
}
for (breakout <- rooms.values.toVector) {
val roomDetail = new BreakoutRoomDetail(
breakout.id, breakout.name,
liveMeeting.props.meetingProp.intId,
breakout.sequence,
breakout.shortName,
breakout.isDefaultName,
breakout.freeJoin,
liveMeeting.props.voiceProp.dialNumber,
breakout.voiceConf,
msg.body.durationInMinutes * 60,
liveMeeting.props.password.moderatorPass,
liveMeeting.props.password.viewerPass,
presId, presSlide, msg.body.record,
liveMeeting.props.breakoutProps.privateChatEnabled,
breakout.captureNotes,
breakout.captureSlides,
)
val event = buildCreateBreakoutRoomSysCmdMsg(liveMeeting.props.meetingProp.intId, roomDetail)
outGW.send(event)
}
val breakoutModel = new BreakoutModel(None, msg.body.durationInMinutes * 60, rooms)
state.update(Some(breakoutModel))
}
def buildCreateBreakoutRoomSysCmdMsg(meetingId: String, breakout: BreakoutRoomDetail): BbbCommonEnvCoreMsg = {
val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
val envelope = BbbCoreEnvelope(CreateBreakoutRoomSysCmdMsg.NAME, routing)
val header = BbbCoreBaseHeader(CreateBreakoutRoomSysCmdMsg.NAME)
val body = CreateBreakoutRoomSysCmdMsgBody(meetingId, breakout)
val event = CreateBreakoutRoomSysCmdMsg(header, body)
BbbCommonEnvCoreMsg(envelope, event)
}
def getPresentationId(state: MeetingState2x): String = {
// in very rare cases the presentation conversion generates an error, what should we do?
// those cases where default.pdf is deleted from the whiteboard
var currentPresentation = "blank"
for {
defaultPod <- state.presentationPodManager.getDefaultPod()
curPres <- defaultPod.getCurrentPresentation()
} yield {
currentPresentation = curPres.id
}
currentPresentation
}
def getPresentationSlide(state: MeetingState2x): Int = {
if (!liveMeeting.presModel.getCurrentPage().isEmpty) liveMeeting.presModel.getCurrentPage().get.num else 0
var currentSlide = 0
for {
defaultPod <- state.presentationPodManager.getDefaultPod()
curPres <- defaultPod.getCurrentPresentation()
curPage <- PresentationInPod.getCurrentPage(curPres)
} yield {
currentSlide = curPage.num
}
currentSlide
}
}

View File

@ -0,0 +1,40 @@
package org.bigbluebutton.core.apps.breakout
import org.bigbluebutton.core.api.EjectUserFromBreakoutInternalMsg
import org.bigbluebutton.core.apps.users.UsersApp
import org.bigbluebutton.core.models.{ RegisteredUsers }
import org.bigbluebutton.core.running.{ LiveMeeting, MeetingActor, OutMsgRouter }
import org.bigbluebutton.core2.message.senders.Sender
trait EjectUserFromBreakoutInternalMsgHdlr {
this: MeetingActor =>
val liveMeeting: LiveMeeting
val outGW: OutMsgRouter
def handleEjectUserFromBreakoutInternalMsgHdlr(msg: EjectUserFromBreakoutInternalMsg) = {
for {
registeredUser <- RegisteredUsers.findAllWithExternUserId(msg.extUserId, liveMeeting.registeredUsers)
} yield {
UsersApp.ejectUserFromMeeting(
outGW,
liveMeeting,
registeredUser.id,
msg.ejectedBy,
msg.reason,
msg.reasonCode,
msg.ban
)
// send a system message to force disconnection
Sender.sendDisconnectClientSysMsg(msg.breakoutId, registeredUser.id, msg.ejectedBy, msg.reasonCode, outGW)
//send users update to parent meeting
BreakoutHdlrHelpers.updateParentMeetingWithUsers(liveMeeting, eventBus)
log.info("Eject user {} id={} in breakoutId {}", registeredUser.name, registeredUser.id, msg.breakoutId)
}
}
}

View File

@ -0,0 +1,45 @@
package org.bigbluebutton.core.apps.breakout
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.api.EndBreakoutRoomInternalMsg
import org.bigbluebutton.core.bus.BigBlueButtonEvent
import org.bigbluebutton.core.domain.{ MeetingEndReason, MeetingState2x }
import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter }
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core2.message.senders.{ MsgBuilder }
trait EndAllBreakoutRoomsMsgHdlr extends RightsManagementTrait {
this: MeetingActor =>
val outGW: OutMsgRouter
def handleEndAllBreakoutRoomsMsg(msg: EndAllBreakoutRoomsMsg, state: MeetingState2x): MeetingState2x = {
val meetingId = liveMeeting.props.meetingProp.intId
if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
val reason = "No permission to end breakout rooms for meeting."
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
state
} else {
for {
model <- state.breakout
} yield {
model.rooms.values.foreach { room =>
eventBus.publish(BigBlueButtonEvent(room.id, EndBreakoutRoomInternalMsg(meetingId, room.id, MeetingEndReason.BREAKOUT_ENDED_BY_MOD)))
}
val notifyEvent = MsgBuilder.buildNotifyAllInMeetingEvtMsg(
meetingId,
"info",
"rooms",
"app.toast.breakoutRoomEnded",
"Message when the breakout room is ended",
Vector()
)
outGW.send(notifyEvent)
}
state.update(None)
}
}
}

View File

@ -0,0 +1,30 @@
package org.bigbluebutton.core.apps.breakout
import org.bigbluebutton.core.api.{ CaptureSharedNotesReqInternalMsg, CapturePresentationReqInternalMsg, EndBreakoutRoomInternalMsg }
import org.bigbluebutton.core.bus.{ BigBlueButtonEvent, InternalEventBus }
import org.bigbluebutton.core.running.{ BaseMeetingActor, HandlerHelpers, LiveMeeting, OutMsgRouter }
trait EndBreakoutRoomInternalMsgHdlr extends HandlerHelpers {
this: BaseMeetingActor =>
val liveMeeting: LiveMeeting
val outGW: OutMsgRouter
val eventBus: InternalEventBus
def handleEndBreakoutRoomInternalMsg(msg: EndBreakoutRoomInternalMsg): Unit = {
if (liveMeeting.props.breakoutProps.captureSlides) {
val captureSlidesEvent = BigBlueButtonEvent(msg.breakoutId, CapturePresentationReqInternalMsg("system", msg.parentId))
eventBus.publish(captureSlidesEvent)
}
if (liveMeeting.props.breakoutProps.captureNotes) {
val meetingName: String = liveMeeting.props.meetingProp.name
val captureNotesEvent = BigBlueButtonEvent(msg.breakoutId, CaptureSharedNotesReqInternalMsg(msg.parentId, meetingName))
eventBus.publish(captureNotesEvent)
}
log.info("Breakout room {} ended by parent meeting {}.", msg.breakoutId, msg.parentId)
sendEndMeetingDueToExpiry(msg.reason, eventBus, outGW, liveMeeting, "system")
}
}

View File

@ -0,0 +1,44 @@
package org.bigbluebutton.core.apps.breakout
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 =>
val outGW: OutMsgRouter
def handleRequestBreakoutJoinURLReqMsg(msg: RequestBreakoutJoinURLReqMsg, state: MeetingState2x): MeetingState2x = {
if (permissionFailed(PermissionCheck.GUEST_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
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)
} else {
for {
model <- state.breakout
room <- model.find(msg.body.breakoutId)
requesterUser <- Users2x.findWithIntId(liveMeeting.users2x, msg.header.userId)
} yield {
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)
}
}
}
state
}
}

View File

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

View File

@ -0,0 +1,18 @@
package org.bigbluebutton.core.apps.breakout
import org.bigbluebutton.core.api.{ SendBreakoutUsersAuditInternalMsg }
import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter }
trait SendBreakoutUsersUpdateMsgHdlr {
this: MeetingActor =>
val outGW: OutMsgRouter
def handleSendBreakoutUsersUpdateInternalMsg(msg: SendBreakoutUsersAuditInternalMsg): Unit = {
BreakoutHdlrHelpers.updateParentMeetingWithUsers(
liveMeeting,
eventBus
)
}
}

View File

@ -0,0 +1,63 @@
package org.bigbluebutton.core.apps.breakout
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.api.{ SendMessageToBreakoutRoomInternalMsg }
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core.bus.BigBlueButtonEvent
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.models.{ RegisteredUsers }
import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter }
import org.bigbluebutton.core2.message.senders.{ MsgBuilder }
trait SendMessageToAllBreakoutRoomsMsgHdlr extends RightsManagementTrait {
this: MeetingActor =>
val outGW: OutMsgRouter
def handleSendMessageToAllBreakoutRoomsMsg(msg: SendMessageToAllBreakoutRoomsReqMsg, 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 send message to all breakout rooms for meeting."
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
state
} else {
for {
breakoutModel <- state.breakout
senderUser <- RegisteredUsers.findWithUserId(msg.header.userId, liveMeeting.registeredUsers)
} yield {
breakoutModel.rooms.values.foreach { room =>
eventBus.publish(BigBlueButtonEvent(room.id, SendMessageToBreakoutRoomInternalMsg(props.breakoutProps.parentId, room.id, senderUser.name, msg.body.msg)))
}
val event = buildSendMessageToAllBreakoutRoomsEvtMsg(msg.header.userId, msg.body.msg, breakoutModel.rooms.size)
outGW.send(event)
val notifyModeratorEvent = MsgBuilder.buildNotifyUserInMeetingEvtMsg(
msg.header.userId,
liveMeeting.props.meetingProp.intId,
"info",
"group_chat",
"app.createBreakoutRoom.msgToBreakoutsSent",
"Message for chat sent successfully",
Vector(s"${breakoutModel.rooms.size}")
)
outGW.send(notifyModeratorEvent)
log.debug("Sending message '{}' to all breakout rooms in meeting {}", msg.body.msg, props.meetingProp.intId)
}
state
}
}
def buildSendMessageToAllBreakoutRoomsEvtMsg(senderId: String, msg: String, totalOfRooms: Int): BbbCommonEnvCoreMsg = {
val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
val envelope = BbbCoreEnvelope(SendMessageToAllBreakoutRoomsEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(SendMessageToAllBreakoutRoomsEvtMsg.NAME, liveMeeting.props.meetingProp.intId, "not-used")
val body = SendMessageToAllBreakoutRoomsEvtMsgBody(props.meetingProp.intId, senderId, msg, totalOfRooms)
val event = SendMessageToAllBreakoutRoomsEvtMsg(header, body)
BbbCommonEnvCoreMsg(envelope, event)
}
}

View File

@ -0,0 +1,40 @@
package org.bigbluebutton.core.apps.breakout
import org.bigbluebutton.common2.msgs.{ GroupChatAccess, GroupChatMsgFromUser }
import org.bigbluebutton.core.api.SendMessageToBreakoutRoomInternalMsg
import org.bigbluebutton.core.apps.groupchats.GroupChatApp
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.models.SystemUser
import org.bigbluebutton.core.running.{ LiveMeeting, MeetingActor, OutMsgRouter }
trait SendMessageToBreakoutRoomInternalMsgHdlr {
this: MeetingActor =>
val outGW: OutMsgRouter
def handleSendMessageToBreakoutRoomInternalMsg(msg: SendMessageToBreakoutRoomInternalMsg, state: MeetingState2x, liveMeeting: LiveMeeting, bus: MessageBus): MeetingState2x = {
val newState = for {
sender <- GroupChatApp.findGroupChatUser(SystemUser.ID, liveMeeting.users2x)
chat <- state.groupChats.find(GroupChatApp.MAIN_PUBLIC_CHAT)
} yield {
val groupChatMsgFromUser = GroupChatMsgFromUser(sender.id, sender.copy(name = msg.senderName), true, msg.msg)
val gcm = GroupChatApp.toGroupChatMessage(sender.copy(name = msg.senderName), groupChatMsgFromUser)
val gcs = GroupChatApp.addGroupChatMessage(chat, state.groupChats, gcm)
val event = buildGroupChatMessageBroadcastEvtMsg(
liveMeeting.props.meetingProp.intId,
msg.senderName, GroupChatApp.MAIN_PUBLIC_CHAT, gcm
)
bus.outGW.send(event)
state.update(gcs)
}
newState match {
case Some(ns) => ns
case None => state
}
}
}

View File

@ -0,0 +1,81 @@
package org.bigbluebutton.core.apps.breakout
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.apps.{ BreakoutModel, PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.models.VoiceUsers
import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter }
trait TransferUserToMeetingRequestHdlr extends RightsManagementTrait {
this: MeetingActor =>
val outGW: OutMsgRouter
def handleTransferUserToMeetingRequestMsg(msg: TransferUserToMeetingRequestMsg, 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 transfer user to voice breakout."
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
} else {
processRequest(msg)
}
state
}
def processRequest(msg: TransferUserToMeetingRequestMsg) {
if (msg.body.fromMeetingId == liveMeeting.props.meetingProp.intId) {
// want to transfer from parent meeting to breakout
for {
model <- state.breakout
to <- getVoiceConf(msg.body.toMeetingId, model)
from <- getVoiceConf(msg.body.fromMeetingId, model)
voiceUser <- VoiceUsers.findWithIntId(liveMeeting.voiceUsers, msg.body.userId)
} yield {
val event = buildTransferUserToVoiceConfSysMsg(from, to, voiceUser.voiceUserId)
outGW.send(event)
}
} else {
for {
model <- state.breakout
room <- model.find(msg.body.fromMeetingId)
} yield {
room.voiceUsers.foreach { vu =>
log.info(" ***** Breakout voice user={} userId={}", vu, msg.body.userId)
}
}
for {
model <- state.breakout
to <- getVoiceConf(msg.body.toMeetingId, model)
from <- getVoiceConf(msg.body.fromMeetingId, model)
room <- model.find(msg.body.fromMeetingId)
voiceUser <- room.voiceUsers.find(p => p.id == msg.body.userId)
} yield {
val event = buildTransferUserToVoiceConfSysMsg(from, to, voiceUser.voiceUserId)
outGW.send(event)
}
}
}
def buildTransferUserToVoiceConfSysMsg(fromVoiceConf: String, toVoiceConf: String, voiceUserId: String): BbbCommonEnvCoreMsg = {
val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
val envelope = BbbCoreEnvelope(TransferUserToVoiceConfSysMsg.NAME, routing)
val header = BbbCoreHeaderWithMeetingId(TransferUserToVoiceConfSysMsg.NAME, props.meetingProp.intId)
val body = TransferUserToVoiceConfSysMsgBody(fromVoiceConf, toVoiceConf, voiceUserId)
val event = TransferUserToVoiceConfSysMsg(header, body)
BbbCommonEnvCoreMsg(envelope, event)
}
def getVoiceConf(meetingId: String, breakoutModel: BreakoutModel): Option[String] = {
if (meetingId == liveMeeting.props.meetingProp.intId) {
Some(liveMeeting.props.voiceProp.voiceConf)
} else {
for {
room <- breakoutModel.find(meetingId)
} yield room.voiceConf
}
}
}

View File

@ -0,0 +1,27 @@
package org.bigbluebutton.core.apps.breakout
import org.bigbluebutton.core.api.{ UpdateBreakoutRoomTimeInternalMsg }
import org.bigbluebutton.core.domain.{ MeetingState2x }
import org.bigbluebutton.core.running.{ MeetingActor, OutMsgRouter }
trait UpdateBreakoutRoomTimeInternalMsgHdlr {
this: MeetingActor =>
val outGW: OutMsgRouter
def handleUpdateBreakoutRoomTimeInternalMsgHdlr(msg: UpdateBreakoutRoomTimeInternalMsg, state: MeetingState2x): MeetingState2x = {
val breakoutModel = for {
model <- state.breakout
} yield {
val updatedBreakoutModel = model.setTime(msg.durationInSeconds)
updatedBreakoutModel
}
breakoutModel match {
case Some(model) => state.update(Some(model))
case None => state
}
}
}

View File

@ -0,0 +1,106 @@
package org.bigbluebutton.core.apps.breakout
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.api.{ UpdateBreakoutRoomTimeInternalMsg, 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.core2.message.senders.{ MsgBuilder, Sender }
import org.bigbluebutton.core.util.TimeUtil
trait UpdateBreakoutRoomsTimeMsgHdlr extends RightsManagementTrait {
this: MeetingActor =>
val outGW: OutMsgRouter
def handleUpdateBreakoutRoomsTimeMsg(msg: UpdateBreakoutRoomsTimeReqMsg, 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 update time for breakout rooms for meeting."
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
state
} else if (msg.body.timeInMinutes <= 0) {
log.error("Error while trying to update {} minutes for breakout rooms time in meeting {}. Only positive values are allowed!", msg.body.timeInMinutes, props.meetingProp.intId)
state
} else {
val updatedModel = for {
breakoutModel <- state.breakout
startedOn <- breakoutModel.startedOn
} yield {
val newSecsRemaining = msg.body.timeInMinutes * 60
val breakoutRoomSecsElapsed = TimeUtil.millisToSeconds(System.currentTimeMillis()) - TimeUtil.millisToSeconds(startedOn);
val newDurationInSeconds = breakoutRoomSecsElapsed.toInt + newSecsRemaining
var isNewTimeHigherThanMeetingRemaining = 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 (newSecsRemaining > (mainRoomSecsRemaining - 5)) {
log.error("Error while trying to update {} minutes for breakout rooms time in meeting {}. Parent meeting will end up in {} minutes!", msg.body.timeInMinutes, props.meetingProp.intId, mainRoomTimeRemainingInMinutes)
isNewTimeHigherThanMeetingRemaining = true
}
}
if (isNewTimeHigherThanMeetingRemaining) {
breakoutModel
} else {
breakoutModel.rooms.values.foreach { room =>
eventBus.publish(BigBlueButtonEvent(room.id, UpdateBreakoutRoomTimeInternalMsg(props.breakoutProps.parentId, room.id, newDurationInSeconds)))
val notifyEvent = MsgBuilder.buildNotifyAllInMeetingEvtMsg(
room.id,
"info",
"about",
"app.chat.breakoutDurationUpdated",
"Used when the breakout duration is updated",
Vector(s"${msg.body.timeInMinutes}")
)
outGW.send(notifyEvent)
}
val notifyModeratorEvent = MsgBuilder.buildNotifyUserInMeetingEvtMsg(
msg.header.userId,
liveMeeting.props.meetingProp.intId,
"info",
"promote",
"app.chat.breakoutDurationUpdatedModerator",
"Sent to the moderator that requested breakout duration change",
Vector(s"${msg.body.timeInMinutes}")
)
outGW.send(notifyModeratorEvent)
log.debug("Updating {} minutes for breakout rooms time in meeting {}", msg.body.timeInMinutes, props.meetingProp.intId)
breakoutModel.setTime(newDurationInSeconds)
}
}
val event = buildUpdateBreakoutRoomsTimeEvtMsg(msg.body.timeInMinutes)
outGW.send(event)
//Force Update time remaining in the clients
eventBus.publish(BigBlueButtonEvent(props.meetingProp.intId, SendTimeRemainingAuditInternalMsg(props.meetingProp.intId, msg.body.timeInMinutes)))
updatedModel match {
case Some(model) => state.update(Some(model))
case None => state
}
}
}
def buildUpdateBreakoutRoomsTimeEvtMsg(timeInMinutes: Int): BbbCommonEnvCoreMsg = {
val routing = collection.immutable.HashMap("sender" -> "bbb-apps-akka")
val envelope = BbbCoreEnvelope(UpdateBreakoutRoomsTimeEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(UpdateBreakoutRoomsTimeEvtMsg.NAME, liveMeeting.props.meetingProp.intId, "not-used")
val body = UpdateBreakoutRoomsTimeEvtMsgBody(props.meetingProp.intId, timeInMinutes)
val event = UpdateBreakoutRoomsTimeEvtMsg(header, body)
BbbCommonEnvCoreMsg(envelope, event)
}
}

View File

@ -0,0 +1,118 @@
package org.bigbluebutton.core.apps.caption
import akka.actor.ActorContext
import akka.event.Logging
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.running.{ LiveMeeting }
import org.bigbluebutton.core.apps.{ PermissionCheck, RightsManagementTrait }
class CaptionApp2x(implicit val context: ActorContext) extends RightsManagementTrait {
val log = Logging(context.system, getClass)
def getCaptionHistory(liveMeeting: LiveMeeting): Map[String, TranscriptVO] = {
liveMeeting.captionModel.getHistory()
}
def updateCaptionOwner(liveMeeting: LiveMeeting, name: String, locale: String, userId: String): Map[String, TranscriptVO] = {
liveMeeting.captionModel.updateTranscriptOwner(name, locale, userId)
}
def editCaptionHistory(liveMeeting: LiveMeeting, userId: String, startIndex: Integer, endIndex: Integer, name: String, text: String): Boolean = {
liveMeeting.captionModel.editHistory(userId, startIndex, endIndex, name, text)
}
def checkCaptionOwnerLogOut(liveMeeting: LiveMeeting, userId: String): Option[(String, TranscriptVO)] = {
liveMeeting.captionModel.checkCaptionOwnerLogOut(userId)
}
def isUserCaptionOwner(liveMeeting: LiveMeeting, userId: String, name: String): Boolean = {
liveMeeting.captionModel.isUserCaptionOwner(userId, name)
}
def handle(msg: EditCaptionHistoryPubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
def broadcastEvent(msg: EditCaptionHistoryPubMsg): Unit = {
val routing = Routing.addMsgToClientRouting(
MessageTypes.BROADCAST_TO_MEETING,
liveMeeting.props.meetingProp.intId, msg.header.userId
)
val envelope = BbbCoreEnvelope(EditCaptionHistoryEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(EditCaptionHistoryEvtMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
val body = EditCaptionHistoryEvtMsgBody(msg.body.startIndex, msg.body.endIndex, msg.body.name, msg.body.locale, msg.body.text)
val event = EditCaptionHistoryEvtMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(msgEvent)
}
if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId)
&& isUserCaptionOwner(liveMeeting, msg.header.userId, msg.body.name)) {
val meetingId = liveMeeting.props.meetingProp.intId
val reason = "No permission to edit caption history in meeting."
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
} else {
val successfulEdit = editCaptionHistory(liveMeeting, msg.header.userId, msg.body.startIndex,
msg.body.endIndex, msg.body.name, msg.body.text)
if (successfulEdit) {
broadcastEvent(msg)
}
}
}
def handle(msg: SendCaptionHistoryReqMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
def broadcastEvent(msg: SendCaptionHistoryReqMsg, history: Map[String, TranscriptVO]): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, liveMeeting.props.meetingProp.intId, msg.header.userId)
val envelope = BbbCoreEnvelope(SendCaptionHistoryRespMsg.NAME, routing)
val header = BbbClientMsgHeader(SendCaptionHistoryRespMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
val body = SendCaptionHistoryRespMsgBody(history)
val event = SendCaptionHistoryRespMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(msgEvent)
}
broadcastEvent(msg, getCaptionHistory(liveMeeting))
}
def handle(msg: UpdateCaptionOwnerPubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
def broadcastUpdateCaptionOwnerEvent(name: String, locale: String, newOwnerId: String): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, newOwnerId)
val envelope = BbbCoreEnvelope(UpdateCaptionOwnerEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(UpdateCaptionOwnerEvtMsg.NAME, liveMeeting.props.meetingProp.intId, newOwnerId)
val body = UpdateCaptionOwnerEvtMsgBody(name, locale, newOwnerId)
val event = UpdateCaptionOwnerEvtMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(msgEvent)
}
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 caption owners."
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
} else {
updateCaptionOwner(liveMeeting, msg.body.name, msg.body.locale, msg.body.ownerId).foreach(f => {
broadcastUpdateCaptionOwnerEvent(f._1, f._2.locale, f._2.ownerId)
})
}
}
def handleUserLeavingMsg(userId: String, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
def broadcastUpdateCaptionOwnerEvent(name: String, locale: String, newOwnerId: String): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, newOwnerId)
val envelope = BbbCoreEnvelope(UpdateCaptionOwnerEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(UpdateCaptionOwnerEvtMsg.NAME, liveMeeting.props.meetingProp.intId, newOwnerId)
val body = UpdateCaptionOwnerEvtMsgBody(name, locale, newOwnerId)
val event = UpdateCaptionOwnerEvtMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(msgEvent)
}
for {
transcriptInfo <- checkCaptionOwnerLogOut(liveMeeting, userId)
} yield {
broadcastUpdateCaptionOwnerEvent(transcriptInfo._1, transcriptInfo._2.locale, transcriptInfo._2.ownerId)
}
}
}

View File

@ -0,0 +1,12 @@
package org.bigbluebutton.core.apps.chat
import akka.actor.ActorContext
class ChatApp2x(implicit val context: ActorContext)
extends GetChatHistoryReqMsgHdlr
with SendPublicMessagePubMsgHdlr
with SendPrivateMessagePubMsgHdlr
with ClearPublicChatHistoryPubMsgHdlr
with UserTypingPubMsgHdlr {
}

View File

@ -0,0 +1,47 @@
package org.bigbluebutton.core.apps.chat
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.apps.{ ChatModel, PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.running.{ LiveMeeting, LogHelper }
import org.bigbluebutton.core.domain.MeetingState2x
trait ClearPublicChatHistoryPubMsgHdlr extends LogHelper with RightsManagementTrait {
def handle(msg: ClearPublicChatHistoryPubMsg, state: MeetingState2x,
liveMeeting: LiveMeeting, bus: MessageBus): MeetingState2x = {
def broadcastEvent(msg: ClearPublicChatHistoryPubMsg): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, msg.header.userId)
val envelope = BbbCoreEnvelope(ClearPublicChatHistoryEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(ClearPublicChatHistoryEvtMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
val body = ClearPublicChatHistoryEvtMsgBody(msg.body.chatId)
val event = ClearPublicChatHistoryEvtMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(msgEvent)
}
if (permissionFailed(PermissionCheck.MOD_LEVEL, PermissionCheck.VIEWER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
val meetingId = liveMeeting.props.meetingProp.intId
val reason = "No permission to clear chat in meeting."
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
state
} else {
ChatModel.clearPublicChatHistory(liveMeeting.chatModel)
val newState = for {
gc <- state.groupChats.find(msg.body.chatId)
} yield {
broadcastEvent(msg)
val newGc = gc.clearMessages()
val gcs = state.groupChats.update(newGc)
state.update(gcs)
}
newState match {
case Some(ns) => ns
case None => state
}
}
}
}

View File

@ -0,0 +1,27 @@
package org.bigbluebutton.core.apps.chat
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.apps.ChatModel
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.running.{ LiveMeeting, LogHelper }
trait GetChatHistoryReqMsgHdlr extends LogHelper {
def handle(msg: GetChatHistoryReqMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
log.debug(" GOT CHAT HISTORY")
def broadcastEvent(msg: GetChatHistoryReqMsg, history: Array[ChatMessageVO]): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, liveMeeting.props.meetingProp.intId, msg.header.userId)
val envelope = BbbCoreEnvelope(GetChatHistoryRespMsg.NAME, routing)
val header = BbbClientMsgHeader(GetChatHistoryRespMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
val body = GetChatHistoryRespMsgBody(history)
val event = GetChatHistoryRespMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(msgEvent)
}
val history = ChatModel.getChatHistory(liveMeeting.chatModel)
broadcastEvent(msg, history)
}
}

View File

@ -0,0 +1,26 @@
package org.bigbluebutton.core.apps.chat
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.running.{ LiveMeeting, LogHelper }
trait SendPrivateMessagePubMsgHdlr extends LogHelper {
def handle(msg: SendPrivateMessagePubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
log.debug("SENDING PRIVATE CHAT MESSAGE")
def broadcastEvent(message: ChatMessageVO, userId: String): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, liveMeeting.props.meetingProp.intId, userId)
val envelope = BbbCoreEnvelope(SendPrivateMessageEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(SendPrivateMessageEvtMsg.NAME, liveMeeting.props.meetingProp.intId, userId)
val body = SendPrivateMessageEvtMsgBody(message)
val event = SendPrivateMessageEvtMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(msgEvent)
}
broadcastEvent(msg.body.message, msg.body.message.fromUserId)
broadcastEvent(msg.body.message, msg.body.message.toUserId)
}
}

View File

@ -0,0 +1,27 @@
package org.bigbluebutton.core.apps.chat
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.apps.ChatModel
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.running.{ LiveMeeting, LogHelper }
trait SendPublicMessagePubMsgHdlr extends LogHelper {
def handle(msg: SendPublicMessagePubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
log.debug("SENDING CHAT MESSAGE")
def broadcastEvent(msg: SendPublicMessagePubMsg, message: ChatMessageVO): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, msg.header.userId)
val envelope = BbbCoreEnvelope(SendPublicMessageEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(SendPublicMessageEvtMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
val body = SendPublicMessageEvtMsgBody(msg.body.message)
val event = SendPublicMessageEvtMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(msgEvent)
}
ChatModel.addNewChatMessage(liveMeeting.chatModel, msg.body.message)
broadcastEvent(msg, msg.body.message)
}
}

View File

@ -0,0 +1,21 @@
package org.bigbluebutton.core.apps.chat
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.running.{ LiveMeeting, LogHelper }
trait UserTypingPubMsgHdlr extends LogHelper {
def handle(msg: UserTypingPubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
def broadcastEvent(msg: UserTypingPubMsg): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, msg.header.userId)
val envelope = BbbCoreEnvelope(UserTypingEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(UserTypingEvtMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
val body = UserTypingEvtMsgBody(msg.body.chatId, msg.header.userId)
val event = UserTypingEvtMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(msgEvent)
}
broadcastEvent(msg)
}
}

View File

@ -0,0 +1,13 @@
package org.bigbluebutton.core.apps.externalvideo
import akka.actor.ActorContext
import akka.event.Logging
class ExternalVideoApp2x(implicit val context: ActorContext)
extends StartExternalVideoPubMsgHdlr
with UpdateExternalVideoPubMsgHdlr
with StopExternalVideoPubMsgHdlr {
val log = Logging(context.system, getClass)
}

View File

@ -0,0 +1,45 @@
package org.bigbluebutton.core.apps.externalvideo
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.apps.{ ExternalVideoModel, PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.running.LiveMeeting
import org.bigbluebutton.core.apps.screenshare.ScreenshareApp2x.{ requestBroadcastStop }
trait StartExternalVideoPubMsgHdlr extends RightsManagementTrait {
this: ExternalVideoApp2x =>
def handle(msg: StartExternalVideoPubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
log.info("Received StartExternalVideoPubMsgr meetingId={} url={}", liveMeeting.props.meetingProp.intId, msg.body.externalVideoUrl)
def broadcastEvent(msg: StartExternalVideoPubMsg) {
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, liveMeeting.props.meetingProp.intId, "nodeJSapp")
val envelope = BbbCoreEnvelope(StartExternalVideoEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(StartExternalVideoEvtMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
val body = StartExternalVideoEvtMsgBody(msg.body.externalVideoUrl)
val event = StartExternalVideoEvtMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(msgEvent)
}
if (liveMeeting.props.meetingProp.disabledFeatures.contains("externalVideos")) {
val meetingId = liveMeeting.props.meetingProp.intId
val reason = "External Videos is disabled for this meeting."
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
} else 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 {
// Request a screen broadcast stop (goes to SFU, comes back through
// ScreenshareRtmpBroadcastStoppedVoiceConfEvtMsg)
requestBroadcastStop(bus.outGW, liveMeeting)
ExternalVideoModel.setURL(liveMeeting.externalVideoModel, msg.body.externalVideoUrl)
broadcastEvent(msg)
}
}
}

View File

@ -0,0 +1,27 @@
package org.bigbluebutton.core.apps.externalvideo
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.apps.{ ExternalVideoModel, PermissionCheck, RightsManagementTrait }
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.running.LiveMeeting
import org.bigbluebutton.core2.message.senders.MsgBuilder
trait StopExternalVideoPubMsgHdlr extends RightsManagementTrait {
this: ExternalVideoApp2x =>
def handle(msg: StopExternalVideoPubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
log.info("Received StopExternalVideoPubMsgr meetingId={}", liveMeeting.props.meetingProp.intId)
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
val msgEvent = MsgBuilder.buildStopExternalVideoEvtMsg(liveMeeting.props.meetingProp.intId, msg.header.userId)
bus.outGW.send(msgEvent)
}
}
}

View File

@ -0,0 +1,30 @@
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 extends RightsManagementTrait {
def handle(msg: UpdateExternalVideoPubMsg, liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
def broadcastEvent(msg: UpdateExternalVideoPubMsg) {
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, liveMeeting.props.meetingProp.intId, "nodeJSapp")
val envelope = BbbCoreEnvelope(UpdateExternalVideoEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(UpdateExternalVideoEvtMsg.NAME, liveMeeting.props.meetingProp.intId, msg.header.userId)
val body = UpdateExternalVideoEvtMsgBody(msg.body.status, msg.body.rate, msg.body.time, msg.body.state)
val event = UpdateExternalVideoEvtMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(msgEvent)
}
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)
}
}
}

View File

@ -0,0 +1,5 @@
package org.bigbluebutton.core.apps.groupchats
trait ChangeGroupChatAccessReqMsgHdlr {
}

View File

@ -0,0 +1,38 @@
package org.bigbluebutton.core.apps.groupchats
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.models.GroupChat
import org.bigbluebutton.core.running.LiveMeeting
import org.bigbluebutton.core.models.SystemUser
trait CreateDefaultPublicGroupChat {
this: GroupChatHdlrs =>
def handleCreateDefaultPublicGroupChat(state: MeetingState2x, liveMeeting: LiveMeeting, bus: MessageBus): MeetingState2x = {
val groupChat: GroupChat = GroupChatApp.createDefaultPublicGroupChat()
def buildGroupChatCreatedEvtMsg(meetingId: String, userId: String, gc: GroupChat): BbbCommonEnvCoreMsg = {
val correlationId = "SYSTEM-" + System.currentTimeMillis()
val msgs = gc.msgs.map(m => GroupChatApp.toMessageToUser(m))
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, meetingId, userId)
val envelope = BbbCoreEnvelope(GroupChatCreatedEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(GroupChatCreatedEvtMsg.NAME, meetingId, userId)
val body = GroupChatCreatedEvtMsgBody(correlationId, gc.id, gc.createdBy, gc.access, gc.users, msgs)
val event = GroupChatCreatedEvtMsg(header, body)
BbbCommonEnvCoreMsg(envelope, event)
}
val respMsg = buildGroupChatCreatedEvtMsg(
liveMeeting.props.meetingProp.intId,
SystemUser.ID,
groupChat
)
bus.outGW.send(respMsg)
val groupChats = state.groupChats.add(groupChat)
state.update(groupChats)
}
}

View File

@ -0,0 +1,130 @@
package org.bigbluebutton.core.apps.groupchats
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.models.GroupChat
import org.bigbluebutton.core.running.LiveMeeting
import org.bigbluebutton.core.apps.PermissionCheck
import org.bigbluebutton.SystemConfiguration
import org.bigbluebutton.core.models.Users2x
import org.bigbluebutton.core.models.Roles
import org.bigbluebutton.core2.MeetingStatus2x
trait CreateGroupChatReqMsgHdlr extends SystemConfiguration {
this: GroupChatHdlrs =>
def handle(msg: CreateGroupChatReqMsg, state: MeetingState2x,
liveMeeting: LiveMeeting, bus: MessageBus): MeetingState2x = {
log.debug("RECEIVED CREATE CHAT REQ MESSAGE")
var chatLocked: Boolean = false
for {
user <- Users2x.findWithIntId(liveMeeting.users2x, msg.header.userId)
} yield {
if (user.role != Roles.MODERATOR_ROLE) {
if (msg.body.access == GroupChatAccess.PRIVATE) {
val permissions = MeetingStatus2x.getPermissions(liveMeeting.status)
val modMembers = msg.body.users.filter(userId => Users2x.findWithIntId(liveMeeting.users2x, userId) match {
case Some(user) => user.role == Roles.MODERATOR_ROLE
case None => false
})
// don't lock creation of private chats that involve a moderator
if (modMembers.length == 0) {
chatLocked = user.locked && permissions.disablePrivChat
}
} else {
chatLocked = true
}
}
}
// Check if this message was sent while the lock settings was being changed.
val isDelayedMessage = System.currentTimeMillis() - MeetingStatus2x.getPermissionsChangedOn(liveMeeting.status) < 5000
if (applyPermissionCheck && chatLocked && !isDelayedMessage) {
val meetingId = liveMeeting.props.meetingProp.intId
val reason = "No permission to create a new group chat."
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
state
} else {
val newState = for {
createdBy <- GroupChatApp.findGroupChatUser(msg.header.userId, liveMeeting.users2x)
} yield {
val msgs = msg.body.msg.map(m => GroupChatApp.toGroupChatMessage(createdBy, m))
val users = {
if (msg.body.access == GroupChatAccess.PRIVATE) {
val cu = msg.body.users.toSet + msg.header.userId
cu.flatMap(u => GroupChatApp.findGroupChatUser(u, liveMeeting.users2x)).toVector
} else {
Vector.empty
}
}
val gc = GroupChatApp.createGroupChat(msg.body.access, createdBy, users, msgs)
sendMessages(msg, gc, liveMeeting, bus)
val groupChats = state.groupChats.add(gc)
state.update(groupChats)
}
newState.getOrElse(state)
}
}
def sendMessages(msg: CreateGroupChatReqMsg, gc: GroupChat,
liveMeeting: LiveMeeting, bus: MessageBus): Unit = {
def makeHeader(name: String, meetingId: String, userId: String): BbbClientMsgHeader = {
BbbClientMsgHeader(name, meetingId, userId)
}
def makeEnvelope(msgType: String, name: String, meetingId: String, userId: String): BbbCoreEnvelope = {
val routing = Routing.addMsgToClientRouting(msgType, meetingId, userId)
BbbCoreEnvelope(name, routing)
}
def makeBody(
chatId: String,
access: String, correlationId: String,
createdBy: GroupChatUser, users: Vector[GroupChatUser],
msgs: Vector[GroupChatMsgToUser]
): GroupChatCreatedEvtMsgBody = {
GroupChatCreatedEvtMsgBody(correlationId, chatId, createdBy,
access, users, msgs)
}
val meetingId = liveMeeting.props.meetingProp.intId
val correlationId = msg.body.correlationId
val users = gc.users
val msgs = gc.msgs.map(m => GroupChatApp.toMessageToUser(m))
if (gc.access == GroupChatAccess.PRIVATE) {
def sendDirectMessage(userId: String): Unit = {
val envelope = makeEnvelope(MessageTypes.DIRECT, GroupChatCreatedEvtMsg.NAME, meetingId, userId)
val header = makeHeader(GroupChatCreatedEvtMsg.NAME, meetingId, userId)
val body = makeBody(gc.id, gc.access, correlationId, gc.createdBy, users, msgs)
val event = GroupChatCreatedEvtMsg(header, body)
val outEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(outEvent)
}
users.foreach(u => sendDirectMessage(u.id))
} else {
val meetingId = liveMeeting.props.meetingProp.intId
val userId = msg.header.userId
val envelope = makeEnvelope(MessageTypes.BROADCAST_TO_MEETING, GroupChatCreatedEvtMsg.NAME,
meetingId, userId)
val header = makeHeader(GroupChatCreatedEvtMsg.NAME, meetingId, userId)
val body = makeBody(gc.id, gc.access, correlationId, gc.createdBy, users, msgs)
val event = GroupChatCreatedEvtMsg(header, body)
val outEvent = BbbCommonEnvCoreMsg(envelope, event)
bus.outGW.send(outEvent)
}
}
}

View File

@ -0,0 +1,5 @@
package org.bigbluebutton.core.apps.groupchats
trait DestroyGroupChatReqMsgHdlr {
}

View File

@ -0,0 +1,38 @@
package org.bigbluebutton.core.apps.groupchats
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.running.LiveMeeting
trait GetGroupChatMsgsReqMsgHdlr {
def handle(msg: GetGroupChatMsgsReqMsg, state: MeetingState2x,
liveMeeting: LiveMeeting, bus: MessageBus): MeetingState2x = {
def buildGetGroupChatMsgsRespMsg(meetingId: String, userId: String,
msgs: Vector[GroupChatMsgToUser], chatId: String): BbbCommonEnvCoreMsg = {
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, meetingId, userId)
val envelope = BbbCoreEnvelope(GetGroupChatMsgsRespMsg.NAME, routing)
val header = BbbClientMsgHeader(GetGroupChatMsgsRespMsg.NAME, meetingId, userId)
val body = GetGroupChatMsgsRespMsgBody(chatId, msgs)
val event = GetGroupChatMsgsRespMsg(header, body)
BbbCommonEnvCoreMsg(envelope, event)
}
state.groupChats.find(msg.body.chatId) foreach { gc =>
if (gc.access == GroupChatAccess.PUBLIC || gc.isUserMemberOf(msg.header.userId)) {
val msgs = gc.msgs.toVector map (m => GroupChatMsgToUser(m.id, m.createdOn, m.correlationId,
m.sender, m.chatEmphasizedText, m.message))
val respMsg = buildGetGroupChatMsgsRespMsg(
liveMeeting.props.meetingProp.intId,
msg.header.userId, msgs, gc.id
)
bus.outGW.send(respMsg)
}
}
state
}
}

View File

@ -0,0 +1,41 @@
package org.bigbluebutton.core.apps.groupchats
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.running.LiveMeeting
trait GetGroupChatsReqMsgHdlr {
this: GroupChatHdlrs =>
def handle(msg: GetGroupChatsReqMsg, state: MeetingState2x,
liveMeeting: LiveMeeting, bus: MessageBus): MeetingState2x = {
log.debug("RECEIVED GetGroupChatsReqMsg")
def buildGetGroupChatsRespMsg(meetingId: String, userId: String,
allChats: Vector[GroupChatInfo]): BbbCommonEnvCoreMsg = {
val routing = Routing.addMsgToClientRouting(MessageTypes.DIRECT, meetingId, userId)
val envelope = BbbCoreEnvelope(GetGroupChatsRespMsg.NAME, routing)
val header = BbbClientMsgHeader(GetGroupChatsRespMsg.NAME, meetingId, userId)
val body = GetGroupChatsRespMsgBody(allChats)
val event = GetGroupChatsRespMsg(header, body)
BbbCommonEnvCoreMsg(envelope, event)
}
val publicChats = state.groupChats.findAllPublicChats()
val privateChats = state.groupChats.findAllPrivateChatsForUser(msg.header.userId)
val pubChats = publicChats map (pc => GroupChatInfo(pc.id, pc.access, pc.createdBy, pc.users))
val privChats = privateChats map (pc => GroupChatInfo(pc.id, pc.access, pc.createdBy, pc.users))
val allChats = pubChats ++ privChats
val respMsg = buildGetGroupChatsRespMsg(liveMeeting.props.meetingProp.intId, msg.header.userId, allChats)
bus.outGW.send(respMsg)
state
}
}

View File

@ -0,0 +1,93 @@
package org.bigbluebutton.core.apps.groupchats
import org.bigbluebutton.common2.msgs.{ GroupChatAccess, GroupChatMsgFromUser, GroupChatMsgToUser, GroupChatUser }
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.models._
import org.bigbluebutton.core.running.LiveMeeting
object GroupChatApp {
val MAIN_PUBLIC_CHAT = "MAIN-PUBLIC-GROUP-CHAT"
def createGroupChat(access: String, createBy: GroupChatUser,
users: Vector[GroupChatUser], msgs: Vector[GroupChatMessage]): GroupChat = {
val gcId = GroupChatFactory.genId()
GroupChatFactory.create(gcId, access, createBy, users, msgs)
}
def toGroupChatMessage(sender: GroupChatUser, msg: GroupChatMsgFromUser): GroupChatMessage = {
val now = System.currentTimeMillis()
val id = GroupChatFactory.genId()
GroupChatMessage(id, now, msg.correlationId, now, now, sender, msg.chatEmphasizedText, msg.message)
}
def toMessageToUser(msg: GroupChatMessage): GroupChatMsgToUser = {
GroupChatMsgToUser(id = msg.id, timestamp = msg.timestamp, correlationId = msg.correlationId,
sender = msg.sender, chatEmphasizedText = msg.chatEmphasizedText, message = msg.message)
}
def addGroupChatMessage(chat: GroupChat, chats: GroupChats,
msg: GroupChatMessage): GroupChats = {
val c = chat.add(msg)
chats.update(c)
}
def findGroupChatUser(userId: String, users: Users2x): Option[GroupChatUser] = {
Users2x.findWithIntId(users, userId) match {
case Some(u) => Some(GroupChatUser(u.intId, u.name, u.role))
case None =>
if (userId == SystemUser.ID) {
Some(GroupChatUser(SystemUser.ID))
} else {
None
}
}
}
def createDefaultPublicGroupChat(): GroupChat = {
val createBy = GroupChatUser(SystemUser.ID)
GroupChatFactory.create(MAIN_PUBLIC_CHAT, GroupChatAccess.PUBLIC, createBy, Vector.empty, Vector.empty)
}
def createTestPublicGroupChat(state: MeetingState2x): MeetingState2x = {
val createBy = GroupChatUser(SystemUser.ID)
val defaultPubGroupChat = GroupChatFactory.create(
"TEST_GROUP_CHAT",
GroupChatAccess.PUBLIC, createBy, Vector.empty, Vector.empty
)
val groupChats = state.groupChats.add(defaultPubGroupChat)
state.update(groupChats)
}
def getAllGroupChatsInMeeting(state: MeetingState2x): Vector[GroupChat] = {
state.groupChats.getAllGroupChatsInMeeting()
}
def genTestChatMsgHistory(chatId: String, state: MeetingState2x, userId: String, liveMeeting: LiveMeeting): MeetingState2x = {
def addH(state: MeetingState2x, userId: String, liveMeeting: LiveMeeting, msg: GroupChatMsgFromUser): MeetingState2x = {
val newState = for {
sender <- GroupChatApp.findGroupChatUser(userId, liveMeeting.users2x)
chat <- state.groupChats.find(chatId)
} yield {
val gcm1 = GroupChatApp.toGroupChatMessage(sender, msg)
val gcs1 = GroupChatApp.addGroupChatMessage(chat, state.groupChats, gcm1)
state.update(gcs1)
}
newState match {
case Some(ns) => ns
case None => state
}
}
val sender = GroupChatUser(SystemUser.ID)
val h1 = GroupChatMsgFromUser(correlationId = "cor1", sender = sender, message = "Hello Foo!")
val h2 = GroupChatMsgFromUser(correlationId = "cor2", sender = sender, message = "Hello Bar!")
val h3 = GroupChatMsgFromUser(correlationId = "cor3", sender = sender, message = "Hello Baz!")
val state1 = addH(state, SystemUser.ID, liveMeeting, h1)
val state2 = addH(state1, SystemUser.ID, liveMeeting, h2)
val state3 = addH(state2, SystemUser.ID, liveMeeting, h3)
state3
}
}

View File

@ -0,0 +1,5 @@
package org.bigbluebutton.core.apps.groupchats
trait GroupChatAddUserReqMsgHdlr {
}

View File

@ -0,0 +1,15 @@
package org.bigbluebutton.core.apps.groupchats
import akka.actor.ActorContext
import akka.event.Logging
class GroupChatHdlrs(implicit val context: ActorContext)
extends CreateGroupChatReqMsgHdlr
with CreateDefaultPublicGroupChat
with GetGroupChatMsgsReqMsgHdlr
with GetGroupChatsReqMsgHdlr
with SendGroupChatMessageMsgHdlr
with SyncGetGroupChatsInfoMsgHdlr {
val log = Logging(context.system, getClass)
}

View File

@ -0,0 +1,5 @@
package org.bigbluebutton.core.apps.groupchats
trait GroupChatRemoveUserReqMsgHdlr {
}

View File

@ -0,0 +1,15 @@
package org.bigbluebutton.core.apps.groupchats
import org.bigbluebutton.common2.msgs.{ OpenGroupChatWindowReqMsg }
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.running.LiveMeeting
trait OpenGroupChatWindowReqMsgHdlr {
this: GroupChatHdlrs =>
def handle(msg: OpenGroupChatWindowReqMsg, state: MeetingState2x,
liveMeeting: LiveMeeting, bus: MessageBus): MeetingState2x = {
state
}
}

View File

@ -0,0 +1,77 @@
package org.bigbluebutton.core.apps.groupchats
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.apps.PermissionCheck
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.running.{ HandlerHelpers, LiveMeeting }
import org.bigbluebutton.core.models.Users2x
import org.bigbluebutton.core2.MeetingStatus2x
import org.bigbluebutton.core.models.Roles
trait SendGroupChatMessageMsgHdlr extends HandlerHelpers {
this: GroupChatHdlrs =>
def handle(msg: SendGroupChatMessageMsg, state: MeetingState2x,
liveMeeting: LiveMeeting, bus: MessageBus): MeetingState2x = {
val chatDisabled: Boolean = liveMeeting.props.meetingProp.disabledFeatures.contains("chat")
var chatLocked: Boolean = false
for {
user <- Users2x.findWithIntId(liveMeeting.users2x, msg.header.userId)
groupChat <- state.groupChats.find(msg.body.chatId)
} yield {
if (user.role != Roles.MODERATOR_ROLE && user.locked) {
val permissions = MeetingStatus2x.getPermissions(liveMeeting.status)
if (groupChat.access == GroupChatAccess.PRIVATE) {
val modMembers = groupChat.users.filter(cu => Users2x.findWithIntId(liveMeeting.users2x, cu.id) match {
case Some(user) => user.role == Roles.MODERATOR_ROLE
case None => false
})
// don't lock private chats that involve a moderator
if (modMembers.length == 0) {
chatLocked = permissions.disablePrivChat
}
} else {
chatLocked = permissions.disablePubChat
}
}
}
if (!chatDisabled && !(applyPermissionCheck && chatLocked)) {
val newState = for {
sender <- GroupChatApp.findGroupChatUser(msg.header.userId, liveMeeting.users2x)
chat <- state.groupChats.find(msg.body.chatId)
} yield {
val chatIsPrivate = chat.access == GroupChatAccess.PRIVATE;
val userIsAParticipant = chat.users.filter(u => u.id == sender.id).length > 0;
if ((chatIsPrivate && userIsAParticipant) || !chatIsPrivate) {
val gcm = GroupChatApp.toGroupChatMessage(sender, msg.body.msg)
val gcs = GroupChatApp.addGroupChatMessage(chat, state.groupChats, gcm)
val event = buildGroupChatMessageBroadcastEvtMsg(
liveMeeting.props.meetingProp.intId,
msg.header.userId, msg.body.chatId, gcm
)
bus.outGW.send(event)
state.update(gcs)
} else {
val reason = "User isn't a participant of the chat"
PermissionCheck.ejectUserForFailedPermission(msg.header.meetingId, msg.header.userId, reason, bus.outGW, liveMeeting)
state
}
}
newState match {
case Some(ns) => ns
case None => state
}
} else { state }
}
}

View File

@ -0,0 +1,53 @@
package org.bigbluebutton.core.apps.groupchats
import org.bigbluebutton.common2.msgs._
import org.bigbluebutton.core.bus.MessageBus
import org.bigbluebutton.core.domain.MeetingState2x
import org.bigbluebutton.core.running.LiveMeeting
trait SyncGetGroupChatsInfoMsgHdlr {
this: GroupChatHdlrs =>
def handleSyncGetGroupChatsInfo(state: MeetingState2x, liveMeeting: LiveMeeting, bus: MessageBus): MeetingState2x = {
def buildSyncGetGroupChatsRespMsg(allChats: Vector[GroupChatInfo]): BbbCommonEnvCoreMsg = {
val routing = Routing.addMsgToHtml5InstanceIdRouting(liveMeeting.props.meetingProp.intId, liveMeeting.props.systemProps.html5InstanceId.toString)
val envelope = BbbCoreEnvelope(SyncGetGroupChatsRespMsg.NAME, routing)
val header = BbbClientMsgHeader(SyncGetGroupChatsRespMsg.NAME, liveMeeting.props.meetingProp.intId, "nodeJSapp")
val body = SyncGetGroupChatsRespMsgBody(allChats)
val event = SyncGetGroupChatsRespMsg(header, body)
BbbCommonEnvCoreMsg(envelope, event)
}
def buildSyncGetGroupChatMsgsRespMsg(msgs: Vector[GroupChatMsgToUser], chatId: String): BbbCommonEnvCoreMsg = {
val routing = Routing.addMsgToHtml5InstanceIdRouting(liveMeeting.props.meetingProp.intId, liveMeeting.props.systemProps.html5InstanceId.toString)
val envelope = BbbCoreEnvelope(SyncGetGroupChatMsgsRespMsg.NAME, routing)
val header = BbbClientMsgHeader(SyncGetGroupChatMsgsRespMsg.NAME, liveMeeting.props.meetingProp.intId, "nodeJSapp")
val body = SyncGetGroupChatMsgsRespMsgBody(chatId, msgs)
val event = SyncGetGroupChatMsgsRespMsg(header, body)
BbbCommonEnvCoreMsg(envelope, event)
}
// fetching all the group chats in the meeting
val chats = GroupChatApp.getAllGroupChatsInMeeting(state)
// mapping group chats, while fetching and publishing messages for each group chat
val allChats = chats map (pc => {
val msgs = pc.msgs.toVector map (m => GroupChatMsgToUser(m.id, m.createdOn, m.correlationId,
m.sender, m.chatEmphasizedText, m.message))
val respMsg = buildSyncGetGroupChatMsgsRespMsg(msgs, pc.id)
bus.outGW.send(respMsg)
GroupChatInfo(pc.id, pc.access, pc.createdBy, pc.users)
})
// publishing a message with the group chat info
val respMsg = buildSyncGetGroupChatsRespMsg(allChats)
bus.outGW.send(respMsg)
state
}
}

View File

@ -0,0 +1,63 @@
package org.bigbluebutton.core.apps.layout
import org.bigbluebutton.common2.msgs._
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 }
trait BroadcastLayoutMsgHdlr extends RightsManagementTrait {
this: LayoutApp2x =>
val outGW: OutMsgRouter
def handleBroadcastLayoutMsg(msg: BroadcastLayoutMsg): Unit = {
if (liveMeeting.props.meetingProp.disabledFeatures.contains("layouts")) {
val meetingId = liveMeeting.props.meetingProp.intId
val reason = "Layouts is disabled for this meeting."
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
} else if (permissionFailed(PermissionCheck.GUEST_LEVEL, PermissionCheck.PRESENTER_LEVEL, liveMeeting.users2x, msg.header.userId)) {
val meetingId = liveMeeting.props.meetingProp.intId
val reason = "No permission to broadcast layout to meeting."
PermissionCheck.ejectUserForFailedPermission(meetingId, msg.header.userId, reason, outGW, liveMeeting)
} else {
if (LayoutsType.layoutsType.contains(msg.body.layout)) {
val newlayout = LayoutsType.layoutsType.getOrElse(msg.body.layout, "")
Layouts.setCurrentLayout(liveMeeting.layouts, newlayout)
Layouts.setPushLayout(liveMeeting.layouts, msg.body.pushLayout)
Layouts.setPresentationIsOpen(liveMeeting.layouts, msg.body.presentationIsOpen)
Layouts.setCameraDockIsResizing(liveMeeting.layouts, msg.body.isResizing)
Layouts.setCameraPosition(liveMeeting.layouts, msg.body.cameraPosition)
Layouts.setFocusedCamera(liveMeeting.layouts, msg.body.focusedCamera)
Layouts.setPresentationVideoRate(liveMeeting.layouts, msg.body.presentationVideoRate)
Layouts.setRequestedBy(liveMeeting.layouts, msg.header.userId)
sendBroadcastLayoutEvtMsg(msg.header.userId)
}
}
}
def sendBroadcastLayoutEvtMsg(fromUserId: String): Unit = {
val routing = Routing.addMsgToClientRouting(MessageTypes.BROADCAST_TO_MEETING, liveMeeting.props.meetingProp.intId, fromUserId)
val envelope = BbbCoreEnvelope(BroadcastLayoutEvtMsg.NAME, routing)
val header = BbbClientMsgHeader(BroadcastLayoutEvtMsg.NAME, liveMeeting.props.meetingProp.intId, fromUserId)
val body = BroadcastLayoutEvtMsgBody(
Layouts.getCurrentLayout(liveMeeting.layouts),
Layouts.getPushLayout(liveMeeting.layouts),
Layouts.getPresentationIsOpen(liveMeeting.layouts),
Layouts.getCameraDockIsResizing(liveMeeting.layouts),
Layouts.getCameraPosition(liveMeeting.layouts),
Layouts.getFocusedCamera(liveMeeting.layouts),
Layouts.getPresentationVideoRate(liveMeeting.layouts),
MeetingStatus2x.getPermissions(liveMeeting.status).lockedLayout,
Layouts.getLayoutSetter(liveMeeting.layouts)
)
val event = BroadcastLayoutEvtMsg(header, body)
val msgEvent = BbbCommonEnvCoreMsg(envelope, event)
outGW.send(msgEvent)
}
}

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