chore(legacy): delete dead Tauri commands in commands/playback.rs

Removes 11 #[tauri::command] functions that are unregistered and have
zero Rust call sites:
- pause_playback, resume_playback, stop_playback
- play_next_gapless, set_media_metadata
- get_audio_devices, reinit_audio_device
- get_audio_output_status, get_pipewire_sinks
- set_pipewire_default_sink, debug_get_cpal_devices

play_track, seek, and set_volume are preserved - they are still
consumed by src-tauri/src/api_server/mod.rs (REST endpoints for
the PWA remote). They will be migrated to call commands_v2/playback
or core_bridge in a later step.

Helper functions used solely by the deleted commands are also
removed: AudioDevice, AudioOutputStatus, PipewireSink structs,
get_pipewire_device_descriptions (linux + non-linux), and
cpal_device_name. Helpers reachable from play_track / seek /
set_volume / prefetch_track / get_playback_state (parse_quality,
limit_quality_for_device, download_audio, get_stream_info,
extract_audio_format_from_header, download_and_stream,
spawn_prefetch, prefetch constants and PREFETCH_SEMAPHORE) are
preserved.

cc
This commit is contained in:
vicrodh committed 2026-04-26 17:07:56 -06:00
1 parent 92ceb025a1
commit aecdd4f5c8
1 file changed
-469
-469
View File
@@ -1028,121 +1028,6 @@ fn spawn_prefetch(
}
}
/// Pause playback
#[tauri::command]
pub fn pause_playback(state: State<'_, AppState>) -> Result<(), String> {
log::info!("Command: pause_playback");
state.media_controls.set_playback(false);
state.player.pause()
}
/// Resume playback
#[tauri::command]
pub fn resume_playback(state: State<'_, AppState>) -> Result<(), String> {
log::info!("Command: resume_playback");
state.media_controls.set_playback(true);
state.player.resume()
}
/// Stop playback
#[tauri::command]
pub fn stop_playback(state: State<'_, AppState>) -> Result<(), String> {
log::info!("Command: stop_playback");
state.media_controls.set_stopped();
state.player.stop()
}
/// Queue next track for gapless playback (cache-only, no download)
/// Returns true if gapless was queued, false if track not cached or ineligible
#[tauri::command]
pub async fn play_next_gapless(
track_id: u64,
state: State<'_, AppState>,
offline_cache: State<'_, OfflineCacheState>,
) -> Result<bool, String> {
log::info!("Command: play_next_gapless for track {}", track_id);
// Check offline cache (persistent disk cache)
{
let cached_path = {
let db_opt = offline_cache.db.lock().await;
if let Some(db) = db_opt.as_ref() {
if let Ok(Some(file_path)) = db.get_file_path(track_id) {
Some(file_path)
} else {
None
}
} else {
None
}
};
if let Some(file_path) = cached_path {
let path = std::path::Path::new(&file_path);
if path.exists() {
log::info!("[GAPLESS] Track {} from OFFLINE cache", track_id);
let audio_data = std::fs::read(path)
.map_err(|e| format!("Failed to read cached file: {}", e))?;
state.player.play_next(audio_data, track_id)?;
return Ok(true);
}
}
}
// Check memory cache (L1)
let cache = state.audio_cache.clone();
if let Some(cached) = cache.get(track_id) {
log::info!(
"[GAPLESS] Track {} from MEMORY cache ({} bytes)",
track_id,
cached.size_bytes
);
state.player.play_next(cached.data, track_id)?;
return Ok(true);
}
// Check playback cache (L2 - disk)
if let Some(playback_cache) = cache.get_playback_cache() {
if let Some(audio_data) = playback_cache.get(track_id) {
log::info!(
"[GAPLESS] Track {} from DISK cache ({} bytes)",
track_id,
audio_data.len()
);
state.player.play_next(audio_data, track_id)?;
return Ok(true);
}
}
log::info!(
"[GAPLESS] Track {} not in any cache, gapless not possible",
track_id
);
Ok(false)
}
/// Set media controls metadata (for MPRIS integration)
#[tauri::command]
pub fn set_media_metadata(
title: String,
artist: String,
album: String,
duration_secs: Option<u64>,
cover_url: Option<String>,
state: State<'_, AppState>,
) -> Result<(), String> {
log::info!("Command: set_media_metadata - {} by {}", title, artist);
crate::update_media_controls_metadata(
&state.media_controls,
&title,
&artist,
&album,
duration_secs,
cover_url,
);
state.media_controls.set_playback_with_progress(true, 0);
Ok(())
}
/// Set volume (0.0 - 1.0)
#[tauri::command]
pub fn set_volume(volume: f32, state: State<'_, AppState>) -> Result<(), String> {
@@ -1182,357 +1067,3 @@ pub fn get_playback_state(state: State<'_, AppState>) -> Result<PlaybackState, S
Ok(playback_state)
}
/// Audio device information
#[derive(serde::Serialize)]
pub struct AudioDevice {
pub name: String, // Technical name (internal use)
pub display_name: String, // Friendly name for UI
pub is_default: bool,
pub device_type: Option<String>, // "USB", "PCI", "HDMI", etc.
}
/// Get friendly device names from PipeWire/PulseAudio
#[cfg(target_os = "linux")]
fn get_pipewire_device_descriptions() -> std::collections::HashMap<String, String> {
use std::process::Command;
let mut map = std::collections::HashMap::new();
// Try pactl (PipeWire/PulseAudio)
if let Ok(output) = Command::new("pactl").args(&["list", "sinks"]).output() {
if let Ok(text) = String::from_utf8(output.stdout) {
let mut current_name: Option<String> = None;
for line in text.lines() {
let line = line.trim();
// Extract Name: field
if let Some(name) = line.strip_prefix("Name: ") {
current_name = Some(name.to_string());
}
// Extract Description: field
if let Some(desc) = line.strip_prefix("Description: ") {
if let Some(name) = &current_name {
map.insert(name.clone(), desc.to_string());
log::debug!("Device mapping: {} -> {}", name, desc);
}
}
}
}
}
log::info!(
"Found {} device descriptions from PipeWire/PulseAudio",
map.len()
);
map
}
#[cfg(not(target_os = "linux"))]
fn get_pipewire_device_descriptions() -> std::collections::HashMap<String, String> {
std::collections::HashMap::new()
}
fn cpal_device_name(device: &rodio::cpal::Device) -> Option<String> {
use rodio::cpal::traits::DeviceTrait;
device
.description()
.ok()
.map(|description| description.name().to_string())
}
/// Get available audio output devices
#[tauri::command]
pub fn get_audio_devices() -> Result<Vec<AudioDevice>, String> {
log::info!("Command: get_audio_devices");
use rodio::cpal::traits::HostTrait;
let host = rodio::cpal::default_host();
let default_device_name = host
.default_output_device()
.and_then(|d| cpal_device_name(&d));
// Get friendly names from PipeWire/PulseAudio
let friendly_names = get_pipewire_device_descriptions();
let devices: Vec<AudioDevice> = host
.output_devices()
.map_err(|e| format!("Failed to enumerate devices: {}", e))?
.filter_map(|device| {
cpal_device_name(&device).map(|name| {
let is_default = default_device_name
.as_ref()
.map(|d| d == &name)
.unwrap_or(false);
// Try to get friendly name from PipeWire/PulseAudio
let display_name = friendly_names.get(&name).cloned().unwrap_or_else(|| {
// Fallback: clean up technical name slightly
name.replace("alsa_output.", "")
.replace("_", " ")
.replace("pro-output", "")
.trim()
.to_string()
});
// Detect device type from technical name
let device_type = if name.contains("usb") || name.contains("USB") {
Some("USB".to_string())
} else if name.contains("hdmi") || name.contains("HDMI") {
Some("HDMI".to_string())
} else if name.contains("pci") || name.contains("PCI") {
Some("PCI".to_string())
} else if name.contains("bluetooth") || name.contains("bluez") {
Some("Bluetooth".to_string())
} else {
None
};
log::debug!(
"Device: {} -> {} (type: {:?}, default: {})",
name,
display_name,
device_type,
is_default
);
AudioDevice {
name,
display_name,
is_default,
device_type,
}
})
})
.collect();
log::info!("Found {} audio output devices", devices.len());
Ok(devices)
}
/// Current audio output status
#[derive(serde::Serialize)]
pub struct AudioOutputStatus {
pub device_name: Option<String>,
pub is_playing: bool,
}
/// Get current audio output status (what device is actually being used)
#[tauri::command]
pub fn get_audio_output_status(
state: tauri::State<'_, crate::AppState>,
) -> Result<AudioOutputStatus, String> {
let device_name = state.player.state.current_device();
let is_playing = state.player.state.is_playing();
Ok(AudioOutputStatus {
device_name,
is_playing,
})
}
/// Reinitialize audio device (releases and re-acquires)
/// Call this when changing audio settings like exclusive mode or output device
#[deprecated(since = "1.2.0", note = "Use v2_reinit_audio_device instead")]
#[tauri::command]
pub fn reinit_audio_device(
device: Option<String>,
state: tauri::State<'_, crate::AppState>,
audio_settings_state: tauri::State<'_, crate::config::audio_settings::AudioSettingsState>,
) -> Result<(), String> {
log::info!("Command: reinit_audio_device {:?}", device);
// Reload settings from database to ensure Player has latest config (including backend_type)
if let Ok(guard) = audio_settings_state.store.lock() {
if let Some(store) = guard.as_ref() {
if let Ok(fresh_settings) = store.get_settings() {
log::info!(
"Reloading audio settings before reinit (backend_type: {:?})",
fresh_settings.backend_type
);
let _ = state.player.reload_settings(fresh_settings);
}
}
}
state.player.reinit_device(device)
}
/// PipeWire/PulseAudio sink information (cross-platform compatible struct)
#[derive(serde::Serialize, Clone)]
pub struct PipewireSink {
/// Internal name (e.g., "alsa_output.usb-XXX" on Linux, device name on Mac)
pub name: String,
/// User-friendly description (e.g., "AB13X Headset Adapter Analog Stereo")
pub description: String,
/// Current volume percentage (0-100) - only available on Linux
pub volume: Option<u32>,
/// Whether this is the default sink
pub is_default: bool,
}
/// Get audio output devices using CPAL (works on all platforms including Linux/PipeWire)
/// CRITICAL: Uses CPAL device names so they match what the player can open
#[cfg(target_os = "linux")]
#[tauri::command]
pub fn get_pipewire_sinks() -> Result<Vec<PipewireSink>, String> {
log::debug!("Command: get_pipewire_sinks (Linux, using CPAL)");
use rodio::cpal::traits::{DeviceTrait, HostTrait};
let host = rodio::cpal::default_host();
// Get default device name from CPAL
let default_device_name = host
.default_output_device()
.and_then(|d| cpal_device_name(&d));
log::debug!("CPAL default device: {:?}", default_device_name);
// Enumerate all output devices using CPAL
let sinks: Vec<PipewireSink> = host
.output_devices()
.map_err(|e| format!("Failed to enumerate devices: {}", e))?
.enumerate()
.filter_map(|(idx, device)| {
let name = match cpal_device_name(&device) {
Some(name) => name,
None => {
log::warn!(" [{}] Failed to get device description", idx);
return None;
}
};
let is_default = default_device_name
.as_ref()
.map(|d| d == &name)
.unwrap_or(false);
// Get detailed device info for logging
let configs_info = device
.supported_output_configs()
.ok()
.map(|configs| {
let config_strs: Vec<String> = configs
.take(3) // Just first 3 configs for brevity
.map(|c| format!("{}ch/{}Hz", c.channels(), c.max_sample_rate()))
.collect();
config_strs.join(", ")
})
.unwrap_or_else(|| "no configs".to_string());
log::debug!(
" [{}] Device: '{}' (default: {}) - Configs: {}",
idx,
name,
is_default,
configs_info
);
// CRITICAL: Use CPAL device name as both name and description
// This ensures the name we save is the exact name CPAL can find later
Some(PipewireSink {
name: name.clone(),
description: name, // CPAL names are already user-friendly in PipeWire
volume: None, // Volume not available via CPAL
is_default,
})
})
.collect();
log::debug!("Found {} audio output devices via CPAL", sinks.len());
Ok(sinks)
}
/// Get audio output devices using cpal (macOS/Windows fallback)
/// Returns devices in the same format as Linux for UI compatibility
#[cfg(not(target_os = "linux"))]
#[tauri::command]
pub fn get_pipewire_sinks() -> Result<Vec<PipewireSink>, String> {
log::info!("Command: get_pipewire_sinks (non-Linux, using cpal)");
use rodio::cpal::traits::HostTrait;
let host = rodio::cpal::default_host();
let default_device_name = host
.default_output_device()
.and_then(|d| cpal_device_name(&d));
let sinks: Vec<PipewireSink> = host
.output_devices()
.map_err(|e| format!("Failed to enumerate devices: {}", e))?
.filter_map(|device| {
cpal_device_name(&device).map(|name| {
let is_default = default_device_name
.as_ref()
.map(|d| d == &name)
.unwrap_or(false);
PipewireSink {
name: name.clone(),
description: name, // On Mac/Windows, name is usually descriptive enough
volume: None, // Volume not available via cpal
is_default,
}
})
})
.collect();
log::info!("Found {} audio output devices", sinks.len());
Ok(sinks)
}
/// Set the default PipeWire/PulseAudio sink
/// This is used when the user selects an audio device in settings
#[cfg(target_os = "linux")]
#[tauri::command]
pub fn set_pipewire_default_sink(sink_name: String) -> Result<(), String> {
use std::process::Command;
log::info!("Command: set_pipewire_default_sink {}", sink_name);
let output = Command::new("pactl")
.args(["set-default-sink", &sink_name])
.output()
.map_err(|e| format!("Failed to run pactl: {}", e))?;
if !output.status.success() {
let stderr = String::from_utf8_lossy(&output.stderr);
return Err(format!("pactl set-default-sink failed: {}", stderr));
}
log::info!("Set default sink to: {}", sink_name);
Ok(())
}
/// Set the default sink (no-op on non-Linux)
#[cfg(not(target_os = "linux"))]
#[tauri::command]
pub fn set_pipewire_default_sink(_sink_name: String) -> Result<(), String> {
log::info!("Command: set_pipewire_default_sink (no-op on non-Linux)");
Ok(())
}
/// DEBUG: Get CPAL device names for comparison with PipeWire sinks
#[tauri::command]
pub fn debug_get_cpal_devices() -> Result<Vec<String>, String> {
use rodio::cpal::traits::HostTrait;
log::info!("Command: debug_get_cpal_devices");
let host = rodio::cpal::default_host();
let devices: Vec<String> = host
.output_devices()
.map_err(|e| format!("Failed to enumerate devices: {}", e))?
.filter_map(|device| cpal_device_name(&device))
.collect();
log::info!("CPAL devices: {:?}", devices);
Ok(devices)
}