Files
PlaylistShared/PlaylistShared.Pwa/Components/Global/AudioPlayer.razor

271 lines
10 KiB
Plaintext
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
@using Microsoft.JSInterop
@inject IAudioPlayerService AudioPlayerService
@inject IJSRuntime JS
@inject TokenStorage TokenStorage
@inject PlayerStorage PlayerStorage
@inject AuthenticationStateProvider AuthProvider
@inject ISnackbar Snackbar
@inject HttpClient Http
<MudPaper Class="pa-2" Elevation="0" Width="100%" Style="background-color: rgba(0,0,0,0.05); border-radius: 8px;">
<MudStack Row AlignItems="AlignItems.Center" Wrap="Wrap.Wrap">
<!-- Кнопки управления -->
<MudItem @onmouseenter="() => { _isPlayHovered = true; }"
@onmouseleave="() => { _isPlayHovered = false; }"
style="position: relative; display: inline-block; cursor: pointer; border-radius: 4px; overflow: hidden; width: 50px; height: 50px;">
@if (!string.IsNullOrEmpty(AudioPlayerService.CurrentTrackCoverUrl))
{
<MudImage Src="@AudioPlayerService.CurrentTrackCoverUrl.FormatCoverUrl(50, 50)" Height="50" Width="50" Class="rounded" Style="display: block;" />
}
<MudItem class="play-overlay"
style="position: absolute; top: 0; left: 0; right: 0; bottom: 0; background: transparent; display: flex; align-items: center; justify-content: center; border-radius: 4px;">
<MudToggleIconButton Toggled="@AudioPlayerService.IsPlaying"
Icon="@Icons.Material.Filled.PlayArrow"
Color="@Color.Primary"
ToggledIcon="@Icons.Material.Filled.Pause"
ToggledColor="@Color.Primary"
ToggledChanged="OnPlayClick" />
</MudItem>
</MudItem>
<!-- Название и прогресс -->
<MudStack AlignItems="AlignItems.Stretch" Class="flex-grow-1" Style="height: 100%;" Spacing="0">
<MudStack Row AlignItems="AlignItems.Stretch" Class="flex-grow-1">
<MudText Typo="Typo.body2" Style="font-weight: 500; line-height: 1.2;">@AudioPlayerService.CurrentTrackTitle</MudText>
<MudSpacer />
<MudText Typo="Typo.body2" Style="font-weight: 500; line-height: 1.2;">@AudioPlayerService.CurrentTimeString / @AudioPlayerService.TotalTimeString</MudText>
</MudStack>
<MudSlider Value="@AudioPlayerService.CurrentProgress" Class="mt-n1" Min="0" Max="100" Size="Size.Small" ValueChanged="@((double newValue) => SeekTo(newValue))" Step="0.01" />
<MudProgressLinear Color="Color.Primary" Buffer="true" Value="@AudioPlayerService.CurrentProgress" BufferValue="@_bufferValue" Class="my-7" />
<TrackProgress Value="@AudioPlayerService.CurrentProgress"
Min="0"
Max="100"
Color="Color.Primary"
BufferValue="@_bufferValue" />
</MudStack>
<!-- Громкость -->
<MudItem @onmouseenter="() => _volumeIsOpen = true"
@onmouseleave="() => _volumeIsOpen = false"
@onwheel="OnVolumeHandleWheel"
Style="position: relative; display: flex; align-items: center;">
<MudIconButton Icon="@(AudioPlayerService.CurrentVolume == 0 ? Icons.Material.Filled.VolumeOff : Icons.Material.Filled.VolumeUp)"
Size="Size.Small"
Color="Color.Default"
OnClick="ToggleMute" />
@* Попавер с минимальной шириной *@
<MudPopover Open="@_volumeIsOpen"
AnchorOrigin="Origin.TopCenter"
TransformOrigin="Origin.BottomCenter"
Fixed="true"
Class="pa-0 mt-n5"
Style="height:120px; width: 10px; background-color: transparent !important; overflow: visible !important;">
<MudProgressLinear Vertical="true" Color="Color.Primary" Size="Size.Medium" Value="@AudioPlayerService.CurrentVolume" />
</MudPopover>
</MudItem>
</MudStack>
</MudPaper>
<audio id="@_audioId" style="display: none;"></audio>
@code {
private const double _volumeDefault = 50;
// Генерируем уникальный ID для аудиоэлемента, чтобы избежать конфликтов при множественных экземплярах
private string _audioId = $"audio_{Guid.NewGuid():N}";
private IJSObjectReference? _audioModule;
private IJSObjectReference? _audioElement;
// Громкость
private bool _volumeIsOpen;
private double _volumeBeforeMute;
private double _bufferValue;
private bool _isPlayHovered;
protected override async Task OnInitializedAsync()
{
AudioPlayerService.OnLoadAndPlayRequested += OnServiceLoadAndPlay;
AudioPlayerService.OnPlayRequested += OnServicePlay;
AudioPlayerService.OnPauseRequested += OnServicePause;
AudioPlayerService.OnSeekRequested += OnServiceSeek;
AudioPlayerService.OnVolumeChangeRequested += OnServiceVolumeChange;
AudioPlayerService.OnStateChanged += OnServiceStateChanged;
}
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await EnsureAudioModuleAsync();
}
}
private async Task EnsureAudioModuleAsync()
{
if (_audioModule == null)
_audioModule = await JS.InvokeAsync<IJSObjectReference>("import", "/js/AudioPlayer.js");
if (_audioElement == null)
_audioElement = await _audioModule.InvokeAsync<IJSObjectReference>("init", _audioId, DotNetObjectReference.Create(this));
}
[JSInvokable]
public async Task OnAudioEnded()
{
AudioPlayerService.NotifyTrackEnded();
}
[JSInvokable]
public async Task OnTimeUpdate(double currentTime, double duration)
{
if (!double.IsNaN(duration) && !double.IsNaN(currentTime) && duration > 0)
{
AudioPlayerService.UpdateProgress(currentTime, duration);
}
}
[JSInvokable]
public async Task OnDownloadProgress(double second)
{
_bufferValue = second / AudioPlayerService.TotalTime * 100;
}
private async Task<bool> CheckAuthAsync()
{
var authState = await AuthProvider.GetAuthenticationStateAsync();
if (!authState.User.Identity?.IsAuthenticated == true)
{
Snackbar.Add("Воспроизведение доступно только авторизованным пользователям", Severity.Warning);
return false;
}
return true;
}
#region Обработка сервиса
private async Task OnServiceLoadAndPlay(string trackId, string? accessToken, string? sharedPlaylistId)
{
if (string.IsNullOrWhiteSpace(accessToken))
{
var tokens = await TokenStorage.GetTokensAsync();
accessToken = tokens.token;
}
if (string.IsNullOrWhiteSpace(accessToken) && string.IsNullOrWhiteSpace(sharedPlaylistId))
{
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);
}
private async Task OnServicePlay()
{
if (_audioElement == null) return;
await _audioElement.InvokeVoidAsync("play");
}
private async Task OnServicePause()
{
if (_audioElement == null) return;
await _audioElement.InvokeVoidAsync("pause");
}
private async Task OnServiceSeek(double time)
{
if (_audioElement == null) return;
try
{
await _audioElement.InvokeVoidAsync("setCurrentTime", time);
}
catch (Exception ex)
{
Console.WriteLine($"Seek error: {ex.Message}");
}
}
private async Task OnServiceVolumeChange(double volume)
{
if (_audioElement == null) return;
if (volume == AudioPlayerService.CurrentVolume) return;
try
{
await _audioElement.InvokeVoidAsync("setVolume", volume / 100);
StateHasChanged();
}
catch (Exception ex)
{
Console.WriteLine($"Volume change error: {ex.Message}");
}
}
private void OnServiceStateChanged()
{
InvokeAsync(StateHasChanged);
}
#endregion
private async Task OnVolumeHandleWheel(WheelEventArgs e)
{
// Изменяем громкость на 5 единиц за один тик колесика
double step = 5;
double newVolume = e.DeltaY < 0
? Math.Min(AudioPlayerService.CurrentVolume + step, 100)
: Math.Max(AudioPlayerService.CurrentVolume - step, 0);
await ChangeVolume(newVolume);
}
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 (AudioPlayerService.CurrentVolume > 0)
{
_volumeBeforeMute = AudioPlayerService.CurrentVolume;
await AudioPlayerService.SetVolumeAsync(0);
}
else
{
await AudioPlayerService.SetVolumeAsync(_volumeBeforeMute);
_volumeBeforeMute = 0;
}
}
private async Task OnPlayClick()
{
if (AudioPlayerService.IsPlaying)
await AudioPlayerService.PauseAsync();
else
await AudioPlayerService.PlayAsync();
}
public async ValueTask DisposeAsync()
{
try
{
if (_audioElement != null)
await _audioElement.DisposeAsync();
if (_audioModule != null)
await _audioModule.DisposeAsync();
}
catch { }
}
}