1use bevy::{
2 ecs::{component::HookContext, world::DeferredWorld},
3 prelude::*,
4};
5use crossbeam_channel::{unbounded, Receiver};
6pub use tts::{self, Backends, Error, Features, UtteranceId};
7
8#[derive(Component, Resource, Clone, Deref, DerefMut)]
9#[component(on_add = on_tts_added, on_remove=on_tts_removed)]
10pub struct Tts(pub tts::Tts);
11
12impl Default for Tts {
13 fn default() -> Self {
14 Self(tts::Tts::default().unwrap())
15 }
16}
17
18impl Tts {
19 pub fn screen_reader_available() -> bool {
20 tts::Tts::screen_reader_available()
21 }
22}
23
24#[derive(Event, Debug)]
25pub enum TtsEvent {
26 UtteranceBegin(UtteranceId),
27 UtteranceEnd(UtteranceId),
28 UtteranceStop(UtteranceId),
29}
30
31#[derive(Component, Resource)]
32struct TtsChannel(Receiver<TtsEvent>);
33
34fn on_tts_added(mut world: DeferredWorld, HookContext { entity, .. }: HookContext) {
35 let tts = &world.get::<Tts>(entity).unwrap();
36 let channel = setup_tts(tts);
37 world.commands().entity(entity).insert(channel);
38}
39
40fn on_tts_removed(mut world: DeferredWorld, HookContext { entity, .. }: HookContext) {
41 world.commands().entity(entity).remove::<TtsChannel>();
42}
43
44fn poll_callbacks(
45 mut commands: Commands,
46 channel: Res<TtsChannel>,
47 mut events: EventWriter<TtsEvent>,
48 speakers: Query<(Entity, &TtsChannel), With<Tts>>,
49) {
50 if let Ok(msg) = channel.0.try_recv() {
51 events.write(msg);
52 }
53 for (entity, channel) in &speakers {
54 if let Ok(msg) = channel.0.try_recv() {
55 commands.entity(entity).trigger(msg);
56 }
57 }
58}
59
60fn setup_tts(tts: &Tts) -> TtsChannel {
61 let (tx, rx) = unbounded();
62 let tx_begin = tx.clone();
63 let tx_end = tx.clone();
64 let tx_stop = tx;
65 let Features {
66 utterance_callbacks,
67 ..
68 } = tts.supported_features();
69 if utterance_callbacks {
70 tts.on_utterance_begin(Some(Box::new(move |utterance| {
71 tx_begin.send(TtsEvent::UtteranceBegin(utterance)).unwrap();
72 })))
73 .unwrap();
74 tts.on_utterance_end(Some(Box::new(move |utterance| {
75 tx_end.send(TtsEvent::UtteranceEnd(utterance)).unwrap();
76 })))
77 .unwrap();
78 tts.on_utterance_stop(Some(Box::new(move |utterance| {
79 tx_stop.send(TtsEvent::UtteranceStop(utterance)).unwrap();
80 })))
81 .unwrap();
82 }
83 TtsChannel(rx)
84}
85pub struct TtsPlugin;
86
87impl Plugin for TtsPlugin {
88 fn build(&self, app: &mut App) {
89 let tts = Tts::default();
90 let channel = setup_tts(&tts);
91 app.add_event::<TtsEvent>()
92 .insert_resource(channel)
93 .insert_resource(tts)
94 .add_systems(Update, poll_callbacks);
95 }
96}