mirror of
https://github.com/vector-im/element-call.git
synced 2026-08-29 21:15:19 +00:00
Voice call Pip circular media view
This commit is contained in:
@@ -238,7 +238,7 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
// Merge the refs so they can attach to the same element
|
// Merge the refs so they can attach to the same element
|
||||||
const containerRef = useMergedRefs(containerRef1, containerRef2);
|
const containerRef = useMergedRefs(containerRef1, containerRef2);
|
||||||
|
|
||||||
const { showControls, header: headerStyle } = useUrlParams();
|
const { showControls, header: headerStyle, callIntent } = useUrlParams();
|
||||||
|
|
||||||
const muteAllAudio = useBehavior(muteAllAudio$);
|
const muteAllAudio = useBehavior(muteAllAudio$);
|
||||||
const toggleAudio = useBehavior(muteStates.audio.toggle$);
|
const toggleAudio = useBehavior(muteStates.audio.toggle$);
|
||||||
@@ -503,6 +503,8 @@ export const InCallView: FC<InCallViewProps> = ({
|
|||||||
showRingingStatus={vm.ringingStatusLocation === "tile"}
|
showRingingStatus={vm.ringingStatusLocation === "tile"}
|
||||||
focusable={!contentObscured}
|
focusable={!contentObscured}
|
||||||
aria-hidden={contentObscured}
|
aria-hidden={contentObscured}
|
||||||
|
// Use the Circular rendering of the audio tile in PiP
|
||||||
|
circularAudioTile={callIntent === "audio"}
|
||||||
/>
|
/>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -62,6 +62,42 @@ Please see LICENSE in the repository root for full details.
|
|||||||
opacity: 50%;
|
opacity: 50%;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* When no video is shown, circular media has no tile box at all: just the
|
||||||
|
avatar rendered on the app background */
|
||||||
|
.media.circular .bg {
|
||||||
|
background-color: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* A disc behind the avatar, poking out around its edge to form a circular
|
||||||
|
speaking indicator. It uses the same gradient as the speaking border of grid
|
||||||
|
tiles. */
|
||||||
|
.media.circular .bg::before {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
border-radius: 50%;
|
||||||
|
background:
|
||||||
|
linear-gradient(
|
||||||
|
119deg,
|
||||||
|
rgba(13, 92, 189, 0.7) 0%,
|
||||||
|
rgba(13, 189, 168, 0.7) 100%
|
||||||
|
),
|
||||||
|
linear-gradient(
|
||||||
|
180deg,
|
||||||
|
rgba(13, 92, 189, 0.9) 0%,
|
||||||
|
rgba(13, 189, 168, 0.9) 100%
|
||||||
|
);
|
||||||
|
background-blend-mode: overlay, normal;
|
||||||
|
opacity: 0; /* Hidden unless speaking */
|
||||||
|
transition: opacity ease 0.15s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.media.circular.speaking .bg::before {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
/* CSS makes us put a condition here, even though all we want to do is
|
/* CSS makes us put a condition here, even though all we want to do is
|
||||||
unconditionally select the container so we can use cqmin units */
|
unconditionally select the container so we can use cqmin units */
|
||||||
@container mediaView (width > 0) {
|
@container mediaView (width > 0) {
|
||||||
@@ -70,6 +106,13 @@ unconditionally select the container so we can use cqmin units */
|
|||||||
inline-size: 50cqmin;
|
inline-size: 50cqmin;
|
||||||
block-size: 50cqmin;
|
block-size: 50cqmin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.media.circular .bg::before {
|
||||||
|
/* Sized so that the disc extends beyond the avatar by the width of the
|
||||||
|
speaking indicator */
|
||||||
|
inline-size: calc(50cqmin + 2 * var(--cpd-border-width-4));
|
||||||
|
block-size: calc(50cqmin + 2 * var(--cpd-border-width-4));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar > img {
|
.avatar > img {
|
||||||
|
|||||||
@@ -54,6 +54,9 @@ interface Props extends ComponentProps<typeof animated.div> {
|
|||||||
rtcBackendIdentity?: string;
|
rtcBackendIdentity?: string;
|
||||||
// The focus url, mainly for debugging purposes
|
// The focus url, mainly for debugging purposes
|
||||||
focusUrl?: string;
|
focusUrl?: string;
|
||||||
|
circularAudioTile?: boolean;
|
||||||
|
// render a circular speaking indicator if circularAudioTile is enabled.
|
||||||
|
circularSpeakingIndicator?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const MediaView: FC<Props> = ({
|
export const MediaView: FC<Props> = ({
|
||||||
@@ -84,6 +87,8 @@ export const MediaView: FC<Props> = ({
|
|||||||
videoStreamStats,
|
videoStreamStats,
|
||||||
rtcBackendIdentity,
|
rtcBackendIdentity,
|
||||||
focusUrl,
|
focusUrl,
|
||||||
|
circularAudioTile,
|
||||||
|
circularSpeakingIndicator,
|
||||||
...props
|
...props
|
||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
@@ -112,7 +117,9 @@ export const MediaView: FC<Props> = ({
|
|||||||
return (
|
return (
|
||||||
<animated.div
|
<animated.div
|
||||||
className={classNames(styles.media, className, {
|
className={classNames(styles.media, className, {
|
||||||
|
[styles.circular]: circularAudioTile && !videoEnabled,
|
||||||
[styles.mirror]: mirror,
|
[styles.mirror]: mirror,
|
||||||
|
[styles.speaking]: circularSpeakingIndicator,
|
||||||
})}
|
})}
|
||||||
style={style}
|
style={style}
|
||||||
ref={ref}
|
ref={ref}
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ interface SpotlightItemBaseProps {
|
|||||||
mxcAvatarUrl: string | undefined;
|
mxcAvatarUrl: string | undefined;
|
||||||
showNameTags: boolean;
|
showNameTags: boolean;
|
||||||
focusable: boolean;
|
focusable: boolean;
|
||||||
|
circularAudioTile?: boolean;
|
||||||
"aria-hidden"?: boolean;
|
"aria-hidden"?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -103,8 +104,9 @@ const SpotlightRemoteUserMediaItem: FC<SpotlightRemoteUserMediaItemProps> = ({
|
|||||||
...props
|
...props
|
||||||
}) => {
|
}) => {
|
||||||
const waitingForMedia = useBehavior(vm.waitingForMedia$);
|
const waitingForMedia = useBehavior(vm.waitingForMedia$);
|
||||||
|
const speaking = useBehavior(vm.speaking$);
|
||||||
return (
|
return (
|
||||||
<MediaView waitingForMedia={waitingForMedia} mirror={false} {...props} />
|
<MediaView waitingForMedia={waitingForMedia} mirror={false} {...props} circularSpeakingIndicator={speaking} />
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -243,6 +245,7 @@ interface SpotlightItemProps {
|
|||||||
targetHeight: number;
|
targetHeight: number;
|
||||||
showNameTags: boolean;
|
showNameTags: boolean;
|
||||||
showRingingStatus: boolean;
|
showRingingStatus: boolean;
|
||||||
|
circularAudioTile?: boolean;
|
||||||
focusable: boolean;
|
focusable: boolean;
|
||||||
intersectionObserver$: Observable<IntersectionObserver>;
|
intersectionObserver$: Observable<IntersectionObserver>;
|
||||||
/**
|
/**
|
||||||
@@ -259,6 +262,7 @@ const SpotlightItem: FC<SpotlightItemProps> = ({
|
|||||||
targetHeight,
|
targetHeight,
|
||||||
showNameTags,
|
showNameTags,
|
||||||
showRingingStatus,
|
showRingingStatus,
|
||||||
|
circularAudioTile,
|
||||||
focusable,
|
focusable,
|
||||||
intersectionObserver$,
|
intersectionObserver$,
|
||||||
snap,
|
snap,
|
||||||
@@ -295,6 +299,7 @@ const SpotlightItem: FC<SpotlightItemProps> = ({
|
|||||||
displayName,
|
displayName,
|
||||||
mxcAvatarUrl,
|
mxcAvatarUrl,
|
||||||
showNameTags,
|
showNameTags,
|
||||||
|
circularAudioTile,
|
||||||
focusable,
|
focusable,
|
||||||
"aria-hidden": ariaHidden,
|
"aria-hidden": ariaHidden,
|
||||||
};
|
};
|
||||||
@@ -389,6 +394,7 @@ interface Props {
|
|||||||
showIndicators: boolean;
|
showIndicators: boolean;
|
||||||
showNameTags: boolean;
|
showNameTags: boolean;
|
||||||
showRingingStatus: boolean;
|
showRingingStatus: boolean;
|
||||||
|
circularAudioTile?: boolean;
|
||||||
focusable: boolean;
|
focusable: boolean;
|
||||||
className?: string;
|
className?: string;
|
||||||
style?: ComponentProps<typeof animated.div>["style"];
|
style?: ComponentProps<typeof animated.div>["style"];
|
||||||
@@ -404,6 +410,7 @@ export const SpotlightTile: FC<Props> = ({
|
|||||||
showIndicators,
|
showIndicators,
|
||||||
showNameTags,
|
showNameTags,
|
||||||
showRingingStatus,
|
showRingingStatus,
|
||||||
|
circularAudioTile,
|
||||||
focusable = true,
|
focusable = true,
|
||||||
className,
|
className,
|
||||||
style,
|
style,
|
||||||
@@ -517,6 +524,7 @@ export const SpotlightTile: FC<Props> = ({
|
|||||||
showRingingStatus={showRingingStatus}
|
showRingingStatus={showRingingStatus}
|
||||||
showNameTags={showNameTags}
|
showNameTags={showNameTags}
|
||||||
focusable={focusable}
|
focusable={focusable}
|
||||||
|
circularAudioTile={circularAudioTile}
|
||||||
intersectionObserver$={intersectionObserver$}
|
intersectionObserver$={intersectionObserver$}
|
||||||
// This is how we get the container to scroll to the right media
|
// This is how we get the container to scroll to the right media
|
||||||
// when the previous/next buttons are clicked: we temporarily
|
// when the previous/next buttons are clicked: we temporarily
|
||||||
|
|||||||
Reference in New Issue
Block a user