Merge remote-tracking branch 'upstream/v2.6.x-release' into fix-tomcat-warnings

This commit is contained in:
GuiLeme 2022-07-05 16:15:05 -03:00
commit 4b4bbc4d42
25 changed files with 454 additions and 163 deletions

View File

@ -0,0 +1,65 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2017 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.core.record.events
import org.bigbluebutton.common2.domain.SimpleVoteOutVO
import scala.collection.immutable.List
import scala.collection.Map
import scala.collection.mutable.ArrayBuffer
import spray.json._
import DefaultJsonProtocol._
class AddTldrawShapeWhiteboardRecordEvent extends AbstractWhiteboardRecordEvent {
import AddTldrawShapeWhiteboardRecordEvent._
implicit object AnyJsonFormat extends JsonFormat[Any] {
def write(x: Any) = x match {
case n: Int => JsNumber(n)
case s: String => JsString(s)
case d: Double => JsNumber(d)
case m: scala.collection.immutable.Map[String, _] => mapFormat[String, Any].write(m)
case l: List[_] => listFormat[Any].write(l)
case b: Boolean if b == true => JsTrue
case b: Boolean if b == false => JsFalse
}
def read(value: JsValue) = {}
}
setEvent("AddTldrawShapeEvent")
def setUserId(id: String) {
eventMap.put(USER_ID, id)
}
def setAnnotationId(id: String) {
eventMap.put(SHAPE_ID, id)
}
def addAnnotation(annotation: scala.collection.immutable.Map[String, Any]) {
eventMap.put(SHAPE_DATA, annotation.toJson.compactPrint)
}
}
object AddTldrawShapeWhiteboardRecordEvent {
protected final val USER_ID = "userId"
protected final val SHAPE_ID = "shapeId"
protected final val SHAPE_DATA = "shapeData"
}

View File

@ -0,0 +1,39 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2017 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.core.record.events
class DeleteTldrawShapeRecordEvent extends AbstractWhiteboardRecordEvent {
import DeleteTldrawShapeRecordEvent._
setEvent("DeleteTldrawShapeEvent")
def setUserId(userId: String) {
eventMap.put(USER_ID, userId)
}
def setShapeId(shapeId: String) {
eventMap.put(SHAPE_ID, shapeId)
}
}
object DeleteTldrawShapeRecordEvent {
protected final val USER_ID = "userId"
protected final val SHAPE_ID = "shapeId"
}

View File

