diff --git a/DeepDrftPublic.Client/Clients/TrackMediaClient.cs b/DeepDrftPublic.Client/Clients/TrackMediaClient.cs
index cd715e1..fa65636 100644
--- a/DeepDrftPublic.Client/Clients/TrackMediaClient.cs
+++ b/DeepDrftPublic.Client/Clients/TrackMediaClient.cs
@@ -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();
diff --git a/DeepDrftTests/TrackMediaStreamingEnabledTests.cs b/DeepDrftTests/TrackMediaStreamingEnabledTests.cs
new file mode 100644
index 0000000..dc0501a
--- /dev/null
+++ b/DeepDrftTests/TrackMediaStreamingEnabledTests.cs
@@ -0,0 +1,80 @@
+using System.Net;
+using DeepDrftModels.Enums;
+using DeepDrftPublic.Client.Clients;
+
+namespace DeepDrftTests;
+
+///
+/// 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 SetBrowserResponseStreamingEnabled(true), which records it in
+/// under the framework key "WebAssemblyEnableStreamingResponse".
+/// A stub handler reads that option back during SendAsync — 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 , so both are asserted here.
+///
+[TestFixture]
+public class TrackMediaStreamingEnabledTests
+{
+ // The framework key SetBrowserResponseStreamingEnabled writes into HttpRequestMessage.Options.
+ private static readonly HttpRequestOptionsKey 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 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");
+ }
+}