Consolidate play/pause icon logic into PlaybackIcons mapper and PlayStateIcon component

Add Disabled parameter to PlayStateIcon; forward to MudIconButton;
pass Disabled="!IsLoaded" from PlayerControls to match Stop button parity.
This commit is contained in:
daniel-c-harvey
2026-06-06 10:46:32 -04:00
parent 1d97729e57
commit e3fe401abf
9 changed files with 93 additions and 19 deletions
@@ -13,8 +13,7 @@ else
<MudPaper Elevation="8" Class="player-surface pa-3">
<div class="player-layout">
<PlayerTransportZone IsPlaying="IsPlaying"
IsLoaded="IsLoaded"
<PlayerTransportZone IsLoaded="IsLoaded"
IsLoading="IsLoading"
IsStreaming="IsStreaming"
LoadProgress="LoadProgress"
@@ -17,8 +17,6 @@ public partial class AudioPlayerBar : ComponentBase, IAsyncDisposable
private bool IsLoading => PlayerService?.IsLoading ?? false;
private bool IsStreaming => PlayerService?.CanStartStreaming ?? false;
private bool IsStreamingMode => PlayerService?.IsStreamingMode ?? false;
private bool IsPlaying => PlayerService?.IsPlaying ?? false;
private bool IsPaused => PlayerService?.IsPaused ?? false;
private double? Duration => PlayerService?.Duration;
private double Volume => PlayerService?.Volume ?? 0;
private double LoadProgress => PlayerService?.LoadProgress ?? 0;
@@ -1,9 +1,11 @@
@namespace DeepDrftPublic.Client.Controls.AudioPlayerBar
@using DeepDrftPublic.Client.Controls
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="2">
<MudIconButton Icon="@GetPlayIcon()"
<PlayStateIcon Size="Size.Large"
Color="Color.Primary"
Size="Size.Large"
OnClick="@TogglePlayPause"
Disabled="!IsLoaded"/>
Disabled="!IsLoaded"
OnToggle="@TogglePlayPause"/>
<MudIconButton Icon="@Icons.Material.Filled.Stop"
Color="Color.Primary"
Size="Size.Large"
@@ -1,16 +1,10 @@
using Microsoft.AspNetCore.Components;
using MudBlazor;
using Microsoft.AspNetCore.Components;
namespace DeepDrftPublic.Client.Controls.AudioPlayerBar;
public partial class PlayerControls : ComponentBase
{
[Parameter] public required bool IsPlaying { get; set; }
[Parameter] public required bool IsLoaded { get; set; }
[Parameter] public required EventCallback TogglePlayPause { get; set; }
[Parameter] public required EventCallback Stop { get; set; }
private string GetPlayIcon()
{
return IsPlaying ? Icons.Material.Filled.Pause : Icons.Material.Filled.PlayArrow;
}
}
@@ -1,8 +1,7 @@
@namespace DeepDrftPublic.Client.Controls.AudioPlayerBar
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1" Class="@Class">
<PlayerControls IsPlaying="IsPlaying"
IsLoaded="IsLoaded"
<PlayerControls IsLoaded="IsLoaded"
TogglePlayPause="TogglePlayPause"
Stop="Stop"/>
@if (IsLoading && !IsStreaming)
@@ -4,7 +4,6 @@ namespace DeepDrftPublic.Client.Controls.AudioPlayerBar;
public partial class PlayerTransportZone : ComponentBase
{
[Parameter] public bool IsPlaying { get; set; }
[Parameter] public bool IsLoaded { get; set; }
[Parameter] public bool IsLoading { get; set; }
[Parameter] public bool IsStreaming { get; set; }
@@ -0,0 +1,7 @@
@namespace DeepDrftPublic.Client.Controls
<MudIconButton Icon="@Icon"
Color="Color"
Size="Size"
Disabled="@Disabled"
OnClick="@OnToggle"/>
@@ -0,0 +1,62 @@
using DeepDrftModels.DTOs;
using DeepDrftPublic.Client.Helpers;
using DeepDrftPublic.Client.Services;
using Microsoft.AspNetCore.Components;
using MudBlazor;
namespace DeepDrftPublic.Client.Controls;
/// <summary>
/// Renders the play/pause transport glyph for either a specific track or global playback,
/// reading live state off the cascaded <see cref="IStreamingPlayerService"/>. The icon is
/// always resolved through <see cref="PlaybackIcons.Resolve"/>.
/// </summary>
public partial class PlayStateIcon : ComponentBase, IDisposable
{
[CascadingParameter] public IStreamingPlayerService? PlayerService { get; set; }
/// <summary>
/// When non-null, the icon reflects this track's playback state (active only when it is the
/// current track). When null, it reflects global playback state.
/// </summary>
[Parameter] public TrackDto? Track { get; set; }
[Parameter] public Size Size { get; set; } = Size.Medium;
[Parameter] public Color Color { get; set; } = Color.Primary;
[Parameter] public bool Disabled { get; set; } = false;
[Parameter] public EventCallback OnToggle { get; set; }
private IStreamingPlayerService? _subscribedService;
private bool IsActive => Track is null || PlayerService?.CurrentTrack?.Id == Track.Id;
private bool IsPlaying => IsActive && (PlayerService?.IsPlaying ?? false);
private bool IsPaused => IsActive && (PlayerService?.IsPaused ?? false);
private string Icon => PlaybackIcons.Resolve(IsPlaying, IsPaused);
protected override void OnParametersSet()
{
// The cascade is IsFixed, so the provider's re-render never reaches us; subscribe to the
// multicast side-channel to re-render on every player state change. Reference-guarded so
// re-parametering is idempotent. Mirrors AudioPlayerBar / TracksView.
if (PlayerService != null && !ReferenceEquals(PlayerService, _subscribedService))
{
if (_subscribedService != null)
_subscribedService.StateChanged -= OnPlayerStateChanged;
PlayerService.StateChanged += OnPlayerStateChanged;
_subscribedService = PlayerService;
}
}
private void OnPlayerStateChanged() => InvokeAsync(StateHasChanged);
public void Dispose()
{
if (_subscribedService != null)
{
_subscribedService.StateChanged -= OnPlayerStateChanged;
_subscribedService = null;
}
}
}
@@ -0,0 +1,14 @@
using MudBlazor;
namespace DeepDrftPublic.Client.Helpers;
/// <summary>
/// Single source of truth for mapping playback state to a transport glyph across
/// DeepDrftPublic.Client. Surfaces that render a play/pause icon call <see cref="Resolve"/>
/// instead of inlining their own ternary.
/// </summary>
public static class PlaybackIcons
{
public static string Resolve(bool isPlaying, bool isPaused)
=> (isPlaying && !isPaused) ? Icons.Material.Filled.Pause : Icons.Material.Filled.PlayArrow;
}