Merge branch 'waveform-fixes' into dev
This commit is contained in:
@@ -32,6 +32,8 @@ public partial class WaveformSeeker : ComponentBase, IAsyncDisposable
|
||||
private ElementReference _seekerElement;
|
||||
private IStreamingPlayerService? _subscribedService;
|
||||
private IJSObjectReference? _jsModule;
|
||||
private IJSObjectReference? _resizeObserver;
|
||||
private DotNetObjectReference<WaveformSeeker>? _dotNetRef;
|
||||
|
||||
// Bars currently drawn (normalized [0,1] heights). Recomputed only when the source profile
|
||||
// identity or the rendered width changes — not on every seek/progress tick.
|
||||
@@ -108,12 +110,7 @@ public partial class WaveformSeeker : ComponentBase, IAsyncDisposable
|
||||
|
||||
if (_jsModule is null) return;
|
||||
|
||||
// Gate the interop call: only measure width on the first render or when the cached width
|
||||
// has been reset to zero (e.g. after a track change resets state). Subsequent renders
|
||||
// driven by position ticks or hover events don't need a width measurement — the element
|
||||
// size hasn't changed. ResizeObserver integration is deferred; for now first-render-only
|
||||
// measurement is sufficient since the bar is a fixed-height dock widget.
|
||||
if (firstRender || _elementWidth == 0)
|
||||
if (firstRender)
|
||||
{
|
||||
var width = await _jsModule.InvokeAsync<double>("getWidth", _seekerElement);
|
||||
if (width > 0 && Math.Abs(width - _elementWidth) > 1)
|
||||
@@ -122,6 +119,20 @@ public partial class WaveformSeeker : ComponentBase, IAsyncDisposable
|
||||
RebuildBars();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
_dotNetRef = DotNetObjectReference.Create(this);
|
||||
_resizeObserver = await _jsModule.InvokeAsync<IJSObjectReference>("observeResize", _seekerElement, _dotNetRef);
|
||||
}
|
||||
}
|
||||
|
||||
[JSInvokable]
|
||||
public void OnWidthChanged(double width)
|
||||
{
|
||||
if (width > 0 && Math.Abs(width - _elementWidth) > 1)
|
||||
{
|
||||
_elementWidth = width;
|
||||
RebuildBars();
|
||||
InvokeAsync(StateHasChanged);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -172,6 +183,11 @@ 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.
|
||||
if (_jsModule is not null)
|
||||
{
|
||||
@@ -185,28 +201,28 @@ public partial class WaveformSeeker : ComponentBase, IAsyncDisposable
|
||||
}
|
||||
}
|
||||
|
||||
_isSeeking = true;
|
||||
_seekFraction = FractionFromOffset(e.OffsetX);
|
||||
if (Duration is not > 0) { _isSeeking = false; return; }
|
||||
await OnSeekStart.InvokeAsync();
|
||||
await OnSeekChange.InvokeAsync(_seekFraction * Duration!.Value);
|
||||
await OnSeekChange.InvokeAsync(_seekFraction * Duration.Value);
|
||||
}
|
||||
|
||||
private async Task HandlePointerMove(PointerEventArgs e)
|
||||
{
|
||||
if (!CanSeek) return;
|
||||
if (Duration is not > 0) return;
|
||||
|
||||
var fraction = FractionFromOffset(e.OffsetX);
|
||||
|
||||
if (_isSeeking)
|
||||
{
|
||||
_seekFraction = fraction;
|
||||
await OnSeekChange.InvokeAsync(fraction * Duration!.Value);
|
||||
await OnSeekChange.InvokeAsync(fraction * Duration.Value);
|
||||
}
|
||||
else
|
||||
{
|
||||
_showHover = true;
|
||||
_hoverFraction = fraction;
|
||||
_hoverTime = fraction * Duration!.Value;
|
||||
_hoverTime = fraction * Duration.Value;
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
@@ -214,10 +230,11 @@ public partial class WaveformSeeker : ComponentBase, IAsyncDisposable
|
||||
private async Task HandlePointerUp(PointerEventArgs e)
|
||||
{
|
||||
if (!_isSeeking) return;
|
||||
|
||||
_seekFraction = FractionFromOffset(e.OffsetX);
|
||||
_isSeeking = false;
|
||||
await OnSeekEnd.InvokeAsync(_seekFraction * Duration!.Value);
|
||||
|
||||
if (Duration is not > 0) return;
|
||||
_seekFraction = FractionFromOffset(e.OffsetX);
|
||||
await OnSeekEnd.InvokeAsync(_seekFraction * Duration.Value);
|
||||
}
|
||||
|
||||
private async Task HandlePointerLeave()
|
||||
@@ -227,7 +244,8 @@ public partial class WaveformSeeker : ComponentBase, IAsyncDisposable
|
||||
if (_isSeeking)
|
||||
{
|
||||
_isSeeking = false;
|
||||
await OnSeekEnd.InvokeAsync(_seekFraction * Duration!.Value);
|
||||
if (Duration is > 0)
|
||||
await OnSeekEnd.InvokeAsync(_seekFraction * Duration.Value);
|
||||
}
|
||||
|
||||
if (_showHover)
|
||||
@@ -261,6 +279,15 @@ public partial class WaveformSeeker : ComponentBase, IAsyncDisposable
|
||||
_subscribedService = null;
|
||||
}
|
||||
|
||||
if (_resizeObserver is not null)
|
||||
{
|
||||
try { await (_jsModule?.InvokeVoidAsync("unobserveResize", _resizeObserver) ?? ValueTask.CompletedTask); } catch { }
|
||||
try { await _resizeObserver.DisposeAsync(); } catch { }
|
||||
_resizeObserver = null;
|
||||
}
|
||||
_dotNetRef?.Dispose();
|
||||
_dotNetRef = null;
|
||||
|
||||
if (_jsModule is not null)
|
||||
{
|
||||
try
|
||||
|
||||
@@ -10,3 +10,18 @@ export function capturePointer(element, pointerId) {
|
||||
// setPointerCapture keeps a drag tracking even when the pointer leaves the element bounds.
|
||||
element?.setPointerCapture?.(pointerId);
|
||||
}
|
||||
|
||||
export function observeResize(element, dotNetRef) {
|
||||
if (!element || typeof ResizeObserver === 'undefined') return null;
|
||||
const observer = new ResizeObserver(entries => {
|
||||
for (const entry of entries) {
|
||||
dotNetRef.invokeMethodAsync('OnWidthChanged', entry.contentRect.width).catch(() => {});
|
||||
}
|
||||
});
|
||||
observer.observe(element);
|
||||
return observer;
|
||||
}
|
||||
|
||||
export function unobserveResize(observer) {
|
||||
observer?.disconnect();
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user