Smooth Seeking (no scrub audio)

This commit is contained in:
daniel-c-harvey
2025-12-07 04:54:05 -05:00
parent 20db222a0f
commit c5fdf12ef4
2 changed files with 46 additions and 18 deletions
@@ -32,16 +32,20 @@ else
Indeterminate="@(LoadProgress == 0)"/> Indeterminate="@(LoadProgress == 0)"/>
} }
</div> </div>
<TimestampLabel CurrentTime="CurrentTime" Duration="Duration"/> <TimestampLabel CurrentTime="DisplayTime" Duration="Duration"/>
</div> </div>
<div class="seekbar-flex mx-3"> <div class="seekbar-flex mx-3"
@onpointerdown="OnSeekStart"
@onpointerup="@(() => OnSeekEnd(_seekPosition))"
@onpointerleave="@(() => { if (_isSeeking) OnSeekEnd(_seekPosition); })">
<MudSlider T="double" <MudSlider T="double"
Min="0" Min="0"
Max="@(Duration ?? 0D)" Max="@(Duration ?? 0D)"
Step="0.1" Step="0.1"
Value="@CurrentTime" Value="@DisplayTime"
ValueChanged="@OnSeek" ValueChanged="@OnSeekChange"
Immediate="true"
Disabled="@(!CanSeek)"/> Disabled="@(!CanSeek)"/>
</div> </div>
@@ -67,17 +71,22 @@ else
Indeterminate="@(LoadProgress == 0)"/> Indeterminate="@(LoadProgress == 0)"/>
} }
</div> </div>
<TimestampLabel CurrentTime="CurrentTime" Duration="Duration"/> <TimestampLabel CurrentTime="DisplayTime" Duration="Duration"/>
<VolumeControls Volume="@Volume" VolumeChanged="@OnVolumeChange"/> <VolumeControls Volume="@Volume" VolumeChanged="@OnVolumeChange"/>
</div> </div>
<MudSlider T="double" <div @onpointerdown="OnSeekStart"
Min="0" @onpointerup="@(() => OnSeekEnd(_seekPosition))"
Max="@(Duration ?? 0D)" @onpointerleave="@(() => { if (_isSeeking) OnSeekEnd(_seekPosition); })">
Step="0.1" <MudSlider T="double"
Value="@CurrentTime" Min="0"
ValueChanged="@OnSeek" Max="@(Duration ?? 0D)"
Disabled="@(!CanSeek)"/> Step="0.1"
Value="@DisplayTime"
ValueChanged="@OnSeekChange"
Immediate="true"
Disabled="@(!CanSeek)"/>
</div>
</div> </div>
@* Control Buttons - positioned absolutely like original *@ @* Control Buttons - positioned absolutely like original *@
@@ -9,6 +9,8 @@ public partial class AudioPlayerBar : ComponentBase
[CascadingParameter] public required IStreamingPlayerService PlayerService { get; set; } [CascadingParameter] public required IStreamingPlayerService PlayerService { get; set; }
private bool _isMinimized = true; private bool _isMinimized = true;
private bool _isSeeking = false;
private double _seekPosition = 0;
private bool IsLoaded => PlayerService.IsLoaded; private bool IsLoaded => PlayerService.IsLoaded;
private bool IsLoading => PlayerService.IsLoading; private bool IsLoading => PlayerService.IsLoading;
@@ -16,12 +18,16 @@ public partial class AudioPlayerBar : ComponentBase
private bool IsStreamingMode => PlayerService.IsStreamingMode; private bool IsStreamingMode => PlayerService.IsStreamingMode;
private bool IsPlaying => PlayerService.IsPlaying; private bool IsPlaying => PlayerService.IsPlaying;
private bool IsPaused => PlayerService.IsPaused; private bool IsPaused => PlayerService.IsPaused;
private double CurrentTime => PlayerService.CurrentTime;
private double? Duration => PlayerService.Duration; private double? Duration => PlayerService.Duration;
private double Volume => PlayerService.Volume; private double Volume => PlayerService.Volume;
private double LoadProgress => PlayerService.LoadProgress; private double LoadProgress => PlayerService.LoadProgress;
private string? ErrorMessage => PlayerService.ErrorMessage; private string? ErrorMessage => PlayerService.ErrorMessage;
/// <summary>
/// Display time - shows seek position while dragging, otherwise current playback time.
/// </summary>
private double DisplayTime => _isSeeking ? _seekPosition : PlayerService.CurrentTime;
/// <summary> /// <summary>
/// Seek is enabled once track is loaded AND duration is known (from WAV header). /// Seek is enabled once track is loaded AND duration is known (from WAV header).
/// This allows seeking even during streaming, including seeking beyond buffered content. /// This allows seeking even during streaming, including seeking beyond buffered content.
@@ -74,8 +80,21 @@ public partial class AudioPlayerBar : ComponentBase
await PlayerService.Stop(); await PlayerService.Stop();
} }
private async Task OnSeek(double position) private void OnSeekStart()
{ {
_isSeeking = true;
_seekPosition = PlayerService.CurrentTime;
}
private void OnSeekChange(double position)
{
_seekPosition = position;
StateHasChanged();
}
private async Task OnSeekEnd(double position)
{
_isSeeking = false;
await PlayerService.Seek(position); await PlayerService.Seek(position);
} }