diff --git a/src/room/InCallView.tsx b/src/room/InCallView.tsx index 5872cfb44..7fbb0205c 100644 --- a/src/room/InCallView.tsx +++ b/src/room/InCallView.tsx @@ -238,7 +238,7 @@ export const InCallView: FC = ({ // Merge the refs so they can attach to the same element const containerRef = useMergedRefs(containerRef1, containerRef2); - const { showControls, header: headerStyle } = useUrlParams(); + const { showControls, header: headerStyle, callIntent } = useUrlParams(); const muteAllAudio = useBehavior(muteAllAudio$); const toggleAudio = useBehavior(muteStates.audio.toggle$); @@ -503,6 +503,8 @@ export const InCallView: FC = ({ showRingingStatus={vm.ringingStatusLocation === "tile"} focusable={!contentObscured} aria-hidden={contentObscured} + // Use the Circular rendering of the audio tile in PiP + circularAudioTile={callIntent === "audio"} /> ); } diff --git a/src/tile/MediaView.module.css b/src/tile/MediaView.module.css index 5e7b3b28d..cf3ca370f 100644 --- a/src/tile/MediaView.module.css +++ b/src/tile/MediaView.module.css @@ -62,6 +62,42 @@ Please see LICENSE in the repository root for full details. 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 unconditionally select the container so we can use cqmin units */ @container mediaView (width > 0) { @@ -70,6 +106,13 @@ unconditionally select the container so we can use cqmin units */ inline-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 { diff --git a/src/tile/MediaView.tsx b/src/tile/MediaView.tsx index f860f41eb..165bcc88f 100644 --- a/src/tile/MediaView.tsx +++ b/src/tile/MediaView.tsx @@ -54,6 +54,9 @@ interface Props extends ComponentProps { rtcBackendIdentity?: string; // The focus url, mainly for debugging purposes focusUrl?: string; + circularAudioTile?: boolean; + // render a circular speaking indicator if circularAudioTile is enabled. + circularSpeakingIndicator?: boolean; } export const MediaView: FC = ({ @@ -84,6 +87,8 @@ export const MediaView: FC = ({ videoStreamStats, rtcBackendIdentity, focusUrl, + circularAudioTile, + circularSpeakingIndicator, ...props }) => { const { t } = useTranslation(); @@ -112,7 +117,9 @@ export const MediaView: FC = ({ return ( = ({ ...props }) => { const waitingForMedia = useBehavior(vm.waitingForMedia$); + const speaking = useBehavior(vm.speaking$); return ( - + ); }; @@ -243,6 +245,7 @@ interface SpotlightItemProps { targetHeight: number; showNameTags: boolean; showRingingStatus: boolean; + circularAudioTile?: boolean; focusable: boolean; intersectionObserver$: Observable; /** @@ -259,6 +262,7 @@ const SpotlightItem: FC = ({ targetHeight, showNameTags, showRingingStatus, + circularAudioTile, focusable, intersectionObserver$, snap, @@ -295,6 +299,7 @@ const SpotlightItem: FC = ({ displayName, mxcAvatarUrl, showNameTags, + circularAudioTile, focusable, "aria-hidden": ariaHidden, }; @@ -389,6 +394,7 @@ interface Props { showIndicators: boolean; showNameTags: boolean; showRingingStatus: boolean; + circularAudioTile?: boolean; focusable: boolean; className?: string; style?: ComponentProps["style"]; @@ -404,6 +410,7 @@ export const SpotlightTile: FC = ({ showIndicators, showNameTags, showRingingStatus, + circularAudioTile, focusable = true, className, style, @@ -517,6 +524,7 @@ export const SpotlightTile: FC = ({ showRingingStatus={showRingingStatus} showNameTags={showNameTags} focusable={focusable} + circularAudioTile={circularAudioTile} intersectionObserver$={intersectionObserver$} // This is how we get the container to scroll to the right media // when the previous/next buttons are clicked: we temporarily