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:
@@ -183,12 +183,20 @@ public partial class WaveformSeeker : ComponentBase, IAsyncDisposable
|
|||||||
{
|
{
|
||||||
if (!CanSeek) return;
|
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;
|
_isSeeking = true;
|
||||||
_seekFraction = FractionFromOffset(e.OffsetX);
|
_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)
|
if (_jsModule is not null)
|
||||||
{
|
{
|
||||||
try
|
try
|
||||||
@@ -197,13 +205,9 @@ public partial class WaveformSeeker : ComponentBase, IAsyncDisposable
|
|||||||
}
|
}
|
||||||
catch
|
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)
|
private async Task HandlePointerMove(PointerEventArgs e)
|
||||||
|
|||||||
@@ -274,14 +274,18 @@ export class AudioPlayer {
|
|||||||
return { success: false, error: 'Invalid seek position' };
|
return { success: false, error: 'Invalid seek position' };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get buffered duration (accounting for playback offset)
|
const bufferStart = this.scheduler.getPlaybackOffset();
|
||||||
const bufferedDuration = this.scheduler.getTotalDuration() + this.scheduler.getPlaybackOffset();
|
const bufferEnd = this.scheduler.getTotalDuration() + bufferStart;
|
||||||
|
|
||||||
// Check if seeking within buffered content
|
// Position must be within [bufferStart, bufferEnd] to use buffered content.
|
||||||
if (position <= bufferedDuration) {
|
// A lower-bound check is required: after a seek-beyond-buffer, bufferStart is
|
||||||
|
// set to the prior seek position. Seeking to a position below bufferStart would
|
||||||
|
// produce a negative bufferRelativePosition in seekWithinBuffer, silently
|
||||||
|
// clamping to position 0 of the offset buffer instead of the requested time.
|
||||||
|
if (position >= bufferStart && position <= bufferEnd) {
|
||||||
return this.seekWithinBuffer(position);
|
return this.seekWithinBuffer(position);
|
||||||
} else {
|
} else {
|
||||||
// Seeking beyond buffer - signal C# to fetch new stream
|
// Seeking outside buffered window - signal C# to fetch new stream
|
||||||
return this.seekBeyondBuffer(position);
|
return this.seekBeyondBuffer(position);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user