@@ -2231,6 +2231,9 @@ const TranscriptCtrl = {
22312231// messages so that failures are visible on Android where DevTools is unavailable.
22322232const DebugLog = {
22332233 _entries : [ ] ,
2234+ // Timestamp of last GitHub Issue open — prevents duplicate submissions
2235+ // from double-tap or multiple rapid clicks on mobile.
2236+ _lastIssueOpenAt : 0 ,
22342237
22352238 /** Convert mixed console arguments to a single string. */
22362239 _fmt ( args ) {
@@ -2307,10 +2310,11 @@ const DebugLog = {
23072310 }
23082311 return [
23092312 `UA: ${ navigator . userAgent } ` ,
2310- `SR: ${ sr ? 'available' : 'NOT AVAILABLE' } | running: ${ State . isRunning } | lang: ${ State . recognitionLang || '(auto)' } ` ,
2313+ `SR: ${ sr ? 'available' : 'NOT AVAILABLE' } | running: ${ State . isRunning } | lang: ${ State . recognitionLang || '(auto)' } | isMobile: ${ isMobileBrowser ( ) } ` ,
23112314 `Online: ${ navigator . onLine } | SecureCtx: ${ window . isSecureContext } | SW: ${ sw } ` ,
23122315 `AudioCtx: ${ ctx ? `${ ctx . state } @ ${ ctx . sampleRate } Hz` : 'not started' } | Meyda: ${ meydaStr } ` ,
23132316 `MicEnergy: ${ micEnergyStr } ` ,
2317+ `SRRetries: noResult=${ SpeechEngine . _noResultCount } quickRestart=${ SpeechEngine . _quickRestartCount } network=${ SpeechEngine . _networkRetryCount } ` ,
23142318 `Viewport: ${ window . innerWidth } \u00d7${ window . innerHeight } | Screen: ${ window . screen . width } \u00d7${ window . screen . height } ` ,
23152319 `Config: maxSpeakers: ${ State . maxSpeakers } | matchThreshold: ${ CFG . SIGNATURE_MATCH_SIMILARITY } | hysteresisMargin: ${ CFG . HYSTERESIS_MARGIN } | hysteresisLock: ${ CFG . HYSTERESIS_LOCK_MS } ms` ,
23162320 `Speakers: ${ State . profiles . length } active — ${ profilesStr } ` ,
@@ -2400,6 +2404,11 @@ const DebugLog = {
24002404 * would exceed GitHub's effective limit (~8 000 encoded characters).
24012405 */
24022406 openGitHubIssue ( ) {
2407+ // Prevent duplicate submissions from double-tap or rapid clicks on mobile.
2408+ const now = Date . now ( ) ;
2409+ if ( now - this . _lastIssueOpenAt < 3000 ) return ;
2410+ this . _lastIssueOpenAt = now ;
2411+
24032412 const BASE_URL = 'https://github.com/mgifford/EchoLocate/issues/new' ;
24042413 const MAX_URL = 8000 ; // conservative safe limit for GitHub URLs
24052414 const TITLE_PART = '?title=' + encodeURIComponent ( 'Bug Report' ) + '&body=' ;
@@ -2744,12 +2753,19 @@ const SpeechEngine = {
27442753 // Some Android devices route mic audio exclusively to whichever API
27452754 // initialises first; suspending the AudioContext temporarily hands that
27462755 // priority to SR. The AudioContext is resumed inside rec.onstart once SR
2747- // is listening and its pipeline is active.
2756+ // is listening and its pipeline is active. A short delay after the
2757+ // suspend gives the Android audio hardware time to settle before SR begins.
27482758 if ( isMobileBrowser ( ) && State . audioCtx && State . audioCtx . state === 'running' ) {
27492759 console . log ( '[EchoLocate] Mobile: suspending AudioContext to give SpeechRecognition mic priority' ) ;
2750- State . audioCtx . suspend ( ) . catch ( ( ) => { } ) . finally ( ( ) => this . _startRec ( ) ) ;
2760+ State . audioCtx . suspend ( )
2761+ . catch ( ( ) => { } )
2762+ . then ( ( ) => new Promise ( resolve => setTimeout ( resolve , 200 ) ) )
2763+ . then ( ( ) => this . _startRec ( ) ) ;
27512764 return ;
27522765 }
2766+ if ( isMobileBrowser ( ) ) {
2767+ console . log ( `[EchoLocate] Mobile: AudioContext state is ${ State . audioCtx ?. state ?? 'none' } — starting SR without suspend` ) ;
2768+ }
27532769 this . _startRec ( ) ;
27542770 } ,
27552771
0 commit comments