Fix 9 design majors: ITrackService interface, IsDescending, ArrayPool base, CliUtils, sort sentinel cleanup, content controller via TrackService, Skip property

This commit is contained in:
Daniel Harvey
2026-05-17 16:10:56 -04:00
parent fc5b8de81a
commit 4c9bf0ca8d
15 changed files with 97 additions and 71 deletions
@@ -225,6 +225,9 @@ public class AudioInteropService : IAsyncDisposable
return await InvokeJsAsync<AudioOperationResult>("DeepDrftAudio.disposePlayer", playerId);
}
// TODO: The typeof(T) switch below requires updating whenever a new result type is added.
// Consider introducing a shared marker interface (e.g. IAudioResult with a static factory
// method) so InvokeJsAsync can construct the failure result generically without a type switch.
private async Task<T> InvokeJsAsync<T>(string identifier, params object[] args)
{
try
@@ -2,6 +2,7 @@ using DeepDrftModels.Entities;
using DeepDrftWeb.Client.Clients;
using Microsoft.AspNetCore.Components;
using NetBlocks.Models;
using System.Buffers;
namespace DeepDrftWeb.Client.Services;
@@ -135,34 +136,30 @@ public abstract class AudioPlayerService : IPlayerService, IAsyncDisposable
private async Task StreamAudio(TrackMediaResponse audio)
{
const int bufferSize = 32 * 1024;
var rentedBuffer = ArrayPool<byte>.Shared.Rent(bufferSize);
try
{
const int bufferSize = 32 * 1024;
long totalBytesRead = 0;
int currentBytes;
do
{
var buffer = new byte[bufferSize];
currentBytes = await audio.Stream.ReadAsync(buffer, 0, buffer.Length);
currentBytes = await audio.Stream.ReadAsync(rentedBuffer, 0, bufferSize);
if (currentBytes > 0)
{
totalBytesRead += currentBytes;
if (currentBytes < bufferSize)
{
var trimmedBuffer = new byte[currentBytes];
Array.Copy(buffer, trimmedBuffer, currentBytes);
buffer = trimmedBuffer;
}
var appendResult = await _audioInterop.AppendAudioBlockAsync(PlayerId, buffer);
// Slice to actual bytes read before sending to interop
var chunk = rentedBuffer[..currentBytes];
var appendResult = await _audioInterop.AppendAudioBlockAsync(PlayerId, chunk);
if (!appendResult.Success)
{
throw new Exception($"Failed to append audio block: {appendResult.Error}");
}
if (audio.ContentLength > 0)
{
LoadProgress = Math.Min(1.0, (double)totalBytesRead / audio.ContentLength);
@@ -170,13 +167,13 @@ public abstract class AudioPlayerService : IPlayerService, IAsyncDisposable
}
}
} while (currentBytes > 0);
var finalizeResult = await _audioInterop.FinalizeAudioBufferAsync(PlayerId);
if (!finalizeResult.Success)
{
throw new Exception($"Failed to finalize audio buffer: {finalizeResult.Error}");
}
Duration = finalizeResult.Duration;
LoadProgress = 1.0;
IsLoaded = true;
@@ -191,6 +188,10 @@ public abstract class AudioPlayerService : IPlayerService, IAsyncDisposable
await NotifyStateChanged();
throw;
}
finally
{
ArrayPool<byte>.Shared.Return(rentedBuffer);
}
}
public async Task TogglePlayPause()
@@ -53,8 +53,6 @@ public class StreamingAudioPlayerService : AudioPlayerService, IStreamingPlayerS
// Resume AudioContext immediately on track selection (user interaction) to avoid clicks later
await _audioInterop.EnsureAudioContextReady(PlayerId);
// NotifyStateChanged();
await NotifyTrackSelected();
await LoadTrackStreaming(track);