Skip to content

Commit 2e06104

Browse files
JSKittyclaude
andcommitted
feat: unified cpal audio engine with streaming decode and precomputed FFT waveforms
Replace HTML5 <audio> + Web Audio API with a Rust-native cpal audio engine. One persistent output stream handles all playback — voice messages, audio attachments, and notification sounds (desktop) — with real-time mixing. Key changes: - audio_engine.rs: singleton cpal stream, multi-source mixer, streaming decode for non-WAV formats, mixer-side linear interpolation for sample rate conversion, crossfade on seek, incremental FFT waveform computation - Precomputed waveform data (64 bins x 30fps) sent to frontend once, eliminating per-frame IPC for visualization - Voice recording preview plays through engine (no more Blob URLs/IPC) - send_recording command keeps audio in Rust — WAV bytes never cross IPC - audio_stop_all on chat navigation prevents ghost playback - audio_probe for instant duration display before first play - Removed voice_message Tauri command (now internal-only function) Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent efc60fa commit 2e06104

24 files changed

Lines changed: 2020 additions & 696 deletions

src-tauri/Cargo.lock

Lines changed: 78 additions & 3 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src-tauri/Cargo.toml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,8 @@ fast-thumbhash = "0.2"
5757
base64-simd = "0.8"
5858
cpal = "0.16.0"
5959
hound = "3.5.1"
60-
symphonia = { version = "0.5.5", features = ["mp3", "wav", "flac", "pcm"] }
60+
symphonia = { version = "0.5.5", features = ["mp3", "wav", "flac", "pcm", "aac", "isomp4", "ogg", "vorbis"] }
61+
rustfft = "6.2"
6162
rusqlite = { version = "0.32", features = ["bundled"] }
6263
# Pin OpenMLS crypto deps to versions MDK was tested with (newer versions pull broken sha3/digest pre-release chain)
6364
openmls_rust_crypto = "=0.5.0"

src-tauri/build.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@ fn main() {
3333
// Message commands
3434
"message",
3535
"paste_message",
36-
"voice_message",
3736
"file_message",
3837
"file_message_compressed",
3938
"get_file_info",
@@ -193,6 +192,16 @@ fn main() {
193192
"run_maintenance",
194193
// Debug commands (conditional)
195194
"debug_hot_reload_sync",
195+
// Audio engine commands
196+
"audio_probe",
197+
"audio_load",
198+
"audio_play",
199+
"audio_pause",
200+
"audio_seek",
201+
"audio_stop",
202+
"audio_stop_all",
203+
"audio_set_volume",
204+
"send_recording",
196205
// Encryption toggle commands
197206
"get_encryption_status",
198207
"disable_encryption",

src-tauri/capabilities/default.json

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,6 @@
3131
"allow-toggle-chat-mute",
3232
"allow-message",
3333
"allow-paste-message",
34-
"allow-voice-message",
3534
"allow-file-message",
3635
"allow-file-message-compressed",
3736
"allow-forward-attachment",
@@ -218,6 +217,15 @@
218217
"allow-is-directory",
219218
"allow-zip-directory",
220219
"allow-cleanup-zip",
221-
"allow-update-group-metadata"
220+
"allow-update-group-metadata",
221+
"allow-audio-probe",
222+
"allow-audio-load",
223+
"allow-audio-play",
224+
"allow-audio-pause",
225+
"allow-audio-seek",
226+
"allow-audio-stop",
227+
"allow-audio-stop-all",
228+
"allow-audio-set-volume",
229+
"allow-send-recording"
222230
]
223231
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Automatically generated - DO NOT EDIT!
2+
3+
[[permission]]
4+
identifier = "allow-audio-load"
5+
description = "Enables the audio_load command without any pre-configured scope."
6+
commands.allow = ["audio_load"]
7+
8+
[[permission]]
9+
identifier = "deny-audio-load"
10+
description = "Denies the audio_load command without any pre-configured scope."
11+
commands.deny = ["audio_load"]
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Automatically generated - DO NOT EDIT!
2+
3+
[[permission]]
4+
identifier = "allow-audio-pause"
5+
description = "Enables the audio_pause command without any pre-configured scope."
6+
commands.allow = ["audio_pause"]
7+
8+
[[permission]]
9+
identifier = "deny-audio-pause"
10+
description = "Denies the audio_pause command without any pre-configured scope."
11+
commands.deny = ["audio_pause"]
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Automatically generated - DO NOT EDIT!
2+
3+
[[permission]]
4+
identifier = "allow-audio-play"
5+
description = "Enables the audio_play command without any pre-configured scope."
6+
commands.allow = ["audio_play"]
7+
8+
[[permission]]
9+
identifier = "deny-audio-play"
10+
description = "Denies the audio_play command without any pre-configured scope."
11+
commands.deny = ["audio_play"]
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Automatically generated - DO NOT EDIT!
2+
3+
[[permission]]
4+
identifier = "allow-audio-probe"
5+
description = "Enables the audio_probe command without any pre-configured scope."
6+
commands.allow = ["audio_probe"]
7+
8+
[[permission]]
9+
identifier = "deny-audio-probe"
10+
description = "Denies the audio_probe command without any pre-configured scope."
11+
commands.deny = ["audio_probe"]
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Automatically generated - DO NOT EDIT!
2+
3+
[[permission]]
4+
identifier = "allow-audio-seek"
5+
description = "Enables the audio_seek command without any pre-configured scope."
6+
commands.allow = ["audio_seek"]
7+
8+
[[permission]]
9+
identifier = "deny-audio-seek"
10+
description = "Denies the audio_seek command without any pre-configured scope."
11+
commands.deny = ["audio_seek"]
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
# Automatically generated - DO NOT EDIT!
2+
3+
[[permission]]
4+
identifier = "allow-audio-set-volume"
5+
description = "Enables the audio_set_volume command without any pre-configured scope."
6+
commands.allow = ["audio_set_volume"]
7+
8+
[[permission]]
9+
identifier = "deny-audio-set-volume"
10+
description = "Denies the audio_set_volume command without any pre-configured scope."
11+
commands.deny = ["audio_set_volume"]

0 commit comments

Comments
 (0)