Плеер
This commit is contained in:
84
PlaylistShared.Api/Controllers/AudioController.cs
Normal file
84
PlaylistShared.Api/Controllers/AudioController.cs
Normal file
@@ -0,0 +1,84 @@
|
||||
using Microsoft.AspNetCore.Authorization;
|
||||
using Microsoft.AspNetCore.Identity;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using PlaylistShared.Api.Entities;
|
||||
using PlaylistShared.Api.Services;
|
||||
using System.Security.Claims;
|
||||
|
||||
namespace PlaylistShared.Api.Controllers;
|
||||
|
||||
[ApiController]
|
||||
[Route("api/[controller]")]
|
||||
public class AudioController : ControllerBase
|
||||
{
|
||||
private readonly UserManager<ApplicationUser> _userManager;
|
||||
private readonly YandexMusicService _yandexService;
|
||||
private readonly JwtService _jwtService;
|
||||
|
||||
public AudioController(
|
||||
UserManager<ApplicationUser> userManager,
|
||||
YandexMusicService yandexService,
|
||||
JwtService jwtService)
|
||||
{
|
||||
_userManager = userManager;
|
||||
_yandexService = yandexService;
|
||||
_jwtService = jwtService;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Потоковое воспроизведение трека из Яндекс.Музыки.
|
||||
/// </summary>
|
||||
/// <param name="trackId">ID трека (например, "21696942").</param>
|
||||
[HttpGet("track/{trackId}")]
|
||||
[AllowAnonymous]
|
||||
public async Task<IActionResult> StreamTrack(string trackId, [FromQuery] string? access_token = null)
|
||||
{
|
||||
var user = await GetUserFromToken(access_token);
|
||||
if (user == null)
|
||||
return Unauthorized();
|
||||
|
||||
var streamUrl = await _yandexService.GetTrackFileUrlAsync(user, trackId);
|
||||
if (string.IsNullOrEmpty(streamUrl))
|
||||
return NotFound();
|
||||
|
||||
var httpClient = new HttpClient();
|
||||
var request = new HttpRequestMessage(HttpMethod.Get, streamUrl);
|
||||
|
||||
// Пробрасываем Range-заголовок клиента к Яндекс.Музыке
|
||||
if (Request.Headers.ContainsKey("Range"))
|
||||
{
|
||||
request.Headers.Add("Range", Request.Headers["Range"].ToString());
|
||||
}
|
||||
var response = await httpClient.SendAsync(request, HttpCompletionOption.ResponseHeadersRead);
|
||||
|
||||
// Если Яндекс.Музыка поддерживает range, пробрасываем статус 206
|
||||
Response.StatusCode = (int)response.StatusCode;
|
||||
Response.ContentType = response.Content.Headers.ContentType?.ToString() ?? "audio/mpeg";
|
||||
|
||||
if (response.Content.Headers.Contains("Content-Range"))
|
||||
Response.Headers.Add("Content-Range", response.Content.Headers.ContentRange?.ToString());
|
||||
if (response.Headers.Contains("Accept-Ranges"))
|
||||
Response.Headers.Add("Accept-Ranges", response.Headers.AcceptRanges?.ToString());
|
||||
if (response.Content.Headers.Contains("Content-Length"))
|
||||
Response.Headers.Add("Content-Length", response.Content.Headers.ContentLength?.ToString());
|
||||
|
||||
await response.Content.CopyToAsync(Response.Body);
|
||||
return new EmptyResult();
|
||||
}
|
||||
|
||||
private async Task<ApplicationUser?> GetUserFromToken(string? token)
|
||||
{
|
||||
if (string.IsNullOrEmpty(token))
|
||||
return null;
|
||||
|
||||
var principal = _jwtService.ValidateToken(token);
|
||||
if (principal == null)
|
||||
return null;
|
||||
|
||||
var userId = principal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
|
||||
if (string.IsNullOrEmpty(userId))
|
||||
return null;
|
||||
|
||||
return await _userManager.FindByIdAsync(userId);
|
||||
}
|
||||
}
|
||||
@@ -27,7 +27,7 @@
|
||||
<PackageReference Include="Swashbuckle.AspNetCore" Version="10.1.7" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.Swagger" Version="10.1.7" />
|
||||
<PackageReference Include="Swashbuckle.AspNetCore.SwaggerUI" Version="10.1.7" />
|
||||
<PackageReference Include="YandexMusic" Version="0.0.5" />
|
||||
<PackageReference Include="YandexMusic" Version="0.0.6" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup>
|
||||
|
||||
@@ -46,4 +46,29 @@ public class JwtService
|
||||
|
||||
return (tokenString, refreshToken, tokenDescriptor.Expires.Value);
|
||||
}
|
||||
|
||||
public ClaimsPrincipal? ValidateToken(string token)
|
||||
{
|
||||
var tokenHandler = new JwtSecurityTokenHandler();
|
||||
var key = Encoding.UTF8.GetBytes(_configuration["Jwt:Key"]!);
|
||||
try
|
||||
{
|
||||
var principal = tokenHandler.ValidateToken(token, new TokenValidationParameters
|
||||
{
|
||||
ValidateIssuerSigningKey = true,
|
||||
IssuerSigningKey = new SymmetricSecurityKey(key),
|
||||
ValidateIssuer = true,
|
||||
ValidIssuer = _configuration["Jwt:Issuer"],
|
||||
ValidateAudience = true,
|
||||
ValidAudience = _configuration["Jwt:Audience"],
|
||||
ValidateLifetime = true,
|
||||
ClockSkew = TimeSpan.Zero
|
||||
}, out _);
|
||||
return principal;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -72,6 +72,15 @@ public class YandexMusicService
|
||||
return await playlist.RemoveTracksAsync(tracks.ToArray());
|
||||
}
|
||||
|
||||
public async Task<string?> GetTrackFileUrlAsync(ApplicationUser user, string trackId)
|
||||
{
|
||||
using var client = await CreateClientAsync(user);
|
||||
if (client == null) return null;
|
||||
var track = await client.GetTrackAsync(trackId);
|
||||
if (track == null) return null;
|
||||
return await track.GetLinkAsync();
|
||||
}
|
||||
|
||||
public string EncryptToken(string token) => _dataProtector.Protect(token);
|
||||
|
||||
public string DecryptToken(string encryptedToken)
|
||||
|
||||
Reference in New Issue
Block a user