@capgo/capacitor-speech-recognition 8.1.10 → 8.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -244,24 +244,29 @@ Checks whether the native speech recognition service is usable on the current de
244
244
  ### isOnDeviceRecognitionAvailable(...)
245
245
 
246
246
  ```typescript
247
- isOnDeviceRecognitionAvailable(options?: Pick<SpeechRecognitionStartOptions, "language"> | undefined) => Promise<SpeechRecognitionAvailability>
247
+ isOnDeviceRecognitionAvailable(options?: Pick<SpeechRecognitionStartOptions, "language" | "preferLegacyRecognizer"> | undefined) => Promise<SpeechRecognitionAvailability>
248
248
  ```
249
249
 
250
- Checks whether the platform's newer on-device recognition path is available for the selected locale.
250
+ Checks whether on-device speech recognition is available for the selected locale.
251
251
 
252
252
  This is the capability check you should use before enabling `useOnDeviceRecognition`.
253
- A `true` result means the current device, OS version, and locale can use the newer
254
- on-device path for that platform.
253
+ On iOS, the result depends on which recognizer path `start()` will use:
255
254
 
256
- Returns `false` when the device only supports the legacy recognizer path.
255
+ - When `preferLegacyRecognizer` is `false` (default) on iOS 26+, this checks the modern
256
+ `SpeechAnalyzer` path.
257
+ - On older iOS versions, or when `preferLegacyRecognizer` is `true`, this checks
258
+ `SFSpeechRecognizer.supportsOnDeviceRecognition` for the legacy path.
259
+
260
+ Pass the same `preferLegacyRecognizer` value here and in `start()` so the availability
261
+ check matches the route that recognition will take.
257
262
 
258
263
  Platform SDK docs:
