Files
zyt/TUICallKit-Vue3/tongji/composables/useSpeechToText.js
T
2026-06-01 18:04:23 +08:00

261 lines
7.3 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import { ref, onUnmounted } from 'vue'
const MIN_HOLD_MS = 280
/**
* 语音转文字(长按说话):微信小程序 WechatSIH5 Web Speech API。
*
* @param {{ onResult?: (text: string) => void, showToast?: (msg: string) => void }} options
*/
export function useSpeechToText({ onResult, showToast } = {}) {
const sttListening = ref(false)
const sttHolding = ref(false)
const sttSupported = ref(false)
let wxRecordManager = null
let h5Recognition = null
let holdStartTs = 0
let holdSessionId = 0
let startRequested = false
let recordAuthorized = null
function notify(msg) {
if (!msg) return
if (typeof showToast === 'function') {
showToast(msg)
return
}
uni.showToast({ title: msg, icon: 'none', duration: 2000 })
}
function emitResult(text) {
const t = String(text || '').trim()
const heldMs = Date.now() - holdStartTs
if (!t) {
if (heldMs < MIN_HOLD_MS) return
notify('没听清,请再试一次')
return
}
if (typeof onResult === 'function') {
onResult(t)
}
}
function stopListening() {
// #ifdef MP-WEIXIN
if (wxRecordManager && (sttListening.value || startRequested)) {
try {
wxRecordManager.stop()
} catch (e) {}
}
// #endif
// #ifdef H5
if (h5Recognition && sttListening.value) {
try {
h5Recognition.stop()
} catch (e) {}
}
// #endif
startRequested = false
sttListening.value = false
}
function ensureRecordAuth(onGranted) {
// #ifdef MP-WEIXIN
if (recordAuthorized === true) {
onGranted()
return
}
uni.authorize({
scope: 'scope.record',
success() {
recordAuthorized = true
onGranted()
},
fail() {
recordAuthorized = false
sttHolding.value = false
uni.showModal({
title: '需要麦克风权限',
content: '请在设置中允许使用麦克风,以便长按说话',
confirmText: '去设置',
success(res) {
if (res.confirm) {
uni.openSetting({})
}
}
})
}
})
// #endif
// #ifndef MP-WEIXIN
onGranted()
// #endif
}
function startWechatRecord(session) {
// #ifdef MP-WEIXIN
if (!wxRecordManager || session !== holdSessionId || !sttHolding.value) {
return
}
try {
startRequested = true
wxRecordManager.start({
duration: 60000,
lang: 'zh_CN'
})
} catch (e) {
startRequested = false
sttHolding.value = false
notify('无法开始录音')
}
// #endif
}
function startH5Record() {
// #ifdef H5
if (!h5Recognition) {
notify('当前浏览器不支持语音输入')
sttHolding.value = false
return
}
try {
startRequested = true
h5Recognition.start()
sttListening.value = true
} catch (e) {
startRequested = false
sttHolding.value = false
notify('无法开始语音识别,请检查麦克风权限')
}
// #endif
}
/** 长按开始:手指按下 */
function beginHoldSpeech() {
if (!sttSupported.value) {
notify('当前环境不支持语音输入')
return
}
if (sttHolding.value) return
const session = ++holdSessionId
holdStartTs = Date.now()
sttHolding.value = true
// #ifdef MP-WEIXIN
ensureRecordAuth(() => startWechatRecord(session))
// #endif
// #ifdef H5
startH5Record()
// #endif
// #ifndef MP-WEIXIN
// #ifndef H5
sttHolding.value = false
notify('当前端暂不支持语音输入')
// #endif
// #endif
}
/** 松手结束:手指抬起 */
function endHoldSpeech() {
if (!sttHolding.value && !sttListening.value && !startRequested) {
return
}
sttHolding.value = false
if (sttListening.value || startRequested) {
stopListening()
return
}
holdSessionId += 1
}
// #ifdef MP-WEIXIN
try {
// eslint-disable-next-line no-undef
const plugin = requirePlugin('WechatSI')
wxRecordManager = plugin.getRecordRecognitionManager()
sttSupported.value = !!wxRecordManager
wxRecordManager.onStart = () => {
if (!sttHolding.value) {
try {
wxRecordManager.stop()
} catch (e) {}
return
}
startRequested = false
sttListening.value = true
}
wxRecordManager.onStop = (res) => {
sttListening.value = false
startRequested = false
emitResult(res?.result)
}
wxRecordManager.onError = (res) => {
sttListening.value = false
startRequested = false
sttHolding.value = false
notify(res?.msg || '语音识别失败')
}
} catch (e) {
console.warn('WechatSI record recognition not available', e)
}
// #endif
// #ifdef H5
try {
if (typeof window !== 'undefined') {
const SR = window.SpeechRecognition || window.webkitSpeechRecognition
if (SR) {
h5Recognition = new SR()
h5Recognition.lang = 'zh-CN'
h5Recognition.interimResults = false
h5Recognition.continuous = false
h5Recognition.maxAlternatives = 1
sttSupported.value = true
h5Recognition.onresult = (event) => {
const item = event.results?.[0]?.[0]
emitResult(item?.transcript || '')
}
h5Recognition.onend = () => {
sttListening.value = false
startRequested = false
sttHolding.value = false
}
h5Recognition.onerror = (event) => {
sttListening.value = false
startRequested = false
sttHolding.value = false
if (event?.error === 'not-allowed') {
notify('请允许浏览器使用麦克风')
} else if (event?.error !== 'aborted') {
notify('语音识别失败')
}
}
}
}
} catch (e) {
console.warn('H5 SpeechRecognition not available', e)
}
// #endif
onUnmounted(() => {
sttHolding.value = false
holdSessionId += 1
stopListening()
})
return {
sttListening,
sttHolding,
sttSupported,
beginHoldSpeech,
endHoldSpeech,
stopSpeechToText: endHoldSpeech
}
}