mirror of
https://github.com/vector-im/element-call.git
synced 2026-07-18 18:59:23 +00:00
Add "sound waves" to transparent spotlight tiles
To indicate when the user is speaking.
This commit is contained in:
@@ -51,6 +51,54 @@ Please see LICENSE in the repository root for full details.
|
|||||||
background-color: var(--video-tile-background);
|
background-color: var(--video-tile-background);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.waves {
|
||||||
|
transition: opacity ease 0.3s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waves[data-visible="true"] {
|
||||||
|
opacity: 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
.waves[data-visible="false"] {
|
||||||
|
opacity: 0;
|
||||||
|
|
||||||
|
@media not (prefers-reduced-motion) {
|
||||||
|
.wave {
|
||||||
|
transform: translate(-50%, -50%) scale(0.9);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.wave {
|
||||||
|
border: var(--cpd-border-width-1) solid var(--cpd-color-alpha-gray-300);
|
||||||
|
transition: transform ease 0.2s;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wave,
|
||||||
|
.speakingBorder {
|
||||||
|
border-radius: var(--cpd-radius-pill-effect);
|
||||||
|
position: absolute;
|
||||||
|
top: 50%;
|
||||||
|
left: 50%;
|
||||||
|
transform: translate(-50%, -50%);
|
||||||
|
}
|
||||||
|
|
||||||
|
.speakingBorder {
|
||||||
|
background:
|
||||||
|
radial-gradient(#0467dd, #0bc491),
|
||||||
|
linear-gradient(0deg, #0467dd 0%, #0bc491 100%);
|
||||||
|
background-blend-mode: overlay, normal;
|
||||||
|
outline: var(--cpd-border-width-4) solid var(--cpd-color-bg-canvas-default);
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
content: "";
|
||||||
|
position: absolute;
|
||||||
|
inset: var(--cpd-border-width-2);
|
||||||
|
border-radius: var(--cpd-radius-pill-effect);
|
||||||
|
background: var(--cpd-color-bg-canvas-default);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
.avatar {
|
.avatar {
|
||||||
position: absolute;
|
position: absolute;
|
||||||
top: 50%;
|
top: 50%;
|
||||||
@@ -73,6 +121,35 @@ unconditionally select the container so we can use cqmin units */
|
|||||||
inline-size: 50cqmin;
|
inline-size: 50cqmin;
|
||||||
block-size: 50cqmin;
|
block-size: 50cqmin;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.waves + .avatar {
|
||||||
|
/* Make the avatar slightly smaller to accommodate sound waves, if present */
|
||||||
|
inline-size: 38cqmin;
|
||||||
|
block-size: 38cqmin;
|
||||||
|
}
|
||||||
|
|
||||||
|
.wave:nth-child(1) {
|
||||||
|
inline-size: calc(38cqmin + var(--cpd-space-5x) + 3 * var(--cpd-space-10x));
|
||||||
|
block-size: calc(38cqmin + var(--cpd-space-5x) + 3 * var(--cpd-space-10x));
|
||||||
|
background: var(--cpd-color-alpha-gray-200);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wave:nth-child(2) {
|
||||||
|
inline-size: calc(38cqmin + var(--cpd-space-5x) + 2 * var(--cpd-space-10x));
|
||||||
|
block-size: calc(38cqmin + var(--cpd-space-5x) + 2 * var(--cpd-space-10x));
|
||||||
|
background: var(--cpd-color-alpha-gray-300);
|
||||||
|
}
|
||||||
|
|
||||||
|
.wave:nth-child(3) {
|
||||||
|
inline-size: calc(38cqmin + var(--cpd-space-5x) + var(--cpd-space-10x));
|
||||||
|
block-size: calc(38cqmin + var(--cpd-space-5x) + var(--cpd-space-10x));
|
||||||
|
background: var(--cpd-color-alpha-gray-400);
|
||||||
|
}
|
||||||
|
|
||||||
|
.speakingBorder {
|
||||||
|
inline-size: calc(38cqmin + var(--cpd-space-3x));
|
||||||
|
block-size: calc(38cqmin + var(--cpd-space-3x));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.avatar > img {
|
.avatar > img {
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ interface Props extends ComponentProps<typeof animated.div> {
|
|||||||
video: TrackReferenceOrPlaceholder | undefined;
|
video: TrackReferenceOrPlaceholder | undefined;
|
||||||
videoFit: "cover" | "contain";
|
videoFit: "cover" | "contain";
|
||||||
mirror: boolean;
|
mirror: boolean;
|
||||||
|
soundWaves?: boolean;
|
||||||
userId: string;
|
userId: string;
|
||||||
videoEnabled: boolean;
|
videoEnabled: boolean;
|
||||||
unencryptedWarning: boolean;
|
unencryptedWarning: boolean;
|
||||||
@@ -66,6 +67,7 @@ export const MediaView: FC<Props> = ({
|
|||||||
video,
|
video,
|
||||||
videoFit,
|
videoFit,
|
||||||
mirror,
|
mirror,
|
||||||
|
soundWaves,
|
||||||
userId,
|
userId,
|
||||||
videoEnabled,
|
videoEnabled,
|
||||||
unencryptedWarning,
|
unencryptedWarning,
|
||||||
@@ -92,7 +94,10 @@ export const MediaView: FC<Props> = ({
|
|||||||
const [handRaiseTimerVisible] = useSetting(showHandRaisedTimer);
|
const [handRaiseTimerVisible] = useSetting(showHandRaisedTimer);
|
||||||
const [showConnectionStats] = useSetting(showConnectionStatsSetting);
|
const [showConnectionStats] = useSetting(showConnectionStatsSetting);
|
||||||
|
|
||||||
const avatarSize = Math.round(Math.min(targetWidth, targetHeight) / 2);
|
const avatarSize = Math.round(
|
||||||
|
Math.min(targetWidth, targetHeight) *
|
||||||
|
(soundWaves === undefined ? 0.5 : 0.38),
|
||||||
|
);
|
||||||
|
|
||||||
const warnings = unencryptedWarning && (
|
const warnings = unencryptedWarning && (
|
||||||
<Tooltip
|
<Tooltip
|
||||||
@@ -124,6 +129,14 @@ export const MediaView: FC<Props> = ({
|
|||||||
{...props}
|
{...props}
|
||||||
>
|
>
|
||||||
<div className={styles.bg}>
|
<div className={styles.bg}>
|
||||||
|
{soundWaves !== undefined && (
|
||||||
|
<div className={styles.waves} data-visible={soundWaves}>
|
||||||
|
<div className={styles.wave} />
|
||||||
|
<div className={styles.wave} />
|
||||||
|
<div className={styles.wave} />
|
||||||
|
<div className={styles.speakingBorder} />
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
<Avatar
|
<Avatar
|
||||||
id={userId}
|
id={userId}
|
||||||
name={displayName}
|
name={displayName}
|
||||||
|
|||||||
@@ -79,6 +79,7 @@ interface SpotlightMemberMediaItemBaseProps extends SpotlightItemBaseProps {
|
|||||||
interface SpotlightUserMediaItemBaseProps extends SpotlightMemberMediaItemBaseProps {
|
interface SpotlightUserMediaItemBaseProps extends SpotlightMemberMediaItemBaseProps {
|
||||||
videoFit: "contain" | "cover";
|
videoFit: "contain" | "cover";
|
||||||
videoEnabled: boolean;
|
videoEnabled: boolean;
|
||||||
|
soundWaves: boolean | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
interface SpotlightLocalUserMediaItemProps extends SpotlightUserMediaItemBaseProps {
|
interface SpotlightLocalUserMediaItemProps extends SpotlightUserMediaItemBaseProps {
|
||||||
@@ -121,6 +122,7 @@ const SpotlightUserMediaItem: FC<SpotlightUserMediaItemProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const videoFit = useBehavior(vm.videoFit$);
|
const videoFit = useBehavior(vm.videoFit$);
|
||||||
const videoEnabled = useBehavior(vm.videoEnabled$);
|
const videoEnabled = useBehavior(vm.videoEnabled$);
|
||||||
|
const speaking = useBehavior(vm.speaking$);
|
||||||
|
|
||||||
// Whenever target bounds change, inform the viewModel
|
// Whenever target bounds change, inform the viewModel
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -133,6 +135,7 @@ const SpotlightUserMediaItem: FC<SpotlightUserMediaItemProps> = ({
|
|||||||
RefAttributes<HTMLDivElement> = {
|
RefAttributes<HTMLDivElement> = {
|
||||||
videoFit,
|
videoFit,
|
||||||
videoEnabled,
|
videoEnabled,
|
||||||
|
soundWaves: props.bgStyle === "transparent" ? speaking : undefined,
|
||||||
targetWidth,
|
targetWidth,
|
||||||
targetHeight,
|
targetHeight,
|
||||||
...props,
|
...props,
|
||||||
|
|||||||
Reference in New Issue
Block a user