mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-22 22:45:41 +00:00
34b4445c13
Two independent user-facing fixes rolled together. ── Lyrics: YouLyPlus provider (issue #172) ── Adds word-by-word synced (karaoke) lyrics as an alternative lyrics mode. Backed by the public lyricsplus aggregator (Apple Music / Spotify / Musixmatch / QQ Music) — no API keys on our side, subscription costs are borne by the backend operator. Five mirrors are tried on network failure. Settings → Lyrics now exposes a mode radio (Standard vs YouLyPlus) plus a static-only toggle. YouLyPlus misses silently fall back to the existing server + LRCLIB + Netease pipeline so obscure tracks still resolve. The drag-to-reorder source list is hidden in YouLyPlus mode since the external aggregator manages its own source priority. Rendering: per-word spans on each line, active word highlighted with accent-tinted glow. Subscribed imperatively via usePlayerStore.subscribe so 500 ms progress ticks do not re-render the whole lyrics block. The Fullscreen Player reuses the same pattern; the 5-line rail layout is unchanged. white-space: pre on .fs-lyric-word preserves the trailing spaces the API embeds in each syllabus token (flex context would otherwise collapse them). i18n: new keys in all 8 locales (de, en, fr, nl, nb, ru, es, zh). ── macOS mic-prompt suppression ── Ships a vendored cpal 0.15.3 at patches/cpal-0.15.3/ wired via [patch.crates-io]. The only change is in src/host/coreaudio/macos/mod.rs: audio_unit_from_device now unconditionally uses IOType::DefaultOutput for output streams instead of conditionally choosing HalOutput. AUHAL (HalOutput) is classified as input-capable by macOS TCC and triggers the microphone-permission dialog at first launch / after each update even for playback-only apps. Previous attempts (removing NSMicrophoneUsageDescription, setting com.apple.security.device.audio-input false in Entitlements.plist) did not suppress the prompt because TCC fires at AudioUnit instantiation, not at plist level. The upstream fix in cpal PR #1070 / cpal 0.17 only helps when the pinned device equals the system default; always-DefaultOutput covers all cases. Tradeoff: per-device output selection is a no-op on macOS — the stream always follows the system default. Settings.tsx surfaces this via audioOutputDeviceMacNotice (hides the CustomSelect on macOS) and skips audio_list_devices + device-watcher effects there. Matches the behaviour of Apple Music / Spotify on macOS. Also removes the now-obsolete com.apple.security.device.audio-input entitlement (sandbox is disabled anyway, so it was ignored). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
75 lines
2.7 KiB
Rust
75 lines
2.7 KiB
Rust
extern crate anyhow;
|
|
extern crate cpal;
|
|
|
|
use cpal::traits::{DeviceTrait, HostTrait};
|
|
|
|
fn main() -> Result<(), anyhow::Error> {
|
|
println!("Supported hosts:\n {:?}", cpal::ALL_HOSTS);
|
|
let available_hosts = cpal::available_hosts();
|
|
println!("Available hosts:\n {:?}", available_hosts);
|
|
|
|
for host_id in available_hosts {
|
|
println!("{}", host_id.name());
|
|
let host = cpal::host_from_id(host_id)?;
|
|
|
|
let default_in = host.default_input_device().map(|e| e.name().unwrap());
|
|
let default_out = host.default_output_device().map(|e| e.name().unwrap());
|
|
println!(" Default Input Device:\n {:?}", default_in);
|
|
println!(" Default Output Device:\n {:?}", default_out);
|
|
|
|
let devices = host.devices()?;
|
|
println!(" Devices: ");
|
|
for (device_index, device) in devices.enumerate() {
|
|
println!(" {}. \"{}\"", device_index + 1, device.name()?);
|
|
|
|
// Input configs
|
|
if let Ok(conf) = device.default_input_config() {
|
|
println!(" Default input stream config:\n {:?}", conf);
|
|
}
|
|
let input_configs = match device.supported_input_configs() {
|
|
Ok(f) => f.collect(),
|
|
Err(e) => {
|
|
println!(" Error getting supported input configs: {:?}", e);
|
|
Vec::new()
|
|
}
|
|
};
|
|
if !input_configs.is_empty() {
|
|
println!(" All supported input stream configs:");
|
|
for (config_index, config) in input_configs.into_iter().enumerate() {
|
|
println!(
|
|
" {}.{}. {:?}",
|
|
device_index + 1,
|
|
config_index + 1,
|
|
config
|
|
);
|
|
}
|
|
}
|
|
|
|
// Output configs
|
|
if let Ok(conf) = device.default_output_config() {
|
|
println!(" Default output stream config:\n {:?}", conf);
|
|
}
|
|
let output_configs = match device.supported_output_configs() {
|
|
Ok(f) => f.collect(),
|
|
Err(e) => {
|
|
println!(" Error getting supported output configs: {:?}", e);
|
|
Vec::new()
|
|
}
|
|
};
|
|
if !output_configs.is_empty() {
|
|
println!(" All supported output stream configs:");
|
|
for (config_index, config) in output_configs.into_iter().enumerate() {
|
|
println!(
|
|
" {}.{}. {:?}",
|
|
device_index + 1,
|
|
config_index + 1,
|
|
config
|
|
);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Ok(())
|
|
}
|