33 KiB
Phase 18 — Opus Low-Data Streaming (dual-format lossless + Opus delivery)
Product spec. Status: design / framing — implementation-ready pending Daniel's open-question calls. Author: product-designer. Date: 2026-06-23. No code has been written by this doc.
This phase is the concrete realization of the long-deferred "Non-WAV formats" intent
(CONTEXT.md §5, the "1.2" the streaming-feature items reference). It supersedes the abstract "a
processor per format + a decoder strategy" framing with a specific, Daniel-directed product: two
delivery formats per track — the existing lossless WAV path and a new low-data Ogg Opus path — so the
listener gets a choice, with Opus the bandwidth-friendly default-candidate.
Surfaces (named precisely):
- Ingest / preprocessing:
DeepDrftContent(AudioProcessor/AudioProcessorRouter/TrackContentService/WaveformProfileService) +DeepDrftAPI(upload/persist —UnifiedTrackService.UploadAsync, replace-audio) +DeepDrftManager(CMS upload form, only if a per-upload control is wanted — see OQ4). - Delivery / decode:
DeepDrftAPI(the track stream endpoint +Rangehandler) +DeepDrftPublicproxy (TrackProxyController) +DeepDrftPublic.Clientplayer stack (StreamingAudioPlayerService,TrackMediaClient) +DeepDrftPublic/Interop/audioTS decoders (AudioPlayer.createFormatDecoderregistry, a newOpusFormatDecoder).
Sequencing headline: Phase 18 comes BEFORE Phase 21 (Windowed Streaming Buffer). Phase 21's windowing must work across both formats — its C5 invariant already anticipated this ("must not foreclose MP3/FLAC"); Opus is now the concrete VBR/containerized driver of that invariant. See §6 and the Phase 21 cross-reference.
0. State of the world (what already exists — verified 2026-06-23)
This phase is much further along than the "Non-WAV formats" backlog line implies, on both sides. Two prior efforts already built most of the multi-format substrate; what is missing is specifically the derived-Opus-artifact idea, not generic format support.
Producer side is already multi-format (router landed):
AudioProcessorRouter.ProcessAudioFileAsync(filePath)routes by extension —.wav→AudioProcessor,.mp3→Mp3AudioProcessor,.flac→FlacAudioProcessor(DeepDrftContent/CLAUDE.md).TrackContentService.AddTrackAsync(filePath, mimeType)is format-agnostic: it selects the processor, generates an entry GUID, and stores the original bytes with correct extension/MIME in thetracksvault.- So today the system can ingest and store WAV/MP3/FLAC. It does not transcode — it keeps the original. There is no derived artifact and no second format per track.
Decoder side is a wired strategy registry (not "implemented-not-wired" anymore):
AudioPlayer.createFormatDecoder(contentType)(AudioPlayer.ts:117) dispatches onContent-Type:audio/mpeg|audio/mp3→Mp3FormatDecoder,audio/flac|audio/x-flac→FlacFormatDecoder, default →WavFormatDecoder. All three decoders exist and implementIFormatDecoder.IFormatDecoder(IFormatDecoder.ts) is a clean per-format strategy:tryParseHeader,getAlignedSegmentSize,wrapSegment,calculateByteOffset, plus aFormatInfocarryingbyteRate,blockAlign,audioDataOffset, and aseekDataaccelerator slot (already polymorphic:Mp3VbrSeekData | FlacSeekData). This is the seam anOpusFormatDecoderslots into.- Correction to the Phase 21 spec's §2 C3 note ("MP3/FLAC implemented, not yet wired"): the registry is wired and dispatches on content-type today. Phase 21's invariant still holds; the parenthetical is stale and is corrected by this phase's reconciliation.
What this means for the gap. Daniel's direction is not "add format support" — that substrate exists. It is "derive a second, low-data artifact (Opus fullband 320) at ingest and let the listener pick which to stream." That is two genuinely new things: (1) a transcode-at-ingest step that produces a derived artifact per track (the router stores originals; nothing derives Opus), and (2) a per-format delivery selection so the same track can be served as either WAV or Opus on request.
1. Goal
Dual-format delivery. Every track is streamable in two formats:
- Lossless — the existing WAV path, unchanged. The archival / audiophile option.
- Low-data — a derived Ogg Opus, fullband, 320 kbps artifact. The bandwidth-friendly default-candidate.
The listener chooses; Opus is the recommended default. The bespoke Web Audio decode→schedule graph is
retained by deliberate choice (Daniel) — Opus is fed through the same IFormatDecoder strategy
seam, not through an HTML <media> element or MSE.
Why Opus fullband 320. Opus is the modern, royalty-free, best-in-class lossy codec; "fullband"
(48 kHz, full 20 kHz audio bandwidth) at 320 kbps is transparent-to-most-listeners quality at roughly
1/4 to 1/5 the bytes of 16-bit/44.1 stereo WAV (~1411 kbps). For a 1 GB DJ MIX (Phase 9 Mix
medium), that is the difference between a ~1 GB transfer and a ~220 MB transfer — the headline
low-data win, and directly relevant to the Phase 21 long-stream case.
Non-goals. This phase does not retire WAV (it stays as the lossless option), does not change the
bespoke graph for MSE (explicitly rejected — see §2 / Phase 21 OQ5), and does not add new transport
mechanisms beyond the existing stream + Range primitive.
2. Constraints / invariants (the contract that must hold)
- C1 — Keep the bespoke Web Audio graph. MSE is rejected (Daniel, deliberate). The custom
decode→schedule graph is a long-term commitment, not a stopgap. Opus is fed through the existing
IFormatDecoder→StreamDecoder→PlaybackSchedulerpipeline. (This is the same decision recorded as Phase 21 OQ5 = NO; the two phases share it.) - C2 — Preprocessing is additive; the WAV path is untouched. The Opus artifact is a second
derived artifact per track, not a replacement. The existing WAV in the
tracksvault stays byte-for-byte as it is today; the lossless stream path is unchanged. A track with no Opus artifact (legacy rows, or a transcode that hasn't run yet) must still play losslessly — Opus is strictly additive. - C3 — Reuse the landed
Range/offset seek path; do not fork it. Phase 4'sRange: bytes=X-→206primitive (clientTrackMediaClient→DeepDrftPublicproxy →DeepDrftAPI) is the substrate for Opus seek too. Opus seek math differs from WAV (VBR / container-paged, see §3.4) but it is expressed through the sameIFormatDecoder.calculateByteOffsetseam the MP3/FLAC decoders already use — no second seek mechanism. - C4 — Opus slots the
IFormatDecoderregistry; no format branches leak elsewhere. The newOpusFormatDecoderis selected byAudioPlayer.createFormatDecoderonContent-Type: audio/ogg/audio/opus. The rest of the player stack stays format-agnostic. Noif (opus)outside the decoder and the one selection point. - C5 — Format selection is a delivery-time decision, resolved server-side from a listener
signal. The same
TrackEntity/EntryKeyaddresses both artifacts; the format is a parameter on the stream request (query param orAcceptnegotiation — see §3.3), not a different track id and not a different vault entry key. One track, two renderings (the standing "one source, multiple views" preference applied to delivery). - C6 — Transcode failure must not block ingest. If the Opus transcode fails or is slow, the
track still persists with its lossless artifact and is playable. Opus is generated best-effort and
can be (re)generated later — mirror the waveform-datum model (
WaveformProfileService: compute on upload, regenerate on demand via a CMS action), which is exactly the "derived artifact, generated at ingest, regenerable" pattern this needs. - C7 — The vault model holds: derived artifact is a new entry, not a mutation. The Opus bytes
live in the FileDatabase under the track's
EntryKey— either in the existingtracksvault under a derived key, or in a new sibling vault (see §3.2 options). Either way it isAudioBinarywith the.opus/.oggextension and correct MIME, registered like any other vault resource.
3. Architectural shape
3.0 The mental model
A track has one source artifact (the uploaded WAV/MP3/FLAC, stored as-is today) and gains one
derived low-data artifact (Ogg Opus fullband 320, produced at ingest). The stream endpoint serves
either, selected per request. The player picks a decoder by the response Content-Type exactly as
it does today. Seeking uses the same Range primitive; the byte↔time math is the decoder's job.
INGEST (DeepDrftContent + DeepDrftAPI)
upload → AudioProcessorRouter (existing) → store SOURCE artifact in vault [unchanged]
→ TRANSCODE to Opus 320 → store DERIVED artifact [NEW]
→ WaveformProfileService (existing, unchanged)
DELIVERY (DeepDrftAPI → DeepDrftPublic proxy → DeepDrftPublic.Client → Interop/audio)
GET api/track/{id}?format=opus|lossless → serve the chosen artifact's bytes (+ Range) [NEW param]
player: createFormatDecoder(Content-Type) → OpusFormatDecoder | Wav | Mp3 | Flac [+1 decoder]
3.1 Where the transcode lives (relative to existing processing)
The transcode is a new processor sibling to the existing format processors, invoked after the
source is stored, in the same orchestration that already calls WaveformProfileService:
- It belongs in
DeepDrftContent(the binary-content domain library) as e.g. anOpusTranscodeService/OpusProcessor, not in a host and not in a controller (per the*.Services-owns-domain-logic convention). - It is invoked from
UnifiedTrackService.UploadAsync(the same placeWaveformProfileServicecomputes the high-res datum on every new track) and from the replace-audio path (which already regenerates both waveform datums — Opus is the third derived thing to regenerate there). - Like the waveform datum, it gets a regenerate trigger: a CMS per-track / bulk action and an
ApiKey-gated endpoint, so existing tracks can be backfilled. This mirrors the landed
"Generate All Profiles / Backfill High-res" bulk actions on
Releases.razor— Backfill Opus is the natural third bulk action.
The transcode engine itself is staff-engineer's call (FFmpeg/libopus via a process invocation, a
managed binding, or a libopus P/Invoke). The spec fixes the artifact (Ogg Opus, fullband, 320 kbps)
and the seam (a derived artifact produced post-store, regenerable, failure-tolerant), not the tool.
Note a real operational constraint to flag for implementation: transcoding a 1 GB WAV is CPU- and
time-expensive and must not block the upload response — it wants the same off-the-hot-path treatment
the upload body staging already gets (Upload:StagingPath), likely a background/queued step. This is
the single biggest implementation risk and is called out as such.
3.2 Where the Opus artifact is stored (two options)
Option S1 — derived key in the existing tracks vault (recommended). Store the Opus bytes under
a derived entry key alongside the source, e.g. {entryKey} for source and {entryKey}.opus (or a
parallel key convention) in the same tracks vault. Pro: no new vault type, co-located with the
source, simplest lookup. Con: mixes two artifacts per logical track in one vault's index.
Option S2 — a new sibling vault (e.g. track-opus). Mirror the track-waveforms precedent
(Phase 12 added a dedicated vault for the derived high-res datum). Opus bytes keyed by the same
EntryKey in a track-opus vault. Pro: clean separation of source vs. derived, matches the
established "derived artifacts get their own vault" pattern (track-waveforms), easy to enumerate /
backfill / purge independently. Con: one more vault to register.
Recommendation: S2 — it is the pattern the codebase already chose for the other derived
per-track artifact (the high-res waveform datum), so it is the least surprising and keeps the source
tracks vault meaning exactly one thing. Final call is staff-engineer's; both are viable.
3.3 How a listener's format choice reaches the bytes
The stream endpoint gains a format selector. Two candidate mechanisms:
- D-a — explicit query param
GET api/track/{id}?format=opus|lossless(recommended). Mirrors the existingoffsetquery param the proxy already forwards (TrackProxyController). Explicit, cache-friendly (distinct URLs), trivial to thread through the proxy, and the player already knows which it asked for. Server resolves the param → the right artifact → sets the rightContent-Type, which the player's existingcreateFormatDecoderthen dispatches on. No new decoder-selection mechanism — the response content-type does the work it already does. - D-b — HTTP content negotiation (
Accept: audio/oggvsaudio/wav). More "correct" REST, but the proxy + WASM client wiring is fussier and caches are content-type-varied. Not worth it here.
Recommended: D-a. The selection policy (which format a given listener gets by default, and how they switch) is a genuine product call — see OQ1/OQ2, deliberately not decided here. The mechanism (a query param resolved server-side to an artifact + content-type) is settled.
Server-side fallback rule (C2): if format=opus is requested but no Opus artifact exists for that
track (not yet transcoded / backfilled), the endpoint falls back to lossless rather than 404ing —
Opus is additive, so its absence degrades to "you get the lossless one," never to "no audio."
3.4 The Opus decoder + seek math (the genuinely new decode work)
OpusFormatDecoder implements IFormatDecoder is the new code on the delivery side. Two things make it
harder than the WAV decoder and need to be flagged:
- Containerized, paged format — not raw-frame-sliceable. WAV's
wrapSegmentprepends a 44-byte PCM header to any PCM-aligned byte run; the current model assumes you can wrap an arbitrary aligned raw-audio slice and hand it todecodeAudioData. Ogg Opus is page-structured (Ogg pages carrying Opus packets, plus mandatoryOpusHead/OpusTagssetup pages at the start). A mid-stream byte slice is not independently decodable without the setup header and without landing on Ogg page boundaries. SoOpusFormatDecoder'sgetAlignedSegmentSizemust align to Ogg page boundaries (scan for theOggScapture pattern — analogous to FLAC's frame-sync scan, for which theIFormatDecoderinterface already passesrawDatatogetAlignedSegmentSize), andwrapSegment/the continuation path must carry theOpusHeadsetup (analogous to FLAC'sstreamInfoBytesinFlacSeekData). TheIFormatDecoderabstraction already has the shape for this — a format-specificseekDataaccelerator and a setup-bytes carry — because FLAC needed the same kind of thing. A newOpusSeekDatavariant joinsMp3VbrSeekData | FlacSeekData. - VBR byte↔time mapping is approximate (the Phase 21 C5 case, concretely). Opus at "320 kbps" is
effectively VBR; there is no exact
byteRatefor offset math the way CBR WAV has. Seek-by-offset uses an approximate mapping (granule-position/Ogg-page interpolation, the Opus analogue of MP3's Xing TOC or FLAC's SEEKTABLE).calculateByteOffsetreturns a best-effort page-aligned offset; the decoder then re-syncs to the next Ogg page. This is exactly the "VBR formats: the mapping is approximate" case Phase 21's C5 invariant anticipated — Opus is the format that makes that invariant load-bearing rather than hypothetical.
Browser decode-support constraint (real, must be designed around). The bespoke graph decodes
segments via AudioContext.decodeAudioData. Ogg-Opus support in decodeAudioData is long-standing in
Chrome and Firefox but arrived in Safari only at 18.4 (macOS 15.4 / iOS 18.4, March 2025); older
Safari decodes Opus only in a CAF container, not Ogg. iOS Safari is a primary music-listening surface,
so this is not a corner case. Implications: (1) the lossless WAV path is the universal fallback for
listeners whose browser can't decode Ogg Opus — which C2's additive design already provides for free;
(2) format-default policy (OQ2) should consider capability detection — don't hand Ogg Opus to a Safari
that can't decode it. This intersects Phase 1.7 (Safari compatibility) and is flagged there too.
([Browser support: caniuse / WebKit 18.4 release notes — see Sources.])
3.5 The three candidate directions (shape-level)
Per file convention the alternatives are recorded; the recommendation follows.
Direction A — Derived Opus artifact at ingest + format param on delivery (recommended). What §3.1
–3.4 describe: transcode to Opus 320 post-store, store as a derived artifact (S2 vault), serve via a
?format= param resolved server-side to bytes + content-type, decode via a new OpusFormatDecoder in
the existing registry. Why recommended: additive (C2), reuses every existing seam (the processor
orchestration, the waveform-datum derived-artifact pattern, the Range path, the decoder registry),
and the only genuinely new code is one transcode step + one decoder. Two derived artifacts per track,
both regenerable.
Direction B — On-the-fly transcode at delivery (no stored Opus artifact). Transcode WAV→Opus per
request in the stream endpoint, streaming the Opus out as it encodes. Why not (default): moves
expensive CPU onto the hot request path (a 1 GB mix transcoded per play is untenable), breaks
Range/seek (you can't byte-offset into a stream you're encoding live), and defeats caching. It is
storage-cheaper (no second artifact on disk), so it is the fallback only if disk cost ever dominates —
but for a music site where the same tracks are played repeatedly, precompute-once wins decisively.
Rejected as the primary.
Direction C — Replace WAV ingest with Opus-only (transcode and discard the lossless source). Make Opus the stored format; drop WAV. Why not: violates Daniel's explicit "lossless streaming optional — two delivery formats, listener gets a choice." Lossless is a kept option, not a thing to transcode away. Also irreversibly lossy at ingest (you can never recover the WAV). Rejected outright; recorded only because "just store Opus" is the tempting simplification and the spec should say why not.
3.6 SOLID / road-not-taken rationale
- OCP, via the existing seams. The transcode is a new processor sibling (the router pattern is
already open for extension); the decoder is a new
IFormatDecoder(the registry is already open for extension); the artifact is a new derived vault resource (thetrack-waveformsprecedent is exactly this). Phase 18 adds three new leaf implementations and zero changes to existing format code — the strongest possible OCP signal that the seams were designed right. - SRP, preserved. Transcoding is a content-domain processor concern (
DeepDrftContent); delivery selection is a thin endpoint concern (DeepDrftAPIresolves a param to an artifact); decode is theOpusFormatDecoder's concern; byte↔time math stays inside that decoder viacalculateByteOffset. No responsibility crosses a boundary it doesn't already own. - DIP / "one source, multiple views." One
TrackEntity/EntryKeyis the single source; "lossless WAV" and "low-data Opus" are two views (renderings) of it, diverging only at the delivery/decode layer — the same discipline the dark-mode and track-browse surfaces follow. - Road not taken — a separate
TrackEntityrow (or a new track id) per format. Tempting (one row = one streamable file) but it fractures the track identity: shares, queues, play-counts (Phase 16), release membership, and waveform data all key on one track, and doubling rows to carry a format would force every one of those surfaces to dedupe. Format is a delivery attribute of one track, not a second track. Rejected — keep one identity, two artifacts.
4. Format selection — the product surface (deliberately under-specified; see OQ1/OQ2)
Daniel has not specified the selection UX. What is settled by his direction: there are two formats,
Opus is the bandwidth-friendly default-candidate, lossless is the kept option. What is open: how a
listener expresses the choice, whether it is remembered, and whether the default is global or adapts.
These are genuine product calls — see §6. The mechanism (a ?format= param the player sends; §3.3)
supports any of the policies, so the policy can be decided after the substrate lands.
5. Use cases
- UC1 — Listener streams the low-data Opus of a long mix (the headline win). A ~1 GB lossless mix transfers as ~220 MB of Opus; playback through the bespoke graph is identical in feel, far cheaper on bandwidth. (Compounds with Phase 21 windowing for the memory side.)
- UC2 — Listener prefers lossless and switches to it. The same track served as WAV via
?format=lossless; the bespoke graph decodes it exactly as today. - UC3 — Legacy / not-yet-transcoded track.
?format=opusrequested, no Opus artifact yet → server falls back to lossless (C2); the listener still hears the track. A later Backfill-Opus pass produces the artifact. - UC4 — Admin backfills Opus for the existing catalogue. A bulk "Backfill Opus" CMS action (the third sibling to the existing Generate-Profiles / Backfill-High-res actions) transcodes every track lacking an Opus artifact.
- UC5 — Replace-audio regenerates Opus. The existing replace-audio path (which already regenerates both waveform datums and re-derives duration) also regenerates the Opus artifact from the new source.
- UC6 — Seek within an Opus stream. Backward/forward seek resolves via the existing
Rangepath; the offset is theOpusFormatDecoder's approximate page-aligned mapping (§3.4), re-syncing to the next Ogg page — the VBR analogue of the WAV exact-offset seek. - UC7 — Safari that can't decode Ogg Opus. Capability-gated to the lossless path (§3.4), so the listener still plays audio. (Ties to OQ2 + Phase 1.7.)
6. Open questions for Daniel (genuine product decisions, not implementation detail)
- OQ1 — Selection UX: how does a listener choose lossless vs. low-data? Candidates: a global
toggle in the player bar / settings ("Stream quality: Low-data / Lossless"); a per-track control; an
automatic default with a manual override. Recommend a single global quality toggle (player bar
or a settings affordance) — it is the Spotify/Bandcamp/SoundCloud idiom (one account/session-level
"streaming quality" setting), low-friction, and matches a small-sharp-tool posture better than
per-track choosers.
[Daniel decision] - OQ2 — Default policy: what does a listener get before they choose? Opus is the
default-candidate per Daniel — confirm Opus-by-default. Sub-questions: should the default be
capability-aware (don't serve Ogg Opus to a browser that can't decode it — §3.4 Safari < 18.4)?
Should it be network-aware (Opus on cellular, lossless on wifi)? Recommend Opus by default,
capability-gated (fall back to lossless when the browser can't decode Ogg Opus), and defer
network-awareness as gold-plating for v1.
[Daniel decision] - OQ3 — Is the choice remembered, and at what scope? Per-session (resets each visit) vs.
persisted (cookie/
localStorage, like thedarkModecookie) vs. (future) per-account once identity exists. Recommend persisted via a cookie/localStoragesetting, mirroring the dark-mode precedent — one truth, seeded at prerender, carried to WASM.[Daniel decision] - OQ4 — Per-upload Opus control in the CMS, or always-on? Should the CMS upload form let an admin
opt a track out of Opus generation (e.g. a track meant to be lossless-only), or is Opus always
generated for every track? Recommend always-on (simpler; Opus is additive and cheap to serve;
the listener's format choice already covers "I want lossless"). A per-track opt-out is a later
refinement if a real need appears.
[Daniel decision] - OQ5 — Opus container/extension specifics. Ogg Opus (
.opus/audio/ogg) is the assumption (broadestdecodeAudioDatasupport; Daniel said "Ogg Opus"). Confirm — vs. CAF-wrapped Opus (older Safari) or WebM-Opus. Recommend Ogg Opus as Daniel directed; CAF-fallback for old Safari is not worth it given the lossless fallback already covers those browsers (§3.4).[Daniel steer — confirms §3.4, not a blocker] - OQ6 — Transcode execution model (flag, leans implementation). Synchronous-at-upload is a
non-starter for 1 GB mixes (§3.1); the realistic options are a background/queued transcode after the
source is stored. This is largely staff-engineer's call, but it has a product-visible
consequence: a freshly uploaded track may be lossless-only for a short window until its Opus
artifact finishes. Confirm that "Opus appears shortly after upload, lossless available immediately"
is acceptable (it is the waveform-datum model already in place).
[Daniel steer]
7. Acceptance criteria
- AC1 (headline) — Dual-format delivery works. A track can be streamed as either lossless WAV or
Ogg Opus 320 from the same
EntryKey, selected per request; both play correctly through the bespoke Web Audio graph. - AC2 — Opus is the low-data win. The Opus artifact of a representative track is materially smaller than its lossless source (target ~1/4–1/5 the bytes); a long mix's Opus transfer is correspondingly smaller.
- AC3 — Additive, non-breaking (C2). The existing lossless WAV path is byte-for-byte unchanged; a
track with no Opus artifact still plays losslessly;
?format=opuson such a track falls back to lossless (no 404, no silence). - AC4 — Transcode at ingest, regenerable (C6). A new upload produces an Opus artifact best-effort after the source is stored; a transcode failure does not block the upload or break playback; a Backfill-Opus action (re)generates artifacts for existing tracks; replace-audio regenerates the Opus artifact from the new source.
- AC5 — Opus seek via the existing
Rangepath (C3). Forward and backward seek in an Opus stream resolve through the landedRange: bytes=X-primitive, with the offset coming fromOpusFormatDecoder.calculateByteOffset; no new seek mechanism is introduced. - AC6 — No format branches leak (C4). The only Opus-specific code is
OpusFormatDecoder, itsOpusSeekData, the onecreateFormatDecoderselection arm, and the transcode processor + delivery param resolution. The format-agnostic player/scheduler code is unchanged. - AC7 — Capability-safe default (OQ2). A browser that cannot decode Ogg Opus is served (or falls back to) the lossless path and plays audio; no listener gets silence because of codec support.
- AC8 — Windowing-ready (the Phase 21 handshake). The
OpusFormatDecoder's approximate byte↔time mapping is the one Phase 21's windowed refill will call; Opus playback must be windowable by the same machinery (verified jointly when Phase 21 lands on top — see §8 / Phase 21 cross-ref).
8. Wave decomposition
Dependency shape: 18.1 → 18.2 → {18.3, 18.4}, with 18.5 validating end-to-end. 18.1 (the
transcode/derived-artifact ingest) is the cold-start prerequisite — until an Opus artifact exists,
nothing downstream has bytes to serve or decode. 18.3 (delivery param) and 18.4 (the decoder) are
largely parallel once 18.2 (storage/lookup) settles, but both need an artifact to test against.
- 18.1 — Ingest transcode: derive + store the Opus artifact (cold-start; load-bearing). New
OpusTranscodeService/processor inDeepDrftContent, invoked post-store fromUnifiedTrackService.UploadAsyncalongsideWaveformProfileService; produces Ogg Opus fullband 320; stores it as a derived artifact (S2 vault recommended). Failure-tolerant (C6) and off the hot path (background/queued — OQ6). Independent of the delivery/decoder waves; can begin immediately. - 18.2 — Storage + lookup contract. The derived-artifact key/vault convention and the server-side
resolution "given
EntryKey+ format, return the rightAudioBinary+ content-type," including the C2 fallback (no Opus → lossless). Depends on 18.1 (an artifact must exist to resolve to). - 18.3 — Delivery: format param + proxy threading.
?format=opus|losslesson theDeepDrftAPItrack stream endpoint (resolves via 18.2), forwarded through theDeepDrftPublicTrackProxyController(mirror the existingoffsetparam threading), and theRangehandler serving the chosen artifact's bytes. The player sends the param viaTrackMediaClient. Depends on 18.2. Parallel-ok with 18.4. - 18.4 —
OpusFormatDecoderin the player stack. NewIFormatDecoderimplementation (Ogg-page-alignedgetAlignedSegmentSizeviaOggSscan,OpusHeadsetup carry inwrapSegment/continuation, approximate page-interpolationcalculateByteOffsetwith anOpusSeekDataaccelerator); one new arm inAudioPlayer.createFormatDecoderonaudio/ogg/audio/opus. Capability detection for the lossless fallback (§3.4, OQ2). Depends on 18.2 (needs Opus bytes to decode). Parallel-ok with 18.3; they meet at 18.5. - 18.5 — Backfill + selection UX + end-to-end validation. The Backfill-Opus CMS bulk action (third sibling to Generate-Profiles / Backfill-High-res) and replace-audio Opus regeneration; the listener selection control per OQ1/OQ3 (global persisted quality toggle, recommended); and the AC1–AC8 acceptance pass — including AC8's confirmation that Opus is windowable so Phase 21 can build on it. Depends on 18.1–18.4. (Selection UX can be split out if Daniel wants the substrate proven before the control lands — flag at planning time.)
9. Cross-references (read before implementing)
CONTEXT.md §5"Non-WAV formats" — the deferred intent this phase realizes (now concrete: derived Opus low-data path, not generic format support).PLAN.mdPhase 21 /product-notes/phase-21-windowed-streaming-buffer.md— sequenced AFTER this phase. Phase 21's C5 invariant ("WAV-only shipping target; must not foreclose MP3/FLAC") is now driven by Opus's VBR/paged seek math; Phase 21 OQ5 (adopt MSE) is resolved NO — the bespoke graph stays (the same C1 decision recorded here). Windowing a VBR/Opus stream usesOpusFormatDecoder.calculateByteOffset's approximate mapping — exactly the C5 case.PLAN.mdPhase 4 (landed) /COMPLETED.md— the HTTPRange: bytes=X-primitive Opus seek reuses.PLAN.mdPhase 1.5 (gapless) / 1.6 (track-skip on error) / 1.7 (Safari) — 1.5's "encoder padding/priming" caveat applies to Opus (it has pre-skip samples inOpusHead); 1.6's byte-scan-to-next-frame is the Ogg-page-sync analogue; 1.7's Safari floor intersects §3.4's Ogg-OpusdecodeAudioDatasupport (Safari < 18.4).PLAN.mdPhase 12 /product-notes/phase-12-waveform-visualizer-generalization.md— theWaveformProfileServicederived-artifact-at-ingest + regenerate pattern this transcode mirrors (compute on upload, regenerate via CMS action / endpoint, its owntrack-waveformsvault → the S2 precedent).PLAN.mdPhase 9 — defines theMixmedium (single long track), the canonical low-data case.PLAN.mdPhase 16 — play/share telemetry keys on one track identity; the §3.6 road-not-taken (one-row-per-format) would have fractured this — kept to one identity, two artifacts.DeepDrftContent/Processors/AudioProcessor.cs+AudioProcessorRouter+DeepDrftContent/CLAUDE.md— the existing format-router and theWaveformProfileServicederived-artifact seam; 18.1 lives here.DeepDrftPublic/Interop/audio/IFormatDecoder.ts— the strategy interfaceOpusFormatDecoderimplements;FlacFormatDecoder.tsis the nearest prior art (setup-bytes carry + frame-sync scan).DeepDrftPublic/Interop/audio/AudioPlayer.ts(createFormatDecoder, lines 117–125) — the decoder registry gaining the Opus arm.DeepDrftPublic.Client/Clients/TrackMediaClient.cs+DeepDrftPublic/Controllers/TrackProxyController.cs— the media fetch + proxy that thread the new?format=param (mirroringoffset).
Sources
- Ogg Opus support in
decodeAudioData: Chrome/Firefox long-standing; Safari added Ogg-Opus at 18.4 (macOS 15.4 / iOS 18.4, March 2025) — prior Safari decoded Opus only in CAF. https://chromestatus.com/feature/5649634416394240 ; https://www.testmuai.com/learning-hub/opus-audio-codec-browser-support/