From 2aff8fda472d6d3e099406b7ce2ce4c3559551e9 Mon Sep 17 00:00:00 2001 From: joren Date: Tue, 31 Mar 2026 00:57:09 +0200 Subject: [PATCH] fix: stabilize seek slider and clean backend lint issues --- rust/src/lib.rs | 1 + rust/src/player/decoder.rs | 64 ++++++++++++++++++------------------ rust/src/player/output.rs | 1 + src/backend/qobuzbackend.cpp | 28 ++++++++-------- src/view/maintoolbar.cpp | 19 +++++++++++ src/view/maintoolbar.hpp | 3 ++ 6 files changed, 70 insertions(+), 46 deletions(-) diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 67d97d4..417465c 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -1,4 +1,5 @@ //! qobuz-backend: C-ABI library consumed by the Qt frontend. +#![allow(clippy::missing_safety_doc)] mod api; mod player; diff --git a/rust/src/player/decoder.rs b/rust/src/player/decoder.rs index 1adea75..13df9fd 100644 --- a/rust/src/player/decoder.rs +++ b/rust/src/player/decoder.rs @@ -157,7 +157,7 @@ impl MediaSource for SegmentStreamSource { fn http_client() -> &'static reqwest::blocking::Client { static CLIENT: OnceLock = OnceLock::new(); - CLIENT.get_or_init(|| reqwest::blocking::Client::new()) + CLIENT.get_or_init(reqwest::blocking::Client::new) } fn fetch_segment(url: &str, cancel: &Arc) -> Option> { @@ -260,40 +260,43 @@ fn decrypt_and_extract_frames(data: &mut [u8], key: Option<&[u8; 16]>) -> Vec= 36 { - if &data[pos + 8..pos + 24] == QBZ1_UUID { - let body = pos + 24; - if body + 12 > data.len() { - pos += box_size; - continue; + if data[pos + 4..pos + 8] == *b"uuid" + && box_size >= 36 + && data[pos + 8..pos + 24] == QBZ1_UUID + { + let body = pos + 24; + if body + 12 > data.len() { + pos += box_size; + continue; + } + + let raw_offset = read_u32_be(data, body + 4) as usize; + let num_samples = read_u24_be(data, body + 9) as usize; + let sample_data_start = pos + raw_offset; + let table_start = body + 12; + + let mut offset = sample_data_start; + for i in 0..num_samples { + let e = table_start + i * 16; + if e + 16 > data.len() { + break; } + let size = read_u32_be(data, e) as usize; + let enc = u16::from_be_bytes([data[e + 6], data[e + 7]]) != 0; - let raw_offset = read_u32_be(data, body + 4) as usize; - let num_samples = read_u24_be(data, body + 9) as usize; - let sample_data_start = pos + raw_offset; - let table_start = body + 12; - - let mut offset = sample_data_start; - for i in 0..num_samples { - let e = table_start + i * 16; - if e + 16 > data.len() { - break; - } - let size = read_u32_be(data, e) as usize; - let enc = u16::from_be_bytes([data[e + 6], data[e + 7]]) != 0; - - let end = offset + size; - if end <= data.len() { - if enc && key.is_some() { + let end = offset + size; + if end <= data.len() { + if enc { + if let Some(k) = key { let mut iv = [0u8; 16]; iv[..8].copy_from_slice(&data[e + 8..e + 16]); - Ctr128BE::::new(key.unwrap().into(), (&iv).into()) + Ctr128BE::::new(k.into(), (&iv).into()) .apply_keystream(&mut data[offset..end]); } - frames.extend_from_slice(&data[offset..end]); } - offset += size; + frames.extend_from_slice(&data[offset..end]); } + offset += size; } } pos += box_size; @@ -845,7 +848,7 @@ impl Seek for HttpStreamSource { .send() { Ok(r) => r, - Err(e) => return Err(io::Error::new(io::ErrorKind::Other, e.to_string())), + Err(e) => return Err(io::Error::other(e.to_string())), }; if resp.status() == reqwest::StatusCode::PARTIAL_CONTENT { @@ -881,10 +884,7 @@ impl Seek for HttpStreamSource { self.pos = self.reader_pos; Ok(self.pos) } else { - Err(io::Error::new( - io::ErrorKind::Other, - format!("HTTP Error {}", resp.status()), - )) + Err(io::Error::other(format!("HTTP Error {}", resp.status()))) } } } diff --git a/rust/src/player/output.rs b/rust/src/player/output.rs index c1aa828..0f6e91a 100644 --- a/rust/src/player/output.rs +++ b/rust/src/player/output.rs @@ -90,6 +90,7 @@ impl AudioOutput { Ok(()) } + #[allow(dead_code)] pub fn flush(&self) { // Wait until the ring buffer is fully emptied by cpal while !self._ring.is_empty() { diff --git a/src/backend/qobuzbackend.cpp b/src/backend/qobuzbackend.cpp index 8c1497b..8153d9e 100644 --- a/src/backend/qobuzbackend.cpp +++ b/src/backend/qobuzbackend.cpp @@ -297,7 +297,7 @@ void QobuzBackend::onEvent(int eventType, const QString &json) case EV_SEARCH_OK: emit searchResult(obj); break; - case 26: // EV_MOST_POPULAR_OK + case EV_MOST_POPULAR_OK: emit mostPopularResult(obj); break; case EV_SEARCH_ERR: @@ -312,7 +312,7 @@ void QobuzBackend::onEvent(int eventType, const QString &json) case EV_ARTIST_OK: emit artistLoaded(obj); break; - case 24: // EV_ARTIST_RELEASES_OK + case EV_ARTIST_RELEASES_OK: emit artistReleasesLoaded( obj["release_type"].toString(), obj["items"].toArray(), @@ -320,25 +320,25 @@ void QobuzBackend::onEvent(int eventType, const QString &json) obj["offset"].toInt() ); break; - case 25: // EV_DEEP_SHUFFLE_OK + case EV_DEEP_SHUFFLE_OK: emit deepShuffleTracksLoaded(obj["tracks"].toArray()); break; - case 29: // EV_DYNAMIC_SUGGEST_OK + case EV_DYNAMIC_SUGGEST_OK: emit dynamicSuggestionsLoaded(obj); break; - case 27: // EV_GENRES_OK + case EV_GENRES_OK: emit genresLoaded(obj); break; - case 28: // EV_FEATURED_ALBUMS_OK + case EV_FEATURED_ALBUMS_OK: emit featuredAlbumsLoaded(obj); break; - case 30: // EV_FEATURED_PLAYLISTS_OK + case EV_FEATURED_PLAYLISTS_OK: emit featuredPlaylistsLoaded(obj); break; - case 31: // EV_DISCOVER_PLAYLISTS_OK + case EV_DISCOVER_PLAYLISTS_OK: emit discoverPlaylistsLoaded(obj); break; - case 32: // EV_PLAYLIST_SEARCH_OK + case EV_PLAYLIST_SEARCH_OK: emit playlistSearchLoaded(obj); break; case EV_ARTIST_ERR: @@ -368,19 +368,19 @@ void QobuzBackend::onEvent(int eventType, const QString &json) case EV_STATE_CHANGED: emit stateChanged(obj["state"].toString()); break; - case 20: // EV_PLAYLIST_CREATED + case EV_PLAYLIST_CREATED: emit playlistCreated(obj); break; - case 21: // EV_PLAYLIST_DELETED + case EV_PLAYLIST_DELETED: emit playlistDeleted(obj); break; - case 22: // EV_PLAYLIST_TRACK_ADDED + case EV_PLAYLIST_TRACK_ADDED: emit playlistTrackAdded(static_cast(obj["playlist_id"].toDouble())); break; - case 33: // EV_PLAYLIST_SUBSCRIBED + case EV_PLAYLIST_SUBSCRIBED: emit playlistSubscribed(static_cast(obj["playlist_id"].toDouble())); break; - case 34: // EV_PLAYLIST_UNSUBSCRIBED + case EV_PLAYLIST_UNSUBSCRIBED: emit playlistUnsubscribed(static_cast(obj["playlist_id"].toDouble())); break; case EV_USER_OK: diff --git a/src/view/maintoolbar.cpp b/src/view/maintoolbar.cpp index 8f87cd4..daf94e5 100644 --- a/src/view/maintoolbar.cpp +++ b/src/view/maintoolbar.cpp @@ -5,6 +5,7 @@ #include #include #include +#include MainToolBar::MainToolBar(QobuzBackend *backend, PlayQueue *queue, QWidget *parent) : QToolBar(parent) @@ -221,6 +222,10 @@ void MainToolBar::onProgressReleased() const quint64 dur = m_backend->duration(); if (dur > 0) { const quint64 target = dur * static_cast(m_progress->value()) / 1000; + m_seekPending = true; + m_pendingSeekTarget = target; + m_pendingSeekStartedMs = QDateTime::currentMSecsSinceEpoch(); + updateProgress(target, dur); m_backend->seek(target); } } @@ -238,6 +243,8 @@ void MainToolBar::onBackendStateChanged(const QString &state) void MainToolBar::onTrackChanged(const QJsonObject &track) { + m_seekPending = false; + m_seeking = false; setCurrentTrack(track); const qint64 trackId = static_cast(track["id"].toDouble()); @@ -258,6 +265,18 @@ void MainToolBar::onTrackChanged(const QJsonObject &track) void MainToolBar::onPositionChanged(quint64 position, quint64 duration) { + if (m_seekPending) { + const qint64 nowMs = QDateTime::currentMSecsSinceEpoch(); + const quint64 delta = (position > m_pendingSeekTarget) + ? (position - m_pendingSeekTarget) + : (m_pendingSeekTarget - position); + + if (delta > 2 && (nowMs - m_pendingSeekStartedMs) < 1500) + return; + + m_seekPending = false; + } + updateProgress(position, duration); } diff --git a/src/view/maintoolbar.hpp b/src/view/maintoolbar.hpp index 2f7b61f..2cb6d60 100644 --- a/src/view/maintoolbar.hpp +++ b/src/view/maintoolbar.hpp @@ -86,6 +86,9 @@ private: QVector m_recentTracks; bool m_playing = false; bool m_seeking = false; + bool m_seekPending = false; + quint64 m_pendingSeekTarget = 0; + qint64 m_pendingSeekStartedMs = 0; bool m_fetchingAutoplay = false; void requestAutoplaySuggestions();