diff --git a/Cargo.toml b/Cargo.toml index 9bce264..eb15d80 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,4 +34,4 @@ objc = "0.2" [target.wasm32-unknown-unknown.dependencies] wasm-bindgen = "0.2" -web-sys = { version = "0.3", features = ["EventTarget", "SpeechSynthesis", "SpeechSynthesisEvent", "SpeechSynthesisUtterance", "Window", ] } +web-sys = { version = "0.3", features = ["EventTarget", "SpeechSynthesis", "SpeechSynthesisErrorCode", "SpeechSynthesisErrorEvent", "SpeechSynthesisEvent", "SpeechSynthesisUtterance", "Window", ] } diff --git a/src/backends/speech_dispatcher.rs b/src/backends/speech_dispatcher.rs index dfafdf4..72332ca 100644 --- a/src/backends/speech_dispatcher.rs +++ b/src/backends/speech_dispatcher.rs @@ -46,9 +46,16 @@ impl SpeechDispatcher { f(utterance_id); } }))); - sd.0.on_cancel(Some(Box::new(|_msg_id, client_id| { + sd.0.on_cancel(Some(Box::new(|msg_id, client_id| { let mut speaking = SPEAKING.lock().unwrap(); speaking.insert(client_id, false); + let mut callbacks = CALLBACKS.lock().unwrap(); + let backend_id = BackendId::SpeechDispatcher(client_id); + let cb = callbacks.get_mut(&backend_id).unwrap(); + let utterance_id = UtteranceId::SpeechDispatcher(msg_id); + if let Some(f) = cb.utterance_stop.as_mut() { + f(utterance_id); + } }))); sd.0.on_pause(Some(Box::new(|_msg_id, client_id| { let mut speaking = SPEAKING.lock().unwrap(); diff --git a/src/backends/web.rs b/src/backends/web.rs index 4696e7c..384a222 100644 --- a/src/backends/web.rs +++ b/src/backends/web.rs @@ -5,7 +5,10 @@ use lazy_static::lazy_static; use log::{info, trace}; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; -use web_sys::{SpeechSynthesisEvent, SpeechSynthesisUtterance}; +use web_sys::{ + SpeechSynthesisErrorCode, SpeechSynthesisErrorEvent, SpeechSynthesisEvent, + SpeechSynthesisUtterance, +}; use crate::{Backend, BackendId, Error, Features, UtteranceId, CALLBACKS}; @@ -85,6 +88,18 @@ impl Backend for Web { mappings.retain(|v| v.1 != utterance_id); }) as Box); utterance.set_onend(Some(callback.as_ref().unchecked_ref())); + let callback = Closure::wrap(Box::new(move |evt: SpeechSynthesisErrorEvent| { + if evt.error() == SpeechSynthesisErrorCode::Cancel { + let mut callbacks = CALLBACKS.lock().unwrap(); + let callback = callbacks.get_mut(&id).unwrap(); + if let Some(f) = callback.utterance_stop.as_mut() { + f(utterance_id); + } + } + let mut mappings = UTTERANCE_MAPPINGS.lock().unwrap(); + mappings.retain(|v| v.1 != utterance_id); + }) as Box); + utterance.set_onerror(Some(callback.as_ref().unchecked_ref())); if interrupt { self.stop()?; } diff --git a/src/backends/winrt.rs b/src/backends/winrt.rs index e81894b..ee4c6b4 100644 --- a/src/backends/winrt.rs +++ b/src/backends/winrt.rs @@ -76,7 +76,7 @@ impl WinRT { let mut mappings = UTTERANCE_MAPPINGS.lock().unwrap(); let mut callbacks = CALLBACKS.lock().unwrap(); let callbacks = callbacks.get_mut(&self.id).unwrap(); - if let Some(callback) = callbacks.utterance_end.as_mut() { + if let Some(callback) = callbacks.utterance_stop.as_mut() { let mappings = UTTERANCE_MAPPINGS.lock().unwrap(); for mapping in &*mappings { callback(mapping.2); diff --git a/src/lib.rs b/src/lib.rs index e3cbbde..ecf89ad 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -134,6 +134,7 @@ pub trait Backend { struct Callbacks { utterance_begin: Option>, utterance_end: Option>, + utterance_stop: Option>, } unsafe impl Send for Callbacks {} @@ -474,6 +475,27 @@ impl TTS { Err(Error::UnsupportedFeature) } } + /** + * Called when this speech synthesizer is stopped and still has utterances in its queue. + */ + pub fn on_utterance_stop( + &self, + callback: Option>, + ) -> Result<(), Error> { + let Features { + utterance_callbacks, + .. + } = self.supported_features(); + if utterance_callbacks { + let mut callbacks = CALLBACKS.lock().unwrap(); + let id = self.0.id().unwrap(); + let mut callbacks = callbacks.get_mut(&id).unwrap(); + callbacks.utterance_stop = callback; + Ok(()) + } else { + Err(Error::UnsupportedFeature) + } + } } impl Drop for TTS {