#[cfg(target_arch = "wasm32")] use std::sync::Mutex; use lazy_static::lazy_static; use log::{info, trace}; use oxilangtag::LanguageTag; use wasm_bindgen::prelude::*; use wasm_bindgen::JsCast; use web_sys::{ SpeechSynthesisErrorCode, SpeechSynthesisErrorEvent, SpeechSynthesisEvent, SpeechSynthesisUtterance, SpeechSynthesisVoice, }; use crate::{Backend, BackendId, Error, Features, UtteranceId, Voice, CALLBACKS}; #[derive(Clone, Debug)] pub struct Web { id: BackendId, rate: f32, pitch: f32, volume: f32, voice: Option, } lazy_static! { static ref NEXT_BACKEND_ID: Mutex = Mutex::new(0); static ref UTTERANCE_MAPPINGS: Mutex> = Mutex::new(Vec::new()); static ref NEXT_UTTERANCE_ID: Mutex = Mutex::new(0); } impl Web { pub fn new() -> Result { info!("Initializing Web backend"); let mut backend_id = NEXT_BACKEND_ID.lock().unwrap(); let rv = Web { id: BackendId::Web(*backend_id), rate: 1., pitch: 1., volume: 1., voice: None, }; *backend_id += 1; Ok(rv) } } impl Backend for Web { fn id(&self) -> Option { Some(self.id) } fn supported_features(&self) -> Features { Features { stop: true, rate: true, pitch: true, volume: true, is_speaking: true, voice: true, get_voice: true, utterance_callbacks: true, } } fn speak(&mut self, text: &str, interrupt: bool) -> Result, Error> { trace!("speak({}, {})", text, interrupt); let utterance = SpeechSynthesisUtterance::new_with_text(text).unwrap(); utterance.set_rate(self.rate); utterance.set_pitch(self.pitch); utterance.set_volume(self.volume); if self.voice.is_some() { utterance.set_voice(self.voice.as_ref()); } let id = self.id().unwrap(); let mut uid = NEXT_UTTERANCE_ID.lock().unwrap(); let utterance_id = UtteranceId::Web(*uid); *uid += 1; drop(uid); let mut mappings = UTTERANCE_MAPPINGS.lock().unwrap(); mappings.push((self.id, utterance_id)); drop(mappings); let callback = Closure::wrap(Box::new(move |_evt: SpeechSynthesisEvent| { let mut callbacks = CALLBACKS.lock().unwrap(); let callback = callbacks.get_mut(&id).unwrap(); if let Some(f) = callback.utterance_begin.as_mut() { f(utterance_id); } }) as Box); utterance.set_onstart(Some(callback.as_ref().unchecked_ref())); let callback = Closure::wrap(Box::new(move |_evt: SpeechSynthesisEvent| { let mut callbacks = CALLBACKS.lock().unwrap(); let callback = callbacks.get_mut(&id).unwrap(); if let Some(f) = callback.utterance_end.as_mut() { f(utterance_id); } let mut mappings = UTTERANCE_MAPPINGS.lock().unwrap(); 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::Canceled { 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()?; } if let Some(window) = web_sys::window() { let speech_synthesis = window.speech_synthesis().unwrap(); speech_synthesis.speak(&utterance); Ok(Some(utterance_id)) } else { Err(Error::NoneError) } } fn stop(&mut self) -> Result<(), Error> { trace!("stop()"); if let Some(window) = web_sys::window() { let speech_synthesis = window.speech_synthesis().unwrap(); speech_synthesis.cancel(); } Ok(()) } fn min_rate(&self) -> f32 { 0.1 } fn max_rate(&self) -> f32 { 10. } fn normal_rate(&self) -> f32 { 1. } fn get_rate(&self) -> Result { Ok(self.rate) } fn set_rate(&mut self, rate: f32) -> Result<(), Error> { self.rate = rate; Ok(()) } fn min_pitch(&self) -> f32 { 0. } fn max_pitch(&self) -> f32 { 2. } fn normal_pitch(&self) -> f32 { 1. } fn get_pitch(&self) -> Result { Ok(self.pitch) } fn set_pitch(&mut self, pitch: f32) -> Result<(), Error> { self.pitch = pitch; Ok(()) } fn min_volume(&self) -> f32 { 0. } fn max_volume(&self) -> f32 { 1. } fn normal_volume(&self) -> f32 { 1. } fn get_volume(&self) -> Result { Ok(self.volume) } fn set_volume(&mut self, volume: f32) -> Result<(), Error> { self.volume = volume; Ok(()) } fn is_speaking(&self) -> Result { trace!("is_speaking()"); if let Some(window) = web_sys::window() { match window.speech_synthesis() { Ok(speech_synthesis) => Ok(speech_synthesis.speaking()), Err(e) => Err(Error::JavaScriptError(e)), } } else { Err(Error::NoneError) } } fn voice(&self) -> Result, Error> { if let Some(voice) = &self.voice { Ok(Some(voice.clone().into())) } else { if let Some(window) = web_sys::window() { let speech_synthesis = window.speech_synthesis().unwrap(); for voice in speech_synthesis.get_voices().iter() { let voice: SpeechSynthesisVoice = voice.into(); if voice.default() { return Ok(Some(voice.into())); } } } else { return Err(Error::NoneError); } Ok(None) } } fn voices(&self) -> Result, Error> { if let Some(window) = web_sys::window() { let speech_synthesis = window.speech_synthesis().unwrap(); let mut rv: Vec = vec![]; for v in speech_synthesis.get_voices().iter() { let v: SpeechSynthesisVoice = v.into(); rv.push(v.into()); } Ok(rv) } else { Err(Error::NoneError) } } fn set_voice(&mut self, voice: &Voice) -> Result<(), Error> { if let Some(window) = web_sys::window() { let speech_synthesis = window.speech_synthesis().unwrap(); for v in speech_synthesis.get_voices().iter() { let v: SpeechSynthesisVoice = v.into(); if v.voice_uri() == voice.id { self.voice = Some(v); return Ok(()); } } Err(Error::OperationFailed) } else { Err(Error::NoneError) } } } impl Drop for Web { fn drop(&mut self) { let mut mappings = UTTERANCE_MAPPINGS.lock().unwrap(); mappings.retain(|v| v.0 != self.id); } } impl From for Voice { fn from(other: SpeechSynthesisVoice) -> Self { let language = LanguageTag::parse(other.lang()).unwrap(); Voice { id: other.voice_uri(), name: other.name(), gender: None, language, } } }