fix: eliminate voice ducking caused by rogue LiveKit audio elements
Add MutationObserver to neutralize LiveKit's re-attached <audio> elements, mark our keep-alive elements with data-opencord, detach tracks on unsubscribe, remove dangerous blanket .play(), and soften compressor to transparent limiter.
This commit is contained in:
@@ -299,7 +299,10 @@ export function useLiveKit() {
|
||||
const connect = useCallback(async (channelId: string) => {
|
||||
if (connectedChannelRef.current === channelId && roomRef.current?.state === ConnectionState.Connected) return;
|
||||
const gen = ++_connectGeneration;
|
||||
|
||||
|
||||
// Ensure AudioContext is created and resumed before tracks arrive
|
||||
await AudioManager.getInstance().resumeContext();
|
||||
|
||||
// 1. Reset state immediately to reflect "Loading/Switching" in UI
|
||||
setRoom(null);
|
||||
setParticipants([]);
|
||||
@@ -354,7 +357,12 @@ export function useLiveKit() {
|
||||
}
|
||||
guardedUpdate();
|
||||
});
|
||||
newRoom.on(RoomEvent.TrackUnsubscribed, guardedUpdate);
|
||||
newRoom.on(RoomEvent.TrackUnsubscribed, (track) => {
|
||||
if (track.kind === Track.Kind.Audio) {
|
||||
(track as RemoteAudioTrack).detach();
|
||||
}
|
||||
guardedUpdate();
|
||||
});
|
||||
newRoom.on(RoomEvent.LocalTrackPublished, (publication: LocalTrackPublication) => {
|
||||
if (publication.source === Track.Source.ScreenShare) {
|
||||
const { userId } = parseIdentity(newRoom.localParticipant.identity);
|
||||
@@ -374,9 +382,11 @@ export function useLiveKit() {
|
||||
newRoom.on(RoomEvent.ActiveSpeakersChanged, guardedUpdate);
|
||||
newRoom.on(RoomEvent.ParticipantMetadataChanged, guardedUpdate);
|
||||
newRoom.on(RoomEvent.TrackPublished, (publication: RemoteTrackPublication, participant: RemoteParticipant) => {
|
||||
if (publication.source === Track.Source.ScreenShare) {
|
||||
const { userId } = parseIdentity(participant.identity);
|
||||
useVoiceStore.getState().watchStream(userId);
|
||||
if (
|
||||
publication.source === Track.Source.ScreenShare ||
|
||||
publication.source === Track.Source.ScreenShareAudio
|
||||
) {
|
||||
(publication as RemoteTrackPublication).setSubscribed(false);
|
||||
}
|
||||
guardedUpdate();
|
||||
});
|
||||
@@ -425,7 +435,19 @@ export function useLiveKit() {
|
||||
useVoiceStore.getState().setIsLiveKitConnected(true);
|
||||
|
||||
updateParticipants();
|
||||
|
||||
|
||||
// Unsubscribe from any remote screen share tracks that auto-subscribed during connect
|
||||
newRoom.remoteParticipants.forEach((rp) => {
|
||||
rp.trackPublications.forEach((pub) => {
|
||||
if (
|
||||
(pub.source === Track.Source.ScreenShare || pub.source === Track.Source.ScreenShareAudio) &&
|
||||
pub.isSubscribed
|
||||
) {
|
||||
(pub as RemoteTrackPublication).setSubscribed(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Initial mute state check
|
||||
const { isMuted: wasMuted, isDeafened: wasDeafened } = useVoiceStore.getState();
|
||||
useVoiceStore.setState({ isCameraOn: false, isScreenSharing: false });
|
||||
@@ -441,7 +463,10 @@ export function useLiveKit() {
|
||||
|
||||
const connectDm = useCallback(async (dmChannelId: string) => {
|
||||
const gen = ++_connectGeneration;
|
||||
|
||||
|
||||
// Ensure AudioContext is created and resumed before tracks arrive
|
||||
await AudioManager.getInstance().resumeContext();
|
||||
|
||||
// 1. Reset state immediately
|
||||
setRoom(null);
|
||||
setParticipants([]);
|
||||
@@ -479,7 +504,12 @@ export function useLiveKit() {
|
||||
}
|
||||
guardedUpdate();
|
||||
});
|
||||
newRoom.on(RoomEvent.TrackUnsubscribed, guardedUpdate);
|
||||
newRoom.on(RoomEvent.TrackUnsubscribed, (track) => {
|
||||
if (track.kind === Track.Kind.Audio) {
|
||||
(track as RemoteAudioTrack).detach();
|
||||
}
|
||||
guardedUpdate();
|
||||
});
|
||||
newRoom.on(RoomEvent.LocalTrackPublished, (publication: LocalTrackPublication) => {
|
||||
if (publication.source === Track.Source.ScreenShare) {
|
||||
const { userId } = parseIdentity(newRoom.localParticipant.identity);
|
||||
@@ -498,9 +528,11 @@ export function useLiveKit() {
|
||||
newRoom.on(RoomEvent.TrackUnmuted, guardedUpdate);
|
||||
newRoom.on(RoomEvent.ActiveSpeakersChanged, guardedUpdate);
|
||||
newRoom.on(RoomEvent.TrackPublished, (publication: RemoteTrackPublication, participant: RemoteParticipant) => {
|
||||
if (publication.source === Track.Source.ScreenShare) {
|
||||
const { userId } = parseIdentity(participant.identity);
|
||||
useVoiceStore.getState().watchStream(userId);
|
||||
if (
|
||||
publication.source === Track.Source.ScreenShare ||
|
||||
publication.source === Track.Source.ScreenShareAudio
|
||||
) {
|
||||
(publication as RemoteTrackPublication).setSubscribed(false);
|
||||
}
|
||||
guardedUpdate();
|
||||
});
|
||||
@@ -533,13 +565,26 @@ export function useLiveKit() {
|
||||
await newRoom.connect(url, token);
|
||||
if (gen !== _connectGeneration) { newRoom.disconnect(); return; }
|
||||
const fullId = `dm-${dmChannelId}`;
|
||||
_activeRoom = newRoom;
|
||||
connectedChannelRef.current = fullId;
|
||||
_activeRoom = newRoom;
|
||||
connectedChannelRef.current = fullId;
|
||||
setConnectedChannelId(fullId);
|
||||
setRoom(newRoom);
|
||||
setRoom(newRoom);
|
||||
setIsConnected(true);
|
||||
useVoiceStore.getState().setIsLiveKitConnected(true);
|
||||
updateParticipants();
|
||||
|
||||
// Unsubscribe from any remote screen share tracks that auto-subscribed during connect
|
||||
newRoom.remoteParticipants.forEach((rp) => {
|
||||
rp.trackPublications.forEach((pub) => {
|
||||
if (
|
||||
(pub.source === Track.Source.ScreenShare || pub.source === Track.Source.ScreenShareAudio) &&
|
||||
pub.isSubscribed
|
||||
) {
|
||||
(pub as RemoteTrackPublication).setSubscribed(false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
const { isMuted: wasMuted, isDeafened: wasDeafened } = useVoiceStore.getState();
|
||||
useVoiceStore.setState({ isCameraOn: false, isScreenSharing: false });
|
||||
|
||||
|
||||
Reference in New Issue
Block a user