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:
Jannis Braun
2026-02-20 07:03:41 +01:00
parent 6f777f97ce
commit 5c38f076d3
22 changed files with 511 additions and 254 deletions
+7 -6
View File
@@ -1,5 +1,5 @@
import { create } from 'zustand';
export const useUIStore = create((set) => ({
export const useUIStore = create((set, get) => ({
sidebarOpen: true,
memberListOpen: true,
activeModal: null,
@@ -15,11 +15,12 @@ export const useUIStore = create((set) => ({
toggleMemberList: () => set((state) => ({ memberListOpen: !state.memberListOpen })),
openModal: (modal, data = {}) => set({ activeModal: modal, modalData: data }),
closeModal: () => set({ activeModal: null, modalData: {} }),
setIsMobile: (isMobile) => set({
isMobile,
sidebarOpen: !isMobile,
memberListOpen: !isMobile,
}),
setIsMobile: (isMobile) => {
const prev = get().isMobile;
if (prev === isMobile)
return;
set({ isMobile, sidebarOpen: !isMobile, memberListOpen: !isMobile });
},
setShowDms: (show) => set({ showDms: show }),
openImagePreview: (url) => set({ activeModal: 'imagePreview', imagePreviewUrl: url }),
closeImagePreview: () => set({ activeModal: null, imagePreviewUrl: null }),
+6 -6
View File
@@ -43,7 +43,7 @@ interface UIState {
setPipCollapsed: (collapsed: boolean) => void;
}
export const useUIStore = create<UIState>((set) => ({
export const useUIStore = create<UIState>((set, get) => ({
sidebarOpen: true,
memberListOpen: true,
activeModal: null,
@@ -62,11 +62,11 @@ export const useUIStore = create<UIState>((set) => ({
openModal: (modal, data = {}) => set({ activeModal: modal, modalData: data }),
closeModal: () => set({ activeModal: null, modalData: {} }),
setIsMobile: (isMobile) => set({
isMobile,
sidebarOpen: !isMobile,
memberListOpen: !isMobile,
}),
setIsMobile: (isMobile) => {
const prev = get().isMobile;
if (prev === isMobile) return;
set({ isMobile, sidebarOpen: !isMobile, memberListOpen: !isMobile });
},
setShowDms: (show) => set({ showDms: show }),
+8 -1
View File
@@ -30,7 +30,7 @@ export const useVoiceStore = create()(persist((set, get) => ({
streamVolumes: new Map(),
streamMutes: new Map(),
watchingStreams: new Set(),
streamAttenuationEnabled: true,
streamAttenuationEnabled: false,
streamAttenuationStrength: 50,
setStreamVolume: (userId, volume) => {
set((state) => {
@@ -203,6 +203,13 @@ export const useVoiceStore = create()(persist((set, get) => ({
}),
}), {
name: 'opencord-voice-settings',
version: 1,
migrate: (persistedState, version) => {
if (version === 0) {
persistedState.streamAttenuationEnabled = false;
}
return persistedState;
},
storage: createJSONStorage(() => localStorage),
// Only persist these keys. Maps and Sets are complex to serialize.
partialize: (state) => ({
+8 -1
View File
@@ -107,7 +107,7 @@ export const useVoiceStore = create<VoiceState>()(
streamVolumes: new Map(),
streamMutes: new Map(),
watchingStreams: new Set(),
streamAttenuationEnabled: true,
streamAttenuationEnabled: false,
streamAttenuationStrength: 50,
setStreamVolume: (userId, volume) => {
@@ -296,6 +296,13 @@ export const useVoiceStore = create<VoiceState>()(
}),
{
name: 'opencord-voice-settings',
version: 1,
migrate: (persistedState: any, version: number) => {
if (version === 0) {
persistedState.streamAttenuationEnabled = false;
}
return persistedState;
},
storage: createJSONStorage(() => localStorage),
// Only persist these keys. Maps and Sets are complex to serialize.
partialize: (state) => ({