packages/storybook8/stories/Concepts/Spotlight/snippets/CustomSpotlightComponents.snippet.tsx (147 lines of code) (raw):
import { Call, CallAgent, Features, MeetingLocator } from '@azure/communication-calling';
import { AzureCommunicationTokenCredential, CommunicationUserIdentifier } from '@azure/communication-common';
import {
CallAgentProvider,
CallClientProvider,
CallProvider,
CameraButton,
CompositeLocale,
ControlBar,
EndCallButton,
FluentThemeProvider,
MicrophoneButton,
ScreenShareButton,
StatefulCallClient,
VideoGallery,
VideoStreamOptions,
createStatefulCallClient,
fromFlatCommunicationIdentifier,
toFlatCommunicationIdentifier,
useCall,
usePropsFor
} from '@azure/communication-react';
import { Dropdown, IDropdownOption, PartialTheme, PrimaryButton, Stack, Theme, mergeStyles } from '@fluentui/react';
import React, { useEffect, useState } from 'react';
export type ContainerProps = {
userId: CommunicationUserIdentifier;
token: string;
formFactor?: 'desktop' | 'mobile';
fluentTheme?: PartialTheme | Theme;
locale?: CompositeLocale;
meetingLink?: string;
displayName?: string;
};
export const ContosoCallContainer = (props: ContainerProps): JSX.Element => {
const [statefulCallClient, setStatefulCallClient] = useState<StatefulCallClient>();
const [callAgent, setCallAgent] = useState<CallAgent>();
const [call, setCall] = useState<Call>();
useEffect(() => {
const statefulCallClient = createStatefulCallClient({
userId: { communicationUserId: toFlatCommunicationIdentifier(props.userId) }
});
// Request camera and microphone access once we have access to the device manager
statefulCallClient.getDeviceManager().then((deviceManager) => {
deviceManager.askDevicePermission({ video: true, audio: true });
});
setStatefulCallClient(statefulCallClient);
}, [props.userId]);
useEffect(() => {
const tokenCredential = new AzureCommunicationTokenCredential(props.token);
if (callAgent === undefined && statefulCallClient && props.displayName) {
const createUserAgent = async (): Promise<void> => {
setCallAgent(await statefulCallClient.createCallAgent(tokenCredential, { displayName: props.displayName }));
};
createUserAgent();
}
}, [props.token, statefulCallClient, props.displayName, callAgent]);
useEffect(() => {
if (callAgent !== undefined) {
setCall(callAgent.join({ meetingLink: props.meetingLink } as MeetingLocator));
}
}, [callAgent, props.meetingLink]);
return (
<FluentThemeProvider>
<>
{statefulCallClient && (
<CallClientProvider callClient={statefulCallClient}>
{callAgent && (
<CallAgentProvider callAgent={callAgent}>
{call && (
<CallProvider call={call}>
<CallingComponents />
</CallProvider>
)}
</CallAgentProvider>
)}
</CallClientProvider>
)}
</>
</FluentThemeProvider>
);
};
const CallingComponents = (): JSX.Element => {
const [selectedParticipants, setSelectedParticipants] = useState<string[]>([]);
const videoGalleryProps = usePropsFor(VideoGallery);
const cameraProps = usePropsFor(CameraButton);
const microphoneProps = usePropsFor(MicrophoneButton);
const screenShareProps = usePropsFor(ScreenShareButton);
const endCallProps = usePropsFor(EndCallButton);
const call = useCall();
// Only enable buttons when the call has connected.
// For more advanced handling of pre-call configuration, see our other samples such as [Call Readiness](../../ui-library-call-readiness/README.md)
const buttonsDisabled = !(call?.state === 'InLobby' || call?.state === 'Connected');
if (call?.state === 'Disconnected') {
return <CallEnded />;
}
const onChange = (_: React.FormEvent<HTMLDivElement>, item?: IDropdownOption): void => {
if (item) {
setSelectedParticipants(
item.selected
? [...selectedParticipants, item.key as string]
: selectedParticipants.filter((key) => key !== item.key)
);
}
};
return (
<Stack className={mergeStyles({ height: '100%' })}>
<div style={{ width: '100vw', height: '100vh' }}>
{videoGalleryProps && <VideoGallery {...videoGalleryProps} localVideoViewOptions={localViewVideoOptions} />}
</div>
<ControlBar layout="floatingBottom">
{cameraProps && <CameraButton {...cameraProps} disabled={buttonsDisabled ?? cameraProps.disabled} />}
{microphoneProps && (
<MicrophoneButton {...microphoneProps} disabled={buttonsDisabled ?? microphoneProps.disabled} />
)}
{screenShareProps && <ScreenShareButton {...screenShareProps} disabled={buttonsDisabled} />}
{endCallProps && <EndCallButton {...endCallProps} disabled={buttonsDisabled} />}
<Dropdown
placeholder="Select participants to spotlight"
label="Spotlight participants"
selectedKeys={selectedParticipants}
onChange={onChange}
multiSelect
options={videoGalleryProps.remoteParticipants.map((participant) => ({
key: participant.userId,
text: participant.displayName ?? 'Unnamed participant'
}))}
/>
<PrimaryButton
onClick={() => {
if (call && selectedParticipants && selectedParticipants.length > 0) {
call
.feature(Features.Spotlight)
.startSpotlight(selectedParticipants.map((p) => fromFlatCommunicationIdentifier(p)));
}
}}
disabled={!call || !selectedParticipants || selectedParticipants.length === 0}
>
Spotlight participant(s)
</PrimaryButton>
</ControlBar>
</Stack>
);
};
const localViewVideoOptions: VideoStreamOptions = {
isMirrored: true,
scalingMode: 'Fit'
};
const CallEnded = (): JSX.Element => {
return <h1>You ended the call.</h1>;
};