feat: pipeline DAV2 startup with bounded read-ahead#104
Conversation
✅ Deploy Preview for muzinga ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
|
@claude please review this |
|
Claude finished @knzeng-e's task in 3m 52s —— View job Review complete
Reviewed Findings1. (Minor / test-coverage gap) No unit test exercises the fail-closed vs. fallback branch in
Suggested smallest fix: a focused test around 2. (Nit)
What looks solid
Nothing here blocks merge in my read; #1 is worth a follow-up test before this becomes the default streaming path, and #2 is a one-line doc clarification. |
Why this PR exists
DAV2 already avoids downloading and decrypting a whole protected track before playback, but its MSE pump was still serial:
That leaves the network and Web Crypto idle while
SourceBufferappends, and it makes later playback more exposed to public-gateway latency. This PR implements the next production slice of the Project 5 streaming gate without changing who may receive a content key.Backlog / Project 5: Refs #88
What changes
SourceBufferappends strictly ordered even when fetch/decrypt work finishes out of order.CryptoKeyonce per playback instead of once per chunk.Content-Rangevalues before decryption, so a truncated gateway response can retry elsewhere instead of looking like ciphertext tampering.appendBuffer()failure listeners.The steady-state shape is:
There are never more than three chunks in the preparation window, about 1.5 MiB with today’s 512 KiB container default.
Technical context for reviewers
DAV2 encryption chunks and MSE media segments solve different problems. DAV2 chunks are independent AES-256-GCM authentication units. MSE sees the decrypted appends as one logical byte stream and retains incomplete parser input between calls, so an encryption boundary may split a media frame. The reconstructed bytes must still conform to the MSE byte-stream format for their MIME type.
That is why this PR keeps both checks:
MediaSource.isTypeSupported(mediaMime)decides whether streaming is worth attempting.Useful references:
replaceTrack()and negotiation modelProduct and security invariants
This preserves Dotify’s north star: reduce dead air in the shared listening moment while keeping artist sovereignty and infrastructure complexity below the surface.
Reviewer map
web/src/features/catalog/audioV2Pipeline.ts: concurrency bound, cancellation, and ordered append invariant.web/src/hooks/useCatalog.ts: DAV2/MSE integration, first-chunk metrics, and authentication error boundary.web/src/features/catalog/audioV2Gateway.ts: HTTP range response validation and gateway retry classification.web/src/features/catalog/audioV2Recovery.ts: the security-relevant fail-closed versus recoverable fallback decision used byuseCatalog.web/src/shared/utils/audioV2.ts: one imported AES key per playback/container.Content-Rangemismatch.docs/design/dotify-v2-access-and-streaming.md: standards review, room-stream coherence, and remaining [Streaming][P0] Pipeline DAV2 startup for sub-2-second first sound #88/[Rooms][P1] Productionize WebRTC with TURN, quality metrics, and an SFU path #89 decisions.Validation
cd web && npm run fmtcd web && npm run fmt:checkcd web && npm run test:unit- 170 passingcd web && npm run lint- passes with 3 pre-existing hook dependency warningscd web && npm run buildcd web && npm run test:e2e -- e2e/room-join.spec.ts- 6 passingnode scripts/backlog-sync.mjs --check --offline- passes with the existing ticket-24 mapping and duplicate ticket-08 warningsgit diff --checkWhat this PR does not prove
The #88 latency budgets are not claimed yet. Release evidence still needs real DAV2 assets and public gateways across Chrome, Firefox, Safari, iOS, and Android, followed by the backend read-through decision. Worker decryption, first-chunk sizing, and telemetry export also remain in #88. TURN, ICE restart/quality telemetry, and SFU scaling remain room-reliability work in #89.