@@ -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,19 @@ 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
+ if #available( iOS 18 . 0 , * ) , let previousResult = previousResult {
441
+ previousResultWasFinal = previousResult. speechRecognitionMetadata? . speechDuration ?? 0 > 0
442
+ }
443
+
444
+ if !previousResultWasFinal || previousResult? . transcriptions. isEmpty {
445
+ // https://developer.mozilla.org/en-US/docs/Web/API/SpeechRecognition/nomatch_event
446
+ // The nomatch event of the Web Speech API is fired
447
+ // when the speech recognition service returns a final result with no significant recognition.
448
+ sendEvent ( " nomatch " )
449
+ return
450
+ }
430
451
}
431
452
432
453
sendEvent (
@@ -436,6 +457,8 @@ public class ExpoSpeechRecognitionModule: Module {
436
457
" results " : results. map { $0. toDictionary ( ) } ,
437
458
]
438
459
)
460
+
461
+ previousResult = result
439
462
}
440
463
441
464
func handleRecognitionError( _ error: Error ) {
0 commit comments