mirror of
https://github.com/Psychotoxical/psysonic.git
synced 2026-07-22 07:15:47 +00:00
feat(playback): global speed with three strategies (#852)
* feat(playback): global speed with three strategies Add Settings → Audio and player-bar controls for global playback speed (speed with auto pitch correction as default, varispeed, manual pitch shift). Time-stretch runs on a background worker; Orbit sessions force 1.0× passthrough. * fix(playback): align seekbar, seek, and progress on content timeline Unify UI timebase across varispeed and preserve strategies: full-track duration, speed-scaled progress for DSP paths, and content-timeline seeks without varispeed scaling. Reset the sample counter after seek so clicks land correctly; restart playback on strategy/enable changes instead of fragile hot-switching. * fix(ui): anchor playback speed popover like volume controls Replace the centered EQ-style modal with a player-bar popover (outside click, Escape, reposition on scroll). Show compact controls in the bar and overflow menu; keep strategy hints and labels in Settings only. * docs(release): CHANGELOG and credits for playback speed (PR #852) * docs(changelog): add playback speed entry for PR #852 * fix(clippy): simplify raw_counter_samples branch for CI Collapse duplicate if branches flagged by clippy::if-same-then-else. * fix(ui): wheel on pitch slider adjusts pitch in speed popover In compact player-bar controls, scroll over the pitch row changes pitch; elsewhere in the panel changes speed. Stop propagation so overflow menu wheel does not tweak volume. * fix(playback): address PR #852 review and drop ineffective dynamic imports Translate playback-rate strings for de/fr/es/zh/nb/nl/ro; restamp sample counter on live preserve-path speed changes; use neutral rate atomics for radio progress; static-import playerStore in playListenSession (move preview volume sync to previewPlayerVolumeSync side-effect module). * fix(i18n): translate playback-rate strategy labels in all locales Replace leftover English Varispeed/Pitch strings in ru and other non-en settings blocks so popover strategy buttons and hints read natively. * fix(i18n): refine German varispeed label to "Tonhöhe folgt dem Tempo" --------- Co-authored-by: Psychotoxical <171614930+Psychotoxical@users.noreply.github.com>
This commit is contained in:
@@ -12,6 +12,7 @@ use tauri::{AppHandle, Emitter, Runtime};
|
||||
|
||||
use super::engine::AudioCurrent;
|
||||
use super::helpers::{ramp_sink_volume, ProgressPayload, MASTER_HEADROOM};
|
||||
use super::playback_rate::{effective_duration_secs, effective_position_secs, PlaybackRateAtomics};
|
||||
use super::state::ChainedInfo;
|
||||
|
||||
/// Sink for the three progress events the task emits. Production wraps an
|
||||
@@ -68,6 +69,7 @@ pub(crate) fn spawn_progress_task<E: ProgressEmitter>(
|
||||
gapless_switch_at: Arc<AtomicU64>,
|
||||
current_playback_url: Arc<Mutex<Option<String>>>,
|
||||
stream_playback_armed: Arc<AtomicBool>,
|
||||
playback_rate: PlaybackRateAtomics,
|
||||
) {
|
||||
// Keep progress aligned with audible output (ALSA/PipeWire/Pulse queue) on
|
||||
// Linux; mirrors the quantum policy used for stream open/reopen plus a small
|
||||
@@ -196,10 +198,11 @@ pub(crate) fn spawn_progress_task<E: ProgressEmitter>(
|
||||
|
||||
// Read playback snapshot under a single lock to minimize contention
|
||||
// with seek/play/pause commands that also touch `current`.
|
||||
let (dur, paused_at) = {
|
||||
let (base_dur, paused_at) = {
|
||||
let cur = current_arc.lock().unwrap();
|
||||
(cur.duration_secs, cur.paused_at)
|
||||
};
|
||||
let dur = effective_duration_secs(base_dur, &playback_rate);
|
||||
let is_paused = paused_at.is_some();
|
||||
|
||||
let pos_raw = if !stream_playback_armed.load(Ordering::Relaxed) {
|
||||
@@ -207,7 +210,8 @@ pub(crate) fn spawn_progress_task<E: ProgressEmitter>(
|
||||
} else if let Some(p) = paused_at {
|
||||
p
|
||||
} else {
|
||||
(samples / divisor).min(dur.max(0.001))
|
||||
effective_position_secs(samples / divisor, &playback_rate)
|
||||
.min(dur.max(0.001))
|
||||
};
|
||||
let progress_latency = if is_paused {
|
||||
0.0
|
||||
@@ -333,6 +337,7 @@ mod tests {
|
||||
gapless_switch_at: Arc<AtomicU64>,
|
||||
playback_url: Arc<Mutex<Option<String>>>,
|
||||
stream_playback_armed: Arc<AtomicBool>,
|
||||
playback_rate: PlaybackRateAtomics,
|
||||
}
|
||||
|
||||
impl TaskHarness {
|
||||
@@ -362,6 +367,7 @@ mod tests {
|
||||
gapless_switch_at: Arc::new(AtomicU64::new(0)),
|
||||
playback_url: Arc::new(Mutex::new(None)),
|
||||
stream_playback_armed: Arc::new(AtomicBool::new(true)),
|
||||
playback_rate: PlaybackRateAtomics::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -381,6 +387,7 @@ mod tests {
|
||||
self.gapless_switch_at.clone(),
|
||||
self.playback_url.clone(),
|
||||
self.stream_playback_armed.clone(),
|
||||
self.playback_rate.clone(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user