From dc03d9b358a2c7d06986448fbabee9e76e73e67b Mon Sep 17 00:00:00 2001 From: Valere Date: Fri, 5 Jun 2026 10:29:36 +0200 Subject: [PATCH] fix: Initial unmute is reverted --- .../localMember/Publisher.test.ts | 2 + .../CallViewModel/localMember/Publisher.ts | 52 +++++++++++++------ 2 files changed, 39 insertions(+), 15 deletions(-) diff --git a/src/state/CallViewModel/localMember/Publisher.test.ts b/src/state/CallViewModel/localMember/Publisher.test.ts index 00cd50a1..21775c58 100644 --- a/src/state/CallViewModel/localMember/Publisher.test.ts +++ b/src/state/CallViewModel/localMember/Publisher.test.ts @@ -63,6 +63,7 @@ function createMockLocalTrack(source: Track.Source): LocalTrack { function createMockMuteState(enabled$: BehaviorSubject): { enabled$: BehaviorSubject; + syncing$: BehaviorSubject; setHandler: (h: (enabled: boolean) => void) => void; unsetHandler: () => void; } { @@ -70,6 +71,7 @@ function createMockMuteState(enabled$: BehaviorSubject): { const ms = { enabled$, + syncing$: new BehaviorSubject(false), setHandler: vi.fn().mockImplementation((h: (enabled: boolean) => void) => { currentHandler = h; }), diff --git a/src/state/CallViewModel/localMember/Publisher.ts b/src/state/CallViewModel/localMember/Publisher.ts index a6435212..0d5f263a 100644 --- a/src/state/CallViewModel/localMember/Publisher.ts +++ b/src/state/CallViewModel/localMember/Publisher.ts @@ -112,27 +112,49 @@ export class Publisher { this.logger.info("Local track published", localTrackPublication); const lkRoom = this.connection.livekitRoom; if (!this.shouldPublish) { + this.logger.debug("Not publishing, pausing upstream"); this.pauseUpstreams(lkRoom, [localTrackPublication.source]).catch((e) => { this.logger.error(`Failed to pause upstreams`, e); }); } - // also check the mute state and apply it if (localTrackPublication.source === Track.Source.Microphone) { - const enabled = this.muteStates.audio.enabled$.value; - lkRoom.localParticipant.setMicrophoneEnabled(enabled).catch((e) => { - this.logger.error( - `Failed to enable microphone track, enabled:${enabled}`, - e, - ); - }); + const muteState = this.muteStates.audio; + // skip this if a sync is in progress: enabled$ still reflects the old + // state while the handler is mid-flight, so the handler itself will apply + // the correct mute state once it completes. + if (!muteState.syncing$.value) { + const enabled = muteState.enabled$.value; + if (!enabled) { + this.logger.info( + "Local audio track just published but muted meanwhile, setting enabled to false", + ); + lkRoom.localParticipant.setMicrophoneEnabled(false).catch((e) => { + this.logger.error( + `Failed to enable microphone track, enabled:${enabled}`, + e, + ); + }); + } + } } else if (localTrackPublication.source === Track.Source.Camera) { - const enabled = this.muteStates.video.enabled$.value; - lkRoom.localParticipant.setCameraEnabled(enabled).catch((e) => { - this.logger.error( - `Failed to enable camera track, enabled:${enabled}`, - e, - ); - }); + const muteState = this.muteStates.video; + // skip this if a sync is in progress: enabled$ still reflects the old + // state while the handler is mid-flight, so the handler itself will apply + // the correct mute state once it completes. + if (!muteState.syncing$.value) { + const enabled = muteState.enabled$.value; + if (!enabled) { + this.logger.info( + "Local video track just published but muted meanwhile, setting enabled to false", + ); + lkRoom.localParticipant.setCameraEnabled(false).catch((e) => { + this.logger.error( + `Failed to enable camera track, enabled:${enabled}`, + e, + ); + }); + } + } } } /**