mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-22 14:35:41 +00:00
refactor: extract psysonic-audio crate (M3/7)
Moves all audio playback code (Symphonia decode, rodio output, HTTP
streaming, gapless, previews, and the seven stream/ source-type
submodules from the prior split) out of the top crate into a new
psysonic-audio crate.
crates/psysonic-audio/ new lib crate, depends on
psysonic-core + psysonic-analysis
src/{engine,helpers,decode,…}.rs flattened layout (no more
extra audio/ namespace level)
src/stream/ seven submodules from M0
src/lib.rs re-exports macros from
psysonic-core and the public
API surface
The audio↔analysis edges identified in the dep survey are now real
crate deps (audio depends on analysis directly: AnalysisCache reads,
recommended_gain_for_target, submit_analysis_cpu_seed). Only the
analysis→audio back-edge goes through the PlaybackQueryHandle port
registered in M2.
Cross-crate ref migrations applied via batch sed:
crate::audio::* → crate::* (intra-crate)
crate::analysis_cache::* → psysonic_analysis::analysis_cache::*
crate::submit_analysis_cpu_seed → psysonic_analysis::analysis_runtime::*
crate::subsonic_wire_user_agent → psysonic_core::user_agent::*
Top crate keeps `crate::audio::*` paths working via
`pub use psysonic_audio as audio;` — lib_commands/cli callers untouched.
`stop_audio_engine` (mac process-exit cleanup) moved into the audio
crate as `pub fn stop_audio_engine` since it reaches AudioEngine
internals; tray.rs now re-exports the moved fn.
Two small visibility promotions in engine.rs:
pub(crate) fn analysis_track_id_is_current_playback → pub
pub(crate) fn ranged_loudness_backfill_should_defer → pub
Behaviour preserving. Cargo check + clippy --workspace clean.
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
//! Background audio_preload: fetch the next track's bytes ahead of time
|
||||
//! and seed the analysis cache. Distinct from `audio_chain_preload`
|
||||
//! (which constructs the gapless source chain) and `audio_play` (which
|
||||
//! starts playback). All three live in this audio submodule.
|
||||
|
||||
use std::sync::atomic::Ordering;
|
||||
use std::time::Duration;
|
||||
|
||||
use tauri::{AppHandle, Emitter, State};
|
||||
|
||||
use super::engine::{audio_http_client, AudioEngine};
|
||||
use super::helpers::{analysis_cache_track_id, same_playback_target};
|
||||
use super::state::PreloadedTrack;
|
||||
|
||||
#[tauri::command]
|
||||
pub async fn audio_preload(
|
||||
url: String,
|
||||
duration_hint: f64,
|
||||
analysis_track_id: Option<String>,
|
||||
app: AppHandle,
|
||||
state: State<'_, AudioEngine>,
|
||||
) -> Result<(), String> {
|
||||
{
|
||||
let preloaded = state.preloaded.lock().unwrap();
|
||||
if preloaded.as_ref().is_some_and(|p| same_playback_target(&p.url, &url)) {
|
||||
let _ = app.emit("audio:preload-ready", url.clone());
|
||||
return Ok(());
|
||||
}
|
||||
}
|
||||
// Throttle: wait 8 s before starting the background download so it does not
|
||||
// compete with the decode + sink-feed work of the just-started current track.
|
||||
// If the user skips during the wait the generation counter changes and we abort.
|
||||
let gen_snapshot = state.generation.load(Ordering::Relaxed);
|
||||
tokio::time::sleep(Duration::from_secs(8)).await;
|
||||
if state.generation.load(Ordering::Relaxed) != gen_snapshot {
|
||||
return Ok(());
|
||||
}
|
||||
let data: Vec<u8> = if let Some(path) = url.strip_prefix("psysonic-local://") {
|
||||
tokio::fs::read(path).await.map_err(|e| e.to_string())?
|
||||
} else {
|
||||
let response = audio_http_client(&state).get(&url).send().await.map_err(|e| e.to_string())?;
|
||||
if !response.status().is_success() {
|
||||
return Ok(());
|
||||
}
|
||||
response.bytes().await.map_err(|e| e.to_string())?.into()
|
||||
};
|
||||
let _ = duration_hint; // kept in API for compatibility
|
||||
let logical_trim = analysis_track_id
|
||||
.as_ref()
|
||||
.map(|s| s.trim().to_string())
|
||||
.filter(|s| !s.is_empty());
|
||||
if let Some(track_id) = analysis_cache_track_id(logical_trim.as_deref(), &url) {
|
||||
crate::app_deprintln!(
|
||||
"[stream] audio_preload: bytes ready track_id={} size_mib={:.2} — invoking full-track analysis",
|
||||
track_id,
|
||||
data.len() as f64 / (1024.0 * 1024.0)
|
||||
);
|
||||
let high = crate::engine::analysis_track_id_is_current_playback(&state, &track_id);
|
||||
if let Err(e) = psysonic_analysis::analysis_runtime::submit_analysis_cpu_seed(app.clone(), track_id.clone(), data.clone(), high).await {
|
||||
crate::app_eprintln!("[analysis] preload seed failed for {}: {}", track_id, e);
|
||||
}
|
||||
}
|
||||
let url_for_emit = url.clone();
|
||||
*state.preloaded.lock().unwrap() = Some(PreloadedTrack { url, data });
|
||||
let _ = app.emit("audio:preload-ready", url_for_emit);
|
||||
Ok(())
|
||||
}
|
||||
Reference in New Issue
Block a user