@memori.ai/memori-react
Version:
[](https://www.npmjs.com/package/@memori.ai/memori-react)   {
const [isPlaying, setIsPlaying] = useState(false);
const [speakerMuted, setSpeakerMuted] = useState(getLocalConfig('muteSpeaker', !defaultEnableAudio || !defaultSpeakerActive || autoStart));
const { addViseme, resetVisemeQueue, startProcessing, stopProcessing, } = useViseme();
const [hasUserActivatedSpeak, setHasUserActivatedSpeak] = useState(false);
const [error, setError] = useState(null);
const audioRef = useRef(null);
const audioWrapperRef = useRef(null);
const globalSpeakRef = useRef(null);
const visemeLoadedRef = useRef(false);
const apiUrl = options.apiUrl || '/api/tts';
const loadVisemeData = useCallback((visemeData) => {
resetVisemeQueue();
visemeLoadedRef.current = false;
if (visemeData && visemeData.length > 0) {
console.log(`[useTTS] Loading ${visemeData.length} viseme events`);
visemeData.forEach(viseme => {
addViseme(viseme.visemeId, viseme.audioOffset);
});
visemeLoadedRef.current = true;
return true;
}
else {
console.warn('[useTTS] No viseme data available');
return false;
}
}, [addViseme, resetVisemeQueue]);
const createAudioWrapper = useCallback(() => {
if (!audioRef.current) {
console.warn('[useTTS] Cannot create audio wrapper: audio element is null');
return null;
}
const wrapper = {
state: 'running',
onstatechange: null,
get currentTime() {
return audioRef.current ? audioRef.current.currentTime : 0;
}
};
const handlePause = () => {
wrapper.state = 'suspended';
if (wrapper.onstatechange) {
wrapper.onstatechange.call(null, new Event('statechange'));
}
};
const handlePlay = () => {
wrapper.state = 'running';
if (wrapper.onstatechange) {
wrapper.onstatechange.call(null, new Event('statechange'));
}
};
const handleEnded = () => {
wrapper.state = 'closed';
if (wrapper.onstatechange) {
wrapper.onstatechange.call(null, new Event('statechange'));
}
};
audioRef.current.addEventListener('pause', handlePause);
audioRef.current.addEventListener('play', handlePlay);
audioRef.current.addEventListener('ended', handleEnded);
const cleanupEventListeners = () => {
if (audioRef.current) {
audioRef.current.removeEventListener('pause', handlePause);
audioRef.current.removeEventListener('play', handlePlay);
audioRef.current.removeEventListener('ended', handleEnded);
}
};
wrapper.cleanup = cleanupEventListeners;
console.log('[useTTS] Created audio wrapper for viseme processing');
return wrapper;
}, []);
const cleanup = useCallback(() => {
var _a;
console.log('[useTTS] Cleaning up audio and viseme resources');
if (audioWrapperRef.current && audioWrapperRef.current.cleanup) {
audioWrapperRef.current.cleanup();
console.log('[useTTS] Cleaned up audio wrapper event listeners');
}
audioWrapperRef.current = null;
stopProcessing();
console.log('[useTTS] Stopped viseme processing');
if ((_a = audioRef.current) === null || _a === void 0 ? void 0 : _a.src) {
URL.revokeObjectURL(audioRef.current.src);
console.log('[useTTS] Revoked audio object URL');
audioRef.current = null;
}
visemeLoadedRef.current = false;
}, [stopProcessing]);
const stop = useCallback(() => {
console.log('[useTTS] Stopping audio playback');
if (audioRef.current) {
audioRef.current.pause();
audioRef.current.currentTime = 0;
}
setIsPlaying(false);
cleanup();
}, [cleanup]);
const emitEndSpeakEvent = useCallback(() => {
console.log('[useTTS] Emitting end speak event');
const e = new CustomEvent('MemoriEndSpeak');
document.dispatchEvent(e);
if (options.continuousSpeech && options.onEndSpeakStartListen) {
console.log('[useTTS] Starting continuous speech listening');
options.onEndSpeakStartListen();
}
}, [options.continuousSpeech, options.onEndSpeakStartListen]);
const speak = useCallback(async (text) => {
console.log('[useTTS] Starting speech synthesis for text:', text);
if (!text || options.preview || speakerMuted) {
console.log('[useTTS] Speech cancelled - empty text, preview mode, or muted');
emitEndSpeakEvent();
return;
}
if (!hasUserActivatedSpeak) {
console.log('[useTTS] First user activation of speak');
setHasUserActivatedSpeak(true);
}
try {
stop();
setIsPlaying(true);
setError(null);
const processedText = sanitizeText(text);
console.log('[useTTS] Processed text:', processedText);
console.log('[useTTS] Making API request to:', 'http://localhost:3000/api/tts', config.voice);
const response = await fetch('http://localhost:3000/api/tts', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
text: processedText,
tenant: config.tenant || 'www.aisuru.com',
voice: config.voice,
model: config.model || 'tts-1',
region: config.region,
provider: config.provider,
includeVisemes: true,
}),
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.error || `API error: ${response.status}`);
}
console.log('[useTTS] Checking for viseme data in response headers');
const visemeDataHeader = response.headers.get('X-Viseme-Data');
console.log('[useTTS] Viseme data header present:', visemeDataHeader ? 'Yes' : 'No');
let hasVisemeData = false;
if (visemeDataHeader) {
console.log('[useTTS] Found viseme data header, attempting to parse');
try {
const visemeData = JSON.parse(visemeDataHeader);
console.log('[useTTS] Successfully parsed viseme data, entries:', visemeData.length);
hasVisemeData = loadVisemeData(visemeData);
console.log('[useTTS] Loaded viseme data into queue:', hasVisemeData);
}
catch (err) {
console.error('[useTTS] Failed to parse viseme data:', err);
}
}
else {
console.log('[useTTS] No viseme data found in response headers');
}
console.log('[useTTS] Getting audio blob from response');
const audioBlob = await response.blob();
console.log('[useTTS] Received audio blob of size:', audioBlob.size);
const audioUrl = URL.createObjectURL(audioBlob);
console.log('[useTTS] Created audio URL:', audioUrl);
console.log('[useTTS] Creating new Audio element');
audioRef.current = new Audio(audioUrl);
console.log('[useTTS] Configuring audio event handlers');
if (hasVisemeData) {
audioWrapperRef.current = createAudioWrapper();
}
audioRef.current.oncanplaythrough = async () => {
var _a;
console.log('[useTTS] Audio can play through, ready to start playback');
try {
if (hasVisemeData && audioWrapperRef.current) {
console.log('[useTTS] Starting viseme processing before audio playback');
startProcessing(audioWrapperRef.current);
console.log('[useTTS] Viseme processing started successfully');
}
console.log('[useTTS] Starting audio playback');
await ((_a = audioRef.current) === null || _a === void 0 ? void 0 : _a.play());
console.log('[useTTS] Audio playback started successfully');
if (audioRef.current) {
audioRef.current.oncanplaythrough = null;
}
}
catch (e) {
console.error('[useTTS] Error in canplaythrough handler:', e);
cleanup();
emitEndSpeakEvent();
}
};
audioRef.current.onended = () => {
console.log('[useTTS] Audio playback ended normally');
setIsPlaying(false);
cleanup();
emitEndSpeakEvent();
};
audioRef.current.onerror = e => {
console.error('[useTTS] Audio playback error:', e);
setIsPlaying(false);
cleanup();
const errorMsg = new Error(`Audio error: ${e}`);
setError(errorMsg);
emitEndSpeakEvent();
};
console.log('[useTTS] Beginning audio load');
audioRef.current.load();
}
catch (err) {
console.error('[useTTS] Error during speech synthesis:', err);
setIsPlaying(false);
cleanup();
const errorMsg = err instanceof Error ? err : new Error(String(err));
setError(errorMsg);
try {
if ('speechSynthesis' in window) {
console.log('[useTTS] Attempting browser fallback synthesis');
const utterance = new SpeechSynthesisUtterance(sanitizeText(text));
window.speechSynthesis.speak(utterance);
}
}
catch (fallbackErr) {
console.error('[useTTS] Browser fallback synthesis error:', fallbackErr);
}
emitEndSpeakEvent();
}
}, [
config,
speakerMuted,
options.preview,
hasUserActivatedSpeak,
stop,
cleanup,
loadVisemeData,
createAudioWrapper,
startProcessing,
emitEndSpeakEvent,
]);
const toggleMute = useCallback((mute) => {
const newMuteState = mute !== undefined ? mute : !speakerMuted;
console.log('[useTTS] Toggling mute state to:', newMuteState);
setSpeakerMuted(newMuteState);
if (newMuteState && isPlaying) {
stop();
}
}, [speakerMuted, isPlaying, stop]);
useEffect(() => {
console.log('[useTTS] Updating global memoriSpeaking state:', isPlaying);
if (typeof window !== 'undefined') {
window.memoriSpeaking = isPlaying;
}
}, [isPlaying]);
useEffect(() => {
if (typeof window !== 'undefined') {
console.log('[useTTS] Setting up global speak function');
globalSpeakRef.current = window.speak;
window.speak = speak;
return () => {
console.log('[useTTS] Cleaning up global speak function');
window.speak = globalSpeakRef.current;
};
}
}, [speak]);
useEffect(() => {
return () => {
console.log('[useTTS] Component unmounting, cleaning up');
stop();
};
}, [stop]);
return {
speak,
stop,
isPlaying,
speakerMuted,
toggleMute,
hasUserActivatedSpeak,
setHasUserActivatedSpeak,
error,
setError,
};
}
//# sourceMappingURL=useTTS.js.map