analytica-frontend-lib
Version:
Repositório público dos componentes utilizados nas plataformas da Analytica Ensino
187 lines (183 loc) • 5.36 kB
JavaScript
import {
useAccessibilityStore
} from "./chunk-7CD5FIDV.mjs";
// src/hooks/useTTS.ts
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
// src/components/AccessibilityWidget/tts/WebSpeechProvider.ts
var WebSpeechProvider = class {
synth;
currentUtterance = null;
constructor(synth = getSynth()) {
this.synth = synth;
}
isSupported() {
return this.synth !== null;
}
async getVoices() {
if (!this.synth) return [];
const synth = this.synth;
const collect = () => synth.getVoices().map((v) => ({
id: v.voiceURI,
name: v.name,
lang: v.lang,
isLocal: v.localService
}));
const initial = collect();
if (initial.length > 0) return initial;
return new Promise((resolve) => {
const onChange = () => {
synth.removeEventListener("voiceschanged", onChange);
resolve(collect());
};
synth.addEventListener("voiceschanged", onChange);
setTimeout(() => {
synth.removeEventListener("voiceschanged", onChange);
resolve(collect());
}, 1500);
});
}
speak(text, options = {}, events = {}) {
if (!this.synth) {
events.onError?.("S\xEDntese de voz n\xE3o dispon\xEDvel neste navegador.");
return;
}
const trimmed = text.trim();
if (!trimmed) return;
this.synth.cancel();
const utterance = new SpeechSynthesisUtterance(trimmed);
utterance.rate = options.rate ?? 1;
utterance.pitch = options.pitch ?? 1;
const allVoices = this.synth.getVoices();
let chosenVoice;
if (options.voiceId) {
chosenVoice = allVoices.find((v) => v.voiceURI === options.voiceId);
} else if (options.lang) {
chosenVoice = allVoices.find(
(v) => v.lang.toLowerCase().startsWith(options.lang.toLowerCase())
);
}
if (chosenVoice) {
utterance.voice = chosenVoice;
utterance.lang = chosenVoice.lang;
}
utterance.onstart = () => events.onStart?.();
utterance.onend = () => {
this.currentUtterance = null;
events.onEnd?.();
};
utterance.onpause = () => events.onPause?.();
utterance.onresume = () => events.onResume?.();
utterance.onerror = (event) => {
this.currentUtterance = null;
events.onError?.(event.error || "Erro desconhecido na s\xEDntese.");
};
this.currentUtterance = utterance;
this.synth.speak(utterance);
}
pause() {
this.synth?.pause();
}
resume() {
this.synth?.resume();
}
stop() {
this.synth?.cancel();
this.currentUtterance = null;
}
};
var getSynth = () => {
if (typeof globalThis === "undefined") return null;
const synth = globalThis.speechSynthesis;
return synth ?? null;
};
// src/hooks/useTTS.ts
var useTTS = (providerOverride) => {
const setTTSStatus = useAccessibilityStore((s) => s.setTTSStatus);
const ttsRate = useAccessibilityStore((s) => s.ttsRate);
const ttsVoiceId = useAccessibilityStore((s) => s.ttsVoiceId);
const provider = useMemo(
() => providerOverride ?? new WebSpeechProvider(),
[providerOverride]
);
const [voices, setVoices] = useState([]);
const rateRef = useRef(ttsRate);
const voiceIdRef = useRef(ttsVoiceId);
rateRef.current = ttsRate;
voiceIdRef.current = ttsVoiceId;
const isActiveSpeakerRef = useRef(false);
useEffect(() => {
let mounted = true;
if (provider.isSupported()) {
provider.getVoices().then((list) => {
if (mounted) setVoices(list);
}).catch(() => void 0);
}
return () => {
mounted = false;
if (isActiveSpeakerRef.current) {
provider.stop();
setTTSStatus("idle");
isActiveSpeakerRef.current = false;
}
};
}, [provider, setTTSStatus]);
const hasPortugueseVoice = voices.some(
(v) => v.lang.toLowerCase().startsWith("pt")
);
const speak = useCallback(
(text) => {
isActiveSpeakerRef.current = true;
provider.speak(
text,
{
rate: rateRef.current,
voiceId: voiceIdRef.current ?? void 0
// Não fixamos `lang: 'pt-BR'` aqui — em sistemas sem voz pt-BR
// instalada, o browser fica tentando achar uma e nunca emite
// áudio. O provider usa o `voice.lang` quando há voz escolhida.
},
{
onStart: () => setTTSStatus("speaking"),
onEnd: () => {
isActiveSpeakerRef.current = false;
setTTSStatus("idle");
},
onPause: () => setTTSStatus("paused"),
onResume: () => setTTSStatus("speaking"),
onError: () => {
isActiveSpeakerRef.current = false;
setTTSStatus("idle");
}
}
);
},
[provider, setTTSStatus]
);
const pause = useCallback(() => {
provider.pause();
setTTSStatus("paused");
}, [provider, setTTSStatus]);
const resume = useCallback(() => {
provider.resume();
setTTSStatus("speaking");
}, [provider, setTTSStatus]);
const stop = useCallback(() => {
isActiveSpeakerRef.current = false;
provider.stop();
setTTSStatus("idle");
}, [provider, setTTSStatus]);
return {
isSupported: provider.isSupported(),
voices,
hasPortugueseVoice,
speak,
pause,
resume,
stop
};
};
export {
WebSpeechProvider,
useTTS
};
//# sourceMappingURL=chunk-4PIITPCV.mjs.map