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; 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( { 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(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([]); const currentSenderRef = useRef(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(null); const voiceChatState = useStreamingAvatarVoiceChatState(); const sessionState = useStreamingAvatarSessionState(); const messageState = useStreamingAvatarMessageState(); const listeningState = useStreamingAvatarListeningState(); const talkingState = useStreamingAvatarTalkingState(); const connectionQualityState = useStreamingAvatarConnectionQualityState(); return ( {children} ); }; export const useStreamingAvatarContext = () => { return React.useContext(StreamingAvatarContext); };