Skip to content

Commit 870d010

Browse files
Full screen captions banner api (#5164)
* Add extra bundle for enable video effect * hide video gallery and display caption only * Caption fullscreen mode api * Revert "Add extra bundle for enable video effect" This reverts commit 3e1ea7c. * remove test code * Update packages/react-components/src/components/CaptionsBanner.tsx Co-authored-by: edwardlee-msft <[email protected]> Signed-off-by: Porter Nan <[email protected]> * Update CaptionsBanner.tsx Signed-off-by: Porter Nan <[email protected]> * Update api view * Fix linting problem --------- Signed-off-by: Porter Nan <[email protected]> Co-authored-by: edwardlee-msft <[email protected]>
1 parent db232bb commit 870d010

12 files changed

+141
-18
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"type": "minor",
3+
"area": "improvement",
4+
"workstream": "Full screen captions",
5+
"comment": "Caption fullscreen mode api",
6+
"packageName": "@azure/communication-react",
7+
"email": "[email protected]",
8+
"dependentChangeType": "patch"
9+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{
2+
"type": "minor",
3+
"area": "improvement",
4+
"workstream": "Full screen captions",
5+
"comment": "Caption fullscreen mode api",
6+
"packageName": "@azure/communication-react",
7+
"email": "[email protected]",
8+
"dependentChangeType": "patch"
9+
}

packages/communication-react/review/beta/communication-react.api.md

+3
Original file line numberDiff line numberDiff line change
@@ -728,6 +728,9 @@ export type CallCompositeIcons = {
728728

729729
// @public
730730
export type CallCompositeOptions = {
731+
captionsBanner?: {
732+
height: 'full' | 'default';
733+
};
731734
errorBar?: boolean;
732735
callControls?: boolean | CallControlOptions;
733736
deviceChecks?: DeviceCheckOptions;

packages/communication-react/review/stable/communication-react.api.md

+3
Original file line numberDiff line numberDiff line change
@@ -537,6 +537,9 @@ export type CallCompositeIcons = {
537537

538538
// @public
539539
export type CallCompositeOptions = {
540+
captionsBanner?: {
541+
height: 'full' | 'default';
542+
};
540543
errorBar?: boolean;
541544
callControls?: boolean | CallControlOptions;
542545
remoteVideoTileMenuOptions?: RemoteVideoTileMenuOptions;

packages/react-components/src/components/CaptionsBanner.tsx

+38-6
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
3-
import { Stack, FocusZone, Spinner } from '@fluentui/react';
3+
import { Stack, FocusZone, Spinner, useTheme } from '@fluentui/react';
44
import React, { useEffect, useRef, useState, useCallback } from 'react';
55
import { _Caption } from './Caption';
66
import {
77
captionContainerClassName,
88
captionsBannerClassName,
9+
captionsBannerFullHeightClassName,
910
captionsContainerClassName,
11+
loadingBannerFullHeightStyles,
1012
loadingBannerStyles
1113
} from './styles/Captions.style';
1214
import { OnRenderAvatarCallback } from '../types';
@@ -50,16 +52,30 @@ export interface _CaptionsBannerProps {
5052
* @defaultValue 'default'
5153
*/
5254
formFactor?: 'default' | 'compact';
55+
captionsOptions?: {
56+
height: 'full' | 'default';
57+
};
5358
}
5459

60+
const SCROLL_OFFSET_ALLOWANCE = 20;
61+
5562
/**
5663
* @internal
5764
* A component for displaying a CaptionsBanner with user icon, displayName and captions text.
5865
*/
5966
export const _CaptionsBanner = (props: _CaptionsBannerProps): JSX.Element => {
60-
const { captions, isCaptionsOn, startCaptionsInProgress, onRenderAvatar, strings, formFactor = 'default' } = props;
67+
const {
68+
captions,
69+
isCaptionsOn,
70+
startCaptionsInProgress,
71+
onRenderAvatar,
72+
strings,
73+
formFactor = 'default',
74+
captionsOptions
75+
} = props;
6176
const captionsScrollDivRef = useRef<HTMLDivElement>(null);
6277
const [isAtBottomOfScroll, setIsAtBottomOfScroll] = useState<boolean>(true);
78+
const theme = useTheme();
6379

6480
const scrollToBottom = (): void => {
6581
if (captionsScrollDivRef.current) {
@@ -73,7 +89,7 @@ export const _CaptionsBanner = (props: _CaptionsBannerProps): JSX.Element => {
7389
}
7490
const atBottom =
7591
Math.ceil(captionsScrollDivRef.current.scrollTop) >=
76-
captionsScrollDivRef.current.scrollHeight - captionsScrollDivRef.current.clientHeight;
92+
captionsScrollDivRef.current.scrollHeight - captionsScrollDivRef.current.clientHeight - SCROLL_OFFSET_ALLOWANCE;
7793

7894
setIsAtBottomOfScroll(atBottom);
7995
}, []);
@@ -97,9 +113,17 @@ export const _CaptionsBanner = (props: _CaptionsBannerProps): JSX.Element => {
97113
return (
98114
<>
99115
{startCaptionsInProgress && (
100-
<FocusZone as="ul" className={captionsContainerClassName}>
116+
<FocusZone as="ul" className={captionsContainerClassName} data-ui-id="captions-banner">
101117
{isCaptionsOn && (
102-
<div ref={captionsScrollDivRef} className={captionsBannerClassName(formFactor)}>
118+
<div
119+
ref={captionsScrollDivRef}
120+
className={
121+
captionsOptions?.height === 'full'
122+
? captionsBannerFullHeightClassName(theme)
123+
: captionsBannerClassName(formFactor)
124+
}
125+
data-ui-id="captions-banner-inner"
126+
>
103127
{captions.map((caption) => {
104128
return (
105129
<div key={caption.id} className={captionContainerClassName} data-is-focusable={true}>
@@ -110,7 +134,15 @@ export const _CaptionsBanner = (props: _CaptionsBannerProps): JSX.Element => {
110134
</div>
111135
)}
112136
{!isCaptionsOn && (
113-
<Stack verticalAlign="center" styles={loadingBannerStyles(formFactor)} data-is-focusable={true}>
137+
<Stack
138+
verticalAlign="center"
139+
styles={
140+
captionsOptions?.height === 'full'
141+
? loadingBannerFullHeightStyles(theme)
142+
: loadingBannerStyles(formFactor)
143+
}
144+
data-is-focusable={true}
145+
>
114146
<Spinner label={strings?.captionsBannerSpinnerText} ariaLive="assertive" labelPosition="right" />
115147
</Stack>
116148
)}

packages/react-components/src/components/styles/Captions.style.ts

+32-1
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
// Copyright (c) Microsoft Corporation.
22
// Licensed under the MIT License.
33

4-
import { IStackStyles, mergeStyles } from '@fluentui/react';
4+
import { IStackStyles, ITheme, mergeStyles } from '@fluentui/react';
55
import { _pxToRem } from '@internal/acs-ui-common';
66
import { scrollbarStyles } from './Common.style';
77

@@ -62,6 +62,22 @@ export const captionsBannerClassName = (formFactor: 'default' | 'compact'): stri
6262
});
6363
};
6464

65+
/**
66+
* @private
67+
*/
68+
export const captionsBannerFullHeightClassName = (theme: ITheme): string => {
69+
return mergeStyles({
70+
overflowX: 'hidden',
71+
overflowY: 'auto',
72+
height: '100%',
73+
width: '100%',
74+
position: 'absolute',
75+
backgroundColor: theme.palette.white,
76+
left: 0,
77+
...scrollbarStyles
78+
});
79+
};
80+
6581
/**
6682
* @private
6783
*/
@@ -73,6 +89,21 @@ export const loadingBannerStyles = (formFactor: 'default' | 'compact'): IStackSt
7389
};
7490
};
7591

92+
/**
93+
* @private
94+
*/
95+
export const loadingBannerFullHeightStyles = (theme: ITheme): IStackStyles => {
96+
return {
97+
root: {
98+
height: '100%',
99+
width: '100%',
100+
position: 'absolute',
101+
left: 0,
102+
backgroundColor: theme.palette.white
103+
}
104+
};
105+
};
106+
76107
/**
77108
* @private
78109
*/

packages/react-composites/src/composites/CallComposite/CallComposite.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -153,6 +153,9 @@ export interface LocalVideoTileOptions {
153153
* @public
154154
*/
155155
export type CallCompositeOptions = {
156+
captionsBanner?: {
157+
height: 'full' | 'default';
158+
};
156159
/**
157160
* Surface Azure Communication Services backend errors in the UI with {@link @azure/communication-react#ErrorBar}.
158161
* Hide or show the error bar.

packages/react-composites/src/composites/CallComposite/components/CallArrangement.tsx

+12-1
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,9 @@ export interface CallArrangementProps {
135135
pinnedParticipants?: string[];
136136
setPinnedParticipants?: (pinnedParticipants: string[]) => void;
137137
doNotShowCameraAccessNotifications?: boolean;
138+
captionsOptions?: {
139+
height: 'full' | 'default';
140+
};
138141
}
139142

140143
/**
@@ -462,6 +465,13 @@ export const CallArrangement = (props: CallArrangementProps): JSX.Element => {
462465
const minMaxDragPosition = useMinMaxDragPosition(props.modalLayerHostId);
463466
const pipStyles = useMemo(() => getPipStyles(theme), [theme]);
464467

468+
const galleryContainerStyles = useMemo(() => {
469+
return {
470+
...mediaGalleryContainerStyles,
471+
...(props?.captionsOptions?.height === 'full' ? { root: { postion: 'absolute' } } : {})
472+
};
473+
}, [props?.captionsOptions?.height]);
474+
465475
if (isTeamsMeeting) {
466476
filteredLatestErrorNotifications
467477
.filter((notification) => notification.type === 'teamsMeetingCallNetworkQualityLow')
@@ -583,7 +593,7 @@ export const CallArrangement = (props: CallArrangementProps): JSX.Element => {
583593
<Stack horizontal grow>
584594
<Stack.Item style={callCompositeContainerCSS}>
585595
<Stack.Item styles={callGalleryStyles} grow>
586-
<Stack verticalFill styles={mediaGalleryContainerStyles}>
596+
<Stack verticalFill styles={galleryContainerStyles}>
587597
<Stack.Item styles={notificationsContainerStyles}>
588598
{
589599
/* @conditional-compile-remove(breakout-rooms) */
@@ -621,6 +631,7 @@ export const CallArrangement = (props: CallArrangementProps): JSX.Element => {
621631
{true &&
622632
/* @conditional-compile-remove(PSTN-calls) */ /* @conditional-compile-remove(one-to-n-calling) */ !isInLocalHold && (
623633
<CaptionsBanner
634+
captionsOptions={props.captionsOptions}
624635
isMobile={props.mobileView}
625636
onFetchAvatarPersonaData={props.onFetchAvatarPersonaData}
626637
useTeamsCaptions={useTeamsCaptions}

packages/react-composites/src/composites/CallComposite/components/MediaGallery.tsx

+15-6
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,9 @@ export interface MediaGalleryProps {
6666
setPromptProps: (props: PromptProps) => void;
6767
hideSpotlightButtons?: boolean;
6868
videoTilesOptions?: VideoTilesOptions;
69+
captionsOptions?: {
70+
height: 'full' | 'default';
71+
};
6972
}
7073

7174
/**
@@ -78,7 +81,8 @@ export const MediaGallery = (props: MediaGalleryProps): JSX.Element => {
7881
setIsPromptOpen,
7982
setPromptProps,
8083
hideSpotlightButtons,
81-
videoTilesOptions
84+
videoTilesOptions,
85+
captionsOptions
8286
} = props;
8387

8488
const videoGalleryProps = usePropsFor(VideoGallery);
@@ -160,6 +164,10 @@ export const MediaGallery = (props: MediaGalleryProps): JSX.Element => {
160164
setPromptProps
161165
);
162166

167+
const galleryStyles = useMemo(() => {
168+
return { ...VideoGalleryStyles, ...(captionsOptions?.height === 'full' ? { root: { postion: 'absolute' } } : {}) };
169+
}, [captionsOptions?.height]);
170+
163171
const onPinParticipant = useMemo(() => {
164172
return setPinnedParticipants
165173
? (userId: string) => {
@@ -190,7 +198,7 @@ export const MediaGallery = (props: MediaGalleryProps): JSX.Element => {
190198
videoTilesOptions={videoTilesOptions}
191199
localVideoViewOptions={localVideoViewOptions}
192200
remoteVideoViewOptions={remoteVideoViewOptions}
193-
styles={VideoGalleryStyles}
201+
styles={galleryStyles}
194202
layout={layoutBasedOnUserSelection()}
195203
showCameraSwitcherInLocalPreview={props.isMobile}
196204
localVideoCameraCycleButtonProps={cameraSwitcherProps}
@@ -222,27 +230,28 @@ export const MediaGallery = (props: MediaGalleryProps): JSX.Element => {
222230
);
223231
}, [
224232
videoGalleryProps,
233+
videoTilesOptions,
234+
galleryStyles,
225235
props.isMobile,
226236
props.localVideoTileOptions,
237+
props.userSetGalleryLayout,
227238
cameraSwitcherProps,
228239
onRenderAvatar,
229240
remoteVideoTileMenuOptions,
230241
overflowGalleryPosition,
231242
userRole,
232243
isRoomsCall,
233244
containerAspectRatio,
234-
props.userSetGalleryLayout,
235245
pinnedParticipants,
236246
onPinParticipant,
237247
onUnpinParticipant,
238-
layoutBasedOnTilePosition,
239248
reactionResources,
249+
hideSpotlightButtons,
240250
onStartLocalSpotlightWithPrompt,
241251
onStopLocalSpotlightWithPrompt,
242252
onStartRemoteSpotlightWithPrompt,
243253
onStopRemoteSpotlightWithPrompt,
244-
hideSpotlightButtons,
245-
videoTilesOptions
254+
layoutBasedOnTilePosition
246255
]);
247256

248257
return (

packages/react-composites/src/composites/CallComposite/pages/CallPage.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ export const CallPage = (props: CallPageProps): JSX.Element => {
157157
setPromptProps={setPromptProps}
158158
hideSpotlightButtons={options?.spotlight?.hideSpotlightButtons}
159159
videoTilesOptions={options?.videoTilesOptions}
160+
captionsOptions={options?.captionsBanner}
160161
/>
161162
);
162163
}
@@ -211,6 +212,7 @@ export const CallPage = (props: CallPageProps): JSX.Element => {
211212
setPinnedParticipants={setPinnedParticipants}
212213
/* @conditional-compile-remove(call-readiness) */
213214
doNotShowCameraAccessNotifications={props.options?.deviceChecks?.camera === 'doNotPrompt'}
215+
captionsOptions={options?.captionsBanner}
214216
/>
215217
{<Prompt isOpen={isPromptOpen} onDismiss={() => setIsPromptOpen(false)} {...promptProps} />}
216218
</>

packages/react-composites/src/composites/CallComposite/styles/CallPage.styles.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,8 @@ export const galleryParentContainerStyles = (backgroundColor: string): IStackSty
5959
*/
6060
export const mediaGalleryContainerStyles: IStackItemStyles = {
6161
root: {
62-
height: '100%'
62+
height: '100%',
63+
width: '100%'
6364
}
6465
};
6566

packages/react-composites/src/composites/common/CaptionsBanner.tsx

+13-3
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ export const CaptionsBanner = (props: {
2121
isMobile: boolean;
2222
useTeamsCaptions?: boolean;
2323
onFetchAvatarPersonaData?: AvatarPersonaDataCallback;
24+
captionsOptions?: {
25+
height: 'full' | 'default';
26+
};
2427
}): JSX.Element => {
2528
const captionsBannerProps = useAdaptedSelector(_captionsBannerSelector);
2629

@@ -36,9 +39,15 @@ export const CaptionsBanner = (props: {
3639
setIsCaptionsSettingsOpen(false);
3740
};
3841

39-
const containerClassName = mergeStyles({
40-
position: 'relative'
41-
});
42+
const containerClassName = mergeStyles(
43+
props.captionsOptions?.height === 'full'
44+
? mergeStyles({
45+
position: 'absolute',
46+
height: '100%',
47+
width: '100%'
48+
})
49+
: { position: 'relative' }
50+
);
4251

4352
const floatingChildClassName = mergeStyles({
4453
position: 'absolute',
@@ -90,6 +99,7 @@ export const CaptionsBanner = (props: {
9099
<_CaptionsBanner
91100
{...captionsBannerProps}
92101
{...handlers}
102+
captionsOptions={props.captionsOptions}
93103
onRenderAvatar={onRenderAvatar}
94104
formFactor={props.isMobile ? 'compact' : 'default'}
95105
strings={captionsBannerStrings}

0 commit comments

Comments
 (0)