Initialize project and implement decentralized voice chat client (PipeWire, Opus, Iroh, Iced)
This commit is contained in:
@@ -0,0 +1,93 @@
|
||||
use iroh::EndpointId;
|
||||
use bytes::Bytes;
|
||||
use thiserror::Error;
|
||||
use tokio::sync::mpsc::Receiver;
|
||||
use async_trait::async_trait;
|
||||
use serde::{Serialize, Deserialize};
|
||||
use std::str::FromStr;
|
||||
|
||||
#[derive(Error, Debug)]
|
||||
pub enum NetError {
|
||||
#[error("Failed to initialize network: {0}")]
|
||||
Init(String),
|
||||
#[error("Failed to connect/dial peer: {0}")]
|
||||
Connection(String),
|
||||
#[error("Gossip swarm error: {0}")]
|
||||
Gossip(String),
|
||||
#[error("Serialization / Deserialization error: {0}")]
|
||||
Serde(String),
|
||||
#[error("Invalid ticket: {0}")]
|
||||
InvalidTicket(String),
|
||||
#[error("Other network error: {0}")]
|
||||
Other(String),
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
|
||||
pub struct PeerState {
|
||||
pub name: String,
|
||||
pub is_muted: bool,
|
||||
pub addr: iroh::EndpointAddr,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum RoomEvent {
|
||||
PeerJoined(EndpointId, PeerState),
|
||||
PeerLeft(EndpointId),
|
||||
PeerUpdated(EndpointId, PeerState),
|
||||
}
|
||||
|
||||
#[derive(Serialize, Deserialize, Clone, Debug)]
|
||||
pub struct PeerSpeakTicket {
|
||||
pub host_addr: iroh::EndpointAddr,
|
||||
pub topic_id: [u8; 32],
|
||||
}
|
||||
|
||||
impl ToString for PeerSpeakTicket {
|
||||
fn to_string(&self) -> String {
|
||||
let serialized = serde_json::to_vec(self).unwrap();
|
||||
// Convert to base64 URL-safe string
|
||||
base64::Engine::encode(&base64::engine::general_purpose::URL_SAFE_NO_PAD, &serialized)
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for PeerSpeakTicket {
|
||||
type Err = NetError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
let decoded = base64::Engine::decode(&base64::engine::general_purpose::URL_SAFE_NO_PAD, s)
|
||||
.map_err(|e| NetError::InvalidTicket(e.to_string()))?;
|
||||
let ticket: PeerSpeakTicket = serde_json::from_slice(&decoded)
|
||||
.map_err(|e| NetError::InvalidTicket(e.to_string()))?;
|
||||
Ok(ticket)
|
||||
}
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait NetworkTransport: Send + Sync {
|
||||
/// Send a low-latency unreliable datagram to a specific peer (for audio).
|
||||
async fn send_datagram(&self, peer_id: EndpointId, data: Bytes) -> Result<(), NetError>;
|
||||
|
||||
/// Subscribes to incoming datagrams from any peer.
|
||||
async fn receive_datagrams(&self) -> Result<Receiver<(EndpointId, Bytes)>, NetError>;
|
||||
}
|
||||
|
||||
#[async_trait]
|
||||
pub trait RoomState: Send + Sync {
|
||||
/// Joins a room using a gossip ticket string and announces our state.
|
||||
async fn join(&self, ticket: &str, self_state: PeerState) -> Result<(), NetError>;
|
||||
|
||||
/// Updates our local state (e.g. when user mutes/unmutes) and broadcasts it.
|
||||
async fn update_self_state(&self, self_state: PeerState) -> Result<(), NetError>;
|
||||
|
||||
/// Leaves the room and announces departure.
|
||||
async fn leave(&self) -> Result<(), NetError>;
|
||||
|
||||
/// Returns a list of currently active peers in the room.
|
||||
fn active_peers(&self) -> Vec<(EndpointId, PeerState)>;
|
||||
|
||||
/// Subscribes to room events (peer joined, peer left, peer updated).
|
||||
async fn subscribe_events(&self) -> Result<Receiver<RoomEvent>, NetError>;
|
||||
}
|
||||
|
||||
pub mod iroh_impl;
|
||||
pub mod gossip;
|
||||
Reference in New Issue
Block a user