using DeepDrftModels.DTOs; using DeepDrftPublic.Client.Services; using DeepDrftPublic.Client.ViewModels; using Microsoft.AspNetCore.Components; namespace DeepDrftPublic.Client.Pages; public partial class TrackDetail : ComponentBase, IDisposable { private const string PersistKey = "track-detail"; [Parameter] public required string EntryKey { get; set; } [Inject] public required TrackDetailViewModel ViewModel { get; set; } [Inject] public required PersistentComponentState PersistentState { get; set; } [CascadingParameter] public required IStreamingPlayerService PlayerService { get; set; } private IStreamingPlayerService? _subscribedService; private PersistingComponentStateSubscription _persistingSubscription; protected override async Task OnInitializedAsync() { // Carry the prerendered track across the prerender -> interactive (WASM) seam. // Without this, the WASM pass gets a fresh scoped ViewModel, re-renders the // skeleton, and re-fetches. Mirror the TracksView bridge: persist on the way // out of prerender, restore on the interactive pass, and only fetch on a miss. _persistingSubscription = PersistentState.RegisterOnPersisting(PersistTrack); if (PersistentState.TryTakeFromJson(PersistKey, out var restored) && restored is not null) { ViewModel.Track = restored; ViewModel.IsLoading = false; } else { await ViewModel.Load(EntryKey); } } protected override void OnParametersSet() { // The play button's icon reads off the player's live state (CurrentTrack / // IsPlaying / IsPaused), which mutates outside this component's render path. // The cascade is IsFixed, so the provider's re-render never reaches us — // subscribe to the multicast side-channel and re-render on every state change. if (PlayerService != null && !ReferenceEquals(PlayerService, _subscribedService)) { if (_subscribedService != null) _subscribedService.StateChanged -= OnPlayerStateChanged; PlayerService.StateChanged += OnPlayerStateChanged; _subscribedService = PlayerService; } } private void OnPlayerStateChanged() => InvokeAsync(StateHasChanged); private Task PersistTrack() { if (ViewModel.Track is not null) { PersistentState.PersistAsJson(PersistKey, ViewModel.Track); } return Task.CompletedTask; } private async Task PlayTrack() { if (ViewModel.Track is null) return; var isThisTrack = PlayerService.CurrentTrack?.Id == ViewModel.Track.Id; // Toggle play/pause if this track is already the active one (playing or paused); // otherwise start a fresh stream. SelectTrackStreaming is the live entry point — // the buffered SelectTrack path is dead. if (isThisTrack && (PlayerService.IsPlaying || PlayerService.IsPaused)) { await PlayerService.TogglePlayPause(); } else { await PlayerService.SelectTrackStreaming(ViewModel.Track); } } public void Dispose() { _persistingSubscription.Dispose(); if (_subscribedService != null) { _subscribedService.StateChanged -= OnPlayerStateChanged; _subscribedService = null; } } }