feat(call): add sound effects and incoming call notification UI

- Implemented sound management for call events including mute, unmute, deafen, undeafen, call joined, call depart, and dialing sounds.
- Created a new CallSounds class to handle sound playback and state management.
- Added IncomingCallNotification component to display incoming call alerts with caller information and action buttons.
- Styled the incoming call notification using vanilla-extract CSS for animations and layout.
- Integrated sound playback for incoming calls with a 15-second threshold for dialing sound.
This commit is contained in:
2026-02-05 03:24:18 +11:00
parent cd912025f1
commit 1a452f52ca
27 changed files with 1888 additions and 179 deletions

View File

@@ -5,7 +5,7 @@ export const CallOverlayContainer = style([
DefaultReset,
{
position: 'fixed',
bottom: config.space.S400,
top: '60px',
right: config.space.S400,
zIndex: 9999,
backgroundColor: color.Surface.Container,
@@ -16,6 +16,7 @@ export const CallOverlayContainer = style([
maxWidth: '400px',
transition: 'box-shadow 0.2s ease',
userSelect: 'none',
color: color.Surface.OnContainer,
},
]);
@@ -44,6 +45,7 @@ export const CallOverlayHeader = style({
alignItems: 'center',
justifyContent: 'space-between',
marginBottom: config.space.S200,
color: color.Surface.OnContainer,
});
export const CallOverlayControls = style({
@@ -55,6 +57,7 @@ export const CallOverlayControls = style({
export const CallDuration = style({
fontVariantNumeric: 'tabular-nums',
color: color.Surface.OnContainer,
});
export const VideoContainer = style({
@@ -111,21 +114,6 @@ export const ScreenShareContainer = style({
},
});
export const ScreenShareLabel = style({
position: 'absolute',
top: config.space.S100,
left: config.space.S100,
backgroundColor: 'rgba(0, 0, 0, 0.7)',
color: 'white',
padding: `${config.space.S100} ${config.space.S200}`,
borderRadius: config.radii.R300,
fontSize: '0.75rem',
display: 'flex',
alignItems: 'center',
gap: config.space.S100,
zIndex: 1,
});
export const ScreenShareVideoContainer = style({
width: '100%',
height: '100%',
@@ -140,3 +128,89 @@ export const ScreenShareVideo = style({
height: '100%',
objectFit: 'contain',
});
export const ParticipantsSection = style({
marginBottom: config.space.S200,
borderRadius: config.radii.R300,
backgroundColor: color.Surface.ContainerActive,
overflow: 'hidden',
});
export const ParticipantsHeader = style({
display: 'flex',
alignItems: 'center',
justifyContent: 'space-between',
width: '100%',
padding: `${config.space.S200} ${config.space.S300}`,
backgroundColor: 'transparent',
border: 'none',
cursor: 'pointer',
color: color.Surface.OnContainer,
transition: 'background-color 0.15s ease',
':hover': {
backgroundColor: color.Surface.ContainerHover,
},
});
export const ParticipantsHeaderText = style({
color: color.Surface.OnContainer,
});
export const ParticipantsList = style({
padding: `0 ${config.space.S200} ${config.space.S200}`,
display: 'flex',
flexDirection: 'column',
gap: config.space.S100,
});
export const ParticipantItem = style({
display: 'flex',
alignItems: 'center',
gap: config.space.S200,
padding: config.space.S200,
borderRadius: config.radii.R300,
backgroundColor: color.Surface.Container,
transition: 'background-color 0.15s ease',
':hover': {
backgroundColor: color.Surface.ContainerHover,
},
});
export const ParticipantName = style({
color: color.Surface.OnContainer,
fontWeight: 500,
});
export const ParticipantId = style({
color: color.Secondary.Main,
opacity: 0.7,
});
export const YouBadge = style({
color: color.Primary.Main,
fontWeight: 600,
});
export const ParticipantMutedIcon = style({
color: color.Critical.Main,
flexShrink: 0,
});
export const ParticipantAvatarWrapper = style({
position: 'relative',
width: '32px',
height: '32px',
flexShrink: 0,
borderRadius: '50%',
transition: 'box-shadow 0.15s ease',
});
export const ParticipantAvatar = style({
width: '32px',
height: '32px',
borderRadius: '50%',
});
export const ParticipantSpeaking = style({
boxShadow: `0 0 0 2px ${color.Success.Main}`,
});