diff --git a/Cargo.toml b/Cargo.toml index 082c2a4..96fdb72 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,7 +9,7 @@ exclude = ["*.cfg", "*.yml"] edition = "2018" [lib] -crate-type = ["lib", "staticlib"] +crate-type = ["lib", "cdylib", "staticlib"] [dependencies] lazy_static = "1" @@ -34,4 +34,4 @@ objc = "0.2" [target.wasm32-unknown-unknown.dependencies] wasm-bindgen = "0.2" -web-sys = { version = "0.3", features = ["SpeechSynthesis", "SpeechSynthesisUtterance", "Window", ] } +web-sys = { version = "0.3", features = ["EventTarget", "SpeechSynthesis", "SpeechSynthesisEvent", "SpeechSynthesisUtterance", "Window", ] } diff --git a/src/backends/web.rs b/src/backends/web.rs index 89db8a9..619a350 100644 --- a/src/backends/web.rs +++ b/src/backends/web.rs @@ -3,32 +3,43 @@ use std::sync::Mutex; use lazy_static::lazy_static; use log::{info, trace}; -use web_sys::SpeechSynthesisUtterance; +use wasm_bindgen::prelude::*; +use wasm_bindgen::JsCast; +use web_sys::{SpeechSynthesisEvent, SpeechSynthesisUtterance}; -use crate::{Backend, Error, Features, UtteranceId}; +use crate::{Backend, BackendId, Error, Features, UtteranceId, CALLBACKS}; pub struct Web { + id: BackendId, rate: f32, pitch: f32, volume: f32, } lazy_static! { - static ref NEXT_UTTERANCE_ID: Mutex = Mutex::new(0); + static ref NEXT_BACKEND_ID: Mutex = Mutex::new(0); } impl Web { pub fn new() -> Result { info!("Initializing Web backend"); - Ok(Web { + let mut backend_id = NEXT_BACKEND_ID.lock().unwrap(); + let rv = Web { + id: BackendId::Web(*backend_id), rate: 1., pitch: 1., volume: 1., - }) + }; + *backend_id += 1; + Ok(rv) } } impl Backend for Web { + fn id(&self) -> Option { + Some(self.id) + } + fn supported_features(&self) -> Features { Features { stop: true, @@ -36,6 +47,7 @@ impl Backend for Web { pitch: true, volume: true, is_speaking: true, + utterance_callbacks: true, } } @@ -45,15 +57,33 @@ impl Backend for Web { utterance.set_rate(self.rate); utterance.set_pitch(self.pitch); utterance.set_volume(self.volume); + let id = self.id().unwrap(); + let utterance_id = UtteranceId::Web(utterance.clone()); + let callback = Closure::wrap(Box::new(move |evt: SpeechSynthesisEvent| { + let callbacks = CALLBACKS.lock().unwrap(); + let callback = callbacks.get(&id).unwrap(); + if let Some(f) = callback.utterance_begin { + let utterance_id = UtteranceId::Web(evt.utterance()); + f(utterance_id); + } + }) as Box); + utterance.set_onstart(Some(callback.as_ref().unchecked_ref())); + let callback = Closure::wrap(Box::new(move |evt: SpeechSynthesisEvent| { + let callbacks = CALLBACKS.lock().unwrap(); + let callback = callbacks.get(&id).unwrap(); + if let Some(f) = callback.utterance_end { + let utterance_id = UtteranceId::Web(evt.utterance()); + f(utterance_id); + } + }) as Box); + utterance.set_onend(Some(callback.as_ref().unchecked_ref())); if interrupt { self.stop()?; } if let Some(window) = web_sys::window() { let speech_synthesis = window.speech_synthesis().unwrap(); speech_synthesis.speak(&utterance); - let mut utterance_id = NEXT_UTTERANCE_ID.lock().unwrap(); - *utterance_id += 1; - Ok(Some(UtteranceId::Web(*utterance_id))) + Ok(Some(utterance_id)) } else { Err(Error::NoneError) } diff --git a/src/lib.rs b/src/lib.rs index ced1f17..622df31 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -25,6 +25,8 @@ use libc::c_char; #[cfg(target_os = "macos")] use objc::{class, msg_send, sel, sel_impl}; use thiserror::Error; +#[cfg(target_arch = "wasm32")] +use web_sys::SpeechSynthesisUtterance; #[cfg(windows)] use tts_winrt_bindings::windows::media::playback::MediaPlaybackItem; @@ -63,7 +65,7 @@ pub enum UtteranceId { #[cfg(target_os = "linux")] SpeechDispatcher(u64), #[cfg(target_arch = "wasm32")] - Web(u64), + Web(SpeechSynthesisUtterance), #[cfg(windows)] WinRT(MediaPlaybackItem), #[cfg(any(target_os = "macos", target_os = "ios"))]