Stream Opus/derived read path: serve from seekable disk FileStream, never a whole-file byte[]; HasOpusAsync is index-only
This commit is contained in:
@@ -705,9 +705,10 @@ public class TrackController : ControllerBase
|
||||
// Streams the track's audio bytes with HTTP Range support. The optional `format` selector (Phase 18.3)
|
||||
// picks the delivery rendering: absent or unrecognized ⇒ Lossless (byte-identical to pre-Phase-18 —
|
||||
// the existing zero-copy disk-stream path, untouched); `opus` ⇒ the derived Ogg Opus 320 artifact
|
||||
// when present, falling back to lossless when it is not (C2 — never 404/silence). The Opus path serves
|
||||
// the resolved in-memory bytes via File(..., enableRangeProcessing: true) so Range: bytes=X- still
|
||||
// yields 206 (load-bearing for streaming + seek), matching the lossless disk-stream's range behavior.
|
||||
// when present, falling back to lossless when it is not (C2 — never 404/silence). The Opus path streams
|
||||
// the resolved artifact from a seekable disk FileStream via File(..., enableRangeProcessing: true) —
|
||||
// no whole-file byte[] — so Range: bytes=X- still yields 206 (load-bearing for streaming + seek),
|
||||
// matching the lossless disk-stream's range behavior.
|
||||
[HttpGet("{trackId}")]
|
||||
public async Task<ActionResult> GetTrack(string trackId, [FromQuery] string? format = null)
|
||||
{
|
||||
@@ -777,13 +778,16 @@ public class TrackController : ControllerBase
|
||||
}
|
||||
|
||||
// The ?format=opus arm of GetTrack. Resolves the Opus artifact (or the lossless fallback when none
|
||||
// exists, C2) via TrackFormatResolver and serves the resolved bytes with explicit range processing.
|
||||
// enableRangeProcessing:true is the load-bearing detail the 18.2 reviewer flagged: File(byte[], ...)
|
||||
// does NOT get ASP.NET's automatic range handling unless asked, so without this flag a Range: bytes=X-
|
||||
// would silently return the whole body as 200 instead of a 206 slice — breaking seek for the Opus path
|
||||
// (and Phase 21 windowing). The resolver reports the *actually-served* format via ResolvedAudio, so the
|
||||
// content-type matches the bytes (audio/ogg on a hit, the source MIME on a fallback) and the eventual
|
||||
// client decoder dispatches correctly.
|
||||
// exists, C2) via TrackFormatResolver and streams the resolved bytes from a seekable disk FileStream —
|
||||
// never a whole-file byte[] (a ~220 MB Opus / ~970 MB lossless managed allocation per request was the
|
||||
// read-path OOM defect this closes). enableRangeProcessing:true is load-bearing: the seekable FileStream
|
||||
// lets ASP.NET honour Range: bytes=X- with a 206 slice (seek + Phase 21 windowing). The resolver reports
|
||||
// the *actually-served* format via ResolvedAudio, so the content-type matches the bytes (audio/ogg on a
|
||||
// hit, the source MIME on a fallback) and the eventual client decoder dispatches correctly.
|
||||
//
|
||||
// Disposal mirrors the lossless GetTrack path exactly: File() takes ownership of the stream on success
|
||||
// and disposes it after the response; the inner try disposes ResolvedAudio (and its FileStream) on any
|
||||
// pre-handoff throw so a handle never leaks.
|
||||
private async Task<ActionResult> GetTrackOpusAsync(string trackId)
|
||||
{
|
||||
try
|
||||
@@ -795,11 +799,27 @@ public class TrackController : ControllerBase
|
||||
return NotFound();
|
||||
}
|
||||
|
||||
string contentType;
|
||||
long streamLength;
|
||||
Stream innerStream;
|
||||
try
|
||||
{
|
||||
contentType = resolved.ContentType;
|
||||
// Length from the seekable FileStream — a metadata read, not a body load.
|
||||
streamLength = resolved.Stream.Length;
|
||||
innerStream = resolved.Stream;
|
||||
}
|
||||
catch
|
||||
{
|
||||
await resolved.DisposeAsync();
|
||||
throw;
|
||||
}
|
||||
|
||||
_logger.LogInformation(
|
||||
"Streaming track {TrackId} as {Format} ({Size} bytes, {ContentType})",
|
||||
trackId, resolved.ResolvedFormat, resolved.Audio.Buffer.Length, resolved.ContentType);
|
||||
trackId, resolved.ResolvedFormat, streamLength, contentType);
|
||||
|
||||
return File(resolved.Audio.Buffer, resolved.ContentType, enableRangeProcessing: true);
|
||||
return File(innerStream, contentType, enableRangeProcessing: true);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
Reference in New Issue
Block a user