Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions examples/next-app/components/ExampleComponent.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ export const ExampleComponent = () => {
isPaused,
volume,
setVolume,
playerQueueLength,
} = useVoice();

const [textValue, setTextValue] = useState('');
Expand Down Expand Up @@ -87,6 +88,12 @@ export const ExampleComponent = () => {
</div>
<div>{isPlaying ? 'true' : 'false'}</div>
</div>
<div>
<div className={'text-sm font-medium uppercase'}>
Player queue length
</div>
<div>{playerQueueLength}</div>
</div>
<div>
<div className={'text-sm font-medium uppercase'}>
Ready state
Expand Down
7 changes: 5 additions & 2 deletions packages/react/src/lib/VoiceProvider.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ export type VoiceContextType = {
callDurationTimestamp: string | null;
toolStatusStore: ReturnType<typeof useToolStatus>['store'];
chatMetadata: ChatMetadataMessage | null;
playerQueueLength: number;
isPaused: boolean;
volume: number;
setVolume: (level: number) => void;
Expand Down Expand Up @@ -540,6 +541,7 @@ export const VoiceProvider: FC<VoiceProviderProps> = ({
callDurationTimestamp,
toolStatusStore: toolStatus.store,
chatMetadata: messageStore.chatMetadata,
playerQueueLength: player.queueLength,
isPaused,
volume: player.volume,
setVolume: player.setVolume,
Expand All @@ -552,6 +554,9 @@ export const VoiceProvider: FC<VoiceProviderProps> = ({
player.isPlaying,
player.muteAudio,
player.unmuteAudio,
player.queueLength,
player.volume,
player.setVolume,
mic.fft,
mic.isMuted,
mic.mute,
Expand All @@ -577,8 +582,6 @@ export const VoiceProvider: FC<VoiceProviderProps> = ({
callDurationTimestamp,
toolStatus.store,
isPaused,
player.volume,
player.setVolume,
],
);

Expand Down
1 change: 1 addition & 0 deletions packages/react/src/lib/useSoundPlayer.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ describe('useSoundPlayer', () => {
expect(result.current.volume).toBe(1.0); // full volume
expect(result.current.isAudioMuted).toBe(false); // not muted
expect(result.current.isPlaying).toBe(false); // not playing
expect(result.current.queueLength).toBe(0); // empty queue
expect(result.current.fft).toEqual(generateEmptyFft()); // empty fft
});
});
27 changes: 24 additions & 3 deletions packages/react/src/lib/useSoundPlayer.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { convertBase64ToBlob } from 'hume';
import { useCallback, useRef, useState } from 'react';
import z from 'zod';

import { convertLinearFrequenciesToBark } from './convertFrequencyScale';
import { generateEmptyFft } from './generateEmptyFft';
Expand All @@ -14,6 +15,7 @@ export const useSoundPlayer = (props: {
const [isAudioMuted, setIsAudioMuted] = useState(false);
const [volume, setVolumeState] = useState<number>(1.0);
const [fft, setFft] = useState<number[]>(generateEmptyFft());
const [queueLength, setQueueLength] = useState(0);

const audioContext = useRef<AudioContext | null>(null);
const analyserNode = useRef<AnalyserNode | null>(null);
Expand Down Expand Up @@ -52,7 +54,7 @@ export const useSoundPlayer = (props: {

await initAudioContext.audioWorklet
.addModule(
'https://storage.googleapis.com/evi-react-sdk-assets/audio-worklet-20250506.js',
'https://storage.googleapis.com/evi-react-sdk-assets/audio-worklet-20250507.js',
)
.catch((e) => {
console.log(e);
Expand All @@ -63,11 +65,29 @@ export const useSoundPlayer = (props: {
workletNode.current = worklet;

worklet.port.onmessage = (e: MessageEvent) => {
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access
if (e.data?.type === 'ended') {
const endedEvent = z
.object({
type: z.literal('ended'),
})
.safeParse(e.data);

if (endedEvent.success) {
setIsPlaying(false);
onStopAudio.current('stream');
}

const queueLengthEvent = z
.object({
type: z.literal('queueLength'),
length: z.number(),
})
.safeParse(e.data);
if (queueLengthEvent.success) {
if (queueLengthEvent.data.length === 0) {
setIsPlaying(false);
}
setQueueLength(queueLengthEvent.data.length);
}
};

frequencyDataIntervalId.current = window.setInterval(() => {
Expand Down Expand Up @@ -205,5 +225,6 @@ export const useSoundPlayer = (props: {
clearQueue,
volume,
setVolume,
queueLength,
};
};
6 changes: 6 additions & 0 deletions packages/react/src/worklets/audio-worklet.js
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ class BufferQueue {
this._hasPushed = false;
}

get size() {
return this._buffers.length;
}

read() {
if (!this._hasPushed) {
return null;
Expand Down Expand Up @@ -85,6 +89,7 @@ class AudioStreamProcessor extends AudioWorkletProcessor {
this._fadeOutActive = false;
this._fadeOutCounter = 0;
}
this.port.postMessage({ type: 'queueLength', length: this._bq.size });
break;
case 'end':
this._shouldStop = true;
Expand Down Expand Up @@ -118,6 +123,7 @@ class AudioStreamProcessor extends AudioWorkletProcessor {
const channels = output.length;

const block = this._bq.read();
this.port.postMessage({ type: 'queueLength', length: this._bq.size });

if (block) {
for (let ch = 0; ch < channels; ch++) {
Expand Down