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