Files
lampac/mods/Music/Controllers/PlaybackController.cs
T

170 lines
8.6 KiB
C#
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.AspNetCore.Mvc;
namespace Music;
public class PlaybackController : BaseController
{
[HttpGet]
[Route("music/matches")]
async public Task<ActionResult> Matches(string id, string provider, string audio_provider, string playback_mode, string title, string artist_name, string album_id, string album_title, int? duration_ms, string date, string isrc, string query)
{
string profileId = MusicProfileIdentity.Resolve(requestInfo, Request);
var track = await MusicPlaybackService.ResolveRequestTrackAsync(id, provider, title, artist_name, album_id, album_title, duration_ms, date, isrc);
var result = string.IsNullOrWhiteSpace(query)
? await MusicResolver.GetMatchesAsync(track, audio_provider, playback_mode, profileId)
: await MusicResolver.GetMatchesByQueryAsync(track, audio_provider, query, playback_mode);
return ContentTo(MusicJson.Serialize(result));
}
[HttpPost]
[Route("music/match/select")]
async public Task<ActionResult> SelectMatch(string id, string provider, string audio_provider, string playback_mode, string match_id, string title, string artist_name, string album_id, string album_title, int? duration_ms, string date, string isrc, string query)
{
string profileId = MusicProfileIdentity.Resolve(requestInfo, Request);
var track = await MusicPlaybackService.ResolveRequestTrackAsync(id, provider, title, artist_name, album_id, album_title, duration_ms, date, isrc);
bool saved = await MusicResolver.SelectMatchAsync(track, audio_provider, match_id, playback_mode, profileId, query);
return ContentTo(MusicJson.Serialize(new
{
saved,
track_id = id,
audio_provider,
match_id
}));
}
[HttpPost]
[Route("music/match/reset")]
async public Task<ActionResult> ResetMatch(string id, string provider, string playback_mode, string title, string artist_name, string album_id, string album_title, int? duration_ms, string date, string isrc)
{
var track = await MusicPlaybackService.ResolveRequestTrackAsync(id, provider, title, artist_name, album_id, album_title, duration_ms, date, isrc);
bool reset = track != null && await MusicSourceMatchService.DeleteAsync(track.id, playback_mode);
return ContentTo(MusicJson.Serialize(new
{
reset,
track_id = id
}));
}
[HttpGet]
[Route("music/play")]
async public Task<ActionResult> Play(string id, string provider, string audio_provider, string stream_mode, string playback_mode, string title, string artist_name, string album_id, string album_title, int? duration_ms, string date, string isrc)
{
string profileId = MusicProfileIdentity.Resolve(requestInfo, Request);
var track = await MusicPlaybackService.ResolveRequestTrackAsync(id, provider, title, artist_name, album_id, album_title, duration_ms, date, isrc);
var result = await MusicPlaybackService.ResolveTrackAsync(track, audio_provider, stream_mode, playback_mode, profileId, HttpContext.RequestAborted);
MusicPlaybackService.PrepareStreamSources(host, track, provider, audio_provider, stream_mode, playback_mode, result);
return ContentTo(MusicJson.Serialize(result));
}
[HttpGet]
[Route("music/stream")]
async public Task<ActionResult> Stream(string ticket, string id, string provider, string audio_provider, string quality, string stream_mode, string playback_mode, string title, string artist_name, string album_id, string album_title, int? duration_ms, string date, string isrc)
{
if (MusicStreamTicketService.TryGet(ticket, out var ticketSource))
{
// тикет жив, но upstream-ссылка внутри могла умереть (403/404/410/416 —
// googlevideo TTL/IP-привязка): не транслируем смерть клиенту, а
// падаем в пере-резолв по fallback-параметрам ниже
var relayed = await MusicStreamRelayService.RelayAsync(HttpContext, ticketSource, failOnUpstreamError: !string.IsNullOrWhiteSpace(id));
if (relayed != null)
return relayed;
}
string profileId = MusicProfileIdentity.Resolve(requestInfo, Request);
var track = await MusicPlaybackService.ResolveRequestTrackAsync(id, provider, title, artist_name, album_id, album_title, duration_ms, date, isrc);
// upstream-ссылка умерла (403/410/416 в relay) — кэш sources для этого
// трека может держать те же мёртвые URL: пере-резолв идёт с refreshSources
var result = await MusicPlaybackService.ResolveTrackAsync(track, audio_provider, stream_mode, playback_mode, profileId, HttpContext.RequestAborted, refreshSources: true);
if (track == null || result?.available != true || result.sources.Count == 0)
return StatusCode(404);
var source = MusicPlaybackService.SelectSource(result.sources, quality, stream_mode);
if (source == null || string.IsNullOrWhiteSpace(source.url))
return StatusCode(404);
return await MusicStreamRelayService.RelayAsync(HttpContext, source);
}
[HttpPost]
[Route("music/history/mark")]
async public Task<ActionResult> MarkHistory(string id, string provider, string title, string artist_name, string album_id, string album_title, int? duration_ms, string date, string isrc, bool count_play = false, long? played_ms = null, string image = null)
{
var track = await MusicPlaybackService.ResolveRequestTrackAsync(id, provider, title, artist_name, album_id, album_title, duration_ms, date, isrc);
string profileId = MusicProfileIdentity.Resolve(requestInfo, Request);
if (track != null)
{
image = image?.Trim();
if (image?.StartsWith("//", StringComparison.Ordinal) == true)
image = "https:" + image;
if (track.images?.Any(i => !string.IsNullOrWhiteSpace(i?.url)) != true
&& !string.IsNullOrWhiteSpace(image) && image.Length <= 8192
&& Uri.TryCreate(image, UriKind.Absolute, out var imageUri)
&& (imageUri.Scheme == Uri.UriSchemeHttp || imageUri.Scheme == Uri.UriSchemeHttps))
{
// Client artwork belongs to this history entry, not the shared metadata cache.
track = MusicJson.Deserialize<MusicTrack>(MusicJson.Serialize(track));
track.images = new List<MusicImage> { new() { url = imageUri.AbsoluteUri } };
}
await MusicPlaybackHistoryService.SaveAsync(profileId, track);
// статистику инкрементим только по честному прослушиванию:
// mark дёргается и для обновления payload (смена источника),
// без флага это накручивало бы счётчики
if (count_play || played_ms > 0)
await MusicStatsService.RecordPlayAsync(profileId, track, count_play, played_ms, HttpContext.RequestAborted);
}
return ContentTo(MusicJson.Serialize(new
{
saved = track != null,
track_id = id
}));
}
[HttpPost]
[Route("music/history/remove")]
async public Task<ActionResult> RemoveHistory(string id)
{
string profileId = MusicProfileIdentity.Resolve(requestInfo, Request);
bool removed = await MusicPlaybackHistoryService.RemoveAsync(profileId, id);
return ContentTo(MusicJson.Serialize(new
{
removed,
track_id = id
}));
}
[HttpGet]
[Route("music/playlist.m3u")]
async public Task<ActionResult> Playlist(string ids, string provider, string audio_provider, string stream_mode, string playback_mode, string playlist_strategy)
{
string profileId = MusicProfileIdentity.Resolve(requestInfo, Request);
string playlist = await MusicPlaylistService.BuildAsync(new MusicPlaylistRequest
{
ids = ids,
provider = provider,
audio_provider = audio_provider,
stream_mode = stream_mode,
playback_mode = playback_mode,
playlist_strategy = playlist_strategy,
profile_id = profileId,
host = host,
uid = Request.Query["uid"].FirstOrDefault(),
account_email = Request.Query["account_email"].FirstOrDefault(),
cancellation_token = HttpContext.RequestAborted
});
return Content(playlist, "audio/x-mpegurl; charset=utf-8");
}
}