Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add a prop to control mirroring of local participant video #1506

Merged
merged 5 commits into from
Oct 3, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
myandrienko marked this conversation as resolved.
Show resolved Hide resolved
Original file line number Diff line number Diff line change
Expand Up @@ -77,26 +77,28 @@ const MyApp = () => {

#### Props

| Name | Description | Type |
| ------------------------- | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------ |
| `groupSize` | The number of participants to display per page | `number` \| `undefined` |
| `excludeLocalParticipant` | Whether to exclude the local participant from the grid | `boolean` \| `undefined` |
| `pageArrowsVisible` | Turns on/off the pagination arrows | `boolean` \| `undefined` |
| `ParticipantViewUI` | [See `ParticipantView` documentation](../../../ui-components/core/participant-view/#participantviewui) | [See `ParticipantView` documentation](../../../ui-components/core/participant-view/#participantviewui) |
| `VideoPlaceholder` | [See `ParticipantView` documentation](../../../ui-components/core/participant-view/#videoplaceholder) | [See `ParticipantView` documentation](../../../ui-components/core/participant-view/#videoplaceholder) |
| Name | Description | Type |
| ----------------------------- | ------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------------------------------ |
| `groupSize` | The number of participants to display per page | `number` \| `undefined` |
| `excludeLocalParticipant` | Whether to exclude the local participant from the grid | `boolean` \| `undefined` |
| `mirrorLocalParticipantVideo` | Whether to mirror the user's own video (default `true`) | `boolean` \| `undefined` |
| `pageArrowsVisible` | Turns on/off the pagination arrows | `boolean` \| `undefined` |
| `ParticipantViewUI` | [See `ParticipantView` documentation](../../../ui-components/core/participant-view/#participantviewui) | [See `ParticipantView` documentation](../../../ui-components/core/participant-view/#participantviewui) |
| `VideoPlaceholder` | [See `ParticipantView` documentation](../../../ui-components/core/participant-view/#videoplaceholder) | [See `ParticipantView` documentation](../../../ui-components/core/participant-view/#videoplaceholder) |

### `SpeakerLayout`

#### Props

| Name | Description | Type |
| ---------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `participantsBarPosition` | The position of the participants who are not in focus, the default is `bottom`. Providing `null` will hide the bar | `top` \| `bottom` \| `left` \| `right` \| `null` |
| `excludeLocalParticipant` | Whether to exclude the local participant from the layout | `boolean` \| `undefined` |
| `pageArrowsVisible` | Turns on/off the pagination arrows | `boolean` \| `undefined` |
| `ParticipantViewUISpotlight` | The participant UI for the spotlight view, [see `ParticipantView` documentation](../../../ui-components/core/participant-view/#participantviewui) | [See `ParticipantView` documentation](../../../ui-components/core/participant-view/#participantviewui) |
| `ParticipantViewUIBar` | The participant UI for the participants in the bar, [see `ParticipantView` documentation](../../../ui-components/core/participant-view/#participantviewui) | [See `ParticipantView` documentation](../../../ui-components/core/participant-view/#participantviewui) |
| `VideoPlaceholder` | [See `ParticipantView` documentation](../../../ui-components/core/participant-view/#videoplaceholder) | [See `ParticipantView` documentation](../../../ui-components/core/participant-view/#videoplaceholder) |
| Name | Description | Type |
| ----------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------ |
| `participantsBarPosition` | The position of the participants who are not in focus, the default is `bottom`. Providing `null` will hide the bar | `top` \| `bottom` \| `left` \| `right` \| `null` |
| `excludeLocalParticipant` | Whether to exclude the local participant from the layout | `boolean` \| `undefined` |
| `mirrorLocalParticipantVideo` | Whether to mirror the user's own video (default `true`) | `boolean` \| `undefined` |
| `pageArrowsVisible` | Turns on/off the pagination arrows | `boolean` \| `undefined` |
| `ParticipantViewUISpotlight` | The participant UI for the spotlight view, [see `ParticipantView` documentation](../../../ui-components/core/participant-view/#participantviewui) | [See `ParticipantView` documentation](../../../ui-components/core/participant-view/#participantviewui) |
| `ParticipantViewUIBar` | The participant UI for the participants in the bar, [see `ParticipantView` documentation](../../../ui-components/core/participant-view/#participantviewui) | [See `ParticipantView` documentation](../../../ui-components/core/participant-view/#participantviewui) |
| `VideoPlaceholder` | [See `ParticipantView` documentation](../../../ui-components/core/participant-view/#videoplaceholder) | [See `ParticipantView` documentation](../../../ui-components/core/participant-view/#videoplaceholder) |

### `LivestreamLayout`

Expand All @@ -110,6 +112,7 @@ const MyApp = () => {
| `showDuration` | Will show the duration of the livestream | `boolean` |
| `showLiveBadge` | Will show a badge whether the livestream is live or not | `boolean` |
| `showSpeakerName` | Will show the name of the speaker | `boolean` |
| `mirrorLocalParticipantVideo` | Whether to mirror the user's own video (default `true`) | `boolean` |
| `floatingParticipantProps` | Props to pass to the floating participant view | `object` |
| `floatingParticipantProps.position` | Position of the floating participant view | `string` |

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -126,6 +126,16 @@ const App = ({ client, callId }) => {
};
```

### Non-mirrored video

By default, local participant video (video feed from the user's own camera) is mirrored, since people are generally more accustomed to seeing themselves that way. It's possible to disable this behavior using the `mirrorLocalParticipantVideo` prop, which is supported by all built-in layout components:

```tsx
<SpeakerLayout mirrorLocalParticipantVideo={false} />
// or
<PaginatedGridLayout mirrorLocalParticipantVideo={false} />
```

## Standalone ParticipantView customization (in custom call layouts)

The `ParticipantViewUI` property accepts three possible values:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,12 @@ export type LivestreamLayoutProps = {
*/
showSpeakerName?: boolean;

/**
* When set to `false` disables mirroring of the local partipant's video.
* @default true
*/
mirrorLocalParticipantVideo?: boolean;

/**
* The props to pass to the floating participant element.
*/
Expand Down Expand Up @@ -116,6 +122,9 @@ export const LivestreamLayout = (props: LivestreamLayoutProps) => {
)}
participant={currentSpeaker}
ParticipantViewUI={FloatingParticipantOverlay || Overlay}
mirror={
props.mirrorLocalParticipantVideo !== false ? undefined : false
}
muteAudio // audio is rendered by ParticipantsAudio
/>
)}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,12 @@ type PaginatedGridLayoutGroupProps = {
* The group of participants to render.
*/
group: Array<StreamVideoParticipant>;
} & Pick<ParticipantViewProps, 'VideoPlaceholder'> &
} & Pick<ParticipantViewProps, 'VideoPlaceholder' | 'mirror'> &
Required<Pick<ParticipantViewProps, 'ParticipantViewUI'>>;

const PaginatedGridLayoutGroup = ({
group,
mirror,
VideoPlaceholder,
ParticipantViewUI,
}: PaginatedGridLayoutGroupProps) => {
Expand All @@ -43,6 +44,7 @@ const PaginatedGridLayoutGroup = ({
key={participant.sessionId}
participant={participant}
muteAudio
mirror={mirror}
VideoPlaceholder={VideoPlaceholder}
ParticipantViewUI={ParticipantViewUI}
/>
Expand All @@ -63,6 +65,12 @@ export type PaginatedGridLayoutProps = {
*/
excludeLocalParticipant?: boolean;

/**
* When set to `false` disables mirroring of the local partipant's video.
* @default true
*/
mirrorLocalParticipantVideo?: boolean;

/**
* Turns on/off the pagination arrows.
* @default true
Expand All @@ -76,6 +84,7 @@ export const PaginatedGridLayout = (props: PaginatedGridLayoutProps) => {
? props.groupSize || GROUP_SIZE
: GROUP_SIZE,
excludeLocalParticipant = false,
mirrorLocalParticipantVideo = true,
pageArrowsVisible = true,
VideoPlaceholder,
ParticipantViewUI = DefaultParticipantViewUI,
Expand Down Expand Up @@ -121,6 +130,7 @@ export const PaginatedGridLayout = (props: PaginatedGridLayoutProps) => {
}, [page, pageCount]);

const selectedGroup = participantGroups[page];
const mirror = mirrorLocalParticipantVideo ? undefined : false;

if (!call) return null;

Expand All @@ -143,6 +153,7 @@ export const PaginatedGridLayout = (props: PaginatedGridLayoutProps) => {
{selectedGroup && (
<PaginatedGridLayoutGroup
group={selectedGroup}
mirror={mirror}
VideoPlaceholder={VideoPlaceholder}
ParticipantViewUI={ParticipantViewUI}
/>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,11 @@ export type SpeakerLayoutProps = {
* @default false
*/
excludeLocalParticipant?: boolean;
/**
* When set to `false` disables mirroring of the local partipant's video.
* @default true
*/
mirrorLocalParticipantVideo?: boolean;
/**
* Turns on/off the pagination arrows.
* @default true
Expand All @@ -58,6 +63,7 @@ export const SpeakerLayout = ({
VideoPlaceholder,
participantsBarPosition = 'bottom',
participantsBarLimit,
mirrorLocalParticipantVideo = true,
excludeLocalParticipant = false,
pageArrowsVisible = true,
}: SpeakerLayoutProps) => {
Expand Down Expand Up @@ -117,6 +123,8 @@ export const SpeakerLayout = ({
);
}

const mirror = mirrorLocalParticipantVideo ? undefined : false;

if (!call) return null;

return (
Expand All @@ -134,6 +142,7 @@ export const SpeakerLayout = ({
<ParticipantView
participant={participantInSpotlight}
muteAudio={true}
mirror={mirror}
trackType={
isSpeakerScreenSharing ? 'screenShareTrack' : 'videoTrack'
}
Expand Down Expand Up @@ -164,6 +173,7 @@ export const SpeakerLayout = ({
participant={participantInSpotlight}
ParticipantViewUI={ParticipantViewUIBar}
VideoPlaceholder={VideoPlaceholder}
mirror={mirror}
muteAudio={true}
/>
</div>
Expand All @@ -177,6 +187,7 @@ export const SpeakerLayout = ({
participant={participant}
ParticipantViewUI={ParticipantViewUIBar}
VideoPlaceholder={VideoPlaceholder}
mirror={mirror}
muteAudio={true}
/>
</div>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,12 @@ export type ParticipantViewProps = {
*/
trackType?: VideoTrackType | 'none';

/**
* Forces participant's video to be mirrored or unmirrored. By default, video track
* from the local participant is mirrored, and all other videos are not mirrored.
*/
mirror?: boolean;

/**
* This prop is only useful for advanced use-cases (for example, building your own layout).
* When set to `true` it will mute the give participant's audio stream on the client side.
Expand Down Expand Up @@ -69,6 +75,7 @@ export const ParticipantView = forwardRef<HTMLDivElement, ParticipantViewProps>(
{
participant,
trackType = 'videoTrack',
mirror,
muteAudio,
refs: { setVideoElement, setVideoPlaceholderElement } = {},
className,
Expand Down Expand Up @@ -176,6 +183,7 @@ export const ParticipantView = forwardRef<HTMLDivElement, ParticipantViewProps>(
trackType !== 'videoTrack' ||
isParticipantVideoEnabled(participant.sessionId)
}
mirror={mirror}
autoPlay
/>
{isComponentType(ParticipantViewUI) ? (
Expand Down
12 changes: 10 additions & 2 deletions packages/react-sdk/src/core/components/Video/Video.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,11 @@ export type VideoProps = ComponentPropsWithoutRef<'video'> & {
* even if the participant has published video.
*/
enabled?: boolean;

/**
* Forces the video to be mirrored or unmirrored. By default, video track
* from the local participant is mirrored, and all other videos are not mirrored.
*/
mirror?: boolean;
/**
* The track type to display.
*/
Expand Down Expand Up @@ -61,6 +65,7 @@ export type VideoProps = ComponentPropsWithoutRef<'video'> & {

export const Video = ({
enabled,
mirror,
trackType,
participant,
className,
Expand Down Expand Up @@ -147,7 +152,10 @@ export const Video = ({
viewportVisibilityState?.[trackType] === VisibilityState.INVISIBLE;

const hasNoVideoOrInvisible = !enabled || !isPublishingTrack || isInvisible;
const mirrorVideo = isLocalParticipant && trackType === 'videoTrack';
const mirrorVideo =
mirror === undefined
? isLocalParticipant && trackType === 'videoTrack'
: mirror;
const isScreenShareTrack = trackType === 'screenShareTrack';
return (
<>
Expand Down
Loading