Commit Graph

58 Commits

Author SHA1 Message Date
nk
52c8c3f69f fix(enrich): быстрый now-playing проход — только Deezer (без затыка iTunes)
iTunes-фолбэк в coverFast с троттлингом 3.5с сериализовал проход — он не
успевал за сменой треков, покрытие падало. Теперь быстрый проход = только
Deezer (параллельно, без троттла). Промахи Deezer добирает фоновый enrichOne
(iTunes через прокси с троттлингом).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 16:40:06 +03:00
nk
28487a7911 fix(enrich): iTunes/Deezer через DE-прокси + троттлинг (RU-IP забанен)
Корневая причина пропавших обложек: RU-IP сервера ЗАБАНЕН Apple (iTunes search
→ 429 «Rate limit ... 121.127.37.212»), а Deezer из РФ отдаёт ПУСТОЙ каталог.
Оба источника с сервера не работали. Теперь iTunes/Deezer-поиск ходит через тот
же DE-прокси, что и Discogs (DISCOGS_PROXY): с DE-IP iTunes доступен, Deezer
отдаёт каталог. Deezer сделан первичным (высокий лимит), iTunes — фолбэк с
сериализацией (3.5с интервал), чтобы не забанить общий DE-IP. Скачивание самих
картинок (mzstatic/dzcdn) — напрямую, они из РФ доступны.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 16:30:06 +03:00
nk
59aa23ff77 fix(enrich): coverFast — очищенный iTunes + Deezer (не множить лимит)
Прошлый вариант делал 2 запроса iTunes на каждый now-playing-трек без обложки
(170+ играющих) → упирался в лимит iTunes ~20/мин, обложки не наливались.
coverFast теперь: 1 запрос iTunes по ОЧИЩЕННОМУ названию (чаще матчит ремиксы/
«(Original Mix)») → на промахе/429 фолбэк Deezer (отдельный лимит). Полный
fetchItunes (full→clean→deezer) остаётся для фонового enrichOne.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 16:06:43 +03:00
nk
ba9b4054e8 feat(enrich): больше обложек — очистка запроса iTunes + фолбэк Deezer
Суффиксы названий («(Original Mix)», «(SEA)», «[... Dub]», «feat. X»)
ломали точный матч iTunes (limit=1) — у многих треков (особенно электроника/
лаунж/ремиксы Royal Radio и др.) обложка не находилась, хотя в iTunes/Deezer
она есть. Теперь fetchItunes: (1) запрос как есть → (2) очищенный (без скобок/
feat) → (3) фолбэк Deezer (публичный API, без ключа) только за обложкой.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 15:50:20 +03:00
nk
35f9a2b7cc chore(stations): Royal Radio — https вместо http (301-редирект)
10 каналов royalradio.space переведены на https (http отдавал 301→https,
ExoPlayer не шёл по кросс-протокольному редиректу). Прод-БД обновлена точечно.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 15:31:13 +03:00
nk
c2e941f1c3 chore(stations): Romantika (711) — рабочий HLS вместо мёртвого srv21
Главный Радио Романтика был в offline-ids (мёртвый srv21.gpmradio). Обновлён
на hls-01-gpm.hostingradio.ru/romantika495. Прод-БД поправлена точечно.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 15:18:25 +03:00
nk
326bbbc0ee chore(stations): Like FM (718) — рабочий HLS, группа Like FM
Старый поток srv21.gpmradio мёртв (станция была в offline-ids). Обновлён на
hls-01-gpm.hostingradio.ru/likefm495 (главный Like FM), groupId 27. Прод-БД
обновлена точечно (без полного reseed, чтобы не сбросить жанры now-playing).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 15:05:09 +03:00
nk
87cc67072c chore(scripts): пробер битрейт-вариантов потоков станций
Перебирает соседние битрейты в маунте каждого потока, проверяет живость
(заголовки + content-type), пишет массив qualities в stations.json клиента.
Пропускает HLS (emgsound) и Love (UID). Использован для фичи переключения
качества в плеере.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 12:37:24 +03:00
nk
3c6dbed659 feat(now-playing): Radio ROKS через TavR Media API (трек + обложки)
Главный канал ROKS не отдаёт трек по ICY (StreamTitle пустой), сабканалы —
без обложек. Новый RoksNowPlayingService опрашивает o.tavr.media/roks
(главный) и /roks4songs (сабканалы по type ukr/bal/new/har), отдаёт и трек,
и обложку static.radioroks.ua. Исключил genre='Radio ROKS' из ICY-поллера.
2026-06-04 11:44:30 +03:00
nk
51576f7198 feat(now-playing): Радио Монте-Карло через Крутой Медиа API
Все 21 канал Монте-Карло — сеть Крутой Медиа (dfm.ru/api/n/current).
Добавил genre='Radio Monte Carlo' в DfmNowPlayingService, матчинг по
слагу из маута потока (basename без битрейта: blues96.aacp -> blues),
исключил из ICY-поллера. Чинит 5 каналов, залипших на 'Дух — Тишина'
(Blues, Chill Lounge, Italiano, Meditation, Summertime).
2026-06-04 10:55:09 +03:00
nk
d6b8be124e perf(enrich): 3-й IP Discogs (token3 через форс-IPv4 RU) + concurrency 12
token3 ходит напрямую с RU, но форсирован IPv4 (121.127.37.212) — для Discogs
это 3-й IP (token1=RU-v6, token2=DE-v4, token3=RU-v4). ~162/мин потолок. Без
доп. инфры. concurrency 12 чтобы задействовать 3 IP.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 10:21:37 +03:00
nk
7457498f5b perf(enrich): concurrency 8 — задействовать оба IP Discogs (RU+DE)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 10:10:39 +03:00
nk
e982fde730 feat(enrich): 2-й токен Discogs через DE-прокси (2 IP → ~108/мин жанров)
Discogs лимитит по IP. token1 идёт напрямую (IP RU), token2 — через форвард-прокси
на DE (IP DE, tinyproxy, доступ только с RU). Два IP, у каждого свой слот ~54/мин
→ суммарно ~108/мин жанров без 429. undici ProxyAgent. Без DISCOGS_PROXY — только
token1 (54/мин).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 10:01:37 +03:00
nk
dfdfb7e4ab fix(enrich): Discogs троттлит по IP — общий лимит ~54/мин (стоп 429-шторм)
Два токена с одного IP не помогают (Discogs лимитит по IP, не по токену) —
вызывало 100% 429. Вернул общий интервал 1100мс (~54/мин). 2-й токен полезен
только на втором IP (DE-воркер).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 09:52:39 +03:00
nk
94e7f46b39 perf(enrich): ротация двух токенов Discogs (~108/мин жанров)
Discogs лимит ~60/мин на токен. Поддержка нескольких токенов (DISCOGS_TOKEN,
DISCOGS_TOKEN2) — у каждого свой слот, берём наименее загруженный → суммарно
вдвое быстрее жанры/стили/лейблы, без 429. Токены — в env (не в гите).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-04 09:45:35 +03:00
nk
ed94bd73d7 perf(enrich): cover-проход эфира 8 параллельно + защита от наложения
~447 живых станций, смена ~120 треков/мин — проход по 4 не успевал. Теперь 8
параллельно + guard против перекрытия крон-запусков.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 21:02:33 +03:00
nk
36043c32b0 perf(enrich): быстрый cover-only проход эфира через iTunes (без Discogs-гейта)
Discogs-лимитер делал Discogs узким местом (54/мин) для ВСЕХ треков, тормозя
обложки. Теперь крон now-playing красит эфир обложками напрямую через iTunes
(4 параллельно, без Discogs), а полное обогащение жанрами идёт фоном. Обложки
живого набора появляются быстро.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 20:56:50 +03:00
nk
5164843824 perf(enrich): rate-limiter на Discogs + concurrency 5 (обложки быстрее)
Discogs ограничен ≥1.1с между вызовами (≤54/мин, без 429) независимо от
параллельности. Параллельность 5 → обложки (iTunes, без лимита) и скачивание
льются быстрее. Решает медленное наполнение обложек живого набора.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 20:51:28 +03:00
nk
588857a73e feat(now-playing): MAXIMUM через тот же Крутой-API (dfm.ru/api/n/current)
MAXIMUM — сеть Крутой Медиа, её каналы в том же /api/n/current. Поллер расширен
на genre IN (DFM, MAXIMUM) + алиасы (maxbritpop, max80, maximum90, rockhits и т.д.).
ICY исключает MAXIMUM.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 20:36:48 +03:00
nk
d46020bd37 chore(stations): Love Radio -> потоки n340 (синхр. с клиентом)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 20:14:26 +03:00
nk
bd2cd36f1e fix(now-playing): Love Radio — ICY авторизованных n340-потоков (per-channel)
player/online кэширует один трек на все каналы. Берём трек из ICY самих потоков
(каждый поток физически разный), читая их с сессионным UID (бэкенд берёт из config).
Теперь у каждого Love-канала свой трек.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 20:09:01 +03:00
nk
68c919c8ba fix(now-playing): Love Radio — online?musicStreamId (per-channel), не history
history/list игнорирует musicStreamId (всегда главный эфир) → все каналы показывали
один трек. player/online?musicStreamId отдаёт верный трек канала.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 18:23:56 +03:00
nk
fa7742d06e feat(now-playing): Love Radio через api.loveradio.ru (ICY шлёт мусор onlinestop56k)
ICY-потоки Love Radio отдают 'onlinestop56k' вместо трека. Берём текущий трек из
их API (player/history/list?musicStreamId=N&limit=1, data[0]). Статичный маппинг
наших станций -> musicStreamId. ICY-поллер исключает genre='Love Radio'.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 18:19:12 +03:00
nk
338f189f33 fix(enrich): не помечать done при сбое запроса iTunes (промах не застывает)
Отличаем сбой запроса (сеть/HTTP-ошибка → ретраить, оставляем pending) от чистого
'не найдено' (done). Раньше транзиентный сбой iTunes под нагрузкой навсегда лишал
трек обложки. fetchItunes теперь бросает при !res.ok.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 18:09:54 +03:00
nk
40a9f3968f feat(stations): корректный health-check + эндпоинт offline-ids
health-check переписан: живой = пришли заголовки 200-399 (рвём соединение сразу,
не ждём бесконечное тело аудиопотока), параллельно, прогон при старте + ежечасно.
Раньше GET висел на живых потоках до таймаута → ложный offline. Новый GET /stations/offline-ids
отдаёт station_id оффлайн-станций — клиент их скрывает.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 17:56:59 +03:00
nk
c2f638e1a1 chore(stations): синхр. — отключены 67 мёртвых станций
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 17:48:45 +03:00
nk
7ff48fff29 fix(now-playing): EMG-фолбэк slug europaplus-{x} (Fresh застрял)
Slug из хоста потока не всегда = meta-slug: hls-01-fresh → meta это europaplus-fresh.
Если по основному slug пусто — пробуем europaplus-{slug}. Fresh теперь обновляется.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 17:26:51 +03:00
nk
8f0ec8a5b8 perf(enrich): снизить параллельность 3->2 (уложиться в лимит Discogs 60/мин)
Темп 83/мин давал Discogs 429 (жанры не дотягивались). concurrency=2, throttle 1.2с
→ ~40/мин, под лимитом. Обложки (iTunes) и жанры (Discogs) перестают падать по rate-limit.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 17:17:12 +03:00
nk
db09274060 fix(enrich): нормализовать пунктуацию в поиске iTunes (St.Thomas → St Thomas)
Пунктуация без пробела (St.Thomas, feat.) ломала запрос к iTunes — обложка
существующего трека не находилась. Заменяем не-буквенно-цифровые символы на
пробел перед поиском.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 17:08:25 +03:00
nk
982c42cdf2 chore(stations): синхр. — отключены мёртвые EP-каналы Acoustic/ResiDance
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 17:05:15 +03:00
nk
7e6b0c8dc6 feat(now-playing): DFM/Крутой Медиа через dfm.ru/api/n/current
Сабканалы DFM (Skrillex, Daft Punk, K-Pop, Игромания и др.) не отдают ICY-метаданные.
Единый веб-API dfm.ru/api/n/current даёт текущий трек + WebP-обложку по всем ~147
каналам (ключ slug). DfmNowPlayingService матчит наши DFM-станции по нормализованному
имени (+ числовой префикс, + алиасы для годов/Игромании/Pioneer). ICY-поллер
исключает genre=DFM.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 16:24:33 +03:00
nk
3215dd5a4e fix(now-playing): отсекать JSON-статус в ICY StreamTitle (101.ru)
Потоки 101.ru (Comedy Club, StandUp, Женский StandUp и др.) шлют в StreamTitle
JSON {"status":1,...} вместо трека — он попадал в now_playing как название.
ICY-парсер и ingest теперь отсекают значения, начинающиеся с { или [.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 16:11:26 +03:00
nk
3049b1ec89 chore(stations): актуальные emgsound HLS-потоки для 8 каналов Европы Плюс
Europa Plus/Top 40/New/Party/Urban/Acoustic/ResiDance/Fresh переведены со старых
потоков на emgsound HLS — теперь их ловит EMG-поллер (now-playing + обложки).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 15:47:27 +03:00
nk
499863744f fix(now-playing): EMG-поллер не фильтрует по isOnline + чинит ошибочный offline
health-check ошибочно метит HLS-потоки emgsound как offline → поллер их пропускал.
Теперь поллим все emgsound-станции и при успешном получении трека ставим isOnline=true.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 15:34:05 +03:00
nk
38b2aee26d feat(now-playing): EMG (Европа Плюс и др.) now-playing через meta.hostingradio
Станции группы ЕМГ (emgsound.ru) получают текущий трек + готовую WebP-обложку
из единого meta.hostingradio.ru/emg/{slug}/history (slug из хоста потока,
order=desc → первый = сейчас). Заводится через NowPlayingService.ingest
(чарты + обогащение). ICY-поллер теперь пропускает emgsound (там HLS без ICY).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 15:28:06 +03:00
nk
dcc2f599f9 fix(enrich): непрерывный бэкафилл (пополнять очередь когда почти пуста)
Прежнее условие «очередь пуста» почти не срабатывало — now-playing-крон держал
очередь занятой, и холодный бэклог (~21k) не двигался. Теперь раз в минуту
подкидываем 100 pending когда очередь почти пуста; играющие треки идут вперёд
по приоритету.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 15:05:51 +03:00
nk
5bd7bfb923 feat(enrich): iTunes-фолбэк для жанра/альбома/года (гибрид с Discogs)
iTunes-запрос (уже делается ради обложки) теперь отдаёт и альбом/год/жанр.
Жанр: Discogs (тонкий) → iTunes (грубый фолбэк) — поднимает покрытие жанров
в чартах. Альбом и дата релиза заполняются из iTunes. Стили и лейбл — по-прежнему
только Discogs.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 15:00:02 +03:00
nk
554c1730a3 perf(enrich): параллельная обработка очереди (3 трека) — быстрее покрывать живой набор
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 14:49:25 +03:00
nk
bb74d631c1 feat(enrich): крон — гарантировать обложку играющим сейчас трекам
Раз в минуту проходим now_playing: создаём Track при отсутствии (без записи
проигрывания) и приоритетно обогащаем тех, у кого нет обложки. Now-playing-обложки
(DFM и др.) появляются быстро у всех станций, не дожидаясь смены трека/бэкафилла.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 14:39:13 +03:00
nk
916fc301e4 feat(enrich): обложки через iTunes Search + приоритет играющим трекам
Покрытие обложек у Discogs низкое (нет не-электроники, нишевого). Добавлен
iTunes Search API (без ключа, Apple-арт — как у Record) основным источником
обложки: iTunes → Discogs → существующая, далее WebP. Играющие сейчас треки
(recordPlay) ставятся в НАЧАЛО очереди обогащения — обложка успевает появиться,
пока трек звучит. Троттлинг 1.5с.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 14:33:52 +03:00
nk
96fabac7f5 fix(now-playing): резолвить обложку трека на чтении /now-playing
Обложка ICY-станций (DFM) теперь подтягивается из обогащённого трека по normKey
в момент ответа API, а не записи now_playing — появляется сразу после обогащения,
без ожидания следующего опроса станции (~6 мин).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 14:20:41 +03:00
nk
f379110975 feat(now-playing): DFM и др. ICY-станции — обложки + чарты + ротация
ICY-станции (DFM и пр.) теперь полноценно «как Record»:
- ICY-поллер вызывает recordPlay → треки идут в чарты и обогащаются Discogs,
  откуда берётся обложка (раньше now_playing писался напрямую, мимо чартов)
- обложка now-playing: если источник не дал (ICY всегда null) — подставляем
  обложку обогащённого трека из нашей БД по normKey (NowPlayingService.resolveCover)
- ротация курсора по всем станциям (окно 70) вместо первых 50 по кругу —
  раньше 363 из 413 станций не опрашивались
- общий NowPlayingService.ingest для Record и ICY (дедуп логики)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 14:08:07 +03:00
nk
149421740f perf(enrich): ускорить бэкафилл (батч 240 каждые 5 мин, ~50 треков/мин)
В базе ~22к треков — прежние 30/10мин слишком медленно. Батч подобран под
троттлинг очереди (~50 запросов/мин, под лимитом Discogs 60/мин), пропускаем
тик если прошлый батч ещё не дожёван.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 13:43:19 +03:00
nk
0efba7c691 feat(enrich): обогащение треков через Discogs + самохостинг обложек (WebP)
При первом появлении трека подтягиваем жанр/стиль/лейбл/год из Discogs
и сохраняем обложку в едином формате WebP 500x500 у себя (/covers). Дальше
пользователю отдаём только из своей БД — внешние сервисы в рантайме не дёргаем.

- Track: +genre/styles/label/year/discogsId/enrichStatus (миграция)
- EnrichModule: DiscogsService (поиск), CoverStorageService (sharp->webp),
  EnrichmentService (очередь с троттлингом + бэкафилл-крон каждые 10 мин)
- charts: фильтр чартов по жанру (?genre=), GET /charts/genres,
  жанр/стиль/лейбл/год в выдаче чарта и детальной странице
- main: раздача /covers статикой; docker: volume covers_data + env
  DISCOGS_TOKEN/PUBLIC_BASE_URL/COVERS_DIR
- убран MusicBrainz-фолбэк (заменён Discogs)

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-03 13:28:22 +03:00
nk
24ed44e8ab feat(now-playing): добавить name станции в ответ (для матча обложек на клиенте) 2026-06-03 12:10:04 +03:00
nk
df20e0fac6 feat(now-playing): REST GET /now-playing (ключ — id станции каталога)
Клиенту нужен now-playing с правильным маппингом id (Record now-эндпоинт
использует id now-слотов, не каталога). Отдаём текущие треки по станциям
с stationId = catalog id, чтобы клиент сопоставлял по station.id.
2026-06-03 10:51:49 +03:00
nk
e0990540b9 fix(charts): отсекать джинглы/шоу/названия станций из сбора чартов
Record API при эфире шоу/джингла возвращает название станции/сети как трек
(пустой artist/song или совпадение с названием станции). Такие записи
накапливали проигрывания и забивали топ. Теперь recordPlay их пропускает
(кэш названий станций).
2026-06-03 10:38:05 +03:00
nk
38fe92d695 feat(charts): сбор статистики проигрываний и API чартов
- модели Track / TrackPlay / TrackLike (+ миграция add_charts)
- сбор проигрываний в now-playing-поллере: при смене трека на станции
  пишется TrackPlay (нормализация artist+song -> Track), fire-and-forget
  обогащение через MusicBrainz (album/releaseDate)
- ChartsModule: GET /charts/tracks (период day/week/month/all, ранг, тренд,
  проигрывания, станции, лайки), GET /charts/tracks/:id (метрики, таймлайны
  популярности и лайков по дням, топ станций, isLiked), POST/DELETE like
- OptionalAuthGuard для публичной детальной страницы с опц. userId
2026-06-02 23:40:13 +03:00
nk
bbfec76a7b fix: use Node.js http module for reliable ICY parsing 2026-06-02 20:19:21 +03:00
nk
d0874ae9db fix: parallel ICY polling with batch size 10 and 5s timeout 2026-06-02 20:15:15 +03:00