fix(api): stream audio store path to eliminate whole-file buffering (OOM)

Processors now emit a ProcessedAudio plan with a streamed writer instead of a whole-file AudioBinary; vault writes stream via RegisterResourceStreamingAsync. Header parsing is bounded. Wave 2 (waveform/Opus) still re-reads the full file by design.
This commit is contained in:
daniel-c-harvey
2026-06-25 15:27:28 -04:00
parent 1e063d95f4
commit 79bbbd4956
13 changed files with 920 additions and 168 deletions
+14 -11
View File
@@ -68,9 +68,9 @@ public class TrackReplaceAudioTests
var originalDuration = before!.Duration;
var replacement = await WriteWavAsync(BuildMinimalPcmWav(6.0), ".wav");
var newAudio = await content.ReplaceTrackAudioAsync(entryKey, replacement);
var newDuration = await content.ReplaceTrackAudioAsync(entryKey, replacement);
Assert.That(newAudio, Is.Not.Null, "Replace should return the freshly stored audio");
Assert.That(newDuration, Is.Not.Null, "Replace should return the freshly stored audio's duration");
var after = await content.GetAudioBinaryAsync(entryKey);
Assert.Multiple(() =>
@@ -78,8 +78,8 @@ public class TrackReplaceAudioTests
Assert.That(after, Is.Not.Null, "The track must remain retrievable under the same EntryKey");
Assert.That(after!.Duration, Is.GreaterThan(originalDuration),
"The retrieved audio must reflect the longer replacement, not the original");
Assert.That(newAudio!.Duration, Is.EqualTo(after.Duration),
"The returned binary must match what is stored under the key");
Assert.That(newDuration!.Value, Is.EqualTo(after.Duration),
"The returned duration must match what is stored under the key");
});
}
@@ -101,9 +101,9 @@ public class TrackReplaceAudioTests
Assert.That(wavFilesBefore, Is.Not.Empty, "Sanity: the original .wav backing file exists");
var replacement = await WriteFlacAsync();
var newAudio = await content.ReplaceTrackAudioAsync(entryKey, replacement);
var newDuration = await content.ReplaceTrackAudioAsync(entryKey, replacement);
Assert.That(newAudio, Is.Not.Null);
Assert.That(newDuration, Is.Not.Null);
Assert.Multiple(() =>
{
Assert.That(Directory.GetFiles(vaultDir, "*.wav"), Is.Empty,
@@ -135,12 +135,15 @@ public class TrackReplaceAudioTests
Assert.That(staleHighRes, Is.Not.Null);
var replacement = await WriteWavAsync(BuildMinimalPcmWav(20.0), ".wav");
var newAudio = await content.ReplaceTrackAudioAsync(entryKey, replacement);
Assert.That(newAudio, Is.Not.Null);
var newDuration = await content.ReplaceTrackAudioAsync(entryKey, replacement);
Assert.That(newDuration, Is.Not.Null);
// Regen step (mirrors the orchestrator).
Assert.That(await waveforms.ComputeAndStoreAsync(newAudio!.Buffer, entryKey), Is.True);
Assert.That(await waveforms.ComputeAndStoreHighResAsync(newAudio.Buffer, entryKey, newAudio.Duration), Is.True);
// Regen step (mirrors the orchestrator, which re-reads the freshly stored audio from the vault
// rather than consuming an in-memory buffer the streamed store no longer hands back).
var newStored = await content.GetAudioBinaryAsync(entryKey);
Assert.That(newStored, Is.Not.Null);
Assert.That(await waveforms.ComputeAndStoreAsync(newStored!.Buffer, entryKey), Is.True);
Assert.That(await waveforms.ComputeAndStoreHighResAsync(newStored.Buffer, entryKey, newStored.Duration), Is.True);
var freshHighRes = await waveforms.GetProfileAsync(entryKey, VaultConstants.TrackWaveforms);
var freshProfile = await waveforms.GetProfileAsync(entryKey);