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:
1 file changed
-469
@@ -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) = ¤t_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)
|
||||
}
|
||||
Reference in new issue
Block a user