P0-фича из спеки. PlayerController: startSleepTimer/cancelSleepTimer — в последние
20с экспоненциальный fade-out громкости (frac^2), затем пауза + возврат громкости.
В плеере — пилюля «Таймер сна» (иконка Moon): при активном показывает остаток
M:SS акцентом. Шторка с интервалами 15/30/45/60/90/120 мин + «Выключить».
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Christmas Chill и др. остаются в разделе Radio Record (genre=Radio Record),
просто скрыт сам тег-чип через hiddenTags.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
У части станций Record поле stream_128 ведёт на мёртвый маунт {prefix}64.aacp
(404) — обложка/трек есть, а поток молчит (Summer Lounge, Beach Party, Reggae,
Mashup, Afro House, Nu Dance, Workout, Gop FM…). Поле stream_320 ({prefix}96.aacp)
живо у всех. Сменён приоритет выбора потока на stream_320.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Record API кладёт категории станции в поле "genre" (массив {id,name}), а
StationDto/ApiMapper читали только "tags" (у станции отсутствует) → у всех
станций Рекорда жанровые теги были пустыми, и чипы вроде «Лето» при клике
показывали пустоту. Добавлено поле genres (@SerialName genre), маплю genre+tags
в Station.tags. Раздел «Лето» теперь наполняется летними станциями Record
(Chill House, Beach Party, Tropical, Summer Lounge, Mashup и др.).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Многострочные stream_url (url1\nurl2) рвали воспроизведение и health-check.
Почищены на одиночный HTTPS-поток AzuraCast (16 каналов).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- боковой nav-rail слева вместо нижнего бара в альбоме (SideNavRail)
- мини-плеер уезжает под контент в альбомной раскладке
- плеер эфира: двухпанельный (обложка слева, инфо/эквалайзер/контролы справа)
- плеер записи: слева управление, справа прокручиваемый список треков
- сетки станций и избранного: 4 колонки в альбоме вместо 2
- хелпер isLandscape() через LocalConfiguration
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Корень: NowPlayingSocketClient копит трек по станции и не чистит; combine
предпочитал socket (socketMap[id] ?: restMap[id]). Если сокет один раз
прислал трек и отвалился, залипшее значение НАВСЕГДА затеняло свежий REST —
на открытом плеере трек/обложка не менялись (Radio Record и др.). Теперь
приоритет REST (он регулярно поллится), socket — фолбэк. Поллинг плеера
ускорен 10с→5с.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bug1: плеер записи (singleton ExoPlayer) не глушился при закрытии шторки и
уходе с экрана → аудио-сирота без управления, запуск радио конфликтовал.
Теперь воспроизведение записи останавливается на onDismiss и onDispose
экрана записей, а старт радио глушит плеер записи (взаимоисключение).
Bug2: обложка/трек на открытом плеере обновлялись с задержкой при записи.
Эмиссия спектра ограничена ~45/с (было ~86/с) — меньше перегруз перерисовки;
поллинг now-playing в захвате маркеров ускорен 15с→8с (точнее тайм-коды).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Визуализатор отставал/висел: медленный спад (бар держался ~300мс после
удара) и редкие обновления. Теперь FFT с перекрытием 50% (~43 обновл/с),
мгновенный рост на удар и быстрый спад (0.78→0.55), убран искусственный
троттл 33мс. Реакция плотнее попадает в бит.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Эквалайзер почти не двигался: лог-маппинг схлопывал низы в 1-2 бина,
нормализация была слабой (двигались лишь правые полосы). Переделано:
FFT 1024→2048 (разрешение низов), полосы по частотам 40Гц-16кГц со
средним по бинам, автогейн по бегущему пику (всегда полная высота),
перцептивный лифт (sqrt) + лёгкий подъём верхов. Теперь реагируют все
полосы и заметно.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Эквалайзер на плеере больше не декоративная синус-волна — реагирует на
реальный звук. Через TeeAudioProcessor подключаемся к декодированному PCM в
аудио-конвейере ExoPlayer (без разрешений/микрофона), считаем FFT → лог-полосы
(AudioSpectrumAnalyzer), PlayerController отдаёт спектр StateFlow'ом, LiveEqualizer
рисует столбики по уровням (с быстрым ростом/плавным спадом). Когда звука нет
(пауза/float-выход) — фолбэк на прежнюю синус-волну.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Вместо простой смены — эффект переворота (как страница альбома/пластинка):
старая обложка улетает передней гранью (0–90°), новая прилетает задней
(90–180°, контр-вращение чтобы не зеркалилась). Компонент FlipCover,
подключён к обложке в плеере; срабатывает при смене coverUrl трека.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Обложки наливались общей очередью (1.5с) — играющий трек ждал свою очередь.
Добавлена приоритетная дорожка: трек, который слушают сейчас, обогащается
первым (PlayerViewModel → NowPlayingRepository.enrichCoverNow). Троттл общей
очереди ускорен 1.5с→0.8с. Дедуп разнесён на enqueued/processed, чтобы
дорожки не дублировали работу.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Серверный IP забанен Apple (iTunes search 429), а Deezer из РФ пуст — обложки
перестали наливаться. Теперь iTunes-поиск делает КЛИЕНТ (его IP не забанен):
для now-playing-треков без обложки ищет арт в iTunes и шлёт ССЫЛКУ на наш
бэкенд (POST /covers/submit), сервер качает её (CDN из РФ доступен) и кладёт
WebP — дальше обложка приходит всем через /now-playing. Дедуп по треку +
троттлинг 1.5с (CoverEnrichmentManager). Сервер: host-whitelist (SSRF),
идемпотентность (first-write-wins).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Многие станции отдают по http 301 на https; ExoPlayer по умолчанию не идёт
по кросс-протокольному редиректу и не играет их. Включён общий HTTP-источник
с setAllowCrossProtocolRedirects(true) — такие станции заиграют без правки URL.
ICY-метаданные сохраняются (обрабатываются на уровне ProgressiveMediaSource).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
royalradio.space по http отдаёт 301 на https, а ExoPlayer по умолчанию НЕ
следует кросс-протокольным редиректам (http→https) — поэтому все 10 каналов
Royal Radio не воспроизводились. Потоки переведены на прямой https (200
audio/mpeg). Прод-БД тоже обновлена (для ICY now-playing).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
В разделе Romantika были только саб-каналы (Piano Covers, Love Songs,
Акустика, Прикосновение, Easy Listening), а главный «Romantika» (711) был
отключён — мёртвый поток srv21.gpmradio (и в offline-ids бэкенда). Включил
главный на рабочем HLS (hls-01-gpm.hostingradio.ru/romantika495) + фирменный
логотип Романтики (применяется ко всем каналам сети). Прод-БД: 711 → online.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Раздел Like FM был пустой — все тематические каналы «Хиты по годам» (101.ru)
закрыты (404). Like FM теперь вещает как единый канал (на сайте — только
региональные FM-частоты того же эфира). Включил главный Like FM на рабочем
HLS (hls-01-gpm.hostingradio.ru/likefm495, Москва 87.9), группа «Like FM»,
+ фирменный логотип. Бэкенд: 718 помечена online (была в offline-ids).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Radio ROKS и Kiss FM (TavR Media, хосты radioroks.ua / kissfm.ua) недоступны
с российских IP без VPN. Теперь для пользователей из РФ они полностью скрыты
— и сами станции (везде, где используется список), и их чипы-категории.
Страна определяется по IP (api.country.is → ipapi.co; при VPN вернёт страну
выходного узла, тогда станции доступны и НЕ скрываются), с фолбэком на страну
SIM/сети/локали устройства, если IP-сервис недоступен (в РФ часто заблокирован).
Код страны кэшируется (DataStore). Фильтр в GetStationsUseCase (combine со
страной) + чипы в StationsViewModel. id 741 «Радио РОКС» (stream.roks.com) —
российская, под правило не попадает.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Грид растянут под чипы (верхний contentPadding = высота чипов), а чипы
вынесены отдельным слоем поверх грида с фоном-градиентом (вверху
непрозрачный — маскирует прокрутку, книзу прозрачный). Свечение играющей
станции из верхнего ряда больше не режется границей и мягко проступает
из-под чипов категорий.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Центр свечения по cy двигался с sin(t*1.3) — некратная гармоника давала
скачок на стыке цикла. Заменено на sin(2t): значения и скорость совпадают
на t=0 и t=2π, петля повторяется ровно и плавно.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1) Горизонтальный свайп по области списка переключает фильтры-чипы в их
порядке ([Все]+жанры), выбранный чип автоскроллится в зону видимости.
Вертикальная прокрутка грида сохраняется.
2) У играющей станции в списке — мягкое радиальное свечение позади обложки,
которое «гуляет» (двигается центр) и вылезает из-под краёв, + эквалайзер-
бейдж в углу. Источник активной станции — PlayerController.currentStationId.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
navigationBarsPadding не применялся в partial-режиме ModalBottomSheet —
список треков налезал на системную навигацию. Включён skipPartiallyExpanded
(как у радио-плеера) + navigationBarsPadding на контенте.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
У RecordingPlayerSheet не было navigationBarsPadding — нижние строки
списка треков накладывались на кнопки навигации Android. Добавлен отступ
под навбар (вне скролла, чтобы низ всегда был над кнопками).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Раньше запись просто писала тело URL в файл — у HLS это m3u8-плейлист
(текст), а не аудио, поэтому EMG-станции не записывались. Добавлен
HLS-рекордер: резолвит мастер→медиа-плейлист, опрашивает его и докачивает
новые .ts-сегменты, склеивая в файл (валидный MPEG-TS, ExoPlayer играет
и перематывает). На первом проходе пишется только хвост окна — запись
начинается примерно с момента нажатия. Сплошные потоки (ICY) — прежним
путём (recordRaw). Тайм-коды треков работают и для EMG (now-playing с бэка).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1) Перемотка: записи эфира — сырой ADTS-AAC/MP3 без индексов, ExoPlayer
считал их неперематываемыми (старт всегда с нуля). Включён CBR-seeking
(DefaultExtractorsFactory.setConstantBitrateSeekingEnabled) — seek работает.
2) Тайм-коды треков: при записи фиксируются смены now-playing с offset от
начала (модель TrackMarker, колонка markers в recordings, миграция v6,
захват через NowPlayingRepository — свой поллинг, не зависит от экрана).
В плеере записи — список «Треки в записи»: тайм-код + название, тап
переходит к моменту, текущий трек подсвечен.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
refreshStations пересоздавал каталог с isFavorite=false, а insertAll
(OnConflictStrategy.REPLACE) затирал строки — отметки «избранное»
пропадали при каждом старте. Перед вставкой считываем текущие id
избранного (getFavoriteIdsOnce) и проставляем их новым записям.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Плеер живёт в ModalBottomSheet без скролла. На телефонах с высоким dpi
высоты в dp меньше (480dpi → ~800dp против ~914dp у эмулятора 420dpi),
из-за чего низ — кнопка «Текст песни» — обрезался шторкой и был виден
лишь полоской. Добавлен verticalScroll (низ доступен на любом экране) и
ужата высота (обложка 220→190, крупные отступы), чтобы влезало без скролла.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
На реальном телефоне мелкий TextButton (13sp + иконка 16dp в приглушённом
акценте) почти не виден. Заменён на пилюлю с фоном surface2: иконка 20dp,
текст 15sp medium — читается на физическом экране.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Перепроверены все 594 рабочие станции на наличие битрейт-вариантов
потока (скрипт-пробер). У 71 станции найдено по 2–4 качества
(Record-флагманы 96/64/32, zaycev 256/128/48, ВГТРК 192/128/64,
НАШЕ/Орфей/Шансон HQ и др.) — записаны в поле qualities в stations.json.
HLS (EMG) и Love (UID-привязка) корректно пропущены.
Клиент: модель StreamQuality, хранение в Room (миграция v5),
предпочтение битрейта в настройках. На экране плеера — чип текущего
качества (виден только если вариантов ≥2) и шторка «Качество звука»
со ступенями; переключение на лету без сброса now-playing, выбор
запоминается между станциями.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Сгенерены из их SVG на фирменном цвете канала, захостены у нас (/covers/love_*_s.webp),
заданы через StationLogos.byName. Вместо унылых буквенных плиток — фирменные логотипы.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Потоки Love защищены: клиент берёт UID из их player/config (со своего IP) и
подставляет в n340-поток — играет музыка. LoveStreamResolver + LoveApi. Каталог
переведён на n340. Now-playing главного Love Radio по ICY; саб-каналы трек не
отдают нигде — показываем без трека.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
При обновлении каталога тянем GET /stations/offline-ids и удаляем эти станции
из локальной БД. Мёртвые плитки теперь пропадают сами (бэк их метит health-check'ом),
без пересборки приложения. Фолбэк на статичный enabled, если бэк недоступен.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Свип по всем потокам (корректная проверка: живой = пришли заголовки 200,
мёртвый = ошибка/4xx/5xx/нет ответа). Помечены enabled=false. Активных 595/697.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Потоки не отвечают (000/404), meta now-playing пуст — каналы не вещают.
enabled=false → скрыты в приложении.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Станции с одинаковым именем в разных сетях (напр. «Deep» у Record и DFM)
показывали один и тот же трек — матч был по lowercase-имени. Каталожный id
(== station.id) уникален и совпадает со stationId в /now-playing, поэтому
матчим по id. Убран весь by-name путь (репозиторий, плеер, карточки).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
8 каналов EP (Europa Plus/Top 40/New/Party/Urban/Acoustic/ResiDance/Fresh)
переведены на рабочие emgsound HLS — играют + получают now-playing/обложки.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Плеер искал now-playing по числовому id станции, а у локальных станций (DFM)
id не совпадает с каталожным → API-путь с обложкой не срабатывал, плеер падал
на ICY из потока (без обложки). Теперь getNowPlaying матчит по id, затем по
имени станции (как карточки). DFM-обложки появляются и в плеере.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Раньше now-track (трек/исполнитель + обложка) показывался ТОЛЬКО при наличии
обложки трека — поэтому DFM-станции без обогащённой обложки оставались пустой
плиткой. Теперь: если трек известен — всегда показываем подпись, а фоном берём
обложку трека → лого станции → плитку. DFM работает как Record.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Подтягиваем обогащённые данные с бэкенда (Discogs): genre/styles/label/year
в чартах и детальной странице.
- ChartEntry/TrackStats + DTO: добавлены genre/styles/label/year
- RadiolaApi: getCharts(?genre=), новый getGenres()
- ChartsViewModel: состояние выбранного жанра + список жанров, перезагрузка
- ChartsScreen: ряд чипов-фильтров по жанру (Все + жанры),
жанр/стили чипами и «Лейбл · Год» на детальной
- убран демо-fallback (SAMPLE_CHARTS) — бэкенд живой
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
StationLogos: карта домен -> URL логотипа для сетей без своего API обложек.
Comedy Radio (Comedy FM/Club/Spa, comedy-radio.ru) -> официальный apple-touch
логотип. Расширяемо по сетям.
Для станций без своей обложки (и для Radio Record — единый стиль) карточка
показывает обложку играющего трека с тёмным градиентом и подписью трек/исполнитель.
Источник — /now-playing (теперь с name станции), матч по имени, обновление 20с.
Приоритет: трек -> логотип станции -> фирменная плитка.