Restore IsStreamingMode on recovery; guard superseded-load else-branch
RecoverFromFailedRefill now sets IsStreamingMode=true so the in-place seek-retry route isn't wedged. The generic-catch unload path is gated on the loadCts identity, so a superseded load no longer clobbers a newer operation's state.
This commit is contained in:
@@ -295,15 +295,22 @@ public class StreamingAudioPlayerService : AudioPlayerService, IStreamingPlayerS
|
||||
{
|
||||
await RecoverFromFailedRefill(CurrentTime, userError);
|
||||
}
|
||||
else
|
||||
else if (ReferenceEquals(_streamingCancellation, loadCts))
|
||||
{
|
||||
// First-segment failure (nothing buffered / playing yet) or superseded load: the
|
||||
// normal unload-to-error path is correct — no buffered tail to halt.
|
||||
// First-segment failure (nothing buffered / playing yet), still the active operation:
|
||||
// the normal unload-to-error path is correct — nothing is in the scheduler to halt.
|
||||
ErrorMessage = userError;
|
||||
LoadProgress = 0;
|
||||
IsLoaded = false;
|
||||
IsStreamingMode = false;
|
||||
}
|
||||
else
|
||||
{
|
||||
// Superseded load: a newer seek (or track switch) has already claimed _streamingCancellation
|
||||
// and owns all shared state. Writing IsLoaded/IsStreamingMode here would corrupt the live
|
||||
// operation — mirror the OCE catch's identity guard and do nothing to shared state.
|
||||
_logger.LogDebug("Generic throw on superseded load for track {TrackId} — newer operation owns state, skipping unload", track.EntryKey);
|
||||
}
|
||||
}
|
||||
finally
|
||||
{
|
||||
@@ -879,13 +886,16 @@ public class StreamingAudioPlayerService : AudioPlayerService, IStreamingPlayerS
|
||||
_logger.LogWarning("Refill-failure recovery interop did not succeed: {Error}", recovered.Error);
|
||||
}
|
||||
|
||||
// Settle C# into the matching recoverable state: not playing, paused at the target, still loaded.
|
||||
// IsLoaded = true is load-bearing — the "paused-but-loaded" contract lets the listener retry
|
||||
// the seek or pick another track; resetting to unloaded would evict the track identity.
|
||||
// Settle C# into the matching recoverable state: not playing, paused at the target, still loaded
|
||||
// and still in streaming mode. IsLoaded = true and IsStreamingMode = true are both load-bearing —
|
||||
// the "paused-but-loaded" contract lets the listener retry the seek (Seek early-returns when
|
||||
// !IsLoaded || !IsStreamingMode), resume via TogglePlayPause, or pick another track. Resetting
|
||||
// either to false would wedge at least one of the three retry routes (AC6 / Phase 21.3).
|
||||
ErrorMessage = userFacingError;
|
||||
IsPlaying = false;
|
||||
IsPaused = true;
|
||||
IsLoaded = true;
|
||||
IsStreamingMode = true;
|
||||
CurrentTime = seekPosition;
|
||||
IsSeekingBeyondBuffer = false;
|
||||
await NotifyStateChanged();
|
||||
|
||||
@@ -353,6 +353,8 @@ public class SegmentedStreamLoopTests
|
||||
"a truncated segment while cursor < totalLength is a failure: scheduler must be halted via recovery");
|
||||
Assert.That(player.IsLoaded, Is.True,
|
||||
"recovery leaves the track loaded so the listener can retry — not torn down to unloaded");
|
||||
Assert.That(player.IsStreamingMode, Is.True,
|
||||
"recovery must restore IsStreamingMode=true so Seek() is not wedged (AC6 / Phase 21.3 retry contract)");
|
||||
Assert.That(player.IsPaused, Is.True,
|
||||
"recovery settles into a paused state, not playing");
|
||||
Assert.That(player.ErrorMessage, Is.Not.Null.And.Not.Empty,
|
||||
@@ -381,6 +383,8 @@ public class SegmentedStreamLoopTests
|
||||
"a mid-stream fetch failure must halt the scheduler via recovery, not leave it to drain");
|
||||
Assert.That(player.IsLoaded, Is.True,
|
||||
"recovery leaves the track loaded so the listener can retry — not torn down to unloaded");
|
||||
Assert.That(player.IsStreamingMode, Is.True,
|
||||
"recovery must restore IsStreamingMode=true so Seek() is not wedged (AC6 / Phase 21.3 retry contract)");
|
||||
Assert.That(player.IsPaused, Is.True,
|
||||
"recovery settles into a paused state, not playing");
|
||||
Assert.That(player.ErrorMessage, Is.Not.Null.And.Not.Empty,
|
||||
|
||||
Reference in New Issue
Block a user