diff --git a/COMPLETED.md b/COMPLETED.md index 620546c..08e39a6 100644 --- a/COMPLETED.md +++ b/COMPLETED.md @@ -6,6 +6,46 @@ Newest entries at the top. Group by phase/wave header (mirroring `PLAN.md` / `CM --- +## WaveformSeeker Wave 2 — DOM seekbar + Interop module + +**Status:** W2 (WaveformSeeker component) landed on 2026-06-05 (branch `waveform-w2-seeker`, pending merge to dev). + +### W2 — WaveformSeeker component (seekbar replacement) + +**Landed 2026-06-05.** + +Implemented the interactive WaveformSeeker component: a bar-chart-styled seekbar replacing `MudSlider` in `PlayerSeekZone`, with DOM-rendered progress split via CSS and lazy-loaded pointer-capture drag interop. + +**Component changes (`DeepDrftPublic.Client/Controls/AudioPlayerBar`):** +- `WaveformSeeker.razor` (+ `.cs`, `.css`) — new component consuming `WaveformProfile double[]?` and `Duration`, rendering bars as DOM elements with clip-overlay progress. Single CSS variable (`--seek-position`) changes per seek gesture; no per-bar re-render. +- Pointer-capture drag wired via `waveformSeeker.js` (ES module, lazy-loaded). Calculates seek target from click/drag position and invokes `OnSeekRequested` callback (delegates to `IPlayerService.SeekAsync`). +- Flat floor-height fallback when profile is unavailable — seek gesture always works, with or without loudness data. +- `PlayerSeekZone.razor` — now hosts `WaveformSeeker` in place of the removed `MudSlider` placeholder. + +**Interop changes (`DeepDrftPublic/Interop/audio/`):** +- New `waveformSeeker.ts` module (separate from the TS audio bundle) — `PointerCaptureHandler` class managing `pointerdown` / `pointermove` / `pointerup` lifecycle. Compiled to `waveformSeeker.js` in `wwwroot/js/audio/`. +- Module loaded on first use (not bundled with audio stack) to defer its parse cost until the player is expanded and the seekbar is visible. + +**`.gitignore` scoping:** +- Added scoped negation to track hand-authored `waveformSeeker.js` alongside existing TS-output ignore rule — allows the compiled JS to be committed for fast startup without committing intermediate TS compiler outputs. + +**Service changes (`IPlayerService` / `AudioPlayerService` / `StreamingAudioPlayerService`):** +- New `WaveformProfile double[]?` property added to service interface and implementations. +- Fetched fire-and-forget on track load via `GetWaveformProfileAsync(trackId, cancellationToken)` — existing HTTP call from W1-T2. +- Cancellable via the track-reset flow (same cancellation token that stops spectrum animation). +- Cleared on reset with all other track state. + +**Testing:** +- Manual verification: seekbar renders flat when profile unavailable; dragable when profile present; CSS clip-overlay tracks seek position correctly. + +**Architecture notes:** +- WaveformSeeker does not re-fetch the profile — it consumes the same `IPlayerService.WaveformProfile` fetched during track load. No additional HTTP round-trip per seek gesture. +- Interop module (`waveformSeeker.js`) is independent of the audio playback stack — can be updated or replaced without touching audio scheduling logic. +- Pointer-capture semantics ensure seek is responsive even when the browser's event queue is saturated by animation frames. +- Flat fallback ensures seek gestures always work, even on tracks with no profile data (uploaded before W1, or on profile-generation failure). + +--- + ## WaveformSeeker Wave 1 — Loudness profile + layout refactor **Status:** W1-T1 (backend loudness computation), W1-T2 (HTTP transport), and W1-T3 (player layout refactor) landed on 2026-06-05.