Files
deepdrft/DeepDrftPublic.Client/Clients/TrackMediaClient.cs
T

100 lines
3.5 KiB
C#

using System.Net;
using System.Net.Http.Json;
using DeepDrftModels.DTOs;
using Microsoft.Extensions.DependencyInjection;
using NetBlocks.Models;
namespace DeepDrftPublic.Client.Clients;
public class TrackMediaResponse : IDisposable
{
public Stream Stream { get; }
public long ContentLength { get; }
public TrackMediaResponse(Stream stream, long contentLength)
{
Stream = stream;
ContentLength = contentLength;
}
public void Dispose()
{
Stream?.Dispose();
}
}
public class TrackMediaClient
{
private readonly HttpClient _http;
public TrackMediaClient(IHttpClientFactory httpClientFactory)
{
_http = httpClientFactory.CreateClient("DeepDrft.Content");
}
/// <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.
/// </summary>
public async Task<ApiResult<TrackMediaResponse>> GetTrackMedia(
string trackId,
long byteOffset = 0,
CancellationToken cancellationToken = default)
{
try
{
// Build URL with optional offset parameter
var url = byteOffset > 0
? $"api/track/{trackId}?offset={byteOffset}"
: $"api/track/{trackId}";
// Use HttpCompletionOption.ResponseHeadersRead to get stream immediately
var response = await _http.GetAsync(url, 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));
}
catch (Exception e)
{
return ApiResult<TrackMediaResponse>.CreateFailResult(e.Message);
}
}
/// <summary>
/// Fetches a track's stored waveform loudness profile. A 404 means no profile is stored
/// (existing tracks predate profiling, or computation failed at upload); callers treat that
/// as "render a flat seekbar" rather than an error, so it surfaces as a fail result with a
/// stable message rather than throwing.
/// </summary>
public async Task<ApiResult<WaveformProfileDto>> GetWaveformProfileAsync(string trackId, CancellationToken cancellationToken = default)
{
try
{
var response = await _http.GetAsync($"api/track/{trackId}/waveform", cancellationToken);
if (response.StatusCode == HttpStatusCode.NotFound)
{
return ApiResult<WaveformProfileDto>.CreateFailResult("No waveform profile available");
}
response.EnsureSuccessStatusCode();
var profile = await response.Content.ReadFromJsonAsync<WaveformProfileDto>();
if (profile is null)
{
return ApiResult<WaveformProfileDto>.CreateFailResult("Waveform profile response was empty");
}
return ApiResult<WaveformProfileDto>.CreatePassResult(profile);
}
catch (Exception e)
{
return ApiResult<WaveformProfileDto>.CreateFailResult(e.Message);
}
}
}