using MudBlazor; using PlaylistShared.Shared; using PlaylistShared.Shared.DTO; using System.Net.Http.Json; namespace PlaylistShared.Pwa.Services; public class AudioPlayerService : IAudioPlayerService { private readonly TokenStorage _tokenStorage; private readonly ISnackbar _snackbar; private readonly HttpClient _http; private readonly PlayerStorage _playerStorage; private string? _currentTrackId; private YandexTrack? _currentTrack; private bool _isPlaying; private double _currentVolume = 50; private double _currentProgress; private double _currentTime = 0; private double _totalTime = 0; private string _currentTimeString = "0:00"; private string _totalTimeString = "0:00"; public string? CurrentTrackId => _currentTrackId; public YandexTrack? CurrentTrack => _currentTrack; public bool IsPlaying => _isPlaying; public double CurrentVolume { get => _currentVolume; set { _currentVolume = value; OnStateChanged?.Invoke(); } } public double CurrentProgress => _currentProgress; public double CurrentTime => _currentTime; public double TotalTime => _totalTime; public string CurrentTimeString => _currentTimeString; public string TotalTimeString => _totalTimeString; public event Action? OnStateChanged; public AudioPlayerService(TokenStorage tokenStorage, ISnackbar snackbar, HttpClient httpClient, PlayerStorage playerStorage) { _tokenStorage = tokenStorage; _snackbar = snackbar; _http = httpClient; _playerStorage = playerStorage; _ = LoadVolume(); } private async Task LoadVolume() { var savedVolume = await _playerStorage.GetVolumeAsync(); if (savedVolume != null) { _currentVolume = savedVolume.Value; } } // Внешние команды (вызываются из компонентов) public async Task LoadAndPlayAsync(string trackId, string? accessToken = null, string? playlistShareToken = null, YandexTrack? track = null) { if (_currentTrackId == trackId) { await PlayAsync(); return; } // Если accessToken не передан, пытаемся получить его из хранилища if (string.IsNullOrWhiteSpace(accessToken)) { var tokens = await _tokenStorage.GetTokensAsync(); accessToken = tokens.token; } // Проверяем, есть ли чем авторизоваться if (string.IsNullOrWhiteSpace(accessToken) && string.IsNullOrWhiteSpace(playlistShareToken)) { _snackbar.Add("Не удалось воспроизвести трек: отсутствует токен авторизации или идентификатор расшаренного плейлиста.", Severity.Error); return; } // Если title и coverUrl не переданы, нужно запросить через API if (track is null) { try { track = await GetTrackInfo(trackId, accessToken, playlistShareToken); } catch (Exception ex) { // Логируем ошибку, но продолжаем без обложки/названия Console.WriteLine($"Failed to fetch track info: {ex.Message}"); } } _currentTrack = track; _isPlaying = true; OnStateChanged?.Invoke(); OnLoadAndPlayRequested?.Invoke(trackId, accessToken, playlistShareToken); } public async Task PlayAsync() { _isPlaying = true; OnStateChanged?.Invoke(); OnPlayRequested?.Invoke(); } public async Task PauseAsync() { _isPlaying = false; OnStateChanged?.Invoke(); OnPauseRequested?.Invoke(); } public async Task SeekToAsync(double second) { OnSeekRequested?.Invoke(second); } public async Task SetVolumeAsync(double volume) { _currentVolume = volume; OnStateChanged?.Invoke(); OnVolumeChangeRequested?.Invoke(volume); await _playerStorage.SetVolumeAsync(volume); } // События для связи с реальным AudioPlayer компонентом public event Func? OnLoadAndPlayRequested; public event Func? OnPlayRequested; public event Func? OnPauseRequested; public event Func? OnSeekRequested; public event Func? OnVolumeChangeRequested; // Внутренние методы для обновления состояния из AudioPlayer public void SetPlayingState(bool isPlaying) { _isPlaying = isPlaying; OnStateChanged?.Invoke(); } public void SetCurrentTrack(string? trackId) { _currentTrackId = trackId; OnStateChanged?.Invoke(); } public void UpdateProgress(double currentTime, double totalTime) { var progress = (currentTime / totalTime) * 100; _currentProgress = progress; _currentTime = currentTime; _currentTimeString = FormatDuration(currentTime); _totalTime = totalTime; _totalTimeString = FormatDuration(totalTime); OnStateChanged?.Invoke(); } public void NotifyTrackEnded() { _isPlaying = false; _currentTrackId = null; _currentProgress = 0; _currentTime = 0; _currentTimeString = "0:00"; _totalTime = 0; _currentTimeString = "0:00"; OnStateChanged?.Invoke(); } /// /// Вспомогательный метод для получения информации о треке через API /// /// /// /// /// private async Task GetTrackInfo(string trackId, string? accessToken, string? sharedPlaylistId) { var url = $"/api/audio/track-info/{trackId}"; if (!string.IsNullOrEmpty(accessToken)) url += $"?access_token={accessToken}"; else if (!string.IsNullOrEmpty(sharedPlaylistId)) url += $"?shared_id={sharedPlaylistId}"; var response = await _http.GetFromJsonAsync>(url); if (response?.Success == true) { return response.Data; } return null; } private string FormatDuration(double seconds) { var mins = (int)(seconds / 60); var secs = (int)(seconds % 60); return $"{mins}:{secs:D2}"; } }