[video-control-plugin-sdk] created extensible-are screenshare helper and refactored buttons rendering for screenshare

This commit is contained in:
Guilherme Leme 2024-07-31 14:47:54 -03:00
parent da62c1068e
commit 6f35d4d366
10 changed files with 378 additions and 32 deletions

View File

@ -0,0 +1,53 @@
import { useEffect, useState, useContext } from 'react';
import * as PluginSdk from 'bigbluebutton-html-plugin-sdk';
import {
ExtensibleAreaComponentManagerProps, ExtensibleArea,
ExtensibleAreaComponentManager,
} from '../../types';
import { PluginsContext } from '../../../../components-data/plugin-context/context';
const ScreenshareHelperPluginStateContainer = ((
props: ExtensibleAreaComponentManagerProps,
) => {
const {
uuid,
generateItemWithId,
extensibleAreaMap,
pluginApi,
} = props;
const [
screenshareHelperItems,
setScreenshareHelperItems,
] = useState<PluginSdk.ScreenshareHelperInterface[]>([]);
const {
pluginsExtensibleAreasAggregatedState,
setPluginsExtensibleAreasAggregatedState,
} = useContext(PluginsContext);
useEffect(() => {
extensibleAreaMap[uuid].screenshareHelperItems = screenshareHelperItems;
const aggregatedScreenshareHelperItems = ([] as PluginSdk.ScreenshareHelperInterface[]).concat(
...Object.values(extensibleAreaMap)
.map((extensibleArea: ExtensibleArea) => extensibleArea.screenshareHelperItems),
);
setPluginsExtensibleAreasAggregatedState(
{
...pluginsExtensibleAreasAggregatedState,
screenshareHelperItems: aggregatedScreenshareHelperItems,
},
);
}, [screenshareHelperItems]);
pluginApi.setScreenshareHelperItems = (items: PluginSdk.ScreenshareHelperInterface[]) => {
const itemsWithId = items.map(generateItemWithId) as PluginSdk.ScreenshareHelperInterface[];
setScreenshareHelperItems(itemsWithId);
return itemsWithId.map((i) => i.id);
};
return null;
}) as ExtensibleAreaComponentManager;
export default ScreenshareHelperPluginStateContainer;

View File

