11faf8888f
Replace the open-ended forward GET with sequential bounded bytes=start-end segments, the next fetched only when the scheduler drains below low-water, so the browser holds ~one segment regardless of file size. Seek converges on the same loop. Strip BP-DIAG.
97 lines
4.1 KiB
C#
97 lines
4.1 KiB
C#
using System.Net;
|
|
using System.Net.Http.Headers;
|
|
using DeepDrftModels.Enums;
|
|
using DeepDrftPublic.Client.Clients;
|
|
|
|
namespace DeepDrftTests;
|
|
|
|
/// <summary>
|
|
/// Pins the Phase 21 Direction B bounded-Range request shape on <see cref="TrackMediaClient.GetTrackMedia"/>.
|
|
/// The network-memory bound rests on each forward fetch being a finite <c>bytes=start-end</c> slice (so the
|
|
/// browser buffers only one segment), and on the caller learning the file total from the 206
|
|
/// <c>Content-Range</c> header (the EOF boundary the segment cursor advances toward). Both are request/response
|
|
/// plumbing the harness can observe directly; the actual browser memory behaviour is Daniel's manual re-run.
|
|
/// </summary>
|
|
[TestFixture]
|
|
public class TrackMediaBoundedRangeTests
|
|
{
|
|
// Captures the outgoing Range header and returns a 206 with a Content-Range so TotalLength resolves.
|
|
private sealed class RangeCapturingHandler : HttpMessageHandler
|
|
{
|
|
private readonly long _total;
|
|
public RangeHeaderValue? CapturedRange { get; private set; }
|
|
|
|
public RangeCapturingHandler(long total) => _total = total;
|
|
|
|
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
|
{
|
|
CapturedRange = request.Headers.Range;
|
|
|
|
var from = request.Headers.Range?.Ranges.First().From ?? 0;
|
|
var to = request.Headers.Range?.Ranges.First().To ?? (_total - 1);
|
|
var body = new byte[to - from + 1];
|
|
|
|
var response = new HttpResponseMessage(HttpStatusCode.PartialContent)
|
|
{
|
|
Content = new ByteArrayContent(body),
|
|
};
|
|
response.Content.Headers.ContentRange = new ContentRangeHeaderValue(from, to, _total);
|
|
return Task.FromResult(response);
|
|
}
|
|
}
|
|
|
|
private sealed class SingleClientFactory : IHttpClientFactory
|
|
{
|
|
private readonly HttpMessageHandler _handler;
|
|
public SingleClientFactory(HttpMessageHandler handler) => _handler = handler;
|
|
public HttpClient CreateClient(string name) =>
|
|
new(_handler, disposeHandler: false) { BaseAddress = new Uri("https://content.test/") };
|
|
}
|
|
|
|
[Test]
|
|
public async Task GetTrackMedia_WithByteEnd_SendsBoundedRange()
|
|
{
|
|
var handler = new RangeCapturingHandler(total: 100_000_000);
|
|
var client = new TrackMediaClient(new SingleClientFactory(handler));
|
|
|
|
await client.GetTrackMedia("track-1", byteOffset: 0, byteEnd: 4 * 1024 * 1024 - 1, format: AudioFormat.Lossless);
|
|
|
|
var range = handler.CapturedRange!.Ranges.First();
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(range.From, Is.EqualTo(0), "bounded request starts at the cursor");
|
|
Assert.That(range.To, Is.EqualTo(4 * 1024 * 1024 - 1),
|
|
"bounded request must carry an inclusive end so the server returns a finite slice (one segment)");
|
|
});
|
|
}
|
|
|
|
[Test]
|
|
public async Task GetTrackMedia_WithoutByteEnd_SendsOpenEndedRange()
|
|
{
|
|
var handler = new RangeCapturingHandler(total: 100_000_000);
|
|
var client = new TrackMediaClient(new SingleClientFactory(handler));
|
|
|
|
await client.GetTrackMedia("track-1", byteOffset: 1024, byteEnd: null, format: AudioFormat.Lossless);
|
|
|
|
var range = handler.CapturedRange!.Ranges.First();
|
|
Assert.Multiple(() =>
|
|
{
|
|
Assert.That(range.From, Is.EqualTo(1024), "open-ended request starts at the cursor");
|
|
Assert.That(range.To, Is.Null, "no byteEnd → open-ended bytes=start- (pre-Direction-B shape, kept working)");
|
|
});
|
|
}
|
|
|
|
[Test]
|
|
public async Task GetTrackMedia_206Response_SurfacesTotalLengthFromContentRange()
|
|
{
|
|
var handler = new RangeCapturingHandler(total: 970_000_000);
|
|
var client = new TrackMediaClient(new SingleClientFactory(handler));
|
|
|
|
var result = await client.GetTrackMedia("track-1", byteOffset: 0, byteEnd: 4 * 1024 * 1024 - 1);
|
|
|
|
Assert.That(result.Success, Is.True);
|
|
Assert.That(result.Value!.TotalLength, Is.EqualTo(970_000_000),
|
|
"the file total (the segment cursor's EOF boundary) must come from the 206 Content-Range");
|
|
}
|
|
}
|