docs: update CLAUDE.md files to reflect Range header seek, remove WavOffsetService references
This commit is contained in:
@@ -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<T>`, `PagedResult<T>`. 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.
|
||||
|
||||
|
||||
@@ -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 \
|
||||
|
||||
@@ -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<bool> 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<WavOffsetService>();
|
||||
services.AddSingleton<FileDatabase>(/* from FileDatabase.FromAsync */);
|
||||
services.AddScoped<AudioProcessor>();
|
||||
services.AddScoped<TrackService>(); // DeepDrftContent.Services.TrackService
|
||||
|
||||
@@ -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<TrackDto>` (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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
Reference in New Issue
Block a user