@ -19,6 +19,7 @@ import {
} from './types';
import FloatingWindowPluginStateContainer from './components/floating-window/manager';
import GenericContentPluginStateContainer from './components/generic-content/manager';
import ScreenshareHelperPluginStateContainer from './components/screenshare-helper/manager';
const extensibleAreaMap: ExtensibleAreaMap = {};
@ -36,6 +37,7 @@ const extensibleAreaComponentManagers: ExtensibleAreaComponentManager[] = [
UserListItemAdditionalInformationPluginStateContainer,
FloatingWindowPluginStateContainer,
GenericContentPluginStateContainer,
ScreenshareHelperPluginStateContainer,
];
function generateItemWithId<T extends PluginProvidedUiItemDescriptor>(

View File

@ -15,6 +15,7 @@ export interface ExtensibleArea {
actionsBarItems: PluginSdk.ActionsBarInterface[];
presentationDropdownItems: PluginSdk.PresentationDropdownInterface[];
navBarItems: PluginSdk.NavBarInterface[];
screenshareHelperItems: PluginSdk.ScreenshareHelperInterface[];
optionsDropdownItems: PluginSdk.OptionsDropdownInterface[];
cameraSettingsDropdownItems: PluginSdk.CameraSettingsDropdownInterface[];
userCameraDropdownItems: PluginSdk.UserCameraDropdownInterface[];

View File

@ -5,7 +5,9 @@ import { debounce } from '/imports/utils/debounce';
import FullscreenButtonContainer from '/imports/ui/components/common/fullscreen-button/container';
import SwitchButtonContainer from './switch-button/container';
import Styled from './styles';
import * as PluginSdk from 'bigbluebutton-html-plugin-sdk';
import VolumeSlider from '../external-video-player/volume-slider/component';
import PluginButtonContainer from './plugin-button/container';
import AutoplayOverlay from '../media/autoplay-overlay/component';
import logger from '/imports/startup/client/logger';
import playAndRetry from '/imports/utils/mediaElementPlayRetry';
@ -38,6 +40,32 @@ const MOBILE_HOVER_TIMEOUT = 5000;
const MEDIA_FLOW_PROBE_INTERVAL = 500;
const SCREEN_SIZE_DISPATCH_INTERVAL = 500;
const renderPluginItems = (pluginItems, bottom, right) => {
if (pluginItems !== undefined) {
return (
<>
{
pluginItems.map((pluginItem) => {
const returnComponent = (
<PluginButtonContainer
key={`${pluginItem.type}-${pluginItem.id}-${pluginItem.label}`}
dark
bottom={bottom}
right={right}
icon={pluginItem.icon}
label={pluginItem.label}
onClick={pluginItem.onClick}
/>
);
return returnComponent;
})
}
</>
);
}
return (<></>);
};
class ScreenshareComponent extends React.Component {
static renderScreenshareContainerInside(mainText) {
return (
@ -70,6 +98,8 @@ class ScreenshareComponent extends React.Component {
this.dispatchScreenShareSize = this.dispatchScreenShareSize.bind(this);
this.handleOnMuted = this.handleOnMuted.bind(this);
this.dispatchScreenShareSize = this.dispatchScreenShareSize.bind(this);
this.renderScreenshareButtons = this.renderScreenshareButtons.bind(this);
this.splitPluginItems = this.splitPluginItems.bind(this);
this.debouncedDispatchScreenShareSize = debounce(
this.dispatchScreenShareSize,
SCREEN_SIZE_DISPATCH_INTERVAL,
@ -336,14 +366,16 @@ class ScreenshareComponent extends React.Component {
if (!ALLOW_FULLSCREEN) return null;
return (
<FullscreenButtonContainer
key={uniqueId('fullscreenButton-')}
elementName={intl.formatMessage(this.locales.label)}
fullscreenRef={this.screenshareContainer}
elementId={fullscreenElementId}
isFullscreen={fullscreenContext}
dark
/>
<Styled.FullscreenButtonWrapperForScreenshare>
<FullscreenButtonContainer
key={uniqueId('fullscreenButton-')}
elementName={intl.formatMessage(this.locales.label)}
fullscreenRef={this.screenshareContainer}
elementId={fullscreenElementId}
isFullscreen={fullscreenContext}
dark
/>
</Styled.FullscreenButtonWrapperForScreenshare>
);
}
@ -410,7 +442,8 @@ class ScreenshareComponent extends React.Component {
return [(
<Styled.HoverToolbar
toolbarStyle={toolbarStyle}
key='hover-toolbar-screenshare'>
key="hover-toolbar-screenshare"
>
<VolumeSlider
volume={getVolume()}
muted={getVolume() === 0}
@ -418,8 +451,8 @@ class ScreenshareComponent extends React.Component {
onMuted={this.handleOnMuted}
/>
</Styled.HoverToolbar>
),
(deviceInfo.isMobile) && this.renderMobileVolumeControlOverlay(),
),
(deviceInfo.isMobile) && this.renderMobileVolumeControlOverlay(),
];
}
@ -446,17 +479,41 @@ class ScreenshareComponent extends React.Component {
);
}
splitPluginItems() {
const { pluginScreenshareHelperItems } = this.props;
return pluginScreenshareHelperItems.reduce((result, item) => {
switch (item.position) {
case PluginSdk.ScreenshareHelperItemPosition.TOP_RIGHT:
result.topRightPluginItems.push(item);
break;
case PluginSdk.ScreenshareHelperItemPosition.TOP_LEFT:
result.topLeftPluginItems.push(item);
break;
case PluginSdk.ScreenshareHelperItemPosition.BOTTOM_RIGHT:
result.bottomRightPluginItems.push(item);
break;
case PluginSdk.ScreenshareHelperItemPosition.BOTTOM_LEFT:
result.bottomLeftPluginItems.push(item);
break;
default:
break;
}
return result;
}, {
topRightPluginItems: [],
topLeftPluginItems: [],
bottomRightPluginItems: [],
bottomLeftPluginItems: [],
});
}
renderScreensharePresenter() {
const { switched } = this.state;
const { isGloballyBroadcasting, intl } = this.props;
return (
<Styled.ScreenshareContainer
switched={switched}
key="screenshareContainer"
ref={(ref) => { this.screenshareContainer = ref; }}
>
{isGloballyBroadcasting && this.renderSwitchButton()}
<>
{this.renderVideo(switched)}
{
@ -473,7 +530,7 @@ class ScreenshareComponent extends React.Component {
intl.formatMessage(this.locales.presenterLoadingLabel),
)
}
</Styled.ScreenshareContainer>
</>
);
}
@ -482,15 +539,7 @@ class ScreenshareComponent extends React.Component {
const { loaded } = this.state;
return (
<Styled.ScreenshareContainer
switched
key="screenshareContainer"
ref={(ref) => {
this.screenshareContainer = ref;
}}
id="screenshareContainer"
>
{loaded && this.renderFullscreenButton()}
<>
{this.renderVideo(true)}
{loaded && enableVolumeControl && this.renderVolumeSlider() }
@ -503,12 +552,61 @@ class ScreenshareComponent extends React.Component {
: null
}
</Styled.ScreenshareContainerDefault>
</Styled.ScreenshareContainer>
</>
);
}
renderScreenshareButtons() {
const { isPresenter, isGloballyBroadcasting } = this.props;
const { loaded } = this.state;
const {
topRightPluginItems,
topLeftPluginItems,
bottomRightPluginItems,
bottomLeftPluginItems,
} = this.splitPluginItems();
return (
<>
<Styled.ScreenshareButtonsContainterWrapper
positionYAxis="top"
positionXAxis="left"
>
{renderPluginItems(topLeftPluginItems, false, false)}
</Styled.ScreenshareButtonsContainterWrapper>
<Styled.ScreenshareButtonsContainterWrapper
positionYAxis="top"
positionXAxis="right"
>
{isPresenter
// Presenter button:
? isGloballyBroadcasting && this.renderSwitchButton()
// Non-presenter button:
: loaded && this.renderFullscreenButton()}
{renderPluginItems(topRightPluginItems, false, true)}
</Styled.ScreenshareButtonsContainterWrapper>
<Styled.ScreenshareButtonsContainterWrapper
positionYAxis="bottom"
positionXAxis="left"
>
{renderPluginItems(bottomLeftPluginItems, true, false)}
</Styled.ScreenshareButtonsContainterWrapper>
<Styled.ScreenshareButtonsContainterWrapper
positionYAxis="bottom"
positionXAxis="right"
>
{renderPluginItems(bottomRightPluginItems, true, true)}
</Styled.ScreenshareButtonsContainterWrapper>
</>
);
}
render() {
const { loaded, autoplayBlocked, mediaFlowing} = this.state;
const {
loaded,
autoplayBlocked,
mediaFlowing,
switched,
} = this.state;
const {
isPresenter,
isGloballyBroadcasting,
@ -565,7 +663,19 @@ class ScreenshareComponent extends React.Component {
</Styled.SpinnerWrapper>
)}
{autoplayBlocked ? this.renderAutoplayOverlay() : null}
{isPresenter ? this.renderScreensharePresenter() : this.renderScreenshareDefault()}
<Styled.ScreenshareContainer
switched={isPresenter ? switched : true}
key="screenshareContainer"
ref={(ref) => {
this.screenshareContainer = ref;
}}
id="screenshareContainer"
>
{this.renderScreenshareButtons()}
{isPresenter
? this.renderScreensharePresenter()
: this.renderScreenshareDefault()}
</Styled.ScreenshareContainer>
</div>
);
}
@ -577,6 +687,9 @@ ScreenshareComponent.propTypes = {
intl: PropTypes.shape({
formatMessage: PropTypes.func.isRequired,
}).isRequired,
pluginScreenshareHelperItems: PropTypes.arrayOf(PropTypes.objectOf({
position: PropTypes.string,
})).isRequired,
isPresenter: PropTypes.bool.isRequired,
layoutContextDispatch: PropTypes.func.isRequired,
enableVolumeControl: PropTypes.bool.isRequired,

View File

@ -1,4 +1,4 @@
import React from 'react';
import React, { useContext } from 'react';
import { useMutation, useReactiveVar } from '@apollo/client';
import { defineMessages } from 'react-intl';
import {
@ -11,6 +11,7 @@ import {
useScreenshareHasAudio,
useBroadcastContentType,
} from './service';
import { PluginsContext } from '/imports/ui/components/components-data/plugin-context/context';
import ScreenshareComponent from './component';
import { layoutSelect, layoutSelectOutput, layoutDispatch } from '../layout/context';
import getFromUserSettings from '/imports/ui/services/users-settings';
@ -93,6 +94,7 @@ const ScreenshareContainer = (props) => {
const screenShare = layoutSelectOutput((i) => i.screenShare);
const fullscreen = layoutSelect((i) => i.fullscreen);
const layoutContextDispatch = layoutDispatch();
const { pluginsExtensibleAreasAggregatedState } = useContext(PluginsContext);
const { element } = fullscreen;
const fullscreenElementId = 'Screenshare';
@ -137,11 +139,18 @@ const ScreenshareContainer = (props) => {
const isCameraAsContentBroadcasting = useIsCameraAsContentBroadcasting();
const hasAudio = useScreenshareHasAudio();
let pluginScreenshareHelperItems = [];
if (pluginsExtensibleAreasAggregatedState.screenshareHelperItems) {
pluginScreenshareHelperItems = [
...pluginsExtensibleAreasAggregatedState.screenshareHelperItems,
];
}
if (isScreenBroadcasting || isCameraAsContentBroadcasting) {
return (
<ScreenshareComponent
{
...{
pluginScreenshareHelperItems,
layoutContextDispatch,
...props,
...screenShare,

View File

@ -0,0 +1,40 @@
import React from 'react';
import Styled from './styles';
interface PluginButtonComponentProps {
dark: boolean;
bottom: boolean;
right: boolean;
icon: string;
label: string;
onClick: () => void;
}
const PluginButtonComponent = ({
dark = false,
bottom = false,
onClick = () => {},
right,
icon,
label,
}: PluginButtonComponentProps) => (
<Styled.PluginButtonWrapper
{...{
dark,
bottom,
right,
}}
>
<Styled.PluginButton
color="default"
icon={icon}
size="sm"
onClick={onClick}
hideLabel
label={label}
data-test="switchButton"
/>
</Styled.PluginButtonWrapper>
);
export default PluginButtonComponent;

View File

@ -0,0 +1,34 @@
import React from 'react';
import PluginButtonComponent from './component';
interface PluginButtonContainerProps {
dark: boolean;
bottom: boolean;
right: boolean;
icon: string;
label: string;
onClick: () => void;
}
const PluginButtonContainer = (props: PluginButtonContainerProps) => {
const {
dark,
bottom,
right,
icon,
label,
onClick,
} = props;
return (
<PluginButtonComponent
dark={dark}
bottom={bottom}
right={right}
icon={icon}
label={label}
onClick={onClick}
/>
);
};
export default PluginButtonContainer;

View File

@ -0,0 +1,64 @@
import styled from 'styled-components';
import {
colorWhite,
colorTransparent,
colorBlack,
} from '/imports/ui/stylesheets/styled-components/palette';
import Button from '/imports/ui/components/common/button/component';
const PluginButtonWrapper = styled.div`
background-color: ${colorTransparent};
cursor: pointer;
border: 0;
z-index: 2;
margin: 2px;
[dir="rtl"] & {
right: auto;
left: 0;
${({ fullScreenEnabled }) => fullScreenEnabled && `
left: 1.75rem;
`}
}
[class*="presentationZoomControls"] & {
position: relative !important;
}
${({ dark }) => dark && `
background-color: rgba(0,0,0,.3);
& button i {
color: ${colorWhite};
}
`}
${({ dark }) => !dark && `
background-color: ${colorTransparent};
& button i {
color: ${colorBlack};
}
`}
`;
const PluginButton = styled(Button)`
padding: 5px;
&,
&:active,
&:hover,
&:focus {
background-color: ${colorTransparent} !important;
border: none !important;
i {
border: none !important;
background-color: ${colorTransparent} !important;
font-size: 1rem;
}
}
`;
export default {
PluginButtonWrapper,
PluginButton,
};

View File

@ -90,7 +90,37 @@ const HoverToolbar = styled.div`
`}
`;
const ScreenshareButtonsContainterWrapper = styled.div`
position: absolute;
z-index: 2;
display: flex;
width: 50%;
${({ positionXAxis }) => positionXAxis === 'right' && `
right: 0;
flex-direction: row-reverse;
`}
${({ positionXAxis }) => positionXAxis === 'left' && `
left: 0;
`}
${({ positionYAxis }) => positionYAxis === 'top' && `
top: 0;
`}
${({ positionYAxis }) => positionYAxis === 'bottom' && `
bottom: 0;
`}
`;
const FullscreenButtonWrapperForScreenshare = styled.div`
& > * {
position: relative !important;
}
`;
export default {
FullscreenButtonWrapperForScreenshare,
ScreenshareButtonsContainterWrapper,
ScreenshareContainerInside,
MainText,
ScreenshareVideo,

View File

@ -7,7 +7,7 @@ import {
import Button from '/imports/ui/components/common/button/component';
const SwitchButtonWrapper = styled.div`
position: absolute;
position: relative;
right: 0;
left: auto;
background-color: ${colorTransparent};