bevy_talk 0.19.0

Voice chat while in game
Documentation
use bevy_app::prelude::*;
use bevy_connect::prelude::*;
use bevy_ecs::prelude::*;
use serde::{Deserialize, Serialize};
use std::net::IpAddr;

use crate::piper::AudioPiper;

pub struct TalkPlugin;
pub struct StartTalk;
pub struct StopTalk;
pub struct ConnectTalk {
    ip: IpAddr,
    port: u16,
    host: bool,
}
pub struct DisconnectTalk;

impl Plugin for TalkPlugin {
    fn build(&self, app: &mut App) {
        app.add_plugins(SessionPlugin::<AudioMessage>::default());
        app.add_systems(
            PreUpdate,
            send.run_if(
                resource_exists::<Channel<AudioMessage>>.and_then(resource_exists::<AudioPiper>),
            ),
        );
        app.add_systems(
            PostUpdate,
            recv.run_if(
                resource_exists::<Channel<AudioMessage>>.and_then(resource_exists::<AudioPiper>),
            ),
        );
    }
}

impl Command for StartTalk {
    type Out = ();

    fn apply(self, world: &mut World) {
        world.insert_resource(AudioPiper::new());
    }
}

impl Command for StopTalk {
    type Out = ();

    fn apply(self, world: &mut World) {
        world.remove_resource::<AudioPiper>();
    }
}

impl ConnectTalk {
    pub fn host(ip: IpAddr, port: u16) -> Self {
        Self {
            ip,
            port,
            host: true,
        }
    }

    pub fn client(ip: IpAddr, port: u16) -> Self {
        Self {
            ip,
            port,
            host: false,
        }
    }
}

impl Command for ConnectTalk {
    type Out = ();

    fn apply(self, world: &mut World) {
        world
            .commands()
            .queue(SessionConnectCommand::<AudioMessage>::from_config(
                SessionConfig::Direct {
                    addr: Some(self.ip),
                    port: self.port,
                    host: self.host,
                    compress: false,
                    key: None,
                    options: Default::default(),
                },
            ));
    }
}

impl Command for DisconnectTalk {
    type Out = ();

    fn apply(self, world: &mut World) {
        world
            .commands()
            .queue(SessionDisconnectCommand::<AudioMessage>::default());
    }
}

#[derive(Serialize, Deserialize)]
enum AudioMessage {
    Audio(AudioPacket),
}

#[derive(Serialize, Deserialize)]
struct AudioPacket {
    data: Vec<f32>,
}

fn recv(ap: Res<AudioPiper>, mut ch: MessageReader<MessageReceivedEvent<AudioMessage>>) {
    let mut out = vec![];
    for p in ch.read() {
        let AudioMessage::Audio(p) = p.message.as_ref();
        out.extend_from_slice(&p.data);
    }
    if !out.is_empty() {
        ap.sink(&out);
    }
}

fn send(ap: Res<AudioPiper>, mut ch: ResMut<Channel<AudioMessage>>) {
    let data = ap.read();
    if !data.is_empty() {
        ch.broadcast(AudioMessage::Audio(AudioPacket { data }));
    }
}