259
264
  iOS: [Speech](https://developer.apple.com/documentation/speech)
260
265
  Android: [SpeechRecognizer](https://developer.android.com/reference/android/speech/SpeechRecognizer)
261
266
 
262
- | Param | Type |
263
- | ------------- | ----------------------------------------------------------------------------------------------------------------------------------- |
264
- | **`options`** | <code><a href="#pick">Pick</a>&lt;<a href="#speechrecognitionstartoptions">SpeechRecognitionStartOptions</a>, 'language'&gt;</code> |
267
+ | Param | Type |
268
+ | ------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------- |
269
+ | **`options`** | <code><a href="#pick">Pick</a>&lt;<a href="#speechrecognitionstartoptions">SpeechRecognitionStartOptions</a>, 'language' \| 'preferLegacyRecognizer'&gt;</code> |
265
270
 
266
271
  **Returns:** <code>Promise&lt;<a href="#speechrecognitionavailability">SpeechRecognitionAvailability</a>&gt;</code>
267
272
 
@@ -555,19 +560,20 @@ Removes every registered listener.
555
560
 
556
561
  Configure how the recognizer behaves when calling {@link SpeechRecognitionPlugin.start}.
557
562
 
558
- | Prop | Type | Description |
559
- | ---------------------------- | --------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
560
- | **`language`** | <code>string</code> | Locale identifier such as `en-US`. When omitted the device language is used. |
561
- | **`maxResults`** | <code>number</code> | Maximum number of final matches returned by native APIs. Defaults to `5`. |
562
- | **`prompt`** | <code>string</code> | Prompt message shown inside the Android system dialog (ignored on iOS). |
563
- | **`popup`** | <code>boolean</code> | When `true`, Android shows the OS speech dialog instead of running inline recognition. Defaults to `false`. |
564
- | **`partialResults`** | <code>boolean</code> | Emits partial transcription updates through the `partialResults` listener while audio is captured. |
565
- | **`addPunctuation`** | <code>boolean</code> | Enables native punctuation handling where supported (iOS 16+). |
566
- | **`contextualStrings`** | <code>string[]</code> | Words or phrases that should be recognized more accurately by native speech APIs. On iOS, these are passed to `SFSpeechRecognitionRequest.contextualStrings` when the plugin uses the legacy `SFSpeechRecognizer` path. That path is the default on all iOS versions, the fallback below iOS 26, and still available on iOS 26+ by leaving `useOnDeviceRecognition` disabled. Ignored by Android and by the iOS 26+ `SpeechAnalyzer` path. |
567
- | **`useOnDeviceRecognition`** | <code>boolean</code> | Opt in to the platform's newer on-device recognition path when available. On iOS 26+, this uses Apple's `SpeechAnalyzer` / `SpeechTranscriber` pipeline. On recent Android versions, this uses the on-device `SpeechRecognizer` path. It is intentionally opt-in so existing apps keep the legacy flow unless they choose to roll out the new behavior. On iOS, leaving this disabled keeps `SFSpeechRecognizer` on every supported OS version. Enabling it on older iOS versions or unsupported locales falls back to `SFSpeechRecognizer`. Use {@link SpeechRecognitionPlugin.isOnDeviceRecognitionAvailable} before enabling it in production. Platform SDK docs: iOS: [Speech](https://developer.apple.com/documentation/speech), [SpeechAnalyzer](https://developer.apple.com/documentation/speech/speechanalyzer), [SpeechTranscriber](https://developer.apple.com/documentation/speech/speechtranscriber) Android: [SpeechRecognizer](https://developer.android.com/reference/android/speech/SpeechRecognizer) Defaults to `false`. |
568
- | **`allowForSilence`** | <code>number</code> | Allow a number of milliseconds of silence before splitting the recognition session into segments. Required to be greater than zero and currently supported on Android only. |
569
- | **`continuousPTT`** | <code>boolean</code> | EXPERIMENTAL: Keep a PTT session alive across silence by restarting recognition while the button stays held. This restart behavior is implemented for Android inline recognition and iOS native recognition. |
570
- | **`muteRecognizerBeep`** | <code>boolean</code> | Suppresses the Android system beep when inline recognition starts or restarts. Uses a best-effort combination of an undocumented recognizer intent extra and temporary notification/system stream volume muting. Some devices ignore the intent extra; the volume fallback is the portable path. Defaults to `true` when `continuousPTT` is enabled. |
563
+ | Prop | Type | Description | Default | Since |
564
+ | ---------------------------- | --------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | ------ |
565
+ | **`language`** | <code>string</code> | Locale identifier such as `en-US`. When omitted the device language is used. | | |
566
+ | **`maxResults`** | <code>number</code> | Maximum number of final matches returned by native APIs. Defaults to `5`. | | |
567
+ | **`prompt`** | <code>string</code> | Prompt message shown inside the Android system dialog (ignored on iOS). | | |
568
+ | **`popup`** | <code>boolean</code> | When `true`, Android shows the OS speech dialog instead of running inline recognition. Defaults to `false`. | | |
569
+ | **`partialResults`** | <code>boolean</code> | Emits partial transcription updates through the `partialResults` listener while audio is captured. | | |
570
+ | **`addPunctuation`** | <code>boolean</code> | Enables native punctuation handling where supported (iOS 16+). | | |
571
+ | **`contextualStrings`** | <code>string[]</code> | Words or phrases that should be recognized more accurately by native speech APIs. On iOS, these are passed to `SFSpeechRecognitionRequest.contextualStrings` when the plugin uses the legacy `SFSpeechRecognizer` path. That path is the default on all iOS versions, the fallback below iOS 26, and still available on iOS 26+ either by leaving `useOnDeviceRecognition` disabled or by setting `preferLegacyRecognizer` — so contextual strings and on-device recognition can be used together. Ignored by Android and by the iOS 26+ `SpeechAnalyzer` path. | | |
572
+ | **`useOnDeviceRecognition`** | <code>boolean</code> | Opt in to the platform's newer on-device recognition path when available. On iOS 26+, this uses Apple's `SpeechAnalyzer` / `SpeechTranscriber` pipeline. On recent Android versions, this uses the on-device `SpeechRecognizer` path. It is intentionally opt-in so existing apps keep the legacy flow unless they choose to roll out the new behavior. On iOS, leaving this disabled keeps `SFSpeechRecognizer` on every supported OS version. On the legacy `SFSpeechRecognizer` path, enabling this rejects with `ON_DEVICE_RECOGNITION_UNAVAILABLE` when on-device recognition is not supported for the locale. On iOS 26+, enabling this without `preferLegacyRecognizer` uses the modern `SpeechAnalyzer` path when available; otherwise recognition falls back to the legacy path with the same rejection rule. Use {@link SpeechRecognitionPlugin.isOnDeviceRecognitionAvailable} before enabling it in production. Platform SDK docs: iOS: [Speech](https://developer.apple.com/documentation/speech), [SpeechAnalyzer](https://developer.apple.com/documentation/speech/speechanalyzer), [SpeechTranscriber](https://developer.apple.com/documentation/speech/speechtranscriber) Android: [SpeechRecognizer](https://developer.android.com/reference/android/speech/SpeechRecognizer) Defaults to `false`. | | |
573
+ | **`preferLegacyRecognizer`** | <code>boolean</code> | iOS only: skip the modern `SpeechAnalyzer` path even when it is available, so that `useOnDeviceRecognition` applies to `SFSpeechRecognizer` (`requiresOnDeviceRecognition`) instead. Useful on iOS 26 devices where the modern path starts and stops a session without ever emitting `partialResults`. | <code>false</code> | 8.1.11 |
574
+ | **`allowForSilence`** | <code>number</code> | Allow a number of milliseconds of silence before splitting the recognition session into segments. Required to be greater than zero and currently supported on Android only. | | |
575
+ | **`continuousPTT`** | <code>boolean</code> | EXPERIMENTAL: Keep a PTT session alive across silence by restarting recognition while the button stays held. This restart behavior is implemented for Android inline recognition and iOS native recognition. | | |
576
+ | **`muteRecognizerBeep`** | <code>boolean</code> | Suppresses the Android system beep when inline recognition starts or restarts. Uses a best-effort combination of an undocumented recognizer intent extra and temporary notification/system stream volume muting. Some devices ignore the intent extra; the volume fallback is the portable path. Defaults to `true` when `continuousPTT` is enabled. | | |
571
577
 
572
578
 
573
579
  #### SpeechRecognitionMatches
@@ -42,7 +42,7 @@ public class SpeechRecognitionPlugin extends Plugin implements Constants {
42
42
 
43
43
  public static final String SPEECH_RECOGNITION = "speechRecognition";
44
44
  private static final String TAG = "SpeechRecognition";
45
- private static final String PLUGIN_VERSION = "8.0.10";
45
+ private static final String PLUGIN_VERSION = "8.1.11";
46
46
  private static final int FORCE_STOP_TIMEOUT_MS = 1500;
47
47
  private static final int STOP_FALLBACK_TIMEOUT_MS = 500;
48
48
  private static final int CONTINUOUS_RESTART_DELAY_MS = 100;
package/dist/docs.json CHANGED
@@ -19,17 +19,17 @@
19
19
  },
20
20
  {
21
21
  "name": "isOnDeviceRecognitionAvailable",
22
- "signature": "(options?: Pick<SpeechRecognitionStartOptions, \"language\"> | undefined) => Promise<SpeechRecognitionAvailability>",
22
+ "signature": "(options?: Pick<SpeechRecognitionStartOptions, \"language\" | \"preferLegacyRecognizer\"> | undefined) => Promise<SpeechRecognitionAvailability>",
23
23
  "parameters": [
24
24
  {
25
25
  "name": "options",
26
26
  "docs": "",
27
- "type": "Pick<SpeechRecognitionStartOptions, 'language'> | undefined"
27
+ "type": "Pick<SpeechRecognitionStartOptions, 'language' | 'preferLegacyRecognizer'> | undefined"
28
28
  }
29
29
  ],
30
30
  "returns": "Promise<SpeechRecognitionAvailability>",
31
31
  "tags": [],
32
- "docs": "Checks whether the platform's newer on-device recognition path is available for the selected locale.\n\nThis is the capability check you should use before enabling `useOnDeviceRecognition`.\nA `true` result means the current device, OS version, and locale can use the newer\non-device path for that platform.\n\nReturns `false` when the device only supports the legacy recognizer path.\n\nPlatform SDK docs:\niOS: [Speech](https://developer.apple.com/documentation/speech)\nAndroid: [SpeechRecognizer](https://developer.android.com/reference/android/speech/SpeechRecognizer)",
32
+ "docs": "Checks whether on-device speech recognition is available for the selected locale.\n\nThis is the capability check you should use before enabling `useOnDeviceRecognition`.\nOn iOS, the result depends on which recognizer path `start()` will use:\n\n- When `preferLegacyRecognizer` is `false` (default) on iOS 26+, this checks the modern\n `SpeechAnalyzer` path.\n- On older iOS versions, or when `preferLegacyRecognizer` is `true`, this checks\n `SFSpeechRecognizer.supportsOnDeviceRecognition` for the legacy path.\n\nPass the same `preferLegacyRecognizer` value here and in `start()` so the availability\ncheck matches the route that recognition will take.\n\nPlatform SDK docs:\niOS: [Speech](https://developer.apple.com/documentation/speech)\nAndroid: [SpeechRecognizer](https://developer.android.com/reference/android/speech/SpeechRecognizer)",
33
33
  "complexTypes": [
34
34
  "SpeechRecognitionAvailability",
35
35
  "Pick",
@@ -397,14 +397,30 @@
397
397
  {
398
398
  "name": "contextualStrings",
399
399
  "tags": [],
400
- "docs": "Words or phrases that should be recognized more accurately by native speech APIs.\n\nOn iOS, these are passed to `SFSpeechRecognitionRequest.contextualStrings`\nwhen the plugin uses the legacy `SFSpeechRecognizer` path. That path is the\ndefault on all iOS versions, the fallback below iOS 26, and still available\non iOS 26+ by leaving `useOnDeviceRecognition` disabled.\n\nIgnored by Android and by the iOS 26+ `SpeechAnalyzer` path.",
400
+ "docs": "Words or phrases that should be recognized more accurately by native speech APIs.\n\nOn iOS, these are passed to `SFSpeechRecognitionRequest.contextualStrings`\nwhen the plugin uses the legacy `SFSpeechRecognizer` path. That path is the\ndefault on all iOS versions, the fallback below iOS 26, and still available on\niOS 26+ either by leaving `useOnDeviceRecognition` disabled or by setting\n`preferLegacyRecognizer` — so contextual strings and on-device recognition can\nbe used together.\n\nIgnored by Android and by the iOS 26+ `SpeechAnalyzer` path.",
401
401
  "complexTypes": [],
402
402
  "type": "string[] | undefined"
403
403
  },
404
404
  {
405
405
  "name": "useOnDeviceRecognition",
406
406
  "tags": [],
407
- "docs": "Opt in to the platform's newer on-device recognition path when available.\n\nOn iOS 26+, this uses Apple's `SpeechAnalyzer` / `SpeechTranscriber` pipeline.\nOn recent Android versions, this uses the on-device `SpeechRecognizer` path.\n\nIt is intentionally opt-in so existing apps keep the legacy flow unless they choose\nto roll out the new behavior.\nOn iOS, leaving this disabled keeps `SFSpeechRecognizer` on every supported OS version.\nEnabling it on older iOS versions or unsupported locales falls back to `SFSpeechRecognizer`.\n\nUse {@link SpeechRecognitionPlugin.isOnDeviceRecognitionAvailable} before enabling it in production.\n\nPlatform SDK docs:\niOS: [Speech](https://developer.apple.com/documentation/speech),\n[SpeechAnalyzer](https://developer.apple.com/documentation/speech/speechanalyzer),\n[SpeechTranscriber](https://developer.apple.com/documentation/speech/speechtranscriber)\nAndroid: [SpeechRecognizer](https://developer.android.com/reference/android/speech/SpeechRecognizer)\n\nDefaults to `false`.",
407
+ "docs": "Opt in to the platform's newer on-device recognition path when available.\n\nOn iOS 26+, this uses Apple's `SpeechAnalyzer` / `SpeechTranscriber` pipeline.\nOn recent Android versions, this uses the on-device `SpeechRecognizer` path.\n\nIt is intentionally opt-in so existing apps keep the legacy flow unless they choose\nto roll out the new behavior.\nOn iOS, leaving this disabled keeps `SFSpeechRecognizer` on every supported OS version.\nOn the legacy `SFSpeechRecognizer` path, enabling this rejects with\n`ON_DEVICE_RECOGNITION_UNAVAILABLE` when on-device recognition is not supported for the locale.\nOn iOS 26+, enabling this without `preferLegacyRecognizer` uses the modern `SpeechAnalyzer` path\nwhen available; otherwise recognition falls back to the legacy path with the same rejection rule.\n\nUse {@link SpeechRecognitionPlugin.isOnDeviceRecognitionAvailable} before enabling it in production.\n\nPlatform SDK docs:\niOS: [Speech](https://developer.apple.com/documentation/speech),\n[SpeechAnalyzer](https://developer.apple.com/documentation/speech/speechanalyzer),\n[SpeechTranscriber](https://developer.apple.com/documentation/speech/speechtranscriber)\nAndroid: [SpeechRecognizer](https://developer.android.com/reference/android/speech/SpeechRecognizer)\n\nDefaults to `false`.",
408
+ "complexTypes": [],
409
+ "type": "boolean | undefined"
410
+ },
411
+ {
412
+ "name": "preferLegacyRecognizer",
413
+ "tags": [
414
+ {
415
+ "text": "false",
416
+ "name": "default"
417
+ },
418
+ {
419
+ "text": "8.1.11",
420
+ "name": "since"
421
+ }
422
+ ],
423
+ "docs": "iOS only: skip the modern `SpeechAnalyzer` path even when it is available,\nso that `useOnDeviceRecognition` applies to `SFSpeechRecognizer`\n(`requiresOnDeviceRecognition`) instead.\n\nUseful on iOS 26 devices where the modern path starts and stops a session\nwithout ever emitting `partialResults`.",
408
424
  "complexTypes": [],
409
425
  "type": "boolean | undefined"
410
426
  },
@@ -42,8 +42,10 @@ export interface SpeechRecognitionStartOptions {
42
42
  *
43
43
  * On iOS, these are passed to `SFSpeechRecognitionRequest.contextualStrings`
44
44
  * when the plugin uses the legacy `SFSpeechRecognizer` path. That path is the
45
- * default on all iOS versions, the fallback below iOS 26, and still available
46
- * on iOS 26+ by leaving `useOnDeviceRecognition` disabled.
45
+ * default on all iOS versions, the fallback below iOS 26, and still available on
46
+ * iOS 26+ either by leaving `useOnDeviceRecognition` disabled or by setting
47
+ * `preferLegacyRecognizer` — so contextual strings and on-device recognition can
48
+ * be used together.
47
49
  *
48
50
  * Ignored by Android and by the iOS 26+ `SpeechAnalyzer` path.
49
51
  */
@@ -57,7 +59,10 @@ export interface SpeechRecognitionStartOptions {
57
59
  * It is intentionally opt-in so existing apps keep the legacy flow unless they choose
58
60
  * to roll out the new behavior.
59
61
  * On iOS, leaving this disabled keeps `SFSpeechRecognizer` on every supported OS version.
60
- * Enabling it on older iOS versions or unsupported locales falls back to `SFSpeechRecognizer`.
62
+ * On the legacy `SFSpeechRecognizer` path, enabling this rejects with
63
+ * `ON_DEVICE_RECOGNITION_UNAVAILABLE` when on-device recognition is not supported for the locale.
64
+ * On iOS 26+, enabling this without `preferLegacyRecognizer` uses the modern `SpeechAnalyzer` path
65
+ * when available; otherwise recognition falls back to the legacy path with the same rejection rule.
61
66
  *
62
67
  * Use {@link SpeechRecognitionPlugin.isOnDeviceRecognitionAvailable} before enabling it in production.
63
68
  *
@@ -70,6 +75,18 @@ export interface SpeechRecognitionStartOptions {
70
75
  * Defaults to `false`.
71
76
  */
72
77
  useOnDeviceRecognition?: boolean;
78
+ /**
79
+ * iOS only: skip the modern `SpeechAnalyzer` path even when it is available,
80
+ * so that `useOnDeviceRecognition` applies to `SFSpeechRecognizer`
81
+ * (`requiresOnDeviceRecognition`) instead.
82
+ *
83
+ * Useful on iOS 26 devices where the modern path starts and stops a session
84
+ * without ever emitting `partialResults`.
85
+ *
86
+ * @default false
87
+ * @since 8.1.11
88
+ */
89
+ preferLegacyRecognizer?: boolean;
73
90
  /**
74
91
  * Allow a number of milliseconds of silence before splitting the recognition session into segments.
75
92
  * Required to be greater than zero and currently supported on Android only.
@@ -238,19 +255,24 @@ export interface SpeechRecognitionPlugin {
238
255
  */
239
256
  available(): Promise<SpeechRecognitionAvailability>;
240
257
  /**
241
- * Checks whether the platform's newer on-device recognition path is available for the selected locale.
258
+ * Checks whether on-device speech recognition is available for the selected locale.
242
259
  *
243
260
  * This is the capability check you should use before enabling `useOnDeviceRecognition`.
244
- * A `true` result means the current device, OS version, and locale can use the newer
245
- * on-device path for that platform.
261
+ * On iOS, the result depends on which recognizer path `start()` will use:
262
+ *
263
+ * - When `preferLegacyRecognizer` is `false` (default) on iOS 26+, this checks the modern
264
+ * `SpeechAnalyzer` path.
265
+ * - On older iOS versions, or when `preferLegacyRecognizer` is `true`, this checks
266
+ * `SFSpeechRecognizer.supportsOnDeviceRecognition` for the legacy path.
246
267
  *
247
- * Returns `false` when the device only supports the legacy recognizer path.
268
+ * Pass the same `preferLegacyRecognizer` value here and in `start()` so the availability
269
+ * check matches the route that recognition will take.
248
270
  *
249
271
  * Platform SDK docs:
250
272
  * iOS: [Speech](https://developer.apple.com/documentation/speech)
251
273
  * Android: [SpeechRecognizer](https://developer.android.com/reference/android/speech/SpeechRecognizer)
252
274
  */
253
- isOnDeviceRecognitionAvailable(options?: Pick<SpeechRecognitionStartOptions, 'language'>): Promise<SpeechRecognitionAvailability>;
275
+ isOnDeviceRecognitionAvailable(options?: Pick<SpeechRecognitionStartOptions, 'language' | 'preferLegacyRecognizer'>): Promise<SpeechRecognitionAvailability>;
254
276
  /**
255
277
  * Begins capturing audio and transcribing speech.
256
278
  *
@@ -1 +1 @@
1
- {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["import type { PermissionState, PluginListenerHandle } from '@capacitor/core';\n\n/**\n * Permission map returned by `checkPermissions` and `requestPermissions`.\n *\n * On Android the state maps to the `RECORD_AUDIO` permission.\n * On iOS it combines speech recognition plus microphone permission.\n */\nexport interface SpeechRecognitionPermissionStatus {\n speechRecognition: PermissionState;\n}\n\n/**\n * Configure how the recognizer behaves when calling {@link SpeechRecognitionPlugin.start}.\n */\nexport interface SpeechRecognitionStartOptions {\n /**\n * Locale identifier such as `en-US`. When omitted the device language is used.\n */\n language?: string;\n /**\n * Maximum number of final matches returned by native APIs. Defaults to `5`.\n */\n maxResults?: number;\n /**\n * Prompt message shown inside the Android system dialog (ignored on iOS).\n */\n prompt?: string;\n /**\n * When `true`, Android shows the OS speech dialog instead of running inline recognition.\n * Defaults to `false`.\n */\n popup?: boolean;\n /**\n * Emits partial transcription updates through the `partialResults` listener while audio is captured.\n */\n partialResults?: boolean;\n /**\n * Enables native punctuation handling where supported (iOS 16+).\n */\n addPunctuation?: boolean;\n /**\n * Words or phrases that should be recognized more accurately by native speech APIs.\n *\n * On iOS, these are passed to `SFSpeechRecognitionRequest.contextualStrings`\n * when the plugin uses the legacy `SFSpeechRecognizer` path. That path is the\n * default on all iOS versions, the fallback below iOS 26, and still available\n * on iOS 26+ by leaving `useOnDeviceRecognition` disabled.\n *\n * Ignored by Android and by the iOS 26+ `SpeechAnalyzer` path.\n */\n contextualStrings?: string[];\n /**\n * Opt in to the platform's newer on-device recognition path when available.\n *\n * On iOS 26+, this uses Apple's `SpeechAnalyzer` / `SpeechTranscriber` pipeline.\n * On recent Android versions, this uses the on-device `SpeechRecognizer` path.\n *\n * It is intentionally opt-in so existing apps keep the legacy flow unless they choose\n * to roll out the new behavior.\n * On iOS, leaving this disabled keeps `SFSpeechRecognizer` on every supported OS version.\n * Enabling it on older iOS versions or unsupported locales falls back to `SFSpeechRecognizer`.\n *\n * Use {@link SpeechRecognitionPlugin.isOnDeviceRecognitionAvailable} before enabling it in production.\n *\n * Platform SDK docs:\n * iOS: [Speech](https://developer.apple.com/documentation/speech),\n * [SpeechAnalyzer](https://developer.apple.com/documentation/speech/speechanalyzer),\n * [SpeechTranscriber](https://developer.apple.com/documentation/speech/speechtranscriber)\n * Android: [SpeechRecognizer](https://developer.android.com/reference/android/speech/SpeechRecognizer)\n *\n * Defaults to `false`.\n */\n useOnDeviceRecognition?: boolean;\n /**\n * Allow a number of milliseconds of silence before splitting the recognition session into segments.\n * Required to be greater than zero and currently supported on Android only.\n */\n allowForSilence?: number;\n /**\n * EXPERIMENTAL: Keep a PTT session alive across silence by restarting recognition while the button stays held.\n *\n * This restart behavior is implemented for Android inline recognition and iOS native recognition.\n */\n continuousPTT?: boolean;\n /**\n * Suppresses the Android system beep when inline recognition starts or restarts.\n *\n * Uses a best-effort combination of an undocumented recognizer intent extra and\n * temporary notification/system stream volume muting. Some devices ignore the\n * intent extra; the volume fallback is the portable path.\n *\n * Defaults to `true` when `continuousPTT` is enabled.\n */\n muteRecognizerBeep?: boolean;\n}\n\n/**\n * Raised whenever a partial transcription is produced.\n */\nexport interface SpeechRecognitionPartialResultEvent {\n /**\n * Current recognition matches when the native recognizer reports them.\n *\n * This can be omitted for forced or accumulated-only payloads.\n */\n matches?: string[];\n /**\n * Accumulated transcription from earlier continuous PTT cycles.\n */\n accumulated?: string;\n /**\n * Final accumulated text including the current result.\n */\n accumulatedText?: string;\n /**\n * `true` when the plugin is restarting recognition inside a continuous PTT session.\n */\n isRestarting?: boolean;\n /**\n * `true` when the payload was emitted by `forceStop()`.\n */\n forced?: boolean;\n}\n\n/**\n * Raised whenever a segmented result is produced (Android only).\n */\nexport interface SpeechRecognitionSegmentResultEvent {\n matches: string[];\n}\n\n/**\n * Finite state values for the recognition session lifecycle.\n */\nexport type ListeningFiniteState = 'startingListening' | 'started' | 'stoppingListening' | 'stopped';\n\n/**\n * Why a listening state transition happened.\n */\nexport type ListeningReason = 'userStart' | 'userStop' | 'forceStop' | 'results' | 'silence' | 'error' | 'unknown';\n\n/**\n * Raised when the listening state changes.\n *\n * The original `status` field is preserved for backward compatibility and is present\n * on the binary `started` / `stopped` states.\n */\nexport interface SpeechRecognitionListeningEvent {\n /**\n * Finite state of the recognition session.\n */\n state?: ListeningFiniteState;\n /**\n * Unique identifier for the current listening session.\n */\n sessionId?: number;\n /**\n * Why this state transition occurred.\n */\n reason?: ListeningReason;\n /**\n * Error code when the transition is caused by an error.\n */\n errorCode?: string;\n /**\n * Backward-compatible binary state used by earlier releases.\n */\n status?: 'started' | 'stopped';\n}\n\n/**\n * Raised whenever native recognition reports an error.\n */\nexport interface SpeechRecognitionErrorEvent {\n code: string;\n message: string;\n sessionId: number;\n}\n\n/**\n * Emitted after native resources have been torn down and the plugin is ready for another session.\n */\nexport interface SpeechRecognitionReadyEvent {\n sessionId: number;\n}\n\nexport interface SpeechRecognitionAvailability {\n available: boolean;\n}\n\nexport interface SpeechRecognitionMatches {\n matches?: string[];\n}\n\nexport interface SpeechRecognitionLanguages {\n languages: string[];\n}\n\nexport interface SpeechRecognitionListening {\n listening: boolean;\n}\n\n/**\n * Options for {@link SpeechRecognitionPlugin.forceStop}.\n */\nexport interface ForceStopOptions {\n /**\n * Android only: timeout in milliseconds before forcing stop via destroy/recreate.\n *\n * On iOS, the current session is stopped immediately and this value is ignored.\n *\n * Defaults to `1500`.\n */\n timeout?: number;\n}\n\n/**\n * Result from {@link SpeechRecognitionPlugin.getLastPartialResult}.\n */\nexport interface LastPartialResult {\n /**\n * Whether a partial result is currently cached.\n */\n available: boolean;\n /**\n * The most recent transcript text known to the native recognizer.\n */\n text: string;\n /**\n * All current match alternatives when available.\n */\n matches?: string[];\n}\n\n/**\n * Options for {@link SpeechRecognitionPlugin.setPTTState}.\n */\nexport interface PTTStateOptions {\n /**\n * Whether the PTT button is currently held.\n */\n held: boolean;\n /**\n * When set, updates whether Android should suppress the recognizer start beep for the active session.\n *\n * Beep suppression is best-effort and device-specific; see {@link SpeechRecognitionStartOptions.muteRecognizerBeep}.\n */\n mute?: boolean;\n}\n\nexport interface SpeechRecognitionPlugin {\n /**\n * Checks whether the native speech recognition service is usable on the current device.\n */\n available(): Promise<SpeechRecognitionAvailability>;\n /**\n * Checks whether the platform's newer on-device recognition path is available for the selected locale.\n *\n * This is the capability check you should use before enabling `useOnDeviceRecognition`.\n * A `true` result means the current device, OS version, and locale can use the newer\n * on-device path for that platform.\n *\n * Returns `false` when the device only supports the legacy recognizer path.\n *\n * Platform SDK docs:\n * iOS: [Speech](https://developer.apple.com/documentation/speech)\n * Android: [SpeechRecognizer](https://developer.android.com/reference/android/speech/SpeechRecognizer)\n */\n isOnDeviceRecognitionAvailable(\n options?: Pick<SpeechRecognitionStartOptions, 'language'>,\n ): Promise<SpeechRecognitionAvailability>;\n /**\n * Begins capturing audio and transcribing speech.\n *\n * When `partialResults` is `true`, the returned promise resolves immediately and updates are\n * streamed through the `partialResults` listener until the session ends.\n *\n * The default path keeps the legacy recognizer behavior for backward compatibility.\n * Pass `useOnDeviceRecognition: true` only after checking\n * {@link SpeechRecognitionPlugin.isOnDeviceRecognitionAvailable}.\n */\n start(options?: SpeechRecognitionStartOptions): Promise<SpeechRecognitionMatches>;\n /**\n * Stops listening and tears down native resources.\n */\n stop(): Promise<void>;\n /**\n * Force stops the current session.\n *\n * On Android, this first tries a normal stop and then falls back to destroy/recreate after `timeout`.\n * On iOS, the current session is stopped immediately.\n *\n * If a partial transcript is cached, it is emitted through the `partialResults` listener with `forced: true`.\n */\n forceStop(options?: ForceStopOptions): Promise<void>;\n /**\n * Gets the last cached partial transcription result.\n */\n getLastPartialResult(): Promise<LastPartialResult>;\n /**\n * Updates the current push-to-talk button state.\n *\n * Use this together with `continuousPTT` or with a custom hold-to-talk flow.\n */\n setPTTState(options: PTTStateOptions): Promise<void>;\n /**\n * Gets the locales supported by the underlying recognizer.\n *\n * Android 13+ devices no longer expose this list; in that case `languages` is empty.\n */\n getSupportedLanguages(): Promise<SpeechRecognitionLanguages>;\n /**\n * Returns whether the plugin is actively listening for speech.\n */\n isListening(): Promise<SpeechRecognitionListening>;\n /**\n * Gets the current permission state.\n */\n checkPermissions(): Promise<SpeechRecognitionPermissionStatus>;\n /**\n * Requests the microphone + speech recognition permissions.\n */\n requestPermissions(): Promise<SpeechRecognitionPermissionStatus>;\n /**\n * Returns the native plugin version bundled with this package.\n *\n * Useful when reporting issues to confirm that native and JS versions match.\n */\n getPluginVersion(): Promise<{ version: string }>;\n /**\n * Listen for segmented session completion events (Android only).\n */\n addListener(eventName: 'endOfSegmentedSession', listenerFunc: () => void): Promise<PluginListenerHandle>;\n /**\n * Listen for segmented recognition results (Android only).\n */\n addListener(\n eventName: 'segmentResults',\n listenerFunc: (event: SpeechRecognitionSegmentResultEvent) => void,\n ): Promise<PluginListenerHandle>;\n /**\n * Listen for partial transcription updates emitted while `partialResults` is enabled.\n */\n addListener(\n eventName: 'partialResults',\n listenerFunc: (event: SpeechRecognitionPartialResultEvent) => void,\n ): Promise<PluginListenerHandle>;\n /**\n * Listen for changes to the native listening state.\n */\n addListener(\n eventName: 'listeningState',\n listenerFunc: (event: SpeechRecognitionListeningEvent) => void,\n ): Promise<PluginListenerHandle>;\n /**\n * Listen for recognition errors.\n */\n addListener(\n eventName: 'error',\n listenerFunc: (event: SpeechRecognitionErrorEvent) => void,\n ): Promise<PluginListenerHandle>;\n /**\n * Listen for the recognizer becoming ready for another session.\n */\n addListener(\n eventName: 'readyForNextSession',\n listenerFunc: (event: SpeechRecognitionReadyEvent) => void,\n ): Promise<PluginListenerHandle>;\n /**\n * Removes every registered listener.\n */\n removeAllListeners(): Promise<void>;\n}\n"]}
1
+ {"version":3,"file":"definitions.js","sourceRoot":"","sources":["../../src/definitions.ts"],"names":[],"mappings":"","sourcesContent":["import type { PermissionState, PluginListenerHandle } from '@capacitor/core';\n\n/**\n * Permission map returned by `checkPermissions` and `requestPermissions`.\n *\n * On Android the state maps to the `RECORD_AUDIO` permission.\n * On iOS it combines speech recognition plus microphone permission.\n */\nexport interface SpeechRecognitionPermissionStatus {\n speechRecognition: PermissionState;\n}\n\n/**\n * Configure how the recognizer behaves when calling {@link SpeechRecognitionPlugin.start}.\n */\nexport interface SpeechRecognitionStartOptions {\n /**\n * Locale identifier such as `en-US`. When omitted the device language is used.\n */\n language?: string;\n /**\n * Maximum number of final matches returned by native APIs. Defaults to `5`.\n */\n maxResults?: number;\n /**\n * Prompt message shown inside the Android system dialog (ignored on iOS).\n */\n prompt?: string;\n /**\n * When `true`, Android shows the OS speech dialog instead of running inline recognition.\n * Defaults to `false`.\n */\n popup?: boolean;\n /**\n * Emits partial transcription updates through the `partialResults` listener while audio is captured.\n */\n partialResults?: boolean;\n /**\n * Enables native punctuation handling where supported (iOS 16+).\n */\n addPunctuation?: boolean;\n /**\n * Words or phrases that should be recognized more accurately by native speech APIs.\n *\n * On iOS, these are passed to `SFSpeechRecognitionRequest.contextualStrings`\n * when the plugin uses the legacy `SFSpeechRecognizer` path. That path is the\n * default on all iOS versions, the fallback below iOS 26, and still available on\n * iOS 26+ either by leaving `useOnDeviceRecognition` disabled or by setting\n * `preferLegacyRecognizer` — so contextual strings and on-device recognition can\n * be used together.\n *\n * Ignored by Android and by the iOS 26+ `SpeechAnalyzer` path.\n */\n contextualStrings?: string[];\n /**\n * Opt in to the platform's newer on-device recognition path when available.\n *\n * On iOS 26+, this uses Apple's `SpeechAnalyzer` / `SpeechTranscriber` pipeline.\n * On recent Android versions, this uses the on-device `SpeechRecognizer` path.\n *\n * It is intentionally opt-in so existing apps keep the legacy flow unless they choose\n * to roll out the new behavior.\n * On iOS, leaving this disabled keeps `SFSpeechRecognizer` on every supported OS version.\n * On the legacy `SFSpeechRecognizer` path, enabling this rejects with\n * `ON_DEVICE_RECOGNITION_UNAVAILABLE` when on-device recognition is not supported for the locale.\n * On iOS 26+, enabling this without `preferLegacyRecognizer` uses the modern `SpeechAnalyzer` path\n * when available; otherwise recognition falls back to the legacy path with the same rejection rule.\n *\n * Use {@link SpeechRecognitionPlugin.isOnDeviceRecognitionAvailable} before enabling it in production.\n *\n * Platform SDK docs:\n * iOS: [Speech](https://developer.apple.com/documentation/speech),\n * [SpeechAnalyzer](https://developer.apple.com/documentation/speech/speechanalyzer),\n * [SpeechTranscriber](https://developer.apple.com/documentation/speech/speechtranscriber)\n * Android: [SpeechRecognizer](https://developer.android.com/reference/android/speech/SpeechRecognizer)\n *\n * Defaults to `false`.\n */\n useOnDeviceRecognition?: boolean;\n /**\n * iOS only: skip the modern `SpeechAnalyzer` path even when it is available,\n * so that `useOnDeviceRecognition` applies to `SFSpeechRecognizer`\n * (`requiresOnDeviceRecognition`) instead.\n *\n * Useful on iOS 26 devices where the modern path starts and stops a session\n * without ever emitting `partialResults`.\n *\n * @default false\n * @since 8.1.11\n */\n preferLegacyRecognizer?: boolean;\n /**\n * Allow a number of milliseconds of silence before splitting the recognition session into segments.\n * Required to be greater than zero and currently supported on Android only.\n */\n allowForSilence?: number;\n /**\n * EXPERIMENTAL: Keep a PTT session alive across silence by restarting recognition while the button stays held.\n *\n * This restart behavior is implemented for Android inline recognition and iOS native recognition.\n */\n continuousPTT?: boolean;\n /**\n * Suppresses the Android system beep when inline recognition starts or restarts.\n *\n * Uses a best-effort combination of an undocumented recognizer intent extra and\n * temporary notification/system stream volume muting. Some devices ignore the\n * intent extra; the volume fallback is the portable path.\n *\n * Defaults to `true` when `continuousPTT` is enabled.\n */\n muteRecognizerBeep?: boolean;\n}\n\n/**\n * Raised whenever a partial transcription is produced.\n */\nexport interface SpeechRecognitionPartialResultEvent {\n /**\n * Current recognition matches when the native recognizer reports them.\n *\n * This can be omitted for forced or accumulated-only payloads.\n */\n matches?: string[];\n /**\n * Accumulated transcription from earlier continuous PTT cycles.\n */\n accumulated?: string;\n /**\n * Final accumulated text including the current result.\n */\n accumulatedText?: string;\n /**\n * `true` when the plugin is restarting recognition inside a continuous PTT session.\n */\n isRestarting?: boolean;\n /**\n * `true` when the payload was emitted by `forceStop()`.\n */\n forced?: boolean;\n}\n\n/**\n * Raised whenever a segmented result is produced (Android only).\n */\nexport interface SpeechRecognitionSegmentResultEvent {\n matches: string[];\n}\n\n/**\n * Finite state values for the recognition session lifecycle.\n */\nexport type ListeningFiniteState = 'startingListening' | 'started' | 'stoppingListening' | 'stopped';\n\n/**\n * Why a listening state transition happened.\n */\nexport type ListeningReason = 'userStart' | 'userStop' | 'forceStop' | 'results' | 'silence' | 'error' | 'unknown';\n\n/**\n * Raised when the listening state changes.\n *\n * The original `status` field is preserved for backward compatibility and is present\n * on the binary `started` / `stopped` states.\n */\nexport interface SpeechRecognitionListeningEvent {\n /**\n * Finite state of the recognition session.\n */\n state?: ListeningFiniteState;\n /**\n * Unique identifier for the current listening session.\n */\n sessionId?: number;\n /**\n * Why this state transition occurred.\n */\n reason?: ListeningReason;\n /**\n * Error code when the transition is caused by an error.\n */\n errorCode?: string;\n /**\n * Backward-compatible binary state used by earlier releases.\n */\n status?: 'started' | 'stopped';\n}\n\n/**\n * Raised whenever native recognition reports an error.\n */\nexport interface SpeechRecognitionErrorEvent {\n code: string;\n message: string;\n sessionId: number;\n}\n\n/**\n * Emitted after native resources have been torn down and the plugin is ready for another session.\n */\nexport interface SpeechRecognitionReadyEvent {\n sessionId: number;\n}\n\nexport interface SpeechRecognitionAvailability {\n available: boolean;\n}\n\nexport interface SpeechRecognitionMatches {\n matches?: string[];\n}\n\nexport interface SpeechRecognitionLanguages {\n languages: string[];\n}\n\nexport interface SpeechRecognitionListening {\n listening: boolean;\n}\n\n/**\n * Options for {@link SpeechRecognitionPlugin.forceStop}.\n */\nexport interface ForceStopOptions {\n /**\n * Android only: timeout in milliseconds before forcing stop via destroy/recreate.\n *\n * On iOS, the current session is stopped immediately and this value is ignored.\n *\n * Defaults to `1500`.\n */\n timeout?: number;\n}\n\n/**\n * Result from {@link SpeechRecognitionPlugin.getLastPartialResult}.\n */\nexport interface LastPartialResult {\n /**\n * Whether a partial result is currently cached.\n */\n available: boolean;\n /**\n * The most recent transcript text known to the native recognizer.\n */\n text: string;\n /**\n * All current match alternatives when available.\n */\n matches?: string[];\n}\n\n/**\n * Options for {@link SpeechRecognitionPlugin.setPTTState}.\n */\nexport interface PTTStateOptions {\n /**\n * Whether the PTT button is currently held.\n */\n held: boolean;\n /**\n * When set, updates whether Android should suppress the recognizer start beep for the active session.\n *\n * Beep suppression is best-effort and device-specific; see {@link SpeechRecognitionStartOptions.muteRecognizerBeep}.\n */\n mute?: boolean;\n}\n\nexport interface SpeechRecognitionPlugin {\n /**\n * Checks whether the native speech recognition service is usable on the current device.\n */\n available(): Promise<SpeechRecognitionAvailability>;\n /**\n * Checks whether on-device speech recognition is available for the selected locale.\n *\n * This is the capability check you should use before enabling `useOnDeviceRecognition`.\n * On iOS, the result depends on which recognizer path `start()` will use:\n *\n * - When `preferLegacyRecognizer` is `false` (default) on iOS 26+, this checks the modern\n * `SpeechAnalyzer` path.\n * - On older iOS versions, or when `preferLegacyRecognizer` is `true`, this checks\n * `SFSpeechRecognizer.supportsOnDeviceRecognition` for the legacy path.\n *\n * Pass the same `preferLegacyRecognizer` value here and in `start()` so the availability\n * check matches the route that recognition will take.\n *\n * Platform SDK docs:\n * iOS: [Speech](https://developer.apple.com/documentation/speech)\n * Android: [SpeechRecognizer](https://developer.android.com/reference/android/speech/SpeechRecognizer)\n */\n isOnDeviceRecognitionAvailable(\n options?: Pick<SpeechRecognitionStartOptions, 'language' | 'preferLegacyRecognizer'>,\n ): Promise<SpeechRecognitionAvailability>;\n /**\n * Begins capturing audio and transcribing speech.\n *\n * When `partialResults` is `true`, the returned promise resolves immediately and updates are\n * streamed through the `partialResults` listener until the session ends.\n *\n * The default path keeps the legacy recognizer behavior for backward compatibility.\n * Pass `useOnDeviceRecognition: true` only after checking\n * {@link SpeechRecognitionPlugin.isOnDeviceRecognitionAvailable}.\n */\n start(options?: SpeechRecognitionStartOptions): Promise<SpeechRecognitionMatches>;\n /**\n * Stops listening and tears down native resources.\n */\n stop(): Promise<void>;\n /**\n * Force stops the current session.\n *\n * On Android, this first tries a normal stop and then falls back to destroy/recreate after `timeout`.\n * On iOS, the current session is stopped immediately.\n *\n * If a partial transcript is cached, it is emitted through the `partialResults` listener with `forced: true`.\n */\n forceStop(options?: ForceStopOptions): Promise<void>;\n /**\n * Gets the last cached partial transcription result.\n */\n getLastPartialResult(): Promise<LastPartialResult>;\n /**\n * Updates the current push-to-talk button state.\n *\n * Use this together with `continuousPTT` or with a custom hold-to-talk flow.\n */\n setPTTState(options: PTTStateOptions): Promise<void>;\n /**\n * Gets the locales supported by the underlying recognizer.\n *\n * Android 13+ devices no longer expose this list; in that case `languages` is empty.\n */\n getSupportedLanguages(): Promise<SpeechRecognitionLanguages>;\n /**\n * Returns whether the plugin is actively listening for speech.\n */\n isListening(): Promise<SpeechRecognitionListening>;\n /**\n * Gets the current permission state.\n */\n checkPermissions(): Promise<SpeechRecognitionPermissionStatus>;\n /**\n * Requests the microphone + speech recognition permissions.\n */\n requestPermissions(): Promise<SpeechRecognitionPermissionStatus>;\n /**\n * Returns the native plugin version bundled with this package.\n *\n * Useful when reporting issues to confirm that native and JS versions match.\n */\n getPluginVersion(): Promise<{ version: string }>;\n /**\n * Listen for segmented session completion events (Android only).\n */\n addListener(eventName: 'endOfSegmentedSession', listenerFunc: () => void): Promise<PluginListenerHandle>;\n /**\n * Listen for segmented recognition results (Android only).\n */\n addListener(\n eventName: 'segmentResults',\n listenerFunc: (event: SpeechRecognitionSegmentResultEvent) => void,\n ): Promise<PluginListenerHandle>;\n /**\n * Listen for partial transcription updates emitted while `partialResults` is enabled.\n */\n addListener(\n eventName: 'partialResults',\n listenerFunc: (event: SpeechRecognitionPartialResultEvent) => void,\n ): Promise<PluginListenerHandle>;\n /**\n * Listen for changes to the native listening state.\n */\n addListener(\n eventName: 'listeningState',\n listenerFunc: (event: SpeechRecognitionListeningEvent) => void,\n ): Promise<PluginListenerHandle>;\n /**\n * Listen for recognition errors.\n */\n addListener(\n eventName: 'error',\n listenerFunc: (event: SpeechRecognitionErrorEvent) => void,\n ): Promise<PluginListenerHandle>;\n /**\n * Listen for the recognizer becoming ready for another session.\n */\n addListener(\n eventName: 'readyForNextSession',\n listenerFunc: (event: SpeechRecognitionReadyEvent) => void,\n ): Promise<PluginListenerHandle>;\n /**\n * Removes every registered listener.\n */\n removeAllListeners(): Promise<void>;\n}\n"]}
@@ -0,0 +1,21 @@
1
+ import Foundation
2
+
3
+ enum LegacyOnDeviceRecognitionRequirement {
4
+ case notRequired
5
+ case required
6
+ case unavailable
7
+
8
+ static let unavailableErrorCode = "ON_DEVICE_RECOGNITION_UNAVAILABLE"
9
+ static let unavailableErrorMessage = "On-device speech recognition is not available on this device."
10
+
11
+ static func evaluate(
12
+ useOnDeviceRecognition: Bool,
13
+ supportsOnDeviceRecognition: Bool
14
+ ) -> LegacyOnDeviceRecognitionRequirement {
15
+ guard useOnDeviceRecognition else {
16
+ return .notRequired
17
+ }
18
+
19
+ return supportsOnDeviceRecognition ? .required : .unavailable
20
+ }
21
+ }
@@ -0,0 +1,39 @@
1
+ import Foundation
2
+ import Speech
3
+
4
+ enum OnDeviceRecognitionAvailabilityResolver {
5
+ enum Route {
6
+ case modern
7
+ case legacy
8
+ }
9
+
10
+ static func route(
11
+ preferLegacyRecognizer: Bool,
12
+ modernPathSupportedOnOS: Bool
13
+ ) -> Route {
14
+ if modernPathSupportedOnOS && !preferLegacyRecognizer {
15
+ return .modern
16
+ }
17
+ return .legacy
18
+ }
19
+
20
+ static func legacyAvailability(
21
+ recognizerExists: Bool,
22
+ supportsOnDeviceRecognition: Bool
23
+ ) -> Bool {
24
+ guard recognizerExists else {
25
+ return false
26
+ }
27
+ return supportsOnDeviceRecognition
28
+ }
29
+
30
+ static func legacyAvailability(for locale: Locale) -> Bool {
31
+ guard let recognizer = SFSpeechRecognizer(locale: locale) else {
32
+ return false
33
+ }
34
+ return legacyAvailability(
35
+ recognizerExists: true,
36
+ supportsOnDeviceRecognition: recognizer.supportsOnDeviceRecognition
37
+ )
38
+ }
39
+ }
@@ -22,7 +22,7 @@ private enum ListeningReason: String {
22
22
  // swiftlint:disable type_body_length
23
23
  @objc(SpeechRecognitionPlugin)
24
24
  public final class SpeechRecognitionPlugin: CAPPlugin, CAPBridgedPlugin {
25
- private let pluginVersion = "8.0.10"
25
+ private let pluginVersion = "8.1.11"
26
26
  public let identifier = "SpeechRecognitionPlugin"
27
27
  public let jsName = "SpeechRecognition"
28
28
  public let pluginMethods: [CAPPluginMethod] = [
@@ -69,15 +69,32 @@ public final class SpeechRecognitionPlugin: CAPPlugin, CAPBridgedPlugin {
69
69
 
70
70
  @objc func isOnDeviceRecognitionAvailable(_ call: CAPPluginCall) {
71
71
  let locale = Locale(identifier: call.getString("language") ?? Locale.current.identifier)
72
+ let preferLegacyRecognizer = call.getBool("preferLegacyRecognizer") ?? false
73
+ let modernPathSupportedOnOS: Bool
72
74
  if #available(iOS 26.0, *) {
73
- Task { @MainActor in
74
- let isAvailable = await SpeechAnalyzerRecognitionSupport.supports(locale: locale)
75
- call.resolve(["available": isAvailable])
75
+ modernPathSupportedOnOS = true
76
+ } else {
77
+ modernPathSupportedOnOS = false
78
+ }
79
+
80
+ switch OnDeviceRecognitionAvailabilityResolver.route(
81
+ preferLegacyRecognizer: preferLegacyRecognizer,
82
+ modernPathSupportedOnOS: modernPathSupportedOnOS
83
+ ) {
84
+ case .modern:
85
+ if #available(iOS 26.0, *) {
86
+ Task { @MainActor in
87
+ let isAvailable = await SpeechAnalyzerRecognitionSupport.supports(locale: locale)
88
+ call.resolve(["available": isAvailable])
89
+ }
90
+ } else {
91
+ call.resolve(["available": false])
76
92
  }
77
- return
93
+ case .legacy:
94
+ call.resolve([
95
+ "available": OnDeviceRecognitionAvailabilityResolver.legacyAvailability(for: locale)
96
+ ])
78
97
  }
79
-
80
- call.resolve(["available": false])
81
98
  }
82
99
 
83
100
  @objc func start(_ call: CAPPluginCall) {
@@ -102,6 +119,7 @@ public final class SpeechRecognitionPlugin: CAPPlugin, CAPBridgedPlugin {
102
119
  addPunctuation: call.getBool("addPunctuation") ?? false,
103
120
  contextualStrings: contextualStrings,
104
121
  useOnDeviceRecognition: call.getBool("useOnDeviceRecognition") ?? false,
122
+ preferLegacyRecognizer: call.getBool("preferLegacyRecognizer") ?? false,
105
123
  continuousPTT: call.getBool("continuousPTT") ?? false
106
124
  )
107
125
 
@@ -279,6 +297,7 @@ public final class SpeechRecognitionPlugin: CAPPlugin, CAPBridgedPlugin {
279
297
  let locale = Locale(identifier: options.language)
280
298
  if #available(iOS 26.0, *),
281
299
  options.useOnDeviceRecognition,
300
+ !options.preferLegacyRecognizer,
282
301
  await SpeechAnalyzerRecognitionSupport.supports(locale: locale) {
283
302
  beginModernRecognition(call: call, options: options, locale: locale, sessionId: sessionId, restarting: restarting)
284
303
  } else {
@@ -314,6 +333,28 @@ public final class SpeechRecognitionPlugin: CAPPlugin, CAPBridgedPlugin {
314
333
  return
315
334
  }
316
335
 
336
+ let onDeviceRequirement = LegacyOnDeviceRecognitionRequirement.evaluate(
337
+ useOnDeviceRecognition: options.useOnDeviceRecognition,
338
+ supportsOnDeviceRecognition: recognizer.supportsOnDeviceRecognition
339
+ )
340
+
341
+ if onDeviceRequirement == .unavailable {
342
+ let message = LegacyOnDeviceRecognitionRequirement.unavailableErrorMessage
343
+ call?.reject(message)
344
+ emitErrorEvent(
345
+ code: LegacyOnDeviceRecognitionRequirement.unavailableErrorCode,
346
+ message: message,
347
+ sessionId: sessionId
348
+ )
349
+ activeCall = nil
350
+ finishSessionIfNeeded(
351
+ sessionId: sessionId,
352
+ reason: .error,
353
+ errorCode: LegacyOnDeviceRecognitionRequirement.unavailableErrorCode
354
+ )
355
+ return
356
+ }
357
+
317
358
  speechRecognizer = recognizer
318
359
 
319
360
  do {
@@ -328,6 +369,17 @@ public final class SpeechRecognitionPlugin: CAPPlugin, CAPBridgedPlugin {
328
369
 
329
370
  let recognitionRequest = SFSpeechAudioBufferRecognitionRequest()
330
371
  recognitionRequest.shouldReportPartialResults = options.partialResults
372
+
373
+ if onDeviceRequirement == .required {
374
+ // Honour `useOnDeviceRecognition` on the legacy path. Without this the
375
+ // option has no effect here and the audio is sent to Apple's servers, even
376
+ // though `SFSpeechRecognizer` has supported on-device recognition since
377
+ // iOS 13. Verified on device: with this line, transcription keeps working
378
+ // in airplane mode.
379
+ if #available(iOS 13.0, *) {
380
+ recognitionRequest.requiresOnDeviceRecognition = true
381
+ }
382
+ }
331
383
  if !options.contextualStrings.isEmpty {
332
384
  recognitionRequest.contextualStrings = options.contextualStrings
333
385
  }
@@ -854,5 +906,7 @@ private struct RecognitionOptions {
854
906
  let addPunctuation: Bool
855
907
  let contextualStrings: [String]
856
908
  let useOnDeviceRecognition: Bool
909
+ /// Skip the modern (SpeechAnalyzer) path even when it is available.
910
+ let preferLegacyRecognizer: Bool
857
911
  let continuousPTT: Bool
858
912
  }
@@ -1,7 +1,155 @@
1
1
  import XCTest
2
+ @testable import SpeechRecognitionPlugin
2
3
 
3
4
  final class SpeechRecognitionPluginTests: XCTestCase {
4
- func testExample() {
5
- XCTAssertTrue(true)
5
+ func testLegacyOnDeviceRequirement_notRequiredWhenFlagDisabled() {
6
+ XCTAssertEqual(
7
+ LegacyOnDeviceRecognitionRequirement.evaluate(
8
+ useOnDeviceRecognition: false,
9
+ supportsOnDeviceRecognition: false
10
+ ),
11
+ .notRequired
12
+ )
13
+ XCTAssertEqual(
14
+ LegacyOnDeviceRecognitionRequirement.evaluate(
15
+ useOnDeviceRecognition: false,
16
+ supportsOnDeviceRecognition: true
17
+ ),
18
+ .notRequired
19
+ )
20
+ }
21
+
22
+ func testLegacyOnDeviceRequirement_requiredWhenSupported() {
23
+ XCTAssertEqual(
24
+ LegacyOnDeviceRecognitionRequirement.evaluate(
25
+ useOnDeviceRecognition: true,
26
+ supportsOnDeviceRecognition: true
27
+ ),
28
+ .required
29
+ )
30
+ }
31
+
32
+ func testLegacyOnDeviceRequirement_unavailableWhenRequestedButNotSupported() {
33
+ XCTAssertEqual(
34
+ LegacyOnDeviceRecognitionRequirement.evaluate(
35
+ useOnDeviceRecognition: true,
36
+ supportsOnDeviceRecognition: false
37
+ ),
38
+ .unavailable
39
+ )
40
+ }
41
+
42
+ func testLegacyOnDeviceRequirement_unavailableErrorMetadataMatchesAndroid() {
43
+ XCTAssertEqual(
44
+ LegacyOnDeviceRecognitionRequirement.unavailableErrorCode,
45
+ "ON_DEVICE_RECOGNITION_UNAVAILABLE"
46
+ )
47
+ XCTAssertEqual(
48
+ LegacyOnDeviceRecognitionRequirement.unavailableErrorMessage,
49
+ "On-device speech recognition is not available on this device."
50
+ )
51
+ }
52
+
53
+ func testOnDeviceAvailabilityRoute_modernWhenModernOSAndLegacyNotPreferred() {
54
+ XCTAssertEqual(
55
+ OnDeviceRecognitionAvailabilityResolver.route(
56
+ preferLegacyRecognizer: false,
57
+ modernPathSupportedOnOS: true
58
+ ),
59
+ .modern
60
+ )
61
+ }
62
+
63
+ func testOnDeviceAvailabilityRoute_legacyWhenLegacyPreferredOnModernOS() {
64
+ XCTAssertEqual(
65
+ OnDeviceRecognitionAvailabilityResolver.route(
66
+ preferLegacyRecognizer: true,
67
+ modernPathSupportedOnOS: true
68
+ ),
69
+ .legacy
70
+ )
71
+ }
72
+
73
+ func testOnDeviceAvailabilityRoute_legacyWhenModernOSUnsupported() {
74
+ XCTAssertEqual(
75
+ OnDeviceRecognitionAvailabilityResolver.route(
76
+ preferLegacyRecognizer: false,
77
+ modernPathSupportedOnOS: false
78
+ ),
79
+ .legacy
80
+ )
81
+ }
82
+
83
+ func testOnDeviceAvailabilityRoute_legacyWhenLegacyPreferredOnOlderOS() {
84
+ XCTAssertEqual(
85
+ OnDeviceRecognitionAvailabilityResolver.route(
86
+ preferLegacyRecognizer: true,
87
+ modernPathSupportedOnOS: false
88
+ ),
89
+ .legacy
90
+ )
91
+ }
92
+
93
+ func testLegacyOnDeviceAvailability_falseWhenRecognizerMissing() {
94
+ XCTAssertFalse(
95
+ OnDeviceRecognitionAvailabilityResolver.legacyAvailability(
96
+ recognizerExists: false,
97
+ supportsOnDeviceRecognition: true
98
+ )
99
+ )
100
+ }
101
+
102
+ func testLegacyOnDeviceAvailability_falseWhenOnDeviceUnsupported() {
103
+ XCTAssertFalse(
104
+ OnDeviceRecognitionAvailabilityResolver.legacyAvailability(
105
+ recognizerExists: true,
106
+ supportsOnDeviceRecognition: false
107
+ )
108
+ )
109
+ }
110
+
111
+ func testLegacyOnDeviceAvailability_trueWhenOnDeviceSupported() {
112
+ XCTAssertTrue(
113
+ OnDeviceRecognitionAvailabilityResolver.legacyAvailability(
114
+ recognizerExists: true,
115
+ supportsOnDeviceRecognition: true
116
+ )
117
+ )
118
+ }
119
+
120
+ func testBeginRecognitionRouteAlignment_prefersModernOnlyWhenEligible() {
121
+ let modernPathSupportedOnOS = true
122
+ let preferLegacyRecognizer = false
123
+ let useOnDeviceRecognition = true
124
+
125
+ let availabilityRoute = OnDeviceRecognitionAvailabilityResolver.route(
126
+ preferLegacyRecognizer: preferLegacyRecognizer,
127
+ modernPathSupportedOnOS: modernPathSupportedOnOS
128
+ )
129
+ let beginRecognitionUsesModern =
130
+ modernPathSupportedOnOS &&
131
+ useOnDeviceRecognition &&
132
+ !preferLegacyRecognizer
133
+
134
+ XCTAssertEqual(availabilityRoute, .modern)
135
+ XCTAssertTrue(beginRecognitionUsesModern)
136
+ }
137
+
138
+ func testBeginRecognitionRouteAlignment_prefersLegacyWhenLegacyFlagSet() {
139
+ let modernPathSupportedOnOS = true
140
+ let preferLegacyRecognizer = true
141
+ let useOnDeviceRecognition = true
142
+
143
+ let availabilityRoute = OnDeviceRecognitionAvailabilityResolver.route(
144
+ preferLegacyRecognizer: preferLegacyRecognizer,
145
+ modernPathSupportedOnOS: modernPathSupportedOnOS
146
+ )
147
+ let beginRecognitionUsesModern =
148
+ modernPathSupportedOnOS &&
149
+ useOnDeviceRecognition &&
150
+ !preferLegacyRecognizer
151
+
152
+ XCTAssertEqual(availabilityRoute, .legacy)
153
+ XCTAssertFalse(beginRecognitionUsesModern)
6
154
  }
7
155
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@capgo/capacitor-speech-recognition",
3
- "version": "8.1.10",
3
+ "version": "8.2.0",
4
4
  "description": "Capacitor plugin for comprehensive on-device speech recognition with live partial results.",
5
5
  "main": "dist/plugin.cjs.js",
6
6
  "module": "dist/esm/index.js",
@@ -35,7 +35,7 @@
35
35
  ],
36
36
  "scripts": {
37
37
  "verify": "npm run verify:ios && npm run verify:android && npm run verify:web",
38
- "verify:ios": "xcodebuild -scheme CapgoCapacitorSpeechRecognition -destination generic/platform=iOS",
38
+ "verify:ios": "bash scripts/verify-ios.sh",
39
39
  "verify:android": "cd android && ./gradlew clean build test && cd ..",
40
40
  "verify:web": "npm run build",
41
41
  "lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint",