Enable WASM response streaming on audio media fetch
Without SetBrowserResponseStreamingEnabled the browser buffers the whole body before yielding, so the Phase 21.2 read-loop pause backpressured an already-downloaded payload. Set it on both the initial and seek/refill requests; safe no-op on the SSR path.
This commit is contained in:
@@ -3,6 +3,7 @@ using System.Net.Http.Headers;
|
||||
using System.Net.Http.Json;
|
||||
using DeepDrftModels.DTOs;
|
||||
using DeepDrftModels.Enums;
|
||||
using Microsoft.AspNetCore.Components.WebAssembly.Http;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
using NetBlocks.Models;
|
||||
|
||||
@@ -79,6 +80,18 @@ public class TrackMediaClient
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, uri);
|
||||
request.Headers.Range = new RangeHeaderValue(byteOffset, null);
|
||||
|
||||
// Stream the response body incrementally instead of buffering it whole (Phase 21.4 fix).
|
||||
// In Blazor WebAssembly the HttpClient is backed by the browser fetch API; without this the
|
||||
// browser buffers the ENTIRE body before the response stream yields a byte, so the 21.2
|
||||
// read-loop pause (StreamingAudioPlayerService) backpressures nothing — the whole payload is
|
||||
// already in memory. Enabling streaming makes ReadAsync pull from a browser ReadableStream
|
||||
// whose backpressure reaches the underlying fetch, so pausing reads genuinely throttles the
|
||||
// network. This is a request-option flag, not a runtime call: on the SSR server-to-server path
|
||||
// the SocketsHttpHandler simply ignores the unknown option, so it is safe unguarded. Applies to
|
||||
// BOTH the initial stream (byteOffset 0) and the seek/refill Range requests (21.3) — both share
|
||||
// this method, so both depend on the same backpressure.
|
||||
request.SetBrowserResponseStreamingEnabled(true);
|
||||
|
||||
// Use HttpCompletionOption.ResponseHeadersRead to get stream immediately
|
||||
var response = await _http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
using System.Net;
|
||||
using DeepDrftModels.Enums;
|
||||
using DeepDrftPublic.Client.Clients;
|
||||
|
||||
namespace DeepDrftTests;
|
||||
|
||||
/// <summary>
|
||||
/// Pins the Phase 21.4 transport fix: every audio media fetch must carry the browser response-streaming
|
||||
/// option so the body streams incrementally in WASM. Without it the browser buffers the whole payload
|
||||
/// before the response stream yields a byte, and the 21.2 read-loop pause backpressures nothing.
|
||||
///
|
||||
/// The flag is set by <c>SetBrowserResponseStreamingEnabled(true)</c>, which records it in
|
||||
/// <see cref="HttpRequestMessage.Options"/> under the framework key <c>"WebAssemblyEnableStreamingResponse"</c>.
|
||||
/// A stub handler reads that option back during <c>SendAsync</c> — the same network-boundary fake the Opus
|
||||
/// format-selection tests use. True network backpressure is browser-only and cannot be unit-profiled; this
|
||||
/// asserts the request is *configured* for streaming, which is the part the harness can observe. Daniel's
|
||||
/// 21.4 manual re-run confirms the actual bounded-memory behaviour.
|
||||
///
|
||||
/// Both the initial full-stream request (byteOffset 0) and the seek/refill Range request (byteOffset > 0,
|
||||
/// Phase 21.3) flow through <see cref="TrackMediaClient.GetTrackMedia"/>, so both are asserted here.
|
||||
/// </summary>
|
||||
[TestFixture]
|
||||
public class TrackMediaStreamingEnabledTests
|
||||
{
|
||||
// The framework key SetBrowserResponseStreamingEnabled writes into HttpRequestMessage.Options.
|
||||
private static readonly HttpRequestOptionsKey<bool> StreamingOptionKey = new("WebAssemblyEnableStreamingResponse");
|
||||
|
||||
// Captures the streaming option off each outgoing request, then returns a minimal 200 with a body so
|
||||
// GetTrackMedia reaches its pass path (ReadAsStreamAsync over ByteArrayContent).
|
||||
private sealed class CapturingHandler : HttpMessageHandler
|
||||
{
|
||||
public bool? StreamingEnabled { get; private set; }
|
||||
|
||||
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
|
||||
{
|
||||
StreamingEnabled = request.Options.TryGetValue(StreamingOptionKey, out var enabled) ? enabled : null;
|
||||
|
||||
var response = new HttpResponseMessage(HttpStatusCode.OK)
|
||||
{
|
||||
Content = new ByteArrayContent("audio-bytes"u8.ToArray()),
|
||||
};
|
||||
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_InitialStream_EnablesBrowserResponseStreaming()
|
||||
{
|
||||
var handler = new CapturingHandler();
|
||||
var client = new TrackMediaClient(new SingleClientFactory(handler));
|
||||
|
||||
var result = await client.GetTrackMedia("track-1", byteOffset: 0, format: AudioFormat.Lossless);
|
||||
|
||||
Assert.That(result.Success, Is.True, "the fetch should succeed against the stub");
|
||||
Assert.That(handler.StreamingEnabled, Is.True,
|
||||
"the initial media stream must enable browser response streaming or the read-loop pause backpressures nothing");
|
||||
}
|
||||
|
||||
[Test]
|
||||
public async Task GetTrackMedia_SeekOffsetStream_EnablesBrowserResponseStreaming()
|
||||
{
|
||||
var handler = new CapturingHandler();
|
||||
var client = new TrackMediaClient(new SingleClientFactory(handler));
|
||||
|
||||
var result = await client.GetTrackMedia("track-1", byteOffset: 1_048_576, format: AudioFormat.Opus);
|
||||
|
||||
Assert.That(result.Success, Is.True, "the offset fetch should succeed against the stub");
|
||||
Assert.That(handler.StreamingEnabled, Is.True,
|
||||
"the seek/refill Range request must also enable streaming — 21.3 refill depends on the same backpressure");
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user