bevy_tts/
lib.rs

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}