@ -32,23 +32,28 @@ class ResizeAndMoveSlideRecordEvent extends AbstractPresentationRecordEvent {
eventMap.put(ID, id)
}
def setXCamera(xCamera: Double) {
eventMap.put(X_CAMERA, xCamera.toString)
def setXOffset(offset: Double) {
eventMap.put(X_OFFSET, offset.toString)
}
def setYCamera(yCamera: Double) {
eventMap.put(Y_CAMERA, yCamera.toString)
def setYOffset(offset: Double) {
eventMap.put(Y_OFFSET, offset.toString)
}
def setZoom(zoom: Double) {
eventMap.put(ZOOM, zoom.toString)
def setWidthRatio(ratio: Double) {
eventMap.put(WIDTH_RATIO, ratio.toString)
}
def setHeightRatio(ratio: Double) {
eventMap.put(HEIGHT_RATIO, ratio.toString)
}
}
object ResizeAndMoveSlideRecordEvent {
protected final val PRES_NAME = "presentationName"
protected final val ID = "id"
protected final val X_CAMERA = "xCamera"
protected final val Y_CAMERA = "yCamera"
protected final val ZOOM = "zoom"
protected final val X_OFFSET = "xOffset"
protected final val Y_OFFSET = "yOffset"
protected final val WIDTH_RATIO = "widthRatio"
protected final val HEIGHT_RATIO = "heightRatio"
}

View File

@ -0,0 +1,54 @@
/**
* BigBlueButton open source conferencing system - http://www.bigbluebutton.org/
*
* Copyright (c) 2017 BigBlueButton Inc. and by respective authors (see below).
*
* This program is free software; you can redistribute it and/or modify it under the
* terms of the GNU Lesser General Public License as published by the Free Software
* Foundation; either version 3.0 of the License, or (at your option) any later
* version.
*
* BigBlueButton is distributed in the hope that it will be useful, but WITHOUT ANY
* WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License along
* with BigBlueButton; if not, see <http://www.gnu.org/licenses/>.
*
*/
package org.bigbluebutton.core.record.events
class TldrawCameraChangedRecordEvent extends AbstractPresentationRecordEvent {
import TldrawCameraChangedRecordEvent._
setEvent("TldrawCameraChangedEvent")
def setPresentationName(name: String) {
eventMap.put(PRES_NAME, name)
}
def setId(id: String) {
eventMap.put(ID, id)
}
def setXCamera(xCamera: Double) {
eventMap.put(X_CAMERA, xCamera.toString)
}
def setYCamera(yCamera: Double) {
eventMap.put(Y_CAMERA, yCamera.toString)
}
def setZoom(zoom: Double) {
eventMap.put(ZOOM, zoom.toString)
}
}
object TldrawCameraChangedRecordEvent {
protected final val PRES_NAME = "presentationName"
protected final val ID = "id"
protected final val X_CAMERA = "xCamera"
protected final val Y_CAMERA = "yCamera"
protected final val ZOOM = "zoom"
}

View File

@ -184,7 +184,7 @@ class RedisRecorderActor(
}
private def handleResizeAndMovePageEvtMsg(msg: ResizeAndMovePageEvtMsg) {
val ev = new ResizeAndMoveSlideRecordEvent()
val ev = new TldrawCameraChangedRecordEvent()
ev.setMeetingId(msg.header.meetingId)
ev.setPodId(msg.body.podId)
ev.setPresentationName(msg.body.presentationId)
@ -280,7 +280,7 @@ class RedisRecorderActor(
private def handleSendWhiteboardAnnotationsEvtMsg(msg: SendWhiteboardAnnotationsEvtMsg) {
msg.body.annotations.foreach(annotation => {
val ev = new AddShapeWhiteboardRecordEvent()
val ev = new AddTldrawShapeWhiteboardRecordEvent()
ev.setMeetingId(msg.header.meetingId)
ev.setPresentation(getPresentationId(annotation.wbId))
ev.setPageNumber(getPageNum(annotation.wbId))
@ -320,7 +320,7 @@ class RedisRecorderActor(
private def handleDeleteWhiteboardAnnotationsEvtMsg(msg: DeleteWhiteboardAnnotationsEvtMsg) {
msg.body.annotationsIds.foreach(annotationId => {
val ev = new UndoAnnotationRecordEvent()
val ev = new DeleteTldrawShapeRecordEvent()
ev.setMeetingId(msg.header.meetingId)
ev.setPresentation(getPresentationId(msg.body.whiteboardId))
ev.setPageNumber(getPageNum(msg.body.whiteboardId))

View File

@ -1 +1 @@
git clone --branch v2.9.0-alpha.3 --depth 1 https://github.com/bigbluebutton/bbb-webrtc-sfu bbb-webrtc-sfu
git clone --branch v2.9.0-alpha.4 --depth 1 https://github.com/bigbluebutton/bbb-webrtc-sfu bbb-webrtc-sfu

View File

@ -1 +1 @@
BIGBLUEBUTTON_RELEASE=2.6.0-alpha.1
BIGBLUEBUTTON_RELEASE=2.6.0-alpha.2

View File

@ -5,7 +5,7 @@
meteor-base@1.5.1
mobile-experience@1.1.0
mongo@1.14.6
mongo@1.15.0
reactive-var@1.0.11
standard-minifier-css@1.8.1

View File

@ -1 +1 @@
METEOR@2.7.1
METEOR@2.7.3

View File

@ -1,10 +1,10 @@
allow-deny@1.1.1
autoupdate@1.8.0
babel-compiler@7.9.0
babel-runtime@1.5.0
babel-runtime@1.5.1
base64@1.0.12
binary-heap@1.0.11
blaze-tools@1.1.2
blaze-tools@1.1.3
boilerplate-generator@1.7.1
caching-compiler@1.2.2
caching-html-compiler@1.2.1
@ -25,7 +25,7 @@ es5-shim@4.8.0
fetch@0.1.1
geojson-utils@1.0.10
hot-code-push@1.0.4
html-tools@1.1.2
html-tools@1.1.3
htmljs@1.1.1
http@2.0.0
id-map@1.1.1
@ -43,11 +43,11 @@ minifier-js@2.7.4
minimongo@1.8.0
mobile-experience@1.1.0
mobile-status-bar@1.1.0
modern-browsers@0.1.7
modern-browsers@0.1.8
modules@0.18.0
modules-runtime@0.13.0
mongo@1.14.6
mongo-decimal@0.1.2
mongo@1.15.0
mongo-decimal@0.1.3
mongo-dev-server@1.1.0
mongo-id@1.0.8
npm-mongo@4.3.1
@ -55,7 +55,7 @@ ordered-dict@1.1.0
promise@0.12.0
random@1.2.0
react-fast-refresh@0.2.3
react-meteor-data@2.4.0
react-meteor-data@2.5.1
reactive-dict@1.3.0
reactive-var@1.0.11
reload@1.3.1
@ -64,12 +64,12 @@ rocketchat:streamer@1.1.0
routepolicy@1.1.1
session@1.2.0
shell-server@0.5.0
socket-stream-client@0.4.0
spacebars-compiler@1.3.0
socket-stream-client@0.5.0
spacebars-compiler@1.3.1
standard-minifier-css@1.8.1
standard-minifier-js@2.8.0
static-html@1.3.2
templating-tools@1.2.1
templating-tools@1.2.2
tracker@1.2.0
typescript@4.5.4
underscore@1.0.10

View File

@ -347,6 +347,7 @@ class InputStreamLiveSelector extends Component {
currentOutputDeviceId,
isListenOnly,
isRTL,
shortcuts,
} = this.props;
const inputDeviceList = !isListenOnly
@ -379,33 +380,43 @@ class InputStreamLiveSelector extends Component {
const dropdownListComplete = inputDeviceList.concat(outputDeviceList).concat(leaveAudioOption);
return (
<BBBMenu
trigger={(
<>
{isListenOnly
? this.renderListenOnlyButton()
: this.renderMuteToggleButton()}
<Styled.AudioDropdown
emoji="device_list_selector"
label={intl.formatMessage(intlMessages.changeAudioDevice)}
hideLabel
tabIndex={0}
rotate
/>
</>
)}
actions={dropdownListComplete}
opts={{
id: 'default-dropdown-menu',
keepMounted: true,
transitionDuration: 0,
elevation: 3,
getContentAnchorEl: null,
fullwidth: 'true',
anchorOrigin: { vertical: 'top', horizontal: isRTL ? 'left' : 'right' },
transformOrigin: { vertical: 'bottom', horizontal: isRTL ? 'right' : 'left' },
}}
/>
<>
{!isListenOnly ? (
<span
style={{ display: 'none' }}
accessKey={shortcuts.leaveaudio}
onClick={() => handleLeaveAudio()}
aria-hidden="true"
/>
) : null}
<BBBMenu
trigger={(
<>
{isListenOnly
? this.renderListenOnlyButton()
: this.renderMuteToggleButton()}
<Styled.AudioDropdown
emoji="device_list_selector"
label={intl.formatMessage(intlMessages.changeAudioDevice)}
hideLabel
tabIndex={0}
rotate
/>
</>
)}
actions={dropdownListComplete}
opts={{
id: 'default-dropdown-menu',
keepMounted: true,
transitionDuration: 0,
elevation: 3,
getContentAnchorEl: null,
fullwidth: 'true',
anchorOrigin: { vertical: 'top', horizontal: isRTL ? 'left' : 'right' },
transformOrigin: { vertical: 'bottom', horizontal: isRTL ? 'right' : 'left' },
}}
/>
</>
);
}
@ -434,4 +445,4 @@ InputStreamLiveSelector.propTypes = propTypes;
InputStreamLiveSelector.defaultProps = defaultProps;
export default withShortcutHelper(injectIntl(InputStreamLiveSelector),
['leaveAudio']);
['leaveAudio', 'toggleMute']);

View File

@ -24,6 +24,8 @@ const propTypes = {
hideLabel: PropTypes.bool,
className: PropTypes.string,
rotate: PropTypes.bool,
};
const defaultProps = {
@ -35,6 +37,7 @@ const defaultProps = {
hideLabel: false,
onClick: null,
className: '',
rotate: false,
};
const ButtonEmoji = (props) => {
@ -42,6 +45,7 @@ const ButtonEmoji = (props) => {
hideLabel,
className,
hidden,
rotate,
...newProps
} = props;
@ -52,7 +56,7 @@ const ButtonEmoji = (props) => {
onClick,
} = newProps;
const IconComponent = (<Styled.EmojiButtonIcon iconName={emoji} />);
const IconComponent = (<Styled.EmojiButtonIcon iconName={emoji} rotate={rotate} />);
return (
<span>

View File

@ -65,15 +65,6 @@ const EmojiButton = styled.button`
margin-top: 40%;
color: ${btnDefaultColor};
}
${({ rotate }) => rotate && `
span {
i {
transform: rotate(180deg);
margin-top: 20%;
}
}
`}
`;
const EmojiButtonSpace = styled.div`

View File

@ -2,26 +2,31 @@ import React, { memo } from 'react';
import PropTypes from 'prop-types';
import cx from 'classnames';
import _ from 'lodash';
import Styled from './styles';
const propTypes = {
iconName: PropTypes.string.isRequired,
prependIconName: PropTypes.string,
rotate: PropTypes.bool,
};
const defaultProps = {
prependIconName: 'icon-bbb-',
rotate: false,
};
const Icon = ({
className,
prependIconName,
iconName,
rotate,
...props
}) => (
<i
<Styled.Icon
className={cx(className, [prependIconName, iconName].join(''))}
// ToastContainer from react-toastify passes a useless closeToast prop here
{..._.omit(props, ['closeToast', 'animations', 'rotate'])}
{..._.omit(props, ['closeToast', 'animations'])}
$rotate={rotate}
/>
);

View File

@ -0,0 +1,12 @@
import styled from 'styled-components';
const Icon = styled.i`
${({ $rotate }) => $rotate && `
transform: rotate(180deg);
margin-top: 20%;
`}
`;
export default {
Icon,
};

View File

@ -80,7 +80,7 @@ const PositionLabel = (props) => {
isMultiUserActive,
} = props;
const { name, color, userId, presenter } = currentUser;
const { name, color } = currentUser;
const prevCurrentPoint = usePrevious(currentPoint);
React.useEffect(() => {
@ -127,8 +127,8 @@ export default function Cursors(props) {
const end = () => {
publishCursorUpdate({
xPercent: null,
yPercent: null,
xPercent: -1.0,
yPercent: -1.0,
whiteboardId: whiteboardId,
});
setActive(false);

View File

@ -15,6 +15,7 @@ import {
MODELS,
getVirtualBgImagePath,
} from '/imports/ui/services/virtual-background/service'
import logger from '/imports/startup/client/logger';
const blurValue = '25px';
@ -195,9 +196,23 @@ class VirtualBackgroundService {
* @returns {void}
*/
_renderMask() {
this.resizeSource();
this.runInference();
this.runPostProcessing();
try {
this.resizeSource();
this.runInference();
this.runPostProcessing();
} catch (error) {
// TODO This is a high frequency log so that's why it's debug level.
// Should be reviewed later when the actual problem with runPostProcessing
// throwing on stalled pages/iframes - prlanzarin Jun 30 2022
logger.debug({
logCode: 'virtualbg_renderMask_failure',
extraInfo: {
errorMessage: error.message,
errorCode: error.code,
errorName: error.name,
},
}, `Virtual background renderMask failed: ${error.message || error.name}`);
}
this._maskFrameTimerWorker.postMessage({
id: SET_TIMEOUT,

View File

@ -3595,24 +3595,6 @@
"integrity": "sha1-YTObbyR1/Hcv2cnYP1yFddwVSuE=",
"dev": true
},
"is-extendable": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/is-extendable/-/is-extendable-1.0.1.tgz",
"integrity": "sha512-arnXMxT1hhoKo9k1LZdmlNyJdDDfy2v0fXjFlmok4+i8ul/6WlbVge9bhM74OpNPQPMGUToDtz+KXa1PneJxOA==",
"requires": {
"is-plain-object": "^2.0.4"
},
"dependencies": {
"is-plain-object": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz",
"integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==",
"requires": {
"isobject": "^3.0.1"
}
}
}
},
"is-extglob": {
"version": "2.1.1",
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
@ -3733,11 +3715,6 @@
"integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=",
"dev": true
},
"isobject": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz",
"integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8="
},
"isstream": {
"version": "0.1.2",
"resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz",
@ -5337,14 +5314,6 @@
}
}
},
"object.omit": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/object.omit/-/object.omit-3.0.0.tgz",
"integrity": "sha512-EO+BCv6LJfu+gBIF3ggLicFebFLN5zqzz/WWJlMFfkMyGth+oBkhxzDl0wx2W4GkLzuQs/FsSkXZb2IMWQqmBQ==",
"requires": {
"is-extendable": "^1.0.0"
}
},
"object.values": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.5.tgz",
@ -5913,16 +5882,6 @@
"tinycolor2": "^1.4.1"
}
},
"react-cursor-position": {
"version": "3.0.3",
"resolved": "https://registry.npmjs.org/react-cursor-position/-/react-cursor-position-3.0.3.tgz",
"integrity": "sha512-jrmHFKQtfNdvfJ5hIH+FOb2h9+mgcD8/POOY7LngmsYCJNlg6IYdnGdbMGMTeyue/iUvY+t20t20RDrH+qW5dw==",
"requires": {
"object-assign": "^4.1.1",
"object.omit": "^3.0.0",
"prop-types": "^15.6.0"
}
},
"react-dom": {
"version": "17.0.2",
"resolved": "https://registry.npmjs.org/react-dom/-/react-dom-17.0.2.tgz",

View File

@ -59,7 +59,7 @@ source /etc/lsb-release
# Set up specific version of node
if [ "$DISTRIB_CODENAME" == "focal" ]; then
node_version="14.19.1"
node_version="14.19.3"
if [[ ! -d /usr/share/node-v${node_version}-linux-x64 ]]; then
cd /usr/share
tar xfz "node-v${node_version}-linux-x64.tar.gz"

View File

@ -92,11 +92,11 @@ cp bbb-html5-frontend@.service staging/usr/lib/systemd/system
mkdir -p staging/usr/share
if [ ! -f node-v14.19.1-linux-x64.tar.gz ]; then
wget https://nodejs.org/dist/v14.19.1/node-v14.19.1-linux-x64.tar.gz
if [ ! -f node-v14.19.3-linux-x64.tar.gz ]; then
wget https://nodejs.org/dist/v14.19.3/node-v14.19.3-linux-x64.tar.gz
fi
cp node-v14.19.1-linux-x64.tar.gz staging/usr/share
cp node-v14.19.3-linux-x64.tar.gz staging/usr/share
if [ -f staging/usr/share/meteor/bundle/programs/web.browser/head.html ]; then
sed -i "s/VERSION/$(($BUILD))/" staging/usr/share/meteor/bundle/programs/web.browser/head.html

View File

@ -49,7 +49,7 @@ fi
export MONGO_OPLOG_URL=mongodb://127.0.1.1/local
export MONGO_URL=mongodb://127.0.1.1/meteor
export NODE_ENV=production
export NODE_VERSION=node-v14.19.1-linux-x64
export NODE_VERSION=node-v14.19.3-linux-x64
export SERVER_WEBSOCKET_COMPRESSION=0
export BIND_IP=127.0.0.1
PORT=$PORT /usr/share/$NODE_VERSION/bin/node --max-old-space-size=2048 --max_semi_space_size=128 main.js NODEJS_BACKEND_INSTANCE_ID=$INSTANCE_ID

View File

@ -49,7 +49,7 @@ fi
export MONGO_OPLOG_URL=mongodb://127.0.1.1/local
export MONGO_URL=mongodb://127.0.1.1/meteor
export NODE_ENV=production
export NODE_VERSION=node-v14.19.1-linux-x64
export NODE_VERSION=node-v14.19.3-linux-x64
export SERVER_WEBSOCKET_COMPRESSION=0
export BIND_IP=127.0.0.1
PORT=$PORT /usr/share/$NODE_VERSION/bin/node --max-old-space-size=2048 --max_semi_space_size=128 main.js

View File

@ -960,7 +960,7 @@ module BigBlueButton
# The following events are considered to indicate that the presentation
# area was actively used during the session.
when 'AddShapeEvent', 'ModifyTextEvent', 'UndoShapeEvent',
'ClearPageEvent'
'ClearPageEvent', 'AddTldrawShapeEvent', 'DeleteTldrawShapeEvent'
BigBlueButton.logger.debug("Seen a #{event['eventname']} event, presentation area used.")
return true
# We ignore the first SharePresentationEvent, since it's the default
@ -1093,5 +1093,11 @@ module BigBlueButton
return false
end
# Check if doc has tldraw events
def self.check_for_tldraw_events(events)
return !(events.xpath("recording/event[@eventname='TldrawCameraChangedEvent']").empty? &&
events.xpath("recording/event[@eventname='AddTldrawShapeEvent']").empty?)
end
end
end
end

View File

@ -192,6 +192,10 @@ unless FileTest.directory?(target_dir)
# Copy thumbnails from raw files
FileUtils.cp_r("#{pres_dir}/thumbnails", "#{target_pres_dir}/thumbnails") if File.exist?("#{pres_dir}/thumbnails")
tldraw = BigBlueButton::Events.check_for_tldraw_events(@doc);
if (tldraw)
FileUtils.cp_r("#{pres_dir}/svgs", "#{target_pres_dir}/svgs") if File.exist?("#{pres_dir}/svgs")
end
end
BigBlueButton.logger.info('Generating closed captions')

View File

@ -382,6 +382,30 @@ def svg_render_shape_poll(g, slide, shape)
width: width, height: height, x: x, y: y)
end
def build_tldraw_shape(image_shapes, slide, shape)
shape_in = shape[:in]
shape_out = shape[:out]
if shape_in == shape_out
BigBlueButton.logger.info("Draw #{shape[:shape_id]} Shape #{shape[:shape_unique_id]} is never shown (duration rounds to 0)")
return
end
if (shape_in >= slide[:out]) || (!shape[:out].nil? && shape[:out] <= slide[:in])
BigBlueButton.logger.info("Draw #{shape[:shape_id]} Shape #{shape[:shape_unique_id]} is not visible during image time span")
return
end
tldraw_shape = {
id: shape[:shape_id],
timestamp: shape_in,
undo: (shape[:undo].nil? ? -1 : shape[:undo]),
shape_data: shape[:shape_data]
}
image_shapes.push(tldraw_shape)
end
def svg_render_shape(canvas, slide, shape, image_id)
shape_in = shape[:in]
shape_out = shape[:out]
@ -426,7 +450,7 @@ def svg_render_shape(canvas, slide, shape, image_id)
end
@svg_image_id = 1
def svg_render_image(svg, slide, shapes)
def svg_render_image(svg, slide, shapes, tldraw, tldraw_shapes)
slide_number = slide[:slide]
presentation = slide[:presentation]
slide_in = slide[:in]
@ -458,15 +482,25 @@ def svg_render_image(svg, slide, shapes)
shapes = shapes[presentation][slide_number]
canvas = doc.create_element('g',
class: 'canvas', id: "canvas#{image_id}",
image: "image#{image_id}", display: 'none')
if !tldraw
canvas = doc.create_element('g',
class: 'canvas', id: "canvas#{image_id}",
image: "image#{image_id}", display: 'none')
shapes.each do |shape|
svg_render_shape(canvas, slide, shape, image_id)
shapes.each do |shape|
svg_render_shape(canvas, slide, shape, image_id)
end
svg << canvas unless canvas.element_children.empty?
else
image_shapes = []
shapes.each do |shape|
build_tldraw_shape(image_shapes, slide, shape)
end
tldraw_shapes[image_id] = { :shapes=>image_shapes, :timestamp=> slide_in}
end
svg << canvas unless canvas.element_children.empty?
end
def panzoom_viewbox(panzoom)
@ -483,13 +517,19 @@ def panzoom_viewbox(panzoom)
[x, y, w, h]
end
def panzooms_emit_event(rec, panzoom)
def panzooms_emit_event(rec, panzoom, tldraw)
panzoom_in = panzoom[:in]
return if panzoom_in == panzoom[:out]
rec.event(timestamp: panzoom_in) do
x, y, w, h = panzoom_viewbox(panzoom)
rec.viewBox("#{x} #{y} #{w} #{h}")
if !tldraw
rec.event(timestamp: panzoom_in) do
x, y, w, h = panzoom_viewbox(panzoom)
rec.viewBox("#{x} #{y} #{w} #{h}")
end
else
rec.event(timestamp: panzoom_in) do
rec.cameraAndZoom("#{panzoom[:x_camera]} #{panzoom[:y_camera]} #{panzoom[:zoom]}")
end
end
end
@ -497,14 +537,14 @@ def convert_cursor_coordinate(cursor_coord, panzoom_offset, panzoom_ratio)
(((cursor_coord / 100.0) + (panzoom_offset * MAGIC_MYSTERY_NUMBER / 100.0)) / (panzoom_ratio / 100.0)).round(5)
end
def cursors_emit_event(rec, cursor)
def cursors_emit_event(rec, cursor, tldraw)
cursor_in = cursor[:in]
return if cursor_in == cursor[:out]
rec.event(timestamp: cursor_in) do
panzoom = cursor[:panzoom]
if cursor[:visible]
if @version_atleast_2_0_0
if @version_atleast_2_0_0 && !tldraw
# In BBB 2.0, the cursor now uses the same coordinate system as annotations
# Use the panzoom information to convert it to be relative to viewbox
x = convert_cursor_coordinate(cursor[:x], panzoom[:x_offset], panzoom[:width_ratio])
@ -535,6 +575,53 @@ def determine_slide_number(slide, current_slide)
slide
end
def events_parse_tldraw_shape(shapes, event, current_presentation, current_slide, timestamp)
presentation = event.at_xpath('presentation')
slide = event.at_xpath('pageNumber')
presentation = determine_presentation(presentation, current_presentation)
slide = determine_slide_number(slide, current_slide)
# Set up the shapes data structures if needed
shapes[presentation] ||= {}
shapes[presentation][slide] ||= []
# We only need to deal with shapes for this slide
shapes = shapes[presentation][slide]
# Set up the structure for this shape
shape = {}
# Common properties
shape[:in] = timestamp
shape_data = shape[:shape_data] = JSON.parse(event.at_xpath('shapeData'))
user_id = event.at_xpath('userId')&.text
shape[:user_id] = user_id if user_id
shape_id = event.at_xpath('shapeId')&.text
shape[:id] = shape_id if shape_id
draw_id = shape[:shape_id] = @svg_shape_id
@svg_shape_id += 1
# Find the previous shape, for updates
prev_shape = nil
if shape_id
# If we have a shape ID, look up the previous shape by ID
prev_shape_pos = shapes.rindex { |s| s[:shade_id] == shape_id }
prev_shape = prev_shape_pos ? shapes[prev_shape_pos] : nil
end
if prev_shape
prev_shape[:out] = timestamp
shape[:shape_unique_id] = prev_shape[:shape_unique_id]
else
shape[:shape_unique_id] = @svg_shape_unique_id
@svg_shape_unique_id += 1
end
shapes << shape
end
def events_parse_shape(shapes, event, current_presentation, current_slide, timestamp)
# Figure out what presentation+slide this shape is for, with fallbacks
# for old BBB where this info isn't in the shape messages
@ -734,7 +821,7 @@ def events_parse_clear(shapes, event, current_presentation, current_slide, times
end
end
def events_get_image_info(slide)
def events_get_image_info(slide, tldraw)
slide_deskshare = slide[:deskshare]
slide_presentation = slide[:presentation]
@ -744,7 +831,8 @@ def events_get_image_info(slide)
slide[:src] = 'presentation/logo.png'
else
slide_nr = slide[:slide] + 1
slide[:src] = "presentation/#{slide_presentation}/slide-#{slide_nr}.png"
tldraw ? slide[:src] = "presentation/#{slide_presentation}/svgs/slide#{slide_nr}.svg"
: slide[:src] = "presentation/#{slide_presentation}/slide-#{slide_nr}.png"
slide[:text] = "presentation/#{slide_presentation}/textfiles/slide-#{slide_nr}.txt"
end
image_path = "#{@process_dir}/#{slide[:src]}"
@ -792,6 +880,7 @@ def process_presentation(package_dir)
# Current pan/zoom state
current_x_offset = current_y_offset = 0.0
current_width_ratio = current_height_ratio = 100.0
current_x_camera = current_y_camera = current_zoom = 0.0
# Current cursor status
cursor_x = cursor_y = -1.0
cursor_visible = false
@ -802,6 +891,8 @@ def process_presentation(package_dir)
panzooms = []
cursors = []
shapes = {}
tldraw = BigBlueButton::Events.check_for_tldraw_events(@doc)
tldraw_shapes = {}
# Iterate through the events.xml and store the events, building the
# xml files as we go
@ -836,6 +927,12 @@ def process_presentation(package_dir)
current_height_ratio = event.at_xpath('heightRatio').text.to_f
panzoom_changed = true
when 'TldrawCameraChangedEvent'
current_x_camera = event.at_xpath('xCamera').text.to_f
current_y_camera = event.at_xpath('yCamera').text.to_f
current_zoom = event.at_xpath('zoom').text.to_f
panzoom_changed = true
when 'DeskshareStartedEvent', 'StartWebRTCDesktopShareEvent'
deskshare = slide_changed = true if @presentation_props['include_deskshare']
@ -848,7 +945,10 @@ def process_presentation(package_dir)
when 'AddShapeEvent', 'ModifyTextEvent'
events_parse_shape(shapes, event, current_presentation, current_slide, timestamp)
when 'UndoShapeEvent', 'UndoAnnotationEvent'
when 'AddTldrawShapeEvent'
events_parse_tldraw_shape(shapes, event, current_presentation, current_slide, timestamp)
when 'UndoShapeEvent', 'UndoAnnotationEvent', 'DeleteTldrawShapeEvent'
events_parse_undo(shapes, event, current_presentation, current_slide, timestamp)
when 'ClearPageEvent', 'ClearWhiteboardEvent'
@ -887,7 +987,7 @@ def process_presentation(package_dir)
else
if slide
slide[:out] = timestamp
svg_render_image(svg, slide, shapes)
svg_render_image(svg, slide, shapes, tldraw, tldraw_shapes)
end
BigBlueButton.logger.info("Presentation #{current_presentation} Slide #{current_slide} Deskshare #{deskshare}")
@ -897,7 +997,7 @@ def process_presentation(package_dir)
in: timestamp,
deskshare: deskshare,
}
events_get_image_info(slide)
events_get_image_info(slide, tldraw)
slides << slide
end
end
@ -909,40 +1009,59 @@ def process_presentation(package_dir)
slide_width = slide[:width]
slide_height = slide[:height]
if panzoom &&
(panzoom[:deskshare] == deskshare) &&
((!tldraw &&
(panzoom[:x_offset] == current_x_offset) &&
(panzoom[:y_offset] == current_y_offset) &&
(panzoom[:width_ratio] == current_width_ratio) &&
(panzoom[:height_ratio] == current_height_ratio) &&
(panzoom[:width] == slide_width) &&
(panzoom[:height] == slide_height) &&
(panzoom[:deskshare] == deskshare)
(panzoom[:height] == slide_height)) ||
(tldraw &&
(panzoom[:x_camera] == current_x_camera) &&
(panzoom[:y_camera] == current_y_camera) &&
(panzoom[:zoom] == current_zoom))
)
BigBlueButton.logger.info('Panzoom: skipping, no changes')
panzoom_changed = false
else
if panzoom
panzoom[:out] = timestamp
panzooms_emit_event(panzooms_rec, panzoom)
panzooms_emit_event(panzooms_rec, panzoom, tldraw)
end
if !tldraw
BigBlueButton.logger.info("Panzoom: #{current_x_offset} #{current_y_offset} #{current_width_ratio} #{current_height_ratio} (#{slide_width}x#{slide_height})")
panzoom = {
x_offset: current_x_offset,
y_offset: current_y_offset,
width_ratio: current_width_ratio,
height_ratio: current_height_ratio,
width: slide[:width],
height: slide[:height],
in: timestamp,
deskshare: deskshare,
}
else
BigBlueButton.logger.info("Panzoom: #{current_x_camera} #{current_y_camera} #{current_zoom} (#{slide_width}x#{slide_height})")
panzoom = {
x_camera: current_x_camera,
y_camera: current_y_camera,
zoom: current_zoom,
in: timestamp,
deskshare: deskshare,
}
end
BigBlueButton.logger.info("Panzoom: #{current_x_offset} #{current_y_offset} #{current_width_ratio} #{current_height_ratio} (#{slide_width}x#{slide_height})")
panzoom = {
x_offset: current_x_offset,
y_offset: current_y_offset,
width_ratio: current_width_ratio,
height_ratio: current_height_ratio,
width: slide[:width],
height: slide[:height],
in: timestamp,
deskshare: deskshare,
}
panzooms << panzoom
end
end
# Perform cursor finalization
if cursor_changed || panzoom_changed
unless cursor_x >= 0 && cursor_x <= 100 &&
cursor_y >= 0 && cursor_y <= 100
cursor_visible = false
if !tldraw
unless cursor_x >= 0 && cursor_x <= 100 &&
cursor_y >= 0 && cursor_y <= 100
cursor_visible = false
end
end
panzoom = panzooms.last
@ -955,7 +1074,7 @@ def process_presentation(package_dir)
else
if cursor
cursor[:out] = timestamp
cursors_emit_event(cursors_rec, cursor)
cursors_emit_event(cursors_rec, cursor, tldraw)
end
cursor = {
visible: cursor_visible,
@ -972,26 +1091,27 @@ def process_presentation(package_dir)
# Add the last slide, panzoom, and cursor
slide = slides.last
slide[:out] = last_timestamp
svg_render_image(svg, slide, shapes)
svg_render_image(svg, slide, shapes, tldraw, tldraw_shapes)
panzoom = panzooms.last
panzoom[:out] = last_timestamp
panzooms_emit_event(panzooms_rec, panzoom)
panzooms_emit_event(panzooms_rec, panzoom, tldraw)
cursor = cursors.last
cursor[:out] = last_timestamp
cursors_emit_event(cursors_rec, cursor)
cursors_emit_event(cursors_rec, cursor, tldraw)
cursors_doc = Builder::XmlMarkup.new(indent: 2)
cursors_doc.instruct!
cursors_doc.recording(id: 'cursor_events') { |xml| xml << cursors_rec.target! }
cursors_doc.recording(id: 'cursor_events', tldraw: tldraw) { |xml| xml << cursors_rec.target! }
panzooms_doc = Builder::XmlMarkup.new(indent: 2)
panzooms_doc.instruct!
panzooms_doc.recording(id: 'panzoom_events') { |xml| xml << panzooms_rec.target! }
panzooms_doc.recording(id: 'panzoom_events', tldraw: tldraw) { |xml| xml << panzooms_rec.target! }
# And save the result
File.write("#{package_dir}/#{@shapes_svg_filename}", shapes_doc.to_xml)
File.write("#{package_dir}/#{@panzooms_xml_filename}", panzooms_doc.target!)
File.write("#{package_dir}/#{@cursor_xml_filename}", cursors_doc.target!)
generate_json_file(package_dir, @tldraw_shapes_filename, tldraw_shapes) if tldraw
end
def process_chat_messages(events, bbb_props)
@ -1170,6 +1290,7 @@ end
@panzooms_xml_filename = 'panzooms.xml'
@cursor_xml_filename = 'cursor.xml'
@deskshare_xml_filename = 'deskshare.xml'
@tldraw_shapes_filename = 'tldraw.json'
@svg_shape_id = 1
@svg_shape_unique_id = 1