fix: seek lower-bound guard and pointer-down callback ordering

AudioPlayer.ts: route seeks below bufferStart to seekBeyondBuffer;
previous missing lower-bound caused clamped playback after first seek.
WaveformSeeker: fire OnSeekStart/OnSeekChange before capturePointer
await to prevent fast-click race that locked _isSeeking true.
Latent: WavOffsetService encodes remaining-only DataSize, overwriting
JS this.duration after seek — not fixed here, scope separately.
This commit is contained in:
daniel-c-harvey
2026-06-07 15:02:34 -04:00
parent bd15b66aee
commit daa334a947
2 changed files with 21 additions and 13 deletions
@@ -183,12 +183,20 @@ public partial class WaveformSeeker : ComponentBase, IAsyncDisposable
{
if (!CanSeek) return;
// Set seeking state BEFORE the async capturePointer so a fast mobile tap that fires
// pointerup before capturePointer returns doesn't miss the commit.
_isSeeking = true;
_seekFraction = FractionFromOffset(e.OffsetX);
// Capture the pointer so a drag that leaves the element keeps tracking until release.
// Fire seek-start notifications BEFORE awaiting capturePointer. In Blazor WASM a JS
// interop await yields to the browser event loop. A fast click can fire pointerup
// during that window: HandlePointerUp runs (OnSeekEnd, _isSeeking = false), then
// HandlePointerDown resumes and calls OnSeekStart (_isSeeking stuck true, display
// frozen). Notifying first ensures the ordering is always Start → End, never End → Start.
if (Duration is not > 0) { _isSeeking = false; return; }
await OnSeekStart.InvokeAsync();
await OnSeekChange.InvokeAsync(_seekFraction * Duration.Value);
// Capture AFTER seek-start is notified so a fast pointerup cannot reorder
// OnSeekEnd before OnSeekStart in AudioPlayerBar.
if (_jsModule is not null)
{
try
@@ -197,13 +205,9 @@ public partial class WaveformSeeker : ComponentBase, IAsyncDisposable
}
catch
{
// Capture is a UX nicety; if it fails the gesture still works within the element bounds.
// Capture is a UX nicety; gesture still works within element bounds.
}
}
if (Duration is not > 0) { _isSeeking = false; return; }
await OnSeekStart.InvokeAsync();
await OnSeekChange.InvokeAsync(_seekFraction * Duration.Value);
}
private async Task HandlePointerMove(PointerEventArgs e)