Block initialization until TTS finishes initializing from Android.

This commit is contained in:
Nolan Darilek 2020-12-30 09:23:13 -06:00
parent 1ac0b91981
commit 5634fdb393
7 changed files with 94 additions and 39 deletions

View File

@ -9,7 +9,7 @@ exclude = ["*.cfg", "*.yml"]
edition = "2018"
[lib]
crate-type = ["lib", "cdylib", "staticlib"]
crate-type = ["lib", "dylib", "staticlib"]
[features]
use_tolk = ["tolk"]

View File

@ -33,6 +33,7 @@ android {
dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation "androidx.core:core-ktx:1.2.0"
implementation "androidx.annotation:annotation:1.1.0"
implementation "com.google.android.material:material:1.1.0"
implementation "androidx.constraintlayout:constraintlayout:1.1.3"
}

View File

@ -0,0 +1,15 @@
package rs.tts;
import android.speech.tts.TextToSpeech;
@androidx.annotation.Keep
public class Bridge implements TextToSpeech.OnInitListener {
public int backendId;
public Bridge(int backendId) {
this.backendId = backendId;
}
public native void onInit(int status);
}

View File

@ -1,15 +1,11 @@
package rs.tts
import android.app.NativeActivity
import android.speech.tts.TextToSpeech
import android.speech.tts.TextToSpeech.OnInitListener
class MainActivity : NativeActivity(), OnInitListener {
override fun onInit(status:Int) {
if(status == TextToSpeech.SUCCESS) {
println("Successfully initialized TTS!")
} else {
println("Failed to initialize TTS.")
class MainActivity : NativeActivity() {
companion object {
init {
System.loadLibrary("hello_world")
}
}
}

View File

@ -10,5 +10,6 @@ edition = "2018"
crate-type = ["dylib"]
[dependencies]
jni = "0.18"
ndk-glue = "0.2"
tts = { path = "../.." }

View File

@ -1,10 +1,7 @@
use std::thread;
use std::time::Duration;
use tts::*;
fn run() -> Result<(), Error> {
let mut tts = TTS::default()?;
thread::sleep(Duration::from_secs(5));
let Features {
utterance_callbacks,
..
@ -65,6 +62,5 @@ fn run() -> Result<(), Error> {
#[cfg_attr(target_os = "android", ndk_glue::main(backtrace = "on"))]
pub fn main() {
println!("xxxx In library");
run().expect("Failed to run");
}

View File

@ -1,15 +1,49 @@
#[cfg(target_os = "android")]
use std::sync::Mutex;
use std::collections::HashSet;
use std::os::raw::c_void;
use std::sync::{Mutex, RwLock};
use std::thread;
use std::time::Duration;
use jni::objects::{GlobalRef, JObject};
use jni::JavaVM;
use jni::sys::{jint, JNI_VERSION_1_6};
use jni::{JNIEnv, JavaVM};
use lazy_static::lazy_static;
use log::info;
use crate::{Backend, BackendId, Error, Features, UtteranceId, CALLBACKS};
lazy_static! {
static ref BRIDGE: Mutex<Option<GlobalRef>> = Mutex::new(None);
static ref NEXT_BACKEND_ID: Mutex<u64> = Mutex::new(0);
static ref PENDING_INITIALIZATIONS: RwLock<HashSet<u64>> = RwLock::new(HashSet::new());
}
#[allow(non_snake_case)]
#[no_mangle]
pub extern "system" fn JNI_OnLoad(vm: JavaVM, _: *mut c_void) -> jint {
let env = vm.get_env().expect("Cannot get reference to the JNIEnv");
let b = env
.find_class("rs/tts/Bridge")
.expect("Failed to find `Bridge`");
let b = env
.new_global_ref(b)
.expect("Failed to create `Bridge` `GlobalRef`");
let mut bridge = BRIDGE.lock().unwrap();
*bridge = Some(b);
JNI_VERSION_1_6
}
#[no_mangle]
#[allow(non_snake_case)]
pub unsafe extern "C" fn Java_rs_tts_Bridge_onInit(env: JNIEnv, obj: JObject, status: jint) {
let id = env
.get_field(obj, "backendId", "I")
.expect("Failed to get backend ID")
.i()
.expect("Failed to cast to int") as u64;
let mut pending = PENDING_INITIALIZATIONS.write().unwrap();
(*pending).remove(&id);
}
#[derive(Clone)]
@ -22,23 +56,40 @@ impl Android {
pub(crate) fn new() -> Result<Self, Error> {
info!("Initializing Android backend");
let mut backend_id = NEXT_BACKEND_ID.lock().unwrap();
let id = BackendId::Android(*backend_id);
let bid = *backend_id;
let id = BackendId::Android(bid);
*backend_id += 1;
drop(backend_id);
let native_activity = ndk_glue::native_activity();
let vm = Self::vm()?;
let env = vm.attach_current_thread_permanently()?;
let tts = env.new_object(
"android/speech/tts/TextToSpeech",
"(Landroid/content/Context;Landroid/speech/tts/TextToSpeech$OnInitListener;)V",
&[
native_activity.activity().into(),
native_activity.activity().into(),
],
)?;
println!("Creating global ref");
let tts = env.new_global_ref(tts)?;
println!("Returning");
Ok(Self { id, tts })
let bridge = BRIDGE.lock().unwrap();
if let Some(bridge) = &*bridge {
let bridge = env.new_object(bridge, "(I)V", &[(bid as jint).into()])?;
let tts = env.new_object(
"android/speech/tts/TextToSpeech",
"(Landroid/content/Context;Landroid/speech/tts/TextToSpeech$OnInitListener;)V",
&[native_activity.activity().into(), bridge.into()],
)?;
{
let mut pending = PENDING_INITIALIZATIONS.write().unwrap();
(*pending).insert(bid);
}
let tts = env.new_global_ref(tts)?;
// This hack makes my brain bleed.
loop {
{
let pending = PENDING_INITIALIZATIONS.read().unwrap();
if !(*pending).contains(&bid) {
break;
}
}
thread::sleep(Duration::from_millis(5));
}
Ok(Self { id, tts })
} else {
Err(Error::NoneError)
}
}
fn vm() -> Result<JavaVM, jni::errors::Error> {
@ -65,15 +116,11 @@ impl Backend for Android {
}
fn speak(&mut self, text: &str, interrupt: bool) -> Result<Option<UtteranceId>, Error> {
println!("Speaking {}, {:?}", text, interrupt);
let vm = Self::vm()?;
println!("Retrieved");
let env = vm.get_env()?;
println!("attached");
let tts = self.tts.as_obj();
let text = env.new_string(text)?;
let queue_mode = if interrupt { 0 } else { 1 };
println!("Calling");
env.call_method(
tts,
"speak",
@ -85,7 +132,6 @@ impl Backend for Android {
JObject::null().into(),
],
)?;
println!("Returning");
Ok(None)
}
@ -94,15 +140,15 @@ impl Backend for Android {
}
fn min_rate(&self) -> f32 {
todo!()
0.1
}
fn max_rate(&self) -> f32 {
todo!()
10.
}
fn normal_rate(&self) -> f32 {
todo!()
1.
}
fn get_rate(&self) -> Result<f32, Error> {
@ -114,15 +160,15 @@ impl Backend for Android {
}
fn min_pitch(&self) -> f32 {
todo!()
0.1
}
fn max_pitch(&self) -> f32 {
todo!()
2.
}
fn normal_pitch(&self) -> f32 {
todo!()
1.
}
fn get_pitch(&self) -> Result<f32, Error> {