mirror of
https://github.com/J3vb/OwnCord.git
synced 2026-09-03 03:50:00 +03:00
fix: wire LiveKit speaker detection to voice activation ring
- handleActiveSpeakersChanged now calls setSpeakers() with proper channel_id and speaker list (was incorrectly calling setLocalSpeaking in a loop for all users) - setSpeakers() now updates ALL users including local (LiveKit is sole authority for speaking detection, no local VAD) - Make threshold_mode optional in VoiceSpeakersPayload (LiveKit handles mixing internally) - Update voice store test for new behavior
This commit is contained in:
@@ -21,8 +21,8 @@ import {
|
||||
voiceStore,
|
||||
setLocalMuted,
|
||||
setLocalDeafened,
|
||||
setLocalSpeaking,
|
||||
setLocalCamera,
|
||||
setSpeakers,
|
||||
} from "@stores/voice.store";
|
||||
import { loadPref, savePref } from "@components/settings/helpers";
|
||||
import { createLogger } from "@lib/logger";
|
||||
@@ -203,26 +203,21 @@ function handleTrackUnsubscribed(
|
||||
}
|
||||
|
||||
function handleActiveSpeakersChanged(speakers: Participant[]): void {
|
||||
const speakerIds = new Set<number>();
|
||||
if (currentChannelId === null) return;
|
||||
|
||||
const speakerUserIds: number[] = [];
|
||||
for (const speaker of speakers) {
|
||||
const userId = parseUserId(speaker.identity);
|
||||
if (userId > 0) {
|
||||
speakerIds.add(userId);
|
||||
speakerUserIds.push(userId);
|
||||
}
|
||||
}
|
||||
|
||||
// Update speaking state in voice store for all users in the channel
|
||||
const state = voiceStore.getState();
|
||||
const channelId = state.currentChannelId;
|
||||
if (channelId === null) return;
|
||||
|
||||
const channelUsers = state.voiceUsers.get(channelId);
|
||||
if (!channelUsers) return;
|
||||
|
||||
for (const [userId] of channelUsers) {
|
||||
const isSpeaking = speakerIds.has(userId);
|
||||
setLocalSpeaking(isSpeaking);
|
||||
}
|
||||
// Use setSpeakers to update all users' speaking state at once
|
||||
setSpeakers({
|
||||
channel_id: currentChannelId,
|
||||
speakers: speakerUserIds,
|
||||
});
|
||||
}
|
||||
|
||||
function handleDisconnected(reason?: DisconnectReason): void {
|
||||
|
||||
@@ -269,7 +269,7 @@ export interface VoiceConfigPayload {
|
||||
export interface VoiceSpeakersPayload {
|
||||
readonly channel_id: number;
|
||||
readonly speakers: readonly number[];
|
||||
readonly threshold_mode: string;
|
||||
readonly threshold_mode?: string;
|
||||
}
|
||||
|
||||
export interface VoiceTokenPayload {
|
||||
|
||||
@@ -250,25 +250,18 @@ export function setVoiceConfig(payload: VoiceConfigPayload): void {
|
||||
});
|
||||
}
|
||||
|
||||
/** Update speaking state for users from a voice_speakers event.
|
||||
* Skips the local user — their speaking state is driven by local VAD
|
||||
* (lower latency, same threshold). Prevents flicker from two sources
|
||||
* disagreeing on the same field. */
|
||||
/** Update speaking state for users from a voice_speakers event or
|
||||
* LiveKit's ActiveSpeakersChanged. Updates ALL users including local
|
||||
* (LiveKit is now the sole authority for speaking detection). */
|
||||
export function setSpeakers(payload: VoiceSpeakersPayload): void {
|
||||
voiceStore.setState((prev) => {
|
||||
const existingChannel = prev.voiceUsers.get(payload.channel_id);
|
||||
if (!existingChannel) return prev;
|
||||
|
||||
const currentUserId = authStore.getState().user?.id ?? 0;
|
||||
const speakerSet = new Set(payload.speakers);
|
||||
const nextUsers = new Map<number, VoiceUser>();
|
||||
|
||||
for (const [userId, user] of existingChannel) {
|
||||
// Skip local user — local VAD is the sole authority for our own indicator
|
||||
if (userId === currentUserId) {
|
||||
nextUsers.set(userId, user);
|
||||
continue;
|
||||
}
|
||||
const isSpeaking = speakerSet.has(userId);
|
||||
if (user.speaking !== isSpeaking) {
|
||||
nextUsers.set(userId, { ...user, speaking: isSpeaking });
|
||||
|
||||
@@ -346,14 +346,14 @@ describe("voice store", () => {
|
||||
}));
|
||||
});
|
||||
|
||||
it("does NOT overwrite local user's speaking state", () => {
|
||||
// Local VAD says we're speaking
|
||||
setLocalSpeaking(true);
|
||||
it("updates local user's speaking state from LiveKit", () => {
|
||||
// LiveKit says we're speaking
|
||||
setSpeakers({ channel_id: 10, speakers: [1, 2], threshold_mode: "forwarding" });
|
||||
expect(voiceStore.getState().voiceUsers.get(10)?.get(1)?.speaking).toBe(true);
|
||||
|
||||
// Server says we're NOT speaking — local user should be unchanged
|
||||
// LiveKit says we're NOT speaking — should update
|
||||
setSpeakers({ channel_id: 10, speakers: [2], threshold_mode: "forwarding" });
|
||||
expect(voiceStore.getState().voiceUsers.get(10)?.get(1)?.speaking).toBe(true);
|
||||
expect(voiceStore.getState().voiceUsers.get(10)?.get(1)?.speaking).toBe(false);
|
||||
});
|
||||
|
||||
it("updates remote users' speaking state from server", () => {
|
||||
|
||||
Reference in New Issue
Block a user