feat(playback): stream buffering UI, M4A moov-at-end streaming, hot-cache spill (#737)

* feat(playback): stream buffering UI, ranged M4A tail prefetch, demuxer fix

Defer seekbar/progress until HTTP stream is armed for both legacy and
RangedHttpSource; show buffering overlay on cover art. Add MP4 tail
prefetch and Symphonia isomp4 bounded-mdat/moov-at-EOF probing so
moov-at-end M4A can start without reading the full mdat.

* feat(hot-cache): spill large ranged streams to disk for promote

When a ranged HTTP download completes above the 64 MiB RAM promote cap,
write the existing buffer once to app-data stream-spill/ and register it
for hot-cache promote (rename) and replay via fetch_data. Analysis seeds
from the spill file up to the local-file cap (512 MiB).

* fix(ui): stream buffering — grayscale cover and static clock icon

Desaturate player and queue cover art while isPlaybackBuffering; keep a
non-animated clock overlay for visibility without the spinning animation.

* fix(playback): review follow-up — tests, i18n, spill cleanup, changelog

Clippy and test layout fixes; stream spill orphan cleanup on startup;
buffering flag guard in progress handler; bufferingStream in all player
locales; CHANGELOG and contributor credits for stream/M4A work.

* docs: attribute stream buffering and M4A streaming to PR #737

* test(audio): avoid create_engine in stream spill unit test

CI runners have no audio output device; test spill take/consume via
the Mutex slot only, matching install_stream_completed_spill tests.
This commit is contained in:
cucadmuh
2026-05-16 22:56:47 +03:00
committed by GitHub
parent 1ac354fb67
commit 6ea0acede5
45 changed files with 1280 additions and 105 deletions
+1
View File
@@ -19,6 +19,7 @@
@import './modal.css';
@import './playback-delay-sleep-delayed-start-modal.css';
@import './lucky-mix-pips-shared-by-the-inline-queue-lucky-cube-indicator.css';
@import './playback-buffering-overlay.css';
@import './playlist-edit-modal.css';
@import './artists-page.css';
@import './years-page.css';
@@ -0,0 +1,32 @@
/* Cover art while `isPlaybackBuffering` (player bar + queue current track). */
.player-album-art-wrap.playback-buffering .player-album-art,
.player-album-art-wrap.playback-buffering .player-album-art-placeholder {
filter: grayscale(1);
transition: filter 0.2s ease;
}
.queue-current-cover.playback-buffering img,
.queue-current-cover.playback-buffering .fallback {
filter: grayscale(1);
transition: filter 0.2s ease;
}
.playback-buffering-overlay {
position: absolute;
inset: 0;
z-index: 3;
display: flex;
align-items: center;
justify-content: center;
border-radius: inherit;
pointer-events: none;
color: #fff;
}
.playback-buffering-overlay__icon {
filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.45));
}
.player-album-art-wrap .playback-buffering-overlay {
border-radius: var(--radius-md);
}