feat: seeking support and Last.fm scrobbling
Seeking: - Rust player: seek_requested/seek_target_secs atomics on PlayerStatus - Decoder loop checks for seek each iteration, calls format.seek() and resets decoder - New qobuz_backend_seek C FFI + QobuzBackend::seek(quint64) - Progress slider onProgressReleased now seeks to the dragged position Last.fm: - LastFmScrobbler: now-playing + scrobble (50% or 240s threshold, min 30s) - API signature follows Last.fm spec (sorted params, md5) - Settings dialog: API key/secret, username/password, Connect button with status - AppSettings: lastfm/enabled, api_key, api_secret, session_key - Scrobbler wired to trackChanged, positionChanged, trackFinished in MainWindow Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -8,10 +8,11 @@ use std::sync::{
|
||||
use symphonia::core::{
|
||||
codecs::DecoderOptions,
|
||||
errors::Error as SymphoniaError,
|
||||
formats::FormatOptions,
|
||||
formats::{FormatOptions, SeekMode, SeekTo},
|
||||
io::{MediaSource, MediaSourceStream},
|
||||
meta::MetadataOptions,
|
||||
probe::Hint,
|
||||
units::Time,
|
||||
};
|
||||
|
||||
use crate::player::{output::AudioOutput, PlayerStatus};
|
||||
@@ -172,6 +173,21 @@ pub fn play_track(
|
||||
}
|
||||
}
|
||||
|
||||
if status.seek_requested.load(Ordering::SeqCst) {
|
||||
status.seek_requested.store(false, Ordering::SeqCst);
|
||||
let target = status.seek_target_secs.load(Ordering::Relaxed);
|
||||
let seeked = format.seek(
|
||||
SeekMode::Coarse,
|
||||
SeekTo::Time { time: Time::from(target), track_id: None },
|
||||
);
|
||||
if let Ok(s) = seeked {
|
||||
let actual = s.actual_ts / sample_rate as u64;
|
||||
status.position_secs.store(actual, Ordering::Relaxed);
|
||||
}
|
||||
decoder.reset();
|
||||
continue;
|
||||
}
|
||||
|
||||
let packet = match format.next_packet() {
|
||||
Ok(p) => p,
|
||||
Err(SymphoniaError::IoError(e)) if e.kind() == std::io::ErrorKind::UnexpectedEof => {
|
||||
|
||||
Reference in New Issue
Block a user