From 79a015f60ad21dada9180fb507c3bea30452c67c Mon Sep 17 00:00:00 2001 From: daniel-c-harvey Date: Tue, 9 Jun 2026 07:41:38 -0400 Subject: [PATCH] docs: update CLAUDE.md files to reflect Range header seek, remove WavOffsetService references --- CLAUDE.md | 4 ++-- DeepDrftAPI/CLAUDE.md | 17 ++++++++--------- DeepDrftContent/CLAUDE.md | 18 +----------------- DeepDrftPublic.Client/CLAUDE.md | 6 +++--- DeepDrftTests/CLAUDE.md | 1 - 5 files changed, 14 insertions(+), 32 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index 0827fcf..48ab91e 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -14,7 +14,7 @@ DeepDrftHome is a **net10.0** solution consisting of ten projects implementing a - **DeepDrftShared.Client**: Razor Class Library. Shared Blazor components consumed by both `DeepDrftPublic` and `DeepDrftManager` for consistency across public and admin surfaces. - **DeepDrftData**: Class library. EF Core domain logic: `DeepDrftContext`, `TrackConfiguration`, `Migrations`, `TrackRepository`, `TrackService`, `TrackManager`. Consumed by `DeepDrftAPI` and tests. - **DeepDrftAPI**: ASP.NET Core host. Dual-database authority (SQL metadata + FileDatabase binary). AuthBlocks API host (owns registration, migration/seed, JWT endpoints). Seven track endpoints: `GET api/track/{id}` unauthenticated streaming; `PUT api/track/{id}` vault write (ApiKey); `POST api/track/upload` upload + SQL persist (ApiKey); `DELETE api/track/{id:long}` SQL delete + vault remove (ApiKey); `GET api/track/page` paged metadata list (unauthenticated); `GET api/track/meta/{id:long}` single metadata (ApiKey); `PUT api/track/meta/{id:long}` metadata update (ApiKey). -- **DeepDrftContent**: Class library. The FileDatabase implementation in full (Models, Services, Utils, Abstractions, Constants), `WavOffsetService`, `AudioProcessor`, content-side `TrackService`. Consumed by hosts and tests. +- **DeepDrftContent**: Class library. The FileDatabase implementation in full (Models, Services, Utils, Abstractions, Constants), `AudioProcessor`, content-side `TrackService`. Consumed by hosts and tests. - **DeepDrftModels**: Shared contracts. `TrackEntity`, `TrackDto`, `PagingParameters`, `PagedResult`. Every project references this. - **DeepDrftTests**: NUnit test suite. Comprehensive FileDatabase tests (vault creation, media storage, indexing, factory patterns, utilities). Integration-focused with temp-directory test isolation. @@ -70,7 +70,7 @@ The player is not fetch-then-play: 2. `StreamingAudioPlayerService` reads in adaptive 16–64 KB chunks, pushes each via `AudioInteropService.processStreamingChunk`. 3. TypeScript `StreamDecoder` parses WAV header, decodes chunks to `AudioBuffer`s. `PlaybackScheduler` schedules them on a Web Audio graph. 4. Playback starts as soon as a min buffer is queued; UI duration from parsed header (not waiting for full file). -5. **Seek beyond buffer**: if seek target is past what's decoded, client issues `GET api/track/{id}?offset={byteOffset}`. Server's `WavOffsetService` block-aligns offset, synthesises a fresh 44-byte WAV header, streams `[new header][data from offset]`. Player tears down and re-initialises decoder for the new stream. +5. **Seek beyond buffer**: if seek target is past what's decoded, client issues `GET api/track/{id}` with `Range: bytes={byteOffset}-`. Server streams raw bytes from that file-absolute offset with a `206 Partial Content` response. Player retains the parsed WAV header and feeds the raw PCM continuation into the existing decode pipeline. Keep this seam clean — it is the most architecturally load-bearing part of the playback path. diff --git a/DeepDrftAPI/CLAUDE.md b/DeepDrftAPI/CLAUDE.md index 8fb1e9a..6d93bc6 100644 --- a/DeepDrftAPI/CLAUDE.md +++ b/DeepDrftAPI/CLAUDE.md @@ -22,21 +22,20 @@ Dual-database authority for tracks (SQL metadata + FileDatabase binary) and imag ## What does NOT live here anymore -- `FileDatabase/`, `Processors/`, media models (`AudioBinary`, `ImageBinary`, etc.), `WavOffsetService` — all in `DeepDrftContent` (class library). +- `FileDatabase/`, `Processors/`, media models (`AudioBinary`, `ImageBinary`, etc.) — all in `DeepDrftContent` (class library). - EF Core context and repository — in `DeepDrftData`. - **Hosts only own HTTP surface and wiring.** New domain code goes in `*.Services` (shared libraries) or host-internal `Services/` folders (e.g., `UnifiedTrackService` here for dual-database orchestration). ## The endpoint surface (seven endpoints) -### GET api/track/{trackId}?offset=0 (unauthenticated) +### GET api/track/{trackId} (unauthenticated) -Returns the WAV bytes from the `tracks` vault with optional offset support. +Returns the WAV bytes from the `tracks` vault with HTTP Range support. - **Route parameter `trackId`** (string): the entry id inside the `tracks` vault (i.e. `TrackEntity.EntryKey`). -- **Query parameter `offset`** (optional, default 0): byte position to start streaming from. -- If `offset == 0`: streams the entire file directly from disk without buffering (so 100 MB WAVs do not force 100 MB LOH allocations per request). -- If `offset > 0`: `WavOffsetService.CreateOffsetStream` block-aligns the offset and synthesises a fresh 44-byte WAV header so the response is a valid standalone WAV starting from that byte position. This is load-bearing for seek-beyond-buffer — the player asks for a new stream at the offset it wants to seek to, gets back a valid WAV that starts there, and tears down/re-initialises the decoder. -- Returns 404 if track not found. Returns 500 if vault operations fail (with error swallowing — the vault returns `null`). +- **Range header** (optional): HTTP Range header for byte-range requests (e.g., `Range: bytes=1000-`). Server responds with `206 Partial Content` and streams from the requested offset. +- Streams the file directly from disk with `enableRangeProcessing: true`, supporting both full-file and partial-range requests without synthesizing WAV headers or buffering. +- Returns 200 for full-file requests, 206 for Range requests, 404 if track not found, 500 if vault operations fail (with error swallowing — the vault returns `null`). ### PUT api/track/{trackId} ([ApiKeyAuthorize]) @@ -161,7 +160,7 @@ Configured in `Startup.ConfigureDomainServices()`, applied to all endpoints via 3. Register `FileDatabase` as singleton. 4. Ensure the `tracks` vault exists (type `MediaVaultType.Audio`, created on first boot if missing). 5. Ensure the `images` vault exists (type `MediaVaultType.Image`, created on first boot if missing) via `InitializeImageVault`. -6. Register singletons: `WavOffsetService`, `AudioProcessor`, `ImageProcessor`, `TrackService` (the `DeepDrftContent` version for vault operations). +6. Register singletons: `AudioProcessor`, `ImageProcessor`, `TrackService` (the `DeepDrftContent` version for vault operations). **In `Program.cs`** (SQL + AuthBlocks + wiring): @@ -252,7 +251,7 @@ dotnet build DeepDrftAPI curl -H "ApiKey: your-secret-key" -X GET https://localhost:5002/api/track/page \ -H "Accept: application/json" -curl https://localhost:5002/api/track/test-entry-key?offset=0 +curl https://localhost:5002/api/track/test-entry-key # Test auth endpoints (AuthBlocks API) curl -X POST https://localhost:5002/api/auth/login \ diff --git a/DeepDrftContent/CLAUDE.md b/DeepDrftContent/CLAUDE.md index ade3d96..80b3604 100644 --- a/DeepDrftContent/CLAUDE.md +++ b/DeepDrftContent/CLAUDE.md @@ -6,7 +6,7 @@ See the root `CLAUDE.md` for full architecture overview. This file covers what i ## One-line purpose -Binary-content domain logic. The FileDatabase implementation in full (Models, Services, Utils, Abstractions, Constants), WAV stream-with-offset, audio processing, and the content-side track service. Consumed by `DeepDrftContent` (the host) and `DeepDrftCli` (the admin CLI). +Binary-content domain logic. The FileDatabase implementation in full (Models, Services, Utils, Abstractions, Constants), audio processing, and the content-side track service. Consumed by `DeepDrftContent` (the host) and `DeepDrftCli` (the admin CLI). ## Layout @@ -17,8 +17,6 @@ DeepDrftContent.Services/ │ ├── Models/ # Data models, DTOs, enums │ ├── Services/ # FileDatabase, MediaVault, IndexSystem, IndexWatcher │ └── Utils/ # StructuralMap, StructuralSet, FileUtils -├── Audio/ -│ └── WavOffsetService.cs # Byte offset → valid WAV stream ├── Processors/ │ └── AudioProcessor.cs # WAV file parsing, metadata extraction ├── Constants/ @@ -76,19 +74,6 @@ public async Task RegisterResourceAsync(string vaultId, string entryId, Fi **Callers must check return values.** Do not change this without a deliberate design pass — it's embedded in all FileDatabase tests and client code. -## WAV offset service - -`WavOffsetService.CreateOffsetStream(buffer, byteOffset)`: - -1. Parses the WAV header from the buffer. -2. Block-aligns the byte offset to the nearest block boundary (required for clean audio — misalignment causes clicks). -3. Synthesises a new 44-byte WAV header sized for the remaining data (from offset to EOF). -4. Returns a `MemoryStream` containing `[new header][data from offset]`. - -Used by the content API to serve seek-beyond-buffer requests. The player asks for a new stream at the byte offset it wants to seek to; the server returns a valid WAV that starts there. - -**Block alignment is critical.** Do not bypass it. The WAV fmt chunk tells you the block size; use it. - ## Audio processor `AudioProcessor.ProcessWavFileAsync(filePath)`: @@ -141,7 +126,6 @@ Safety call to ensure the `tracks` vault exists (creates if missing). Called on In `DeepDrftContent/Startup.ConfigureDomainServices()` and `DeepDrftCli/Program.cs`: ```csharp -services.AddSingleton(); services.AddSingleton(/* from FileDatabase.FromAsync */); services.AddScoped(); services.AddScoped(); // DeepDrftContent.Services.TrackService diff --git a/DeepDrftPublic.Client/CLAUDE.md b/DeepDrftPublic.Client/CLAUDE.md index 7c53dc0..4a4fc61 100644 --- a/DeepDrftPublic.Client/CLAUDE.md +++ b/DeepDrftPublic.Client/CLAUDE.md @@ -33,7 +33,7 @@ All interactive UI for the site. Blazor WebAssembly. Pages, controls, the stream - Dark-mode services: `DarkModeServiceBase` (cookie name constant), `DarkModeCookieService` (JS cookie read/write). - `Clients/`: HTTP API clients (both target DeepDrftAPI). - `TrackClient`: SQL metadata API. Uses named `IHttpClientFactory` client `"DeepDrft.API"`. Sends `page` param (not `pageNumber`). Deserializes response as bare `PagedResult` (not wrapped in ApiResultDto envelope). - - `TrackMediaClient`: Content API. Uses named `IHttpClientFactory` client `"DeepDrft.Content"`. Methods like `GetAudioStreamAsync(trackId, offset)` → `Stream`. + - `TrackMediaClient`: Content API. Uses named `IHttpClientFactory` client `"DeepDrft.Content"`. Methods like `GetAudioStreamAsync(trackId, byteOffset?)` → `Stream` with optional Range header support for seek-beyond-buffer. - `ViewModels/`: Component state. - `TracksViewModel`: Scoped. Holds current page, page size, sort column, descending flag. `SetPage(pageNumber)` calls `TrackClient.GetPageAsync` and updates. Registered in `Startup.ConfigureDomainServices`. - `TrackDetailViewModel`: Scoped. Holds loaded track, loading flag, not-found flag. `Load(entryKey)` fetches via `ITrackDataService` and resets all flags per call (prevents cross-navigation bleed). Registered in `Startup.ConfigureDomainServices`. @@ -61,12 +61,12 @@ Both are configured with JSON serializer settings (case-insensitive property mat ### Implementation - `AudioPlayerService` (abstract base): Lifecycle. Stores current track, playback state, volume. `SelectTrack` throws `NotSupportedException` (buffered path is dead); derived classes override `SelectTrackStreaming`. - `StreamingAudioPlayerService` (production): Constructor takes `TrackMediaClient`, `AudioInteropService`, logger. `SelectTrackStreaming`: - 1. Calls `TrackMediaClient.GetAudioStreamAsync(trackId, offset: 0)`. + 1. Calls `TrackMediaClient.GetAudioStreamAsync(trackId)`. 2. `StreamingAudioPlayerService.StreamAudioAsync` reads chunks (16–64 KB adaptive), pushes each via `AudioInteropService.ProcessStreamingChunkAsync` (JS interop call). 3. TypeScript `StreamDecoder` parses WAV header (first chunk), decodes subsequent chunks to `AudioBuffer`s. 4. `PlaybackScheduler` schedules buffers on Web Audio `AudioContext`. 5. Playback starts as soon as a configurable min buffer count is queued. - 6. **Seek beyond buffer**: if seek target is past the decoded range, `Seek(position)` calls `TrackMediaClient.GetAudioStreamAsync(trackId, offset: byteOffset)`. Server's `WavOffsetService` synthesises a new 44-byte WAV header and streams from the offset. Player tears down and re-initialises decoder for the new stream. + 6. **Seek beyond buffer**: if seek target is past the decoded range, `Seek(position)` calls `TrackMediaClient.GetAudioStreamAsync(trackId, byteOffset)` with a file-absolute byte offset. Client sends `Range: bytes={offset}-`; server responds 206 with raw PCM; decoder retains the parsed WAV header and feeds the continuation directly into the decode pipeline. ### Interop bridge - `AudioInteropService.CreatePlayerAsync` polls `DeepDrftAudio.isReady()` before proceeding; `index.ts` sets `ready = true` after attaching the API to `window`. This guards against slow WASM boot / cache misses. diff --git a/DeepDrftTests/CLAUDE.md b/DeepDrftTests/CLAUDE.md index d0719d4..273fafb 100644 --- a/DeepDrftTests/CLAUDE.md +++ b/DeepDrftTests/CLAUDE.md @@ -233,7 +233,6 @@ Be honest about coverage gaps: - `TrackClient` / `TrackMediaClient` (HTTP clients). - The audio player services (streaming, seek, interop). - Dark-mode round-trip (cookie → settings → persistent state). -- `WavOffsetService` (byte offset → new WAV stream). - `AudioProcessor` (WAV parsing, metadata extraction). Any planned work in those areas should consider whether tests need to land alongside. **Testing the FileDatabase thoroughly does not mean testing everything** — it means testing the part that is most likely to break.