Merge branch 'p4-w1-range-streaming' into dev
This commit is contained in:
@@ -1,4 +1,5 @@
|
||||
using System.Net;
|
||||
using System.Net.Http.Headers;
|
||||
using System.Net.Http.Json;
|
||||
using DeepDrftModels.DTOs;
|
||||
using Microsoft.Extensions.DependencyInjection;
|
||||
@@ -10,16 +11,19 @@ public class TrackMediaResponse : IDisposable
|
||||
{
|
||||
public Stream Stream { get; }
|
||||
public long ContentLength { get; }
|
||||
private readonly HttpResponseMessage _response;
|
||||
|
||||
public TrackMediaResponse(Stream stream, long contentLength)
|
||||
public TrackMediaResponse(Stream stream, long contentLength, HttpResponseMessage response)
|
||||
{
|
||||
Stream = stream;
|
||||
ContentLength = contentLength;
|
||||
_response = response;
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
Stream?.Dispose();
|
||||
_response?.Dispose();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -33,10 +37,12 @@ public class TrackMediaClient
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Fetches the WAV stream for a track, optionally starting from a byte offset.
|
||||
/// The cancellation token is forwarded to <see cref="HttpClient.GetAsync"/> so a
|
||||
/// navigation or seek-replacement aborts the in-flight server connection rather
|
||||
/// than leaving the server draining bytes into a dead socket.
|
||||
/// Fetches the WAV stream for a track via an HTTP Range request starting at a
|
||||
/// file-absolute byte offset. <paramref name="byteOffset"/> is the position from
|
||||
/// the start of the file on disk (including the WAV header) — callers seeking into
|
||||
/// audio data must add the header size themselves. The cancellation token aborts
|
||||
/// the in-flight server connection rather than leaving the server draining bytes
|
||||
/// into a dead socket.
|
||||
/// </summary>
|
||||
public async Task<ApiResult<TrackMediaResponse>> GetTrackMedia(
|
||||
string trackId,
|
||||
@@ -45,19 +51,21 @@ public class TrackMediaClient
|
||||
{
|
||||
try
|
||||
{
|
||||
// Build URL with optional offset parameter
|
||||
var url = byteOffset > 0
|
||||
? $"api/track/{trackId}?offset={byteOffset}"
|
||||
: $"api/track/{trackId}";
|
||||
// Same URL for every seek — only the Range header differs. byteOffset 0 is
|
||||
// not special-cased: "bytes=0-" requests the whole file from the start.
|
||||
using var request = new HttpRequestMessage(HttpMethod.Get, $"api/track/{trackId}");
|
||||
request.Headers.Range = new RangeHeaderValue(byteOffset, null);
|
||||
|
||||
// Use HttpCompletionOption.ResponseHeadersRead to get stream immediately
|
||||
var response = await _http.GetAsync(url, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
|
||||
var response = await _http.SendAsync(request, HttpCompletionOption.ResponseHeadersRead, cancellationToken);
|
||||
response.EnsureSuccessStatusCode();
|
||||
|
||||
var contentLength = response.Content.Headers.ContentLength ?? 0;
|
||||
var stream = await response.Content.ReadAsStreamAsync(cancellationToken);
|
||||
|
||||
return ApiResult<TrackMediaResponse>.CreatePassResult(new TrackMediaResponse(stream, contentLength));
|
||||
// TrackMediaResponse takes ownership of both stream and response;
|
||||
// do NOT dispose response here — the caller disposes via TrackMediaResponse.Dispose().
|
||||
return ApiResult<TrackMediaResponse>.CreatePassResult(new TrackMediaResponse(stream, contentLength, response));
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
|
||||
@@ -377,7 +377,7 @@ public class StreamingAudioPlayerService : AudioPlayerService, IStreamingPlayerS
|
||||
|
||||
if (result.Success)
|
||||
{
|
||||
if (result.SeekBeyondBuffer && result.ByteOffset > 0)
|
||||
if (result.SeekBeyondBuffer && result.ByteOffset >= 0)
|
||||
{
|
||||
// Need to load new stream from offset
|
||||
_logger.LogInformation("Seeking beyond buffer to {Position:F2}s, byte offset: {ByteOffset}",
|
||||
|
||||
Reference in New Issue
Block a user