TRAX: error display, retain track on failure, gray idle dot
1. Error text shows in red inside the desktop audio meter bar (scrollable) when TRAX load or playback fails — user gets feedback, not just a silent red dot. 2. LoadTrax no longer nulls _music on error — the track path is retained so the user sees what failed and can retry. 3. OnTraxPlaybackEnded now receives the NAudio exception and surfaces it. 4. TRAX dot is green (playing) or gray (idle/no track) — no more yellow or red states. 207 tests, 0 warnings.
This commit is contained in:
@@ -924,6 +924,12 @@
|
|||||||
VerticalAlignment="Stretch"/>
|
VerticalAlignment="Stretch"/>
|
||||||
<Rectangle Width="2" Fill="#00000080" HorizontalAlignment="Left" Margin="230,0,0,0"
|
<Rectangle Width="2" Fill="#00000080" HorizontalAlignment="Left" Margin="230,0,0,0"
|
||||||
VerticalAlignment="Stretch"/>
|
VerticalAlignment="Stretch"/>
|
||||||
|
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled"
|
||||||
|
Visibility="{Binding TraxError, Converter={StaticResource NotNullToVis}}"
|
||||||
|
VerticalAlignment="Center" Margin="4,0">
|
||||||
|
<TextBlock Text="{Binding TraxError}" Foreground="#e94560" FontSize="9"
|
||||||
|
FontWeight="SemiBold" VerticalAlignment="Center"/>
|
||||||
|
</ScrollViewer>
|
||||||
</Grid>
|
</Grid>
|
||||||
</Border>
|
</Border>
|
||||||
<Grid Width="16" Height="16" Cursor="Hand" Margin="0,0,8,0"
|
<Grid Width="16" Height="16" Cursor="Hand" Margin="0,0,8,0"
|
||||||
|
|||||||
@@ -26,7 +26,7 @@ public sealed class MusicPlayer : IDisposable
|
|||||||
private float _localGain = 1f;
|
private float _localGain = 1f;
|
||||||
|
|
||||||
/// <summary>Raised when playback ends or is stopped (not on natural-end loop).</summary>
|
/// <summary>Raised when playback ends or is stopped (not on natural-end loop).</summary>
|
||||||
public event Action? PlaybackEnded;
|
public event Action<Exception?>? PlaybackEnded;
|
||||||
|
|
||||||
public string? TrackPath { get; private set; }
|
public string? TrackPath { get; private set; }
|
||||||
|
|
||||||
@@ -122,6 +122,6 @@ public sealed class MusicPlayer : IDisposable
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
PlaybackEnded?.Invoke();
|
PlaybackEnded?.Invoke(e.Exception);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -476,6 +476,7 @@ public class MainViewModel : ViewModelBase
|
|||||||
// ─── Mic status dot (the MIC button) ───
|
// ─── Mic status dot (the MIC button) ───
|
||||||
|
|
||||||
private static readonly SolidColorBrush MicProblemBrush = CreateBrush("#f1c40f");
|
private static readonly SolidColorBrush MicProblemBrush = CreateBrush("#f1c40f");
|
||||||
|
private static readonly SolidColorBrush TraxIdleBrush = CreateBrush("#555");
|
||||||
|
|
||||||
/// <summary>Mic connection state, driven by the mixer's MicConnected/MicFailed
|
/// <summary>Mic connection state, driven by the mixer's MicConnected/MicFailed
|
||||||
/// events and the startup device check (see StartMicCaptureAsync).</summary>
|
/// events and the startup device check (see StartMicCaptureAsync).</summary>
|
||||||
@@ -525,12 +526,19 @@ public class MainViewModel : ViewModelBase
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>TRAX status dot: green = playing, yellow = loaded/stopped, red = no track.</summary>
|
private string? _traxError;
|
||||||
|
|
||||||
|
/// <summary>Error message from the last TRAX load/playback failure; null = no error.</summary>
|
||||||
|
public string? TraxError
|
||||||
|
{
|
||||||
|
get => _traxError;
|
||||||
|
private set => SetProperty(ref _traxError, value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>TRAX status dot: green = playing, gray = idle or no track.</summary>
|
||||||
public SolidColorBrush TraxDotBrush => _musicPlayer.IsPlaying
|
public SolidColorBrush TraxDotBrush => _musicPlayer.IsPlaying
|
||||||
? BarOnBrush
|
? BarOnBrush
|
||||||
: _music != null && !string.IsNullOrWhiteSpace(_music.TrackPath)
|
: TraxIdleBrush;
|
||||||
? MicProblemBrush
|
|
||||||
: BarOffBrush;
|
|
||||||
|
|
||||||
public string TraxToolTip => _musicPlayer.TrackName != null
|
public string TraxToolTip => _musicPlayer.TrackName != null
|
||||||
? $"TRAX: {_musicPlayer.TrackName} — left-click {(IsMusicPlaying ? "pauses" : "plays")}, right-click chooses a track"
|
? $"TRAX: {_musicPlayer.TrackName} — left-click {(IsMusicPlaying ? "pauses" : "plays")}, right-click chooses a track"
|
||||||
@@ -574,13 +582,14 @@ public class MainViewModel : ViewModelBase
|
|||||||
_musicPlayer.Load(_music.TrackPath);
|
_musicPlayer.Load(_music.TrackPath);
|
||||||
UpdateTraxLocalGain();
|
UpdateTraxLocalGain();
|
||||||
_music.IsEnabled = true;
|
_music.IsEnabled = true;
|
||||||
|
TraxError = null;
|
||||||
if (play)
|
if (play)
|
||||||
_musicPlayer.Play();
|
_musicPlayer.Play();
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
AppLog.Write($"TRAX: failed to load '{_music.TrackPath}': {ex.Message}");
|
AppLog.Write($"TRAX: failed to load '{_music.TrackPath}': {ex.Message}");
|
||||||
_music = null;
|
TraxError = $"Failed to load: {Path.GetFileName(_music.TrackPath)} — {ex.Message}";
|
||||||
}
|
}
|
||||||
RefreshTraxUi();
|
RefreshTraxUi();
|
||||||
ScheduleSave();
|
ScheduleSave();
|
||||||
@@ -590,10 +599,13 @@ public class MainViewModel : ViewModelBase
|
|||||||
|
|
||||||
private void PauseTrax() => _musicPlayer.Pause();
|
private void PauseTrax() => _musicPlayer.Pause();
|
||||||
|
|
||||||
private void OnTraxPlaybackEnded()
|
private void OnTraxPlaybackEnded(Exception? ex)
|
||||||
{
|
{
|
||||||
// Natural end loops inside the player; here only an explicit stop or a
|
if (ex != null)
|
||||||
// device failure lands — flip the dot back to loaded/stopped.
|
{
|
||||||
|
AppLog.Write($"TRAX: playback error: {ex.Message}");
|
||||||
|
TraxError = $"Playback error: {ex.Message}";
|
||||||
|
}
|
||||||
RefreshTraxUi();
|
RefreshTraxUi();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user