@using Microsoft.JSInterop @inject IAudioPlayerService AudioPlayerService @inject IJSRuntime JS @inject TokenStorage TokenStorage @inject PlayerStorage PlayerStorage @inject AuthenticationStateProvider AuthProvider @inject ISnackbar Snackbar @inject HttpClient Http @if (!string.IsNullOrEmpty(_currentTrackCoverUrl)) { } @_currentTrackTitle @_currentTime / @_totalTime @code { private const double _defaultVolume = 50; private string _audioId = $"audio_{Guid.NewGuid():N}"; private IJSObjectReference? _audioModule; private IJSObjectReference? _audioElement; private double _currentProgress; private double _currentVolume = _defaultVolume; private string _currentTime = "0:00"; private string _totalTime = "0:00"; private bool _isPlaying; private Timer? _progressTimer; private bool _isMuted; private string? _currentAccessToken; private string? _currentSharedPlaylistId; private string? _currentTrackCoverUrl; private string? _currentTrackTitle; protected override async Task OnInitializedAsync() { AudioPlayerService.OnLoadAndPlayRequested += OnLoadAndPlay; AudioPlayerService.OnPlayRequested += OnPlay; AudioPlayerService.OnPauseRequested += OnPause; AudioPlayerService.OnStopRequested += OnStop; AudioPlayerService.OnSeekRequested += OnSeek; AudioPlayerService.OnVolumeChangeRequested += OnVolumeChange; AudioPlayerService.OnStateChanged += OnStateChanged; await LoadSavedVolume(); await AudioPlayerService.SetVolumeAsync(_currentVolume); // синхронизация } protected override async Task OnAfterRenderAsync(bool firstRender) { if (firstRender) { await EnsureAudioModuleAsync(); } } private void OnStateChanged() { _currentTrackTitle = AudioPlayerService.CurrentTrackTitle; _currentTrackCoverUrl = AudioPlayerService.CurrentTrackCoverUrl?.FormatCoverUrl(40, 40); InvokeAsync(StateHasChanged); } private async Task LoadSavedVolume() { var savedVolume = await PlayerStorage.GetVolumeAsync() ?? _defaultVolume; _currentVolume = savedVolume; await AudioPlayerService.SetVolumeAsync(savedVolume); } private async Task EnsureAudioModuleAsync() { if (_audioModule == null) _audioModule = await JS.InvokeAsync("import", "/js/AudioPlayer.js"); if (_audioElement == null) _audioElement = await _audioModule.InvokeAsync("init", _audioId, DotNetObjectReference.Create(this)); } [JSInvokable] public async Task OnAudioEnded() { AudioPlayerService.NotifyTrackEnded(); _isPlaying = false; _currentProgress = 0; StopProgressTimer(); StateHasChanged(); } [JSInvokable] public async Task OnTimeUpdate(double currentTime, double duration) { if (double.IsNaN(currentTime) || double.IsNaN(duration) || duration <= 0) return; var progress = (currentTime / duration) * 100; var currentTimeStr = FormatTime(currentTime); var totalTimeStr = FormatTime(duration); AudioPlayerService.UpdateProgress(progress, currentTimeStr, totalTimeStr); _currentProgress = progress; _currentTime = currentTimeStr; _totalTime = totalTimeStr; await InvokeAsync(StateHasChanged); } [JSInvokable] public async Task OnDurationReady(double duration) { if (duration <= 0) return; _totalTime = FormatTime(duration); await InvokeAsync(StateHasChanged); } private async Task CheckAuthAsync() { var authState = await AuthProvider.GetAuthenticationStateAsync(); if (!authState.User.Identity?.IsAuthenticated == true) { Snackbar.Add("Воспроизведение доступно только авторизованным пользователям", Severity.Warning); return false; } return true; } private async Task OnLoadAndPlay(string trackId, string? accessToken, string? sharedPlaylistId) { //if (!await CheckAuthAsync()) return; var tokens = await TokenStorage.GetTokensAsync(); _currentAccessToken = accessToken ?? tokens.token; _currentSharedPlaylistId = sharedPlaylistId; if (string.IsNullOrWhiteSpace(_currentAccessToken) && string.IsNullOrWhiteSpace(_currentSharedPlaylistId)) { Snackbar.Add("Токен авторизации не найден. Пожалуйста, войдите заново.", Severity.Error); return; } var streamUrl = new Uri(Http.BaseAddress!, $"/api/audio/track/{trackId}").ToString(); await EnsureAudioModuleAsync(); await _audioElement!.InvokeVoidAsync("loadAndPlay", streamUrl, _currentAccessToken, _currentSharedPlaylistId); _isPlaying = true; StartProgressTimer(); StateHasChanged(); } private async Task OnPlay() { if (_audioElement == null) return; await _audioElement.InvokeVoidAsync("play"); _isPlaying = true; StartProgressTimer(); StateHasChanged(); } private async Task OnPause() { if (_audioElement == null) return; await _audioElement.InvokeVoidAsync("pause"); _isPlaying = false; StopProgressTimer(); StateHasChanged(); } private async Task OnStop() { if (_audioElement == null) return; await _audioElement.InvokeVoidAsync("stop"); _isPlaying = false; _currentProgress = 0; StopProgressTimer(); StateHasChanged(); } private async Task OnSeek(double percent) { if (_audioElement == null) return; try { var duration = await _audioElement.InvokeAsync("getDuration"); if (duration > 0 && !double.IsNaN(duration)) { var newTime = (percent / 100) * duration; await _audioElement.InvokeVoidAsync("setCurrentTime", newTime); } } catch (Exception ex) { Console.WriteLine($"Seek error: {ex.Message}"); } } private async Task OnVolumeChange(double volume) { if (_audioElement == null) return; try { await _audioElement.InvokeVoidAsync("setVolume", volume / 100); _isMuted = false; _currentVolume = volume; await PlayerStorage.SetVolumeAsync(volume); StateHasChanged(); } catch (Exception ex) { Console.WriteLine($"Volume change error: {ex.Message}"); } } private async Task TogglePlayPause() { if (_isPlaying) await AudioPlayerService.PauseAsync(); else await AudioPlayerService.PlayAsync(); } private async Task Stop() { await AudioPlayerService.StopAsync(); } private async Task SeekTo(double value) { await AudioPlayerService.SeekToAsync(value); } private async Task ChangeVolume(double value) { await AudioPlayerService.SetVolumeAsync(value); } private async Task ToggleMute() { _isMuted = !_isMuted; var newVolume = _isMuted ? 0 : _currentVolume; await AudioPlayerService.SetVolumeAsync(newVolume); } private void StartProgressTimer() { StopProgressTimer(); _progressTimer = new Timer(async _ => await UpdateProgress(), null, 0, 500); } private void StopProgressTimer() => _progressTimer?.Dispose(); private async Task UpdateProgress() { if (_audioElement == null) return; try { var current = await _audioElement.InvokeAsync("getCurrentTime"); var duration = await _audioElement.InvokeAsync("getDuration"); if (duration > 0 && !double.IsNaN(duration) && !double.IsNaN(current)) { var progress = (current / duration) * 100; var currentTime = FormatTime(current); var totalTime = FormatTime(duration); AudioPlayerService.UpdateProgress(progress, currentTime, totalTime); _currentProgress = progress; _currentTime = currentTime; _totalTime = totalTime; await InvokeAsync(StateHasChanged); } } catch (Exception ex) { Console.WriteLine($"UpdateProgress error: {ex.Message}"); } } private string FormatTime(double seconds) { var total = (int)seconds; var mins = total / 60; var secs = total % 60; return $"{mins}:{secs:D2}"; } public async ValueTask DisposeAsync() { try { StopProgressTimer(); if (_audioElement != null) await _audioElement.DisposeAsync(); if (_audioModule != null) await _audioModule.DisposeAsync(); } catch { } } }