├── .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 |
--------------------------------------------------------------------------------