fix(voice): keep LiveKit TTS tracks alive
This commit is contained in:
+116
@@ -0,0 +1,116 @@
|
||||
import { useEffect, useState } from 'react';
|
||||
import { type AgentState } from '@livekit/components-react';
|
||||
|
||||
export interface Coordinate {
|
||||
x: number;
|
||||
y: number;
|
||||
}
|
||||
|
||||
export function generateConnectingSequence(rows: number, columns: number, radius: number) {
|
||||
const seq = [];
|
||||
const centerY = Math.floor(rows / 2);
|
||||
|
||||
// Calculate the boundaries of the ring based on the ring distance
|
||||
const topLeft = {
|
||||
x: Math.max(0, centerY - radius),
|
||||
y: Math.max(0, centerY - radius),
|
||||
};
|
||||
const bottomRight = {
|
||||
x: columns - 1 - topLeft.x,
|
||||
y: Math.min(rows - 1, centerY + radius),
|
||||
};
|
||||
|
||||
// Top edge
|
||||
for (let x = topLeft.x; x <= bottomRight.x; x++) {
|
||||
seq.push({ x, y: topLeft.y });
|
||||
}
|
||||
|
||||
// Right edge
|
||||
for (let y = topLeft.y + 1; y <= bottomRight.y; y++) {
|
||||
seq.push({ x: bottomRight.x, y });
|
||||
}
|
||||
|
||||
// Bottom edge
|
||||
for (let x = bottomRight.x - 1; x >= topLeft.x; x--) {
|
||||
seq.push({ x, y: bottomRight.y });
|
||||
}
|
||||
|
||||
// Left edge
|
||||
for (let y = bottomRight.y - 1; y > topLeft.y; y--) {
|
||||
seq.push({ x: topLeft.x, y });
|
||||
}
|
||||
|
||||
return seq;
|
||||
}
|
||||
|
||||
export function generateListeningSequence(rows: number, columns: number) {
|
||||
const center = { x: Math.floor(columns / 2), y: Math.floor(rows / 2) };
|
||||
const noIndex = { x: -1, y: -1 };
|
||||
|
||||
return [center, noIndex, noIndex, noIndex, noIndex, noIndex, noIndex, noIndex, noIndex];
|
||||
}
|
||||
|
||||
export function generateThinkingSequence(rows: number, columns: number) {
|
||||
const seq = [];
|
||||
const y = Math.floor(rows / 2);
|
||||
for (let x = 0; x < columns; x++) {
|
||||
seq.push({ x, y });
|
||||
}
|
||||
for (let x = columns - 1; x >= 0; x--) {
|
||||
seq.push({ x, y });
|
||||
}
|
||||
|
||||
return seq;
|
||||
}
|
||||
|
||||
export function useAgentAudioVisualizerGridAnimator(
|
||||
state: AgentState,
|
||||
rows: number,
|
||||
columns: number,
|
||||
interval: number,
|
||||
radius?: number
|
||||
): Coordinate {
|
||||
const [index, setIndex] = useState(0);
|
||||
const [sequence, setSequence] = useState<Coordinate[]>(() => [
|
||||
{
|
||||
x: Math.floor(columns / 2),
|
||||
y: Math.floor(rows / 2),
|
||||
},
|
||||
]);
|
||||
|
||||
useEffect(() => {
|
||||
const clampedRadius = radius
|
||||
? Math.min(radius, Math.floor(Math.max(rows, columns) / 2))
|
||||
: Math.floor(Math.max(rows, columns) / 2);
|
||||
|
||||
if (state === 'thinking') {
|
||||
setSequence(generateThinkingSequence(rows, columns));
|
||||
} else if (state === 'connecting' || state === 'initializing') {
|
||||
const sequence = [...generateConnectingSequence(rows, columns, clampedRadius)];
|
||||
setSequence(sequence);
|
||||
} else if (state === 'listening') {
|
||||
setSequence(generateListeningSequence(rows, columns));
|
||||
} else {
|
||||
setSequence([{ x: Math.floor(columns / 2), y: Math.floor(rows / 2) }]);
|
||||
}
|
||||
setIndex(0);
|
||||
}, [state, rows, columns, radius]);
|
||||
|
||||
useEffect(() => {
|
||||
if (state === 'speaking') {
|
||||
return;
|
||||
}
|
||||
|
||||
const indexInterval = setInterval(() => {
|
||||
setIndex((prev) => {
|
||||
return prev + 1;
|
||||
});
|
||||
}, interval);
|
||||
|
||||
return () => clearInterval(indexInterval);
|
||||
}, [interval, columns, rows, state, sequence.length]);
|
||||
|
||||
return (
|
||||
sequence[index % sequence.length] ?? { x: Math.floor(columns / 2), y: Math.floor(rows / 2) }
|
||||
);
|
||||
}
|
||||
Reference in New Issue
Block a user