started working on voice chat
This commit is contained in:
180
src-tauri/Cargo.lock
generated
180
src-tauri/Cargo.lock
generated
@@ -32,6 +32,28 @@ dependencies = [
|
||||
"alloc-no-stdlib",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "alsa"
|
||||
version = "0.10.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7c88dbbce13b232b26250e1e2e6ac18b6a891a646b8148285036ebce260ac5c3"
|
||||
dependencies = [
|
||||
"alsa-sys",
|
||||
"bitflags 2.10.0",
|
||||
"cfg-if",
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "alsa-sys"
|
||||
version = "0.3.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "db8fee663d06c4e303404ef5f40488a53e062f89ba8bfed81f42325aafad1527"
|
||||
dependencies = [
|
||||
"libc",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "android_system_properties"
|
||||
version = "0.1.5"
|
||||
@@ -551,6 +573,51 @@ dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "coreaudio-rs"
|
||||
version = "0.13.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "1aae284fbaf7d27aa0e292f7677dfbe26503b0d555026f702940805a630eac17"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"libc",
|
||||
"objc2-audio-toolbox",
|
||||
"objc2-core-audio",
|
||||
"objc2-core-audio-types",
|
||||
"objc2-core-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpal"
|
||||
version = "0.17.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5b1f9c7312f19fc2fa12fd7acaf38de54e8320ba10d1a02dcbe21038def51ccb"
|
||||
dependencies = [
|
||||
"alsa",
|
||||
"coreaudio-rs",
|
||||
"dasp_sample",
|
||||
"jack",
|
||||
"jni",
|
||||
"js-sys",
|
||||
"libc",
|
||||
"mach2",
|
||||
"ndk",
|
||||
"ndk-context",
|
||||
"num-derive",
|
||||
"num-traits",
|
||||
"objc2",
|
||||
"objc2-audio-toolbox",
|
||||
"objc2-avf-audio",
|
||||
"objc2-core-audio",
|
||||
"objc2-core-audio-types",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation",
|
||||
"wasm-bindgen",
|
||||
"wasm-bindgen-futures",
|
||||
"web-sys",
|
||||
"windows",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "cpufeatures"
|
||||
version = "0.2.17"
|
||||
@@ -666,6 +733,12 @@ dependencies = [
|
||||
"syn 2.0.111",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "dasp_sample"
|
||||
version = "0.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "0c87e182de0887fd5361989c677c4e8f5000cd9491d6d563161a8f3a5519fc7f"
|
||||
|
||||
[[package]]
|
||||
name = "data-encoding"
|
||||
version = "2.9.0"
|
||||
@@ -1020,6 +1093,7 @@ dependencies = [
|
||||
name = "frangipane"
|
||||
version = "0.1.0"
|
||||
dependencies = [
|
||||
"cpal",
|
||||
"serde",
|
||||
"serde_json",
|
||||
"tauri",
|
||||
@@ -1033,6 +1107,7 @@ dependencies = [
|
||||
"tauri-plugin-store",
|
||||
"tauri-plugin-upload",
|
||||
"tauri-plugin-websocket",
|
||||
"tracing",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1853,6 +1928,33 @@ version = "1.0.15"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "4a5f13b858c8d314ee3e8f639011f7ccefe71f97f96e50151fb991f267928e2c"
|
||||
|
||||
[[package]]
|
||||
name = "jack"
|
||||
version = "0.13.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "73213dab741ae0a4623824289625611d11bb8254ad1fdefc84e480f7632aa528"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"jack-sys",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"log",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "jack-sys"
|
||||
version = "0.5.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6013b7619b95a22b576dfb43296faa4ecbe40abbdb97dfd22ead520775fc86ab"
|
||||
dependencies = [
|
||||
"bitflags 1.3.2",
|
||||
"lazy_static",
|
||||
"libc",
|
||||
"libloading",
|
||||
"log",
|
||||
"pkg-config",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "javascriptcore-rs"
|
||||
version = "1.1.2"
|
||||
@@ -2066,6 +2168,15 @@ dependencies = [
|
||||
"time",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "mach2"
|
||||
version = "0.5.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6a1b95cd5421ec55b445b5ae102f5ea0e768de1f82bd3001e11f426c269c3aea"
|
||||
dependencies = [
|
||||
"libc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "markup5ever"
|
||||
version = "0.14.1"
|
||||
@@ -2235,6 +2346,17 @@ version = "0.1.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9"
|
||||
|
||||
[[package]]
|
||||
name = "num-derive"
|
||||
version = "0.4.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "ed3955f1a9c7c0c15e092f9c887db08b1fc683305fdf6eb6684f22555355e202"
|
||||
dependencies = [
|
||||
"proc-macro2",
|
||||
"quote",
|
||||
"syn 2.0.111",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "num-traits"
|
||||
version = "0.2.19"
|
||||
@@ -2297,6 +2419,31 @@ dependencies = [
|
||||
"objc2-quartz-core",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-audio-toolbox"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "6948501a91121d6399b79abaa33a8aa4ea7857fe019f341b8c23ad6e81b79b08"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"libc",
|
||||
"objc2",
|
||||
"objc2-core-audio",
|
||||
"objc2-core-audio-types",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-avf-audio"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "13a380031deed8e99db00065c45937da434ca987c034e13b87e4441f9e4090be"
|
||||
dependencies = [
|
||||
"objc2",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-cloud-kit"
|
||||
version = "0.3.2"
|
||||
@@ -2308,6 +2455,29 @@ dependencies = [
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-audio"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "e1eebcea8b0dbff5f7c8504f3107c68fc061a3eb44932051c8cf8a68d969c3b2"
|
||||
dependencies = [
|
||||
"dispatch2",
|
||||
"objc2",
|
||||
"objc2-core-audio-types",
|
||||
"objc2-core-foundation",
|
||||
"objc2-foundation",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-audio-types"
|
||||
version = "0.3.2"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5a89f2ec274a0cf4a32642b2991e8b351a404d290da87bb6a9a9d8632490bd1c"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"objc2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "objc2-core-data"
|
||||
version = "0.3.2"
|
||||
@@ -2326,7 +2496,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2a180dd8642fa45cdb7dd721cd4c11b1cadd4929ce112ebd8b9f5803cc79d536"
|
||||
dependencies = [
|
||||
"bitflags 2.10.0",
|
||||
"block2",
|
||||
"dispatch2",
|
||||
"libc",
|
||||
"objc2",
|
||||
]
|
||||
|
||||
@@ -4724,9 +4896,9 @@ checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3"
|
||||
|
||||
[[package]]
|
||||
name = "tracing"
|
||||
version = "0.1.43"
|
||||
version = "0.1.44"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "2d15d90a0b5c19378952d479dc858407149d7bb45a14de0142f6c534b16fc647"
|
||||
checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100"
|
||||
dependencies = [
|
||||
"pin-project-lite",
|
||||
"tracing-attributes",
|
||||
@@ -4746,9 +4918,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "tracing-core"
|
||||
version = "0.1.35"
|
||||
version = "0.1.36"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7a04e24fab5c89c6a36eb8558c9656f30d81de51dfa4d3b45f26b21d61fa0a6c"
|
||||
checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a"
|
||||
dependencies = [
|
||||
"once_cell",
|
||||
]
|
||||
|
||||
@@ -30,4 +30,6 @@ tauri-plugin-dialog = "2"
|
||||
tauri-plugin-fs = "2"
|
||||
tauri-plugin-os = "2"
|
||||
tauri-plugin-notification = "2"
|
||||
cpal = { version = "0.17.1", features = ["jack"] }
|
||||
tracing = "0.1.44"
|
||||
|
||||
|
||||
8
src-tauri/Info.plist
Normal file
8
src-tauri/Info.plist
Normal file
@@ -0,0 +1,8 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>NSMicrophoneUsageDescription</key>
|
||||
<string>Request microphone access for WebRTC</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -57,4 +57,5 @@
|
||||
"os:default",
|
||||
"notification:default"
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
<uses-permission android:name="android.permission.RECORD_AUDIO" />
|
||||
<uses-permission android:name="android.permission.MODIFY_AUDIO_SETTINGS" />
|
||||
|
||||
<!-- AndroidTV support -->
|
||||
<uses-feature android:name="android.software.leanback" android:required="false" />
|
||||
@@ -35,4 +37,4 @@
|
||||
android:resource="@xml/file_paths" />
|
||||
</provider>
|
||||
</application>
|
||||
</manifest>
|
||||
</manifest>
|
||||
175
src-tauri/src/audio.rs
Normal file
175
src-tauri/src/audio.rs
Normal file
@@ -0,0 +1,175 @@
|
||||
use cpal::traits::{DeviceTrait, HostTrait, StreamTrait};
|
||||
use cpal::SampleFormat;
|
||||
use std::sync::{Arc, Mutex};
|
||||
use tauri::{AppHandle, Emitter};
|
||||
|
||||
pub struct AudioState {
|
||||
stream: Arc<Mutex<Option<cpal::Stream>>>,
|
||||
}
|
||||
|
||||
impl AudioState {
|
||||
pub fn new() -> Self {
|
||||
Self {
|
||||
stream: Arc::new(Mutex::new(None)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, serde::Serialize)]
|
||||
struct MicConfig {
|
||||
sample_rate: u32,
|
||||
channels: usize,
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn start_microphone(app: AppHandle, state: tauri::State<AudioState>) -> Result<(), String> {
|
||||
#[cfg(all(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd"
|
||||
)))]
|
||||
let host = cpal::host_from_id(
|
||||
cpal::available_hosts()
|
||||
.into_iter()
|
||||
.find(|id| *id == cpal::HostId::Jack)
|
||||
.expect("jack host unavailable"),
|
||||
)
|
||||
.unwrap_or(
|
||||
cpal::host_from_id(*cpal::available_hosts().first().expect("no host available"))
|
||||
.expect("host not available"),
|
||||
);
|
||||
|
||||
#[cfg(any(not(any(
|
||||
target_os = "linux",
|
||||
target_os = "dragonfly",
|
||||
target_os = "freebsd",
|
||||
target_os = "netbsd"
|
||||
))))]
|
||||
let host = cpal::default_host();
|
||||
|
||||
let device = host
|
||||
.default_input_device()
|
||||
.ok_or("No input device available")?;
|
||||
|
||||
let config = device
|
||||
.default_input_config()
|
||||
.map_err(|e| format!("Failed to get config: {}", e))?;
|
||||
|
||||
println!("Microphone Config: {:?}", config);
|
||||
let sample_rate = config.sample_rate();
|
||||
let device_channels = config.channels() as usize;
|
||||
let channels = 1; // NOTE: temporary
|
||||
|
||||
let buffer_threshold = (sample_rate as usize / 10) * 2;
|
||||
|
||||
app.emit(
|
||||
"microphone-config",
|
||||
MicConfig {
|
||||
sample_rate,
|
||||
channels,
|
||||
},
|
||||
)
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
let sample_format = config.sample_format();
|
||||
let stream_config: cpal::StreamConfig = config.into();
|
||||
|
||||
let err_fn = |err| eprintln!("Stream error: {}", err);
|
||||
let app_handle = app.clone();
|
||||
|
||||
let mut emit_if_full = move |buffer: &mut Vec<u8>| {
|
||||
if buffer.len() >= buffer_threshold {
|
||||
let payload = buffer.clone();
|
||||
let _ = app_handle.emit("microphone-data", payload);
|
||||
buffer.clear();
|
||||
}
|
||||
};
|
||||
|
||||
let app_handle = app.clone();
|
||||
|
||||
let stream = match sample_format {
|
||||
SampleFormat::F32 => {
|
||||
let mut local_buffer = Vec::with_capacity(buffer_threshold * 2);
|
||||
|
||||
device.build_input_stream(
|
||||
&stream_config,
|
||||
move |data: &[f32], _| {
|
||||
for frame in data.chunks(device_channels) {
|
||||
let sample = frame[0]; // Take first channel
|
||||
let s = sample.clamp(-1.0, 1.0);
|
||||
let v = if s >= 0.0 {
|
||||
(s * 32767.0) as i16
|
||||
} else {
|
||||
(s * 32768.0) as i16
|
||||
};
|
||||
local_buffer.extend_from_slice(&v.to_le_bytes());
|
||||
}
|
||||
|
||||
// Only emit if we have enough data
|
||||
if local_buffer.len() >= buffer_threshold {
|
||||
let _ = app_handle.emit("microphone-data", &local_buffer);
|
||||
local_buffer.clear();
|
||||
}
|
||||
},
|
||||
err_fn,
|
||||
None,
|
||||
)
|
||||
}
|
||||
SampleFormat::I16 => {
|
||||
let mut local_buffer = Vec::with_capacity(buffer_threshold * 2);
|
||||
|
||||
device.build_input_stream(
|
||||
&stream_config,
|
||||
move |data: &[i16], _| {
|
||||
for frame in data.chunks(device_channels) {
|
||||
let sample = frame[0];
|
||||
local_buffer.extend_from_slice(&sample.to_le_bytes());
|
||||
}
|
||||
|
||||
if local_buffer.len() >= buffer_threshold {
|
||||
let _ = app_handle.emit("microphone-data", &local_buffer);
|
||||
local_buffer.clear();
|
||||
}
|
||||
},
|
||||
err_fn,
|
||||
None,
|
||||
)
|
||||
}
|
||||
SampleFormat::U16 => {
|
||||
let mut local_buffer = Vec::with_capacity(buffer_threshold * 2);
|
||||
|
||||
device.build_input_stream(
|
||||
&stream_config,
|
||||
move |data: &[u16], _| {
|
||||
for frame in data.chunks(device_channels) {
|
||||
let sample = frame[0];
|
||||
let v = (sample as i32 - 32768) as i16;
|
||||
local_buffer.extend_from_slice(&v.to_le_bytes());
|
||||
}
|
||||
|
||||
if local_buffer.len() >= buffer_threshold {
|
||||
let _ = app_handle.emit("microphone-data", &local_buffer);
|
||||
local_buffer.clear();
|
||||
}
|
||||
},
|
||||
err_fn,
|
||||
None,
|
||||
)
|
||||
}
|
||||
_ => return Err("Unsupported sample format".to_string()),
|
||||
}
|
||||
.map_err(|e| e.to_string())?;
|
||||
|
||||
stream.play().map_err(|e| e.to_string())?;
|
||||
*state.stream.lock().unwrap() = Some(stream);
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[tauri::command]
|
||||
pub fn stop_microphone(state: tauri::State<AudioState>) -> Result<(), String> {
|
||||
let mut stream_guard = state.stream.lock().unwrap();
|
||||
*stream_guard = None;
|
||||
Ok(())
|
||||
}
|
||||
@@ -1,12 +1,15 @@
|
||||
// Learn more about Tauri commands at https://tauri.app/develop/calling-rust/
|
||||
mod audio;
|
||||
use audio::{start_microphone, stop_microphone};
|
||||
|
||||
#[tauri::command]
|
||||
fn greet(name: &str) -> String {
|
||||
format!("Hello, {}! You've been greeted from Rust!", name)
|
||||
fn log(message: &str) {
|
||||
println!("[JS] {}", message);
|
||||
}
|
||||
|
||||
#[cfg_attr(mobile, tauri::mobile_entry_point)]
|
||||
pub fn run() {
|
||||
tauri::Builder::default()
|
||||
.manage(audio::AudioState::new())
|
||||
.plugin(tauri_plugin_notification::init())
|
||||
.plugin(tauri_plugin_os::init())
|
||||
.plugin(tauri_plugin_fs::init())
|
||||
@@ -16,7 +19,11 @@ pub fn run() {
|
||||
.plugin(tauri_plugin_http::init())
|
||||
.plugin(tauri_plugin_store::Builder::new().build())
|
||||
.plugin(tauri_plugin_opener::init())
|
||||
.invoke_handler(tauri::generate_handler![greet])
|
||||
.invoke_handler(tauri::generate_handler![
|
||||
start_microphone,
|
||||
stop_microphone,
|
||||
log
|
||||
])
|
||||
.run(tauri::generate_context!())
|
||||
.expect("error while running tauri application");
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
// Prevents additional console window on Windows in release, DO NOT REMOVE!!
|
||||
#![cfg_attr(not(debug_assertions), windows_subsystem = "windows")]
|
||||
|
||||
mod audio;
|
||||
|
||||
fn main() {
|
||||
frangipane_lib::run()
|
||||
}
|
||||
|
||||
7
src-tauri/tauri.android.conf.json
Normal file
7
src-tauri/tauri.android.conf.json
Normal file
@@ -0,0 +1,7 @@
|
||||
{
|
||||
"bundle": {
|
||||
"android": {
|
||||
"minSdkVersion": 26
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user