feature: Track Meta Labels on Player

This commit is contained in:
daniel-c-harvey
2026-06-06 16:05:45 -04:00
parent c83b132522
commit 1bb6e29e47
9 changed files with 123 additions and 23 deletions
@@ -19,14 +19,15 @@ else
IsLoading="IsLoading"
IsStreaming="IsStreaming"
LoadProgress="LoadProgress"
DisplayTime="DisplayTime"
Duration="Duration"
TogglePlayPause="@TogglePlayPause"
Stop="@Stop"
Class="transport-zone"/>
<VolumeZone Volume="@Volume" VolumeChanged="@OnVolumeChange"/>
<PlayerSeekZone DisplayTime="DisplayTime"
Duration="Duration"
<PlayerSeekZone CurrentTrack="CurrentTrack"
OnSeekStart="@OnSeekStart"
OnSeekEnd="@OnSeekEnd"
OnSeekChange="@OnSeekChange"
@@ -1,3 +1,4 @@
using DeepDrftModels.DTOs;
using DeepDrftPublic.Client.Services;
using Microsoft.AspNetCore.Components;
using MudBlazor;
@@ -28,6 +29,7 @@ public partial class AudioPlayerBar : ComponentBase, IAsyncDisposable
private bool IsStreaming => PlayerService?.CanStartStreaming ?? false;
private bool IsStreamingMode => PlayerService?.IsStreamingMode ?? false;
private double? Duration => PlayerService?.Duration;
private TrackDto? CurrentTrack => PlayerService?.CurrentTrack;
private double Volume => PlayerService?.Volume ?? 0;
private double LoadProgress => PlayerService?.LoadProgress ?? 0;
private string? ErrorMessage => PlayerService?.ErrorMessage;
@@ -1,9 +1,9 @@
@namespace DeepDrftPublic.Client.Controls.AudioPlayerBar
<MudStack Row="false" Spacing="0" Class="@Class">
<MudStack Row="false" Spacing="1" Class="@Class">
<WaveformSeeker OnSeekStart="OnSeekStart"
OnSeekChange="OnSeekChange"
OnSeekEnd="OnSeekEnd"
Class="seek-waveform"/>
<TimestampLabel CurrentTime="DisplayTime" Duration="@Duration"/>
<TrackMetaLabel Track="CurrentTrack"/>
</MudStack>
@@ -1,17 +1,17 @@
using DeepDrftModels.DTOs;
using Microsoft.AspNetCore.Components;
namespace DeepDrftPublic.Client.Controls.AudioPlayerBar;
/// <summary>
/// Centre zone of the player: the <see cref="WaveformSeeker"/> over a timestamp label.
/// The seeker owns the pointer-gesture seek logic and reads playback state off the cascaded
/// player service directly; this zone just forwards the seek callbacks up to
/// <see cref="AudioPlayerBar"/> (whose wiring is unchanged) and renders the timestamp.
/// Centre zone of the player: the <see cref="WaveformSeeker"/> over the now-playing metadata row
/// (<see cref="TrackMetaLabel"/>). The seeker owns the pointer-gesture seek logic and reads playback
/// state off the cascaded player service directly; this zone just forwards the seek callbacks up to
/// <see cref="AudioPlayerBar"/> (whose wiring is unchanged) and renders the current track's metadata.
/// </summary>
public partial class PlayerSeekZone : ComponentBase
{
[Parameter] public double DisplayTime { get; set; }
[Parameter] public double? Duration { get; set; }
[Parameter] public TrackDto? CurrentTrack { get; set; }
[Parameter] public EventCallback OnSeekStart { get; set; }
[Parameter] public EventCallback<double> OnSeekEnd { get; set; }
[Parameter] public EventCallback<double> OnSeekChange { get; set; }
@@ -1,16 +1,19 @@
@namespace DeepDrftPublic.Client.Controls.AudioPlayerBar
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1" Class="@Class">
<PlayerControls IsLoaded="IsLoaded"
CanPlay="CanPlay"
TogglePlayPause="TogglePlayPause"
Stop="Stop"/>
@if (IsLoading && !IsStreaming)
{
<MudProgressCircular Color="Color.Tertiary"
Size="Size.Small"
Max="1D"
Value="@LoadProgress"
Indeterminate="@(LoadProgress == 0)"/>
}
<MudStack Row="false" AlignItems="AlignItems.Center" Spacing="1" Class="@Class">
<MudStack Row="true" AlignItems="AlignItems.Center" Spacing="1">
<PlayerControls IsLoaded="IsLoaded"
CanPlay="CanPlay"
TogglePlayPause="TogglePlayPause"
Stop="Stop"/>
@if (IsLoading && !IsStreaming)
{
<MudProgressCircular Color="Color.Tertiary"
Size="Size.Small"
Max="1D"
Value="@LoadProgress"
Indeterminate="@(LoadProgress == 0)"/>
}
</MudStack>
<TimestampLabel CurrentTime="DisplayTime" Duration="@Duration"/>
</MudStack>
@@ -9,6 +9,8 @@ public partial class PlayerTransportZone : ComponentBase
[Parameter] public bool IsLoading { get; set; }
[Parameter] public bool IsStreaming { get; set; }
[Parameter] public double LoadProgress { get; set; }
[Parameter] public double DisplayTime { get; set; }
[Parameter] public double? Duration { get; set; }
[Parameter] public EventCallback TogglePlayPause { get; set; }
[Parameter] public EventCallback Stop { get; set; }
[Parameter] public string? Class { get; set; }
@@ -0,0 +1,36 @@
@namespace DeepDrftPublic.Client.Controls.AudioPlayerBar
@if (Track is not null)
{
<div class="track-meta-row">
<div class="track-meta-identity">
<MudText Typo="Typo.subtitle2" Class="track-meta-title text-truncate">
@Track.TrackName
</MudText>
<MudText Typo="Typo.subtitle2"> - </MudText>
<MudText Typo="Typo.caption" Class="track-meta-artist text-truncate">
@Track.Artist
</MudText>
</div>
<div class="track-meta-accents">
@if (!string.IsNullOrEmpty(Track.Genre))
{
<MudChip T="string"
Size="Size.Small"
Variant="Variant.Outlined"
Color="Color.Tertiary"
Class="deepdrft-genre-chip">
@Track.Genre
</MudChip>
}
@if (Track.ReleaseDate.HasValue)
{
<MudText Typo="Typo.caption" Class="track-meta-year">
@Track.ReleaseDate.Value.Year
</MudText>
}
</div>
</div>
}
@@ -0,0 +1,14 @@
using DeepDrftModels.DTOs;
using Microsoft.AspNetCore.Components;
namespace DeepDrftPublic.Client.Controls.AudioPlayerBar;
/// <summary>
/// The now-playing metadata row beneath the <see cref="WaveformSeeker"/>: track title + artist on
/// the left, genre chip + release year on the right. Reads nothing from the player service itself —
/// the current <see cref="TrackDto"/> is passed in by <see cref="PlayerSeekZone"/>.
/// </summary>
public partial class TrackMetaLabel : ComponentBase
{
[Parameter] public TrackDto? Track { get; set; }
}
@@ -0,0 +1,42 @@
/* Single space-between row under the waveform: identity on the left, accents on the right.
Colours come from the MudBlazor theme (the dock surface is theme-aware), so unlike the
always-dark TrackCard glass we do not hard-code green-accent overrides here. */
.track-meta-row {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
width: 100%;
padding: 2px 4px 0;
}
/* Left group shrinks and truncates so a long title never pushes the chip off-screen. */
.track-meta-identity {
display: flex;
align-items: baseline;
gap: 8px;
min-width: 0;
flex: 1 1 auto;
}
::deep .track-meta-title {
font-family: var(--deepdrft-font-mono);
min-width: 0;
}
::deep .track-meta-artist {
opacity: 0.75;
min-width: 0;
}
/* Right group keeps its natural size and never shrinks. */
.track-meta-accents {
display: flex;
align-items: center;
gap: 8px;
flex: 0 0 auto;
}
::deep .track-meta-year {
opacity: 0.75;
}