@@ -31,6 +31,8 @@ export class ScribearRecognizer implements Recognizer {
3131 private language : string
3232 private recorder ?: RecordRTC ;
3333 private kSampleRate = 16000 ;
34+ private lastAudioTimestamp : number | null = null ;
35+ private inactivityInterval : any = null ;
3436
3537 urlParams = new URLSearchParams ( window . location . search ) ;
3638 mode = this . urlParams . get ( 'mode' ) ;
@@ -50,21 +52,75 @@ export class ScribearRecognizer implements Recognizer {
5052 }
5153
5254 private async _startRecording ( ) {
53- let mic_stream = await navigator . mediaDevices . getUserMedia ( { audio : true , video : false } ) ;
55+ let mic_stream : MediaStream ;
56+ try {
57+ mic_stream = await navigator . mediaDevices . getUserMedia ( { audio : true , video : false } ) ;
58+ } catch ( e ) {
59+ console . error ( 'Failed to access microphone' , e ) ;
60+ try {
61+ store . dispatch ( { type : 'SET_MIC_INACTIVITY' , payload : true } ) ;
62+ } catch ( dispatchErr ) {
63+ console . error ( 'Failed to dispatch SET_MIC_INACTIVITY after mic access error' , dispatchErr ) ;
64+ }
65+ // Surface an API status error to prompt UI; recognizer encapsulates flag setting here
66+ try {
67+ store . dispatch ( { type : 'CHANGE_API_STATUS' , payload : { scribearServerStatus : STATUS . ERROR , scribearServerMessage : 'Microphone permission denied or unavailable' } } ) ;
68+ } catch ( dispatchErr ) {
69+ console . error ( 'Failed to dispatch CHANGE_API_STATUS after mic access error' , dispatchErr ) ;
70+ }
71+ throw e ;
72+ }
5473
5574 this . recorder = new RecordRTC ( mic_stream , {
5675 type : 'audio' ,
5776 mimeType : 'audio/wav' ,
5877 desiredSampRate : this . kSampleRate ,
5978 timeSlice : 50 ,
6079 ondataavailable : async ( blob : Blob ) => {
80+ // update last audio timestamp and mark that we've received at least one audio chunk
81+ this . lastAudioTimestamp = performance . now ( ) ;
82+ try {
83+ const controlState = ( store . getState ( ) as any ) . ControlReducer ;
84+ if ( controlState ?. micNoAudio === true ) {
85+ store . dispatch ( { type : 'SET_MIC_INACTIVITY' , payload : false } ) ;
86+ }
87+ } catch ( e ) {
88+ console . warn ( 'Failed to clear mic inactivity' , e ) ;
89+ }
6190 this . socket ?. send ( blob ) ;
6291 } ,
6392 recorderType : StereoAudioRecorder ,
6493 numberOfAudioChannels : 1 ,
6594 } ) ;
6695
6796 this . recorder . startRecording ( ) ;
97+
98+ // start inactivity monitor
99+ const thresholdMs = 3000 ;
100+ if ( this . inactivityInterval == null ) {
101+ this . inactivityInterval = setInterval ( ( ) => {
102+ try {
103+ const state : any = store . getState ( ) ;
104+ const listening = state . ControlReducer ?. listening === true ;
105+ const micNoAudio = state . ControlReducer ?. micNoAudio === true ;
106+ if ( listening ) {
107+ if ( ! this . lastAudioTimestamp || ( Date . now ( ) - this . lastAudioTimestamp > thresholdMs ) ) {
108+ if ( ! micNoAudio ) {
109+ store . dispatch ( { type : 'SET_MIC_INACTIVITY' , payload : true } ) ;
110+ }
111+ } else {
112+ if ( micNoAudio ) {
113+ store . dispatch ( { type : 'SET_MIC_INACTIVITY' , payload : false } ) ;
114+ }
115+ }
116+ } else {
117+ if ( micNoAudio ) store . dispatch ( { type : 'SET_MIC_INACTIVITY' , payload : false } ) ;
118+ }
119+ } catch ( e ) {
120+ console . warn ( 'Error in mic inactivity interval' , e ) ;
121+ }
122+ } , 1000 ) ;
123+ }
68124 }
69125
70126 /**
@@ -179,6 +235,15 @@ export class ScribearRecognizer implements Recognizer {
179235 if ( ! this . socket ) { return ; }
180236 this . socket . close ( ) ;
181237 this . socket = null ;
238+ if ( this . inactivityInterval ) {
239+ clearInterval ( this . inactivityInterval ) ;
240+ this . inactivityInterval = null ;
241+ }
242+ try {
243+ store . dispatch ( { type : 'SET_MIC_INACTIVITY' , payload : false } ) ;
244+ } catch ( e ) {
245+ console . warn ( 'Failed to clear mic inactivity on stop' , e ) ;
246+ }
182247 }
183248
184249 /**
0 commit comments