├── .eslintignore ├── .github ├── CODEOWNERS ├── ISSUE_TEMPLATE │ ├── bug_report.md │ └── feature_request.md ├── PULL_REQUEST_TEMPLATE.md └── workflows │ ├── ci.yml │ ├── lock.yml │ ├── needs-reply-remove.yml │ ├── needs-reply.yml │ └── needs-triage.yml ├── .gitignore ├── .npmrc ├── CHANGELOG.md ├── CONTRIBUTING.md ├── CapacitorCommunityTextToSpeech.podspec ├── LICENSE ├── README.md ├── android ├── .gitignore ├── build.gradle ├── gradle.properties ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── proguard-rules.pro ├── settings.gradle └── src │ ├── androidTest │ └── java │ │ └── com │ │ └── getcapacitor │ │ └── android │ │ └── ExampleInstrumentedTest.java │ ├── main │ ├── AndroidManifest.xml │ ├── java │ │ └── com │ │ │ └── getcapacitor │ │ │ └── community │ │ │ └── tts │ │ │ ├── SpeakResultCallback.java │ │ │ ├── TextToSpeech.java │ │ │ └── TextToSpeechPlugin.java │ └── res │ │ └── .gitkeep │ └── test │ └── java │ └── com │ └── getcapacitor │ └── ExampleUnitTest.java ├── ios ├── Plugin.xcodeproj │ ├── project.pbxproj │ ├── project.xcworkspace │ │ ├── contents.xcworkspacedata │ │ └── xcshareddata │ │ │ └── IDEWorkspaceChecks.plist │ └── xcshareddata │ │ └── xcschemes │ │ ├── Plugin.xcscheme │ │ └── PluginTests.xcscheme ├── Plugin.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist ├── Plugin │ ├── Info.plist │ ├── TextToSpeech.swift │ ├── TextToSpeechPlugin.h │ ├── TextToSpeechPlugin.m │ └── TextToSpeechPlugin.swift ├── PluginTests │ ├── Info.plist │ └── TextToSpeechPluginTests.swift └── Podfile ├── package-lock.json ├── package.json ├── rollup.config.mjs ├── src ├── definitions.ts ├── index.ts └── web.ts └── tsconfig.json /.eslintignore: -------------------------------------------------------------------------------- 1 | build 2 | dist 3 | -------------------------------------------------------------------------------- /.github/CODEOWNERS: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/en/github/creating-cloning-and-archiving-repositories/about-code-owners 2 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: "bug: " 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | **Plugin version:** 10 | 11 | 12 | 13 | 14 | **Platform(s):** 15 | 16 | 17 | 18 | 19 | **Current behavior:** 20 | 21 | 22 | 23 | 24 | **Expected behavior:** 25 | 26 | 27 | 28 | 29 | **Steps to reproduce:** 30 | 31 | 32 | 33 | 34 | **Related code:** 35 | 36 | 37 | ``` 38 | insert short code snippets here 39 | ``` 40 | 41 | **Other information:** 42 | 43 | 44 | 45 | 46 | **Capacitor doctor:** 47 | 48 | 49 | ``` 50 | insert the output from `npx cap doctor` here 51 | ``` 52 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: "feat: " 5 | labels: "" 6 | assignees: "" 7 | --- 8 | 9 | **Is your feature request related to a problem? Please describe:** 10 | 11 | 12 | 13 | 14 | **Describe the solution you'd like:** 15 | 16 | 17 | 18 | 19 | **Describe alternatives you've considered:** 20 | 21 | 22 | 23 | 24 | **Additional context:** 25 | 26 | 27 | 28 | -------------------------------------------------------------------------------- /.github/PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | ## Pull request checklist 2 | 3 | Please check if your PR fulfills the following requirements: 4 | 5 | - [ ] The changes have been tested successfully. 6 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: 6 | - master 7 | pull_request: 8 | workflow_dispatch: 9 | 10 | env: 11 | NODE_VERSION: 20 12 | JAVA_VERSION: 21 13 | 14 | jobs: 15 | build: 16 | name: Build 17 | runs-on: macos-15 18 | steps: 19 | - run: sudo xcode-select --switch /Applications/Xcode_16.app 20 | - name: Checkout 21 | uses: actions/checkout@v4 22 | - name: Set up Node.js ${{ env.NODE_VERSION }} 23 | uses: actions/setup-node@v4 24 | with: 25 | node-version: ${{ env.NODE_VERSION }} 26 | - name: Set up Java ${{ env.JAVA_VERSION }} 27 | uses: actions/setup-java@v4 28 | with: 29 | distribution: 'zulu' 30 | java-version: ${{ env.JAVA_VERSION }} 31 | - name: Install dependencies 32 | run: npm ci 33 | - name: Build iOS 34 | run: npm run verify:ios 35 | - name: Build Android 36 | run: npm run verify:android 37 | - name: Build Web 38 | run: npm run verify:web 39 | - name: Upload artifacts 40 | uses: actions/upload-artifact@v4 41 | with: 42 | name: dist 43 | path: dist 44 | lint: 45 | name: Lint 46 | runs-on: macos-15 47 | steps: 48 | - name: Checkout 49 | uses: actions/checkout@v4 50 | - name: Set up Node.js ${{ env.NODE_VERSION }} 51 | uses: actions/setup-node@v4 52 | with: 53 | node-version: ${{ env.NODE_VERSION }} 54 | - name: Install dependencies 55 | run: npm ci 56 | - name: Run Lint script 57 | run: npm run lint 58 | -------------------------------------------------------------------------------- /.github/workflows/lock.yml: -------------------------------------------------------------------------------- 1 | name: Lock old issues and pull requests that are closed 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | workflow_dispatch: 7 | 8 | permissions: 9 | issues: write 10 | pull-requests: write 11 | 12 | concurrency: 13 | group: lock 14 | 15 | jobs: 16 | action: 17 | runs-on: ubuntu-latest 18 | steps: 19 | - uses: dessant/lock-threads@v3 20 | with: 21 | issue-inactive-days: '56' 22 | -------------------------------------------------------------------------------- /.github/workflows/needs-reply-remove.yml: -------------------------------------------------------------------------------- 1 | name: Remove needs-reply label 2 | 3 | on: 4 | issue_comment: 5 | types: 6 | - created 7 | 8 | jobs: 9 | needs-reply: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Remove needs-reply label 13 | run: | 14 | curl --request DELETE \ 15 | --url 'https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/labels/needs%3A%20reply' \ 16 | --header 'Authorization: token ${{ secrets.GITHUB_TOKEN }}' 17 | -------------------------------------------------------------------------------- /.github/workflows/needs-reply.yml: -------------------------------------------------------------------------------- 1 | name: Close old issues that need reply 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | workflow_dispatch: 7 | 8 | jobs: 9 | needs-reply: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Close old issues that need reply 13 | uses: dwieeb/needs-reply@v2 14 | with: 15 | repo-token: ${{ secrets.GITHUB_TOKEN }} 16 | issue-label: 'needs: reply' 17 | -------------------------------------------------------------------------------- /.github/workflows/needs-triage.yml: -------------------------------------------------------------------------------- 1 | name: Add needs-triage label 2 | 3 | on: 4 | issues: 5 | types: 6 | - opened 7 | 8 | jobs: 9 | needs-triage: 10 | runs-on: ubuntu-latest 11 | steps: 12 | - name: Add needs-triage label 13 | if: join(github.event.issue.labels) == '' 14 | run: | 15 | curl --request POST \ 16 | --url 'https://api.github.com/repos/${{ github.repository }}/issues/${{ github.event.issue.number }}/labels' \ 17 | --header 'Authorization: token ${{ secrets.GITHUB_TOKEN }}' \ 18 | --header 'Content-Type: application/json' \ 19 | --header 'Accept: application/vnd.github.v3+json' \ 20 | --data-raw '{ "labels": ["needs: triage"] }' 21 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # node files 2 | dist 3 | node_modules 4 | 5 | # iOS files 6 | Pods 7 | Podfile.lock 8 | Build 9 | xcuserdata 10 | 11 | # macOS files 12 | .DS_Store 13 | 14 | 15 | 16 | # Based on Android gitignore template: https://github.com/github/gitignore/blob/HEAD/Android.gitignore 17 | 18 | # Built application files 19 | *.apk 20 | *.ap_ 21 | 22 | # Files for the ART/Dalvik VM 23 | *.dex 24 | 25 | # Java class files 26 | *.class 27 | 28 | # Generated files 29 | bin 30 | gen 31 | out 32 | 33 | # Gradle files 34 | .gradle 35 | build 36 | 37 | # Local configuration file (sdk path, etc) 38 | local.properties 39 | 40 | # Proguard folder generated by Eclipse 41 | proguard 42 | 43 | # Log Files 44 | *.log 45 | 46 | # Android Studio Navigation editor temp files 47 | .navigation 48 | 49 | # Android Studio captures folder 50 | captures 51 | 52 | # IntelliJ 53 | *.iml 54 | .idea 55 | 56 | # VSCode 57 | .vscode 58 | 59 | # Keystore files 60 | # Uncomment the following line if you do not want to check your keystore files in. 61 | #*.jks 62 | 63 | # External native build folder generated in Android Studio 2.2 and later 64 | .externalNativeBuild 65 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | save-exact=true 2 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. 4 | 5 | ## [6.0.0](https://github.com/capacitor-community/text-to-speech/compare/v5.1.0...v6.0.0) (2025-01-28) 6 | 7 | 8 | ### ⚠ BREAKING CHANGES 9 | 10 | * update to Capacitor 7 (#142) 11 | 12 | ### Features 13 | 14 | * update to Capacitor 7 ([#142](https://github.com/capacitor-community/text-to-speech/issues/142)) ([f50b0c1](https://github.com/capacitor-community/text-to-speech/commit/f50b0c145dbf066c2c2cc6c36a1b0db24c07a08d)) 15 | 16 | ## [5.1.0](https://github.com/capacitor-community/text-to-speech/compare/v5.0.0...v5.1.0) (2024-11-13) 17 | 18 | 19 | ### Features 20 | 21 | * add `queueStrategy` option ([#139](https://github.com/capacitor-community/text-to-speech/issues/139)) ([cb3e92c](https://github.com/capacitor-community/text-to-speech/commit/cb3e92cc04eb0fff6105bb342450fbc763a62c7a)) 22 | 23 | ## [5.0.0](https://github.com/capacitor-community/text-to-speech/compare/v4.1.1...v5.0.0) (2024-09-25) 24 | 25 | 26 | ### ⚠ BREAKING CHANGES 27 | 28 | * **ios:** The speech rate now has a linear behavior. 29 | 30 | ### Bug Fixes 31 | 32 | * **ios:** linear rate adjustment ([#136](https://github.com/capacitor-community/text-to-speech/issues/136)) ([e573114](https://github.com/capacitor-community/text-to-speech/commit/e5731149ed2177538c988b5379160fba5d6212cc)) 33 | 34 | ### [4.1.1](https://github.com/capacitor-community/text-to-speech/compare/v4.1.0...v4.1.1) (2024-09-21) 35 | 36 | 37 | ### Bug Fixes 38 | 39 | * **ios:** prevent UI hangs by optimizing AVAudioSession management ([#135](https://github.com/capacitor-community/text-to-speech/issues/135)) ([aaa4ace](https://github.com/capacitor-community/text-to-speech/commit/aaa4acef7d93b4e3e3a2744a59ad1cedfa77f44f)) 40 | 41 | ## [4.1.0](https://github.com/capacitor-community/text-to-speech/compare/v4.0.2...v4.1.0) (2024-07-17) 42 | 43 | 44 | ### Features 45 | 46 | * add `onRangeStart` listener ([#132](https://github.com/capacitor-community/text-to-speech/issues/132)) ([d6681a6](https://github.com/capacitor-community/text-to-speech/commit/d6681a64bbdc296530fdd14cf02ba9f286efa405)) 47 | 48 | ### [4.0.2](https://github.com/capacitor-community/text-to-speech/compare/v4.0.1...v4.0.2) (2024-06-22) 49 | 50 | 51 | ### Bug Fixes 52 | 53 | * **android:** `getSupportedVoices()` is not available for api levels < 24 ([#124](https://github.com/capacitor-community/text-to-speech/issues/124)) ([0ccace8](https://github.com/capacitor-community/text-to-speech/commit/0ccace825ce67ec75495b202a9fc505bc0c64ce4)) 54 | * **android:** add queries in the AndroidManifest.xml ([#121](https://github.com/capacitor-community/text-to-speech/issues/121)) ([8c3594b](https://github.com/capacitor-community/text-to-speech/commit/8c3594b1df4b6185a472ef2e9e59a1ba3a21a981)) 55 | 56 | ### [4.0.1](https://github.com/capacitor-community/text-to-speech/compare/v4.0.0...v4.0.1) (2024-04-29) 57 | 58 | ### Bug Fixes 59 | 60 | * **android:** comparison method violates its general contract ([ada706e](https://github.com/capacitor-community/text-to-speech/commit/ada706ed26ea99e8d3a6bc66d509b0528a6203c3)) 61 | 62 | ## [4.0.0](https://github.com/capacitor-community/text-to-speech/compare/v3.0.1...v4.0.0) (2024-04-16) 63 | 64 | 65 | ### ⚠ BREAKING CHANGES 66 | 67 | * This plugin now only supports Capacitor 6. 68 | 69 | ### Features 70 | 71 | * update to Capacitor 6 ([#113](https://github.com/capacitor-community/text-to-speech/issues/113)) ([b4c1942](https://github.com/capacitor-community/text-to-speech/commit/b4c1942e7de661d6e79cf6e5f74fbaddb0c94119)) 72 | 73 | ### [3.0.1](https://github.com/capacitor-community/text-to-speech/compare/v3.0.0...v3.0.1) (2023-08-02) 74 | 75 | 76 | ### Bug Fixes 77 | 78 | * **android:** `java.lang.NoClassDefFoundError` error ([#109](https://github.com/capacitor-community/text-to-speech/issues/109)) ([a3d2add](https://github.com/capacitor-community/text-to-speech/commit/a3d2add74e233fbfb09aaeb11e65a2c0e830630e)) 79 | 80 | ## [3.0.0](https://github.com/capacitor-community/text-to-speech/compare/v2.1.0...v3.0.0) (2023-05-04) 81 | 82 | 83 | ### ⚠ BREAKING CHANGES 84 | 85 | * update to Capacitor 5 (#99) 86 | 87 | ### Features 88 | 89 | * update gradle to 8.0.2 and gradle plugin to 8.0.0 ([be75995](https://github.com/capacitor-community/text-to-speech/commit/be7599547964f8520eb4a4108ac4702baf805ce7)) 90 | * update to Capacitor 5 ([#99](https://github.com/capacitor-community/text-to-speech/issues/99)) ([7ca0ec4](https://github.com/capacitor-community/text-to-speech/commit/7ca0ec46ad3dc86710a22fca672e7bb4ab5eee42)) 91 | 92 | ## [2.1.0](https://github.com/capacitor-community/text-to-speech/compare/v2.0.1...v2.1.0) (2023-03-09) 93 | 94 | 95 | ### Features 96 | 97 | * add support for `getSupportedVoices` on iOS ([b13d5a7](https://github.com/capacitor-community/text-to-speech/commit/b13d5a7dc7956e77e5c11b43480901bec544001d)) 98 | * **android:** support the `voice` property ([#90](https://github.com/capacitor-community/text-to-speech/issues/90)) ([1d8865f](https://github.com/capacitor-community/text-to-speech/commit/1d8865f3dbe1ab86a840ff46a8b643e08fdd79ea)) 99 | * **ios:** support the `voice` property ([#93](https://github.com/capacitor-community/text-to-speech/issues/93)) ([8b97820](https://github.com/capacitor-community/text-to-speech/commit/8b9782066dd0da9336bf9cd69698b602ad867659)) 100 | 101 | ### [2.0.1](https://github.com/capacitor-community/text-to-speech/compare/v2.0.0...v2.0.1) (2022-08-04) 102 | 103 | 104 | ### Bug Fixes 105 | 106 | * **ios:** set deployment target to `13.0` ([dcda399](https://github.com/capacitor-community/text-to-speech/commit/dcda399a01d3fdc04eedbdfab4fa49064cba81b7)) 107 | 108 | ## [2.0.0](https://github.com/capacitor-community/text-to-speech/compare/v1.1.3...v2.0.0) (2022-08-04) 109 | 110 | 111 | ### ⚠ BREAKING CHANGES 112 | 113 | * This plugin now only supports Capacitor 4. 114 | 115 | ### Features 116 | 117 | * update to Capacitor 4 ([#81](https://github.com/capacitor-community/text-to-speech/issues/81)) ([e108ff3](https://github.com/capacitor-community/text-to-speech/commit/e108ff360ff7735b089321ff637238c22e580df0)) 118 | 119 | ### [1.1.3](https://github.com/capacitor-community/text-to-speech/compare/v1.1.2...v1.1.3) (2022-04-04) 120 | 121 | 122 | ### Bug Fixes 123 | 124 | * **web:** stop on window unload ([#79](https://github.com/capacitor-community/text-to-speech/issues/79)) ([22f4bd7](https://github.com/capacitor-community/text-to-speech/commit/22f4bd7c1e9c2e6ce745454e46b9cd16ef2c3c55)) 125 | 126 | ### [1.1.2](https://github.com/capacitor-community/text-to-speech/compare/v1.1.1...v1.1.2) (2022-01-26) 127 | 128 | 129 | ### Bug Fixes 130 | 131 | * inline source code in esm map files ([29a1aa8](https://github.com/capacitor-community/text-to-speech/commit/29a1aa8d5256ec1fcd168a0802b616fbd8935a58)) 132 | 133 | ### [1.1.1](https://github.com/capacitor-community/text-to-speech/compare/v1.1.0...v1.1.1) (2021-05-04) 134 | 135 | 136 | ### Bug Fixes 137 | 138 | * **android:** handle IETF BCP 47 language tag string correctly ([#66](https://github.com/capacitor-community/text-to-speech/issues/66)) ([296adcb](https://github.com/capacitor-community/text-to-speech/commit/296adcba96641de60aea7cc74c292f2fdd3f7ada)) 139 | 140 | ## [1.1.0](https://github.com/capacitor-community/text-to-speech/compare/v1.0.0...v1.1.0) (2021-03-20) 141 | 142 | 143 | ### Features 144 | 145 | * add `isLanguageSupported` method ([#58](https://github.com/capacitor-community/text-to-speech/issues/58)) ([eb39f93](https://github.com/capacitor-community/text-to-speech/commit/eb39f93e31c0ec008a0058b32464b4f43471b322)) 146 | 147 | 148 | ### Bug Fixes 149 | 150 | * **android:** shutdown tts on destroy ([#56](https://github.com/capacitor-community/text-to-speech/issues/56)) ([2b6da17](https://github.com/capacitor-community/text-to-speech/commit/2b6da17f4f73c9d4443a2cdd325a5f918dd04bc8)) 151 | * **ios:** `speak` method resolves immediately on iOS ([#59](https://github.com/capacitor-community/text-to-speech/issues/59)) ([e77e8ba](https://github.com/capacitor-community/text-to-speech/commit/e77e8baf459d197798f16e5034288677d5e41cb1)) 152 | 153 | ## [1.0.0](https://github.com/capacitor-community/text-to-speech/compare/v0.2.3...v1.0.0) (2021-03-14) 154 | 155 | 156 | ### ⚠ BREAKING CHANGES 157 | 158 | * `TTSOptions` properties `locale`, `speechRate`, `pitchRate` renamed to `lang`, `rate`, `pitch`. 159 | * remove `setSpeechRate` and `setPitchRate` 160 | * Update to Capacitor v3 161 | 162 | ### Features 163 | 164 | * add Capacitor 3 support ([#47](https://github.com/capacitor-community/text-to-speech/issues/47)) ([912a914](https://github.com/capacitor-community/text-to-speech/commit/912a91455f100aa430d1a108090fb7f8ff2bc8e9)) 165 | 166 | 167 | * remove `setSpeechRate` and `setPitchRate` ([#49](https://github.com/capacitor-community/text-to-speech/issues/49)) ([c4bd0a8](https://github.com/capacitor-community/text-to-speech/commit/c4bd0a85197921f23fd43f3d4a9f6bd2d998183a)) 168 | * rename `TTSOptions` properties ([#51](https://github.com/capacitor-community/text-to-speech/issues/51)) ([83c4370](https://github.com/capacitor-community/text-to-speech/commit/83c43708165f5365158eb05eb22e5f69f15b7bef)) 169 | 170 | ### [0.2.3](https://github.com/capacitor-community/text-to-speech/compare/v0.2.2...v0.2.3) (2021-03-12) 171 | 172 | 173 | ### Bug Fixes 174 | 175 | * **ios:** pod failed to validate ([#46](https://github.com/capacitor-community/text-to-speech/issues/46)) ([6a83100](https://github.com/capacitor-community/text-to-speech/commit/6a831003d3c29f9fa6a46dc27e20267246b3ec1a)) 176 | 177 | ### [0.2.2](https://github.com/capacitor-community/text-to-speech/compare/v0.2.1...v0.2.2) (2021-03-11) 178 | 179 | 180 | ### Bug Fixes 181 | 182 | * **android:** `speechRate` and `pitchRate` are ignored ([#43](https://github.com/capacitor-community/text-to-speech/issues/43)) ([153a500](https://github.com/capacitor-community/text-to-speech/commit/153a500aef2245de61885ce282f7d5111f28b803)) 183 | * **android:** get supported languages ([#29](https://github.com/capacitor-community/text-to-speech/issues/29)) ([bf477aa](https://github.com/capacitor-community/text-to-speech/commit/bf477aab9f713413e8b418d809de26a0482524b0)) 184 | * **android:** get supported voices ([#31](https://github.com/capacitor-community/text-to-speech/issues/31)) ([0870389](https://github.com/capacitor-community/text-to-speech/commit/087038989a6ba77bcce14506b89172046f754ee7)) 185 | * **ios:** not working in background ([#35](https://github.com/capacitor-community/text-to-speech/issues/35)) ([63108ab](https://github.com/capacitor-community/text-to-speech/commit/63108abb6b35ffabb5d04ef9a720267ddad2f33b)) 186 | * **ios:** speech rate adjusted to other platforms ([#36](https://github.com/capacitor-community/text-to-speech/issues/36)) ([d33dceb](https://github.com/capacitor-community/text-to-speech/commit/d33dceb2ed132616a1aaa7b40177ea1d7c6321c3)) 187 | * **web:** stop speaking on a new call ([#44](https://github.com/capacitor-community/text-to-speech/issues/44)) ([6b1b83e](https://github.com/capacitor-community/text-to-speech/commit/6b1b83e28191b1882bc50f2e103f766c4e5182df)) 188 | * different behavior with blank `text` ([#39](https://github.com/capacitor-community/text-to-speech/issues/39)) ([527a51f](https://github.com/capacitor-community/text-to-speech/commit/527a51f7cf6cbc4debec5f239f4479488554d494)) 189 | * publish only necessary files ([#23](https://github.com/capacitor-community/text-to-speech/issues/23)) ([359f2d2](https://github.com/capacitor-community/text-to-speech/commit/359f2d203abff1890369bd10a31668ea202a5ae3)) 190 | 191 | ## [0.2.0](https://github.com/capacitor-community/text-to-speech/compare/v0.1.3...v0.2.0) (2020-06-30) 192 | 193 | ### Bug Fixes 194 | 195 | - Merge pull request #3 from MarnickvdA/master [f84aef3](https://github.com/capacitor-community/text-to-speech/commit/f84aef3f25ebfa402b5b0de7006fe7fda7f2e47b) 196 | 197 | - Fix for issue #2 'Cant run on iOS'. Changed name of the PodSpec file. Also added JSON parser for the package.json file so you won't have to update the plugin information in two places ;) [dbc97c3](https://github.com/capacitor-community/text-to-speech/commit/dbc97c3e8c44e62b1cff9b3cf3b40d1141c58915) 198 | 199 | ### Features 200 | 201 | ### Docs 202 | 203 | - docs: update header and fix minor typo to speak method [99861e6](https://github.com/capacitor-community/text-to-speech/commit/99861e6b41609369db978060e398524dd04f4530) 204 | 205 | - docs: rename `rate` to `speechRate` and `pitch` to `pitchRate` [99861e6](https://github.com/capacitor-community/text-to-speech/commit/99861e6b41609369db978060e398524dd04f4530) 206 | 207 | ### Chores 208 | 209 | - chore: add changelog [b83f97a](https://github.com/capacitor-community/text-to-speech/commit/b83f97aef2413b174d0cc4b423ab9cf54ab9d4fd) 210 | - chore(ios): add volume option [eef1b00](https://github.com/capacitor-community/text-to-speech/commit/eef1b00a54c3e1570ed6a52bc83b556d4c9930ea) 211 | - chore: add homepage property to package [2aad4f4](https://github.com/capacitor-community/text-to-speech/commit/2aad4f43d47fe9d5f776b9ea672f34ac08d81762) 212 | -------------------------------------------------------------------------------- /CONTRIBUTING.md: -------------------------------------------------------------------------------- 1 | # Contributing 2 | 3 | This guide provides instructions for contributing to this Capacitor plugin. 4 | 5 | ## Developing 6 | 7 | ### Local Setup 8 | 9 | 1. Fork and clone the repo. 10 | 1. Install the dependencies. 11 | 12 | ```shell 13 | npm install 14 | ``` 15 | 16 | 1. Install SwiftLint if you're on macOS. 17 | 18 | ```shell 19 | brew install swiftlint 20 | ``` 21 | 22 | ### Scripts 23 | 24 | #### `npm run build` 25 | 26 | Build the plugin web assets and generate plugin API documentation using [`@capacitor/docgen`](https://github.com/ionic-team/capacitor-docgen). 27 | 28 | It will compile the TypeScript code from `src/` into ESM JavaScript in `dist/esm/`. These files are used in apps with bundlers when your plugin is imported. 29 | 30 | Then, Rollup will bundle the code into a single file at `dist/plugin.js`. This file is used in apps without bundlers by including it as a script in `index.html`. 31 | 32 | #### `npm run verify` 33 | 34 | Build and validate the web and native projects. 35 | 36 | This is useful to run in CI to verify that the plugin builds for all platforms. 37 | 38 | #### `npm run lint` / `npm run fmt` 39 | 40 | Check formatting and code quality, autoformat/autofix if possible. 41 | 42 | This template is integrated with ESLint, Prettier, and SwiftLint. Using these tools is completely optional, but the [Capacitor Community](https://github.com/capacitor-community/) strives to have consistent code style and structure for easier cooperation. 43 | 44 | ## Versioning 45 | 46 | Don't change the plugin version manually, the version change is automated with `standard-version` package. 47 | 48 | ## Publishing 49 | 50 | First run: 51 | 52 | ```shell 53 | npm run release 54 | ``` 55 | 56 | That will update the plugin version and update the `CHANGELOG.md` file with latest changes. Then it will ask you to run: 57 | 58 | ```shell 59 | git push --follow-tags origin master && npm publish 60 | ``` 61 | 62 | That creates a tag on gitbhub and publishes the package on npm. 63 | 64 | Go to the [github tags section](https://github.com/capacitor-community/text-to-speech/tags), pick the latest tag and create a release for it. 65 | 66 | > **Note**: The [`files`](https://docs.npmjs.com/cli/v7/configuring-npm/package-json#files) array in `package.json` specifies which files get published. If you rename files/directories or add files elsewhere, you may need to update it. 67 | -------------------------------------------------------------------------------- /CapacitorCommunityTextToSpeech.podspec: -------------------------------------------------------------------------------- 1 | require 'json' 2 | 3 | package = JSON.parse(File.read(File.join(__dir__, 'package.json'))) 4 | 5 | Pod::Spec.new do |s| 6 | s.name = 'CapacitorCommunityTextToSpeech' 7 | s.version = package['version'] 8 | s.summary = package['description'] 9 | s.license = package['license'] 10 | s.homepage = package['repository']['url'] 11 | s.author = package['author'] 12 | s.source = { :git => package['repository']['url'], :tag => s.version.to_s } 13 | s.source_files = 'ios/Plugin/**/*.{swift,h,m,c,cc,mm,cpp}' 14 | s.ios.deployment_target = '14.0' 15 | s.dependency 'Capacitor' 16 | s.swift_version = '5.1' 17 | end 18 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 Robin Genz 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 |


