1- import { reactive , onUnmounted , ref } from 'vue'
1+ import { reactive , onUnmounted , shallowRef , toValue , watch } from 'vue'
2+ import type { MaybeRefOrGetter } from 'vue'
23import type {
34 SpeechHookOptions ,
45 SpeechHandlerResult ,
@@ -16,9 +17,10 @@ import { WebSpeechHandler } from './webSpeechHandler'
1617 * @param options 语音识别配置
1718 * @returns 语音识别控制器
1819 */
19- export function useSpeechHandler ( options : SpeechHookOptions ) : SpeechHandlerResult {
20- // 使用 ref 存储 options,确保能获取最新值
21- const optionsRef = ref ( options )
20+ export function useSpeechHandler ( options : MaybeRefOrGetter < SpeechHookOptions > ) : SpeechHandlerResult {
21+ const handlerRef = shallowRef < SpeechHandler | null > ( null )
22+ const pendingRestart = shallowRef ( false )
23+ const suppressEndCallback = shallowRef ( false )
2224
2325 // 语音识别状态
2426 const speechState = reactive < SpeechState > ( {
@@ -27,86 +29,150 @@ export function useSpeechHandler(options: SpeechHookOptions): SpeechHandlerResul
2729 error : undefined ,
2830 } )
2931
30- // 创建回调函数集合 - 使用函数形式,每次调用时获取最新的 options
32+ const resolveOptions = ( ) => toValue ( options )
33+
34+ const updateSupportState = ( ) => {
35+ const currentOptions = resolveOptions ( )
36+ speechState . isSupported = currentOptions . customHandler
37+ ? currentOptions . customHandler . isSupported ( )
38+ : WebSpeechHandler . isSupported ( )
39+ }
40+
41+ const createHandler = ( currentOptions : SpeechHookOptions ) : SpeechHandler | null => {
42+ if ( currentOptions . customHandler ) {
43+ return currentOptions . customHandler
44+ }
45+
46+ if ( ! WebSpeechHandler . isSupported ( ) ) {
47+ return null
48+ }
49+
50+ return new WebSpeechHandler ( currentOptions )
51+ }
52+
53+ // 创建回调函数集合 - 每次调用时都获取最新的 options
3154 const callbacks : SpeechCallbacks = {
3255 onStart : ( ) => {
3356 speechState . isRecording = true
3457 speechState . error = undefined
35- optionsRef . value . onStart ?.( )
58+ resolveOptions ( ) . onStart ?.( )
3659 } ,
3760 onInterim : ( transcript : string ) => {
38- optionsRef . value . onInterim ?.( transcript )
61+ resolveOptions ( ) . onInterim ?.( transcript )
3962 } ,
4063 onFinal : ( transcript : string ) => {
41- optionsRef . value . onFinal ?.( transcript )
64+ resolveOptions ( ) . onFinal ?.( transcript )
4265 } ,
4366 onEnd : ( transcript ?: string ) => {
67+ const shouldEmitEnd = ! suppressEndCallback . value
68+ const shouldRestart = pendingRestart . value
69+
70+ suppressEndCallback . value = false
71+ pendingRestart . value = false
72+ handlerRef . value = null
73+
4474 if ( speechState . isRecording ) {
4575 speechState . isRecording = false
46- optionsRef . value . onEnd ?.( transcript )
76+ }
77+
78+ if ( shouldEmitEnd ) {
79+ resolveOptions ( ) . onEnd ?.( transcript )
80+ }
81+
82+ updateSupportState ( )
83+
84+ if ( shouldRestart ) {
85+ start ( )
4786 }
4887 } ,
4988 onError : ( error : Error ) => {
5089 speechState . error = error
5190 speechState . isRecording = false
52- optionsRef . value . onError ?.( error )
91+ pendingRestart . value = false
92+ suppressEndCallback . value = false
93+ handlerRef . value = null
94+ resolveOptions ( ) . onError ?.( error )
95+ updateSupportState ( )
5396 } ,
5497 }
5598
56- // 检查是否支持(对于内置 Handler,提前检查避免无效创建)
57- const isBuiltinSupported = WebSpeechHandler . isSupported ( )
58- speechState . isSupported = options . customHandler ? options . customHandler . isSupported ( ) : isBuiltinSupported
59-
60- // 选择语音处理器:如果提供了 customHandler,直接使用;否则在支持的情况下创建 WebSpeechHandler
61- const handler : SpeechHandler | null =
62- options . customHandler ?? ( isBuiltinSupported ? new WebSpeechHandler ( options ) : null )
99+ watch (
100+ ( ) => resolveOptions ( ) . customHandler ,
101+ ( ) => {
102+ if ( ! speechState . isRecording ) {
103+ handlerRef . value = null
104+ }
105+ updateSupportState ( )
106+ } ,
107+ { immediate : true } ,
108+ )
63109
64110 // 开始录音
65111 const start = ( ) => {
66- if ( ! speechState . isSupported || ! handler ) {
112+ const currentOptions = resolveOptions ( )
113+
114+ updateSupportState ( )
115+
116+ if ( ! speechState . isSupported ) {
67117 const error = new Error ( '语音识别不受支持' )
68118 speechState . error = error
69- optionsRef . value . onError ?.( error )
119+ currentOptions . onError ?.( error )
70120 return
71121 }
72122
73- // 如果正在录音,先停止再重新开始
123+ // 如果正在录音,等待当前会话自然结束后再重启
74124 if ( speechState . isRecording ) {
75- handler . stop ( )
76- speechState . isRecording = false
77- // 短暂延迟后重新开始
78- setTimeout ( ( ) => {
79- handler . start ( callbacks )
80- } , 200 )
125+ pendingRestart . value = true
126+ handlerRef . value ?. stop ( )
81127 return
82128 }
83129
130+ const nextHandler = createHandler ( currentOptions )
131+
132+ if ( ! nextHandler || ! nextHandler . isSupported ( ) ) {
133+ const error = new Error ( '语音识别不受支持' )
134+ speechState . error = error
135+ currentOptions . onError ?.( error )
136+ updateSupportState ( )
137+ return
138+ }
139+
140+ handlerRef . value = nextHandler
141+ pendingRestart . value = false
142+ suppressEndCallback . value = false
143+
84144 try {
85- handler . start ( callbacks )
145+ nextHandler . start ( callbacks )
86146 } catch ( error ) {
87147 speechState . error = error instanceof Error ? error : new Error ( '启动失败' )
88- optionsRef . value . onError ?.( speechState . error )
148+ handlerRef . value = null
149+ currentOptions . onError ?.( speechState . error )
89150 }
90151 }
91152
92153 // 停止录音
93154 const stop = ( ) => {
94- if ( ! speechState . isRecording || ! handler ) {
155+ if ( ! speechState . isRecording || ! handlerRef . value ) {
95156 return
96157 }
97158
98- handler . stop ( )
99- callbacks . onEnd ( )
159+ pendingRestart . value = false
160+ suppressEndCallback . value = false
161+ handlerRef . value . stop ( )
100162 }
101163
102164 // 组件卸载时清理资源
103165 onUnmounted ( ( ) => {
104166 // 如果正在录音,先停止
105- if ( speechState . isRecording && handler ) {
106- handler . stop ( )
167+ if ( speechState . isRecording && handlerRef . value ) {
168+ pendingRestart . value = false
169+ suppressEndCallback . value = true
170+ handlerRef . value . stop ( )
107171 // 卸载时不触发 onEnd 回调,避免不必要的副作用
108172 speechState . isRecording = false
109173 }
174+
175+ handlerRef . value = null
110176 } )
111177
112178 return {
0 commit comments