The Mhpdev documentation portal hosts guides, API references, and resources for the open-source packages and tools we build at Mhpdev. - [@mhpdev/react-native-speech](/react-native-speech) — a high-performance text-to-speech library for iOS and Android, with playback controls, events, and a HighlightedText component. - [@mhpdev/react-native-haptics](/react-native-haptics) — iOS haptics and Android vibration effects through one simple API, with a wide range of Android-specific effects. Both libraries are built on Turbo Modules, fully written in TypeScript, and work with bare React Native and Expo. They require the New Architecture (React Native 0.76+ has it enabled; for 0.68–0.75 you need to enable it first — see the [React Native New Architecture guide](https://github.com/reactwg/react-native-new-architecture/blob/main/docs/enable-apps.md)). Expo projects are supported through a [development build](https://docs.expo.dev/develop/introduction/) (`npx expo prebuild`); Expo Go is not supported. You can also explore the [Mhpdev blog](https://www.mhpdev.com/blog) for deep dives and updates from the team, and the source repositories on [GitHub](https://github.com/mhpdev-com/). Triggers a platform-specific haptic feedback on Android. This method is a no-op on iOS. ```ts Haptics.androidHaptics(type: AndroidHapticsFeedback): Promise ``` > **Note:** Availability of each feedback type varies by Android SDK version. If a requested type is unsupported on the device, it safely falls back to `virtual-key`. ## AndroidHapticsFeedback type [#androidhapticsfeedback-type] | Type | Description | Required API | | :----------------------------- | :--------------------------------------------------------------- | :----------- | | `clock-tick` | Feedback for a clock tick, e.g., while setting the time. | — | | `confirm` | Confirms a user's action. | 30+ | | `context-click` | Feedback for a context-click or right-click. | 23+ | | `drag-start` | Indicates the start of a drag action. | 34+ | | `gesture-end` | Indicates the end of a gesture. | 30+ | | `gesture-start` | Indicates the start of a gesture. | 30+ | | `gesture-threshold-activate` | Indicates the activation of a gesture threshold. | 34+ | | `gesture-threshold-deactivate` | Indicates the deactivation of a gesture threshold. | 34+ | | `keyboard-press` | Feedback for pressing a key on a soft keyboard. | 27+ | | `keyboard-release` | Feedback for releasing a key on a soft keyboard. | 27+ | | `keyboard-tap` | Feedback for a tap on a soft keyboard key. | — | | `long-press` | Feedback for a long press on an object. | — | | `no-haptics` | Explicitly specifies that no haptic feedback should be provided. | 34+ | | `reject` | Rejects a user's action. | 30+ | | `segment-frequent-tick` | A frequent tick in a segmented control. | 34+ | | `segment-tick` | A tick in a segmented control. | 34+ | | `text-handle-move` | Feedback for moving a text selection handle. | 27+ | | `toggle-off` | Indicates a toggle has been turned off. | 34+ | | `toggle-on` | Indicates a toggle has been turned on. | 34+ | | `virtual-key` | Feedback for a virtual key press. | — | | `virtual-key-release` | Feedback for a virtual key release. | 27+ | ## Example usage [#example-usage] ```ts import {AndroidHapticsFeedback} from '@mhpdev/react-native-haptics'; // Trigger a long press haptic on Android Haptics.androidHaptics('long-press'); // Trigger a confirm haptic on Android Haptics.androidHaptics('confirm'); ``` > **Warning** > > `androidHaptics` triggers haptic feedback through the system view's > `performHapticFeedback`, so it requires the current activity's view. If there > is no current activity, the call rejects with `E_NO_VIEW`. Invalid type values > reject with `E_INVALID_TYPE`. ## Behavior details [#behavior-details] - The feedback runs on the UI thread so the effect is instant. - Types above their required API use the matching Android `HapticFeedbackConstants` value. - Types below their required API (or otherwise unsupported on the device) fall back to `virtual-key` instead of failing. - The method is a no-op on iOS — you can call it unconditionally in cross-platform code. ## Import [#import] The library exports a default `Haptics` object that provides methods for triggering haptic feedback: ```tsx import Haptics from '@mhpdev/react-native-haptics'; ``` ## Methods [#methods] | Method | Returns | Platform | Description | | ------------------------------ | --------------- | -------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------- | | `Haptics.impact(style)` | `Promise` | Both | Impact feedback, corresponding to `UIImpactFeedbackGenerator` on iOS. See [Impact](/react-native-haptics/impact). | | `Haptics.notification(type)` | `Promise` | Both | Notification feedback, corresponding to `UINotificationFeedbackGenerator` on iOS. See [Notification](/react-native-haptics/notification). | | `Haptics.selection()` | `Promise` | Both | Selection-change feedback, corresponding to `UISelectionFeedbackGenerator` on iOS. `VIRTUAL_KEY` on Android. See [Selection](/react-native-haptics/selection). | | `Haptics.androidHaptics(type)` | `Promise` | Android | Platform-specific Android feedback; a no-op on iOS. See [Android-specific effects](/react-native-haptics/android-effects). | Every method returns a promise that resolves when the haptic feedback is completed. ## Types [#types] ### ImpactFeedback [#impactfeedback] ```ts type ImpactFeedback = 'light' | 'medium' | 'heavy' | 'soft' | 'rigid'; ``` Corresponds to Apple's `UIImpactFeedbackGenerator.FeedbackStyle`. On Android, the behavior is simulated to match the iOS equivalent. `soft` and `rigid` require iOS 13.0+. ### NotificationFeedback [#notificationfeedback] ```ts type NotificationFeedback = 'success' | 'warning' | 'error'; ``` Corresponds to Apple's `UINotificationFeedbackGenerator.FeedbackType`. On Android, the behavior is simulated to match the iOS equivalent. ### AndroidHapticsFeedback [#androidhapticsfeedback] ```ts type AndroidHapticsFeedback = | 'clock-tick' | 'confirm' | 'context-click' | 'drag-start' | 'gesture-end' | 'gesture-start' | 'gesture-threshold-activate' | 'gesture-threshold-deactivate' | 'keyboard-press' | 'keyboard-release' | 'keyboard-tap' | 'long-press' | 'no-haptics' | 'reject' | 'segment-frequent-tick' | 'segment-tick' | 'text-handle-move' | 'toggle-off' | 'toggle-on' | 'virtual-key' | 'virtual-key-release'; ``` A set of predefined haptic types corresponding to Android's native `HapticFeedbackConstants`. Availability varies by Android SDK version; unsupported types fall back to `virtual-key`. ## Error codes [#error-codes] | Code | Method | When it occurs | | ----------------- | ---------------- | ---------------------------------------------------------------- | | `E_INVALID_STYLE` | `impact` | The style is not one of the `ImpactFeedback` values. | | `E_INVALID_TYPE` | `notification` | The type is not one of the `NotificationFeedback` values. | | `E_INVALID_TYPE` | `androidHaptics` | The type is not one of the `AndroidHapticsFeedback` values. | | `E_NO_VIEW` | `androidHaptics` | There is no current activity view to perform haptic feedback on. | | `E_UNEXPECTED` | Any | An unexpected native error occurred. | Rejected promises carry the error code and a descriptive message; invalid values list the allowed values in the message. ## Native module [#native-module] The Turbo Module is registered as `Haptics`. All methods above are declared in the library's `Spec`, so they are fully type-safe when written in TypeScript. The following benchmark results compare `@mhpdev/react-native-haptics` with `expo-haptics` over **200 executions** per effect, reported as the average time per call in milliseconds. The full statistical report (mean, median, min, max, p75, p95, standard deviation) lives in the [benchmark repository](https://github.com/Mhp23/RN-Haptics-Benchmarks). ## iOS (iPhone 14 Pro Max) [#ios-iphone-14-pro-max] | Effect | React Native Haptics (avg ms/call) | Expo Haptics (avg ms/call) | React Native Haptics is | | -------------------- | ---------------------------------: | -------------------------: | ----------------------- | | Impact Heavy | 0.365 | 0.779 | ~2.13x faster | | Notification Success | 0.365 | 1.378 | ~3.78x faster | | Selection | 0.350 | 0.602 | ~1.72x faster | ## Android (Samsung A55) [#android-samsung-a55] | Effect | React Native Haptics (avg ms/call) | Expo Haptics (avg ms/call) | React Native Haptics is | | -------------------- | ---------------------------------: | -------------------------: | ------------------------------- | | Impact Heavy | 2.902 | 9.766 | ~3.36x faster | | Notification Success | 3.433 | 10.837 | ~3.16x faster | | Selection | 3.037 | 11.236 | ~3.70x faster | | Android Haptics | 3.238 | 1.879 | — Expo Haptics is ~1.72x faster | ## Notes [#notes] - Values are averages over 200 executions on the devices named above; your numbers will vary with device and workload. - For Android, the `androidHaptics` methods map to different native mechanisms, which is why the ranking differs from the other effects. - Reproduce or inspect the methodology in the [RN Haptics Benchmarks repository](https://github.com/Mhp23/RN-Haptics-Benchmarks). ## Example application [#example-application] The [example project](https://github.com/mhpdev-com/react-native-haptics/tree/main/example) in the repository renders a button for every impact style, notification type, selection, and — on Android — every `androidHaptics` effect. ## Feedback on button press [#feedback-on-button-press] ```tsx import React from 'react'; import Haptics from '@mhpdev/react-native-haptics'; import {SafeAreaView, StyleSheet, Text, TouchableOpacity} from 'react-native'; const App: React.FC = () => { const onImpactPress = () => { Haptics.impact('heavy'); }; return ( Trigger Heavy Impact ); }; const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center', }, button: { padding: 12.5, borderRadius: 5, backgroundColor: 'skyblue', }, buttonText: { fontSize: 22, fontWeight: '600', }, }); ``` ## Result feedback after an action [#result-feedback-after-an-action] ```ts async function save() { try { await persist(); Haptics.notification('success'); } catch { Haptics.notification('error'); } } ``` ## Cross-platform code with Android-only effects [#cross-platform-code-with-android-only-effects] `androidHaptics` is a no-op on iOS, so it can be called unconditionally; the example project still renders those buttons only on Android: ```tsx import {Platform} from 'react-native'; // Rendering the Android-only section of the example app { Platform.OS !== 'android' ? null : ( Haptics.androidHaptics('confirm')} /> ); } ``` ## Selection feedback [#selection-feedback] ```ts // e.g. in a segmented control change handler onValueChange={() => Haptics.selection()} ``` > **Note** > > For measured timings of each effect, see > [Benchmarks](/react-native-haptics/benchmarks). Triggers an impact haptic feedback. This corresponds to `UIImpactFeedbackGenerator` on iOS. On Android, it simulates the equivalent vibration effect. ```ts Haptics.impact(style: ImpactFeedback): Promise ``` ## ImpactFeedback type [#impactfeedback-type] - `light`: a collision between small, light user interface elements. - `medium`: a collision between moderately sized user interface elements. - `heavy`: a collision between large, heavy user interface elements. - `soft`: a collision between user interface elements that are soft, with a large amount of compression or elasticity. (iOS 13.0+) - `rigid`: a collision between user interface elements that are rigid, with a small amount of compression or elasticity. (iOS 13.0+) ## Example usage [#example-usage] ```ts import {ImpactFeedback} from '@mhpdev/react-native-haptics'; // Trigger a heavy impact Haptics.impact('heavy'); // Trigger a light impact Haptics.impact('light'); ``` Passing a value that is not one of the styles above rejects the returned promise with `E_INVALID_STYLE` — see [Troubleshooting](/react-native-haptics/troubleshooting). React Native Haptics — @mhpdev/react-native-haptics by Mhpdev `@mhpdev/react-native-haptics` is a high-performance React Native library for iOS haptics and Android vibration effects. ## Features [#features] - **High performance** — built with Turbo Modules for Android and iOS. - **Essential methods** — provides the methods you need for triggering native haptic feedback. - **Android-specific effects** — supports a wide range of Android vibration effects through [`androidHaptics`](/react-native-haptics/android-effects). - **Easy to use** — a simple API with a default `Haptics` object. - **Instant feedback** — executes on the UI thread to ensure instant feedback. - **Type safety** — fully type-safe and written in TypeScript. ## Compatibility [#compatibility] | Requirement | Value | | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Architecture | New Architecture (Turbo Modules) only. React Native 0.76+ has it enabled; 0.68–0.75 require [manual setup](https://github.com/reactwg/react-native-new-architecture/blob/main/docs/enable-apps.md). | | React Native | New Architecture compatible versions (see above) | | iOS | Uses your app's `min_ios_version_supported` from React Native. `soft` and `rigid` impact styles require iOS 13.0+. | | Android | minSdk 21 (library default, overridable). Several `androidHaptics` effects require API 23/27/30/34 and [fall back](/react-native-haptics/android-effects) to `virtual-key` when unsupported. | | Expo | Supported with [prebuild](/react-native-haptics/installation#expo); not supported in Expo Go | | Language | TypeScript with bundled type definitions | ## Next steps [#next-steps] - [Installation](/react-native-haptics/installation) — add the package to your project. - [Quick start](/react-native-haptics/quick-start) — trigger your first haptic. - [Benchmarks](/react-native-haptics/benchmarks) — measured performance versus `expo-haptics`. - [Releases](https://github.com/mhpdev-com/react-native-haptics/releases) — version history and changelogs on GitHub. ## Bare React Native [#bare-react-native] Install the package using either npm or Yarn: ```sh npm install @mhpdev/react-native-haptics ``` Or with Yarn: ```sh yarn add @mhpdev/react-native-haptics ``` For iOS, navigate to the `ios` directory and install the pods: ```sh cd ios && pod install ``` ## Expo [#expo] For Expo projects, follow these steps: 1. Install the package: ```sh npx expo install @mhpdev/react-native-haptics ``` 2. Since it is not supported on Expo Go, run: ```sh npx expo prebuild ``` > **Note** > > The library uses Turbo Modules, so it requires the New Architecture. React > Native 0.76 and higher have it enabled by default; on React Native 0.68–0.75 > you need to enable it first. See the [React Native New Architecture > guide](https://github.com/reactwg/react-native-new-architecture/blob/main/docs/enable-apps.md). ## Android requirements [#android-requirements] The library's Android module defaults are set in the package's `android/gradle.properties` and can be overridden from your project: | Property | Default | | --------------------------- | --------------- | | `Haptics_minSdkVersion` | `21` | | `Haptics_targetSdkVersion` | `35` | | `Haptics_compileSdkVersion` | `35` | | `Haptics_kotlinVersion` | `2.0.21` | | `Haptics_ndkVersion` | `27.1.12297006` | Individual `androidHaptics` effects have higher Android API requirements — see [Android-specific effects](/react-native-haptics/android-effects). Triggers a notification haptic feedback to communicate success, warning, or error. This corresponds to `UINotificationFeedbackGenerator` on iOS. On Android, it simulates the equivalent vibration effect. ```ts Haptics.notification(type: NotificationFeedback): Promise ``` ## NotificationFeedback type [#notificationfeedback-type] - `success`: indicates that a task or action has completed successfully. - `warning`: indicates that a task or action has produced a warning. - `error`: indicates that a task or action has failed. ## Example usage [#example-usage] ```ts import {NotificationFeedback} from '@mhpdev/react-native-haptics'; // Trigger a success notification Haptics.notification('success'); // Trigger an error notification Haptics.notification('error'); ``` Passing a value that is not one of the types above rejects the returned promise with `E_INVALID_TYPE` — see [Troubleshooting](/react-native-haptics/troubleshooting). Import the default `Haptics` object and call one of its methods: ```tsx import React from 'react'; import Haptics from '@mhpdev/react-native-haptics'; import {SafeAreaView, StyleSheet, Text, TouchableOpacity} from 'react-native'; const App: React.FC = () => { const onImpactPress = () => { Haptics.impact('heavy'); }; return ( Trigger Heavy Impact ); }; export default App; const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center', }, button: { padding: 12.5, borderRadius: 5, backgroundColor: 'skyblue', }, buttonText: { fontSize: 22, fontWeight: '600', }, }); ``` ## What's next? [#whats-next] - [Impact](/react-native-haptics/impact), [Notification](/react-native-haptics/notification), and [Selection](/react-native-haptics/selection) feedback. - [Android-specific effects](/react-native-haptics/android-effects) — a wide range of native Android vibration effects. - [Benchmarks](/react-native-haptics/benchmarks) — measured performance versus `expo-haptics`. Triggers a haptic feedback to indicate a selection change. This corresponds to `UISelectionFeedbackGenerator` on iOS. On Android, it uses `HapticFeedbackConstants.VIRTUAL_KEY`. ```ts Haptics.selection(): Promise ``` ## Example usage [#example-usage] ```ts // Trigger a selection change haptic Haptics.selection(); ``` Typical use cases are pickers, segmented controls, toggles, and tab switches — anywhere the user changes a selection. To mock the package's methods using the default mock configuration provided, follow these steps: 1. Create a file named `@mhpdev/react-native-haptics.ts` inside your `__mocks__` directory. 2. Copy the following code into that file: ```js jest.mock('@mhpdev/react-native-haptics', () => require('@mhpdev/react-native-haptics/jest'), ); ``` ## What the mock provides [#what-the-mock-provides] The mock exposes all four methods as Jest mocks that resolve, so haptic calls in your components never touch the native module: | Member | Mock behavior | | ------------------------ | --------------------- | | `Haptics.impact` | `jest.fn()` resolving | | `Haptics.notification` | `jest.fn()` resolving | | `Haptics.selection` | `jest.fn()` resolving | | `Haptics.androidHaptics` | `jest.fn()` resolving | Because the methods are Jest mocks, you can assert against them in your tests: ```ts import Haptics from '@mhpdev/react-native-haptics'; it('confirms the user action with haptics', () => { confirmAction(); expect(Haptics.androidHaptics).toHaveBeenCalledWith('confirm'); }); ``` > **Note** > > This mock uses `jest.mock` in the `__mocks__` file, matching the library's > README. Unlike the automatic-mock pattern used by some packages, declare the > `jest.mock(...)` call exactly as shown above. ## The native module cannot be found [#the-native-module-cannot-be-found] The library is built on Turbo Modules and only supports the New Architecture. If the native module is missing at runtime, verify that the New Architecture is enabled — React Native 0.76+ has it enabled by default; on React Native 0.68–0.75 you must enable it first. See the [React Native New Architecture guide](https://github.com/reactwg/react-native-new-architecture/blob/main/docs/enable-apps.md). ## Expo Go is not supported [#expo-go-is-not-supported] The library requires a native build. In Expo projects, run `npx expo prebuild` (see [Installation](/react-native-haptics/installation#expo)) and use a development build instead of Expo Go. ## `E_INVALID_STYLE` or `E_INVALID_TYPE` [#e_invalid_style-or-e_invalid_type] These rejections mean the argument did not match a known value: - `impact` only accepts `'light' | 'medium' | 'heavy' | 'soft' | 'rigid'`. - `notification` only accepts `'success' | 'warning' | 'error'`. - `androidHaptics` only accepts the `AndroidHapticsFeedback` values listed in the [API reference](/react-native-haptics/api-reference#androidhapticsfeedback). The rejection message lists all allowed values, so check the string for typos. ## `E_NO_VIEW` on Android [#e_no_view-on-android] `androidHaptics` triggers feedback through the current activity's view (`performHapticFeedback`). If there is no current activity — for example while the app is backgrounded or during a transition — the call rejects with `E_NO_VIEW`. Trigger haptics while the app is in the foreground, or catch the rejection and retry later. ## An Android effect does nothing on older devices [#an-android-effect-does-nothing-on-older-devices] Effects that rely on newer `HapticFeedbackConstants` values have API requirements (see the table in [Android-specific effects](/react-native-haptics/android-effects)). On devices below the required API, the library falls back to `virtual-key` instead of failing — the sensation differs, but the call still resolves. If an effect feels too weak on a device, prefer an effect with a lower API requirement (such as `long-press` or `keyboard-tap`). ## `soft` and `rigid` behave differently across platforms [#soft-and-rigid-behave-differently-across-platforms] These impact styles are `UIImpactFeedbackStyle` values introduced in iOS 13.0+. On Android every impact style is simulated with a vibration effect, so the feel does not map one-to-one with iOS. ## Vibration does not play at all [#vibration-does-not-play-at-all] Haptics depend on device settings: check that vibration is enabled in the device's sound settings and that any system "haptic feedback" toggle is on. On Android, the system may ignore haptic requests depending on the device's touch-feedback setting. ## Still stuck? [#still-stuck] - Search the [issue tracker](https://github.com/mhpdev-com/react-native-haptics/issues) and open a new issue if the problem persists. - Check the [releases](https://github.com/mhpdev-com/react-native-haptics/releases) page for version-specific notes and fixes. ## Import [#import] The library exports the `Speech` class (default) and the `HighlightedText` component: ```tsx import Speech, {HighlightedText} from '@mhpdev/react-native-speech'; ``` ## Speech class methods [#speech-class-methods] | Method | Returns | Platform | Description | | ---------------------------------------- | ------------------------ | -------- | -------------------------------------------------------------------------------------------------------- | | `Speech.speak(text)` | `Promise` | Both | Speaks text with the global options. Resolves with the utterance ID when queued. | | `Speech.speak(text, options)` | `Promise` | Both | Speaks text with per-utterance options overriding the global settings. Resolves with the utterance ID. | | `Speech.speakWithOptions(text, options)` | `Promise` | Both | **Deprecated.** Use `Speech.speak(text, options)` instead. | | `Speech.stop()` | `Promise` | Both | Immediately stops any ongoing or queued speech. | | `Speech.pause()` | `Promise` | Both | Pauses speech at the next word boundary. Android requires API 26+. Resolves `false` if nothing to pause. | | `Speech.resume()` | `Promise` | Both | Resumes paused speech. Android requires API 26+. Resolves `false` if nothing to resume. | | `Speech.isSpeaking()` | `Promise` | Both | Resolves `true` if speech is active (speaking or paused). | | `Speech.configure(options)` | `void` | Both | Sets the global options for all subsequent `speak()` calls. See [VoiceOptions](#voiceoptions). | | `Speech.reset()` | `void` | Both | Resets all global speech options to their defaults. | | `Speech.getAvailableVoices(language?)` | `Promise` | Both | Lists available voices, optionally filtered by a language code or tag. See [VoiceProps](#voiceprops). | | `Speech.getEngines()` | `Promise` | Android | Lists the installed text-to-speech engines. See [EngineProps](#engineprops). | | `Speech.setEngine(engineName)` | `Promise` | Android | Sets the text-to-speech engine for all subsequent synthesis. | | `Speech.openVoiceDataInstaller()` | `Promise` | Android | Opens the system activity to install or manage TTS voice data. | ## Constants [#constants] | Constant | Type | Description | | ----------------------- | -------- | ----------------------------------------------------------------------------------------------------------------------------------- | | `Speech.maxInputLength` | `number` | Maximum characters per speak call. Android: from `TextToSpeech.getMaxSpeechInputLength`. iOS: `Number.MAX_VALUE` (no system limit). | ## Events [#events] Every event callback receives the utterance `id` and returns a subscription with a `remove()` method for cleanup. | Event | Payload | Description | | ------------------- | ------------------------------------------------ | ----------------------------------------------------------------------- | | `Speech.onStart` | `{id: string}` | Speech started. | | `Speech.onFinish` | `{id: string}` | Speech completed. | | `Speech.onPause` | `{id: string}` | Speech paused (Android: API 26+). | | `Speech.onResume` | `{id: string}` | Speech resumed (Android: API 26+). | | `Speech.onStopped` | `{id: string}` | Speech stopped. | | `Speech.onProgress` | `{id: string; location: number; length: number}` | Progress with the current position and range length (Android: API 26+). | | `Speech.onError` | `{id: string}` | An error occurred during synthesis. | ## Types [#types] ### VoiceOptions [#voiceoptions] | Property | Type | Description | Platform support | | ------------ | --------------------------------- | ------------------------------------------------------------------------------------ | ---------------- | | `language` | `string?` | Language code or IETF BCP 47 language tag (e.g., `'en-US'`, `'fr-FR'`) | Both | | `volume` | `number?` | Volume level from `0.0` to `1.0` | Both | | `voice` | `string?` | Specific voice identifier to use (obtained from `getAvailableVoices()`) | Both | | `pitch` | `number?` | Pitch multiplier: Android `0.1`–`2.0`, iOS `0.5`–`2.0` | Both | | `rate` | `number?` | Speech rate: Android `0.1`–`2.0`, iOS varies based on `AVSpeechUtterance` limits | Both | | `ducking` | `boolean?` | Temporarily lowers audio from other apps while speech is active. Defaults to `false` | Both | | `silentMode` | `'obey' \| 'respect' \| 'ignore'` | Controls how speech interacts with the silent switch. Ignored if `ducking` is `true` | iOS only | ### VoiceProps [#voiceprops] | Property | Type | Description | | ------------ | ------------------------- | ----------------------------------------------- | | `name` | `string` | The name of the voice. | | `identifier` | `string` | The unique identifier for the voice. | | `language` | `string` | The language code (e.g., `'en-US'`, `'fr-FR'`). | | `quality` | `'Default' \| 'Enhanced'` | The quality level of the voice. | ### EngineProps [#engineprops] | Property | Type | Description | | ----------- | --------- | ------------------------------------------------------------------------- | | `name` | `string` | The unique system identifier (e.g., `"com.google.android.tts"`). | | `label` | `string` | The human-readable display name (e.g., `"Google Text-to-Speech Engine"`). | | `isDefault` | `boolean` | Whether this is the default engine. | ### EventProps [#eventprops] | Property | Type | Description | | -------- | -------- | ----------------- | | `id` | `string` | The utterance ID. | ### ProgressEventProps [#progresseventprops] Extends [EventProps](#eventprops). | Property | Type | Description | | ---------- | -------- | ---------------------------------------- | | `length` | `number` | The text being spoken length. | | `location` | `number` | The current position in the spoken text. | ### HighlightedTextProps [#highlightedtextprops] See the [HighlightedText page](/react-native-speech/highlighted-text#properties) for the full property table. ## Native module [#native-module] The Turbo Module is registered as `RNSpeech`. All methods and listeners above are declared in the library's `Spec`, so they are fully type-safe when written in TypeScript. ## Configuring global speech options [#configuring-global-speech-options] Set global speech options that apply to all speech synthesis calls: ```ts Speech.configure(options: VoiceOptions): void ``` ```ts Speech.configure({ language: 'en-US', volume: 1.0, pitch: 1.2, rate: 0.8, ducking: false, silentMode: 'obey', // iOS only; ignored if ducking is true }); ``` ## VoiceOptions [#voiceoptions] | Property | Type | Description | Platform support | | ------------ | --------------------------------- | ----------------------------------------------------------------------------------------------- | ---------------- | | `language` | `string` | Language code or IETF BCP 47 language tag (e.g., `'en-US'`, `'fr-FR'`) | Both | | `volume` | `number` | Volume level from `0.0` (silent) to `1.0` (maximum) | Both | | `voice` | `string` | Specific voice identifier to use (obtained from `getAvailableVoices()`) | Both | | `pitch` | `number` | Pitch multiplier: Android `0.1`–`2.0`, iOS `0.5`–`2.0` | Both | | `rate` | `number` | Speech rate: Android `0.1`–`2.0`, iOS varies based on `AVSpeechUtterance` limits | Both | | `ducking` | `boolean` | If `true`, temporarily lowers audio from other apps while speech is active. Defaults to `false` | Both | | `silentMode` | `'obey' \| 'respect' \| 'ignore'` | Controls how speech interacts with the device's silent switch. Ignored if `ducking` is `true` | iOS only | ## Silent mode (iOS only) [#silent-mode-ios-only] - **`obey`** (default): does not change the app's audio session. Speech follows the system default behavior. - **`respect`**: speech will be silenced by the ringer switch. Use for non-critical audio content. - **`ignore`**: speech will play even if the ringer is off. Use for critical audio when ducking is not desired. `ducking` is intended for critical announcements (for example, navigation) and takes priority over `silentMode` on iOS. ## Resetting speech options [#resetting-speech-options] Reset all global speech options to their default values: ```ts Speech.reset(): void ``` ```ts Speech.reset(); ``` ## Per-utterance options [#per-utterance-options] Individual `speak()` calls can override the global options — see [Playback controls](/react-native-speech/playback-controls#speaking-text). ## Getting available voices [#getting-available-voices] Retrieve a list of all available voices on the device. Optionally, you can filter voices by providing a language code or tag ([IETF BCP 47 language tag](https://www.techonthenet.com/js/language_tags.php)). ```ts Speech.getAvailableVoices(language?: string): Promise ``` Each voice provides the following properties: - `name`: the name of the voice. - `identifier`: the unique identifier for the voice. - `language`: the language tag (e.g., `'en-US'`, `'fr-FR'`). - `quality`: the quality level of the voice (`'Default'` or `'Enhanced'`). ```ts // Retrieve all voices Speech.getAvailableVoices().then(voices => { console.log('Available voices:', voices); }); // Retrieve only English voices Speech.getAvailableVoices('en').then(voices => { console.log('English voices:', voices); }); // Retrieve only English (US) voices Speech.getAvailableVoices('en-US').then(voices => { console.log('English (US) voices:', voices); }); ``` ## Engine management (Android) [#engine-management-android] These methods are available only on Android and allow you to manage the underlying text-to-speech engine. ### Get available engines [#get-available-engines] Gets a list of all available text-to-speech engines installed on the device. ```ts Speech.getEngines(): Promise ``` Each engine provides the following properties: - `name`: the unique system identifier for the engine (e.g., `"com.google.android.tts"`). - `label`: the human-readable display name (e.g., `"Google Text-to-Speech Engine"`). - `isDefault`: a boolean flag indicating if this is the default engine. ```ts Speech.getEngines().then(engines => { engines.forEach(engine => { console.log(`Engine: ${engine.label} (${engine.name})`); if (engine.isDefault) { console.log('This is the default engine.'); } }); }); ``` ### Set speech engine [#set-speech-engine] Sets the text-to-speech engine to use for all subsequent synthesis. ```ts Speech.setEngine(engineName: string): Promise ``` ```ts // First, get available engines const engines = await Speech.getEngines(); if (engines.length > 0) { // Then set a specific engine by its name await Speech.setEngine(engines[0].name); } ``` If the requested engine is not available on the device, the call is rejected with an `engine_error` — see [Troubleshooting](/react-native-speech/troubleshooting). ### Open voice data installer [#open-voice-data-installer] Opens the system activity that allows the user to install or manage TTS voice data. ```ts Speech.openVoiceDataInstaller(): Promise ``` ```ts Speech.openVoiceDataInstaller().catch(error => { console.error('Failed to open voice data installer.', error); }); ``` > **Note** > > The engine management methods and the voice data installer are Android > platform features. Subscribe to event callbacks for speech synthesis lifecycle monitoring. Each callback receives an `id` property that corresponds to the utterance ID returned by `speak()` or `speakWithOptions()`. This allows you to handle events for specific speech operations independently. Each subscription exposes a `remove()` method for cleanup. ## Why use utterance IDs? [#why-use-utterance-ids] Utterance IDs enable precise event tracking when handling multiple concurrent or sequential speech operations: ```ts // Start first speech const id1 = await Speech.speak('Hello'); // Start second speech const id2 = await Speech.speak('World'); // Handle events only for the first speech Speech.onFinish(({id}) => { if (id === id1) { console.log('First speech finished'); } else if (id === id2) { console.log('Second speech finished'); } }); ``` ## onStart [#onstart] Triggers when speech starts. ```ts const startSubscription = Speech.onStart(({id}) => { console.log(`Speech started (ID: ${id})`); }); // Cleanup startSubscription.remove(); ``` ## onFinish [#onfinish] Triggers when speech completes. ```ts const finishSubscription = Speech.onFinish(({id}) => { console.log(`Speech finished (ID: ${id})`); }); // Cleanup finishSubscription.remove(); ``` ## onPause [#onpause] Triggers when speech is paused. On Android, API 26+ (Android 8+) is required. ```ts const pauseSubscription = Speech.onPause(({id}) => { console.log(`Speech paused (ID: ${id})`); }); // Cleanup pauseSubscription.remove(); ``` ## onResume [#onresume] Triggers when speech is resumed. On Android, API 26+ (Android 8+) is required. ```ts const resumeSubscription = Speech.onResume(({id}) => { console.log(`Speech resumed (ID: ${id})`); }); // Cleanup resumeSubscription.remove(); ``` ## onStopped [#onstopped] Triggers when speech is stopped. ```ts const stoppedSubscription = Speech.onStopped(({id}) => { console.log(`Speech stopped (ID: ${id})`); }); // Cleanup stoppedSubscription.remove(); ``` ## onProgress [#onprogress] Triggers during speech with progress information. On Android, API 26+ (Android 8+) is required. Callback parameters: - `id`: the utterance identifier. - `length`: the text being spoken length. - `location`: the current position in the spoken text. ```ts const progressSubscription = Speech.onProgress(({id, location, length}) => { console.log( `Speech ${id} progress, current word length: ${length}, current char position: ${location}`, ); }); // Cleanup progressSubscription.remove(); ``` > **Note** > > `onProgress` reports word ranges you can feed straight into the > [HighlightedText](/react-native-speech/highlighted-text) component for > real-time speech visualization. See the > [examples](/react-native-speech/examples). ## onError [#onerror] Triggers when an error occurs. ```ts const errorSubscription = Speech.onError(({id}) => { console.error(`Speech error (ID: ${id})`); }); // Cleanup errorSubscription.remove(); ``` ## Example application [#example-application] The [example project](https://github.com/mhpdev-com/react-native-speech/tree/main/example) in the repository demonstrates the full flow: configuring options, speaking text, tracking progress, highlighting the current range, and controlling playback. ## Speak with progress highlighting [#speak-with-progress-highlighting] This is the pattern used by the example project: keep the utterance ID, subscribe to the lifecycle events, and turn every `onProgress` event into a highlight for the `HighlightedText` component. ```tsx import React from 'react'; import Speech, { HighlightedText, type HighlightedSegmentProps, } from '@mhpdev/react-native-speech'; const TEXT = 'This high-performance text-to-speech library enables seamless speech management with start, pause, resume, and stop controls.'; const Example: React.FC = () => { const [highlights, setHighlights] = React.useState< Array >([]); const currentId = React.useRef(''); React.useEffect(() => { const startSubscription = Speech.onStart(({id}) => { if (id === currentId.current) { console.log(`Speech ${id} started`); } }); const finishSubscription = Speech.onFinish(({id}) => { if (id === currentId.current) { setHighlights([]); currentId.current = ''; } }); const progressSubscription = Speech.onProgress(({id, location, length}) => { setHighlights([ { start: location, end: location + length, }, ]); }); return () => { startSubscription.remove(); finishSubscription.remove(); progressSubscription.remove(); }; }, []); const onStartPress = async () => { const id = await Speech.speak(TEXT); currentId.current = id; }; return ( ); }; ``` ## Configure a voice and language [#configure-a-voice-and-language] Select a voice from the available voices and apply it through the global configuration: ```ts const enVoices = await Speech.getAvailableVoices('en-US'); Speech.configure({ rate: 0.5, volume: 1, voice: enVoices[3]?.identifier, }); ``` ## Select an engine (Android) [#select-an-engine-android] List the installed engines and switch to one of them: ```ts const engines = await Speech.getEngines(); if (engines?.[0]) { await Speech.setEngine(engines[0].name); } ``` > **Note** > > The library's example project guards `pause` / `resume` buttons behind > `Platform.Version >= 26` on Android, because those controls require API 26+. ## More examples [#more-examples] - [Quick start](/react-native-speech/quick-start) — the minimal speak button. - [HighlightedText](/react-native-speech/highlighted-text) — segment styling and press handling. - [Events](/react-native-speech/events) — every lifecycle event with cleanup. The `HighlightedText` component allows you to display text with customizable highlighted segments. This is especially useful for emphasizing parts of text (e.g., the currently synthesized text). In addition to the specialized properties listed below, the component accepts all standard React Native `` props. ## Importing the component [#importing-the-component] ```tsx import {HighlightedText} from '@mhpdev/react-native-speech'; ``` ## Properties [#properties] | Property | Type | Description | | -------------------- | ------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- | | `text` | `string` | The full text content to be displayed. | | `highlightedStyle` | `StyleProp` | The base style applied to all highlighted segments. Can be overridden per segment through `highlights`. | | `highlights` | `Array<{start: number; end: number; style?: StyleProp}>` | The segments to highlight. Each entry must include `start` (starting character index) and `end` (ending index), plus an optional per-segment `style`. | | `onHighlightedPress` | `(segment: {text: string; start: number; end: number}) => void` | Invoked when a highlighted segment is pressed, with the segment's `text`, `start`, and `end`. | ## Example [#example] ```tsx import React from 'react'; import { HighlightedText, type HighlightedSegmentProps, type HighlightedSegmentArgs, } from '@mhpdev/react-native-speech'; import {Alert, SafeAreaView, StyleSheet} from 'react-native'; const TEXT = 'This is a sample text where some parts are highlighted.'; const ExampleHighlightedText: React.FC = () => { const highlights: Array = [ {start: 10, end: 21}, {start: 43, end: 54, style: styles.customHighlightedStyle}, ]; const onHighlightedPress = React.useCallback( ({text, start, end}: HighlightedSegmentArgs) => Alert.alert( 'Highlighted Segment', `Segment "${text}" starts at ${start} and ends at ${end}`, ), [], ); return ( ); }; const styles = StyleSheet.create({ container: { padding: 16, }, text: { fontSize: 16, }, highlighted: { backgroundColor: 'yellow', fontWeight: 'bold', }, customHighlightedStyle: { color: 'white', backgroundColor: 'blue', }, }); export default ExampleHighlightedText; ``` ## Driving highlights from speech progress [#driving-highlights-from-speech-progress] Combine the component with [`Speech.onProgress`](/react-native-speech/events#onprogress): every progress event reports the `location` and `length` of the currently spoken range, which can be passed as a highlight. See the [examples](/react-native-speech/examples) page for the complete pattern used by the example project. React Native Speech — @mhpdev/react-native-speech by Mhpdev `@mhpdev/react-native-speech` is a high-performance text-to-speech library built for bare React Native and Expo, compatible with Android and iOS. It enables seamless speech management and provides events for detailed synthesis management. ## Features [#features] - **High performance** — built on Turbo Modules for a fast, native-like experience on Android and iOS. - **Full control** — a complete set of methods for comprehensive speech synthesis management. - **Consistent playback** — `pause` and `resume` support for iOS and Android. This functionality is not natively available on Android, so the library provides a custom implementation (API 26+) designed to emulate the iOS experience. - **Optional audio ducking** — automatically lowers other apps' audio to ensure clear, uninterrupted speech. - **Rich events** — a comprehensive event system for precise synthesis lifecycle monitoring. - **Visual feedback** — a customizable [HighlightedText](/react-native-speech/highlighted-text) component for real-time speech visualization. - **Type safety** — fully written in TypeScript with complete type definitions. ## Compatibility [#compatibility] | Requirement | Value | | ------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Architecture | New Architecture (Turbo Modules) only. React Native 0.76+ has it enabled; 0.68–0.75 require [manual setup](https://github.com/reactwg/react-native-new-architecture/blob/main/docs/enable-apps.md). | | React Native | New Architecture compatible versions (see above) | | iOS | Uses your app's `min_ios_version_supported` from React Native; links `AVFoundation` | | Android | minSdk 24 (library default, overridable). `pause` / `resume` require Android 8.0 (API 26+) | | Expo | Supported with [prebuild](/react-native-speech/installation#expo); not supported in Expo Go | | Language | TypeScript with bundled type definitions | ## Next steps [#next-steps] - [Installation](/react-native-speech/installation) — add the package to your project. - [Quick start](/react-native-speech/quick-start) — speak your first utterance. - [API reference](/react-native-speech/api-reference) — every method, option, and type. - [Releases](https://github.com/mhpdev-com/react-native-speech/releases) — version history and changelogs on GitHub. ## Bare React Native [#bare-react-native] Install the package using either npm or Yarn: ```sh npm install @mhpdev/react-native-speech ``` Or with Yarn: ```sh yarn add @mhpdev/react-native-speech ``` For iOS, navigate to the `ios` directory and install the pods: ```sh cd ios && pod install ``` ## Expo [#expo] For Expo projects, follow these steps: 1. Install the package: ```sh npx expo install @mhpdev/react-native-speech ``` 2. Since it is not supported on Expo Go, run: ```sh npx expo prebuild ``` > **Note** > > The library uses Turbo Modules, so it requires the New Architecture. React > Native 0.76 and higher have it enabled by default; on React Native 0.68–0.75 > you need to enable it first. See the [React Native New Architecture > guide](https://github.com/reactwg/react-native-new-architecture/blob/main/docs/enable-apps.md). ## Android requirements [#android-requirements] The library's Android module defaults are set in the package's `android/gradle.properties` and can be overridden from your project: | Property | Default | | ---------------------------- | --------------- | | `RNSpeech_minSdkVersion` | `24` | | `RNSpeech_targetSdkVersion` | `36` | | `RNSpeech_compileSdkVersion` | `36` | | `RNSpeech_kotlinVersion` | `2.0.21` | | `RNSpeech_ndkVersion` | `27.1.12297006` | `pause` and `resume` require Android 8.0 (API 26+). See [Playback controls](/react-native-speech/playback-controls). ## Speaking text [#speaking-text] Speak a given text using the current global settings, or provide per-utterance options: ```ts Speech.speak(text: string): Promise Speech.speak(text: string, options: VoiceOptions): Promise ``` The promise resolves with a unique utterance ID (string) as soon as the utterance is queued. Use it to filter events for this specific speech operation. ```ts const id = await Speech.speak('Hello, world!'); // Track events specific to this utterance Speech.onFinish(({id: eventId}) => { if (eventId === id) { console.log('Speech finished'); } }); ``` Per-utterance options override the [global configuration](/react-native-speech/configuration) for that utterance only: ```ts const id2 = await Speech.speak('Hello!', { language: 'en-US', pitch: 1.5, rate: 0.8, }); // Track events specific to this utterance Speech.onProgress(({id: eventId, location, length}) => { if (eventId === id2) { console.log(`Progress: ${location}/${length}`); } }); ``` ### Text length limit [#text-length-limit] The `Speech.maxInputLength` constant is the maximum number of characters allowed in a single call to the speak methods. Android enforces this limit (determined by `TextToSpeech.getMaxSpeechInputLength`); iOS has no synthesis system limit, so the speech class returns `Number.MAX_VALUE` there. If your text exceeds this limit, you must manually split it into smaller utterances on the JavaScript side. Calls that exceed the limit are rejected — see [Troubleshooting](/react-native-speech/troubleshooting). ## Speaking text with custom options (deprecated) [#speaking-text-with-custom-options-deprecated] ```ts Speech.speakWithOptions(text: string, options: VoiceOptions): Promise ``` > **Warning** > > `speakWithOptions` is deprecated. Use `Speech.speak(text, options)` instead. ## Controlling speech [#controlling-speech] ### Stop speech [#stop-speech] Immediately stops any ongoing or queued speech synthesis: ```ts Speech.stop(): Promise ``` ```ts Speech.stop().then(() => console.log('Speech stopped')); ``` ### Pause speech [#pause-speech] Pauses the current speech at the next word boundary. On Android, API 26+ (Android 8+) is required. ```ts Speech.pause(): Promise ``` ```ts Speech.pause().then(isPaused => { console.log(isPaused ? 'Speech paused' : 'Nothing to pause'); }); ``` ### Resume speech [#resume-speech] Resumes previously paused speech. On Android, API 26+ (Android 8+) is required. ```ts Speech.resume(): Promise ``` ```ts Speech.resume().then(isResumed => { console.log(isResumed ? 'Speech resumed' : 'Nothing to resume'); }); ``` ### Check if speaking [#check-if-speaking] Determine if speech synthesis is currently active: ```ts Speech.isSpeaking(): Promise ``` ```ts Speech.isSpeaking().then(isSpeaking => { console.log(isSpeaking ? 'Currently speaking or paused' : 'Not speaking'); }); ``` Import the default `Speech` class and call `Speech.speak`: ```tsx import React from 'react'; import Speech from '@mhpdev/react-native-speech'; import {SafeAreaView, StyleSheet, Text, TouchableOpacity} from 'react-native'; const App: React.FC = () => { const onSpeakPress = async () => { const id = await Speech.speak('Hello World!'); console.log('Speech queued with id:', id); }; return ( Speak ); }; export default App; const styles = StyleSheet.create({ container: { flex: 1, alignItems: 'center', justifyContent: 'center', }, button: { padding: 12.5, borderRadius: 5, backgroundColor: 'skyblue', }, buttonText: { fontSize: 22, fontWeight: '600', }, }); ``` `speak()` resolves with a unique utterance ID as soon as the utterance is queued. Keep the ID if you want to track events for that specific speech operation — see [Events](/react-native-speech/events). ## What's next? [#whats-next] - Set global voice options with [Configuration](/react-native-speech/configuration). - Explore voices and engines in [Engines and voices](/react-native-speech/engines-and-voices). - Control playback in [Playback controls](/react-native-speech/playback-controls). To mock the package's methods and components using the default mock configuration provided, follow these steps: 1. Create a file named `@mhpdev/react-native-speech.ts` inside your `__mocks__` directory. 2. Copy the following code into that file: ```js module.exports = require('@mhpdev/react-native-speech/jest'); ``` ## What the mock provides [#what-the-mock-provides] The mock returns a class-like default export and a `HighlightedText` mock, so your components render and your calls resolve without the native module: | Member | Mock behavior | | --------------------------------------------- | ------------------------------------------- | | `Speech.maxInputLength` | `4000` | | `Speech.speak` / `speakWithOptions` | Resolves `undefined` | | `Speech.stop` | Resolves `undefined` | | `Speech.pause` / `resume` / `isSpeaking` | Resolves `false` | | `Speech.getAvailableVoices` | Resolves `[]` | | `Speech.getEngines` | Resolves `[]` | | `Speech.setEngine` / `openVoiceDataInstaller` | Resolves `undefined` | | `Speech.configure` / `reset` | No-op | | `Speech.onStart` … `Speech.onProgress` | Return a subscription with a `remove()` spy | | `HighlightedText` | Renders `null` | Because the methods are Jest mocks, you can assert against them in your tests: ```ts import Speech from '@mhpdev/react-native-speech'; it('speaks the announced text', async () => { await announce('Welcome'); expect(Speech.speak).toHaveBeenCalledWith('Welcome'); }); ``` > **Note** > > The mock is installed through the `__mocks__` file, so it applies to every > test file that imports the package. The event subscriptions expose Jest spies, > which you can also use to emit fake events if your tests need them. ## The native module cannot be found [#the-native-module-cannot-be-found] The library is built on Turbo Modules and only supports the New Architecture. If the native module is missing at runtime, verify that the New Architecture is enabled — React Native 0.76+ has it enabled by default; on React Native 0.68–0.75 you must enable it first. See the [React Native New Architecture guide](https://github.com/reactwg/react-native-new-architecture/blob/main/docs/enable-apps.md). ## Expo Go is not supported [#expo-go-is-not-supported] The library requires a native build. In Expo projects, run `npx expo prebuild` (see [Installation](/react-native-speech/installation#expo)) and use a development build instead of Expo Go. ## Speech fails to start on Android [#speech-fails-to-start-on-android] The Android module initializes the text-to-speech engine lazily and rejects with `speech_error` if initialization fails. Common causes: - No text-to-speech engine is installed or the engine lacks voice data for the requested language. Use `Speech.openVoiceDataInstaller()` to send the user to the system installer, and `Speech.getEngines()` / `Speech.setEngine(name)` to switch engines. - The text is `null` — the module rejects calls with null text. ## Text exceeds the maximum input length [#text-exceeds-the-maximum-input-length] On Android, text longer than `Speech.maxInputLength` is rejected. Check the constant before calling `speak()` and split long text into smaller utterances on the JavaScript side. ## Pause and resume do nothing on older Android [#pause-and-resume-do-nothing-on-older-android] `pause` and `resume` require Android 8.0 (API 26+). On lower API levels these calls cannot pause the engine — guard your UI with `Platform.Version >= 26`, as the example project does. ## Switching engines fails [#switching-engines-fails] `Speech.setEngine` rejects with `engine_error` when the requested engine name is not available on the device. Always resolve the engine name from `Speech.getEngines()` instead of hardcoding one. ## Voice data installer cannot open [#voice-data-installer-cannot-open] `Speech.openVoiceDataInstaller()` rejects with `UNSUPPORTED_OPERATION` when the device has no activity that can handle TTS voice data installation. ## iOS audio behaves unexpectedly with the silent switch [#ios-audio-behaves-unexpectedly-with-the-silent-switch] `silentMode` is iOS only: - `obey` (default) keeps the app's audio session unchanged and follows system default behavior. - `respect` silences speech with the ringer switch — use it for non-critical audio. - `ignore` plays speech even with the ringer off — use it for critical audio. `ducking: true` takes priority over `silentMode` and should only be used for critical announcements such as navigation. ## Still stuck? [#still-stuck] - Search the [issue tracker](https://github.com/mhpdev-com/react-native-speech/issues) and open a new issue if the problem persists. - Check the [releases](https://github.com/mhpdev-com/react-native-speech/releases) page for version-specific notes and fixes.