refactor(streamdata): simplify source labeling and remove hostname parsing

Replace hostname-based source labels with stable numbered labels to keep
episode and source selection consistent across serial and movie flows.

Drop unused URL hostname extraction logic from `StreamDataInvoke` and
propagate the selected source parameter through play handling to align
with the new voice-tab source selection structure.
This commit is contained in:
Felix 2026-05-15 22:22:19 +03:00
parent d2e7f9aa6b
commit b38c8bd4f4
2 changed files with 51 additions and 59 deletions

View File

@ -53,7 +53,7 @@ namespace LME.StreamData.Controllers
// play — повернути стрім для конкретного епізоду (call метод) // play — повернути стрім для конкретного епізоду (call метод)
if (play) if (play)
{ {
return await HandlePlay(invoke, init, id, s, e, title, original_title); return await HandlePlay(invoke, init, id, s, e, title, original_title, t);
} }
// Фільм // Фільм
@ -87,8 +87,7 @@ namespace LME.StreamData.Controllers
if (string.IsNullOrWhiteSpace(streamUrl)) if (string.IsNullOrWhiteSpace(streamUrl))
continue; continue;
string hostname = StreamDataInvoke.ExtractHostname(streamUrl); string label = $"Джерело #{index}";
string label = streamUrls.Count > 1 ? $"Джерело {index} ({hostname})" : hostname;
string processedUrl = BuildStreamUrl(init, streamUrl); string processedUrl = BuildStreamUrl(init, streamUrl);
tpl.Append(label, processedUrl, subtitles: subs); tpl.Append(label, processedUrl, subtitles: subs);
index++; index++;
@ -104,7 +103,7 @@ namespace LME.StreamData.Controllers
} }
/// <summary> /// <summary>
/// Обробка серіалу: eps структура → сезони → епізоди → play /// Обробка серіалу: eps → сезони → епізоди з voice-вкладками (джерела)
/// </summary> /// </summary>
private async Task<ActionResult> HandleSerial(StreamDataInvoke invoke, OnlinesSettings init, long tmdbId, string title, string originalTitle, int s, int e, string t, bool rjson) private async Task<ActionResult> HandleSerial(StreamDataInvoke invoke, OnlinesSettings init, long tmdbId, string title, string originalTitle, int s, int e, string t, bool rjson)
{ {
@ -122,10 +121,15 @@ namespace LME.StreamData.Controllers
if (seasons.Count == 0) if (seasons.Count == 0)
return OnError("lme_streamdata", refresh_proxy: true); return OnError("lme_streamdata", refresh_proxy: true);
// Якщо сезон не вибрано — показуємо список сезонів // Кількість джерел (CDN) з першого запиту
var sources = response.data?.stream_urls?.Where(u => !string.IsNullOrWhiteSpace(u)).ToList() ?? new List<string>();
int sourceCount = Math.Max(1, sources.Count);
var displayTitle = !string.IsNullOrEmpty(title) ? title : (!string.IsNullOrEmpty(originalTitle) ? originalTitle : response.data.title);
// Список сезонів
if (s <= 0) if (s <= 0)
{ {
var displayTitle = !string.IsNullOrEmpty(title) ? title : (!string.IsNullOrEmpty(originalTitle) ? originalTitle : response.data.title);
var seasonTpl = new SeasonTpl(seasons.Count); var seasonTpl = new SeasonTpl(seasons.Count);
foreach (var season in seasons) foreach (var season in seasons)
{ {
@ -139,47 +143,52 @@ namespace LME.StreamData.Controllers
); );
} }
// Якщо вибрано сезон але не вибрано епізод — показуємо епізоди // Список епізодів з voice-вкладками для вибору джерела
string seasonKey = s.ToString(); string seasonKey = s.ToString();
if (!eps.ContainsKey(seasonKey) || eps[seasonKey] == null || eps[seasonKey].Count == 0) if (!eps.ContainsKey(seasonKey) || eps[seasonKey] == null || eps[seasonKey].Count == 0)
return OnError("lme_streamdata", refresh_proxy: true); return OnError("lme_streamdata", refresh_proxy: true);
if (e <= 0) var episodeNumbers = eps[seasonKey]
.Select(ep => int.TryParse(ep, out int en) ? en : 0)
.Where(en => en > 0)
.OrderBy(en => en)
.ToList();
if (episodeNumbers.Count == 0)
return OnError("lme_streamdata", refresh_proxy: true);
// Voice-вкладки: кожне джерело як окрема "озвучка"
string selectedSource = string.IsNullOrEmpty(t) ? "1" : t;
int selectedIndex = int.TryParse(selectedSource, out int si) && si >= 1 && si <= sourceCount ? si : 1;
var voiceTpl = new VoiceTpl();
for (int i = 1; i <= sourceCount; i++)
{ {
var episodes = eps[seasonKey] string voiceLink = $"{host}/lite/lme_streamdata?id={tmdbId}&serial=1&s={s}&t={i}&title={HttpUtility.UrlEncode(displayTitle)}&original_title={HttpUtility.UrlEncode(originalTitle)}";
.Select(ep => int.TryParse(ep, out int en) ? en : 0) voiceTpl.Append($"Джерело #{i}", i == selectedIndex, voiceLink);
.Where(en => en > 0)
.OrderBy(en => en)
.ToList();
if (episodes.Count == 0)
return OnError("lme_streamdata", refresh_proxy: true);
var displayTitle = !string.IsNullOrEmpty(title) ? title : (!string.IsNullOrEmpty(originalTitle) ? originalTitle : response.data.title);
var episodeTpl = new EpisodeTpl(episodes.Count);
foreach (var episode in episodes)
{
string episodeName = $"Епізод {episode}";
string callUrl = $"{host}/lite/lme_streamdata?id={tmdbId}&serial=1&s={s}&e={episode}&play=true&title={HttpUtility.UrlEncode(displayTitle)}&original_title={HttpUtility.UrlEncode(originalTitle)}";
episodeTpl.Append(episodeName, displayTitle, s.ToString(), episode.ToString("D2"), accsArgs(callUrl), "call");
}
return Content(
rjson ? episodeTpl.ToJson() : episodeTpl.ToHtml(),
rjson ? "application/json; charset=utf-8" : "text/html; charset=utf-8"
);
} }
// Якщо є play=true з s та e — це вже оброблено вище в play-гілці // Епізоди з посиланнями на вибране джерело
// Сюди не дійдемо, але на всяк випадок: var episodeTpl = new EpisodeTpl(episodeNumbers.Count);
return OnError("lme_streamdata", refresh_proxy: true); foreach (var epNum in episodeNumbers)
{
string episodeName = $"Епізод {epNum}";
string callUrl = $"{host}/lite/lme_streamdata?id={tmdbId}&serial=1&s={s}&e={epNum}&play=true&t={selectedSource}&title={HttpUtility.UrlEncode(displayTitle)}&original_title={HttpUtility.UrlEncode(originalTitle)}";
episodeTpl.Append(episodeName, displayTitle, s.ToString(), epNum.ToString("D2"), accsArgs(callUrl), "call");
}
episodeTpl.Append(voiceTpl);
return Content(
rjson ? episodeTpl.ToJson() : episodeTpl.ToHtml(),
rjson ? "application/json; charset=utf-8" : "text/html; charset=utf-8"
);
} }
/// <summary> /// <summary>
/// Обробка play-запиту: API з season/episode → JSON зі стрімом /// Обробка play-запиту: API з season/episode → JSON з вибраним джерелом
/// </summary> /// </summary>
private async Task<ActionResult> HandlePlay(StreamDataInvoke invoke, OnlinesSettings init, long tmdbId, int season, int episode, string title, string originalTitle) private async Task<ActionResult> HandlePlay(StreamDataInvoke invoke, OnlinesSettings init, long tmdbId, int season, int episode, string title, string originalTitle, string t)
{ {
if (tmdbId <= 0 || season <= 0 || episode <= 0) if (tmdbId <= 0 || season <= 0 || episode <= 0)
return OnError("lme_streamdata", refresh_proxy: true); return OnError("lme_streamdata", refresh_proxy: true);
@ -188,13 +197,16 @@ namespace LME.StreamData.Controllers
if (response?.data?.stream_urls == null || response.data.stream_urls.Count == 0) if (response?.data?.stream_urls == null || response.data.stream_urls.Count == 0)
return OnError("lme_streamdata", refresh_proxy: true); return OnError("lme_streamdata", refresh_proxy: true);
var firstUrl = response.data.stream_urls.FirstOrDefault(u => !string.IsNullOrWhiteSpace(u)); var streamUrls = response.data.stream_urls.Where(u => !string.IsNullOrWhiteSpace(u)).ToList();
if (string.IsNullOrEmpty(firstUrl)) if (streamUrls.Count == 0)
return OnError("lme_streamdata", refresh_proxy: true); return OnError("lme_streamdata", refresh_proxy: true);
// Вибираємо джерело за індексом з voice-вкладки t (1-based)
int sourceIndex = int.TryParse(t, out int si) && si >= 1 && si <= streamUrls.Count ? si - 1 : 0;
string streamUrl = BuildStreamUrl(init, streamUrls[sourceIndex]);
var subs = CollectSubtitles(response); var subs = CollectSubtitles(response);
string displayTitle = !string.IsNullOrEmpty(title) ? title : (!string.IsNullOrEmpty(originalTitle) ? originalTitle : response.data.title); string displayTitle = !string.IsNullOrEmpty(title) ? title : (!string.IsNullOrEmpty(originalTitle) ? originalTitle : response.data.title);
string streamUrl = BuildStreamUrl(init, firstUrl);
return UpdateService.Validate(Content( return UpdateService.Validate(Content(
VideoTpl.ToJson("play", streamUrl, displayTitle, subtitles: subs), VideoTpl.ToJson("play", streamUrl, displayTitle, subtitles: subs),

View File

@ -1,7 +1,6 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Threading.Tasks; using System.Threading.Tasks;
using System.Web;
using LME.StreamData.Models; using LME.StreamData.Models;
using Newtonsoft.Json; using Newtonsoft.Json;
using Shared; using Shared;
@ -141,25 +140,6 @@ namespace LME.StreamData
return Http.Get(_init.cors(url), headers: headers, proxy: _proxyManager.Get()); return Http.Get(_init.cors(url), headers: headers, proxy: _proxyManager.Get());
} }
/// <summary>
/// Витягнути назву хоста з URL для відображення джерела
/// </summary>
public static string ExtractHostname(string url)
{
if (string.IsNullOrEmpty(url))
return "невідомо";
try
{
var uri = new Uri(url);
return uri.Host;
}
catch
{
return "невідомо";
}
}
public static TimeSpan cacheTime(int multiaccess, int home = 5, int mikrotik = 2, OnlinesSettings init = null, int rhub = -1) public static TimeSpan cacheTime(int multiaccess, int home = 5, int mikrotik = 2, OnlinesSettings init = null, int rhub = -1)
{ {
if (init != null && init.rhub && rhub != -1) if (init != null && init.rhub && rhub != -1)