feat: replace Tauri with iced for native cross-platform GUI

- Create iced-app crate with pure Rust GUI using iced 0.13
- Implement server list, connect form, channel/user panels, messaging
- Implement ServerQuery panel for direct server queries
- Add VoiceEngine in tsaudio for decode/buffer/playback pipeline
- Update CI to build iced binary (no frontend build step needed)
- Remove Tauri dependency from workspace
- 69 tests passing across all crates
This commit is contained in:
ReTeamSpeak
2026-05-12 20:35:24 +09:00
parent c878d74a6a
commit c4113373c8
6 changed files with 745 additions and 50 deletions
+117
View File
@@ -0,0 +1,117 @@
use crate::buffer::JitterBuffer;
use crate::codec::OpusDecoder;
use crate::playback::AudioPlayback;
use crate::{AudioConfig, AudioError, AudioFrame, AudioResult};
pub struct VoiceEngine {
decoder: OpusDecoder,
jitter_buffer: JitterBuffer,
playback: AudioPlayback,
config: AudioConfig,
client_decoders: std::collections::HashMap<u16, OpusDecoder>,
}
impl VoiceEngine {
pub fn new(config: AudioConfig) -> AudioResult<Self> {
let decoder = OpusDecoder::new(config.sample_rate, config.channels)?;
let jitter_buffer = JitterBuffer::with_target_delay(100, 60);
let playback = AudioPlayback::new(config.clone());
Ok(Self {
decoder,
jitter_buffer,
playback,
config,
client_decoders: std::collections::HashMap::new(),
})
}
pub fn start(&mut self) -> AudioResult<()> {
self.playback.start()
}
pub fn stop(&mut self) -> AudioResult<()> {
self.playback.stop()
}
pub fn process_voice_data(
&mut self,
client_id: u16,
codec: u8,
packet_id: u16,
audio_data: &[u8],
) -> AudioResult<()> {
let decoder = self.client_decoders.entry(client_id).or_insert_with(|| {
OpusDecoder::new(self.config.sample_rate, self.config.channels)
.unwrap_or_else(|_| {
OpusDecoder::new(48000, 1).unwrap()
})
});
let samples = decoder.decode_packet(audio_data)?;
let frame = AudioFrame::new(self.config.sample_rate, self.config.channels, samples)
.with_sequence(packet_id)
.with_codec(codec);
self.jitter_buffer.push(frame)?;
Ok(())
}
pub fn output_tick(&mut self) -> AudioResult<()> {
if let Some(frame) = self.jitter_buffer.pop() {
self.playback.play(frame)?;
}
Ok(())
}
pub fn run_output_loop(&mut self) -> AudioResult<()> {
loop {
self.output_tick()?;
std::thread::sleep(std::time::Duration::from_millis(5));
}
}
pub fn list_output_devices() -> AudioResult<Vec<String>> {
AudioPlayback::list_devices()
}
pub fn buffered_ms(&self) -> u32 {
self.jitter_buffer.buffered_ms()
}
pub fn jitter_buffer_len(&self) -> usize {
self.jitter_buffer.len()
}
pub fn playback_buffer_len(&self) -> usize {
self.playback.buffer_len()
}
pub fn clear_jitter_buffer(&mut self) {
self.jitter_buffer.clear();
}
pub fn remove_client(&mut self, client_id: u16) {
self.client_decoders.remove(&client_id);
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_voice_engine_new() {
let config = AudioConfig::default();
let engine = VoiceEngine::new(config);
assert!(engine.is_ok());
}
#[test]
fn test_voice_engine_list_devices() {
let devices = VoiceEngine::list_output_devices();
assert!(devices.is_ok());
}
}
+2
View File
@@ -3,12 +3,14 @@
pub mod buffer;
pub mod capture;
pub mod codec;
pub mod engine;
pub mod playback;
pub mod vad;
pub use buffer::*;
pub use capture::*;
pub use codec::*;
pub use engine::*;
pub use playback::*;
pub use vad::*;