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:
@@ -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">
|
||||||
|
|||||||
@@ -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>
|
||||||
|
|||||||
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user