track == null почти не выполнялось: «безымянные» станции шлют ICY-строку без
разделителя → parseIcyTitle делает трек с пустым artist. Показываем кнопку, когда
нет РЕАЛЬНОГО трека (track null ИЛИ пустой artist/song ИЛИ song == имя станции).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- кнопка распознавания в плеере: видна только на музыкальных станциях без
метаданных эфира (track == null), показывает спиннер и результат через Toast
- распознанный трек отображается в плеере и пишется в ОТДЕЛЬНУЮ историю
распознанных (не дублируется в историю эфирных треков — гейт по ключу)
- экран Истории: переключатель «Треки эфира | Распознанные», два списка
- Room: таблица recognized_track (миграция 7→8), DAO/репозиторий
- ShazamRepository → POST /shazam/recognize/{stationId}, маппинг 503/400 в текст
- MusicGenres.isMusicStation — клиентский гейт (синхронизирован с бэкендом)
- bump backend submodule (модуль shazam)
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- AudioSpectrumAnalyzer: FFT считается ТОЛЬКО когда открыт плеер (флаг active);
раньше ~86 FFT/с молотили всегда при проигрывании (даже экран выкл) — главный
пожиратель батареи. Включается из VisualizerHost через DisposableEffect.
- Спектр (45/с) собирается в leaf VisualizerHost, а не на верху PlayerBottomSheet —
весь плеер больше не рекомпозится 45 раз/сек.
- now-playing поллинг (5с) останавливается на паузе (isPlaying.collectLatest) —
раньше на паузе зря дёргали сеть каждые 5с.
- PlayerService.onDestroy отменяет serviceScope (singleton-плеер НЕ релизим).
- refreshStations (парс ~700 станций + сеть + Room) уведён на Dispatchers.IO с
главного потока (jank/ANR на старте).
- Coil ImageLoader: память 25% + диск 100МБ (обложки не перекачиваются каждую сессию).
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
SleepSoundPlayer — процедурная генерация цветного шума через AudioTrack (розовый —
фильтр Келлета, коричневый — random walk). В таймере сна выбор звука: радио плавно
перетекает в выбранный шум (кроссфейд ≤90с), шум играет, к концу затухает — как в
спеке («Smart Sleep Fade»). В шторке таймера — чипы выбора звука.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
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>
- боковой nav-rail слева вместо нижнего бара в альбоме (SideNavRail)
- мини-плеер уезжает под контент в альбомной раскладке
- плеер эфира: двухпанельный (обложка слева, инфо/эквалайзер/контролы справа)
- плеер записи: слева управление, справа прокручиваемый список треков
- сетки станций и избранного: 4 колонки в альбоме вместо 2
- хелпер isLandscape() через LocalConfiguration
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>
Плеер живёт в 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>
- таб-бар только иконки (6 разделов не помещались с подписями)
- «Откройте радио» -> «Выберите радиостанцию»
- кнопки плеера (лайк/prev/next/запись) единого размера 24/48, ряд SpaceBetween
(кнопка записи больше не обрезается и не выбивается размером)
- текст песни: Musixmatch резал соединение -> веб-поиск трека (открывается)
- Coil crossfade для всех обложек (Images.crossfadeModel) — без «моргания» при загрузке
- basicMarquee для длинных названий трека (плеер и мини-плеер) вместо обрезки
- haptic feedback на play/pause и добавление в избранное (плеер, мини-плеер, карточка)
- навбар и мини-плеер: navigationBarsPadding — не налезают на системные кнопки
- плеер: navigationBarsPadding снизу, ряд сервисов не уходит под системную панель
- подписи сервисов без обрезки слов (Яндекс / ВК Музыка / YT Music и т.д.)
- фикс NPE при холодном старте: навбар обращается к NavDestinations напрямую,
не к companion-списку (порядок инициализации Kotlin)