@@ -41,6 +41,14 @@ public class ExpoSpeechRecognitionModule: Module {
41
41
// This is a temporary workaround until the issue is fixed in a future iOS release
42
42
var hasSeenFinalResult : Bool = false
43
43
44
+ // Hack for iOS 18 to avoid sending a "nomatch" event after the final-final result
45
+ // Example event order emitted in iOS 18:
46
+ // [
47
+ // { isFinal: false, transcripts: ["actually", "final", "results"], metadata: { duration: 1500 } },
48
+ // { isFinal: true, transcripts: [] }
49
+ // ]
50
+ var previousResult : SFSpeechRecognitionResult ?
51
+
44
52
public func definition( ) -> ModuleDefinition {
45
53
// Sets the name of the module that JavaScript code will use to refer to the module. Takes a string as an argument.
46
54
// Can be inferred from module's class name, but it's recommended to set it explicitly for clarity.
@@ -130,6 +138,9 @@ public class ExpoSpeechRecognitionModule: Module {
130
138
do {
131
139
let currentLocale = await speechRecognizer? . getLocale ( )
132
140
141
+ // Reset the previous result
142
+ self . previousResult = nil
143
+
133
144
// Re-create the speech recognizer when locales change
134
145
if self . speechRecognizer == nil || currentLocale != options. lang {
135
146
guard let locale = resolveLocale ( localeIdentifier: options. lang) else {
@@ -358,12 +369,14 @@ public class ExpoSpeechRecognitionModule: Module {
358
369
359
370
func sendErrorAndStop( error: String , message: String ) {
360
371
hasSeenFinalResult = false
372
+ previousResult = nil
361
373
sendEvent ( " error " , [ " error " : error, " message " : message] )
362
374
sendEvent ( " end " )
363
375
}
364
376
365
377
func handleEnd( ) {
366
378
hasSeenFinalResult = false
379
+ previousResult = nil
367
380
sendEvent ( " end " )
368
381
}
369
382
@@ -422,11 +435,21 @@ public class ExpoSpeechRecognitionModule: Module {
422
435
}
423
436
424
437
if isFinal && results. isEmpty {
425
- // https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition/nomatch_event
426
- // The nomatch event of the Web Speech API is fired
427
- // when the speech recognition service returns a final result with no significant recognition.
428
- sendEvent ( " nomatch " )
429
- return
438
+ // Hack for iOS 18 to avoid sending a "nomatch" event after the final-final result
439
+ var previousResultWasFinal = false
440
+ var previousResultHadTranscriptions = false
441
+ if #available( iOS 18 . 0 , * ) , let previousResult = previousResult {
442
+ previousResultWasFinal = previousResult. speechRecognitionMetadata? . speechDuration ?? 0 > 0
443
+ previousResultHadTranscriptions = !previousResult. transcriptions. isEmpty
444
+ }
445
+
446
+ if !previousResultWasFinal || !previousResultHadTranscriptions {
447
+ // https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition/nomatch_event
448
+ // The nomatch event of the Web Speech API is fired
449
+ // when the speech recognition service returns a final result with no significant recognition.
450
+ sendEvent ( " nomatch " )
451
+ return
452
+ }
430
453
}
431
454
432
455
sendEvent (
@@ -436,6 +459,8 @@ public class ExpoSpeechRecognitionModule: Module {
436
459
" results " : results. map { $0. toDictionary ( ) } ,
437
460
]
438
461
)
462
+
463
+ previousResult = result
439
464
}
440
465
441
466
func handleRecognitionError( _ error: Error ) {
0 commit comments