mirror of
https://github.com/kilyabin/psysonic.git
synced 2026-07-21 22:15:40 +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>
63 lines
1.7 KiB
Rust
63 lines
1.7 KiB
Rust
use std::sync::Arc;
|
|
|
|
extern crate jni;
|
|
|
|
use self::jni::Executor;
|
|
use self::jni::{errors::Result as JResult, JNIEnv, JavaVM};
|
|
|
|
// constants from android.media.AudioFormat
|
|
pub const ENCODING_PCM_16BIT: i32 = 2;
|
|
pub const ENCODING_PCM_FLOAT: i32 = 4;
|
|
pub const CHANNEL_OUT_MONO: i32 = 4;
|
|
pub const CHANNEL_OUT_STEREO: i32 = 12;
|
|
|
|
fn with_attached<F, R>(closure: F) -> JResult<R>
|
|
where
|
|
F: FnOnce(&mut JNIEnv) -> JResult<R>,
|
|
{
|
|
let android_context = ndk_context::android_context();
|
|
let vm = Arc::new(unsafe { JavaVM::from_raw(android_context.vm().cast())? });
|
|
Executor::new(vm).with_attached(|env| closure(env))
|
|
}
|
|
|
|
fn get_min_buffer_size(
|
|
class: &'static str,
|
|
sample_rate: i32,
|
|
channel_mask: i32,
|
|
format: i32,
|
|
) -> i32 {
|
|
// Unwrapping everything because these operations are not expected to fail
|
|
// or throw exceptions. Android returns negative values for invalid parameters,
|
|
// which is what we expect.
|
|
with_attached(|env| {
|
|
let class = env.find_class(class).unwrap();
|
|
env.call_static_method(
|
|
class,
|
|
"getMinBufferSize",
|
|
"(III)I",
|
|
&[sample_rate.into(), channel_mask.into(), format.into()],
|
|
)
|
|
.unwrap()
|
|
.i()
|
|
})
|
|
.unwrap()
|
|
}
|
|
|
|
pub fn get_audio_track_min_buffer_size(sample_rate: i32, channel_mask: i32, format: i32) -> i32 {
|
|
get_min_buffer_size(
|
|
"android/media/AudioTrack",
|
|
sample_rate,
|
|
channel_mask,
|
|
format,
|
|
)
|
|
}
|
|
|
|
pub fn get_audio_record_min_buffer_size(sample_rate: i32, channel_mask: i32, format: i32) -> i32 {
|
|
get_min_buffer_size(
|
|
"android/media/AudioRecord",
|
|
sample_rate,
|
|
channel_mask,
|
|
format,
|
|
)
|
|
}
|