fix: enable player controls on load, clear track selection on stop and end-of-track
Add StateChanged multicast event to IPlayerService so AudioPlayerBar and TracksView re-render themselves without relying on the IsFixed cascade re-render path. Clear _selectedTrack in TracksView when IsLoaded drops (stop, unload, end-of-track). Set IsLoaded=false in OnPlaybackEndCallback so end-of-track triggers the same clear path. Add JS-module readiness probe in AudioInteropService; delete dead TS and buffered C# path; consolidate GetPlayIcon/FormatTime helpers; fix misleading minimize dock icon.
This commit is contained in:
@@ -16,6 +16,15 @@ public class AudioInteropService : IAsyncDisposable
|
||||
{
|
||||
try
|
||||
{
|
||||
if (!await WaitForModuleReadyAsync())
|
||||
{
|
||||
return new AudioOperationResult
|
||||
{
|
||||
Success = false,
|
||||
Error = "Audio engine failed to load (timed out waiting for DeepDrftAudio module)."
|
||||
};
|
||||
}
|
||||
|
||||
var result = await _jsRuntime.InvokeAsync<AudioOperationResult>("DeepDrftAudio.createPlayer", playerId);
|
||||
return result;
|
||||
}
|
||||
@@ -25,19 +34,34 @@ public class AudioInteropService : IAsyncDisposable
|
||||
}
|
||||
}
|
||||
|
||||
public async Task<AudioOperationResult> InitializeBufferedPlayerAsync(string playerId)
|
||||
// The audio engine is loaded as an ES module via a deferred `import(...)` in
|
||||
// App.razor, so on a slow WASM boot or cache miss it may not have executed by
|
||||
// the time the first track is selected. Poll its readiness probe (tolerating
|
||||
// the window before `window.DeepDrftAudio` even exists, when the call throws)
|
||||
// up to a short timeout before the first interop call.
|
||||
private async Task<bool> WaitForModuleReadyAsync()
|
||||
{
|
||||
return await InvokeJsAsync<AudioOperationResult>("DeepDrftAudio.initializeBufferedPlayer", playerId);
|
||||
}
|
||||
const int timeoutMs = 5000;
|
||||
const int pollIntervalMs = 50;
|
||||
var elapsed = 0;
|
||||
|
||||
public async Task<AudioOperationResult> AppendAudioBlockAsync(string playerId, byte[] audioBlock)
|
||||
{
|
||||
return await InvokeJsAsync<AudioOperationResult>("DeepDrftAudio.appendAudioBlock", playerId, audioBlock);
|
||||
}
|
||||
while (elapsed < timeoutMs)
|
||||
{
|
||||
try
|
||||
{
|
||||
if (await _jsRuntime.InvokeAsync<bool>("DeepDrftAudio.isReady"))
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
// window.DeepDrftAudio not attached yet — keep polling until timeout.
|
||||
}
|
||||
|
||||
public async Task<AudioLoadResult> FinalizeAudioBufferAsync(string playerId)
|
||||
{
|
||||
return await InvokeJsAsync<AudioLoadResult>("DeepDrftAudio.finalizeAudioBuffer", playerId);
|
||||
await Task.Delay(pollIntervalMs);
|
||||
elapsed += pollIntervalMs;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
// Streaming methods
|
||||
@@ -238,8 +262,6 @@ public class AudioInteropService : IAsyncDisposable
|
||||
{
|
||||
if (typeof(T) == typeof(AudioOperationResult))
|
||||
return (T)(object)new AudioOperationResult { Success = false, Error = ex.Message };
|
||||
if (typeof(T) == typeof(AudioLoadResult))
|
||||
return (T)(object)new AudioLoadResult { Success = false, Error = ex.Message };
|
||||
if (typeof(T) == typeof(StreamingResult))
|
||||
return (T)(object)new StreamingResult { Success = false, Error = ex.Message };
|
||||
if (typeof(T) == typeof(SeekResult))
|
||||
@@ -331,14 +353,6 @@ public class SeekResult : AudioOperationResult
|
||||
public long ByteOffset { get; set; }
|
||||
}
|
||||
|
||||
public class AudioLoadResult : AudioOperationResult
|
||||
{
|
||||
public double Duration { get; set; }
|
||||
public int SampleRate { get; set; }
|
||||
public int NumberOfChannels { get; set; }
|
||||
public double LoadProgress { get; set; }
|
||||
}
|
||||
|
||||
public class StreamingResult : AudioOperationResult
|
||||
{
|
||||
public bool CanStartStreaming { get; set; }
|
||||
|
||||
Reference in New Issue
Block a user