263 lines
9.7 KiB
Plaintext
263 lines
9.7 KiB
Plaintext
@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" />
|
||
</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 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)
|
||
{
|
||
var x = second;
|
||
}
|
||
|
||
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 { }
|
||
}
|
||
} |