feat: qbz-1 streaming, gapless prefetch, accurate scrobbling, Range-seek

Port proven playback architecture from qbqt fork:
- Bounded VecDeque buffer with condvar backpressure (4MB cap)
- decrypt_and_extract_frames for clean FLAC frame extraction from ISOBMFF
- Cancel+restart seeking with sub-segment sample skipping
- start_prefetch / QueueNext for gapless transitions with pre-started downloads
- track_transitioned signaling for scrobbler during gapless playback
- Range-request HTTP seeking for non-segmented (MP3) tracks
- OnceLock HTTP client singleton with cancel-aware chunked downloads
- Accumulated listening time scrobbling (prevents false scrobbles from seeking)
- Array-format Last.fm scrobble params (artist[0], track[0], etc.)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
joren
2026-03-30 20:12:23 +02:00
parent 6c8d032ce9
commit 70810bd4b1
16 changed files with 1539 additions and 382 deletions

View File

@@ -209,21 +209,6 @@ quint64 QobuzBackend::duration() const { return qobuz_backend_get_duration(m_bac
int QobuzBackend::volume() const { return qobuz_backend_get_volume(m_backend); }
int QobuzBackend::state() const { return qobuz_backend_get_state(m_backend); }
quint32 QobuzBackend::vizRead(float *buf, quint32 maxSamples)
{
return qobuz_backend_viz_read(m_backend, buf, maxSamples);
}
quint32 QobuzBackend::vizSampleRate() const
{
return qobuz_backend_viz_sample_rate(m_backend);
}
quint32 QobuzBackend::vizChannels() const
{
return qobuz_backend_viz_channels(m_backend);
}
// ---- private slots ----
void QobuzBackend::onPositionTick()
@@ -232,6 +217,9 @@ void QobuzBackend::onPositionTick()
if (qobuz_backend_take_track_finished(m_backend))
emit trackFinished();
if (qobuz_backend_take_track_transitioned(m_backend))
emit trackTransitioned();
}
void QobuzBackend::onEvent(int eventType, const QString &json)