feat: mute screenshare audio independently + fix CI lint/prettier

- Add screenshareAudioMuted state to CallControlState and CallControl
- setSound() now preserves screenshare audio mute when un-deafening
- Add toggleScreenshareAudio() targeting audio[data-lk-source="screen_share_audio"]
- Add ScreenshareAudioButton (volume icon, warns when muted) to controls bar
- Fix unused prevScreenshare variable (ESLint error from prior commit)
- Run Prettier on Controls.tsx and CallControl.ts (CI formatting failures)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-05-24 23:52:57 -04:00
parent 8214387479
commit bb2e25de2d
4 changed files with 83 additions and 12 deletions
+7 -3
View File
@@ -24,6 +24,7 @@ import {
FullscreenButton, FullscreenButton,
MicrophoneButton, MicrophoneButton,
ScreenShareButton, ScreenShareButton,
ScreenshareAudioButton,
SoundButton, SoundButton,
VideoButton, VideoButton,
} from './Controls'; } from './Controls';
@@ -67,9 +68,8 @@ export function CallControls({ callEmbed }: CallControlsProps) {
} }
}, [callEmbedRef]); }, [callEmbedRef]);
const { microphone, video, sound, screenshare, spotlight } = useCallControlState( const { microphone, video, sound, screenshare, spotlight, screenshareAudioMuted } =
callEmbed.control, useCallControlState(callEmbed.control);
);
const [cords, setCords] = useState<RectCords>(); const [cords, setCords] = useState<RectCords>();
const [shareConfirm, setShareConfirm] = useState(false); const [shareConfirm, setShareConfirm] = useState(false);
@@ -346,6 +346,10 @@ export function CallControls({ callEmbed }: CallControlsProps) {
onToggle={() => callEmbed.control.toggleMicrophone()} onToggle={() => callEmbed.control.toggleMicrophone()}
/> />
<SoundButton enabled={sound} onToggle={() => callEmbed.control.toggleSound()} /> <SoundButton enabled={sound} onToggle={() => callEmbed.control.toggleSound()} />
<ScreenshareAudioButton
muted={screenshareAudioMuted}
onToggle={() => callEmbed.control.toggleScreenshareAudio()}
/>
</Box> </Box>
{!compact && <ControlDivider />} {!compact && <ControlDivider />}
<Box shrink="No" alignItems="Inherit" justifyContent="Inherit" gap="200"> <Box shrink="No" alignItems="Inherit" justifyContent="Inherit" gap="200">
+35 -5
View File
@@ -197,11 +197,41 @@ export function FullscreenButton({ isFullscreen, onToggle }: FullscreenButtonPro
aria-pressed={isFullscreen} aria-pressed={isFullscreen}
outlined outlined
> >
{isFullscreen ? ( {isFullscreen ? <ExitFullscreenIcon /> : <FullscreenIcon />}
<ExitFullscreenIcon /> </IconButton>
) : ( )}
<FullscreenIcon /> </TooltipProvider>
)} );
}
type ScreenshareAudioButtonProps = {
muted: boolean;
onToggle: () => void;
};
export function ScreenshareAudioButton({ muted, onToggle }: ScreenshareAudioButtonProps) {
return (
<TooltipProvider
position="Top"
delay={500}
tooltip={
<Tooltip>
<Text size="T200">{muted ? 'Unmute Screenshare Audio' : 'Mute Screenshare Audio'}</Text>
</Tooltip>
}
>
{(anchorRef) => (
<IconButton
ref={anchorRef}
variant={muted ? 'Warning' : 'Surface'}
fill="Soft"
radii="400"
size="400"
onClick={onToggle}
aria-label={muted ? 'Unmute Screenshare Audio' : 'Mute Screenshare Audio'}
aria-pressed={muted}
outlined
>
<Icon size="400" src={muted ? Icons.VolumeMute : Icons.VolumeHigh} filled={muted} />
</IconButton> </IconButton>
)} )}
</TooltipProvider> </TooltipProvider>
+37 -4
View File
@@ -93,6 +93,10 @@ export class CallControl extends EventEmitter implements CallControlState {
return this.state.spotlight; return this.state.spotlight;
} }
public get screenshareAudioMuted(): boolean {
return this.state.screenshareAudioMuted;
}
public async applyState() { public async applyState() {
await this.setMediaState({ await this.setMediaState({
audio_enabled: this.microphone, audio_enabled: this.microphone,
@@ -145,11 +149,24 @@ export class CallControl extends EventEmitter implements CallControlState {
const callDocument = this.iframe.contentDocument ?? this.iframe.contentWindow?.document; const callDocument = this.iframe.contentDocument ?? this.iframe.contentWindow?.document;
if (callDocument) { if (callDocument) {
callDocument.querySelectorAll('audio').forEach((el) => { callDocument.querySelectorAll('audio').forEach((el) => {
el.muted = !sound; const isScreenshareAudio = el.getAttribute('data-lk-source') === 'screen_share_audio';
el.muted = !sound || (isScreenshareAudio && this.screenshareAudioMuted);
}); });
} }
} }
private applyScreenshareAudioMuted(): void {
if (!this.sound) return;
const callDocument = this.iframe.contentDocument ?? this.iframe.contentWindow?.document;
if (callDocument) {
callDocument
.querySelectorAll<HTMLAudioElement>('audio[data-lk-source="screen_share_audio"]')
.forEach((el) => {
el.muted = this.screenshareAudioMuted;
});
}
}
public onMediaState(evt: CustomEvent<ElementMediaStateDetail>) { public onMediaState(evt: CustomEvent<ElementMediaStateDetail>) {
const { data } = evt.detail; const { data } = evt.detail;
if (!data) return; if (!data) return;
@@ -160,6 +177,7 @@ export class CallControl extends EventEmitter implements CallControlState {
this.sound, this.sound,
this.screenshare, this.screenshare,
this.spotlight, this.spotlight,
this.screenshareAudioMuted,
); );
this.state = state; this.state = state;
@@ -171,8 +189,6 @@ export class CallControl extends EventEmitter implements CallControlState {
} }
public onControlMutation() { public onControlMutation() {
const prevScreenshare = this.screenshare;
const screenshare: boolean = this.screenshareButton?.getAttribute('data-kind') === 'primary'; const screenshare: boolean = this.screenshareButton?.getAttribute('data-kind') === 'primary';
const spotlight: boolean = this.spotlightButton?.checked ?? false; const spotlight: boolean = this.spotlightButton?.checked ?? false;
@@ -182,9 +198,9 @@ export class CallControl extends EventEmitter implements CallControlState {
this.sound, this.sound,
screenshare, screenshare,
spotlight, spotlight,
this.screenshareAudioMuted,
); );
this.emitStateUpdate(); this.emitStateUpdate();
} }
public setMicrophone(enabled: boolean) { public setMicrophone(enabled: boolean) {
@@ -215,6 +231,8 @@ export class CallControl extends EventEmitter implements CallControlState {
const sound = !this.sound; const sound = !this.sound;
this.setSound(sound); this.setSound(sound);
// After un-deafening, re-apply screenshare audio mute if active
if (sound) this.applyScreenshareAudioMuted();
const state = new CallControlState( const state = new CallControlState(
this.microphone, this.microphone,
@@ -222,6 +240,7 @@ export class CallControl extends EventEmitter implements CallControlState {
sound, sound,
this.screenshare, this.screenshare,
this.spotlight, this.spotlight,
this.screenshareAudioMuted,
); );
this.state = state; this.state = state;
this.emitStateUpdate(); this.emitStateUpdate();
@@ -231,6 +250,20 @@ export class CallControl extends EventEmitter implements CallControlState {
} }
} }
public toggleScreenshareAudio() {
const screenshareAudioMuted = !this.screenshareAudioMuted;
this.state = new CallControlState(
this.microphone,
this.video,
this.sound,
this.screenshare,
this.spotlight,
screenshareAudioMuted,
);
this.emitStateUpdate();
this.applyScreenshareAudioMuted();
}
public toggleScreenshare() { public toggleScreenshare() {
this.screenshareButton?.click(); this.screenshareButton?.click();
} }
+4
View File
@@ -9,17 +9,21 @@ export class CallControlState {
public readonly spotlight: boolean; public readonly spotlight: boolean;
public readonly screenshareAudioMuted: boolean;
constructor( constructor(
microphone: boolean, microphone: boolean,
video: boolean, video: boolean,
sound: boolean, sound: boolean,
screenshare = false, screenshare = false,
spotlight = false, spotlight = false,
screenshareAudioMuted = false,
) { ) {
this.microphone = microphone; this.microphone = microphone;
this.video = video; this.video = video;
this.sound = sound; this.sound = sound;
this.screenshare = screenshare; this.screenshare = screenshare;
this.spotlight = spotlight; this.spotlight = spotlight;
this.screenshareAudioMuted = screenshareAudioMuted;
} }
} }