2 |

Text to Speech

3 |

@capacitor-community/text-to-speech

4 |

5 | Capacitor community plugin for synthesizing speech from text. 6 |

7 | 8 |

9 | 10 | 11 | 12 |
13 | 14 | 15 | 16 | 17 | 18 |

19 | 20 | ## Maintainers 21 | 22 | | Maintainer | GitHub | Social | 23 | | ---------- | ----------------------------------------- | --------------------------------------------- | 24 | | Robin Genz | [robingenz](https://github.com/robingenz) | [@robin_genz](https://twitter.com/robin_genz) | 25 | 26 | ## Installation 27 | 28 | ``` 29 | npm install @capacitor-community/text-to-speech 30 | npx cap sync 31 | ``` 32 | 33 | ## Configuration 34 | 35 | No configuration required for this plugin. 36 | 37 | ## Demo 38 | 39 | A working example can be found here: [robingenz/capacitor-plugin-demo](https://github.com/robingenz/capacitor-plugin-demo) 40 | 41 | ## Usage 42 | 43 | ```typescript 44 | import { TextToSpeech } from '@capacitor-community/text-to-speech'; 45 | 46 | const speak = async () => { 47 | await TextToSpeech.speak({ 48 | text: 'This is a sample text.', 49 | lang: 'en-US', 50 | rate: 1.0, 51 | pitch: 1.0, 52 | volume: 1.0, 53 | category: 'ambient', 54 | queueStrategy: 1 55 | }); 56 | }; 57 | 58 | const stop = async () => { 59 | await TextToSpeech.stop(); 60 | }; 61 | 62 | const getSupportedLanguages = async () => { 63 | const languages = await TextToSpeech.getSupportedLanguages(); 64 | }; 65 | 66 | const getSupportedVoices = async () => { 67 | const voices = await TextToSpeech.getSupportedVoices(); 68 | }; 69 | 70 | const isLanguageSupported = async (lang: string) => { 71 | const isSupported = await TextToSpeech.isLanguageSupported({ lang }); 72 | }; 73 | ``` 74 | 75 | ## API 76 | 77 | 78 | 79 | * [`speak(...)`](#speak) 80 | * [`stop()`](#stop) 81 | * [`getSupportedLanguages()`](#getsupportedlanguages) 82 | * [`getSupportedVoices()`](#getsupportedvoices) 83 | * [`isLanguageSupported(...)`](#islanguagesupported) 84 | * [`openInstall()`](#openinstall) 85 | * [`addListener('onRangeStart', ...)`](#addlisteneronrangestart-) 86 | * [Interfaces](#interfaces) 87 | * [Enums](#enums) 88 | 89 | 90 | 91 | 92 | 93 | 94 | ### speak(...) 95 | 96 | ```typescript 97 | speak(options: TTSOptions) => Promise 98 | ``` 99 | 100 | Starts the TTS engine and plays the desired text. 101 | 102 | | Param | Type | 103 | | ------------- | ------------------------------------------------- | 104 | | **`options`** | TTSOptions | 105 | 106 | -------------------- 107 | 108 | 109 | ### stop() 110 | 111 | ```typescript 112 | stop() => Promise 113 | ``` 114 | 115 | Stops the TTS engine. 116 | 117 | -------------------- 118 | 119 | 120 | ### getSupportedLanguages() 121 | 122 | ```typescript 123 | getSupportedLanguages() => Promise<{ languages: string[]; }> 124 | ``` 125 | 126 | Returns a list of supported BCP 47 language tags. 127 | 128 | **Returns:** Promise<{ languages: string[]; }> 129 | 130 | -------------------- 131 | 132 | 133 | ### getSupportedVoices() 134 | 135 | ```typescript 136 | getSupportedVoices() => Promise<{ voices: SpeechSynthesisVoice[]; }> 137 | ``` 138 | 139 | Returns a list of supported voices. 140 | 141 | **Returns:** Promise<{ voices: SpeechSynthesisVoice[]; }> 142 | 143 | -------------------- 144 | 145 | 146 | ### isLanguageSupported(...) 147 | 148 | ```typescript 149 | isLanguageSupported(options: { lang: string; }) => Promise<{ supported: boolean; }> 150 | ``` 151 | 152 | Checks if a specific BCP 47 language tag is supported. 153 | 154 | | Param | Type | 155 | | ------------- | ------------------------------ | 156 | | **`options`** | { lang: string; } | 157 | 158 | **Returns:** Promise<{ supported: boolean; }> 159 | 160 | -------------------- 161 | 162 | 163 | ### openInstall() 164 | 165 | ```typescript 166 | openInstall() => Promise 167 | ``` 168 | 169 | Verifies proper installation and availability of resource files on the system. 170 | 171 | Only available for Android. 172 | 173 | -------------------- 174 | 175 | 176 | ### addListener('onRangeStart', ...) 177 | 178 | ```typescript 179 | addListener(eventName: 'onRangeStart', listenerFunc: (info: { start: number; end: number; spokenWord: string; }) => void) => Promise 180 | ``` 181 | 182 | | Param | Type | 183 | | ------------------ | ----------------------------------------------------------------------------------- | 184 | | **`eventName`** | 'onRangeStart' | 185 | | **`listenerFunc`** | (info: { start: number; end: number; spokenWord: string; }) => void | 186 | 187 | **Returns:** Promise<PluginListenerHandle> 188 | 189 | -------------------- 190 | 191 | 192 | ### Interfaces 193 | 194 | 195 | #### TTSOptions 196 | 197 | | Prop | Type | Description | Default | Since | 198 | | ------------------- | ------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | -------------------------------- | ----- | 199 | | **`text`** | string | The text that will be synthesised when the utterance is spoken. | | | 200 | | **`lang`** | string | The language of the utterance. Possible languages can be queried using `getSupportedLanguages`. | "en-US" | | 201 | | **`rate`** | number | The speed at which the utterance will be spoken at. | 1.0 | | 202 | | **`pitch`** | number | The pitch at which the utterance will be spoken at. | 1.0 | | 203 | | **`volume`** | number | The volume that the utterance will be spoken at. | 1.0 | | 204 | | **`voice`** | number | The index of the selected voice that will be used to speak the utterance. Possible voices can be queried using `getSupportedVoices`. | | | 205 | | **`category`** | string | Select the iOS Audio session category. Possible values: `ambient` and `playback`. Use `playback` to play audio even when the app is in the background. Only available for iOS. | "ambient" | | 206 | | **`queueStrategy`** | QueueStrategy | Select the strategy to adopt when several requests to speak overlap. | QueueStrategy.Flush | 5.1.0 | 207 | 208 | 209 | #### SpeechSynthesisVoice 210 | 211 | The SpeechSynthesisVoice interface represents a voice that the system supports. 212 | 213 | | Prop | Type | Description | 214 | | ------------------ | -------------------- | ----------------------------------------------------------------------------------------------------------- | 215 | | **`default`** | boolean | Specifies whether the voice is the default voice for the current app (`true`) or not (`false`). | 216 | | **`lang`** | string | BCP 47 language tag indicating the language of the voice. | 217 | | **`localService`** | boolean | Specifies whether the voice is supplied by a local (`true`) or remote (`false`) speech synthesizer service. | 218 | | **`name`** | string | Human-readable name that represents the voice. | 219 | | **`voiceURI`** | string | Type of URI and location of the speech synthesis service for this voice. | 220 | 221 | 222 | #### PluginListenerHandle 223 | 224 | | Prop | Type | 225 | | ------------ | ----------------------------------------- | 226 | | **`remove`** | () => Promise<void> | 227 | 228 | 229 | ### Enums 230 | 231 | 232 | #### QueueStrategy 233 | 234 | | Members | Value | Description | 235 | | ----------- | -------------- | -------------------------------------------------------------------------------------------------------------------- | 236 | | **`Flush`** | 0 | Use `Flush` to stop the current request when a new request is sent. | 237 | | **`Add`** | 1 | Use `Add` to buffer the speech request. The request will be executed when all previous requests have been completed. | 238 | 239 | 240 | 241 | ## Changelog 242 | 243 | See [CHANGELOG.md](https://github.com/capacitor-community/text-to-speech/blob/master/CHANGELOG.md). 244 | 245 | ## License 246 | 247 | See [LICENSE](https://github.com/capacitor-community/text-to-speech/blob/master/LICENSE). 248 | -------------------------------------------------------------------------------- /android/.gitignore: -------------------------------------------------------------------------------- 1 | /build 2 | -------------------------------------------------------------------------------- /android/build.gradle: -------------------------------------------------------------------------------- 1 | ext { 2 | junitVersion = project.hasProperty('junitVersion') ? rootProject.ext.junitVersion : '4.13.2' 3 | androidxAppCompatVersion = project.hasProperty('androidxAppCompatVersion') ? rootProject.ext.androidxAppCompatVersion : '1.7.0' 4 | androidxJunitVersion = project.hasProperty('androidxJunitVersion') ? rootProject.ext.androidxJunitVersion : '1.2.1' 5 | androidxEspressoCoreVersion = project.hasProperty('androidxEspressoCoreVersion') ? rootProject.ext.androidxEspressoCoreVersion : '3.6.1' 6 | } 7 | 8 | buildscript { 9 | repositories { 10 | google() 11 | mavenCentral() 12 | } 13 | dependencies { 14 | classpath 'com.android.tools.build:gradle:8.2.1' 15 | } 16 | } 17 | 18 | apply plugin: 'com.android.library' 19 | 20 | android { 21 | namespace "com.getcapacitor.community.tts" 22 | compileSdk project.hasProperty('compileSdkVersion') ? rootProject.ext.compileSdkVersion : 35 23 | defaultConfig { 24 | minSdkVersion project.hasProperty('minSdkVersion') ? rootProject.ext.minSdkVersion : 23 25 | targetSdkVersion project.hasProperty('targetSdkVersion') ? rootProject.ext.targetSdkVersion : 35 26 | versionCode 1 27 | versionName "1.0" 28 | testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" 29 | } 30 | buildTypes { 31 | release { 32 | minifyEnabled false 33 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' 34 | } 35 | } 36 | lintOptions { 37 | abortOnError false 38 | } 39 | compileOptions { 40 | sourceCompatibility JavaVersion.VERSION_21 41 | targetCompatibility JavaVersion.VERSION_21 42 | } 43 | } 44 | 45 | repositories { 46 | google() 47 | mavenCentral() 48 | } 49 | 50 | 51 | dependencies { 52 | implementation fileTree(dir: 'libs', include: ['*.jar']) 53 | implementation project(':capacitor-android') 54 | implementation "androidx.appcompat:appcompat:$androidxAppCompatVersion" 55 | testImplementation "junit:junit:$junitVersion" 56 | androidTestImplementation "androidx.test.ext:junit:$androidxJunitVersion" 57 | androidTestImplementation "androidx.test.espresso:espresso-core:$androidxEspressoCoreVersion" 58 | } 59 | -------------------------------------------------------------------------------- /android/gradle.properties: -------------------------------------------------------------------------------- 1 | # Project-wide Gradle settings. 2 | 3 | # IDE (e.g. Android Studio) users: 4 | # Gradle settings configured through the IDE *will override* 5 | # any settings specified in this file. 6 | 7 | # For more details on how to configure your build environment visit 8 | # http://www.gradle.org/docs/current/userguide/build_environment.html 9 | 10 | # Specifies the JVM arguments used for the daemon process. 11 | # The setting is particularly useful for tweaking memory settings. 12 | org.gradle.jvmargs=-Xmx1536m 13 | 14 | # When configured, Gradle will run in incubating parallel mode. 15 | # This option should only be used with decoupled projects. More details, visit 16 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects 17 | # org.gradle.parallel=true 18 | 19 | # AndroidX package structure to make it clearer which packages are bundled with the 20 | # Android operating system, and which are packaged with your app's APK 21 | # https://developer.android.com/topic/libraries/support-library/androidx-rn 22 | android.useAndroidX=true 23 | -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capacitor-community/text-to-speech/386d252fdeaa5072b12e772c8499507a60337f73/android/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /android/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-all.zip 4 | networkTimeout=10000 5 | validateDistributionUrl=true 6 | zipStoreBase=GRADLE_USER_HOME 7 | zipStorePath=wrapper/dists 8 | -------------------------------------------------------------------------------- /android/gradlew: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | 3 | # 4 | # Copyright © 2015-2021 the original authors. 5 | # 6 | # Licensed under the Apache License, Version 2.0 (the "License"); 7 | # you may not use this file except in compliance with the License. 8 | # You may obtain a copy of the License at 9 | # 10 | # https://www.apache.org/licenses/LICENSE-2.0 11 | # 12 | # Unless required by applicable law or agreed to in writing, software 13 | # distributed under the License is distributed on an "AS IS" BASIS, 14 | # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 15 | # See the License for the specific language governing permissions and 16 | # limitations under the License. 17 | # 18 | # SPDX-License-Identifier: Apache-2.0 19 | # 20 | 21 | ############################################################################## 22 | # 23 | # Gradle start up script for POSIX generated by Gradle. 24 | # 25 | # Important for running: 26 | # 27 | # (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is 28 | # noncompliant, but you have some other compliant shell such as ksh or 29 | # bash, then to run this script, type that shell name before the whole 30 | # command line, like: 31 | # 32 | # ksh Gradle 33 | # 34 | # Busybox and similar reduced shells will NOT work, because this script 35 | # requires all of these POSIX shell features: 36 | # * functions; 37 | # * expansions «$var», «${var}», «${var:-default}», «${var+SET}», 38 | # «${var#prefix}», «${var%suffix}», and «$( cmd )»; 39 | # * compound commands having a testable exit status, especially «case»; 40 | # * various built-in commands including «command», «set», and «ulimit». 41 | # 42 | # Important for patching: 43 | # 44 | # (2) This script targets any POSIX shell, so it avoids extensions provided 45 | # by Bash, Ksh, etc; in particular arrays are avoided. 46 | # 47 | # The "traditional" practice of packing multiple parameters into a 48 | # space-separated string is a well documented source of bugs and security 49 | # problems, so this is (mostly) avoided, by progressively accumulating 50 | # options in "$@", and eventually passing that to Java. 51 | # 52 | # Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, 53 | # and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; 54 | # see the in-line comments for details. 55 | # 56 | # There are tweaks for specific operating systems such as AIX, CygWin, 57 | # Darwin, MinGW, and NonStop. 58 | # 59 | # (3) This script is generated from the Groovy template 60 | # https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt 61 | # within the Gradle project. 62 | # 63 | # You can find Gradle at https://github.com/gradle/gradle/. 64 | # 65 | ############################################################################## 66 | 67 | # Attempt to set APP_HOME 68 | 69 | # Resolve links: $0 may be a link 70 | app_path=$0 71 | 72 | # Need this for daisy-chained symlinks. 73 | while 74 | APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path 75 | [ -h "$app_path" ] 76 | do 77 | ls=$( ls -ld "$app_path" ) 78 | link=${ls#*' -> '} 79 | case $link in #( 80 | /*) app_path=$link ;; #( 81 | *) app_path=$APP_HOME$link ;; 82 | esac 83 | done 84 | 85 | # This is normally unused 86 | # shellcheck disable=SC2034 87 | APP_BASE_NAME=${0##*/} 88 | # Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) 89 | APP_HOME=$( cd -P "${APP_HOME:-./}" > /dev/null && printf '%s 90 | ' "$PWD" ) || exit 91 | 92 | # Use the maximum available, or set MAX_FD != -1 to use that value. 93 | MAX_FD=maximum 94 | 95 | warn () { 96 | echo "$*" 97 | } >&2 98 | 99 | die () { 100 | echo 101 | echo "$*" 102 | echo 103 | exit 1 104 | } >&2 105 | 106 | # OS specific support (must be 'true' or 'false'). 107 | cygwin=false 108 | msys=false 109 | darwin=false 110 | nonstop=false 111 | case "$( uname )" in #( 112 | CYGWIN* ) cygwin=true ;; #( 113 | Darwin* ) darwin=true ;; #( 114 | MSYS* | MINGW* ) msys=true ;; #( 115 | NONSTOP* ) nonstop=true ;; 116 | esac 117 | 118 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar 119 | 120 | 121 | # Determine the Java command to use to start the JVM. 122 | if [ -n "$JAVA_HOME" ] ; then 123 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then 124 | # IBM's JDK on AIX uses strange locations for the executables 125 | JAVACMD=$JAVA_HOME/jre/sh/java 126 | else 127 | JAVACMD=$JAVA_HOME/bin/java 128 | fi 129 | if [ ! -x "$JAVACMD" ] ; then 130 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME 131 | 132 | Please set the JAVA_HOME variable in your environment to match the 133 | location of your Java installation." 134 | fi 135 | else 136 | JAVACMD=java 137 | if ! command -v java >/dev/null 2>&1 138 | then 139 | die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 140 | 141 | Please set the JAVA_HOME variable in your environment to match the 142 | location of your Java installation." 143 | fi 144 | fi 145 | 146 | # Increase the maximum file descriptors if we can. 147 | if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then 148 | case $MAX_FD in #( 149 | max*) 150 | # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. 151 | # shellcheck disable=SC2039,SC3045 152 | MAX_FD=$( ulimit -H -n ) || 153 | warn "Could not query maximum file descriptor limit" 154 | esac 155 | case $MAX_FD in #( 156 | '' | soft) :;; #( 157 | *) 158 | # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. 159 | # shellcheck disable=SC2039,SC3045 160 | ulimit -n "$MAX_FD" || 161 | warn "Could not set maximum file descriptor limit to $MAX_FD" 162 | esac 163 | fi 164 | 165 | # Collect all arguments for the java command, stacking in reverse order: 166 | # * args from the command line 167 | # * the main class name 168 | # * -classpath 169 | # * -D...appname settings 170 | # * --module-path (only if needed) 171 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. 172 | 173 | # For Cygwin or MSYS, switch paths to Windows format before running java 174 | if "$cygwin" || "$msys" ; then 175 | APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) 176 | CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) 177 | 178 | JAVACMD=$( cygpath --unix "$JAVACMD" ) 179 | 180 | # Now convert the arguments - kludge to limit ourselves to /bin/sh 181 | for arg do 182 | if 183 | case $arg in #( 184 | -*) false ;; # don't mess with options #( 185 | /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath 186 | [ -e "$t" ] ;; #( 187 | *) false ;; 188 | esac 189 | then 190 | arg=$( cygpath --path --ignore --mixed "$arg" ) 191 | fi 192 | # Roll the args list around exactly as many times as the number of 193 | # args, so each arg winds up back in the position where it started, but 194 | # possibly modified. 195 | # 196 | # NB: a `for` loop captures its iteration list before it begins, so 197 | # changing the positional parameters here affects neither the number of 198 | # iterations, nor the values presented in `arg`. 199 | shift # remove old arg 200 | set -- "$@" "$arg" # push replacement arg 201 | done 202 | fi 203 | 204 | 205 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 206 | DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' 207 | 208 | # Collect all arguments for the java command: 209 | # * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, 210 | # and any embedded shellness will be escaped. 211 | # * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be 212 | # treated as '${Hostname}' itself on the command line. 213 | 214 | set -- \ 215 | "-Dorg.gradle.appname=$APP_BASE_NAME" \ 216 | -classpath "$CLASSPATH" \ 217 | org.gradle.wrapper.GradleWrapperMain \ 218 | "$@" 219 | 220 | # Stop when "xargs" is not available. 221 | if ! command -v xargs >/dev/null 2>&1 222 | then 223 | die "xargs is not available" 224 | fi 225 | 226 | # Use "xargs" to parse quoted args. 227 | # 228 | # With -n1 it outputs one arg per line, with the quotes and backslashes removed. 229 | # 230 | # In Bash we could simply go: 231 | # 232 | # readarray ARGS < <( xargs -n1 <<<"$var" ) && 233 | # set -- "${ARGS[@]}" "$@" 234 | # 235 | # but POSIX shell has neither arrays nor command substitution, so instead we 236 | # post-process each arg (as a line of input to sed) to backslash-escape any 237 | # character that might be a shell metacharacter, then use eval to reverse 238 | # that process (while maintaining the separation between arguments), and wrap 239 | # the whole thing up as a single "set" statement. 240 | # 241 | # This will of course break if any of these variables contains a newline or 242 | # an unmatched quote. 243 | # 244 | 245 | eval "set -- $( 246 | printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | 247 | xargs -n1 | 248 | sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | 249 | tr '\n' ' ' 250 | )" '"$@"' 251 | 252 | exec "$JAVACMD" "$@" 253 | -------------------------------------------------------------------------------- /android/gradlew.bat: -------------------------------------------------------------------------------- 1 | @rem 2 | @rem Copyright 2015 the original author or authors. 3 | @rem 4 | @rem Licensed under the Apache License, Version 2.0 (the "License"); 5 | @rem you may not use this file except in compliance with the License. 6 | @rem You may obtain a copy of the License at 7 | @rem 8 | @rem https://www.apache.org/licenses/LICENSE-2.0 9 | @rem 10 | @rem Unless required by applicable law or agreed to in writing, software 11 | @rem distributed under the License is distributed on an "AS IS" BASIS, 12 | @rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | @rem See the License for the specific language governing permissions and 14 | @rem limitations under the License. 15 | @rem 16 | @rem SPDX-License-Identifier: Apache-2.0 17 | @rem 18 | 19 | @if "%DEBUG%"=="" @echo off 20 | @rem ########################################################################## 21 | @rem 22 | @rem Gradle startup script for Windows 23 | @rem 24 | @rem ########################################################################## 25 | 26 | @rem Set local scope for the variables with windows NT shell 27 | if "%OS%"=="Windows_NT" setlocal 28 | 29 | set DIRNAME=%~dp0 30 | if "%DIRNAME%"=="" set DIRNAME=. 31 | @rem This is normally unused 32 | set APP_BASE_NAME=%~n0 33 | set APP_HOME=%DIRNAME% 34 | 35 | @rem Resolve any "." and ".." in APP_HOME to make it shorter. 36 | for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi 37 | 38 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. 39 | set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" 40 | 41 | @rem Find java.exe 42 | if defined JAVA_HOME goto findJavaFromJavaHome 43 | 44 | set JAVA_EXE=java.exe 45 | %JAVA_EXE% -version >NUL 2>&1 46 | if %ERRORLEVEL% equ 0 goto execute 47 | 48 | echo. 1>&2 49 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 50 | echo. 1>&2 51 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 52 | echo location of your Java installation. 1>&2 53 | 54 | goto fail 55 | 56 | :findJavaFromJavaHome 57 | set JAVA_HOME=%JAVA_HOME:"=% 58 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe 59 | 60 | if exist "%JAVA_EXE%" goto execute 61 | 62 | echo. 1>&2 63 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 64 | echo. 1>&2 65 | echo Please set the JAVA_HOME variable in your environment to match the 1>&2 66 | echo location of your Java installation. 1>&2 67 | 68 | goto fail 69 | 70 | :execute 71 | @rem Setup the command line 72 | 73 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar 74 | 75 | 76 | @rem Execute Gradle 77 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* 78 | 79 | :end 80 | @rem End local scope for the variables with windows NT shell 81 | if %ERRORLEVEL% equ 0 goto mainEnd 82 | 83 | :fail 84 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of 85 | rem the _cmd.exe /c_ return code! 86 | set EXIT_CODE=%ERRORLEVEL% 87 | if %EXIT_CODE% equ 0 set EXIT_CODE=1 88 | if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% 89 | exit /b %EXIT_CODE% 90 | 91 | :mainEnd 92 | if "%OS%"=="Windows_NT" endlocal 93 | 94 | :omega 95 | -------------------------------------------------------------------------------- /android/proguard-rules.pro: -------------------------------------------------------------------------------- 1 | # Add project specific ProGuard rules here. 2 | # You can control the set of applied configuration files using the 3 | # proguardFiles setting in build.gradle. 4 | # 5 | # For more details, see 6 | # http://developer.android.com/guide/developing/tools/proguard.html 7 | 8 | # If your project uses WebView with JS, uncomment the following 9 | # and specify the fully qualified class name to the JavaScript interface 10 | # class: 11 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview { 12 | # public *; 13 | #} 14 | 15 | # Uncomment this to preserve the line number information for 16 | # debugging stack traces. 17 | #-keepattributes SourceFile,LineNumberTable 18 | 19 | # If you keep the line number information, uncomment this to 20 | # hide the original source file name. 21 | #-renamesourcefileattribute SourceFile 22 | -------------------------------------------------------------------------------- /android/settings.gradle: -------------------------------------------------------------------------------- 1 | include ':capacitor-android' 2 | project(':capacitor-android').projectDir = new File('../node_modules/@capacitor/android/capacitor') -------------------------------------------------------------------------------- /android/src/androidTest/java/com/getcapacitor/android/ExampleInstrumentedTest.java: -------------------------------------------------------------------------------- 1 | package com.getcapacitor.android; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import android.content.Context; 6 | import androidx.test.ext.junit.runners.AndroidJUnit4; 7 | import androidx.test.platform.app.InstrumentationRegistry; 8 | import org.junit.Test; 9 | import org.junit.runner.RunWith; 10 | 11 | /** 12 | * Instrumented test, which will execute on an Android device. 13 | * 14 | * @see Testing documentation 15 | */ 16 | @RunWith(AndroidJUnit4.class) 17 | public class ExampleInstrumentedTest { 18 | 19 | @Test 20 | public void useAppContext() throws Exception { 21 | // Context of the app under test. 22 | Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext(); 23 | 24 | assertEquals("com.getcapacitor.android", appContext.getPackageName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /android/src/main/AndroidManifest.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /android/src/main/java/com/getcapacitor/community/tts/SpeakResultCallback.java: -------------------------------------------------------------------------------- 1 | package com.getcapacitor.community.tts; 2 | 3 | public interface SpeakResultCallback { 4 | void onDone(); 5 | void onError(); 6 | void onRangeStart(int start, int end); 7 | } 8 | -------------------------------------------------------------------------------- /android/src/main/java/com/getcapacitor/community/tts/TextToSpeech.java: -------------------------------------------------------------------------------- 1 | package com.getcapacitor.community.tts; 2 | 3 | import android.content.Context; 4 | import android.content.Intent; 5 | import android.content.pm.PackageManager; 6 | import android.content.pm.ResolveInfo; 7 | import android.os.Build; 8 | import android.os.Bundle; 9 | import android.speech.tts.UtteranceProgressListener; 10 | import android.speech.tts.Voice; 11 | import android.util.Log; 12 | import com.getcapacitor.JSArray; 13 | import com.getcapacitor.JSObject; 14 | import java.util.ArrayList; 15 | import java.util.Collections; 16 | import java.util.HashMap; 17 | import java.util.Locale; 18 | import java.util.Map; 19 | import java.util.Set; 20 | 21 | public class TextToSpeech implements android.speech.tts.TextToSpeech.OnInitListener { 22 | 23 | public static final String LOG_TAG = "TextToSpeech"; 24 | 25 | private Context context; 26 | private android.speech.tts.TextToSpeech tts = null; 27 | private int initializationStatus; 28 | private JSObject[] supportedVoices = null; 29 | private Map requests = new HashMap(); 30 | 31 | TextToSpeech(Context context) { 32 | this.context = context; 33 | try { 34 | tts = new android.speech.tts.TextToSpeech(context, this); 35 | tts.setOnUtteranceProgressListener( 36 | new UtteranceProgressListener() { 37 | @Override 38 | public void onStart(String utteranceId) {} 39 | 40 | @Override 41 | public void onDone(String utteranceId) { 42 | SpeakResultCallback callback = requests.get(utteranceId); 43 | if (callback != null) { 44 | callback.onDone(); 45 | requests.remove(utteranceId); 46 | } 47 | } 48 | 49 | @Override 50 | public void onError(String utteranceId) { 51 | SpeakResultCallback callback = requests.get(utteranceId); 52 | if (callback != null) { 53 | callback.onError(); 54 | requests.remove(utteranceId); 55 | } 56 | } 57 | 58 | @Override 59 | public void onRangeStart(String utteranceId, int start, int end, int frame) { 60 | SpeakResultCallback callback = requests.get(utteranceId); 61 | if (callback != null) { 62 | callback.onRangeStart(start, end); 63 | } 64 | } 65 | } 66 | ); 67 | } catch (Exception ex) { 68 | Log.d(LOG_TAG, ex.getLocalizedMessage()); 69 | } 70 | } 71 | 72 | @Override 73 | public void onInit(int status) { 74 | this.initializationStatus = status; 75 | } 76 | 77 | public void speak( 78 | String text, 79 | String lang, 80 | float rate, 81 | float pitch, 82 | float volume, 83 | int voice, 84 | String callbackId, 85 | SpeakResultCallback resultCallback 86 | ) { 87 | speak(text, lang, rate, pitch, volume, voice, callbackId, resultCallback, android.speech.tts.TextToSpeech.QUEUE_FLUSH); 88 | } 89 | 90 | public void speak( 91 | String text, 92 | String lang, 93 | float rate, 94 | float pitch, 95 | float volume, 96 | int voice, 97 | String callbackId, 98 | SpeakResultCallback resultCallback, 99 | int queueStrategy 100 | ) { 101 | if (queueStrategy != android.speech.tts.TextToSpeech.QUEUE_ADD) { 102 | stop(); 103 | } 104 | requests.put(callbackId, resultCallback); 105 | 106 | Locale locale = Locale.forLanguageTag(lang); 107 | 108 | Bundle ttsParams = new Bundle(); 109 | ttsParams.putSerializable(android.speech.tts.TextToSpeech.Engine.KEY_PARAM_UTTERANCE_ID, callbackId); 110 | ttsParams.putSerializable(android.speech.tts.TextToSpeech.Engine.KEY_PARAM_VOLUME, volume); 111 | 112 | tts.setLanguage(locale); 113 | tts.setSpeechRate(rate); 114 | tts.setPitch(pitch); 115 | 116 | if (voice >= 0) { 117 | ArrayList supportedVoices = getSupportedVoicesOrdered(); 118 | if (voice < supportedVoices.size()) { 119 | Voice newVoice = supportedVoices.get(voice); 120 | int resultCode = tts.setVoice(newVoice); 121 | } 122 | } 123 | tts.speak(text, queueStrategy, ttsParams, callbackId); 124 | } 125 | 126 | public void stop() { 127 | tts.stop(); 128 | requests.clear(); 129 | } 130 | 131 | public JSArray getSupportedLanguages() { 132 | ArrayList languages = new ArrayList<>(); 133 | Set supportedLocales = tts.getAvailableLanguages(); 134 | for (Locale supportedLocale : supportedLocales) { 135 | String tag = supportedLocale.toLanguageTag(); 136 | languages.add(tag); 137 | } 138 | JSArray result = JSArray.from(languages.toArray()); 139 | return result; 140 | } 141 | 142 | /** 143 | * @return Ordered list of voices. The order is guaranteed to remain the same as long as the voices in tts.getVoices() do not change. 144 | */ 145 | public ArrayList getSupportedVoicesOrdered() { 146 | Set supportedVoices = tts.getVoices(); 147 | ArrayList orderedVoices = new ArrayList(); 148 | for (Voice supportedVoice : supportedVoices) { 149 | orderedVoices.add(supportedVoice); 150 | } 151 | 152 | //voice.getName() is guaranteed to be unique, so will be used for sorting. 153 | Collections.sort(orderedVoices, (v1, v2) -> v1.getName().compareTo(v2.getName())); 154 | 155 | return orderedVoices; 156 | } 157 | 158 | public JSArray getSupportedVoices() { 159 | ArrayList voices = new ArrayList<>(); 160 | ArrayList supportedVoices = getSupportedVoicesOrdered(); 161 | for (Voice supportedVoice : supportedVoices) { 162 | JSObject obj = this.convertVoiceToJSObject(supportedVoice); 163 | voices.add(obj); 164 | } 165 | JSArray result = JSArray.from(voices.toArray()); 166 | return result; 167 | } 168 | 169 | public void openInstall() { 170 | PackageManager packageManager = context.getPackageManager(); 171 | Intent installIntent = new Intent(); 172 | installIntent.setAction(android.speech.tts.TextToSpeech.Engine.ACTION_CHECK_TTS_DATA); 173 | 174 | ResolveInfo resolveInfo = packageManager.resolveActivity(installIntent, PackageManager.MATCH_DEFAULT_ONLY); 175 | 176 | if (resolveInfo != null) { 177 | installIntent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK); 178 | context.startActivity(installIntent); 179 | } 180 | } 181 | 182 | public boolean isAvailable() { 183 | if (tts != null && initializationStatus == android.speech.tts.TextToSpeech.SUCCESS) { 184 | return true; 185 | } 186 | return false; 187 | } 188 | 189 | public boolean isLanguageSupported(String lang) { 190 | Locale locale = Locale.forLanguageTag(lang); 191 | int result = tts.isLanguageAvailable(locale); 192 | return result == tts.LANG_AVAILABLE || result == tts.LANG_COUNTRY_AVAILABLE || result == tts.LANG_COUNTRY_VAR_AVAILABLE; 193 | } 194 | 195 | public void onDestroy() { 196 | if (tts == null) { 197 | return; 198 | } 199 | tts.stop(); 200 | tts.shutdown(); 201 | } 202 | 203 | private JSObject convertVoiceToJSObject(Voice voice) { 204 | Locale locale = voice.getLocale(); 205 | JSObject obj = new JSObject(); 206 | obj.put("voiceURI", voice.getName()); 207 | obj.put("name", locale.getDisplayLanguage() + " " + locale.getDisplayCountry()); 208 | obj.put("lang", locale.toLanguageTag()); 209 | obj.put("localService", !voice.isNetworkConnectionRequired()); 210 | obj.put("default", false); 211 | return obj; 212 | } 213 | } 214 | -------------------------------------------------------------------------------- /android/src/main/java/com/getcapacitor/community/tts/TextToSpeechPlugin.java: -------------------------------------------------------------------------------- 1 | package com.getcapacitor.community.tts; 2 | 3 | import android.util.Base64; 4 | import android.util.Log; 5 | import com.getcapacitor.JSArray; 6 | import com.getcapacitor.JSObject; 7 | import com.getcapacitor.Plugin; 8 | import com.getcapacitor.PluginCall; 9 | import com.getcapacitor.PluginMethod; 10 | import com.getcapacitor.annotation.CapacitorPlugin; 11 | 12 | @CapacitorPlugin(name = "TextToSpeech") 13 | public class TextToSpeechPlugin extends Plugin { 14 | 15 | public static final String LOG_TAG = "TextToSpeechPlugin"; 16 | 17 | public static final String ERROR_UTTERANCE = "Failed to read text."; 18 | public static final String ERROR_UNSUPPORTED_LANGUAGE = "This language is not supported."; 19 | 20 | private TextToSpeech implementation; 21 | 22 | @Override 23 | public void load() { 24 | implementation = new TextToSpeech(getContext()); 25 | } 26 | 27 | @PluginMethod 28 | public void speak(PluginCall call) { 29 | boolean isAvailable = implementation.isAvailable(); 30 | if (!isAvailable) { 31 | call.unavailable("Not yet initialized or not available on this device."); 32 | return; 33 | } 34 | 35 | String text = call.getString("text", ""); 36 | String lang = call.getString("lang", "en-US"); 37 | float rate = call.getFloat("rate", 1.0f); 38 | float pitch = call.getFloat("pitch", 1.0f); 39 | float volume = call.getFloat("volume", 1.0f); 40 | int voice = call.getInt("voice", -1); 41 | int queueStrategy = call.getInt("queueStrategy", 0); 42 | 43 | boolean isLanguageSupported = implementation.isLanguageSupported(lang); 44 | if (!isLanguageSupported) { 45 | call.reject(ERROR_UNSUPPORTED_LANGUAGE); 46 | return; 47 | } 48 | 49 | SpeakResultCallback resultCallback = new SpeakResultCallback() { 50 | @Override 51 | public void onDone() { 52 | call.resolve(); 53 | } 54 | 55 | @Override 56 | public void onError() { 57 | call.reject(ERROR_UTTERANCE); 58 | } 59 | 60 | @Override 61 | public void onRangeStart(int start, int end) { 62 | JSObject ret = new JSObject(); 63 | ret.put("start", start); 64 | ret.put("end", end); 65 | String spokenWord = text.substring(start, end); 66 | ret.put("spokenWord", spokenWord); 67 | notifyListeners("onRangeStart", ret); 68 | } 69 | }; 70 | 71 | try { 72 | implementation.speak(text, lang, rate, pitch, volume, voice, call.getCallbackId(), resultCallback, queueStrategy); 73 | } catch (Exception ex) { 74 | call.reject(ex.getLocalizedMessage()); 75 | } 76 | } 77 | 78 | @PluginMethod 79 | public void stop(PluginCall call) { 80 | boolean isAvailable = implementation.isAvailable(); 81 | if (!isAvailable) { 82 | call.unavailable("Not yet initialized or not available on this device."); 83 | return; 84 | } 85 | try { 86 | implementation.stop(); 87 | call.resolve(); 88 | } catch (Exception ex) { 89 | call.reject(ex.getLocalizedMessage()); 90 | } 91 | } 92 | 93 | @PluginMethod 94 | public void getSupportedLanguages(PluginCall call) { 95 | try { 96 | JSArray languages = implementation.getSupportedLanguages(); 97 | JSObject ret = new JSObject(); 98 | ret.put("languages", languages); 99 | call.resolve(ret); 100 | } catch (Exception ex) { 101 | call.reject(ex.getLocalizedMessage()); 102 | } 103 | } 104 | 105 | @PluginMethod 106 | public void getSupportedVoices(PluginCall call) { 107 | try { 108 | JSArray voices = implementation.getSupportedVoices(); 109 | JSObject ret = new JSObject(); 110 | ret.put("voices", voices); 111 | call.resolve(ret); 112 | } catch (Exception ex) { 113 | call.reject(ex.getLocalizedMessage()); 114 | } 115 | } 116 | 117 | @PluginMethod 118 | public void isLanguageSupported(PluginCall call) { 119 | String lang = call.getString("lang", ""); 120 | try { 121 | boolean isLanguageSupported = implementation.isLanguageSupported(lang); 122 | JSObject ret = new JSObject(); 123 | ret.put("supported", isLanguageSupported); 124 | call.resolve(ret); 125 | } catch (Exception ex) { 126 | call.reject(ex.getLocalizedMessage()); 127 | } 128 | } 129 | 130 | @PluginMethod 131 | public void openInstall(PluginCall call) { 132 | try { 133 | implementation.openInstall(); 134 | call.resolve(); 135 | } catch (Exception ex) { 136 | call.reject(ex.getLocalizedMessage()); 137 | } 138 | } 139 | 140 | @Override 141 | protected void handleOnDestroy() { 142 | implementation.onDestroy(); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /android/src/main/res/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/capacitor-community/text-to-speech/386d252fdeaa5072b12e772c8499507a60337f73/android/src/main/res/.gitkeep -------------------------------------------------------------------------------- /android/src/test/java/com/getcapacitor/ExampleUnitTest.java: -------------------------------------------------------------------------------- 1 | package com.getcapacitor; 2 | 3 | import static org.junit.Assert.*; 4 | 5 | import org.junit.Test; 6 | 7 | /** 8 | * Example local unit test, which will execute on the development machine (host). 9 | * 10 | * @see Testing documentation 11 | */ 12 | public class ExampleUnitTest { 13 | 14 | @Test 15 | public void addition_isCorrect() throws Exception { 16 | assertEquals(4, 2 + 2); 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /ios/Plugin.xcodeproj/project.pbxproj: -------------------------------------------------------------------------------- 1 | // !$*UTF8*$! 2 | { 3 | archiveVersion = 1; 4 | classes = { 5 | }; 6 | objectVersion = 48; 7 | objects = { 8 | 9 | /* Begin PBXBuildFile section */ 10 | 03FC29A292ACC40490383A1F /* Pods_Plugin.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 3B2A61DA5A1F2DD4F959604D /* Pods_Plugin.framework */; }; 11 | 20C0B05DCFC8E3958A738AF2 /* Pods_PluginTests.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = F6753A823D3815DB436415E3 /* Pods_PluginTests.framework */; }; 12 | 2F98D68224C9AAE500613A4C /* TextToSpeech.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2F98D68124C9AAE400613A4C /* TextToSpeech.swift */; }; 13 | 50ADFF92201F53D600D50D53 /* Plugin.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 50ADFF88201F53D600D50D53 /* Plugin.framework */; }; 14 | 50ADFF97201F53D600D50D53 /* TextToSpeechPluginTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50ADFF96201F53D600D50D53 /* TextToSpeechPluginTests.swift */; }; 15 | 50ADFF99201F53D600D50D53 /* TextToSpeechPlugin.h in Headers */ = {isa = PBXBuildFile; fileRef = 50ADFF8B201F53D600D50D53 /* TextToSpeechPlugin.h */; settings = {ATTRIBUTES = (Public, ); }; }; 16 | 50ADFFA42020D75100D50D53 /* Capacitor.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 50ADFFA52020D75100D50D53 /* Capacitor.framework */; }; 17 | 50ADFFA82020EE4F00D50D53 /* TextToSpeechPlugin.m in Sources */ = {isa = PBXBuildFile; fileRef = 50ADFFA72020EE4F00D50D53 /* TextToSpeechPlugin.m */; }; 18 | 50E1A94820377CB70090CE1A /* TextToSpeechPlugin.swift in Sources */ = {isa = PBXBuildFile; fileRef = 50E1A94720377CB70090CE1A /* TextToSpeechPlugin.swift */; }; 19 | /* End PBXBuildFile section */ 20 | 21 | /* Begin PBXContainerItemProxy section */ 22 | 50ADFF93201F53D600D50D53 /* PBXContainerItemProxy */ = { 23 | isa = PBXContainerItemProxy; 24 | containerPortal = 50ADFF7F201F53D600D50D53 /* Project object */; 25 | proxyType = 1; 26 | remoteGlobalIDString = 50ADFF87201F53D600D50D53; 27 | remoteInfo = Plugin; 28 | }; 29 | /* End PBXContainerItemProxy section */ 30 | 31 | /* Begin PBXFileReference section */ 32 | 2F98D68124C9AAE400613A4C /* TextToSpeech.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextToSpeech.swift; sourceTree = ""; }; 33 | 3B2A61DA5A1F2DD4F959604D /* Pods_Plugin.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_Plugin.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 34 | 50ADFF88201F53D600D50D53 /* Plugin.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Plugin.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 35 | 50ADFF8B201F53D600D50D53 /* TextToSpeechPlugin.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = TextToSpeechPlugin.h; sourceTree = ""; }; 36 | 50ADFF8C201F53D600D50D53 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 37 | 50ADFF91201F53D600D50D53 /* PluginTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = PluginTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 38 | 50ADFF96201F53D600D50D53 /* TextToSpeechPluginTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextToSpeechPluginTests.swift; sourceTree = ""; }; 39 | 50ADFF98201F53D600D50D53 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 40 | 50ADFFA52020D75100D50D53 /* Capacitor.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = Capacitor.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 41 | 50ADFFA72020EE4F00D50D53 /* TextToSpeechPlugin.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = TextToSpeechPlugin.m; sourceTree = ""; }; 42 | 50E1A94720377CB70090CE1A /* TextToSpeechPlugin.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TextToSpeechPlugin.swift; sourceTree = ""; }; 43 | 5E23F77F099397094342571A /* Pods-Plugin.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Plugin.debug.xcconfig"; path = "Pods/Target Support Files/Pods-Plugin/Pods-Plugin.debug.xcconfig"; sourceTree = ""; }; 44 | 91781294A431A2A7CC6EB714 /* Pods-Plugin.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-Plugin.release.xcconfig"; path = "Pods/Target Support Files/Pods-Plugin/Pods-Plugin.release.xcconfig"; sourceTree = ""; }; 45 | 96ED1B6440D6672E406C8D19 /* Pods-PluginTests.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PluginTests.debug.xcconfig"; path = "Pods/Target Support Files/Pods-PluginTests/Pods-PluginTests.debug.xcconfig"; sourceTree = ""; }; 46 | F65BB2953ECE002E1EF3E424 /* Pods-PluginTests.release.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-PluginTests.release.xcconfig"; path = "Pods/Target Support Files/Pods-PluginTests/Pods-PluginTests.release.xcconfig"; sourceTree = ""; }; 47 | F6753A823D3815DB436415E3 /* Pods_PluginTests.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_PluginTests.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 48 | /* End PBXFileReference section */ 49 | 50 | /* Begin PBXFrameworksBuildPhase section */ 51 | 50ADFF84201F53D600D50D53 /* Frameworks */ = { 52 | isa = PBXFrameworksBuildPhase; 53 | buildActionMask = 2147483647; 54 | files = ( 55 | 50ADFFA42020D75100D50D53 /* Capacitor.framework in Frameworks */, 56 | 03FC29A292ACC40490383A1F /* Pods_Plugin.framework in Frameworks */, 57 | ); 58 | runOnlyForDeploymentPostprocessing = 0; 59 | }; 60 | 50ADFF8E201F53D600D50D53 /* Frameworks */ = { 61 | isa = PBXFrameworksBuildPhase; 62 | buildActionMask = 2147483647; 63 | files = ( 64 | 50ADFF92201F53D600D50D53 /* Plugin.framework in Frameworks */, 65 | 20C0B05DCFC8E3958A738AF2 /* Pods_PluginTests.framework in Frameworks */, 66 | ); 67 | runOnlyForDeploymentPostprocessing = 0; 68 | }; 69 | /* End PBXFrameworksBuildPhase section */ 70 | 71 | /* Begin PBXGroup section */ 72 | 50ADFF7E201F53D600D50D53 = { 73 | isa = PBXGroup; 74 | children = ( 75 | 50ADFF8A201F53D600D50D53 /* Plugin */, 76 | 50ADFF95201F53D600D50D53 /* PluginTests */, 77 | 50ADFF89201F53D600D50D53 /* Products */, 78 | 8C8E7744173064A9F6D438E3 /* Pods */, 79 | A797B9EFA3DCEFEA1FBB66A9 /* Frameworks */, 80 | ); 81 | sourceTree = ""; 82 | }; 83 | 50ADFF89201F53D600D50D53 /* Products */ = { 84 | isa = PBXGroup; 85 | children = ( 86 | 50ADFF88201F53D600D50D53 /* Plugin.framework */, 87 | 50ADFF91201F53D600D50D53 /* PluginTests.xctest */, 88 | ); 89 | name = Products; 90 | sourceTree = ""; 91 | }; 92 | 50ADFF8A201F53D600D50D53 /* Plugin */ = { 93 | isa = PBXGroup; 94 | children = ( 95 | 50E1A94720377CB70090CE1A /* TextToSpeechPlugin.swift */, 96 | 2F98D68124C9AAE400613A4C /* TextToSpeech.swift */, 97 | 50ADFF8B201F53D600D50D53 /* TextToSpeechPlugin.h */, 98 | 50ADFFA72020EE4F00D50D53 /* TextToSpeechPlugin.m */, 99 | 50ADFF8C201F53D600D50D53 /* Info.plist */, 100 | ); 101 | path = Plugin; 102 | sourceTree = ""; 103 | }; 104 | 50ADFF95201F53D600D50D53 /* PluginTests */ = { 105 | isa = PBXGroup; 106 | children = ( 107 | 50ADFF96201F53D600D50D53 /* TextToSpeechPluginTests.swift */, 108 | 50ADFF98201F53D600D50D53 /* Info.plist */, 109 | ); 110 | path = PluginTests; 111 | sourceTree = ""; 112 | }; 113 | 8C8E7744173064A9F6D438E3 /* Pods */ = { 114 | isa = PBXGroup; 115 | children = ( 116 | 5E23F77F099397094342571A /* Pods-Plugin.debug.xcconfig */, 117 | 91781294A431A2A7CC6EB714 /* Pods-Plugin.release.xcconfig */, 118 | 96ED1B6440D6672E406C8D19 /* Pods-PluginTests.debug.xcconfig */, 119 | F65BB2953ECE002E1EF3E424 /* Pods-PluginTests.release.xcconfig */, 120 | ); 121 | name = Pods; 122 | sourceTree = ""; 123 | }; 124 | A797B9EFA3DCEFEA1FBB66A9 /* Frameworks */ = { 125 | isa = PBXGroup; 126 | children = ( 127 | 50ADFFA52020D75100D50D53 /* Capacitor.framework */, 128 | 3B2A61DA5A1F2DD4F959604D /* Pods_Plugin.framework */, 129 | F6753A823D3815DB436415E3 /* Pods_PluginTests.framework */, 130 | ); 131 | name = Frameworks; 132 | sourceTree = ""; 133 | }; 134 | /* End PBXGroup section */ 135 | 136 | /* Begin PBXHeadersBuildPhase section */ 137 | 50ADFF85201F53D600D50D53 /* Headers */ = { 138 | isa = PBXHeadersBuildPhase; 139 | buildActionMask = 2147483647; 140 | files = ( 141 | 50ADFF99201F53D600D50D53 /* TextToSpeechPlugin.h in Headers */, 142 | ); 143 | runOnlyForDeploymentPostprocessing = 0; 144 | }; 145 | /* End PBXHeadersBuildPhase section */ 146 | 147 | /* Begin PBXNativeTarget section */ 148 | 50ADFF87201F53D600D50D53 /* Plugin */ = { 149 | isa = PBXNativeTarget; 150 | buildConfigurationList = 50ADFF9C201F53D600D50D53 /* Build configuration list for PBXNativeTarget "Plugin" */; 151 | buildPhases = ( 152 | AB5B3E54B4E897F32C2279DA /* [CP] Check Pods Manifest.lock */, 153 | 50ADFF83201F53D600D50D53 /* Sources */, 154 | 50ADFF84201F53D600D50D53 /* Frameworks */, 155 | 50ADFF85201F53D600D50D53 /* Headers */, 156 | 50ADFF86201F53D600D50D53 /* Resources */, 157 | ); 158 | buildRules = ( 159 | ); 160 | dependencies = ( 161 | ); 162 | name = Plugin; 163 | productName = Plugin; 164 | productReference = 50ADFF88201F53D600D50D53 /* Plugin.framework */; 165 | productType = "com.apple.product-type.framework"; 166 | }; 167 | 50ADFF90201F53D600D50D53 /* PluginTests */ = { 168 | isa = PBXNativeTarget; 169 | buildConfigurationList = 50ADFF9F201F53D600D50D53 /* Build configuration list for PBXNativeTarget "PluginTests" */; 170 | buildPhases = ( 171 | 0596884F929ED6F1DE134961 /* [CP] Check Pods Manifest.lock */, 172 | 50ADFF8D201F53D600D50D53 /* Sources */, 173 | 50ADFF8E201F53D600D50D53 /* Frameworks */, 174 | 50ADFF8F201F53D600D50D53 /* Resources */, 175 | 8E97F58B69A94C6503FC9C85 /* [CP] Embed Pods Frameworks */, 176 | ); 177 | buildRules = ( 178 | ); 179 | dependencies = ( 180 | 50ADFF94201F53D600D50D53 /* PBXTargetDependency */, 181 | ); 182 | name = PluginTests; 183 | productName = PluginTests; 184 | productReference = 50ADFF91201F53D600D50D53 /* PluginTests.xctest */; 185 | productType = "com.apple.product-type.bundle.unit-test"; 186 | }; 187 | /* End PBXNativeTarget section */ 188 | 189 | /* Begin PBXProject section */ 190 | 50ADFF7F201F53D600D50D53 /* Project object */ = { 191 | isa = PBXProject; 192 | attributes = { 193 | LastSwiftUpdateCheck = 0920; 194 | LastUpgradeCheck = 1160; 195 | ORGANIZATIONNAME = "Max Lynch"; 196 | TargetAttributes = { 197 | 50ADFF87201F53D600D50D53 = { 198 | CreatedOnToolsVersion = 9.2; 199 | LastSwiftMigration = 1100; 200 | ProvisioningStyle = Automatic; 201 | }; 202 | 50ADFF90201F53D600D50D53 = { 203 | CreatedOnToolsVersion = 9.2; 204 | LastSwiftMigration = 1100; 205 | ProvisioningStyle = Automatic; 206 | }; 207 | }; 208 | }; 209 | buildConfigurationList = 50ADFF82201F53D600D50D53 /* Build configuration list for PBXProject "Plugin" */; 210 | compatibilityVersion = "Xcode 8.0"; 211 | developmentRegion = en; 212 | hasScannedForEncodings = 0; 213 | knownRegions = ( 214 | en, 215 | Base, 216 | ); 217 | mainGroup = 50ADFF7E201F53D600D50D53; 218 | productRefGroup = 50ADFF89201F53D600D50D53 /* Products */; 219 | projectDirPath = ""; 220 | projectRoot = ""; 221 | targets = ( 222 | 50ADFF87201F53D600D50D53 /* Plugin */, 223 | 50ADFF90201F53D600D50D53 /* PluginTests */, 224 | ); 225 | }; 226 | /* End PBXProject section */ 227 | 228 | /* Begin PBXResourcesBuildPhase section */ 229 | 50ADFF86201F53D600D50D53 /* Resources */ = { 230 | isa = PBXResourcesBuildPhase; 231 | buildActionMask = 2147483647; 232 | files = ( 233 | ); 234 | runOnlyForDeploymentPostprocessing = 0; 235 | }; 236 | 50ADFF8F201F53D600D50D53 /* Resources */ = { 237 | isa = PBXResourcesBuildPhase; 238 | buildActionMask = 2147483647; 239 | files = ( 240 | ); 241 | runOnlyForDeploymentPostprocessing = 0; 242 | }; 243 | /* End PBXResourcesBuildPhase section */ 244 | 245 | /* Begin PBXShellScriptBuildPhase section */ 246 | 0596884F929ED6F1DE134961 /* [CP] Check Pods Manifest.lock */ = { 247 | isa = PBXShellScriptBuildPhase; 248 | buildActionMask = 2147483647; 249 | files = ( 250 | ); 251 | inputPaths = ( 252 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 253 | "${PODS_ROOT}/Manifest.lock", 254 | ); 255 | name = "[CP] Check Pods Manifest.lock"; 256 | outputPaths = ( 257 | "$(DERIVED_FILE_DIR)/Pods-PluginTests-checkManifestLockResult.txt", 258 | ); 259 | runOnlyForDeploymentPostprocessing = 0; 260 | shellPath = /bin/sh; 261 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 262 | showEnvVarsInLog = 0; 263 | }; 264 | 8E97F58B69A94C6503FC9C85 /* [CP] Embed Pods Frameworks */ = { 265 | isa = PBXShellScriptBuildPhase; 266 | buildActionMask = 2147483647; 267 | files = ( 268 | ); 269 | inputPaths = ( 270 | "${PODS_ROOT}/Target Support Files/Pods-PluginTests/Pods-PluginTests-frameworks.sh", 271 | "${BUILT_PRODUCTS_DIR}/Capacitor/Capacitor.framework", 272 | "${BUILT_PRODUCTS_DIR}/CapacitorCordova/Cordova.framework", 273 | ); 274 | name = "[CP] Embed Pods Frameworks"; 275 | outputPaths = ( 276 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Capacitor.framework", 277 | "${TARGET_BUILD_DIR}/${FRAMEWORKS_FOLDER_PATH}/Cordova.framework", 278 | ); 279 | runOnlyForDeploymentPostprocessing = 0; 280 | shellPath = /bin/sh; 281 | shellScript = "\"${PODS_ROOT}/Target Support Files/Pods-PluginTests/Pods-PluginTests-frameworks.sh\"\n"; 282 | showEnvVarsInLog = 0; 283 | }; 284 | AB5B3E54B4E897F32C2279DA /* [CP] Check Pods Manifest.lock */ = { 285 | isa = PBXShellScriptBuildPhase; 286 | buildActionMask = 2147483647; 287 | files = ( 288 | ); 289 | inputPaths = ( 290 | "${PODS_PODFILE_DIR_PATH}/Podfile.lock", 291 | "${PODS_ROOT}/Manifest.lock", 292 | ); 293 | name = "[CP] Check Pods Manifest.lock"; 294 | outputPaths = ( 295 | "$(DERIVED_FILE_DIR)/Pods-Plugin-checkManifestLockResult.txt", 296 | ); 297 | runOnlyForDeploymentPostprocessing = 0; 298 | shellPath = /bin/sh; 299 | shellScript = "diff \"${PODS_PODFILE_DIR_PATH}/Podfile.lock\" \"${PODS_ROOT}/Manifest.lock\" > /dev/null\nif [ $? != 0 ] ; then\n # print error to STDERR\n echo \"error: The sandbox is not in sync with the Podfile.lock. Run 'pod install' or update your CocoaPods installation.\" >&2\n exit 1\nfi\n# This output is used by Xcode 'outputs' to avoid re-running this script phase.\necho \"SUCCESS\" > \"${SCRIPT_OUTPUT_FILE_0}\"\n"; 300 | showEnvVarsInLog = 0; 301 | }; 302 | /* End PBXShellScriptBuildPhase section */ 303 | 304 | /* Begin PBXSourcesBuildPhase section */ 305 | 50ADFF83201F53D600D50D53 /* Sources */ = { 306 | isa = PBXSourcesBuildPhase; 307 | buildActionMask = 2147483647; 308 | files = ( 309 | 50E1A94820377CB70090CE1A /* TextToSpeechPlugin.swift in Sources */, 310 | 2F98D68224C9AAE500613A4C /* TextToSpeech.swift in Sources */, 311 | 50ADFFA82020EE4F00D50D53 /* TextToSpeechPlugin.m in Sources */, 312 | ); 313 | runOnlyForDeploymentPostprocessing = 0; 314 | }; 315 | 50ADFF8D201F53D600D50D53 /* Sources */ = { 316 | isa = PBXSourcesBuildPhase; 317 | buildActionMask = 2147483647; 318 | files = ( 319 | 50ADFF97201F53D600D50D53 /* TextToSpeechPluginTests.swift in Sources */, 320 | ); 321 | runOnlyForDeploymentPostprocessing = 0; 322 | }; 323 | /* End PBXSourcesBuildPhase section */ 324 | 325 | /* Begin PBXTargetDependency section */ 326 | 50ADFF94201F53D600D50D53 /* PBXTargetDependency */ = { 327 | isa = PBXTargetDependency; 328 | target = 50ADFF87201F53D600D50D53 /* Plugin */; 329 | targetProxy = 50ADFF93201F53D600D50D53 /* PBXContainerItemProxy */; 330 | }; 331 | /* End PBXTargetDependency section */ 332 | 333 | /* Begin XCBuildConfiguration section */ 334 | 50ADFF9A201F53D600D50D53 /* Debug */ = { 335 | isa = XCBuildConfiguration; 336 | buildSettings = { 337 | ALWAYS_SEARCH_USER_PATHS = NO; 338 | CLANG_ANALYZER_NONNULL = YES; 339 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 340 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 341 | CLANG_CXX_LIBRARY = "libc++"; 342 | CLANG_ENABLE_MODULES = YES; 343 | CLANG_ENABLE_OBJC_ARC = YES; 344 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 345 | CLANG_WARN_BOOL_CONVERSION = YES; 346 | CLANG_WARN_COMMA = YES; 347 | CLANG_WARN_CONSTANT_CONVERSION = YES; 348 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 349 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 350 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 351 | CLANG_WARN_EMPTY_BODY = YES; 352 | CLANG_WARN_ENUM_CONVERSION = YES; 353 | CLANG_WARN_INFINITE_RECURSION = YES; 354 | CLANG_WARN_INT_CONVERSION = YES; 355 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 356 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 357 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 358 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 359 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 360 | CLANG_WARN_STRICT_PROTOTYPES = YES; 361 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 362 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 363 | CLANG_WARN_UNREACHABLE_CODE = YES; 364 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 365 | CODE_SIGN_IDENTITY = "iPhone Developer"; 366 | COPY_PHASE_STRIP = NO; 367 | CURRENT_PROJECT_VERSION = 1; 368 | DEBUG_INFORMATION_FORMAT = dwarf; 369 | ENABLE_STRICT_OBJC_MSGSEND = YES; 370 | ENABLE_TESTABILITY = YES; 371 | FRAMEWORK_SEARCH_PATHS = ( 372 | "\"${BUILT_PRODUCTS_DIR}/Capacitor\"", 373 | "\"${BUILT_PRODUCTS_DIR}/CapacitorCordova\"", 374 | ); 375 | GCC_C_LANGUAGE_STANDARD = gnu11; 376 | GCC_DYNAMIC_NO_PIC = NO; 377 | GCC_NO_COMMON_BLOCKS = YES; 378 | GCC_OPTIMIZATION_LEVEL = 0; 379 | GCC_PREPROCESSOR_DEFINITIONS = ( 380 | "DEBUG=1", 381 | "$(inherited)", 382 | ); 383 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 384 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 385 | GCC_WARN_UNDECLARED_SELECTOR = YES; 386 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 387 | GCC_WARN_UNUSED_FUNCTION = YES; 388 | GCC_WARN_UNUSED_VARIABLE = YES; 389 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 390 | MTL_ENABLE_DEBUG_INFO = YES; 391 | ONLY_ACTIVE_ARCH = YES; 392 | SDKROOT = iphoneos; 393 | SWIFT_ACTIVE_COMPILATION_CONDITIONS = DEBUG; 394 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 395 | VERSIONING_SYSTEM = "apple-generic"; 396 | VERSION_INFO_PREFIX = ""; 397 | }; 398 | name = Debug; 399 | }; 400 | 50ADFF9B201F53D600D50D53 /* Release */ = { 401 | isa = XCBuildConfiguration; 402 | buildSettings = { 403 | ALWAYS_SEARCH_USER_PATHS = NO; 404 | CLANG_ANALYZER_NONNULL = YES; 405 | CLANG_ANALYZER_NUMBER_OBJECT_CONVERSION = YES_AGGRESSIVE; 406 | CLANG_CXX_LANGUAGE_STANDARD = "gnu++14"; 407 | CLANG_CXX_LIBRARY = "libc++"; 408 | CLANG_ENABLE_MODULES = YES; 409 | CLANG_ENABLE_OBJC_ARC = YES; 410 | CLANG_WARN_BLOCK_CAPTURE_AUTORELEASING = YES; 411 | CLANG_WARN_BOOL_CONVERSION = YES; 412 | CLANG_WARN_COMMA = YES; 413 | CLANG_WARN_CONSTANT_CONVERSION = YES; 414 | CLANG_WARN_DEPRECATED_OBJC_IMPLEMENTATIONS = YES; 415 | CLANG_WARN_DIRECT_OBJC_ISA_USAGE = YES_ERROR; 416 | CLANG_WARN_DOCUMENTATION_COMMENTS = YES; 417 | CLANG_WARN_EMPTY_BODY = YES; 418 | CLANG_WARN_ENUM_CONVERSION = YES; 419 | CLANG_WARN_INFINITE_RECURSION = YES; 420 | CLANG_WARN_INT_CONVERSION = YES; 421 | CLANG_WARN_NON_LITERAL_NULL_CONVERSION = YES; 422 | CLANG_WARN_OBJC_IMPLICIT_RETAIN_SELF = YES; 423 | CLANG_WARN_OBJC_LITERAL_CONVERSION = YES; 424 | CLANG_WARN_OBJC_ROOT_CLASS = YES_ERROR; 425 | CLANG_WARN_RANGE_LOOP_ANALYSIS = YES; 426 | CLANG_WARN_STRICT_PROTOTYPES = YES; 427 | CLANG_WARN_SUSPICIOUS_MOVE = YES; 428 | CLANG_WARN_UNGUARDED_AVAILABILITY = YES_AGGRESSIVE; 429 | CLANG_WARN_UNREACHABLE_CODE = YES; 430 | CLANG_WARN__DUPLICATE_METHOD_MATCH = YES; 431 | CODE_SIGN_IDENTITY = "iPhone Developer"; 432 | COPY_PHASE_STRIP = NO; 433 | CURRENT_PROJECT_VERSION = 1; 434 | DEBUG_INFORMATION_FORMAT = "dwarf-with-dsym"; 435 | ENABLE_NS_ASSERTIONS = NO; 436 | ENABLE_STRICT_OBJC_MSGSEND = YES; 437 | FRAMEWORK_SEARCH_PATHS = ( 438 | "\"${BUILT_PRODUCTS_DIR}/Capacitor\"", 439 | "\"${BUILT_PRODUCTS_DIR}/CapacitorCordova\"", 440 | ); 441 | GCC_C_LANGUAGE_STANDARD = gnu11; 442 | GCC_NO_COMMON_BLOCKS = YES; 443 | GCC_WARN_64_TO_32_BIT_CONVERSION = YES; 444 | GCC_WARN_ABOUT_RETURN_TYPE = YES_ERROR; 445 | GCC_WARN_UNDECLARED_SELECTOR = YES; 446 | GCC_WARN_UNINITIALIZED_AUTOS = YES_AGGRESSIVE; 447 | GCC_WARN_UNUSED_FUNCTION = YES; 448 | GCC_WARN_UNUSED_VARIABLE = YES; 449 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 450 | MTL_ENABLE_DEBUG_INFO = NO; 451 | SDKROOT = iphoneos; 452 | SWIFT_OPTIMIZATION_LEVEL = "-Owholemodule"; 453 | VALIDATE_PRODUCT = YES; 454 | VERSIONING_SYSTEM = "apple-generic"; 455 | VERSION_INFO_PREFIX = ""; 456 | }; 457 | name = Release; 458 | }; 459 | 50ADFF9D201F53D600D50D53 /* Debug */ = { 460 | isa = XCBuildConfiguration; 461 | baseConfigurationReference = 5E23F77F099397094342571A /* Pods-Plugin.debug.xcconfig */; 462 | buildSettings = { 463 | CLANG_ENABLE_MODULES = YES; 464 | CODE_SIGN_IDENTITY = ""; 465 | CODE_SIGN_STYLE = Automatic; 466 | DEFINES_MODULE = YES; 467 | DYLIB_COMPATIBILITY_VERSION = 1; 468 | DYLIB_CURRENT_VERSION = 1; 469 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 470 | INFOPLIST_FILE = Plugin/Info.plist; 471 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 472 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 473 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks $(FRAMEWORK_SEARCH_PATHS)\n$(FRAMEWORK_SEARCH_PATHS)\n$(FRAMEWORK_SEARCH_PATHS)"; 474 | ONLY_ACTIVE_ARCH = YES; 475 | PRODUCT_BUNDLE_IDENTIFIER = com.getcapacitor.Plugin; 476 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 477 | SKIP_INSTALL = YES; 478 | SUPPORTS_MACCATALYST = NO; 479 | SWIFT_OPTIMIZATION_LEVEL = "-Onone"; 480 | SWIFT_VERSION = 5.0; 481 | TARGETED_DEVICE_FAMILY = "1,2"; 482 | }; 483 | name = Debug; 484 | }; 485 | 50ADFF9E201F53D600D50D53 /* Release */ = { 486 | isa = XCBuildConfiguration; 487 | baseConfigurationReference = 91781294A431A2A7CC6EB714 /* Pods-Plugin.release.xcconfig */; 488 | buildSettings = { 489 | CLANG_ENABLE_MODULES = YES; 490 | CODE_SIGN_IDENTITY = ""; 491 | CODE_SIGN_STYLE = Automatic; 492 | DEFINES_MODULE = YES; 493 | DYLIB_COMPATIBILITY_VERSION = 1; 494 | DYLIB_CURRENT_VERSION = 1; 495 | DYLIB_INSTALL_NAME_BASE = "@rpath"; 496 | INFOPLIST_FILE = Plugin/Info.plist; 497 | INSTALL_PATH = "$(LOCAL_LIBRARY_DIR)/Frameworks"; 498 | IPHONEOS_DEPLOYMENT_TARGET = 14.0; 499 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks $(FRAMEWORK_SEARCH_PATHS)"; 500 | ONLY_ACTIVE_ARCH = NO; 501 | PRODUCT_BUNDLE_IDENTIFIER = com.getcapacitor.Plugin; 502 | PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; 503 | SKIP_INSTALL = YES; 504 | SUPPORTS_MACCATALYST = NO; 505 | SWIFT_VERSION = 5.0; 506 | TARGETED_DEVICE_FAMILY = "1,2"; 507 | }; 508 | name = Release; 509 | }; 510 | 50ADFFA0201F53D600D50D53 /* Debug */ = { 511 | isa = XCBuildConfiguration; 512 | baseConfigurationReference = 96ED1B6440D6672E406C8D19 /* Pods-PluginTests.debug.xcconfig */; 513 | buildSettings = { 514 | CODE_SIGN_STYLE = Automatic; 515 | INFOPLIST_FILE = PluginTests/Info.plist; 516 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 517 | PRODUCT_BUNDLE_IDENTIFIER = com.getcapacitor.PluginTests; 518 | PRODUCT_NAME = "$(TARGET_NAME)"; 519 | SWIFT_VERSION = 5.0; 520 | TARGETED_DEVICE_FAMILY = "1,2"; 521 | }; 522 | name = Debug; 523 | }; 524 | 50ADFFA1201F53D600D50D53 /* Release */ = { 525 | isa = XCBuildConfiguration; 526 | baseConfigurationReference = F65BB2953ECE002E1EF3E424 /* Pods-PluginTests.release.xcconfig */; 527 | buildSettings = { 528 | CODE_SIGN_STYLE = Automatic; 529 | INFOPLIST_FILE = PluginTests/Info.plist; 530 | LD_RUNPATH_SEARCH_PATHS = "$(inherited) @executable_path/Frameworks @loader_path/Frameworks"; 531 | PRODUCT_BUNDLE_IDENTIFIER = com.getcapacitor.PluginTests; 532 | PRODUCT_NAME = "$(TARGET_NAME)"; 533 | SWIFT_VERSION = 5.0; 534 | TARGETED_DEVICE_FAMILY = "1,2"; 535 | }; 536 | name = Release; 537 | }; 538 | /* End XCBuildConfiguration section */ 539 | 540 | /* Begin XCConfigurationList section */ 541 | 50ADFF82201F53D600D50D53 /* Build configuration list for PBXProject "Plugin" */ = { 542 | isa = XCConfigurationList; 543 | buildConfigurations = ( 544 | 50ADFF9A201F53D600D50D53 /* Debug */, 545 | 50ADFF9B201F53D600D50D53 /* Release */, 546 | ); 547 | defaultConfigurationIsVisible = 0; 548 | defaultConfigurationName = Release; 549 | }; 550 | 50ADFF9C201F53D600D50D53 /* Build configuration list for PBXNativeTarget "Plugin" */ = { 551 | isa = XCConfigurationList; 552 | buildConfigurations = ( 553 | 50ADFF9D201F53D600D50D53 /* Debug */, 554 | 50ADFF9E201F53D600D50D53 /* Release */, 555 | ); 556 | defaultConfigurationIsVisible = 0; 557 | defaultConfigurationName = Release; 558 | }; 559 | 50ADFF9F201F53D600D50D53 /* Build configuration list for PBXNativeTarget "PluginTests" */ = { 560 | isa = XCConfigurationList; 561 | buildConfigurations = ( 562 | 50ADFFA0201F53D600D50D53 /* Debug */, 563 | 50ADFFA1201F53D600D50D53 /* Release */, 564 | ); 565 | defaultConfigurationIsVisible = 0; 566 | defaultConfigurationName = Release; 567 | }; 568 | /* End XCConfigurationList section */ 569 | }; 570 | rootObject = 50ADFF7F201F53D600D50D53 /* Project object */; 571 | } 572 | -------------------------------------------------------------------------------- /ios/Plugin.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /ios/Plugin.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Plugin.xcodeproj/xcshareddata/xcschemes/Plugin.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 54 | 60 | 61 | 67 | 68 | 69 | 70 | 72 | 73 | 76 | 77 | 78 | -------------------------------------------------------------------------------- /ios/Plugin.xcodeproj/xcshareddata/xcschemes/PluginTests.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 33 | 39 | 40 | 41 | 42 | 43 | 53 | 54 | 60 | 61 | 63 | 64 | 67 | 68 | 69 | -------------------------------------------------------------------------------- /ios/Plugin.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /ios/Plugin.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /ios/Plugin/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | FMWK 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | $(CURRENT_PROJECT_VERSION) 21 | NSPrincipalClass 22 | 23 | 24 | 25 | -------------------------------------------------------------------------------- /ios/Plugin/TextToSpeech.swift: -------------------------------------------------------------------------------- 1 | import AVFoundation 2 | import Capacitor 3 | 4 | enum QUEUE_STRATEGY: Int { 5 | case QUEUE_ADD = 1, QUEUE_FLUSH = 0 6 | } 7 | 8 | @objc public class TextToSpeech: NSObject, AVSpeechSynthesizerDelegate { 9 | let synthesizer = AVSpeechSynthesizer() 10 | var calls: [CAPPluginCall] = [] 11 | let queue = DispatchQueue(label: "backgroundAudioSetup", qos: .userInitiated, attributes: [], autoreleaseFrequency: .inherit, target: nil) 12 | 13 | override init() { 14 | super.init() 15 | self.synthesizer.delegate = self 16 | // set session in background to avoid UI hangs. 17 | queue.async { 18 | do { 19 | let avAudioSessionCategory: AVAudioSession.Category = .playback 20 | try AVAudioSession.sharedInstance().setCategory(avAudioSessionCategory, mode: .default, options: .duckOthers) 21 | try AVAudioSession.sharedInstance().setActive(true) 22 | } catch { 23 | print("Error setting up AVAudioSession: \(error)") 24 | } 25 | } 26 | } 27 | 28 | public func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didCancel utterance: AVSpeechUtterance) { 29 | self.resolveCurrentCall() 30 | } 31 | 32 | public func speechSynthesizer(_ synthesizer: AVSpeechSynthesizer, didFinish utterance: AVSpeechUtterance) { 33 | self.resolveCurrentCall() 34 | } 35 | 36 | @objc public func speak(_ text: String, _ lang: String, _ rate: Float, _ pitch: Float, _ category: String, _ volume: Float, _ voice: Int, _ queueStrategy: Int, _ call: CAPPluginCall) throws { 37 | if queueStrategy == QUEUE_STRATEGY.QUEUE_FLUSH.rawValue { 38 | self.synthesizer.stopSpeaking(at: .immediate) 39 | } 40 | self.calls.append(call) 41 | 42 | let utterance = AVSpeechUtterance(string: text) 43 | utterance.voice = AVSpeechSynthesisVoice(language: lang) 44 | utterance.rate = adjustRate(rate) 45 | utterance.pitchMultiplier = pitch 46 | utterance.volume = volume 47 | 48 | // Find the voice associated with the voice parameter if a voice specified. 49 | // If the specified voice is not available we will fall back to default voice rather than raising an error. 50 | if voice >= 0 { 51 | let allVoices = AVSpeechSynthesisVoice.speechVoices() 52 | if voice < allVoices.count { 53 | utterance.voice = allVoices[voice] 54 | } 55 | } 56 | 57 | synthesizer.speak(utterance) 58 | } 59 | 60 | @objc public func stop() { 61 | synthesizer.stopSpeaking(at: .immediate) 62 | } 63 | 64 | @objc public func getSupportedLanguages() -> [String] { 65 | return Array(AVSpeechSynthesisVoice.speechVoices().map { 66 | return $0.language 67 | }) 68 | } 69 | 70 | @objc public func isLanguageSupported(_ lang: String) -> Bool { 71 | let voice = AVSpeechSynthesisVoice(language: lang) 72 | return voice != nil 73 | } 74 | 75 | // Adjust rate for a closer match to other platform. 76 | @objc private func adjustRate(_ rate: Float) -> Float { 77 | let baseRate: Float = AVSpeechUtteranceDefaultSpeechRate 78 | if rate >= 1.0 { 79 | return (0.1 * rate) + (baseRate - 0.1) 80 | } 81 | return rate * baseRate 82 | } 83 | 84 | @objc private func resolveCurrentCall() { 85 | guard let call = calls.first else { 86 | return 87 | } 88 | call.resolve() 89 | calls.removeFirst() 90 | } 91 | } 92 | -------------------------------------------------------------------------------- /ios/Plugin/TextToSpeechPlugin.h: -------------------------------------------------------------------------------- 1 | #import 2 | 3 | //! Project version number for Plugin. 4 | FOUNDATION_EXPORT double PluginVersionNumber; 5 | 6 | //! Project version string for Plugin. 7 | FOUNDATION_EXPORT const unsigned char PluginVersionString[]; 8 | 9 | // In this header, you should import all the public headers of your framework using statements like #import 10 | 11 | -------------------------------------------------------------------------------- /ios/Plugin/TextToSpeechPlugin.m: -------------------------------------------------------------------------------- 1 | #import 2 | #import 3 | 4 | // Define the plugin using the CAP_PLUGIN Macro, and 5 | // each method the plugin supports using the CAP_PLUGIN_METHOD macro. 6 | CAP_PLUGIN(TextToSpeechPlugin, "TextToSpeech", 7 | CAP_PLUGIN_METHOD(speak, CAPPluginReturnPromise); 8 | CAP_PLUGIN_METHOD(stop, CAPPluginReturnPromise); 9 | CAP_PLUGIN_METHOD(openInstall, CAPPluginReturnPromise); 10 | CAP_PLUGIN_METHOD(getSupportedLanguages, CAPPluginReturnPromise); 11 | CAP_PLUGIN_METHOD(getSupportedVoices, CAPPluginReturnPromise); 12 | CAP_PLUGIN_METHOD(isLanguageSupported, CAPPluginReturnPromise); 13 | ) 14 | -------------------------------------------------------------------------------- /ios/Plugin/TextToSpeechPlugin.swift: -------------------------------------------------------------------------------- 1 | import Foundation 2 | import Capacitor 3 | import AVFoundation 4 | 5 | /** 6 | * Please read the Capacitor iOS Plugin Development Guide 7 | * here: https://capacitorjs.com/docs/plugins/ios 8 | */ 9 | @objc(TextToSpeechPlugin) 10 | public class TextToSpeechPlugin: CAPPlugin { 11 | private static let errorUnsupportedLanguage = "This language is not supported." 12 | 13 | private let implementation = TextToSpeech() 14 | 15 | @objc public func speak(_ call: CAPPluginCall) { 16 | let text = call.getString("text") ?? "" 17 | let lang = call.getString("lang") ?? "en-US" 18 | let rate = call.getFloat("rate") ?? 1.0 19 | let pitch = call.getFloat("pitch") ?? 1.0 20 | let volume = call.getFloat("volume") ?? 1.0 21 | let voice = call.getInt("voice") ?? -1 22 | let category = call.getString("category") ?? "ambient" 23 | let queueStrategy = call.getInt("queueStrategy") ?? 0 24 | 25 | let isLanguageSupported = implementation.isLanguageSupported(lang) 26 | guard isLanguageSupported else { 27 | call.reject(TextToSpeechPlugin.errorUnsupportedLanguage) 28 | return 29 | } 30 | 31 | do { 32 | try implementation.speak(text, lang, rate, pitch, category, volume, voice, queueStrategy, call) 33 | } catch { 34 | call.reject(error.localizedDescription) 35 | } 36 | } 37 | 38 | @objc public func stop(_ call: CAPPluginCall) { 39 | implementation.stop() 40 | call.resolve() 41 | } 42 | 43 | @objc public func openInstall(_ call: CAPPluginCall) { 44 | call.resolve() 45 | } 46 | 47 | @objc func getSupportedLanguages(_ call: CAPPluginCall) { 48 | let languages = self.implementation.getSupportedLanguages() 49 | call.resolve([ 50 | "languages": languages 51 | ]) 52 | } 53 | 54 | @objc func getSupportedVoices(_ call: CAPPluginCall) { 55 | let allVoices = AVSpeechSynthesisVoice.speechVoices() 56 | var res: [[String: Any]] = [] 57 | 58 | for voice in allVoices { 59 | let lang = [ 60 | "default": false, 61 | "lang": voice.language, 62 | "localService": true, 63 | "name": voice.name, 64 | "voiceURI": voice.identifier 65 | ] as [String: Any] 66 | res.append(lang) 67 | } 68 | 69 | call.resolve([ 70 | "voices": res 71 | ]) 72 | } 73 | 74 | @objc func isLanguageSupported(_ call: CAPPluginCall) { 75 | let lang = call.getString("lang") ?? "" 76 | let isLanguageSupported = self.implementation.isLanguageSupported(lang) 77 | call.resolve([ 78 | "supported": isLanguageSupported 79 | ]) 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /ios/PluginTests/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | CFBundleDevelopmentRegion 6 | $(DEVELOPMENT_LANGUAGE) 7 | CFBundleExecutable 8 | $(EXECUTABLE_NAME) 9 | CFBundleIdentifier 10 | $(PRODUCT_BUNDLE_IDENTIFIER) 11 | CFBundleInfoDictionaryVersion 12 | 6.0 13 | CFBundleName 14 | $(PRODUCT_NAME) 15 | CFBundlePackageType 16 | BNDL 17 | CFBundleShortVersionString 18 | 1.0 19 | CFBundleVersion 20 | 1 21 | 22 | 23 | -------------------------------------------------------------------------------- /ios/PluginTests/TextToSpeechPluginTests.swift: -------------------------------------------------------------------------------- 1 | import XCTest 2 | @testable import Plugin 3 | 4 | class TextToSpeechTests: XCTestCase { 5 | 6 | func testEcho() { 7 | // This is an example of a functional test case for a plugin. 8 | // Use XCTAssert and related functions to verify your tests produce the correct results. 9 | 10 | let implementation = TextToSpeech() 11 | let value = "Hello, World!" 12 | let result = implementation.echo(value) 13 | 14 | XCTAssertEqual(value, result) 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /ios/Podfile: -------------------------------------------------------------------------------- 1 | platform :ios, '14.0' 2 | 3 | def capacitor_pods 4 | # Comment the next line if you're not using Swift and don't want to use dynamic frameworks 5 | use_frameworks! 6 | pod 'Capacitor', :path => '../node_modules/@capacitor/ios' 7 | pod 'CapacitorCordova', :path => '../node_modules/@capacitor/ios' 8 | end 9 | 10 | target 'Plugin' do 11 | capacitor_pods 12 | end 13 | 14 | target 'PluginTests' do 15 | capacitor_pods 16 | end 17 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@capacitor-community/text-to-speech", 3 | "version": "6.0.0", 4 | "description": "Capacitor plugin for synthesizing speech from text.", 5 | "main": "dist/plugin.cjs.js", 6 | "module": "dist/esm/index.js", 7 | "types": "dist/esm/index.d.ts", 8 | "unpkg": "dist/plugin.js", 9 | "scripts": { 10 | "verify": "npm run verify:ios && npm run verify:android && npm run verify:web", 11 | "verify:ios": "cd ios && pod install && xcodebuild -workspace Plugin.xcworkspace -scheme Plugin -destination generic/platform=iOS && cd ..", 12 | "verify:android": "cd android && ./gradlew clean build test && cd ..", 13 | "verify:web": "npm run build", 14 | "lint": "npm run eslint && npm run prettier -- --check && npm run swiftlint -- lint", 15 | "fmt": "npm run eslint -- --fix && npm run prettier -- --write && npm run swiftlint -- --fix --format", 16 | "eslint": "eslint . --ext ts", 17 | "prettier": "prettier \"**/*.{css,html,ts,js,java}\" --plugin=prettier-plugin-java", 18 | "swiftlint": "node-swiftlint", 19 | "docgen": "docgen --api TextToSpeechPlugin --output-readme README.md --output-json dist/docs.json", 20 | "build": "npm run clean && npm run docgen && tsc && rollup -c rollup.config.mjs", 21 | "clean": "rimraf ./dist", 22 | "watch": "tsc --watch", 23 | "prepublishOnly": "npm run build", 24 | "release": "standard-version" 25 | }, 26 | "author": "Robin Genz ", 27 | "license": "MIT", 28 | "devDependencies": { 29 | "@capacitor/android": "7.0.0", 30 | "@capacitor/cli": "7.0.0", 31 | "@capacitor/core": "7.0.0", 32 | "@capacitor/docgen": "0.3.0", 33 | "@capacitor/ios": "7.0.0", 34 | "@ionic/eslint-config": "0.4.0", 35 | "@ionic/prettier-config": "4.0.0", 36 | "@ionic/swiftlint-config": "2.0.0", 37 | "eslint": "8.57.0", 38 | "prettier": "3.4.2", 39 | "prettier-plugin-java": "2.6.6", 40 | "rimraf": "6.0.1", 41 | "rollup": "4.30.1", 42 | "standard-version": "9.5.0", 43 | "swiftlint": "2.0.0", 44 | "typescript": "4.1.5" 45 | }, 46 | "peerDependencies": { 47 | "@capacitor/core": ">=7.0.0" 48 | }, 49 | "files": [ 50 | "android/src/main/", 51 | "android/build.gradle", 52 | "dist/", 53 | "ios/Plugin/", 54 | "CapacitorCommunityTextToSpeech.podspec" 55 | ], 56 | "repository": { 57 | "type": "git", 58 | "url": "git+https://github.com/capacitor-community/text-to-speech.git" 59 | }, 60 | "bugs": { 61 | "url": "https://github.com/capacitor-community/text-to-speech/issues" 62 | }, 63 | "keywords": [ 64 | "capacitor", 65 | "plugin", 66 | "native" 67 | ], 68 | "prettier": "@ionic/prettier-config", 69 | "swiftlint": "@ionic/swiftlint-config", 70 | "eslintConfig": { 71 | "extends": "@ionic/eslint-config/recommended" 72 | }, 73 | "capacitor": { 74 | "ios": { 75 | "src": "ios" 76 | }, 77 | "android": { 78 | "src": "android" 79 | } 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /rollup.config.mjs: -------------------------------------------------------------------------------- 1 | export default { 2 | input: 'dist/esm/index.js', 3 | output: [ 4 | { 5 | file: 'dist/plugin.js', 6 | format: 'iife', 7 | name: 'capacitorTextToSpeech', 8 | globals: { 9 | '@capacitor/core': 'capacitorExports', 10 | }, 11 | sourcemap: true, 12 | inlineDynamicImports: true, 13 | }, 14 | { 15 | file: 'dist/plugin.cjs.js', 16 | format: 'cjs', 17 | sourcemap: true, 18 | inlineDynamicImports: true, 19 | }, 20 | ], 21 | external: ['@capacitor/core'], 22 | }; 23 | -------------------------------------------------------------------------------- /src/definitions.ts: -------------------------------------------------------------------------------- 1 | import type { PluginListenerHandle } from '@capacitor/core'; 2 | 3 | export interface TextToSpeechPlugin { 4 | /** 5 | * Starts the TTS engine and plays the desired text. 6 | */ 7 | speak(options: TTSOptions): Promise; 8 | /** 9 | * Stops the TTS engine. 10 | */ 11 | stop(): Promise; 12 | /** 13 | * Returns a list of supported BCP 47 language tags. 14 | */ 15 | getSupportedLanguages(): Promise<{ languages: string[] }>; 16 | /** 17 | * Returns a list of supported voices. 18 | */ 19 | getSupportedVoices(): Promise<{ voices: SpeechSynthesisVoice[] }>; 20 | /** 21 | * Checks if a specific BCP 47 language tag is supported. 22 | */ 23 | isLanguageSupported(options: { lang: string }): Promise<{ supported: boolean }>; 24 | /** 25 | * Verifies proper installation and availability of resource files on the system. 26 | * 27 | * Only available for Android. 28 | */ 29 | openInstall(): Promise; 30 | 31 | addListener( 32 | eventName: 'onRangeStart', 33 | listenerFunc: (info: { start: number; end: number; spokenWord: string }) => void, 34 | ): Promise; 35 | } 36 | 37 | export enum QueueStrategy { 38 | /** 39 | * Use `Flush` to stop the current request when a new request is sent. 40 | */ 41 | Flush = 0, 42 | /** 43 | * Use `Add` to buffer the speech request. The request will be executed when all previous requests have been completed. 44 | */ 45 | Add = 1, 46 | } 47 | 48 | export interface TTSOptions { 49 | /** 50 | * The text that will be synthesised when the utterance is spoken. 51 | * 52 | * @example "Hello world" 53 | */ 54 | text: string; 55 | /** 56 | * The language of the utterance. 57 | * Possible languages can be queried using `getSupportedLanguages`. 58 | * 59 | * @default "en-US" 60 | */ 61 | lang?: string; 62 | /** 63 | * The speed at which the utterance will be spoken at. 64 | * 65 | * @default 1.0 66 | */ 67 | rate?: number; 68 | /** 69 | * The pitch at which the utterance will be spoken at. 70 | * 71 | * @default 1.0 72 | */ 73 | pitch?: number; 74 | /** 75 | * The volume that the utterance will be spoken at. 76 | * 77 | * @default 1.0 78 | */ 79 | volume?: number; 80 | /** 81 | * The index of the selected voice that will be used to speak the utterance. 82 | * Possible voices can be queried using `getSupportedVoices`. 83 | */ 84 | voice?: number; 85 | /** 86 | * Select the iOS Audio session category. 87 | * Possible values: `ambient` and `playback`. 88 | * Use `playback` to play audio even when the app is in the background. 89 | * 90 | * Only available for iOS. 91 | * 92 | * @default "ambient" 93 | */ 94 | category?: string; 95 | /** 96 | * Select the strategy to adopt when several requests to speak overlap. 97 | * 98 | * @since 5.1.0 99 | * @default QueueStrategy.Flush 100 | */ 101 | queueStrategy?: QueueStrategy; 102 | } 103 | 104 | /** 105 | * The SpeechSynthesisVoice interface represents a voice that the system supports. 106 | */ 107 | export interface SpeechSynthesisVoice { 108 | /** 109 | * Specifies whether the voice is the default voice for the current app (`true`) or not (`false`). 110 | * 111 | * @example false 112 | */ 113 | default: boolean; 114 | /** 115 | * BCP 47 language tag indicating the language of the voice. 116 | * 117 | * @example "en-US" 118 | */ 119 | lang: string; 120 | /** 121 | * Specifies whether the voice is supplied by a local (`true`) or remote (`false`) speech synthesizer service. 122 | * 123 | * @example true 124 | */ 125 | localService: boolean; 126 | /** 127 | * Human-readable name that represents the voice. 128 | * 129 | * @example "Microsoft Zira Desktop - English (United States)" 130 | */ 131 | name: string; 132 | /** 133 | * Type of URI and location of the speech synthesis service for this voice. 134 | * 135 | * @example "urn:moz-tts:sapi:Microsoft Zira Desktop - English (United States)?en-US" 136 | */ 137 | voiceURI: string; 138 | } 139 | -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import { registerPlugin } from '@capacitor/core'; 2 | 3 | import type { TextToSpeechPlugin } from './definitions'; 4 | 5 | const TextToSpeech = registerPlugin('TextToSpeech', { 6 | web: () => import('./web').then((m) => new m.TextToSpeechWeb()), 7 | }); 8 | 9 | // Warm up 10 | if ('speechSynthesis' in window) { 11 | window.speechSynthesis; 12 | } 13 | 14 | export * from './definitions'; 15 | export { TextToSpeech }; 16 | -------------------------------------------------------------------------------- /src/web.ts: -------------------------------------------------------------------------------- 1 | import { WebPlugin } from '@capacitor/core'; 2 | 3 | import type { TextToSpeechPlugin, TTSOptions } from './definitions'; 4 | 5 | export class TextToSpeechWeb extends WebPlugin implements TextToSpeechPlugin { 6 | private speechSynthesis: SpeechSynthesis | null = null; 7 | private supportedVoices: SpeechSynthesisVoice[] | undefined; 8 | 9 | constructor() { 10 | super(); 11 | if ('speechSynthesis' in window) { 12 | this.speechSynthesis = window.speechSynthesis; 13 | window.addEventListener('beforeunload', () => { 14 | this.stop(); 15 | }); 16 | } 17 | } 18 | 19 | public async speak(options: TTSOptions): Promise { 20 | if (!this.speechSynthesis) { 21 | this.throwUnsupportedError(); 22 | } 23 | await this.stop(); 24 | const speechSynthesis = this.speechSynthesis; 25 | const utterance = this.createSpeechSynthesisUtterance(options); 26 | return new Promise((resolve, reject) => { 27 | utterance.onend = () => { 28 | resolve(); 29 | }; 30 | utterance.onerror = (event: any) => { 31 | reject(event); 32 | }; 33 | speechSynthesis.speak(utterance); 34 | }); 35 | } 36 | 37 | public async stop(): Promise { 38 | if (!this.speechSynthesis) { 39 | this.throwUnsupportedError(); 40 | } 41 | this.speechSynthesis.cancel(); 42 | } 43 | 44 | public async getSupportedLanguages(): Promise<{ languages: string[] }> { 45 | const voices = this.getSpeechSynthesisVoices(); 46 | const languages = voices.map((voice) => voice.lang); 47 | const filteredLanguages = languages.filter((v, i, a) => a.indexOf(v) == i); 48 | return { languages: filteredLanguages }; 49 | } 50 | 51 | public async getSupportedVoices(): Promise<{ 52 | voices: SpeechSynthesisVoice[]; 53 | }> { 54 | const voices = this.getSpeechSynthesisVoices(); 55 | return { voices }; 56 | } 57 | 58 | public async isLanguageSupported(options: { lang: string }): Promise<{ supported: boolean }> { 59 | const result = await this.getSupportedLanguages(); 60 | const isLanguageSupported = result.languages.includes(options.lang); 61 | return { supported: isLanguageSupported }; 62 | } 63 | 64 | public async openInstall(): Promise { 65 | this.throwUnimplementedError(); 66 | } 67 | 68 | private createSpeechSynthesisUtterance(options: TTSOptions): SpeechSynthesisUtterance { 69 | const voices = this.getSpeechSynthesisVoices(); 70 | const utterance = new SpeechSynthesisUtterance(); 71 | const { text, lang, rate, pitch, volume, voice } = options; 72 | if (voice) { 73 | utterance.voice = voices[voice]; 74 | } 75 | if (volume) { 76 | utterance.volume = volume >= 0 && volume <= 1 ? volume : 1; 77 | } 78 | if (rate) { 79 | utterance.rate = rate >= 0.1 && rate <= 10 ? rate : 1; 80 | } 81 | if (pitch) { 82 | utterance.pitch = pitch >= 0 && pitch <= 2 ? pitch : 2; 83 | } 84 | if (lang) { 85 | utterance.lang = lang; 86 | } 87 | utterance.text = text; 88 | return utterance; 89 | } 90 | 91 | private getSpeechSynthesisVoices(): SpeechSynthesisVoice[] { 92 | if (!this.speechSynthesis) { 93 | this.throwUnsupportedError(); 94 | } 95 | if (!this.supportedVoices || this.supportedVoices.length < 1) { 96 | this.supportedVoices = this.speechSynthesis.getVoices(); 97 | } 98 | return this.supportedVoices; 99 | } 100 | 101 | private throwUnsupportedError(): never { 102 | throw this.unavailable('SpeechSynthesis API not available in this browser.'); 103 | } 104 | 105 | private throwUnimplementedError(): never { 106 | throw this.unimplemented('Not implemented on web.'); 107 | } 108 | } 109 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "allowUnreachableCode": false, 4 | "declaration": true, 5 | "esModuleInterop": true, 6 | "inlineSources": true, 7 | "lib": ["dom", "es2017"], 8 | "module": "esnext", 9 | "moduleResolution": "node", 10 | "noFallthroughCasesInSwitch": true, 11 | "noUnusedLocals": true, 12 | "noUnusedParameters": true, 13 | "outDir": "dist/esm", 14 | "pretty": true, 15 | "sourceMap": true, 16 | "strict": true, 17 | "target": "es2017" 18 | }, 19 | "files": ["src/index.ts"] 20 | } 21 | --------------------------------------------------------------------------------