chore: remove WavOffsetService and ?offset= seek path, superseded by Range header (Phase 4.1)

This commit is contained in:
daniel-c-harvey
2026-06-09 07:30:36 -04:00
parent b372bee365
commit f602eb9772
3 changed files with 34 additions and 389 deletions
+34 -69
View File
@@ -1,7 +1,6 @@
using DeepDrftAPI.Middleware;
using DeepDrftAPI.Models;
using DeepDrftAPI.Services;
using DeepDrftContent.Audio;
using DeepDrftContent.Constants;
using DeepDrftContent.FileDatabase.Services;
using DeepDrftContent.FileDatabase.Models;
@@ -17,7 +16,6 @@ namespace DeepDrftAPI.Controllers;
public class TrackController : ControllerBase
{
private readonly DeepDrftContent.TrackContentService _trackContentService;
private readonly WavOffsetService _wavOffsetService;
private readonly UnifiedTrackService _unifiedService;
private readonly ITrackService _sqlTrackService;
private readonly WaveformProfileService _waveformProfileService;
@@ -32,7 +30,6 @@ public class TrackController : ControllerBase
public TrackController(
DeepDrftContent.TrackContentService trackContentService,
DeepDrftContent.FileDatabase.Services.FileDatabase fileDatabase,
WavOffsetService wavOffsetService,
UnifiedTrackService unifiedService,
ITrackService sqlTrackService,
WaveformProfileService waveformProfileService,
@@ -40,7 +37,6 @@ public class TrackController : ControllerBase
{
_trackContentService = trackContentService;
_fileDatabase = fileDatabase;
_wavOffsetService = wavOffsetService;
_unifiedService = unifiedService;
_sqlTrackService = sqlTrackService;
_waveformProfileService = waveformProfileService;
@@ -352,86 +348,55 @@ public class TrackController : ControllerBase
// --- Parameterized routes ---
[HttpGet("{trackId}")]
public async Task<ActionResult> GetTrack(string trackId, [FromQuery] long offset = 0)
public async Task<ActionResult> GetTrack(string trackId)
{
_logger.LogInformation("GetTrack called with trackId: {TrackId}, offset: {Offset}", trackId, offset);
_logger.LogInformation("GetTrack called with trackId: {TrackId}", trackId);
try
{
// No-offset path: stream the file straight from disk so a 100 MB WAV does not
// force a 100 MB LOH allocation per request. The offset path still loads
// the full buffer because WavOffsetService block-aligns and reslices into
// a composite stream over the in-memory buffer.
if (offset == 0)
var vault = _fileDatabase.GetVault(VaultConstants.Tracks);
if (vault == null)
{
var vault = _fileDatabase.GetVault(VaultConstants.Tracks);
if (vault == null)
{
_logger.LogWarning("Tracks vault not found");
return NotFound();
}
var mediaStream = await vault.GetEntryStreamAsync(trackId);
if (mediaStream == null)
{
_logger.LogWarning("Track not found: {TrackId}", trackId);
return NotFound();
}
// Resolve MIME and log before handing the stream to File().
// If anything here throws, the finally block disposes the wrapper
// (and its inner FileStream) so neither leaks. On the success path
// File() takes ownership of the inner stream; ASP.NET Core disposes
// it after the response body is sent. The wrapper is a thin struct
// with no extra resources, so disposing it after extracting the
// inner stream is a no-op — we only call Dispose() in the catch path.
string streamMimeType;
long streamLength;
Stream innerStream;
try
{
streamMimeType = MimeTypeExtensions.GetMimeType(mediaStream.Extension);
streamLength = mediaStream.Stream.Length;
innerStream = mediaStream.Stream;
}
catch
{
await mediaStream.DisposeAsync();
throw;
}
_logger.LogInformation(
"Streaming track from disk: {TrackId}, Size: {Size} bytes",
trackId, streamLength);
// enableRangeProcessing: true — seek is served by HTTP Range requests.
// The FileStream is seekable, so ASP.NET Core honours an incoming
// Range header by slicing the file and responding 206 Partial Content.
return File(innerStream, streamMimeType, enableRangeProcessing: true);
_logger.LogWarning("Tracks vault not found");
return NotFound();
}
// Offset path: route through TrackContentService.GetAudioBinaryAsync (Track B's
// orchestrator boundary) so the controller stays out of FileDatabase directly.
// The buffered AudioBinary is required because WavOffsetService block-aligns
// and reslices into a composite stream over the in-memory buffer.
var file = await _trackContentService.GetAudioBinaryAsync(trackId);
if (file == null)
var mediaStream = await vault.GetEntryStreamAsync(trackId);
if (mediaStream == null)
{
_logger.LogWarning("Track not found: {TrackId}", trackId);
return NotFound();
}
var mimeType = MimeTypeExtensions.GetMimeType(file.Extension);
var offsetStream = _wavOffsetService.CreateOffsetStream(file.Buffer, offset);
if (offsetStream == null)
// Resolve MIME and log before handing the stream to File().
// If anything here throws, the finally block disposes the wrapper
// (and its inner FileStream) so neither leaks. On the success path
// File() takes ownership of the inner stream; ASP.NET Core disposes
// it after the response body is sent. The wrapper is a thin struct
// with no extra resources, so disposing it after extracting the
// inner stream is a no-op — we only call Dispose() in the catch path.
string streamMimeType;
long streamLength;
Stream innerStream;
try
{
_logger.LogWarning("Invalid offset {Offset} for track: {TrackId}", offset, trackId);
return BadRequest("Invalid offset");
streamMimeType = MimeTypeExtensions.GetMimeType(mediaStream.Extension);
streamLength = mediaStream.Stream.Length;
innerStream = mediaStream.Stream;
}
catch
{
await mediaStream.DisposeAsync();
throw;
}
_logger.LogInformation("Successfully retrieved track with offset: {TrackId}, Offset: {Offset}, StreamSize: {Size} bytes",
trackId, offset, offsetStream.Length);
return File(offsetStream, mimeType);
_logger.LogInformation(
"Streaming track from disk: {TrackId}, Size: {Size} bytes",
trackId, streamLength);
// enableRangeProcessing: true — seek is served by HTTP Range requests.
// The FileStream is seekable, so ASP.NET Core honours an incoming
// Range header by slicing the file and responding 206 Partial Content.
return File(innerStream, streamMimeType, enableRangeProcessing: true);
}
catch (Exception ex)
{
-2
View File
@@ -1,6 +1,5 @@
using DeepDrftAPI.Models;
using DeepDrftContent;
using DeepDrftContent.Audio;
using DeepDrftContent.Constants;
using DeepDrftContent.FileDatabase.Models;
using DeepDrftContent.FileDatabase.Services;
@@ -15,7 +14,6 @@ namespace DeepDrftAPI
public static Task ConfigureDomainServices(WebApplicationBuilder builder)
{
// Audio services
builder.Services.AddSingleton<WavOffsetService>();
builder.Services.AddSingleton<AudioProcessor>();
builder.Services.AddSingleton<TrackContentService>();