Stream the waveform compute so large uploads no longer buffer the whole file (Wave 2 OOM)

This commit is contained in:
daniel-c-harvey
2026-06-25 21:49:11 -04:00
parent aa0b64329f
commit 9347f11ff0
10 changed files with 594 additions and 120 deletions
+23 -9
View File
@@ -756,15 +756,18 @@ public class TrackController : ControllerBase
[HttpPost("{trackId}/waveform")]
public async Task<ActionResult> GenerateWaveform(string trackId)
{
var audio = await _trackContentService.GetAudioBinaryAsync(trackId);
if (audio is null)
// Streaming compute (Wave 2): the WAV is read from the vault in bounded chunks, never buffered
// whole. Tri-state: null = no vault audio (404), false = present but uncomputable / write failed
// (500), true = stored.
var stored = await _waveformProfileService.ComputeAndStoreProfileStreamingAsync(
_ => _trackContentService.OpenAudioStreamAsync(trackId), trackId, HttpContext.RequestAborted);
if (stored is null)
{
_logger.LogWarning("GenerateWaveform: no audio in vault for {TrackId}", trackId);
return NotFound();
}
var stored = await _waveformProfileService.ComputeAndStoreAsync(audio.Buffer, trackId);
if (!stored)
if (stored is false)
{
_logger.LogError("GenerateWaveform: profile computation/storage failed for {TrackId}", trackId);
return StatusCode(500, "Failed to generate waveform profile.");
@@ -784,16 +787,27 @@ public class TrackController : ControllerBase
[HttpPost("{trackId}/waveform/high-res")]
public async Task<ActionResult> GenerateHighResWaveform(string trackId)
{
var audio = await _trackContentService.GetAudioBinaryAsync(trackId);
if (audio is null)
// The high-res bucket count is duration-derived. Read the duration from the vault index metadata
// (no body load); its absence means the track has no vault audio → 404.
var duration = await _trackContentService.GetAudioDurationAsync(trackId);
if (duration is null)
{
_logger.LogWarning("GenerateHighResWaveform: no audio in vault for {TrackId}", trackId);
return NotFound();
}
var stored = await _waveformProfileService.ComputeAndStoreHighResAsync(
audio.Buffer, trackId, audio.Duration);
if (!stored)
// Streaming compute (Wave 2): bounded read of the vault WAV. Tri-state mapping as in
// GenerateWaveform — null (entry vanished between the metadata read and the compute) → 404.
var stored = await _waveformProfileService.ComputeAndStoreHighResStreamingAsync(
_ => _trackContentService.OpenAudioStreamAsync(trackId), trackId, duration.Value,
HttpContext.RequestAborted);
if (stored is null)
{
_logger.LogWarning("GenerateHighResWaveform: no audio in vault for {TrackId}", trackId);
return NotFound();
}
if (stored is false)
{
_logger.LogError("GenerateHighResWaveform: computation/storage failed for {TrackId}", trackId);
return StatusCode(500, "Failed to generate high-res waveform datum.");