更新
This commit is contained in:
@@ -0,0 +1,260 @@
|
||||
import { ref, onUnmounted } from 'vue'
|
||||
|
||||
const MIN_HOLD_MS = 280
|
||||
|
||||
/**
|
||||
* 语音转文字(长按说话):微信小程序 WechatSI;H5 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
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user