Vozduxan Engine (C++)¶
vozduxan is a custom C++ library that wraps libtorrent for low-latency audio streaming. It handles everything from magnet/torrent resolution to piece-priority scheduling to serving the audio stream over a local HTTP server. The Rust side never touches libtorrent directly — it only calls through the C API declared in vozduxan_ffi.rs.
C API surface¶
The library is linked as a static archive (libvozduxan.a) and exposes a flat C ABI:
#[link(name = "vozduxan", kind = "static")]
unsafe extern "C" {
pub fn vozduxan_session_create(config: *const VozduxanConfig) -> *mut VozduxanSession;
pub fn vozduxan_session_destroy(session: *mut VozduxanSession);
pub fn vozduxan_stream_prepare(
session: *mut VozduxanSession,
magnet: *const c_char,
torrent_data: *const u8,
torrent_len: usize,
file_idx: c_int,
is_main: c_int,
progress_fn: VozduxanProgressFn,
userdata: *mut c_void,
) -> VozduxanStreamInfo;
pub fn vozduxan_stream_notify_position(
session: *mut VozduxanSession,
token: *const c_char,
byte_offset: i64,
);
pub fn vozduxan_stream_release(session: *mut VozduxanSession, token: *const c_char);
pub fn vozduxan_list_files(
session: *mut VozduxanSession,
magnet: *const c_char,
torrent_data: *const u8,
torrent_len: usize,
) -> VozduxanFileList;
pub fn vozduxan_file_list_free(list: *mut VozduxanFileList);
pub fn vozduxan_session_evict(session: *mut VozduxanSession);
pub fn vozduxan_session_http_port(session: *mut VozduxanSession) -> u16;
}
VozduxanSession is an opaque C++ object. The Rust side holds it as *mut VozduxanSession inside VozduxanSessionInner and never dereferences it — only passes it back to C functions.
Session creation¶
vozduxan_session_create takes a VozduxanConfig:
#[repr(C)]
pub struct VozduxanConfig {
pub storage_path: *const c_char, // path for libtorrent state & piece cache
pub cache_max_bytes: u64, // 0 = 50 GB default
pub cache_ttl_secs: u32, // 0 = 3600 s default
pub listen_port: c_int, // 0 = random (OS picks)
pub log_fn: VozduxanLogFn, // optional log callback (NULL = stderr only)
pub log_userdata: *mut c_void, // forwarded verbatim to log_fn
}
In neegde all fields use defaults (zeros) except storage_path and the log callback. The storage path is bt/vozduxan/ relative to the executable (see Data Directory).
The log callback is wired to AppDebugLog — every C++ log line goes through:
unsafe extern "C" fn on_vozduxan_log(message: *const c_char, userdata: *mut c_void) {
let debug_log = unsafe { &*(userdata as *const AppDebugLog) };
let msg = CStr::from_ptr(message).to_string_lossy().into_owned();
// Suppress high-frequency peer-connection noise
let lower = msg.to_ascii_lowercase();
if lower.contains("connecttopeer") || lower.contains("connect to peer") {
return;
}
debug_log.push("vozduxan", msg, None);
}
The raw pointer in VozduxanConfig.log_userdata points into the Arc<AppDebugLog> that VozduxanSessionInner keeps alive in its _debug_log_arc field. The Arc must not be dropped while the C++ session exists — if it were, the pointer would dangle and the log callback would write into freed memory.
Stream preparation¶
vozduxan_stream_prepare is the primary entry point for playback. It is a blocking call — it does not return until the stream is ready to serve. On the Rust side it always runs inside tokio::task::spawn_blocking.
magnet URI ─┐
.torrent bytes ─┤──→ vozduxan_stream_prepare ──→ VozduxanStreamInfo
file_idx ─┘ (blocks until prebuffer full)
Two input modes¶
| Mode | Condition | Behaviour |
|---|---|---|
| Magnet + DHT | torrent_data == NULL |
vozduxan resolves metadata via DHT; may take 30–90 s on cold start |
| Torrent file | torrent_data != NULL |
metadata is available immediately; metadata_received_alert is not fired |
For RuTracker content, the Rust layer downloads the .torrent file and passes it as base64. The Tauri command decodes it before calling into vozduxan:
let torrent_bytes: Option<Vec<u8>> = match torrent_file_b64.as_deref() {
None | Some("") => None,
Some(s) => Some(
base64::engine::general_purpose::STANDARD
.decode(s.trim())
.map_err(|e| format!("bad base64: {e}"))?,
),
};
This avoids the DHT metadata wait entirely.
Known invariant
When torrent_data is provided, libtorrent does not emit metadata_received_alert because the metadata is already set on the add_torrent_params struct. vozduxan handles this case internally in C++. Do not add a workaround on the Rust side.
is_main flag¶
is_main = 1 signals active playback — vozduxan cancels any in-flight fast-start for the same torrent and begins a fresh one at the requested file. is_main = 0 is used for background prefetch: the session warms up the file without disrupting any current download.
Return value¶
#[repr(C)]
pub struct VozduxanStreamInfo {
pub url: [u8; 512], // "http://127.0.0.1:PORT/stream/TOKEN\0"
pub token: [u8; 128], // opaque stream identifier, used in subsequent calls
pub file_size: i64,
pub mime_type: [u8; 64],
pub error: VozduxanError,
pub error_msg: [u8; 256],
}
On success, error == VozduxanError::Ok and url contains the full localhost URL that is handed to the <audio> element. The token string identifies the stream for notify_position, release, and stats.
Error codes¶
pub enum VozduxanError {
Ok = 0,
MetadataTimeout = 1, // DHT resolution exceeded 90 s
InvalidFile = 2, // file_idx out of range or not an audio file
BadInput = 3, // null magnet, malformed torrent data
Internal = 99, // unspecified C++ exception
}
Piece priority scheduling¶
Inside C++, vozduxan implements a three-tier piece priority scheme adapted from Tribler research on libtorrent:
| Tier | Piece range (from current position) | Mechanism | Purpose |
|---|---|---|---|
| TIER1 | next 20 pieces | set_piece_deadline(8000 ms) |
Time-critical; stall prevention |
| TIER2 | pieces 20–60 ahead | high priority | Look-ahead buffer |
| TIER3 | remaining pieces | default priority | Background download |
The window slides forward on every vozduxan_notify_position call. The frontend calls this on every timeupdate event from the <audio> element:
// Player.vue
invoke("vozduxan_notify_position", { token, byteOffset: audioEl.currentTime * bytesPerSecond })
C++ recalculates the piece index from byte_offset and re-sets the priority window. This keeps the "deadline" pieces aligned with where playback actually is, not where it started.
Local HTTP server¶
vozduxan runs an internal HTTP/1.1 server bound to 127.0.0.1 on a randomly chosen port. The port is fixed for the lifetime of the session and can be queried with vozduxan_session_http_port. Every VozduxanStreamInfo.url is pre-formatted to include this port:
The server supports HTTP Range requests, which is what browsers use for audio seeking. When <audio> seeks to a new position, it sends Range: bytes=X-. C++ checks a per-stream seek_generation counter between chunk writes. If a new seek arrives (counter incremented) while an old Range response is still in flight, the old transfer aborts immediately without waiting to finish.
Fast-start prebuffer¶
vozduxan_stream_prepare does not return until a minimum amount of data is buffered. The prebuffer size adapts to connection conditions:
- Fast connection (many peers, high download rate): ~32 KB
- Slow connection: up to 512 KB
This is why the call blocks — returning early would cause immediate stalling in the <audio> element. During prebuffering the VozduxanProgressFn callback fires periodically, which the Rust wrapper forwards as a torrent-prepare-progress Tauri event so the frontend can show a spinner with percentage.
unsafe extern "C" fn on_progress(progress: f32, status: *const c_char, userdata: *mut c_void) {
let ctx = unsafe { &*(userdata as *const ProgressCtx) };
let _ = ctx.app.emit(
"torrent-prepare-progress",
PrepareProgressPayload::buffering((progress as f64) * 100.0, msg),
);
}
Release and eviction¶
vozduxan_stream_release releases a stream and joins the priority thread inside C++. This join takes approximately 100 ms. This is why release must never happen synchronously on the tokio async executor — it would block the thread for that duration. The Rust wrapper wraps every release in spawn_blocking:
tokio::task::spawn_blocking(move || {
let token_c = CString::new(token.as_str()).unwrap();
unsafe { ffi::vozduxan_stream_release(inner.ptr, token_c.as_ptr()) };
// ... clear token bucket, maybe call evict
})
.await
Idle torrents accumulate in the libtorrent session until vozduxan_session_evict is called. neegde calls it:
- Every 10 stream releases (tracked by
evict_counterinVozduxanSessionInner) - On every explicit
torrent_dispose_preview(cache purge)
File listing¶
vozduxan_list_files resolves a magnet or torrent and returns the file tree without starting playback. Used by the torrent panel to show tracks before the user picks one.
pub struct VozduxanFileEntry {
pub name: [u8; 512],
pub mime: [u8; 64],
pub size: i64,
pub index: c_int, // the file_idx to pass to vozduxan_stream_prepare later
}
pub struct VozduxanFileList {
pub files: *mut VozduxanFileEntry,
pub count: c_int,
pub error: VozduxanError,
pub error_msg: [u8; 256],
}
VozduxanFileList is heap-allocated inside C++. The Rust wrapper frees it with vozduxan_file_list_free immediately after converting entries to Vec<TorrentFile>.
Download stats¶
vozduxan_stream_stats returns live telemetry for a stream token. It is a fast, non-blocking call (no I/O):
The frontend calls vozduxan_stream_stats periodically to show peer count and download speed in the stream status popup.
Build integration¶
vozduxan is compiled as a static library by build.rs via CMake. cargo:rerun-if-changed must enumerate individual C++ source files — macOS does not update directory mtime when a file inside changes, so without per-file tracking Cargo would not detect edits. See Vozduxan CMake Build.