Files
InteractiveAvatarNextJSDemo/components/logic/context.tsx
Vlad Morozov a3cdf46210 update demo
2025-04-22 19:38:59 +03:00

258 lines
6.5 KiB
TypeScript

import StreamingAvatar, {
ConnectionQuality,
StreamingTalkingMessageEvent,
UserTalkingMessageEvent,
} from "@heygen/streaming-avatar";
import React, { useRef, useState } from "react";
export enum StreamingAvatarSessionState {
INACTIVE = "inactive",
CONNECTING = "connecting",
CONNECTED = "connected",
}
export enum MessageSender {
CLIENT = "CLIENT",
AVATAR = "AVATAR",
}
export interface Message {
id: string;
sender: MessageSender;
content: string;
}
type StreamingAvatarContextProps = {
avatarRef: React.MutableRefObject<StreamingAvatar | null>;
basePath?: string;
isMuted: boolean;
setIsMuted: (isMuted: boolean) => void;
isVoiceChatLoading: boolean;
setIsVoiceChatLoading: (isVoiceChatLoading: boolean) => void;
isVoiceChatActive: boolean;
setIsVoiceChatActive: (isVoiceChatActive: boolean) => void;
sessionState: StreamingAvatarSessionState;
setSessionState: (sessionState: StreamingAvatarSessionState) => void;
stream: MediaStream | null;
setStream: (stream: MediaStream | null) => void;
messages: Message[];
clearMessages: () => void;
handleUserTalkingMessage: ({
detail,
}: {
detail: UserTalkingMessageEvent;
}) => void;
handleStreamingTalkingMessage: ({
detail,
}: {
detail: StreamingTalkingMessageEvent;
}) => void;
handleEndMessage: () => void;
isListening: boolean;
setIsListening: (isListening: boolean) => void;
isUserTalking: boolean;
setIsUserTalking: (isUserTalking: boolean) => void;
isAvatarTalking: boolean;
setIsAvatarTalking: (isAvatarTalking: boolean) => void;
connectionQuality: ConnectionQuality;
setConnectionQuality: (connectionQuality: ConnectionQuality) => void;
};
const StreamingAvatarContext = React.createContext<StreamingAvatarContextProps>(
{
avatarRef: { current: null },
isMuted: true,
setIsMuted: () => {},
isVoiceChatLoading: false,
setIsVoiceChatLoading: () => {},
sessionState: StreamingAvatarSessionState.INACTIVE,
setSessionState: () => {},
isVoiceChatActive: false,
setIsVoiceChatActive: () => {},
stream: null,
setStream: () => {},
messages: [],
clearMessages: () => {},
handleUserTalkingMessage: () => {},
handleStreamingTalkingMessage: () => {},
handleEndMessage: () => {},
isListening: false,
setIsListening: () => {},
isUserTalking: false,
setIsUserTalking: () => {},
isAvatarTalking: false,
setIsAvatarTalking: () => {},
connectionQuality: ConnectionQuality.UNKNOWN,
setConnectionQuality: () => {},
},
);
const useStreamingAvatarSessionState = () => {
const [sessionState, setSessionState] = useState(
StreamingAvatarSessionState.INACTIVE,
);
const [stream, setStream] = useState<MediaStream | null>(null);
return {
sessionState,
setSessionState,
stream,
setStream,
};
};
const useStreamingAvatarVoiceChatState = () => {
const [isMuted, setIsMuted] = useState(true);
const [isVoiceChatLoading, setIsVoiceChatLoading] = useState(false);
const [isVoiceChatActive, setIsVoiceChatActive] = useState(false);
return {
isMuted,
setIsMuted,
isVoiceChatLoading,
setIsVoiceChatLoading,
isVoiceChatActive,
setIsVoiceChatActive,
};
};
const useStreamingAvatarMessageState = () => {
const [messages, setMessages] = useState<Message[]>([]);
const currentSenderRef = useRef<MessageSender | null>(null);
const handleUserTalkingMessage = ({
detail,
}: {
detail: UserTalkingMessageEvent;
}) => {
if (currentSenderRef.current === MessageSender.CLIENT) {
setMessages((prev) => [
...prev.slice(0, -1),
{
...prev[prev.length - 1],
content: [prev[prev.length - 1].content, detail.message].join(""),
},
]);
} else {
currentSenderRef.current = MessageSender.CLIENT;
setMessages((prev) => [
...prev,
{
id: Date.now().toString(),
sender: MessageSender.CLIENT,
content: detail.message,
},
]);
}
};
const handleStreamingTalkingMessage = ({
detail,
}: {
detail: StreamingTalkingMessageEvent;
}) => {
if (currentSenderRef.current === MessageSender.AVATAR) {
setMessages((prev) => [
...prev.slice(0, -1),
{
...prev[prev.length - 1],
content: [prev[prev.length - 1].content, detail.message].join(""),
},
]);
} else {
currentSenderRef.current = MessageSender.AVATAR;
setMessages((prev) => [
...prev,
{
id: Date.now().toString(),
sender: MessageSender.AVATAR,
content: detail.message,
},
]);
}
};
const handleEndMessage = () => {
currentSenderRef.current = null;
};
return {
messages,
clearMessages: () => {
setMessages([]);
currentSenderRef.current = null;
},
handleUserTalkingMessage,
handleStreamingTalkingMessage,
handleEndMessage,
};
};
const useStreamingAvatarListeningState = () => {
const [isListening, setIsListening] = useState(false);
return { isListening, setIsListening };
};
const useStreamingAvatarTalkingState = () => {
const [isUserTalking, setIsUserTalking] = useState(false);
const [isAvatarTalking, setIsAvatarTalking] = useState(false);
return {
isUserTalking,
setIsUserTalking,
isAvatarTalking,
setIsAvatarTalking,
};
};
const useStreamingAvatarConnectionQualityState = () => {
const [connectionQuality, setConnectionQuality] = useState(
ConnectionQuality.UNKNOWN,
);
return { connectionQuality, setConnectionQuality };
};
export const StreamingAvatarProvider = ({
children,
basePath,
}: {
children: React.ReactNode;
basePath?: string;
}) => {
const avatarRef = React.useRef<StreamingAvatar>(null);
const voiceChatState = useStreamingAvatarVoiceChatState();
const sessionState = useStreamingAvatarSessionState();
const messageState = useStreamingAvatarMessageState();
const listeningState = useStreamingAvatarListeningState();
const talkingState = useStreamingAvatarTalkingState();
const connectionQualityState = useStreamingAvatarConnectionQualityState();
return (
<StreamingAvatarContext.Provider
value={{
avatarRef,
basePath,
...voiceChatState,
...sessionState,
...messageState,
...listeningState,
...talkingState,
...connectionQualityState,
}}
>
{children}
</StreamingAvatarContext.Provider>
);
};
export const useStreamingAvatarContext = () => {
return React.useContext(StreamingAvatarContext);
};