Compare commits

..

No commits in common. "master" and "v0.5.4" have entirely different histories.

21 changed files with 371 additions and 1273 deletions

View File

@ -1,2 +0,0 @@
[target.x86_64-pc-windows-msvc]
rustflags = ["-C", "target-feature=+crt-static"]

1
.gitattributes vendored
View File

@ -1 +0,0 @@
*.aar filter=lfs diff=lfs merge=lfs -text

View File

@ -2,54 +2,27 @@ name: Build
on:
push:
branches: [ master ]
pull_request:
branches: [ master ]
jobs:
check:
name: Check
strategy:
matrix:
os: [windows-latest, ubuntu-latest, macos-latest]
runs-on: ${{ matrix.os }}
steps:
- uses: actions/checkout@v2
- uses: Swatinem/rust-cache@v1
- run: sudo apt-get update; sudo apt-get install -y libspeechd-dev
if: ${{ runner.os == 'Linux' }}
- run: |
choco install -qy llvm
echo LIBCLANG_PATH=c:\program` files\llvm\bin >> $Env:GITHUB_ENV
if: ${{ runner.os == 'Windows' }}
- uses: actions-rs/toolchain@v1
with:
profile: minimal
toolchain: stable
components: rustfmt, clippy
override: true
- uses: actions-rs/cargo@v1
with:
command: check
args: --all-features --examples
- uses: actions-rs/cargo@v1
with:
command: fmt
args: --all -- --check
- uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --all-features
build_android:
name: Build Android
build_linux:
name: Build Linux
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
lfs: true
- uses: actions/cache@v2
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
restore-keys: ${{ runner.os }}-gradle
- run: |
./gradlew assemble
- uses: actions/checkout@v2
- run: |
sudo apt-get update
sudo apt-get install -y libspeechd-dev
cargo build --release --verbose
build_windows:
name: Build Windows
runs-on: windows-latest
steps:
- uses: actions/checkout@v2
- run: |
choco install -y llvm
cargo build --release --verbose

View File

@ -3,223 +3,78 @@ name: Release
on:
push:
tags:
- "v*"
- 'v*'
jobs:
build_linux:
name: Build Linux
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: Swatinem/rust-cache@v1
- run: |
sudo apt-get update
sudo apt-get install -y libspeechd-dev
cargo build --release
mkdir linux
mv target/release/*.so linux
- uses: actions/upload-artifact@v1
with:
name: linux
path: linux
build_linux_32:
name: Build Linux (32 bits)
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: Swatinem/rust-cache@v1
- run: |
sudo dpkg --add-architecture i386
sudo apt-get update
sudo apt-get install -y libspeechd-dev:i386 gcc-multilib
rustup target add i686-unknown-linux-gnu
cargo build --release --target i686-unknown-linux-gnu
mkdir linux-32
mv target/i686-unknown-linux-gnu/release/*.so linux-32
- uses: actions/upload-artifact@v1
with:
name: linux-32
path: linux-32
- uses: actions/checkout@v2
- run: |
sudo apt-get update
sudo apt-get install -y libspeechd-dev
cargo build --release --verbose
mv target linux
- uses: actions/upload-artifact@v1
with:
name: linux
path: linux
build_windows:
name: Build Windows
runs-on: windows-latest
env:
LIBCLANG_PATH: c:\program files\llvm\bin
steps:
- uses: actions/checkout@v2
- uses: Swatinem/rust-cache@v1
- run: |
choco install -qy llvm
cargo build --release
mkdir windows
move target\release\*.dll windows
- uses: actions/upload-artifact@v1
with:
name: windows
path: windows
build_windows_32:
name: Build Windows (32 bits)
runs-on: windows-latest
env:
LIBCLANG_PATH: c:\program files\llvm\bin
steps:
- uses: actions/checkout@v2
- uses: Swatinem/rust-cache@v1
- run: |
choco install -qy llvm
rustup target add i686-pc-windows-msvc
cargo build --release --target i686-pc-windows-msvc
mkdir windows-32
move target\i686-pc-windows-msvc\release\*.dll windows-32
- uses: actions/upload-artifact@v1
with:
name: windows-32
path: windows-32
build_uwp:
name: Build UWP
runs-on: windows-latest
env:
LIBCLANG_PATH: c:\program files\llvm\bin
steps:
- uses: actions/checkout@v2
- uses: Swatinem/rust-cache@v1
- run: |
choco install -y llvm
cargo build --release
mkdir uwp
move target\release\godot_tts.dll uwp\godot_tts.uwp.dll
- uses: actions/upload-artifact@v1
with:
name: uwp
path: uwp
build_macos:
name: Build MacOS
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- uses: Swatinem/rust-cache@v1
- run: |
cargo build --release
mkdir macos
mv target/release/*.dylib macos
- uses: actions/upload-artifact@v1
with:
name: macos
path: macos
build_android:
name: Build Android
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
with:
lfs: true
- uses: actions/cache@v2
with:
path: ~/.gradle/caches
key: ${{ runner.os }}-gradle-${{ hashFiles('**/*.gradle') }}
restore-keys: ${{ runner.os }}-gradle
- run: |
./gradlew assemble
mv build android
- uses: actions/upload-artifact@v1
with:
name: android
path: android
build_ios:
name: Build iOS
runs-on: macos-latest
steps:
- uses: actions/checkout@v2
- uses: Swatinem/rust-cache@v1
- run: |
rustup target add aarch64-apple-ios x86_64-apple-ios
cargo install cargo-lipo
cargo lipo --release
mkdir ios
mv target/universal/release/*.a ios
- uses: actions/upload-artifact@v1
with:
name: ios
path: ios
- uses: actions/checkout@v2
- run: |
choco install -y llvm
cargo build --release --verbose
move target windows
- uses: actions/upload-artifact@v1
with:
name: windows
path: windows
package:
name: Package
runs-on: ubuntu-latest
needs:
[
build_linux,
build_linux_32,
build_windows,
build_windows_32,
build_uwp,
build_macos,
build_android,
build_ios,
]
needs: [build_linux, build_windows]
steps:
- uses: actions/checkout@v2
- uses: actions/download-artifact@v1
with:
name: linux
- uses: actions/download-artifact@v1
with:
name: linux-32
- uses: actions/download-artifact@v1
with:
name: windows
- uses: actions/download-artifact@v1
with:
name: windows-32
- uses: actions/download-artifact@v1
with:
name: uwp
- uses: actions/download-artifact@v1
with:
name: macos
- uses: actions/download-artifact@v1
with:
name: android
- uses: actions/download-artifact@v1
with:
name: ios
- run: |
mkdir -p godot-tts/target/release/32
cp linux/*.so godot-tts/target/release
cp linux-32/*.so godot-tts/target/release/32
cp windows/*.dll godot-tts/target/release
cp windows-32/*.dll godot-tts/target/release/32
cp uwp/*.dll godot-tts/target/release
cp macos/*.dylib godot-tts/target/release
cp android/outputs/aar/godot-tts.aar godot-tts/
cp ios/*.a godot-tts/target/release
cp LICENSE godot-tts
cp README.md godot-tts
cp TTS.gd godot-tts.g* godot-tts
rm godot-tts/*.gdnlib
mv godot-tts/godot-tts.gdnlib.release godot-tts/godot-tts.gdnlib
zip -r9 godot-tts godot-tts
- uses: actions/create-release@v1
id: create_release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
draft: false
prerelease: false
- uses: actions/upload-release-asset@v1
id: upload-release-asset
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./godot-tts.zip
asset_name: godot-tts.zip
asset_content_type: application/zip
- uses: actions/checkout@v2
- uses: actions/download-artifact@v1
with:
name: linux
- uses: actions/download-artifact@v1
with:
name: windows
- run: |
mkdir godot-tts
cp godot-tts.gdnlib.release godot-tts/godot-tts.gdnlib
mkdir -p godot-tts/target/release
cp linux/release/*.so godot-tts/target/release
cp windows/release/*.dll godot-tts/target/release
cp LICENSE godot-tts
cp TTS.gd godot-tts.g* godot-tts
cp godot-tts.gdnlib.release godot-tts/godot-tts.gdnlib
rm godot-tts/*.release
cp -R android godot-tts
zip -r9 godot-tts godot-tts
- uses: actions/create-release@v1
id: create_release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
tag_name: ${{ github.ref }}
release_name: Release ${{ github.ref }}
draft: false
prerelease: false
- uses: actions/upload-release-asset@v1
id: upload-release-asset
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
with:
upload_url: ${{ steps.create_release.outputs.upload_url }}
asset_path: ./godot-tts.zip
asset_name: godot-tts.zip
asset_content_type: application/zip

5
.gitignore vendored
View File

@ -5,8 +5,3 @@ bazel-bin
bazel-godot-tts
bazel-out
bazel-testlogs
build
.gradle
.classpath
.project
.settings

View File

@ -5,9 +5,12 @@ authors = ["Nolan Darilek <nolan@thewordnerd.info>"]
edition = "2018"
[lib]
crate-type = ["staticlib", "cdylib"]
crate-type = ["cdylib"]
[dependencies]
env_logger = "0.10"
gdnative = "0.11"
tts = { version = "0.25", features = ["tolk"] }
env_logger = "0.7"
gdnative = "0.8"
tts = "0.3"
[target.'cfg(windows)'.dependencies]
tolk = "0.2"

View File

@ -7,13 +7,11 @@ This addon was primarily developed for the [Godot Accessibility addon](https://g
Text-to-speech is complicated, and some features may not work everywhere. Most optional features have an associated boolean property used to determine if the feature is available. Further, while I do attempt to ensure that this addon works as well as possible on all platforms, there may be bugs, and pull requests are welcome. Known supported platforms include:
* Windows
* Screen readers/SAPI via Tolk (requires `use_tolk` Cargo feature)
* WinRT
* Screen readers via [Tolk](https://github.com/dkager/tolk/)
* Native WinRT
* Linux via [Speech Dispatcher](https://freebsoft.org/speechd)
* MacOS
* AppKit on MacOS 10.13 and below
* AVFoundation on MacOS 10.14 and above, and iOS
* Web
* HTML 5
* Android
## Features
@ -33,32 +31,6 @@ The public-facing API is contained entirely within [TTS.GD](https://github.com/l
Download the [latest release](https://github.com/lightsoutgames/godot-tts/releases).
## Usage
Inside your projects root folder create a new folder named _addons_. Then extract _godot-tts.zip_ into that folder. After attaching TTS.GD to a node you can make calls to the API.
## Notes on Android
Usage on Android is a bit more complicated:
* Set up a [custom Android build](https://docs.godotengine.org/en/latest/getting_started/workflow/export/android_custom_build.html).
* Create a directory reachable at _res://android/plugins_ and place the _godot-tts.aar_ and _.godot-tts.gdap_ files from a release into that directory.
* Update _res://android/build/config.gradle_ to specify a `minSdk` of at least 21. Individual versions may vary, but `minSdk` is the critical value from this example:
```
ext.versions = [
androidGradlePlugin: '3.5.3',
compileSdk : 29,
minSdk : 21,
targetSdk : 29,
buildTools : '29.0.3',
supportCoreUtils : '1.0.0',
kotlinVersion : '1.3.61',
v4Support : '1.0.0'
]
```
## Notes on Universal Windows Platform
Godot's UWP export is [currently broken](https://github.com/godotengine/godot/issues/30558). In order to use this addon in a UWP game, do the following:

168
TTS.gd
View File

@ -1,110 +1,30 @@
tool
extends Node
signal utterance_begin(utterance)
signal utterance_end(utterance)
signal utterance_stop(utterance)
var TTS
var tts
signal done
func _init():
func _ready():
if OS.get_name() == "Server" or OS.has_feature("JavaScript"):
return
elif Engine.has_singleton("GodotTTS"):
tts = Engine.get_singleton("GodotTTS")
elif Engine.has_singleton("AndroidTTS"):
tts = Engine.get_singleton("AndroidTTS")
else:
TTS = preload("godot-tts.gdns")
if TTS and (TTS.can_instance() or Engine.editor_hint):
tts = TTS.new()
if tts:
if not tts is JNISingleton:
self.add_child(tts)
if self.are_utterance_callbacks_supported:
tts.connect("utterance_begin", self, "_on_utterance_begin")
tts.connect("utterance_end", self, "_on_utterance_end")
tts.connect("utterance_stop", self, "_on_utterance_stop")
else:
print_debug("TTS not available!")
func _ready():
pause_mode = Node.PAUSE_MODE_PROCESS
func _get_min_volume():
if OS.has_feature('JavaScript'):
return 0
else:
return 0
var min_volume setget , _get_min_volume
func _get_max_volume():
if OS.has_feature('JavaScript'):
return 1.0
else:
return 0
var max_volume setget , _get_max_volume
func _get_normal_volume():
if OS.has_feature('JavaScript'):
return 1.0
else:
return 0
var normal_volume setget , _get_normal_volume
var javascript_volume = self.normal_volume
func _set_volume(volume):
if volume < self.min_volume:
volume = self.min_volume
elif volume > self.max_volume:
volume = self.max_volume
if OS.has_feature('JavaScript'):
javascript_volume = volume
func _get_volume():
if OS.has_feature('JavaScript'):
return javascript_volume
else:
return 0
var volume setget _set_volume, _get_volume
func _get_volume_percentage():
return range_lerp(self.volume, self.min_volume, self.max_volume, 0, 100)
func _set_volume_percentage(v):
self.rate = range_lerp(v, 0, 100, self.min_volume, self.max_volume)
var volume_percentage setget _set_volume_percentage, _get_volume_percentage
func _get_normal_volume_percentage():
return range_lerp(self.normal_volume, self.min_volume, self.max_volume, 0, 100)
var normal_volume_percentage setget , _get_normal_volume_percentage
func _get_min_rate():
if OS.has_feature('JavaScript'):
return 0.1
elif Engine.has_singleton("GodotTTS"):
elif Engine.has_singleton("AndroidTTS"):
return 0.1
elif tts != null:
return tts.min_rate
@ -117,8 +37,8 @@ var min_rate setget , _get_min_rate
func _get_max_rate():
if OS.has_feature('JavaScript'):
return 10.0
elif Engine.has_singleton("GodotTTS"):
return 10
elif Engine.has_singleton("AndroidTTS"):
return 10.0
elif tts != null:
return tts.max_rate
@ -131,8 +51,8 @@ var max_rate setget , _get_max_rate
func _get_normal_rate():
if OS.has_feature('JavaScript'):
return 1.0
elif Engine.has_singleton("GodotTTS"):
return 1
elif Engine.has_singleton("AndroidTTS"):
return 1.0
elif tts != null:
return tts.normal_rate
@ -142,7 +62,7 @@ func _get_normal_rate():
var normal_rate setget , _get_normal_rate
var javascript_rate = self.normal_rate
var javascript_rate = 50
func _set_rate(rate):
@ -150,7 +70,7 @@ func _set_rate(rate):
rate = self.min_rate
elif rate > self.max_rate:
rate = self.max_rate
if Engine.has_singleton("GodotTTS"):
if Engine.has_singleton("AndroidTTS"):
return tts.set_rate(rate)
elif tts != null:
tts.rate = rate
@ -159,7 +79,7 @@ func _set_rate(rate):
func _get_rate():
if Engine.has_singleton("GodotTTS"):
if Engine.has_singleton("AndroidTTS"):
return tts.get_rate()
elif tts != null:
return tts.rate
@ -191,27 +111,24 @@ var normal_rate_percentage setget , _get_rate_percentage
func speak(text, interrupt := true):
var utterance
if tts != null:
utterance = tts.speak(text, interrupt)
tts.speak(text, interrupt)
elif OS.has_feature('JavaScript'):
var code = (
"""
let utterance = new SpeechSynthesisUtterance("%s")
utterance.rate = %s
utterance.volume = %s
"""
% [text.replace("\n", " "), javascript_rate, javascript_volume]
let utterance = new SpeechSynthesisUtterance("%s")
utterance.rate = %s
"""
% [text.replace("\n", " "), javascript_rate]
)
if interrupt:
code += """
window.speechSynthesis.cancel()
"""
window.speechSynthesis.cancel()
"""
code += "window.speechSynthesis.speak(utterance)"
JavaScript.eval(code)
else:
print_debug("%s: %s" % [text, interrupt])
return utterance
func stop():
@ -221,8 +138,8 @@ func stop():
JavaScript.eval("window.speechSynthesis.cancel()")
func _get_is_rate_supported():
if Engine.has_singleton("GodotTTS"):
func get_is_rate_supported():
if Engine.has_singleton("AndroidTTS"):
return true
elif OS.has_feature('JavaScript'):
return true
@ -232,25 +149,11 @@ func _get_is_rate_supported():
return false
var is_rate_supported setget , _get_is_rate_supported
func _get_are_utterance_callbacks_supported():
if Engine.has_singleton("GodotTTS"):
return true
elif OS.has_feature('JavaScript'):
return false
elif tts != null:
return tts.are_utterance_callbacks_supported()
else:
return false
var are_utterance_callbacks_supported setget , _get_are_utterance_callbacks_supported
var is_rate_supported setget , get_is_rate_supported
func _get_can_detect_is_speaking():
if Engine.has_singleton("GodotTTS"):
if Engine.has_singleton("AndroidTTS"):
return true
elif OS.has_feature('JavaScript'):
return true
@ -263,7 +166,7 @@ var can_detect_is_speaking setget , _get_can_detect_is_speaking
func _get_is_speaking():
if Engine.has_singleton("GodotTTS"):
if Engine.has_singleton("AndroidTTS"):
return tts.is_speaking()
elif OS.has_feature('JavaScript'):
return JavaScript.eval("window.speechSynthesis.speaking")
@ -276,7 +179,7 @@ var is_speaking setget , _get_is_speaking
func _get_can_detect_screen_reader():
if Engine.has_singleton("GodotTTS"):
if Engine.has_singleton("AndroidTTS"):
return true
elif OS.has_feature('JavaScript'):
return false
@ -289,7 +192,7 @@ var can_detect_screen_reader setget , _get_can_detect_screen_reader
func _get_has_screen_reader():
if Engine.has_singleton("GodotTTS"):
if Engine.has_singleton("AndroidTTS"):
return tts.has_screen_reader()
elif OS.has_feature('JavaScript'):
return false
@ -308,16 +211,17 @@ func singular_or_plural(count, singular, plural):
return plural
func _on_utterance_begin(utterance):
emit_signal("utterance_begin", utterance)
var _was_speaking = false
func _on_utterance_end(utterance):
emit_signal("utterance_end", utterance)
func _on_utterance_stop(utterance):
emit_signal("utterance_stop", utterance)
func _process(delta):
if self.is_speaking:
print("xxx Speaking")
_was_speaking = true
elif _was_speaking:
print("xxx Done")
emit_signal("done")
_was_speaking = false
func _exit_tree():

View File

@ -0,0 +1,91 @@
package games.lightsout.godot.tts;
import java.util.List;
import org.godotengine.godot.Godot;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.app.Activity;
import android.content.Context;
import android.speech.tts.TextToSpeech;
import android.view.accessibility.AccessibilityManager;
public class TTS extends Godot.SingletonBase implements TextToSpeech.OnInitListener {
protected Activity appActivity;
protected Context appContext;
private Godot activity = null;
private int instanceId = 0;
private TextToSpeech tts = null;
private float rate = 1f;
private Integer utteranceId = 0;
public void speak(String text, boolean interrupt) {
int mode = TextToSpeech.QUEUE_ADD;
if (interrupt)
mode = TextToSpeech.QUEUE_FLUSH;
tts.speak(text, mode, null, this.utteranceId.toString());
this.utteranceId++;
}
public void stop() {
tts.stop();
}
public float get_rate() {
return this.rate;
}
public void set_rate(float rate) {
this.rate = rate;
tts.setSpeechRate(rate);
}
public boolean is_speaking() {
return tts.isSpeaking();
}
public boolean has_screen_reader() {
AccessibilityManager accessibilityManager = (AccessibilityManager) appContext
.getSystemService(Context.ACCESSIBILITY_SERVICE);
if (accessibilityManager != null) {
List<AccessibilityServiceInfo> screenReaders = accessibilityManager
.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_SPOKEN);
return screenReaders.size() != 0;
} else {
return false;
}
}
public void getInstanceId(int pInstanceId) {
// You will need to call this method from Godot and pass in the
// get_instance_id().
instanceId = pInstanceId;
}
static public Godot.SingletonBase initialize(Activity p_activity) {
return new TTS(p_activity);
}
public TTS(Activity p_activity) {
this.activity = (Godot) p_activity;
this.appActivity = p_activity;
this.appContext = appActivity.getApplicationContext();
this.tts = new TextToSpeech(this.appContext, this);
// Register class name and functions to bind.
registerClass("AndroidTTS", new String[] { "speak", "stop", "get_rate", "set_rate", "has_screen_reader",
"is_speaking", "getInstanceId" });
this.activity.runOnUiThread(new Runnable() {
public void run() {
}
});
}
public void onInit(int status) {
}
}

View File

@ -1,41 +0,0 @@
buildscript {
repositories {
google()
jcenter()
}
dependencies {
classpath "com.android.tools.build:gradle:4.0.0"
}
}
apply plugin: 'com.android.library'
repositories {
google()
jcenter()
}
ext.pluginVersionCode = 1
ext.pluginVersionName = "0.6.0"
android {
compileSdkVersion 29
buildToolsVersion "29.0.3"
defaultConfig {
minSdkVersion 21
targetSdkVersion 29
versionCode pluginVersionCode
versionName pluginVersionName
}
libraryVariants.all { variant ->
variant.outputs.all { output ->
output.outputFileName = "godot-tts.aar"
}
}
}
dependencies {
compileOnly fileTree(dir: 'libs')
}

View File

@ -1,5 +0,0 @@
[config]
name="GodotTTS"
binary_type="local"
binary="godot-tts.aar"

View File

@ -2,17 +2,12 @@
Server.64="res://addons/godot-tts/target/debug/libgodot_tts.so"
Windows.64="res://addons/godot-tts/target/debug/godot_tts.dll"
Windows.32="res://addons/godot-tts/target/debug/32/godot_tts.dll"
UWP.64="res://addons/godot-tts/target/debug/godot_tts.dll"
X11.64="res://addons/godot-tts/target/debug/libgodot_tts.so"
X11.32="res://addons/godot-tts/target/debug/32/libgodot_tts.so"
OSX.64="res://addons/godot-tts/target/debug/libgodot_tts.dylib"
IOS="res://addons/godot-tts/target/debug/libgodot_tts.a"
[dependencies]
Windows.64=[ "res://addons/godot-tts/target/debug/nvdaControllerClient64.dll", "res://addons/godot-tts/target/debug/SAAPI64.dll" ]
Windows.32=[ "res://addons/godot-tts/target/debug/32/dolapi32.dll", "res://addons/godot-tts/target/debug/32/nvdaControllerClient32.dll", "res://addons/godot-tts/target/debug/32/SAAPI32.dll" ]
X11.64=[ ]
[general]

View File

@ -2,17 +2,13 @@
Server.64="res://addons/godot-tts/target/release/libgodot_tts.so"
Windows.64="res://addons/godot-tts/target/release/godot_tts.dll"
Windows.32="res://addons/godot-tts/target/release/32/godot_tts.dll"
UWP.64="res://addons/godot-tts/target/release/godot_tts.uwp.dll"
UWP.64="res://addons/godot-tts/target/release/godot_tts.dll"
X11.64="res://addons/godot-tts/target/release/libgodot_tts.so"
X11.32="res://addons/godot-tts/target/release/32/libgodot_tts.so"
OSX.64="res://addons/godot-tts/target/release/libgodot_tts.dylib"
IOS="res://addons/godot-tts/target/release/libgodot_tts.a"
[dependencies]
Windows.64=[ "res://addons/godot-tts/target/release/nvdaControllerClient64.dll", "res://addons/godot-tts/target/release/SAAPI64.dll" ]
Windows.32=[ "res://addons/godot-tts/target/release/32/dolapi32.dll", "res://addons/godot-tts/target/release/32/nvdaControllerClient32.dll", "res://addons/godot-tts/target/release/32/SAAPI32.dll" ]
Windows.64=["res://addons/godot-tts/target/release/nvdaControllerClient64.dll", "res://addons/godot-tts/target/release/SAAPI64.dll"]
X11.64=[ ]
[general]

Binary file not shown.

View File

@ -1,5 +0,0 @@
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists

234
gradlew vendored
View File

@ -1,234 +0,0 @@
#!/bin/sh
#
# Copyright © 2015-2021 the original authors.
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# https://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
#
##############################################################################
#
# Gradle start up script for POSIX generated by Gradle.
#
# Important for running:
#
# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is
# noncompliant, but you have some other compliant shell such as ksh or
# bash, then to run this script, type that shell name before the whole
# command line, like:
#
# ksh Gradle
#
# Busybox and similar reduced shells will NOT work, because this script
# requires all of these POSIX shell features:
# * functions;
# * expansions «$var», «${var}», «${var:-default}», «${var+SET}»,
# «${var#prefix}», «${var%suffix}», and «$( cmd )»;
# * compound commands having a testable exit status, especially «case»;
# * various built-in commands including «command», «set», and «ulimit».
#
# Important for patching:
#
# (2) This script targets any POSIX shell, so it avoids extensions provided
# by Bash, Ksh, etc; in particular arrays are avoided.
#
# The "traditional" practice of packing multiple parameters into a
# space-separated string is a well documented source of bugs and security
# problems, so this is (mostly) avoided, by progressively accumulating
# options in "$@", and eventually passing that to Java.
#
# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS,
# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly;
# see the in-line comments for details.
#
# There are tweaks for specific operating systems such as AIX, CygWin,
# Darwin, MinGW, and NonStop.
#
# (3) This script is generated from the Groovy template
# https://github.com/gradle/gradle/blob/master/subprojects/plugins/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt
# within the Gradle project.
#
# You can find Gradle at https://github.com/gradle/gradle/.
#
##############################################################################
# Attempt to set APP_HOME
# Resolve links: $0 may be a link
app_path=$0
# Need this for daisy-chained symlinks.
while
APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path
[ -h "$app_path" ]
do
ls=$( ls -ld "$app_path" )
link=${ls#*' -> '}
case $link in #(
/*) app_path=$link ;; #(
*) app_path=$APP_HOME$link ;;
esac
done
APP_HOME=$( cd "${APP_HOME:-./}" && pwd -P ) || exit
APP_NAME="Gradle"
APP_BASE_NAME=${0##*/}
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD=maximum
warn () {
echo "$*"
} >&2
die () {
echo
echo "$*"
echo
exit 1
} >&2
# OS specific support (must be 'true' or 'false').
cygwin=false
msys=false
darwin=false
nonstop=false
case "$( uname )" in #(
CYGWIN* ) cygwin=true ;; #(
Darwin* ) darwin=true ;; #(
MSYS* | MINGW* ) msys=true ;; #(
NONSTOP* ) nonstop=true ;;
esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
# IBM's JDK on AIX uses strange locations for the executables
JAVACMD=$JAVA_HOME/jre/sh/java
else
JAVACMD=$JAVA_HOME/bin/java
fi
if [ ! -x "$JAVACMD" ] ; then
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
else
JAVACMD=java
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
Please set the JAVA_HOME variable in your environment to match the
location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then
case $MAX_FD in #(
max*)
MAX_FD=$( ulimit -H -n ) ||
warn "Could not query maximum file descriptor limit"
esac
case $MAX_FD in #(
'' | soft) :;; #(
*)
ulimit -n "$MAX_FD" ||
warn "Could not set maximum file descriptor limit to $MAX_FD"
esac
fi
# Collect all arguments for the java command, stacking in reverse order:
# * args from the command line
# * the main class name
# * -classpath
# * -D...appname settings
# * --module-path (only if needed)
# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables.
# For Cygwin or MSYS, switch paths to Windows format before running java
if "$cygwin" || "$msys" ; then
APP_HOME=$( cygpath --path --mixed "$APP_HOME" )
CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" )
JAVACMD=$( cygpath --unix "$JAVACMD" )
# Now convert the arguments - kludge to limit ourselves to /bin/sh
for arg do
if
case $arg in #(
-*) false ;; # don't mess with options #(
/?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath
[ -e "$t" ] ;; #(
*) false ;;
esac
then
arg=$( cygpath --path --ignore --mixed "$arg" )
fi
# Roll the args list around exactly as many times as the number of
# args, so each arg winds up back in the position where it started, but
# possibly modified.
#
# NB: a `for` loop captures its iteration list before it begins, so
# changing the positional parameters here affects neither the number of
# iterations, nor the values presented in `arg`.
shift # remove old arg
set -- "$@" "$arg" # push replacement arg
done
fi
# Collect all arguments for the java command;
# * $DEFAULT_JVM_OPTS, $JAVA_OPTS, and $GRADLE_OPTS can contain fragments of
# shell script including quotes and variable substitutions, so put them in
# double quotes to make sure that they get re-expanded; and
# * put everything else in single quotes, so that it's not re-expanded.
set -- \
"-Dorg.gradle.appname=$APP_BASE_NAME" \
-classpath "$CLASSPATH" \
org.gradle.wrapper.GradleWrapperMain \
"$@"
# Use "xargs" to parse quoted args.
#
# With -n1 it outputs one arg per line, with the quotes and backslashes removed.
#
# In Bash we could simply go:
#
# readarray ARGS < <( xargs -n1 <<<"$var" ) &&
# set -- "${ARGS[@]}" "$@"
#
# but POSIX shell has neither arrays nor command substitution, so instead we
# post-process each arg (as a line of input to sed) to backslash-escape any
# character that might be a shell metacharacter, then use eval to reverse
# that process (while maintaining the separation between arguments), and wrap
# the whole thing up as a single "set" statement.
#
# This will of course break if any of these variables contains a newline or
# an unmatched quote.
#
eval "set -- $(
printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" |
xargs -n1 |
sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' |
tr '\n' ' '
)" '"$@"'
exec "$JAVACMD" "$@"

89
gradlew.bat vendored
View File

@ -1,89 +0,0 @@
@rem
@rem Copyright 2015 the original author or authors.
@rem
@rem Licensed under the Apache License, Version 2.0 (the "License");
@rem you may not use this file except in compliance with the License.
@rem You may obtain a copy of the License at
@rem
@rem https://www.apache.org/licenses/LICENSE-2.0
@rem
@rem Unless required by applicable law or agreed to in writing, software
@rem distributed under the License is distributed on an "AS IS" BASIS,
@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
@rem See the License for the specific language governing permissions and
@rem limitations under the License.
@rem
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@rem Gradle startup script for Windows
@rem
@rem ##########################################################################
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
set DIRNAME=%~dp0
if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
@rem Resolve any "." and ".." in APP_HOME to make it shorter.
for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:findJavaFromJavaHome
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
echo.
echo Please set the JAVA_HOME variable in your environment to match the
echo location of your Java installation.
goto fail
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
@rem Execute Gradle
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
if "%ERRORLEVEL%"=="0" goto mainEnd
:fail
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
rem the _cmd.exe /c_ return code!
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
exit /b 1
:mainEnd
if "%OS%"=="Windows_NT" endlocal
:omega

BIN
libs/godot-lib.3.2.3.stable.release.aar (Stored with Git LFS)

Binary file not shown.

View File

@ -1,329 +1,152 @@
use std::sync::mpsc::{channel, Receiver};
use gdnative::prelude::*;
use tts::{Features, Tts, UtteranceId};
#[derive(NativeClass)]
struct Utterance(pub(crate) Option<UtteranceId>);
#[methods]
impl Utterance {
fn new(_owner: &Reference) -> Self {
Self(None)
}
}
#[allow(clippy::enum_variant_names)]
enum Msg {
UtteranceBegin(UtteranceId),
UtteranceEnd(UtteranceId),
UtteranceStop(UtteranceId),
}
#[allow(clippy::upper_case_acronyms)]
#[derive(NativeClass)]
#[inherit(Node)]
#[register_with(Self::register)]
struct TTS(Tts, Receiver<Msg>);
#[methods]
impl TTS {
fn new(owner: &Node) -> Self {
owner.set_pause_mode(2);
let tts = Tts::default().expect("Failed to initialize TTS");
let (tx, rx) = channel();
let Features {
utterance_callbacks,
..
} = tts.supported_features();
if utterance_callbacks {
let tx_end = tx.clone();
let tx_stop = tx.clone();
tts.on_utterance_begin(Some(Box::new(move |utterance| {
tx.send(Msg::UtteranceBegin(utterance))
.expect("Failed to send UtteranceBegin");
})))
.expect("Failed to set utterance_begin callback");
tts.on_utterance_end(Some(Box::new(move |utterance| {
tx_end
.send(Msg::UtteranceEnd(utterance))
.expect("Failed to send UtteranceEnd");
})))
.expect("Failed to set utterance_end callback");
tts.on_utterance_stop(Some(Box::new(move |utterance| {
tx_stop
.send(Msg::UtteranceStop(utterance))
.expect("Failed to send UtteranceStop");
})))
.expect("Failed to set utterance_stop callback");
}
Self(tts, rx)
}
fn register(builder: &ClassBuilder<Self>) {
builder
.property("volume")
.with_getter(|this: &TTS, _| match this.0.get_volume() {
Ok(volume) => volume,
_ => 0.,
})
.with_setter(|this: &mut TTS, _, v: f32| {
let Features {
volume: volume_supported,
..
} = this.0.supported_features();
if volume_supported {
let mut v = v;
if v < this.0.min_volume() {
v = this.0.min_volume();
} else if v > this.0.max_volume() {
v = this.0.max_volume();
}
this.0.set_volume(v).expect("Failed to set volume");
}
})
.done();
builder
.property("min_volume")
.with_getter(|this: &TTS, _| {
let Features {
volume: volume_supported,
..
} = this.0.supported_features();
if volume_supported {
this.0.min_volume()
} else {
0.
}
})
.done();
builder
.property("max_volume")
.with_getter(|this: &TTS, _| {
let Features {
volume: volume_supported,
..
} = this.0.supported_features();
if volume_supported {
this.0.max_volume()
} else {
0.
}
})
.done();
builder
.property("normal_volume")
.with_getter(|this: &TTS, _| {
let Features {
volume: volume_supported,
..
} = this.0.supported_features();
if volume_supported {
this.0.normal_volume()
} else {
0.
}
})
.done();
builder
.property("rate")
.with_getter(|this: &TTS, _| match this.0.get_rate() {
Ok(rate) => rate,
_ => 0.,
})
.with_setter(|this: &mut TTS, _, v: f32| {
let Features {
rate: rate_supported,
..
} = this.0.supported_features();
if rate_supported {
let mut v = v;
if v < this.0.min_rate() {
v = this.0.min_rate();
} else if v > this.0.max_rate() {
v = this.0.max_rate();
}
this.0.set_rate(v).expect("Failed to set rate");
}
})
.done();
builder
.property("min_rate")
.with_getter(|this: &TTS, _| {
let Features {
rate: rate_supported,
..
} = this.0.supported_features();
if rate_supported {
this.0.min_rate()
} else {
0.
}
})
.done();
builder
.property("max_rate")
.with_getter(|this: &TTS, _| {
let Features {
rate: rate_supported,
..
} = this.0.supported_features();
if rate_supported {
this.0.max_rate()
} else {
0.
}
})
.done();
builder
.property("normal_rate")
.with_getter(|this: &TTS, _| {
let Features {
rate: rate_supported,
..
} = this.0.supported_features();
if rate_supported {
this.0.normal_rate()
} else {
0.
}
})
.done();
builder
.property("can_detect_screen_reader")
.with_getter(|_: &TTS, _| cfg!(windows))
.done();
builder
.property("has_screen_reader")
.with_getter(|_, _| Tts::screen_reader_available())
.done();
builder
.property("can_detect_is_speaking")
.with_getter(|this: &TTS, _| {
let Features {
is_speaking: is_speaking_supported,
..
} = this.0.supported_features();
is_speaking_supported
})
.done();
builder
.property("is_speaking")
.with_getter(|this: &TTS, _| {
let Features {
is_speaking: is_speaking_supported,
..
} = this.0.supported_features();
if is_speaking_supported {
this.0
.is_speaking()
.expect("Failed to determine if speaking")
} else {
false
}
})
.done();
builder
.signal("utterance_begin")
.with_param_custom(SignalParam {
name: "utterance".into(),
default: Variant::default(),
export_info: ExportInfo::new(VariantType::Object),
usage: PropertyUsage::DEFAULT,
})
.done();
builder
.signal("utterance_end")
.with_param_custom(SignalParam {
name: "utterance".into(),
default: Variant::default(),
export_info: ExportInfo::new(VariantType::Object),
usage: PropertyUsage::DEFAULT,
})
.done();
builder
.signal("utterance_stop")
.with_param_custom(SignalParam {
name: "utterance".into(),
default: Variant::default(),
export_info: ExportInfo::new(VariantType::Object),
usage: PropertyUsage::DEFAULT,
})
.done();
}
#[method]
fn speak(&mut self, message: String, interrupt: bool) -> Variant {
if let Ok(id) = self.0.speak(message, interrupt) {
let utterance: Instance<Utterance, Unique> = Instance::new();
if id.is_some() {
utterance
.map_mut(|u, _| u.0 = id)
.expect("Failed to set utterance ID");
}
utterance.owned_to_variant()
} else {
Variant::default()
}
}
#[method]
fn stop(&mut self) {
self.0.stop().expect("Failed to stop");
}
#[method]
fn is_rate_supported(&mut self) -> bool {
let Features {
rate: rate_supported,
..
} = self.0.supported_features();
rate_supported
}
#[method]
fn are_utterance_callbacks_supported(&mut self) -> bool {
let Features {
utterance_callbacks: supported,
..
} = self.0.supported_features();
supported
}
#[method]
fn _process(&mut self, #[base] base: &Node, _delta: f32) {
if let Ok(msg) = self.1.try_recv() {
match msg {
Msg::UtteranceBegin(utterance_id) => {
let utterance: Instance<Utterance, Unique> = Instance::new();
utterance
.map_mut(|u, _| u.0 = Some(utterance_id))
.expect("Failed to set utterance ID");
base.emit_signal("utterance_begin", &[utterance.owned_to_variant()]);
}
Msg::UtteranceEnd(utterance_id) => {
let utterance: Instance<Utterance, Unique> = Instance::new();
utterance
.map_mut(|u, _| u.0 = Some(utterance_id))
.expect("Failed to set utterance ID");
base.emit_signal("utterance_end", &[utterance.owned_to_variant()]);
}
Msg::UtteranceStop(utterance_id) => {
let utterance: Instance<Utterance, Unique> = Instance::new();
utterance
.map_mut(|u, _| u.0 = Some(utterance_id))
.expect("Failed to set utterance ID");
base.emit_signal("utterance_stop", &[utterance.owned_to_variant()]);
}
}
}
}
}
fn init(handle: InitHandle) {
env_logger::init();
handle.add_tool_class::<Utterance>();
handle.add_class::<TTS>();
}
godot_init!(init);
use gdnative::init::*;
use gdnative::*;
use tts::{Features, TTS as Tts};
#[derive(NativeClass)]
#[inherit(Node)]
#[register_with(Self::register_properties)]
struct TTS(Tts);
#[methods]
impl TTS {
fn _init(_owner: gdnative::Node) -> Self {
let tts = Tts::default().unwrap();
Self(tts)
}
fn register_properties(builder: &ClassBuilder<Self>) {
builder
.add_property("rate")
.with_getter(|this: &TTS, _| match this.0.get_rate() {
Ok(rate) => rate,
_ => 0.,
})
.with_setter(|this: &mut TTS, _, v: f32| {
let Features {
rate: rate_supported,
..
} = this.0.supported_features();
if rate_supported {
let mut v = v;
if v < this.0.min_rate() {
v = this.0.min_rate();
} else if v > this.0.max_rate() {
v = this.0.max_rate();
}
this.0.set_rate(v).unwrap();
}
})
.done();
builder
.add_property("min_rate")
.with_getter(|this: &TTS, _| {
let Features {
rate: rate_supported,
..
} = this.0.supported_features();
if rate_supported {
this.0.min_rate()
} else {
0.
}
})
.done();
builder
.add_property("max_rate")
.with_getter(|this: &TTS, _| {
let Features {
rate: rate_supported,
..
} = this.0.supported_features();
if rate_supported {
this.0.max_rate()
} else {
0.
}
})
.done();
builder
.add_property("normal_rate")
.with_getter(|this: &TTS, _| {
let Features {
rate: rate_supported,
..
} = this.0.supported_features();
if rate_supported {
this.0.normal_rate()
} else {
0.
}
})
.done();
builder
.add_property("can_detect_screen_reader")
.with_getter(|_: &TTS, _| if cfg!(windows) { true } else { false })
.done();
#[allow(unreachable_code)]
builder
.add_property("has_screen_reader")
.with_getter(|_: &TTS, _| {
#[cfg(windows)]
{
let tolk = tolk::Tolk::new();
return tolk.detect_screen_reader().is_some();
}
false
})
.done();
builder
.add_property("can_detect_is_speaking")
.with_getter(|this: &TTS, _| {
let Features {
is_speaking: is_speaking_supported,
..
} = this.0.supported_features();
return is_speaking_supported;
})
.done();
builder
.add_property("is_speaking")
.with_getter(|this: &TTS, _| {
let Features {
is_speaking: is_speaking_supported,
..
} = this.0.supported_features();
if is_speaking_supported {
return this.0.is_speaking().unwrap();
} else {
return false;
}
})
.done();
}
#[export]
fn speak(&mut self, _owner: Node, message: GodotString, interrupt: bool) {
let message = message.to_string();
self.0.speak(message, interrupt).unwrap();
}
#[export]
fn stop(&mut self, _owner: Node) {
self.0.stop().unwrap();
}
#[export]
fn is_rate_supported(&mut self, _owner: Node) -> bool {
let Features {
rate: rate_supported,
..
} = self.0.supported_features();
rate_supported
}
}
fn init(handle: gdnative::init::InitHandle) {
env_logger::init();
handle.add_class::<TTS>();
}
godot_gdnative_init!();
godot_nativescript_init!(init);
godot_gdnative_terminate!();

View File

@ -1,6 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="games.lightsout.godot.tts">
<application>
<meta-data android:name="org.godotengine.plugin.v1.GodotTTS" android:value="games.lightsout.godot.tts.TTS" />
</application>
</manifest>

View File

@ -1,123 +0,0 @@
package games.lightsout.godot.tts;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.godotengine.godot.Godot;
import org.godotengine.godot.plugin.GodotPlugin;
import org.godotengine.godot.plugin.SignalInfo;
import android.accessibilityservice.AccessibilityServiceInfo;
import android.content.Context;
import android.speech.tts.TextToSpeech;
import android.speech.tts.UtteranceProgressListener;
import android.view.accessibility.AccessibilityManager;
public class TTS extends GodotPlugin implements TextToSpeech.OnInitListener {
private TextToSpeech tts = null;
private float volume = 1f;
private float rate = 1f;
private Integer utteranceId = 0;
public int speak(String text, boolean interrupt) {
int mode = TextToSpeech.QUEUE_ADD;
if (interrupt)
mode = TextToSpeech.QUEUE_FLUSH;
tts.speak(text, mode, null, this.utteranceId.toString());
int rv = this.utteranceId.intValue();
this.utteranceId++;
return rv;
}
public void stop() {
tts.stop();
}
public float get_volume() {
return this.volume;
}
public void set_volume(float volume) {
this.volume = volume;
tts.Engine.KEY_PARAM_VOLUME = volume;
}
public float get_rate() {
return this.rate;
}
public void set_rate(float rate) {
this.rate = rate;
tts.setSpeechRate(rate);
}
public boolean is_speaking() {
return tts.isSpeaking();
}
public boolean has_screen_reader() {
AccessibilityManager accessibilityManager = (AccessibilityManager) getActivity()
.getSystemService(Context.ACCESSIBILITY_SERVICE);
if (accessibilityManager != null) {
List<AccessibilityServiceInfo> screenReaders = accessibilityManager
.getEnabledAccessibilityServiceList(AccessibilityServiceInfo.FEEDBACK_SPOKEN);
return screenReaders.size() != 0;
} else {
return false;
}
}
class Listener extends UtteranceProgressListener {
public void onStart(String utteranceId) {
Integer id = Integer.parseInt(utteranceId);
TTS.this.emitSignal("utterance_begin", id);
}
public void onStop(String utteranceId, Boolean interrupted) {
Integer id = Integer.parseInt(utteranceId);
TTS.this.emitSignal("utterance_stop", id);
}
public void onDone(String utteranceId) {
Integer id = Integer.parseInt(utteranceId);
TTS.this.emitSignal("utterance_end", id);
}
public void onError(String utteranceId) {
Integer id = Integer.parseInt(utteranceId);
TTS.this.emitSignal("utterance_end", id);
}
}
public TTS(Godot godot) {
super(godot);
this.tts = new TextToSpeech(this.getActivity(), this);
tts.setOnUtteranceProgressListener(new Listener());
}
@Override
public String getPluginName() {
return "GodotTTS";
}
@Override
public List<String> getPluginMethods() {
return Arrays.asList("speak", "stop", "get_rate", "set_rate", "has_screen_reader", "is_speaking");
}
@Override
public Set<SignalInfo> getPluginSignals() {
SignalInfo begin = new SignalInfo("utterance_begin", Integer.class);
SignalInfo end = new SignalInfo("utterance_end", Integer.class);
SignalInfo stop = new SignalInfo("utterance_stop", Integer.class);
return new HashSet(Arrays.asList(begin, end, stop));
}
public void onInit(int status) {
}
}