Apply stream-quality change live by reloading at current position
Finish the Settings "Apply" behavior so changing streaming quality mid-track
switches format immediately instead of only persisting the cookie for the next
play.
- SettingsMenu reads the AudioPlayerProvider cascade and threads the player into
StreamQualitySetting as an explicit parameter (the MudMenu panel portals to
MudPopoverProvider, outside the cascade scope, so a [CascadingParameter] there
lands null). StreamQualitySetting's Apply persists the cookie, then asks the
player to reload preserving position.
- Add a "load at timestamp" path to the player rather than restart-from-0-then-
seek (which audibly played the start and raced the just-started scheduler into
a crash). ReloadPreservingPositionAsync loads the track in the newly-resolved
format beginning DIRECTLY at the saved position:
* new JS resolveStreamOffset(position) resolves the file-absolute byte offset
with no playback/buffer state (Opus from its sidecar immediately; WAV after
a header probe),
* StartFromPositionAsync converges onto the existing seek/refill loop
(RunSegmentedStreamAsync with a non-null seekPosition) so the decoder
reinitializes for a header-less Range continuation and starts playback at
the target,
* ProbeHeaderAsync feeds the byte-0 segment to the decoder WITHOUT starting
playback until the WAV header parses (bounded by 256 KB); the probe buffers
are dropped by the continuation's clearForSeek, so nothing is audible.
- IStreamingPlayerService gains ReloadPreservingPositionAsync; the QueueService
test fake implements it.
This commit is contained in:
@@ -24,6 +24,14 @@
|
||||
This browser can't decode Opus — you'll stream lossless.
|
||||
</div>
|
||||
}
|
||||
<div class="d-flex flex-align-right">
|
||||
<MudButton Disabled="!IsApplyEnabled"
|
||||
Color="Color.Primary"
|
||||
Variant="Variant.Filled"
|
||||
OnClick="@ApplyStreamQualitySetting">
|
||||
APPLY
|
||||
</MudButton>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@code {
|
||||
@@ -31,8 +39,17 @@
|
||||
[Inject] public required SettingsCookieService CookieService { get; set; }
|
||||
[Inject] public required AudioInteropService AudioInterop { get; set; }
|
||||
|
||||
// The active player, threaded in from SettingsMenu (which reads it off the AudioPlayerProvider cascade).
|
||||
// It is an explicit [Parameter], NOT a [CascadingParameter], because this control renders inside the
|
||||
// MudMenu panel, which MudBlazor portals to <MudPopoverProvider> — outside AudioPlayerProvider's cascade
|
||||
// scope, so a cascade would land null here. Null during prerender or when no provider is present — Apply
|
||||
// then just persists the preference, with no live track to restart.
|
||||
[Parameter] public IStreamingPlayerService? Player { get; set; }
|
||||
|
||||
private StreamQuality _quality;
|
||||
|
||||
public bool IsApplyEnabled => _quality != Settings.StreamQuality;
|
||||
|
||||
// Null until the capability probe runs (post-render JS interop). false → can decode Opus; true → cannot.
|
||||
private bool _opusUnavailable;
|
||||
|
||||
@@ -60,6 +77,21 @@
|
||||
private async Task OnQualityChanged(StreamQuality quality)
|
||||
{
|
||||
_quality = quality;
|
||||
await CookieService.SetStreamQualityAsync(quality);
|
||||
}
|
||||
|
||||
private async Task ApplyStreamQualitySetting(MouseEventArgs arg)
|
||||
{
|
||||
// Persist the choice first so the cookie + in-memory PublicSiteSettings.StreamQuality both reflect
|
||||
// the new value BEFORE the restart: the reload re-resolves the delivery format via
|
||||
// PreferenceAwareStreamingPlayerService, which reads PublicSiteSettings.StreamQuality fresh.
|
||||
await CookieService.SetStreamQualityAsync(_quality);
|
||||
|
||||
// Switch the currently-playing track to the new format immediately, preserving the listener's
|
||||
// position. No-op inside the player when nothing is loaded — the new preference then simply
|
||||
// applies to the next track played. Fire-and-forget: the reload runs the streaming loop for the
|
||||
// life of the track, so awaiting it would pin this handler open; UI updates flow through the
|
||||
// player's state notifications, not this await.
|
||||
_ = Player?.ReloadPreservingPositionAsync();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user