import { ref, onBeforeUnmount, onMounted } from 'vue' import { onHide, onUnload } from '@dcloudio/uni-app' function formatTextForSpeech(rawText) { return rawText // 去除链接 markdown 格式 [xxx](url) .replace(/\[([^\]]+)\]\([^)]+\)/g, '$1') // 去除 Markdown 语法符号 .replace(/[*_`>#\-]/g, '') // 将换行转换为句号 .replace(/\n+/g, '。') // 多个标点统一转为句号(表示停顿) .replace(/[。,,.、!!??;;::~~…··\-\/\\]{1,}/g, '。') // 合并多个句号 .replace(/([。]{2,})/g, '。') // 去除多余空格 .replace(/\s+/g, ' ') // 去除开头结尾的句号 .replace(/^[。]+|[。]+$/g, '') .trim() } export function useSpeechReader() { const isSpeaking = ref(false) const isPaused = ref(false) let utterance = null const cleanMarkdown = (text) => { return formatTextForSpeech(text) } const speak = (text, options = { lang: 'zh-CN', rate: 0.9, pitch: 1.2 }) => { cancel() // 重置之前的 const voices = speechSynthesis.getVoices() const chineseVoices = voices.filter(v => v.lang.includes('zh')) // console.log(chineseVoices.map((item) => item.name)) const cleanText = cleanMarkdown(text) utterance = new SpeechSynthesisUtterance(cleanText) // utterance.voice = chineseVoices.find(v => v.name === 'Shelley') utterance.lang = options.lang || 'zh-CN' utterance.rate = options.rate || 1 utterance.pitch = options.pitch || 1.1 // 音调(0 - 2,偏高比较柔和) utterance.onend = () => { isSpeaking.value = false isPaused.value = false } speechSynthesis.speak(utterance) isSpeaking.value = true isPaused.value = false } const pause = () => { if (isSpeaking.value && !isPaused.value) { speechSynthesis.pause() isPaused.value = true } } const resume = () => { if (isSpeaking.value && isPaused.value) { speechSynthesis.resume() isPaused.value = false } } const cancel = () => { speechSynthesis.cancel() isSpeaking.value = false isPaused.value = false } // 页面刷新/关闭时 onMounted(() => { if (typeof window !== 'undefined') { window.addEventListener('beforeunload', cancel) } }) onBeforeUnmount(() => { cancel() if (typeof window !== 'undefined') { window.removeEventListener('beforeunload', cancel) } }) onHide(cancel) onUnload(cancel) return { speak, pause, resume, cancel, isSpeaking, isPaused, } }