1
0
mirror of https://github.com/ndarilek/tts-rs.git synced 2024-09-28 23:29:38 +00:00

Compare commits

..

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

18 changed files with 301 additions and 218 deletions

View File

@ -6,16 +6,98 @@ on:
- "v*" - "v*"
jobs: jobs:
check:
name: Check
strategy:
matrix:
os: [windows-latest, ubuntu-22.04, 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' }}
- 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
if: ${{ runner.os != 'Linux' }}
- uses: actions-rs/cargo@v1
with:
command: check
args: --no-default-features --examples
if: ${{ runner.os == 'Linux' }}
- 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
if: ${{ runner.os != 'Linux' }}
- uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --no-default-features
if: ${{ runner.os == 'Linux' }}
check_web:
name: Check Web
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v2
- uses: Swatinem/rust-cache@v1
- uses: actions-rs/toolchain@v1
with:
target: wasm32-unknown-unknown
profile: minimal
toolchain: stable
components: rustfmt, clippy
override: true
- uses: actions-rs/cargo@v1
with:
command: check
args: --all-features --examples --target wasm32-unknown-unknown
- 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 --target wasm32-unknown-unknown
- uses: actions-rs/install@v0.1
with:
crate: cargo-make
- uses: actions-rs/cargo@v1
with:
command: make
args: build-web-example
publish: publish:
name: Publish name: Publish
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
needs: [check, check_web]
env: env:
CARGO_TOKEN: ${{ secrets.CARGO_TOKEN }} CARGO_TOKEN: ${{ secrets.CARGO_TOKEN }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v2
- uses: Swatinem/rust-cache@v1
- uses: actions-rs/toolchain@v1
with:
target: wasm32-unknown-unknown
profile: minimal
toolchain: stable
override: true
- run: | - run: |
sudo apt-get update sudo apt-get update
sudo apt-get install -y libspeechd-dev sudo apt-get install -y libspeechd-dev
cargo login $CARGO_TOKEN cargo login $CARGO_TOKEN
rustup toolchain install stable cargo publish --no-default-features
cargo publish

View File

@ -5,17 +5,6 @@ on:
pull_request: pull_request:
jobs: jobs:
check_formatting:
name: Check Formatting
runs-on: ubuntu-22.04
steps:
- uses: actions/checkout@v4
- run: |
rustup toolchain install stable
cargo fmt --all --check
cd examples/web
cargo fmt --all --check
check: check:
name: Check name: Check
strategy: strategy:
@ -23,40 +12,106 @@ jobs:
os: [windows-latest, ubuntu-22.04, macos-latest] os: [windows-latest, ubuntu-22.04, macos-latest]
runs-on: ${{ matrix.os }} runs-on: ${{ matrix.os }}
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v2
- uses: Swatinem/rust-cache@v1
- run: sudo apt-get update; sudo apt-get install -y libspeechd-dev - run: sudo apt-get update; sudo apt-get install -y libspeechd-dev
if: ${{ runner.os == 'Linux' }} if: ${{ runner.os == 'Linux' }}
- run: | - uses: actions-rs/toolchain@v1
rustup toolchain install stable with:
cargo clippy --all-targets profile: minimal
toolchain: stable
components: rustfmt, clippy
override: true
- uses: actions-rs/cargo@v1
with:
command: check
args: --all-features --examples
if: ${{ runner.os != 'Linux' }}
- uses: actions-rs/cargo@v1
with:
command: check
args: --no-default-features --examples
if: ${{ runner.os == 'Linux' }}
- 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
if: ${{ runner.os != 'Linux' }}
- uses: actions-rs/clippy-check@v1
with:
token: ${{ secrets.GITHUB_TOKEN }}
args: --no-default-features
if: ${{ runner.os == 'Linux' }}
check_web: check_web:
name: Check Web name: Check Web
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v2
- run: | - uses: Swatinem/rust-cache@v1
rustup target add wasm32-unknown-unknown - uses: actions-rs/toolchain@v1
rustup toolchain install stable with:
cargo clippy --all-targets --target wasm32-unknown-unknown target: wasm32-unknown-unknown
profile: minimal
toolchain: stable
components: rustfmt, clippy
override: true
- uses: actions-rs/cargo@v1
with:
command: check
args: --all-features --examples --target wasm32-unknown-unknown
- 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 --target wasm32-unknown-unknown
check_android: check_android:
name: Check Android name: Check Android
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v2
- run: | - uses: Swatinem/rust-cache@v1
rustup target add aarch64-linux-android - uses: actions-rs/toolchain@v1
rustup toolchain install stable with:
cargo clippy --all-targets --target aarch64-linux-android profile: minimal
toolchain: stable
components: rustfmt, clippy
override: true
- uses: actions-rs/install@v0.1
with:
crate: cargo-apk
# use-tool-cache: true
- run: rustup target add aarch64-linux-android armv7-linux-androideabi i686-linux-android x86_64-linux-android
- uses: actions-rs/cargo@v1
with:
command: apk
args: build
check_web_example: check_web_example:
name: Check Web Example name: Check Web Example
runs-on: ubuntu-22.04 runs-on: ubuntu-22.04
steps: steps:
- uses: actions/checkout@v4 - uses: actions/checkout@v2
- run: | - uses: Swatinem/rust-cache@v1
rustup target add wasm32-unknown-unknown - uses: actions-rs/toolchain@v1
rustup toolchain install stable with:
cd examples/web target: wasm32-unknown-unknown
cargo build --target wasm32-unknown-unknown profile: minimal
toolchain: stable
components: rustfmt, clippy
override: true
- uses: actions-rs/install@v0.1
with:
crate: cargo-make
- uses: actions-rs/cargo@v1
with:
command: make
args: build-web-example

View File

@ -1,10 +1,9 @@
[package] [package]
name = "tts" name = "tts"
version = "0.26.3" version = "0.24.0"
authors = ["Nolan Darilek <nolan@thewordnerd.info>"] authors = ["Nolan Darilek <nolan@thewordnerd.info>"]
repository = "https://github.com/ndarilek/tts-rs" repository = "https://github.com/ndarilek/tts-rs"
description = "High-level Text-To-Speech (TTS) interface" description = "High-level Text-To-Speech (TTS) interface"
documentation = "https://docs.rs/tts"
license = "MIT" license = "MIT"
exclude = ["*.cfg", "*.yml"] exclude = ["*.cfg", "*.yml"]
edition = "2021" edition = "2021"
@ -13,35 +12,27 @@ edition = "2021"
crate-type = ["lib", "cdylib", "staticlib"] crate-type = ["lib", "cdylib", "staticlib"]
[features] [features]
speech_dispatcher_0_9 = ["speech-dispatcher/0_9"]
speech_dispatcher_0_10 = ["speech-dispatcher/0_10"] speech_dispatcher_0_10 = ["speech-dispatcher/0_10"]
speech_dispatcher_0_11 = ["speech-dispatcher/0_11"] speech_dispatcher_0_11 = ["speech-dispatcher/0_11"]
default = ["speech_dispatcher_0_11"] default = ["speech_dispatcher_0_11"]
[dependencies] [dependencies]
dyn-clonable = "0.9" dyn-clonable = "0.9"
oxilangtag = "0.1"
lazy_static = "1" lazy_static = "1"
log = "0.4" log = "0.4"
serde = { version = "1", optional = true, features = ["derive"] } serde = { version = "1", optional = true, features = ["derive"] }
thiserror = "1" thiserror = "1"
unic-langid = "0.9.0"
[dev-dependencies] [dev-dependencies]
env_logger = "0.11" env_logger = "0.9"
[target.'cfg(windows)'.dependencies] [target.'cfg(windows)'.dependencies]
tolk = { version = "0.5", optional = true } tolk = { version = "0.5", optional = true }
windows = { version = "0.58", features = [ windows = { version = "0.39", features = ["Foundation", "Foundation_Collections", "Media_Core", "Media_Playback", "Media_SpeechSynthesis", "Storage_Streams"] }
"Foundation",
"Foundation_Collections",
"Media_Core",
"Media_Playback",
"Media_SpeechSynthesis",
"Storage_Streams",
] }
[target.'cfg(target_os = "linux")'.dependencies] [target.'cfg(target_os = "linux")'.dependencies]
speech-dispatcher = { version = "0.16", default-features = false } speech-dispatcher = { version = "0.15", default-features = false }
[target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies] [target.'cfg(any(target_os = "macos", target_os = "ios"))'.dependencies]
cocoa-foundation = "0.1" cocoa-foundation = "0.1"
@ -51,21 +42,11 @@ objc = { version = "0.2", features = ["exception"] }
[target.wasm32-unknown-unknown.dependencies] [target.wasm32-unknown-unknown.dependencies]
wasm-bindgen = "0.2" wasm-bindgen = "0.2"
web-sys = { version = "0.3", features = [ web-sys = { version = "0.3", features = ["EventTarget", "SpeechSynthesis", "SpeechSynthesisErrorCode", "SpeechSynthesisErrorEvent", "SpeechSynthesisEvent", "SpeechSynthesisUtterance", "SpeechSynthesisVoice", "Window", ] }
"EventTarget",
"SpeechSynthesis",
"SpeechSynthesisErrorCode",
"SpeechSynthesisErrorEvent",
"SpeechSynthesisEvent",
"SpeechSynthesisUtterance",
"SpeechSynthesisVoice",
"Window",
] }
[target.'cfg(target_os="android")'.dependencies] [target.'cfg(target_os="android")'.dependencies]
jni = "0.21" jni = "0.19"
ndk-context = "0.1" ndk-glue = "0.6"
[package.metadata.docs.rs] [package.metadata.docs.rs]
no-default-features = true no-default-features = true
features = ["speech_dispatcher_0_11"]

View File

@ -1,16 +1,16 @@
plugins { plugins {
id "com.android.application" id "com.android.application"
id "org.mozilla.rust-android-gradle.rust-android" id "kotlin-android"
} }
android { android {
namespace "rs.tts" compileSdkVersion 30
compileSdkVersion 33 buildToolsVersion "30.0.3"
ndkVersion "25.1.8937393"
defaultConfig { defaultConfig {
applicationId "rs.tts" applicationId "rs.tts"
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 33 targetSdkVersion 30
versionCode 1 versionCode 1
versionName "1.0" versionName "1.0"
} }
@ -21,27 +21,27 @@ android {
proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" proguardFiles getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro"
} }
} }
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = "1.8"
}
} }
dependencies { dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "androidx.core:core-ktx:1.2.0" implementation "androidx.core:core-ktx:1.2.0"
implementation "androidx.annotation:annotation:1.1.0" implementation "androidx.annotation:annotation:1.1.0"
implementation "com.google.android.material:material:1.1.0" implementation "com.google.android.material:material:1.1.0"
implementation "androidx.constraintlayout:constraintlayout:1.1.3" implementation "androidx.constraintlayout:constraintlayout:1.1.3"
} }
apply plugin: "org.mozilla.rust-android-gradle.rust-android" apply plugin: "com.github.willir.rust.cargo-ndk-android"
cargo { cargoNdk {
module = "." module = "."
libname = "tts"
targets = ["arm", "x86"]
}
tasks.whenTaskAdded { task ->
if ((task.name == 'javaPreCompileDebug' || task.name == 'javaPreCompileRelease')) {
task.dependsOn "cargoBuild"
}
} }
project.afterEvaluate { project.afterEvaluate {

View File

@ -1,8 +1,8 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android" package="rs.tts">
<application android:allowBackup="true" android:label="@string/app_name"> <application android:allowBackup="true" android:label="@string/app_name">
<activity android:name=".MainActivity" android:exported="true"> <activity android:name=".MainActivity">
<meta-data android:name="android.app.lib_name" android:value="hello_world" /> <meta-data android:name="android.app.lib_name" android:value="hello_world" />
<intent-filter> <intent-filter>
<action android:name="android.intent.action.MAIN" /> <action android:name="android.intent.action.MAIN" />

View File

@ -1,28 +1,28 @@
// Top-level build file where you can add configuration options common to all sub-projects/modules. // Top-level build file where you can add configuration options common to all sub-projects/modules.
buildscript {
buildscript { ext.kotlin_version = "1.3.72"
repositories { repositories {
google() google()
mavenCentral() jcenter()
maven { maven {
url "https://plugins.gradle.org/m2/" url "https://plugins.gradle.org/m2/"
} }
} }
} dependencies {
classpath "com.android.tools.build:gradle:4.1.1"
plugins { classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
id "com.android.application" version "7.3.0" apply false classpath "gradle.plugin.com.github.willir.rust:plugin:0.3.4"
id "com.android.library" version "7.3.0" apply false // NOTE: Do not place your application dependencies here; they belong
id "org.jetbrains.kotlin.android" version "1.7.21" apply false // in the individual module build.gradle files
id "org.mozilla.rust-android-gradle.rust-android" version "0.9.3" apply false }
} }
allprojects { allprojects {
repositories { repositories {
google() google()
mavenCentral() jcenter()
} }
} }
task clean(type: Delete) { task clean(type: Delete) {
delete rootProject.buildDir delete rootProject.buildDir

View File

@ -2,7 +2,7 @@
name = "hello_world" name = "hello_world"
version = "0.1.0" version = "0.1.0"
authors = ["Nolan Darilek <nolan@thewordnerd.info>"] authors = ["Nolan Darilek <nolan@thewordnerd.info>"]
edition = "2021" edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -10,5 +10,5 @@ edition = "2021"
crate-type = ["dylib"] crate-type = ["dylib"]
[dependencies] [dependencies]
ndk-glue = "0.7" ndk-glue = "0.6"
tts = { path = "../.." } tts = { path = "../.." }

View File

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-7.5.1-bin.zip distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip

View File

@ -1,8 +1 @@
pluginManagement { include ":app"
repositories {
gradlePluginPortal()
google()
mavenCentral()
}
}
include ":app"

View File

@ -1,14 +1,5 @@
#[cfg(target_os = "macos")]
use cocoa_foundation::base::id;
#[cfg(target_os = "macos")]
use cocoa_foundation::foundation::NSDefaultRunLoopMode;
#[cfg(target_os = "macos")]
use cocoa_foundation::foundation::NSRunLoop;
#[cfg(target_os = "macos")]
use objc::class;
#[cfg(target_os = "macos")]
use objc::{msg_send, sel, sel_impl};
use std::{thread, time}; use std::{thread, time};
use tts::*; use tts::*;
fn main() -> Result<(), Error> { fn main() -> Result<(), Error> {
@ -17,14 +8,6 @@ fn main() -> Result<(), Error> {
let mut phrase = 1; let mut phrase = 1;
loop { loop {
tts.speak(format!("Phrase {}", phrase), false)?; tts.speak(format!("Phrase {}", phrase), false)?;
#[cfg(target_os = "macos")]
{
let run_loop: id = unsafe { NSRunLoop::currentRunLoop() };
unsafe {
let date: id = msg_send![class!(NSDate), distantFuture];
let _: () = msg_send![run_loop, runMode:NSDefaultRunLoopMode beforeDate:date];
}
}
let time = time::Duration::from_secs(5); let time = time::Duration::from_secs(5);
thread::sleep(time); thread::sleep(time);
phrase += 1; phrase += 1;

View File

@ -2,7 +2,7 @@
name = "web" name = "web"
version = "0.1.0" version = "0.1.0"
authors = ["Nolan Darilek <nolan@thewordnerd.info>"] authors = ["Nolan Darilek <nolan@thewordnerd.info>"]
edition = "2021" edition = "2018"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
@ -11,3 +11,4 @@ console_log = "0.2"
log = "0.4" log = "0.4"
seed = "0.9" seed = "0.9"
tts = { path = "../.." } tts = { path = "../.." }
wasm-bindgen = "0.2"

View File

@ -28,7 +28,7 @@ lazy_static! {
#[allow(non_snake_case)] #[allow(non_snake_case)]
#[no_mangle] #[no_mangle]
pub extern "system" fn JNI_OnLoad(vm: JavaVM, _: *mut c_void) -> jint { pub extern "system" fn JNI_OnLoad(vm: JavaVM, _: *mut c_void) -> jint {
let mut env = vm.get_env().expect("Cannot get reference to the JNIEnv"); let env = vm.get_env().expect("Cannot get reference to the JNIEnv");
let b = env let b = env
.find_class("rs/tts/Bridge") .find_class("rs/tts/Bridge")
.expect("Failed to find `Bridge`"); .expect("Failed to find `Bridge`");
@ -42,7 +42,7 @@ pub extern "system" fn JNI_OnLoad(vm: JavaVM, _: *mut c_void) -> jint {
#[no_mangle] #[no_mangle]
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub unsafe extern "C" fn Java_rs_tts_Bridge_onInit(mut env: JNIEnv, obj: JObject, status: jint) { pub unsafe extern "C" fn Java_rs_tts_Bridge_onInit(env: JNIEnv, obj: JObject, status: jint) {
let id = env let id = env
.get_field(obj, "backendId", "I") .get_field(obj, "backendId", "I")
.expect("Failed to get backend ID") .expect("Failed to get backend ID")
@ -58,7 +58,7 @@ pub unsafe extern "C" fn Java_rs_tts_Bridge_onInit(mut env: JNIEnv, obj: JObject
#[no_mangle] #[no_mangle]
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub unsafe extern "C" fn Java_rs_tts_Bridge_onStart( pub unsafe extern "C" fn Java_rs_tts_Bridge_onStart(
mut env: JNIEnv, env: JNIEnv,
obj: JObject, obj: JObject,
utterance_id: JString, utterance_id: JString,
) { ) {
@ -69,7 +69,7 @@ pub unsafe extern "C" fn Java_rs_tts_Bridge_onStart(
.expect("Failed to cast to int") as u64; .expect("Failed to cast to int") as u64;
let backend_id = BackendId::Android(backend_id); let backend_id = BackendId::Android(backend_id);
let utterance_id = CString::from(CStr::from_ptr( let utterance_id = CString::from(CStr::from_ptr(
env.get_string(&utterance_id).unwrap().as_ptr(), env.get_string(utterance_id).unwrap().as_ptr(),
)) ))
.into_string() .into_string()
.unwrap(); .unwrap();
@ -85,7 +85,7 @@ pub unsafe extern "C" fn Java_rs_tts_Bridge_onStart(
#[no_mangle] #[no_mangle]
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub unsafe extern "C" fn Java_rs_tts_Bridge_onStop( pub unsafe extern "C" fn Java_rs_tts_Bridge_onStop(
mut env: JNIEnv, env: JNIEnv,
obj: JObject, obj: JObject,
utterance_id: JString, utterance_id: JString,
) { ) {
@ -96,7 +96,7 @@ pub unsafe extern "C" fn Java_rs_tts_Bridge_onStop(
.expect("Failed to cast to int") as u64; .expect("Failed to cast to int") as u64;
let backend_id = BackendId::Android(backend_id); let backend_id = BackendId::Android(backend_id);
let utterance_id = CString::from(CStr::from_ptr( let utterance_id = CString::from(CStr::from_ptr(
env.get_string(&utterance_id).unwrap().as_ptr(), env.get_string(utterance_id).unwrap().as_ptr(),
)) ))
.into_string() .into_string()
.unwrap(); .unwrap();
@ -112,7 +112,7 @@ pub unsafe extern "C" fn Java_rs_tts_Bridge_onStop(
#[no_mangle] #[no_mangle]
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub unsafe extern "C" fn Java_rs_tts_Bridge_onDone( pub unsafe extern "C" fn Java_rs_tts_Bridge_onDone(
mut env: JNIEnv, env: JNIEnv,
obj: JObject, obj: JObject,
utterance_id: JString, utterance_id: JString,
) { ) {
@ -123,7 +123,7 @@ pub unsafe extern "C" fn Java_rs_tts_Bridge_onDone(
.expect("Failed to cast to int") as u64; .expect("Failed to cast to int") as u64;
let backend_id = BackendId::Android(backend_id); let backend_id = BackendId::Android(backend_id);
let utterance_id = CString::from(CStr::from_ptr( let utterance_id = CString::from(CStr::from_ptr(
env.get_string(&utterance_id).unwrap().as_ptr(), env.get_string(utterance_id).unwrap().as_ptr(),
)) ))
.into_string() .into_string()
.unwrap(); .unwrap();
@ -139,7 +139,7 @@ pub unsafe extern "C" fn Java_rs_tts_Bridge_onDone(
#[no_mangle] #[no_mangle]
#[allow(non_snake_case)] #[allow(non_snake_case)]
pub unsafe extern "C" fn Java_rs_tts_Bridge_onError( pub unsafe extern "C" fn Java_rs_tts_Bridge_onError(
mut env: JNIEnv, env: JNIEnv,
obj: JObject, obj: JObject,
utterance_id: JString, utterance_id: JString,
) { ) {
@ -150,7 +150,7 @@ pub unsafe extern "C" fn Java_rs_tts_Bridge_onError(
.expect("Failed to cast to int") as u64; .expect("Failed to cast to int") as u64;
let backend_id = BackendId::Android(backend_id); let backend_id = BackendId::Android(backend_id);
let utterance_id = CString::from(CStr::from_ptr( let utterance_id = CString::from(CStr::from_ptr(
env.get_string(&utterance_id).unwrap().as_ptr(), env.get_string(utterance_id).unwrap().as_ptr(),
)) ))
.into_string() .into_string()
.unwrap(); .unwrap();
@ -179,23 +179,22 @@ impl Android {
let id = BackendId::Android(bid); let id = BackendId::Android(bid);
*backend_id += 1; *backend_id += 1;
drop(backend_id); drop(backend_id);
let ctx = ndk_context::android_context(); let native_activity = ndk_glue::native_activity();
let vm = unsafe { jni::JavaVM::from_raw(ctx.vm().cast()) }?; let vm = Self::vm()?;
let context = unsafe { JObject::from_raw(ctx.context().cast()) }; let env = vm.attach_current_thread_permanently()?;
let mut env = vm.attach_current_thread_permanently()?;
let bridge = BRIDGE.lock().unwrap(); let bridge = BRIDGE.lock().unwrap();
if let Some(bridge) = &*bridge { if let Some(bridge) = &*bridge {
let bridge = env.new_object(bridge, "(I)V", &[(bid as jint).into()])?; let bridge = env.new_object(bridge, "(I)V", &[(bid as jint).into()])?;
let tts = env.new_object( let tts = env.new_object(
"android/speech/tts/TextToSpeech", "android/speech/tts/TextToSpeech",
"(Landroid/content/Context;Landroid/speech/tts/TextToSpeech$OnInitListener;)V", "(Landroid/content/Context;Landroid/speech/tts/TextToSpeech$OnInitListener;)V",
&[(&context).into(), (&bridge).into()], &[native_activity.activity().into(), bridge.into()],
)?; )?;
env.call_method( env.call_method(
&tts, tts,
"setOnUtteranceProgressListener", "setOnUtteranceProgressListener",
"(Landroid/speech/tts/UtteranceProgressListener;)I", "(Landroid/speech/tts/UtteranceProgressListener;)I",
&[(&bridge).into()], &[bridge.into()],
)?; )?;
{ {
let mut pending = PENDING_INITIALIZATIONS.write().unwrap(); let mut pending = PENDING_INITIALIZATIONS.write().unwrap();
@ -230,8 +229,9 @@ impl Android {
} }
fn vm() -> Result<JavaVM, jni::errors::Error> { fn vm() -> Result<JavaVM, jni::errors::Error> {
let ctx = ndk_context::android_context(); let native_activity = ndk_glue::native_activity();
unsafe { jni::JavaVM::from_raw(ctx.vm().cast()) } let vm_ptr = native_activity.vm();
unsafe { jni::JavaVM::from_raw(vm_ptr) }
} }
} }
@ -255,7 +255,7 @@ impl Backend for Android {
fn speak(&mut self, text: &str, interrupt: bool) -> Result<Option<UtteranceId>, Error> { fn speak(&mut self, text: &str, interrupt: bool) -> Result<Option<UtteranceId>, Error> {
let vm = Self::vm()?; let vm = Self::vm()?;
let mut env = vm.get_env()?; let env = vm.get_env()?;
let tts = self.tts.as_obj(); let tts = self.tts.as_obj();
let text = env.new_string(text)?; let text = env.new_string(text)?;
let queue_mode = if interrupt { 0 } else { 1 }; let queue_mode = if interrupt { 0 } else { 1 };
@ -270,10 +270,10 @@ impl Backend for Android {
"speak", "speak",
"(Ljava/lang/CharSequence;ILandroid/os/Bundle;Ljava/lang/String;)I", "(Ljava/lang/CharSequence;ILandroid/os/Bundle;Ljava/lang/String;)I",
&[ &[
(&text).into(), text.into(),
queue_mode.into(), queue_mode.into(),
(&JObject::null()).into(), JObject::null().into(),
(&uid).into(), uid.into(),
], ],
)?; )?;
let rv = rv.i()?; let rv = rv.i()?;
@ -286,7 +286,7 @@ impl Backend for Android {
fn stop(&mut self) -> Result<(), Error> { fn stop(&mut self) -> Result<(), Error> {
let vm = Self::vm()?; let vm = Self::vm()?;
let mut env = vm.get_env()?; let env = vm.get_env()?;
let tts = self.tts.as_obj(); let tts = self.tts.as_obj();
let rv = env.call_method(tts, "stop", "()I", &[])?; let rv = env.call_method(tts, "stop", "()I", &[])?;
let rv = rv.i()?; let rv = rv.i()?;
@ -315,7 +315,7 @@ impl Backend for Android {
fn set_rate(&mut self, rate: f32) -> Result<(), Error> { fn set_rate(&mut self, rate: f32) -> Result<(), Error> {
let vm = Self::vm()?; let vm = Self::vm()?;
let mut env = vm.get_env()?; let env = vm.get_env()?;
let tts = self.tts.as_obj(); let tts = self.tts.as_obj();
let rate = rate as jfloat; let rate = rate as jfloat;
let rv = env.call_method(tts, "setSpeechRate", "(F)I", &[rate.into()])?; let rv = env.call_method(tts, "setSpeechRate", "(F)I", &[rate.into()])?;
@ -346,7 +346,7 @@ impl Backend for Android {
fn set_pitch(&mut self, pitch: f32) -> Result<(), Error> { fn set_pitch(&mut self, pitch: f32) -> Result<(), Error> {
let vm = Self::vm()?; let vm = Self::vm()?;
let mut env = vm.get_env()?; let env = vm.get_env()?;
let tts = self.tts.as_obj(); let tts = self.tts.as_obj();
let pitch = pitch as jfloat; let pitch = pitch as jfloat;
let rv = env.call_method(tts, "setPitch", "(F)I", &[pitch.into()])?; let rv = env.call_method(tts, "setPitch", "(F)I", &[pitch.into()])?;
@ -381,7 +381,7 @@ impl Backend for Android {
fn is_speaking(&self) -> Result<bool, Error> { fn is_speaking(&self) -> Result<bool, Error> {
let vm = Self::vm()?; let vm = Self::vm()?;
let mut env = vm.get_env()?; let env = vm.get_env()?;
let tts = self.tts.as_obj(); let tts = self.tts.as_obj();
let rv = env.call_method(tts, "isSpeaking", "()Z", &[])?; let rv = env.call_method(tts, "isSpeaking", "()Z", &[])?;
let rv = rv.z()?; let rv = rv.z()?;

View File

@ -46,15 +46,13 @@ impl AppKit {
) { ) {
unsafe { unsafe {
let strings: id = *this.get_ivar("strings"); let strings: id = *this.get_ivar("strings");
let str: id = msg_send!(strings, firstObject);
let _: () = msg_send![str, release];
let _: () = msg_send!(strings, removeObjectAtIndex:0);
let count: u32 = msg_send![strings, count]; let count: u32 = msg_send![strings, count];
if count > 0 { if count > 0 {
let str: id = msg_send!(strings, firstObject); let str: id = msg_send!(strings, firstObject);
let _: () = msg_send![str, release]; let _: BOOL = msg_send![synth, startSpeakingString: str];
let _: () = msg_send!(strings, removeObjectAtIndex:0);
if count > 1 {
let str: id = msg_send!(strings, firstObject);
let _: BOOL = msg_send![synth, startSpeakingString: str];
}
} }
} }
} }

View File

@ -1,16 +1,15 @@
#[cfg(any(target_os = "macos", target_os = "ios"))] #[cfg(any(target_os = "macos", target_os = "ios"))]
use std::sync::Mutex; use std::{str::FromStr, sync::Mutex};
use cocoa_foundation::base::{id, nil, NO}; use cocoa_foundation::base::{id, nil, NO};
use cocoa_foundation::foundation::NSString; use cocoa_foundation::foundation::NSString;
use core_foundation::array::CFArray; use core_foundation::array::CFArray;
use core_foundation::base::TCFType;
use core_foundation::string::CFString; use core_foundation::string::CFString;
use lazy_static::lazy_static; use lazy_static::lazy_static;
use log::{info, trace}; use log::{info, trace};
use objc::runtime::{Object, Sel}; use objc::runtime::{Object, Sel};
use objc::{class, declare::ClassDecl, msg_send, sel, sel_impl}; use objc::{class, declare::ClassDecl, msg_send, sel, sel_impl};
use oxilangtag::LanguageTag; use unic_langid::LanguageIdentifier;
use crate::{Backend, BackendId, Error, Features, Gender, UtteranceId, Voice, CALLBACKS}; use crate::{Backend, BackendId, Error, Features, Gender, UtteranceId, Voice, CALLBACKS};
@ -291,28 +290,21 @@ impl Backend for AvFoundation {
} }
fn voices(&self) -> Result<Vec<Voice>, Error> { fn voices(&self) -> Result<Vec<Voice>, Error> {
let voices: CFArray = unsafe { let voices: CFArray = unsafe { msg_send![class!(AVSpeechSynthesisVoice), speechVoices] };
CFArray::wrap_under_get_rule(msg_send![class!(AVSpeechSynthesisVoice), speechVoices])
};
let rv = voices let rv = voices
.iter() .iter()
.map(|v| { .map(|v| {
let id: CFString = unsafe { let id: CFString = unsafe { msg_send![*v as *const Object, identifier] };
CFString::wrap_under_get_rule(msg_send![*v as *const Object, identifier]) let name: CFString = unsafe { msg_send![*v as *const Object, name] };
};
let name: CFString =
unsafe { CFString::wrap_under_get_rule(msg_send![*v as *const Object, name]) };
let gender: i64 = unsafe { msg_send![*v as *const Object, gender] }; let gender: i64 = unsafe { msg_send![*v as *const Object, gender] };
let gender = match gender { let gender = match gender {
1 => Some(Gender::Male), 1 => Some(Gender::Male),
2 => Some(Gender::Female), 2 => Some(Gender::Female),
_ => None, _ => None,
}; };
let language: CFString = unsafe { let language: CFString = unsafe { msg_send![*v as *const Object, language] };
CFString::wrap_under_get_rule(msg_send![*v as *const Object, language])
};
let language = language.to_string(); let language = language.to_string();
let language = LanguageTag::parse(language).unwrap(); let language = LanguageIdentifier::from_str(&language).unwrap();
Voice { Voice {
id: id.to_string(), id: id.to_string(),
name: name.to_string(), name: name.to_string(),

View File

@ -1,10 +1,10 @@
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
use std::{collections::HashMap, sync::Mutex}; use std::{collections::HashMap, str::FromStr, sync::Mutex};
use lazy_static::*; use lazy_static::*;
use log::{info, trace}; use log::{info, trace};
use oxilangtag::LanguageTag;
use speech_dispatcher::*; use speech_dispatcher::*;
use unic_langid::LanguageIdentifier;
use crate::{Backend, BackendId, Error, Features, UtteranceId, Voice, CALLBACKS}; use crate::{Backend, BackendId, Error, Features, UtteranceId, Voice, CALLBACKS};
@ -12,8 +12,8 @@ use crate::{Backend, BackendId, Error, Features, UtteranceId, Voice, CALLBACKS};
pub(crate) struct SpeechDispatcher(Connection); pub(crate) struct SpeechDispatcher(Connection);
lazy_static! { lazy_static! {
static ref SPEAKING: Mutex<HashMap<usize, bool>> = { static ref SPEAKING: Mutex<HashMap<u64, bool>> = {
let m: HashMap<usize, bool> = HashMap::new(); let m: HashMap<u64, bool> = HashMap::new();
Mutex::new(m) Mutex::new(m)
}; };
} }
@ -31,7 +31,7 @@ impl SpeechDispatcher {
let mut callbacks = CALLBACKS.lock().unwrap(); let mut callbacks = CALLBACKS.lock().unwrap();
let backend_id = BackendId::SpeechDispatcher(client_id); let backend_id = BackendId::SpeechDispatcher(client_id);
let cb = callbacks.get_mut(&backend_id).unwrap(); let cb = callbacks.get_mut(&backend_id).unwrap();
let utterance_id = UtteranceId::SpeechDispatcher(msg_id as u64); let utterance_id = UtteranceId::SpeechDispatcher(msg_id);
if let Some(f) = cb.utterance_begin.as_mut() { if let Some(f) = cb.utterance_begin.as_mut() {
f(utterance_id); f(utterance_id);
} }
@ -42,7 +42,7 @@ impl SpeechDispatcher {
let mut callbacks = CALLBACKS.lock().unwrap(); let mut callbacks = CALLBACKS.lock().unwrap();
let backend_id = BackendId::SpeechDispatcher(client_id); let backend_id = BackendId::SpeechDispatcher(client_id);
let cb = callbacks.get_mut(&backend_id).unwrap(); let cb = callbacks.get_mut(&backend_id).unwrap();
let utterance_id = UtteranceId::SpeechDispatcher(msg_id as u64); let utterance_id = UtteranceId::SpeechDispatcher(msg_id);
if let Some(f) = cb.utterance_end.as_mut() { if let Some(f) = cb.utterance_end.as_mut() {
f(utterance_id); f(utterance_id);
} }
@ -53,7 +53,7 @@ impl SpeechDispatcher {
let mut callbacks = CALLBACKS.lock().unwrap(); let mut callbacks = CALLBACKS.lock().unwrap();
let backend_id = BackendId::SpeechDispatcher(client_id); let backend_id = BackendId::SpeechDispatcher(client_id);
let cb = callbacks.get_mut(&backend_id).unwrap(); let cb = callbacks.get_mut(&backend_id).unwrap();
let utterance_id = UtteranceId::SpeechDispatcher(msg_id as u64); let utterance_id = UtteranceId::SpeechDispatcher(msg_id);
if let Some(f) = cb.utterance_stop.as_mut() { if let Some(f) = cb.utterance_stop.as_mut() {
f(utterance_id); f(utterance_id);
} }
@ -188,12 +188,11 @@ impl Backend for SpeechDispatcher {
.0 .0
.list_synthesis_voices()? .list_synthesis_voices()?
.iter() .iter()
.filter(|v| LanguageTag::parse(v.language.clone()).is_ok())
.map(|v| Voice { .map(|v| Voice {
id: v.name.clone(), id: v.name.clone(),
name: v.name.clone(), name: v.name.clone(),
gender: None, gender: None,
language: LanguageTag::parse(v.language.clone()).unwrap(), language: LanguageIdentifier::from_str(&v.language).unwrap(),
}) })
.collect::<Vec<Voice>>(); .collect::<Vec<Voice>>();
Ok(rv) Ok(rv)

View File

@ -1,9 +1,9 @@
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
use std::sync::Mutex; use std::{str::FromStr, sync::Mutex};
use lazy_static::lazy_static; use lazy_static::lazy_static;
use log::{info, trace}; use log::{info, trace};
use oxilangtag::LanguageTag; use unic_langid::LanguageIdentifier;
use wasm_bindgen::prelude::*; use wasm_bindgen::prelude::*;
use wasm_bindgen::JsCast; use wasm_bindgen::JsCast;
use web_sys::{ use web_sys::{
@ -248,7 +248,7 @@ impl Backend for Web {
return Ok(()); return Ok(());
} }
} }
Err(Error::OperationFailed) return Err(Error::OperationFailed);
} else { } else {
Err(Error::NoneError) Err(Error::NoneError)
} }
@ -264,7 +264,7 @@ impl Drop for Web {
impl From<SpeechSynthesisVoice> for Voice { impl From<SpeechSynthesisVoice> for Voice {
fn from(other: SpeechSynthesisVoice) -> Self { fn from(other: SpeechSynthesisVoice) -> Self {
let language = LanguageTag::parse(other.lang()).unwrap(); let language = LanguageIdentifier::from_str(&other.lang()).unwrap();
Voice { Voice {
id: other.voice_uri(), id: other.voice_uri(),
name: other.name(), name: other.name(),

View File

@ -1,12 +1,13 @@
#[cfg(windows)] #[cfg(windows)]
use std::{ use std::{
collections::{HashMap, VecDeque}, collections::{HashMap, VecDeque},
str::FromStr,
sync::Mutex, sync::Mutex,
}; };
use lazy_static::lazy_static; use lazy_static::lazy_static;
use log::{info, trace}; use log::{info, trace};
use oxilangtag::LanguageTag; use unic_langid::LanguageIdentifier;
use windows::{ use windows::{
Foundation::TypedEventHandler, Foundation::TypedEventHandler,
Media::{ Media::{
@ -353,7 +354,7 @@ impl TryInto<Voice> for VoiceInformation {
Gender::Female Gender::Female
}; };
let language: String = self.Language()?.try_into()?; let language: String = self.Language()?.try_into()?;
let language = LanguageTag::parse(language).unwrap(); let language = LanguageIdentifier::from_str(&language).unwrap();
Ok(Voice { Ok(Voice {
id: self.Id()?.try_into()?, id: self.Id()?.try_into()?,
name: self.DisplayName()?.try_into()?, name: self.DisplayName()?.try_into()?,

View File

@ -14,10 +14,9 @@ use std::collections::HashMap;
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
use std::ffi::CStr; use std::ffi::CStr;
use std::fmt; use std::fmt;
use std::rc::Rc;
#[cfg(windows)] #[cfg(windows)]
use std::string::FromUtf16Error; use std::string::FromUtf16Error;
use std::sync::Mutex; use std::sync::{Arc, Mutex};
use std::{boxed::Box, sync::RwLock}; use std::{boxed::Box, sync::RwLock};
#[cfg(any(target_os = "macos", target_os = "ios"))] #[cfg(any(target_os = "macos", target_os = "ios"))]
@ -28,12 +27,12 @@ use lazy_static::lazy_static;
use libc::c_char; use libc::c_char;
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
use objc::{class, msg_send, sel, sel_impl}; use objc::{class, msg_send, sel, sel_impl};
pub use oxilangtag::LanguageTag;
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
use speech_dispatcher::Error as SpeechDispatcherError; use speech_dispatcher::Error as SpeechDispatcherError;
use thiserror::Error; use thiserror::Error;
#[cfg(all(windows, feature = "tolk"))] #[cfg(all(windows, feature = "tolk"))]
use tolk::Tolk; use tolk::Tolk;
pub use unic_langid::LanguageIdentifier;
mod backends; mod backends;
@ -85,7 +84,7 @@ pub enum BackendId {
#[cfg(any(target_os = "macos", target_os = "ios"))] #[cfg(any(target_os = "macos", target_os = "ios"))]
AvFoundation(u64), AvFoundation(u64),
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
SpeechDispatcher(usize), SpeechDispatcher(u64),
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
Web(u64), Web(u64),
#[cfg(windows)] #[cfg(windows)]
@ -96,15 +95,15 @@ impl fmt::Display for BackendId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match self { match self {
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
BackendId::Android(id) => writeln!(f, "Android({id})"), BackendId::Android(id) => writeln!(f, "{}", id),
#[cfg(any(target_os = "macos", target_os = "ios"))] #[cfg(any(target_os = "macos", target_os = "ios"))]
BackendId::AvFoundation(id) => writeln!(f, "AvFoundation({id})"), BackendId::AvFoundation(id) => writeln!(f, "{}", id),
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
BackendId::SpeechDispatcher(id) => writeln!(f, "SpeechDispatcher({id})"), BackendId::SpeechDispatcher(id) => writeln!(f, "{}", id),
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
BackendId::Web(id) => writeln!(f, "Web({id})"), BackendId::Web(id) => writeln!(f, "Web({})", id),
#[cfg(windows)] #[cfg(windows)]
BackendId::WinRt(id) => writeln!(f, "WinRT({id})"), BackendId::WinRt(id) => writeln!(f, "{}", id),
} }
} }
} }
@ -144,13 +143,13 @@ impl fmt::Display for UtteranceId {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
match self { match self {
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
UtteranceId::Android(id) => writeln!(f, "Android({id})"), UtteranceId::Android(id) => writeln!(f, "{}", id),
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
UtteranceId::SpeechDispatcher(id) => writeln!(f, "SpeechDispatcher({id})"), UtteranceId::SpeechDispatcher(id) => writeln!(f, "{}", id),
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
UtteranceId::Web(id) => writeln!(f, "Web({})", id), UtteranceId::Web(id) => writeln!(f, "Web({})", id),
#[cfg(windows)] #[cfg(windows)]
UtteranceId::WinRt(id) => writeln!(f, "WinRt({id})"), UtteranceId::WinRt(id) => writeln!(f, "{}", id),
} }
} }
} }
@ -174,7 +173,7 @@ pub struct Features {
impl fmt::Display for Features { impl fmt::Display for Features {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> Result<(), fmt::Error> {
writeln!(f, "{self:#?}") writeln!(f, "{:#?}", self)
} }
} }
@ -259,7 +258,7 @@ lazy_static! {
} }
#[derive(Clone)] #[derive(Clone)]
pub struct Tts(Rc<RwLock<Box<dyn Backend>>>); pub struct Tts(Arc<RwLock<Box<dyn Backend>>>);
unsafe impl Send for Tts {} unsafe impl Send for Tts {}
@ -272,18 +271,18 @@ impl Tts {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
Backends::SpeechDispatcher => { Backends::SpeechDispatcher => {
let tts = backends::SpeechDispatcher::new()?; let tts = backends::SpeechDispatcher::new()?;
Ok(Tts(Rc::new(RwLock::new(Box::new(tts))))) Ok(Tts(Arc::new(RwLock::new(Box::new(tts)))))
} }
#[cfg(target_arch = "wasm32")] #[cfg(target_arch = "wasm32")]
Backends::Web => { Backends::Web => {
let tts = backends::Web::new()?; let tts = backends::Web::new()?;
Ok(Tts(Rc::new(RwLock::new(Box::new(tts))))) Ok(Tts(Arc::new(RwLock::new(Box::new(tts)))))
} }
#[cfg(all(windows, feature = "tolk"))] #[cfg(all(windows, feature = "tolk"))]
Backends::Tolk => { Backends::Tolk => {
let tts = backends::Tolk::new(); let tts = backends::Tolk::new();
if let Some(tts) = tts { if let Some(tts) = tts {
Ok(Tts(Rc::new(RwLock::new(Box::new(tts))))) Ok(Tts(Arc::new(RwLock::new(Box::new(tts)))))
} else { } else {
Err(Error::NoneError) Err(Error::NoneError)
} }
@ -291,20 +290,20 @@ impl Tts {
#[cfg(windows)] #[cfg(windows)]
Backends::WinRt => { Backends::WinRt => {
let tts = backends::WinRt::new()?; let tts = backends::WinRt::new()?;
Ok(Tts(Rc::new(RwLock::new(Box::new(tts))))) Ok(Tts(Arc::new(RwLock::new(Box::new(tts)))))
} }
#[cfg(target_os = "macos")] #[cfg(target_os = "macos")]
Backends::AppKit => Ok(Tts(Rc::new(RwLock::new( Backends::AppKit => Ok(Tts(Arc::new(RwLock::new(Box::new(
Box::new(backends::AppKit::new()?), backends::AppKit::new()?
)))), ))))),
#[cfg(any(target_os = "macos", target_os = "ios"))] #[cfg(any(target_os = "macos", target_os = "ios"))]
Backends::AvFoundation => Ok(Tts(Rc::new(RwLock::new(Box::new( Backends::AvFoundation => Ok(Tts(Arc::new(RwLock::new(Box::new(
backends::AvFoundation::new()?, backends::AvFoundation::new()?,
))))), ))))),
#[cfg(target_os = "android")] #[cfg(target_os = "android")]
Backends::Android => { Backends::Android => {
let tts = backends::Android::new()?; let tts = backends::Android::new()?;
Ok(Tts(Rc::new(RwLock::new(Box::new(tts))))) Ok(Tts(Arc::new(RwLock::new(Box::new(tts)))))
} }
}; };
if let Ok(backend) = backend { if let Ok(backend) = backend {
@ -318,7 +317,6 @@ impl Tts {
} }
} }
#[allow(clippy::should_implement_trait)]
pub fn default() -> Result<Tts, Error> { pub fn default() -> Result<Tts, Error> {
#[cfg(target_os = "linux")] #[cfg(target_os = "linux")]
let tts = Tts::new(Backends::SpeechDispatcher); let tts = Tts::new(Backends::SpeechDispatcher);
@ -572,7 +570,7 @@ impl Tts {
if utterance_callbacks { if utterance_callbacks {
let mut callbacks = CALLBACKS.lock().unwrap(); let mut callbacks = CALLBACKS.lock().unwrap();
let id = self.0.read().unwrap().id().unwrap(); let id = self.0.read().unwrap().id().unwrap();
let callbacks = callbacks.get_mut(&id).unwrap(); let mut callbacks = callbacks.get_mut(&id).unwrap();
callbacks.utterance_begin = callback; callbacks.utterance_begin = callback;
Ok(()) Ok(())
} else { } else {
@ -592,7 +590,7 @@ impl Tts {
if utterance_callbacks { if utterance_callbacks {
let mut callbacks = CALLBACKS.lock().unwrap(); let mut callbacks = CALLBACKS.lock().unwrap();
let id = self.0.read().unwrap().id().unwrap(); let id = self.0.read().unwrap().id().unwrap();
let callbacks = callbacks.get_mut(&id).unwrap(); let mut callbacks = callbacks.get_mut(&id).unwrap();
callbacks.utterance_end = callback; callbacks.utterance_end = callback;
Ok(()) Ok(())
} else { } else {
@ -612,7 +610,7 @@ impl Tts {
if utterance_callbacks { if utterance_callbacks {
let mut callbacks = CALLBACKS.lock().unwrap(); let mut callbacks = CALLBACKS.lock().unwrap();
let id = self.0.read().unwrap().id().unwrap(); let id = self.0.read().unwrap().id().unwrap();
let callbacks = callbacks.get_mut(&id).unwrap(); let mut callbacks = callbacks.get_mut(&id).unwrap();
callbacks.utterance_stop = callback; callbacks.utterance_stop = callback;
Ok(()) Ok(())
} else { } else {
@ -641,7 +639,7 @@ impl Tts {
impl Drop for Tts { impl Drop for Tts {
fn drop(&mut self) { fn drop(&mut self) {
if Rc::strong_count(&self.0) <= 1 { if Arc::strong_count(&self.0) <= 1 {
if let Some(id) = self.0.read().unwrap().id() { if let Some(id) = self.0.read().unwrap().id() {
let mut callbacks = CALLBACKS.lock().unwrap(); let mut callbacks = CALLBACKS.lock().unwrap();
callbacks.remove(&id); callbacks.remove(&id);
@ -661,7 +659,7 @@ pub struct Voice {
pub(crate) id: String, pub(crate) id: String,
pub(crate) name: String, pub(crate) name: String,
pub(crate) gender: Option<Gender>, pub(crate) gender: Option<Gender>,
pub(crate) language: LanguageTag<String>, pub(crate) language: LanguageIdentifier,
} }
impl Voice { impl Voice {
@ -677,7 +675,7 @@ impl Voice {
self.gender self.gender
} }
pub fn language(&self) -> LanguageTag<String> { pub fn language(&self) -> LanguageIdentifier {
self.language.clone() self.language.clone()
} }
} }