Вынесен глобальный плеер
This commit is contained in:
@@ -1,6 +1,10 @@
|
||||
@using Microsoft.JSInterop
|
||||
@using Microsoft.AspNetCore.Components.Authorization
|
||||
@namespace PlaylistShared.Pwa.Components
|
||||
@inject IAudioPlayerService AudioPlayerService
|
||||
@inject IJSRuntime JS
|
||||
@inject TokenStorage TokenStorage
|
||||
@inject PlayerStorage PlayerStorage
|
||||
@inject AuthenticationStateProvider AuthProvider
|
||||
@inject ISnackbar Snackbar
|
||||
@inject HttpClient Http
|
||||
|
||||
<MudPaper Class="pa-4" Elevation="0" Style="background-color: rgba(0,0,0,0.05); border-radius: 8px;">
|
||||
@@ -56,63 +60,35 @@
|
||||
private bool _isPlaying;
|
||||
private Timer? _progressTimer;
|
||||
private bool _isMuted;
|
||||
private string? _currentAccessToken;
|
||||
private string? _currentSharedPlaylistId;
|
||||
|
||||
[Inject] protected IJSRuntime JS { get; set; } = null!;
|
||||
[Inject] private TokenStorage TokenStorage { get; set; } = null!;
|
||||
[Inject] private PlayerStorage PlayerStorage { get; set; } = null!;
|
||||
[Inject] private AuthenticationStateProvider AuthProvider { get; set; } = null!;
|
||||
[Inject] private ISnackbar Snackbar { get; set; } = null!;
|
||||
|
||||
/// <summary>Требовать ли авторизацию для воспроизведения (по умолчанию true).</summary>
|
||||
[Parameter] public bool RequireAuth { get; set; } = true;
|
||||
|
||||
/// <summary>ID расшаренного плейлиста.</summary>
|
||||
[Parameter] public string SharedPlaylistId { get; set; } = string.Empty;
|
||||
|
||||
/// <summary>Событие при завершении трека.</summary>
|
||||
[Parameter] public EventCallback OnTrackEnded { get; set; }
|
||||
protected override async Task OnInitializedAsync()
|
||||
{
|
||||
AudioPlayerService.OnLoadAndPlayRequested += OnLoadAndPlay;
|
||||
AudioPlayerService.OnPlayRequested += OnPlay;
|
||||
AudioPlayerService.OnPauseRequested += OnPause;
|
||||
AudioPlayerService.OnStopRequested += OnStop;
|
||||
AudioPlayerService.OnSeekRequested += OnSeek;
|
||||
AudioPlayerService.OnVolumeChangeRequested += OnVolumeChange;
|
||||
|
||||
await LoadSavedVolume();
|
||||
await AudioPlayerService.SetVolumeAsync(_currentVolume); // синхронизация
|
||||
}
|
||||
|
||||
protected override async Task OnAfterRenderAsync(bool firstRender)
|
||||
{
|
||||
if (firstRender)
|
||||
{
|
||||
await EnsureAudioModuleAsync();
|
||||
await ChangeVolume(await PlayerStorage.GetVolumeAsync());
|
||||
StateHasChanged();
|
||||
}
|
||||
}
|
||||
|
||||
[JSInvokable]
|
||||
public async Task OnAudioEnded()
|
||||
private async Task LoadSavedVolume()
|
||||
{
|
||||
_isPlaying = false;
|
||||
_currentProgress = 0;
|
||||
StopProgressTimer();
|
||||
if (OnTrackEnded.HasDelegate)
|
||||
await OnTrackEnded.InvokeAsync();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
[JSInvokable]
|
||||
public async Task OnTimeUpdate(double currentTime, double duration)
|
||||
{
|
||||
// Защита от некорректных значений
|
||||
if (double.IsNaN(currentTime) || double.IsNaN(duration) || double.IsInfinity(currentTime) || double.IsInfinity(duration))
|
||||
return;
|
||||
if (duration <= 0) return;
|
||||
|
||||
_currentProgress = (currentTime / duration) * 100;
|
||||
_currentTime = FormatTime(currentTime);
|
||||
// Длительность не обновляем здесь
|
||||
await InvokeAsync(StateHasChanged);
|
||||
}
|
||||
|
||||
[JSInvokable]
|
||||
public async Task OnDurationReady(double duration)
|
||||
{
|
||||
if (double.IsNaN(duration) || double.IsInfinity(duration) || duration <= 0) return;
|
||||
_totalTime = FormatTime(duration);
|
||||
await InvokeAsync(StateHasChanged);
|
||||
var savedVolume = await PlayerStorage.GetVolumeAsync();
|
||||
_currentVolume = savedVolume;
|
||||
await AudioPlayerService.SetVolumeAsync(savedVolume);
|
||||
}
|
||||
|
||||
private async Task EnsureAudioModuleAsync()
|
||||
@@ -123,14 +99,42 @@
|
||||
_audioElement = await _audioModule.InvokeAsync<IJSObjectReference>("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<bool> CheckAuthAsync()
|
||||
{
|
||||
if (!RequireAuth) return true;
|
||||
|
||||
var authState = await AuthProvider.GetAuthenticationStateAsync();
|
||||
var isAuthenticated = authState.User.Identity?.IsAuthenticated == true;
|
||||
|
||||
if (!isAuthenticated)
|
||||
if (!authState.User.Identity?.IsAuthenticated == true)
|
||||
{
|
||||
Snackbar.Add("Воспроизведение доступно только авторизованным пользователям", Severity.Warning);
|
||||
return false;
|
||||
@@ -138,32 +142,30 @@
|
||||
return true;
|
||||
}
|
||||
|
||||
public async Task LoadAndPlayAsync(string trackId)
|
||||
private async Task OnLoadAndPlay(string trackId, string? accessToken, string? sharedPlaylistId)
|
||||
{
|
||||
if (!await CheckAuthAsync()) return;
|
||||
|
||||
|
||||
var tokens = await TokenStorage.GetTokensAsync();
|
||||
var accessToken = tokens.token;
|
||||
|
||||
if (string.IsNullOrWhiteSpace(accessToken) && string.IsNullOrWhiteSpace(SharedPlaylistId))
|
||||
_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, accessToken, SharedPlaylistId);
|
||||
await _audioElement!.InvokeVoidAsync("loadAndPlay", streamUrl, _currentAccessToken, _currentSharedPlaylistId);
|
||||
_isPlaying = true;
|
||||
StartProgressTimer();
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
public async Task PlayAsync()
|
||||
private async Task OnPlay()
|
||||
{
|
||||
if (!await CheckAuthAsync()) return;
|
||||
|
||||
if (_audioElement == null) return;
|
||||
await _audioElement.InvokeVoidAsync("play");
|
||||
_isPlaying = true;
|
||||
@@ -171,7 +173,7 @@
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
public async Task PauseAsync()
|
||||
private async Task OnPause()
|
||||
{
|
||||
if (_audioElement == null) return;
|
||||
await _audioElement.InvokeVoidAsync("pause");
|
||||
@@ -180,7 +182,7 @@
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
public async Task StopAsync()
|
||||
private async Task OnStop()
|
||||
{
|
||||
if (_audioElement == null) return;
|
||||
await _audioElement.InvokeVoidAsync("stop");
|
||||
@@ -190,20 +192,7 @@
|
||||
StateHasChanged();
|
||||
}
|
||||
|
||||
private async Task TogglePlayPause()
|
||||
{
|
||||
if (_isPlaying)
|
||||
await PauseAsync();
|
||||
else
|
||||
await PlayAsync();
|
||||
}
|
||||
|
||||
private async Task Stop()
|
||||
{
|
||||
await StopAsync();
|
||||
}
|
||||
|
||||
private async Task SeekTo(double value)
|
||||
private async Task OnSeek(double percent)
|
||||
{
|
||||
if (_audioElement == null) return;
|
||||
try
|
||||
@@ -211,71 +200,87 @@
|
||||
var duration = await _audioElement.InvokeAsync<double>("getDuration");
|
||||
if (duration > 0 && !double.IsNaN(duration))
|
||||
{
|
||||
var newTime = (value / 100) * duration;
|
||||
var newTime = (percent / 100) * duration;
|
||||
await _audioElement.InvokeVoidAsync("setCurrentTime", newTime);
|
||||
}
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"SeekTo error: {ex.Message}");
|
||||
Console.WriteLine($"Seek error: {ex.Message}");
|
||||
}
|
||||
}
|
||||
|
||||
private async Task ChangeVolume(double value)
|
||||
private async Task OnVolumeChange(double volume)
|
||||
{
|
||||
if (_audioElement == null) return;
|
||||
try
|
||||
{
|
||||
var volume = value / 100;
|
||||
await _audioElement.InvokeVoidAsync("setVolume", volume);
|
||||
await _audioElement.InvokeVoidAsync("setVolume", volume / 100);
|
||||
_isMuted = false;
|
||||
_currentVolume = value;
|
||||
await PlayerStorage.SetVolumeAsync(value);
|
||||
_currentVolume = volume;
|
||||
await PlayerStorage.SetVolumeAsync(volume);
|
||||
StateHasChanged();
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
Console.WriteLine($"ChangeVolume error: {ex.Message}");
|
||||
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()
|
||||
{
|
||||
if (_audioElement == null) return;
|
||||
_isMuted = !_isMuted;
|
||||
var newVolume = _isMuted ? 0 : (_currentVolume / 100);
|
||||
await _audioElement.InvokeVoidAsync("setVolume", newVolume);
|
||||
StateHasChanged();
|
||||
var newVolume = _isMuted ? 0 : _currentVolume;
|
||||
await AudioPlayerService.SetVolumeAsync(newVolume);
|
||||
}
|
||||
|
||||
private void StartProgressTimer()
|
||||
{
|
||||
StopProgressTimer();
|
||||
_progressTimer = new Timer(async _ =>
|
||||
{
|
||||
await UpdateProgress();
|
||||
}, null, 0, 500);
|
||||
_progressTimer = new Timer(async _ => await UpdateProgress(), null, 0, 500);
|
||||
}
|
||||
|
||||
private void StopProgressTimer() => _progressTimer?.Dispose();
|
||||
|
||||
private async Task UpdateProgress()
|
||||
{
|
||||
if (_audioElement == null)
|
||||
{
|
||||
Console.WriteLine("UpdateProgress: _audioElement is null");
|
||||
return;
|
||||
}
|
||||
if (_audioElement == null) return;
|
||||
try
|
||||
{
|
||||
var current = await _audioElement.InvokeAsync<double>("getCurrentTime");
|
||||
var duration = await _audioElement.InvokeAsync<double>("getDuration");
|
||||
|
||||
if (duration > 0 && !double.IsNaN(duration) && !double.IsNaN(current))
|
||||
{
|
||||
_currentProgress = (current / duration) * 100;
|
||||
_currentTime = FormatTime(current);
|
||||
_totalTime = FormatTime(duration);
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user