diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/record/events/AddTldrawShapeWhiteboardRecordEvent.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/record/events/AddTldrawShapeWhiteboardRecordEvent.scala
new file mode 100755
index 0000000000..cdba60459d
--- /dev/null
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/record/events/AddTldrawShapeWhiteboardRecordEvent.scala
@@ -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 .
+ *
+ */
+
+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"
+}
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/record/events/DeleteTldrawShapeRecordEvent.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/record/events/DeleteTldrawShapeRecordEvent.scala
new file mode 100755
index 0000000000..4322e03895
--- /dev/null
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/record/events/DeleteTldrawShapeRecordEvent.scala
@@ -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 .
+ *
+ */
+
+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"
+}
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/record/events/ResizeAndMoveSlideRecordEvent.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/record/events/ResizeAndMoveSlideRecordEvent.scala
index 1797a89ada..0905fd5d8a 100755
--- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/record/events/ResizeAndMoveSlideRecordEvent.scala
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/record/events/ResizeAndMoveSlideRecordEvent.scala
@@ -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"
}
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/record/events/TldrawCameraChangedRecordEvent.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/record/events/TldrawCameraChangedRecordEvent.scala
new file mode 100755
index 0000000000..f5ea3deba8
--- /dev/null
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/core/record/events/TldrawCameraChangedRecordEvent.scala
@@ -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 .
+ *
+ */
+
+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"
+}
diff --git a/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/RedisRecorderActor.scala b/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/RedisRecorderActor.scala
index 0954bed385..04097fc8ab 100755
--- a/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/RedisRecorderActor.scala
+++ b/akka-bbb-apps/src/main/scala/org/bigbluebutton/endpoint/redis/RedisRecorderActor.scala
@@ -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))
diff --git a/bbb-webrtc-sfu.placeholder.sh b/bbb-webrtc-sfu.placeholder.sh
index 6614c9c6e6..16582b4386 100755
--- a/bbb-webrtc-sfu.placeholder.sh
+++ b/bbb-webrtc-sfu.placeholder.sh
@@ -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
diff --git a/bigbluebutton-config/bigbluebutton-release b/bigbluebutton-config/bigbluebutton-release
index ba3d854eed..963fd1e954 100644
--- a/bigbluebutton-config/bigbluebutton-release
+++ b/bigbluebutton-config/bigbluebutton-release
@@ -1 +1 @@
-BIGBLUEBUTTON_RELEASE=2.6.0-alpha.1
+BIGBLUEBUTTON_RELEASE=2.6.0-alpha.2
diff --git a/bigbluebutton-html5/.meteor/packages b/bigbluebutton-html5/.meteor/packages
index 137d6f4190..e7649fe00f 100644
--- a/bigbluebutton-html5/.meteor/packages
+++ b/bigbluebutton-html5/.meteor/packages
@@ -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
diff --git a/bigbluebutton-html5/.meteor/release b/bigbluebutton-html5/.meteor/release
index 8e3f1708ae..66dd7b6647 100644
--- a/bigbluebutton-html5/.meteor/release
+++ b/bigbluebutton-html5/.meteor/release
@@ -1 +1 @@
-METEOR@2.7.1
+METEOR@2.7.3
diff --git a/bigbluebutton-html5/.meteor/versions b/bigbluebutton-html5/.meteor/versions
index bf1e9659b4..ab0881cd0b 100644
--- a/bigbluebutton-html5/.meteor/versions
+++ b/bigbluebutton-html5/.meteor/versions
@@ -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
diff --git a/bigbluebutton-html5/imports/ui/components/audio/audio-controls/input-stream-live-selector/component.jsx b/bigbluebutton-html5/imports/ui/components/audio/audio-controls/input-stream-live-selector/component.jsx
index 8f185edef4..9b657d7a09 100644
--- a/bigbluebutton-html5/imports/ui/components/audio/audio-controls/input-stream-live-selector/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/audio/audio-controls/input-stream-live-selector/component.jsx
@@ -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 (
-
- {isListenOnly
- ? this.renderListenOnlyButton()
- : this.renderMuteToggleButton()}
-
- >
- )}
- 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 ? (
+ handleLeaveAudio()}
+ aria-hidden="true"
+ />
+ ) : null}
+
+ {isListenOnly
+ ? this.renderListenOnlyButton()
+ : this.renderMuteToggleButton()}
+
+ >
+ )}
+ 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']);
diff --git a/bigbluebutton-html5/imports/ui/components/common/button/button-emoji/ButtonEmoji.jsx b/bigbluebutton-html5/imports/ui/components/common/button/button-emoji/ButtonEmoji.jsx
index 51db6a7ab7..13a3710fb3 100644
--- a/bigbluebutton-html5/imports/ui/components/common/button/button-emoji/ButtonEmoji.jsx
+++ b/bigbluebutton-html5/imports/ui/components/common/button/button-emoji/ButtonEmoji.jsx
@@ -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 = ();
+ const IconComponent = ();
return (
diff --git a/bigbluebutton-html5/imports/ui/components/common/button/button-emoji/styles.js b/bigbluebutton-html5/imports/ui/components/common/button/button-emoji/styles.js
index 4463970310..12d93e36ec 100644
--- a/bigbluebutton-html5/imports/ui/components/common/button/button-emoji/styles.js
+++ b/bigbluebutton-html5/imports/ui/components/common/button/button-emoji/styles.js
@@ -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`
diff --git a/bigbluebutton-html5/imports/ui/components/common/icon/component.jsx b/bigbluebutton-html5/imports/ui/components/common/icon/component.jsx
index e3460fa6ef..14d33e2c39 100644
--- a/bigbluebutton-html5/imports/ui/components/common/icon/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/common/icon/component.jsx
@@ -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
}) => (
-
);
diff --git a/bigbluebutton-html5/imports/ui/components/common/icon/styles.js b/bigbluebutton-html5/imports/ui/components/common/icon/styles.js
new file mode 100644
index 0000000000..6f5e2ca554
--- /dev/null
+++ b/bigbluebutton-html5/imports/ui/components/common/icon/styles.js
@@ -0,0 +1,12 @@
+import styled from 'styled-components';
+
+const Icon = styled.i`
+ ${({ $rotate }) => $rotate && `
+ transform: rotate(180deg);
+ margin-top: 20%;
+ `}
+`;
+
+export default {
+ Icon,
+};
diff --git a/bigbluebutton-html5/imports/ui/components/whiteboard/cursors/component.jsx b/bigbluebutton-html5/imports/ui/components/whiteboard/cursors/component.jsx
index c82ba46eb2..03bf381a62 100644
--- a/bigbluebutton-html5/imports/ui/components/whiteboard/cursors/component.jsx
+++ b/bigbluebutton-html5/imports/ui/components/whiteboard/cursors/component.jsx
@@ -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);
diff --git a/bigbluebutton-html5/imports/ui/services/virtual-background/index.js b/bigbluebutton-html5/imports/ui/services/virtual-background/index.js
index 79086339d6..527d9bb54c 100644
--- a/bigbluebutton-html5/imports/ui/services/virtual-background/index.js
+++ b/bigbluebutton-html5/imports/ui/services/virtual-background/index.js
@@ -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,
diff --git a/bigbluebutton-html5/package-lock.json b/bigbluebutton-html5/package-lock.json
index e6c4a850e8..a283b97d72 100644
--- a/bigbluebutton-html5/package-lock.json
+++ b/bigbluebutton-html5/package-lock.json
@@ -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",
diff --git a/build/packages-template/bbb-html5/after-install.sh b/build/packages-template/bbb-html5/after-install.sh
index 733a48ce92..9c3b2513f1 100644
--- a/build/packages-template/bbb-html5/after-install.sh
+++ b/build/packages-template/bbb-html5/after-install.sh
@@ -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"
diff --git a/build/packages-template/bbb-html5/build.sh b/build/packages-template/bbb-html5/build.sh
index 4d04818d82..61fc8434fc 100755
--- a/build/packages-template/bbb-html5/build.sh
+++ b/build/packages-template/bbb-html5/build.sh
@@ -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
diff --git a/build/packages-template/bbb-html5/systemd_start.sh b/build/packages-template/bbb-html5/systemd_start.sh
index 05f7a48fbe..0c33ab00df 100644
--- a/build/packages-template/bbb-html5/systemd_start.sh
+++ b/build/packages-template/bbb-html5/systemd_start.sh
@@ -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
diff --git a/build/packages-template/bbb-html5/systemd_start_frontend.sh b/build/packages-template/bbb-html5/systemd_start_frontend.sh
index 651ee51214..b92a07acc8 100644
--- a/build/packages-template/bbb-html5/systemd_start_frontend.sh
+++ b/build/packages-template/bbb-html5/systemd_start_frontend.sh
@@ -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
diff --git a/record-and-playback/core/lib/recordandplayback/generators/events.rb b/record-and-playback/core/lib/recordandplayback/generators/events.rb
index 97beb67cdf..e145cf3972 100755
--- a/record-and-playback/core/lib/recordandplayback/generators/events.rb
+++ b/record-and-playback/core/lib/recordandplayback/generators/events.rb
@@ -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
\ No newline at end of file
+end
diff --git a/record-and-playback/presentation/scripts/process/presentation.rb b/record-and-playback/presentation/scripts/process/presentation.rb
index aa64702af2..d0b0630b1b 100755
--- a/record-and-playback/presentation/scripts/process/presentation.rb
+++ b/record-and-playback/presentation/scripts/process/presentation.rb
@@ -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')
diff --git a/record-and-playback/presentation/scripts/publish/presentation.rb b/record-and-playback/presentation/scripts/publish/presentation.rb
index 38ad1049e5..503f64e6a0 100755
--- a/record-and-playback/presentation/scripts/publish/presentation.rb
+++ b/record-and-playback/presentation/scripts/publish/presentation.rb
@@ -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