Merge branch 'v2.6.x-release' of https://github.com/bigbluebutton/bigbluebutton into tldraw-viewbox-sync

This commit is contained in:
germanocaumo 2022-08-18 18:41:27 +00:00
commit 7bf8668f23
14 changed files with 167 additions and 22 deletions

View File

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

View File

@ -30,6 +30,7 @@ const DEFAULT_FULLAUDIO_MEDIA_SERVER = MEDIA.audio.fullAudioMediaServer;
const LISTEN_ONLY_OFFERING = MEDIA.listenOnlyOffering;
const MEDIA_TAG = MEDIA.mediaTag.replace(/#/g, '');
const RECONNECT_TIMEOUT_MS = MEDIA.listenOnlyCallTimeout || 15000;
const { audio: NETWORK_PRIORITY } = MEDIA.networkPriorities || {};
const SENDRECV_ROLE = 'sendrecv';
const RECV_ROLE = 'recv';
const BRIDGE_NAME = 'fullaudio';
@ -316,6 +317,7 @@ export default class SFUAudioBridge extends BaseAudioBridge {
offering: isListenOnly ? LISTEN_ONLY_OFFERING : true,
signalCandidates: SIGNAL_CANDIDATES,
traceLogs: TRACE_LOGS,
networkPriority: NETWORK_PRIORITY,
};
this.broker = new AudioBroker(

View File

@ -12,6 +12,7 @@ const SFU_URL = SFU_CONFIG.wsUrl;
const OFFERING = SFU_CONFIG.screenshare.subscriberOffering;
const SIGNAL_CANDIDATES = Meteor.settings.public.kurento.signalCandidates;
const TRACE_LOGS = Meteor.settings.public.kurento.traceLogs;
const { screenshare: NETWORK_PRIORITY } = Meteor.settings.public.media.networkPriorities || {};
const BRIDGE_NAME = 'kurento'
const SCREENSHARE_VIDEO_TAG = 'screenshareVideo';
@ -307,6 +308,7 @@ export default class KurentoScreenshareBridge {
signalCandidates: SIGNAL_CANDIDATES,
forceRelay: shouldForceRelay(),
traceLogs: TRACE_LOGS,
networkPriority: NETWORK_PRIORITY,
};
this.broker = new ScreenshareBroker(

View File

@ -4,6 +4,7 @@ import WhiteboardOverlayContainer from '/imports/ui/components/whiteboard/whiteb
import WhiteboardContainer from '/imports/ui/components/whiteboard/container';
import WhiteboardToolbarContainer from '/imports/ui/components/whiteboard/whiteboard-toolbar/container';
import { HUNDRED_PERCENT, MAX_PERCENT } from '/imports/utils/slideCalcUtils';
import { SPACE } from '/imports/utils/keyCodes';
import { defineMessages, injectIntl } from 'react-intl';
import { toast } from 'react-toastify';
import { Session } from 'meteor/session';
@ -76,6 +77,7 @@ class Presentation extends PureComponent {
fitToWidth: false,
isFullscreen: false,
tldrawAPI: null,
isPanning: false,
};
this.currentPresentationToastId = null;
@ -91,6 +93,8 @@ class Presentation extends PureComponent {
this.handleResize = this.handleResize.bind(this);
this.setTldrawAPI = this.setTldrawAPI.bind(this);
this.renderPresentationMenu = this.renderPresentationMenu.bind(this);
this.setIsPanning = this.setIsPanning.bind(this);
this.handlePanShortcut = this.handlePanShortcut.bind(this);
this.onResize = () => setTimeout(this.handleResize.bind(this), 0);
this.renderCurrentPresentationToast = this.renderCurrentPresentationToast.bind(this);
@ -122,8 +126,22 @@ class Presentation extends PureComponent {
return stateChange;
}
handlePanShortcut(e) {
const { userIsPresenter } = this.props;
if (e.keyCode === SPACE && userIsPresenter) {
switch(e.type) {
case 'keyup':
return this.state.isPanning && this.setIsPanning();
case 'keydown':
return !this.state.isPanning && this.setIsPanning();
}
}
}
componentDidMount() {
this.getInitialPresentationSizes();
this.refPresentationContainer.addEventListener('keydown', this.handlePanShortcut);
this.refPresentationContainer.addEventListener('keyup', this.handlePanShortcut);
this.refPresentationContainer
.addEventListener(FULLSCREEN_CHANGE_EVENT, this.onFullscreenChange);
window.addEventListener('resize', this.onResize, false);
@ -166,7 +184,7 @@ class Presentation extends PureComponent {
clearFakeAnnotations,
} = this.props;
const { presentationWidth, presentationHeight } = this.state;
const { presentationWidth, presentationHeight, isPanning } = this.state;
const {
numCameras: prevNumCameras,
presentationBounds: prevPresentationBounds,
@ -267,6 +285,10 @@ class Presentation extends PureComponent {
value: currentSlide.num,
});
}
if (isPanning || !userIsPresenter && prevProps.userIsPresenter) {
this.setIsPanning();
}
}
componentWillUnmount() {
@ -276,6 +298,8 @@ class Presentation extends PureComponent {
window.removeEventListener('resize', this.onResize, false);
this.refPresentationContainer
.removeEventListener(FULLSCREEN_CHANGE_EVENT, this.onFullscreenChange);
this.refPresentationContainer.removeEventListener('keydown', this.handlePanShortcut);
this.refPresentationContainer.removeEventListener('keyup', this.handlePanShortcut);
if (fullscreenContext) {
layoutContextDispatch({
@ -291,7 +315,14 @@ class Presentation extends PureComponent {
setTldrawAPI(api) {
this.setState({
tldrawAPI: api,
})
});
}
setIsPanning() {
this.setState({
isPanning: !this.state.isPanning,
});
}
handleResize() {
@ -738,6 +769,8 @@ class Presentation extends PureComponent {
layoutContextDispatch,
presentationIsOpen,
}}
setIsPanning={this.setIsPanning}
isPanning={this.state.isPanning}
isZoomed={this.state.isZoomed}
curPageId={this.state.tldrawAPI?.getPage()?.id}
currentSlideNum={currentSlide.num}
@ -993,6 +1026,7 @@ class Presentation extends PureComponent {
presentationHeight={svgHeight}
isViewersCursorLocked={isViewersCursorLocked}
zoomChanger={this.zoomChanger}
isPanning={this.state.isPanning}
fitToWidth={fitToWidth}
zoomValue={zoom}
/>

View File

@ -84,6 +84,10 @@ const intlMessages = defineMessages({
id: 'app.whiteboard.toolbar.multiUserOff',
description: 'Whiteboard toolbar turn multi-user off menu',
},
pan: {
id: 'app.whiteboard.toolbar.tools.hand',
description: 'presentation toolbar pan label',
}
});
class PresentationToolbar extends PureComponent {
@ -257,6 +261,8 @@ class PresentationToolbar extends PureComponent {
toolbarWidth,
multiUserSize,
multiUser,
setIsPanning,
isPanning,
} = this.props;
const { isMobile } = deviceInfo;
@ -393,6 +399,20 @@ class PresentationToolbar extends PureComponent {
/>
</TooltipContainer>
) : null}
<Styled.FitToWidthButton
role="button"
data-test="panButton"
aria-label={intl.formatMessage(intlMessages.pan)}
color="light"
disabled={zoom === HUNDRED_PERCENT}
icon="hand"
size="md"
circle
onClick={setIsPanning}
label={intl.formatMessage(intlMessages.pan)}
hideLabel
panning={isPanning}
/>
<Styled.FitToWidthButton
role="button"
data-test="fitToWidthButton"

View File

@ -89,13 +89,9 @@ const PresentationSlideControls = styled.div`
`;
const PrevSlideButton = styled(Button)`
i {
padding-left: 20%;
}
& > i {
font-size: 1rem;
padding-left: 20%;
[dir="rtl"] & {
-webkit-transform: scale(-1, 1);
@ -105,17 +101,12 @@ const PrevSlideButton = styled(Button)`
transform: scale(-1, 1);
}
}
`;
const NextSlideButton = styled(Button)`
i {
padding-left: 60%;
}
& > i {
font-size: 1rem;
padding-left: 60%;
[dir="rtl"] & {
-webkit-transform: scale(-1, 1);
@ -204,6 +195,12 @@ const FitToWidthButton = styled(Button)`
background-color: ${colorOffWhite};
border: 0;
}
${({ panning }) => panning && `
> span {
background-color: #DCE4EC;
}
`}
`;
const MultiUserTool = styled.span`
@ -213,7 +210,6 @@ const MultiUserTool = styled.span`
height: 1rem;
position: relative;
z-index: 2;
right: 1rem;
bottom: 0.5rem;
color: ${colorWhite};
display: flex;
@ -221,20 +217,35 @@ const MultiUserTool = styled.span`
align-items: center;
box-shadow: 1px 1px ${borderSizeLarge} ${colorGrayDark};
font-size: ${smPaddingX};
[dir="ltr"] & {
right: 1rem;
}
[dir="rtl"] & {
left: 1rem;
}
`;
const MUTPlaceholder = styled.div`
width: 1rem;
height: 1rem;
position: relative;
right: 1rem;
bottom: 0.5rem;
[dir="ltr"] & {
right: 1rem;
}
[dir="rtl"] & {
left: 1rem;
}
`;
const WBAccessButton = styled(Button)`
border: none !important;
& > i {
i {
font-size: 1.2rem;
[dir="rtl"] & {

View File

@ -27,6 +27,7 @@ import WebRtcPeer from '/imports/ui/services/webrtc-base/peer';
// FIXME Remove hardcoded defaults 2.3.
const WS_CONN_TIMEOUT = Meteor.settings.public.kurento.wsConnectionTimeout || 4000;
const { webcam: NETWORK_PRIORITY } = Meteor.settings.public.media.networkPriorities || {};
const {
baseTimeout: CAMERA_SHARE_FAILED_WAIT_TIME = 15000,
maxTimeout: MAX_CAMERA_SHARE_FAILED_WAIT_TIME = 60000,
@ -616,6 +617,7 @@ class VideoProvider extends Component {
iceTransportPolicy: shouldForceRelay() ? 'relay' : undefined,
},
trace: TRACE_LOGS,
networkPriorities: NETWORK_PRIORITY ? { video: NETWORK_PRIORITY } : undefined,
};
try {

View File

@ -46,6 +46,7 @@ export default function Whiteboard(props) {
zoomChanger,
isMultiUserActive,
isRTL,
isPanning,
fitToWidth,
zoomValue,
} = props;
@ -61,6 +62,7 @@ export default function Whiteboard(props) {
assets: {},
});
const [tldrawAPI, setTLDrawAPI] = React.useState(null);
const [forcePanning, setForcePanning] = React.useState(false);
const [zoom, setZoom] = React.useState(HUNDRED_PERCENT);
const [isMounting, setIsMounting] = React.useState(true);
const prevShapes = usePrevious(shapes);
@ -219,8 +221,18 @@ export default function Whiteboard(props) {
// removes image tool from the tldraw toolbar
document.getElementById("TD-PrimaryTools-Image").style.display = 'none';
}
if (tldrawAPI) {
tldrawAPI.isForcePanning = isPanning;
}
});
React.useEffect(() => {
if (tldrawAPI) {
tldrawAPI.isForcePanning = isPanning;
}
}, [isPanning]);
const onMount = (app) => {
app.setSetting('language', document.getElementsByTagName('html')[0]?.lang || 'en');
app.setSetting('dockPosition', isRTL ? 'left' : 'right');
@ -306,7 +318,7 @@ export default function Whiteboard(props) {
const editableWB = (
<Tldraw
key={`wb-${document?.documentElement?.dir}-${document.getElementById('Navbar')?.style?.width}`}
key={`wb-${document?.documentElement?.dir}-${document.getElementById('Navbar')?.style?.width}-${forcePanning}`}
document={doc}
// disable the ability to drag and drop files onto the whiteboard
// until we handle saving of assets in akka.
@ -521,6 +533,8 @@ export default function Whiteboard(props) {
whiteboardId={whiteboardId}
isViewersCursorLocked={isViewersCursorLocked}
isMultiUserActive={isMultiUserActive}
isPanning={isPanning}
>
{hasWBAccess || isPresenter ? editableWB : readOnlyWB}
</Cursors>

View File

@ -126,6 +126,7 @@ export default function Cursors(props) {
hasMultiUserAccess,
isMultiUserActive,
application,
isPanning,
} = props;
const start = () => setActive(true);
@ -260,10 +261,12 @@ export default function Cursors(props) {
});
const multiUserAccess = hasMultiUserAccess(whiteboardId, currentUser?.userId);
let cursorType = multiUserAccess || currentUser?.presenter ? "none" : "default";
if (isPanning) cursorType = 'grab';
return (
<span ref={(r) => (cursorWrapper = r)}>
<div style={{ height: "100%", cursor: multiUserAccess || currentUser?.presenter ? "none" : "default" }}>
<div style={{ height: "100%", cursor: cursorType }}>
{(active && multiUserAccess || (active && currentUser?.presenter)) && (
<PositionLabel
pos={pos}

View File

@ -218,8 +218,13 @@ const getMultiUserSize = (whiteboardId) => {
const multiUserSize = Users.find(
{
meetingId: Auth.meetingID,
userId: { $in: multiUser },
presenter: false,
$or: [
{
userId: { $in: multiUser },
presenter: false,
},
{ presenter: true },
],
},
{ fields: { userId: 1 } }
).fetch();

View File

@ -28,6 +28,7 @@ class AudioBroker extends BaseBroker {
// stream,
// signalCandidates
// traceLogs
// networkPriority
Object.assign(this, options);
}
@ -82,6 +83,7 @@ class AudioBroker extends BaseBroker {
this.onIceCandidate(candidate, this.role);
},
trace: this.traceLogs,
networkPriorities: this.networkPriority ? { audio: this.networkPriority } : undefined,
};
const peerRole = this.role === 'sendrecv' ? this.role : 'recvonly';

View File

@ -37,6 +37,7 @@ class ScreenshareBroker extends BaseBroker {
// mediaServer,
// signalCandidates,
// traceLogs
// networkPriority
Object.assign(this, options);
}
@ -187,6 +188,7 @@ class ScreenshareBroker extends BaseBroker {
videoStream: this.stream,
configuration: this.populatePeerConfiguration(),
trace: this.traceLogs,
networkPriorities: this.networkPriority ? { video: this.networkPriority } : undefined,
};
this.webRtcPeer = new WebRtcPeer('sendonly', options);
this.webRtcPeer.iceQueue = [];

View File

@ -19,6 +19,12 @@ export default class WebRtcPeer extends EventEmitter2 {
this.configuration = this.options.configuration;
this.onicecandidate = this.options.onicecandidate;
this.oncandidategatheringdone = this.options.oncandidategatheringdone;
// this.networkPriorities: <{
// audio: <'very-low' | 'low' | 'medium' | 'high' | undefined>
// video: <'very-low' | 'low' | 'medium' | 'high' | undefined>
// } | undefined >
this.networkPriorities = this.options.networkPriorities;
this.candidateGatheringDone = false;
this._outboundCandidateQueue = [];
@ -41,6 +47,40 @@ export default class WebRtcPeer extends EventEmitter2 {
}
}
_processEncodingOptions() {
this.peerConnection?.getSenders().forEach((sender) => {
const { track } = sender;
if (track) {
// TODO: this is not ideal and a bit anti-spec. The correct thing to do
// would be to set this in the transceiver creation via sendEncodings in
// addTransceiver, but FF doesn't support that. So we should split this
// between Chromium/WebKit (addTransceiver) and FF (this way) later - prlanzarin
const parameters = sender.getParameters();
// The encoder parameters might not be up yet; if that's the case,
// add a filler object so we can alter the parameters anyways
if (parameters.encodings == null || parameters.encodings.length === 0) {
parameters.encodings = [{}];
}
parameters.encodings.forEach((encoding) => {
// networkPriority
if (this.networkPriorities && this.networkPriorities[track.kind]) {
// eslint-disable-next-line no-param-reassign
encoding.networkPriority = this.networkPriorities[track.kind];
}
// Add further custom encoding parameters here
});
try {
sender.setParameters(parameters);
} catch (error) {
this.logger.error('BBB::WebRtcPeer::_processEncodingOptions - setParameters failed', error);
}
}
});
}
_flushInboundCandidateQueue() {
while (this._inboundCandidateQueue.length) {
const entry = this._inboundCandidateQueue.shift();
@ -282,6 +322,7 @@ export default class WebRtcPeer extends EventEmitter2 {
return this.peerConnection.setLocalDescription(offer);
})
.then(() => {
this._processEncodingOptions();
const localDescription = this.getLocalSessionDescriptor();
this.logger.debug('BBB::WebRtcPeer::generateOffer - local description set', localDescription);
return localDescription.sdp;

View File

@ -621,6 +621,13 @@ public:
useRtcLoopbackInChromium: true
# showVolumeMeter: shows an energy bar for microphones in the AudioSettings view
showVolumeMeter: true
# networkPriorities: DSCP markings for each media type. Chromium only, applies
# to sender flows. See https://datatracker.ietf.org/doc/html/rfc8837#section-5
# for further info.
#networkPriorities:
# audio: high
# webcam: medium
# screenshare: medium
stats:
enabled: true
interval: 10000