Skip to content

Commit dc3aef2

Browse files
authored
update some disconnecting logic (#345)
1 parent a0ada2e commit dc3aef2

File tree

7 files changed

+87
-40
lines changed

7 files changed

+87
-40
lines changed

examples/next-app/components/ExampleComponent.tsx

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,10 @@ export const ExampleComponent = () => {
7676
return (
7777
<div>
7878
<div className={'flex flex-col gap-4 font-light'}>
79+
<div>
80+
<div className={'text-sm font-medium uppercase'}>Status</div>
81+
<div>{status.value}</div>
82+
</div>
7983
<div className="flex max-w-sm flex-col gap-4">
8084
{match(status.value)
8185
.with('connected', () => (

examples/next-app/components/Voice.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,10 @@ export const Voice = ({ accessToken }: { accessToken: string }) => {
3030
hostname={process.env.NEXT_PUBLIC_HUME_VOICE_HOSTNAME || 'api.hume.ai'}
3131
messageHistoryLimit={10}
3232
enableAudioWorklet={enableAudioWorklet}
33+
onOpen={() => {
34+
// eslint-disable-next-line no-console
35+
console.log('onOpen');
36+
}}
3337
onMessage={(message) => {
3438
// eslint-disable-next-line no-console
3539
console.log('message', message);
@@ -145,6 +149,8 @@ export const Voice = ({ accessToken }: { accessToken: string }) => {
145149
}, [])}
146150
configId={process.env.NEXT_PUBLIC_HUME_VOICE_WEATHER_CONFIG_ID}
147151
onClose={(event) => {
152+
// eslint-disable-next-line no-console
153+
console.log('onClose', event);
148154
const niceClosure = 1000;
149155
const code = event.code;
150156

packages/embed-react/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@humeai/voice-embed-react",
3-
"version": "0.2.0-beta.15",
3+
"version": "0.2.0-beta.16",
44
"description": "",
55
"main": "./dist/index.js",
66
"module": "./dist/index.mjs",

packages/embed/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@humeai/voice-embed",
3-
"version": "0.2.0-beta.15",
3+
"version": "0.2.0-beta.16",
44
"description": "",
55
"main": "./dist/index.js",
66
"module": "./dist/index.mjs",

packages/react/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@humeai/voice-react",
3-
"version": "0.2.0-beta.15",
3+
"version": "0.2.0-beta.16",
44
"description": "",
55
"main": "./dist/index.js",
66
"module": "./dist/index.mjs",

packages/react/src/lib/VoiceProvider.tsx

Lines changed: 70 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,22 @@ export const VoiceProvider: FC<VoiceProviderProps> = ({
239239
messageHistoryLimit,
240240
});
241241

242+
const checkIsDisconnected = useCallback(() => {
243+
return (
244+
resourceStatusRef.current.mic === 'disconnected' ||
245+
resourceStatusRef.current.audioPlayer === 'disconnected' ||
246+
resourceStatusRef.current.socket === 'disconnected'
247+
);
248+
}, []);
249+
250+
const checkIsDisconnecting = useCallback(() => {
251+
return (
252+
resourceStatusRef.current.mic === 'disconnecting' ||
253+
resourceStatusRef.current.audioPlayer === 'disconnecting' ||
254+
resourceStatusRef.current.socket === 'disconnecting'
255+
);
256+
}, []);
257+
242258
const updateError = useCallback((err: VoiceError | null) => {
243259
setError(err);
244260
if (err !== null) {
@@ -284,10 +300,7 @@ export const VoiceProvider: FC<VoiceProviderProps> = ({
284300

285301
const client = useVoiceClient({
286302
onAudioMessage: (message: AudioOutputMessage) => {
287-
if (
288-
resourceStatusRef.current.audioPlayer === 'disconnecting' ||
289-
resourceStatusRef.current.audioPlayer === 'disconnected'
290-
) {
303+
if (checkIsDisconnecting() || checkIsDisconnected()) {
291304
// disconnection in progress, and resources are being cleaned up.
292305
// ignore the message
293306
return;
@@ -297,6 +310,12 @@ export const VoiceProvider: FC<VoiceProviderProps> = ({
297310
},
298311
onMessage: useCallback(
299312
(message: JSONMessage) => {
313+
if (checkIsDisconnecting() || checkIsDisconnected()) {
314+
// disconnection in progress, and resources are being cleaned up.
315+
// ignore the message
316+
return;
317+
}
318+
300319
// store message
301320
messageStore.onMessage(message);
302321

@@ -327,7 +346,13 @@ export const VoiceProvider: FC<VoiceProviderProps> = ({
327346
onError.current?.(error);
328347
}
329348
},
330-
[messageStore, player, toolStatus],
349+
[
350+
checkIsDisconnected,
351+
checkIsDisconnecting,
352+
messageStore,
353+
player,
354+
toolStatus,
355+
],
331356
),
332357
onClientError,
333358
onToolCallError: useCallback(
@@ -364,34 +389,32 @@ export const VoiceProvider: FC<VoiceProviderProps> = ({
364389
toolStatus.clearStore();
365390
setIsPaused(false);
366391

392+
const resourceShutdownFns = [];
367393
if (resourceStatusRef.current.audioPlayer === 'connected') {
368-
void player.stopAll().then(() => {
369-
resourceStatusRef.current.audioPlayer = 'disconnected';
370-
});
394+
resourceShutdownFns.push(player.stopAll());
371395
}
372-
373396
if (resourceStatusRef.current.mic === 'connected') {
374-
stopStream();
375-
void micStopFnRef.current?.().then(() => {
376-
resourceStatusRef.current.mic = 'disconnected';
377-
});
397+
resourceShutdownFns.push(micStopFnRef.current?.());
378398
}
379399

380-
if (!error) {
381-
// if there's an error, keep the error status. otherwise, set status to disconnected
382-
setStatus({ value: 'disconnected' });
400+
if (resourceShutdownFns.length > 0) {
401+
void Promise.all(resourceShutdownFns).then(() => {
402+
resourceStatusRef.current.audioPlayer = 'disconnected';
403+
resourceStatusRef.current.mic = 'disconnected';
404+
// if audio player and mic were connected at the time the socket
405+
// shut down, we can assume that the connection was closed by
406+
// the server, and not the user. Therefore, set the status
407+
// to 'disconnected'
408+
setStatus({ value: 'disconnected' });
409+
onClose.current?.(event);
410+
});
411+
} else {
412+
// if audio player and mic were not connected at the time the socket,
413+
// no need to setStatus because the user initiated the disconnect.
414+
onClose.current?.(event);
383415
}
384-
onClose.current?.(event);
385416
},
386-
[
387-
clearMessagesOnDisconnect,
388-
error,
389-
messageStore,
390-
player,
391-
stopStream,
392-
stopTimer,
393-
toolStatus,
394-
],
417+
[clearMessagesOnDisconnect, messageStore, player, stopTimer, toolStatus],
395418
),
396419
onToolCall: props.onToolCall,
397420
});
@@ -488,6 +511,13 @@ export const VoiceProvider: FC<VoiceProviderProps> = ({
488511
return;
489512
}
490513

514+
if (checkIsDisconnecting()) {
515+
console.warn(
516+
'Currently disconnecting from a chat. Cannot connect until the previous call is disconnected.',
517+
);
518+
return;
519+
}
520+
491521
updateError(null);
492522
setStatus({ value: 'connecting' });
493523
resourceStatusRef.current.socket = 'connecting';
@@ -612,8 +642,8 @@ export const VoiceProvider: FC<VoiceProviderProps> = ({
612642

613643
stopTimer();
614644

615-
// MICROPHONE - shut this down before shutting down the websocket
616-
// call stopStream separately because the user could stop the
645+
// MICROPHONE - shut this down before shutting down the websocket.
646+
// Call stopStream separately because the user could stop the
617647
// the connection before the microphone is initialized
618648
stopStream();
619649
await mic.stop();
@@ -639,10 +669,10 @@ export const VoiceProvider: FC<VoiceProviderProps> = ({
639669
setIsPaused(false);
640670
}, [
641671
stopTimer,
642-
client,
643-
player,
644672
stopStream,
645673
mic,
674+
client,
675+
player,
646676
clearMessagesOnDisconnect,
647677
toolStatus,
648678
messageStore,
@@ -663,12 +693,9 @@ export const VoiceProvider: FC<VoiceProviderProps> = ({
663693
);
664694

665695
useEffect(() => {
666-
if (
667-
error !== null &&
668-
status.value !== 'error' &&
669-
status.value !== 'disconnected'
670-
) {
671-
// If the status is ever set to `error`, disconnect the voice.
696+
if (error !== null && status.value !== 'error') {
697+
// If the status is ever set to `error`, disconnect the call
698+
// and clean up resources.
672699
setStatus({ value: 'error', reason: error.message });
673700
void disconnectAndCleanUpResources();
674701
}
@@ -678,6 +705,13 @@ export const VoiceProvider: FC<VoiceProviderProps> = ({
678705
// disconnect from socket when the voice provider component unmounts
679706
return () => {
680707
void disconnectAndCleanUpResources();
708+
setStatus({ value: 'disconnected' });
709+
isConnectingRef.current = false;
710+
resourceStatusRef.current = {
711+
mic: 'disconnected',
712+
audioPlayer: 'disconnected',
713+
socket: 'disconnected',
714+
};
681715
};
682716
// eslint-disable-next-line react-hooks/exhaustive-deps
683717
}, []);

packages/react/src/lib/useVoiceClient.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,10 @@ export const useVoiceClient = (props: {
159159
message.type === 'chat_metadata' ||
160160
message.type === 'assistant_end'
161161
) {
162-
const messageWithReceivedAt = { ...message, receivedAt: new Date() };
162+
const messageWithReceivedAt = {
163+
...message,
164+
receivedAt: new Date(),
165+
};
163166
onMessage.current?.(messageWithReceivedAt);
164167
return;
165168
}

0 commit comments

Comments
 (0)