Технические вопросы
Почему Riff хранит данные в FlatBuffers, разделён на два процесса и выглядит так просто? Это более технический FAQ о решениях, продиктованных работой на недорогих автомобильных магнитолах.
Переход пользовательских настроек и истории на FlatBuffers относится к 1.1.0 RC. Публичная сборка 1.0.2 в разделе загрузок ещё использует для них SQLite/WAL.
Почему FlatBuffers, если для Android есть Room и SQLite?
Room и SQLite — обычный и разумный выбор. В Riff тоже была база SQLite с WAL. Но магнитола может получать питание через автомобильную проводку, а её накопитель и прошивка не всегда ведут себя так, как ожидает приложение.
Надёжность SQLite, включая WAL с synchronous=FULL, опирается на выполнение накопителем запросов
синхронизации. Если контроллер сообщает об успешном сохранении раньше времени или нарушает порядок
записи, внезапное отключение питания может повредить данные. SQLite прямо описывает
такие ограничения накопителей.
В 1.1.0 Riff сохраняет состояние неизменяемыми снимками FlatBuffers: сначала готовит и проверяет новую версию, затем переключает указатель на неё. Предыдущая принятая версия сохраняется для восстановления. Так у приложения есть явно определённые правила публикации, проверки и отката. Сам FlatBuffers не исправляет неисправный контроллер и не гарантирует сохранность при любом обрыве питания. Убийство процесса и искусственные ошибки записи проверяются программно; поведение самого накопителя требует реального отключения питания или специального стенда. Такая проверка Riff пока остаётся отдельной задачей.
Есть и вопрос накладных расходов. На старом мобильном процессоре лишние промежуточные объекты и работа сборщика мусора могут заметно мешать воспроизведению. FlatBuffers позволяет обращаться к данным прямо в бинарном буфере, без обязательной распаковки всей структуры. Адаптеры и интерфейс всё равно создают объекты: выбор формата не означает полного отсутствия аллокаций или GC.
Зачем два процесса и такой сложный обмен между ними?
Декодирование и ML-инференс требуют памяти и процессорного времени. На старой магнитоле запас этих ресурсов невелик. Android может завершить процесс при нехватке памяти; отдельный риск — сбой нативного декодера на конкретном файле. Высокая загрузка CPU сама по себе не означает, что Android обязательно убьёт процесс. Правила его времени жизни описаны в документации Android.
Поэтому Riff разделяет воспроизведение и подготовку библиотеки. Player держит интерфейс и звук, Indexer разбирает файлы и выполняет анализ. Гибель Indexer — предусмотренный сценарий восстановления: после перезапуска он должен продолжить работу, а Player — сохранить принятую библиотеку и текущую очередь.
Отсюда файловые дескрипторы, подтверждения, повторы и номера сеансов. Нужно отличить потерянный ответ от невыполненной команды, повтор от нового действия, старый процесс от нового. В 1.1.0 отдельно различаются «принято в память» и «сохранено на накопитель». Подробности — в статье о взаимодействии Player и Indexer.
Почему интерфейс такой простой, если есть Material Design?
Автору известны и Material Design, и Compose. Интерфейс Riff собран на Compose, но ориентирован на экран, у которого могут быть слабая контрастность, блики на солнце и неточное распознавание касаний.
Крупная контрастная кнопка на такой матрице остаётся различимой и даёт большую область попадания. Тонкие подписи, небольшие элементы и деликатные градиенты в тех же условиях могут потеряться. Поэтому в Riff много свободного места, большие зоны нажатия и отчётливое разделение переднего плана и фона. Вид намеренно утилитарный: важнее прочитать и нажать нужное.
Крупные кнопки не отменяют ограничений на действия во время движения. Эти ограничения зависят от режима работы и автомобильной системы.
Для чего экран с банковской картой?
Это физическая линейка. Размер обычной банковской карты известен: совмещая её с контуром на экране, можно измерить реальный масштаб изображения.
Android переводит dp в пиксели, используя сообщённую устройством плотность. Если одна прошивка со
значением 160 dpi используется на панелях с разной физической плотностью, одинаковые элементы
интерфейса получаются разного размера. Само наличие dp эту ошибку не исправляет.
Как Android учитывает плотность.
Калибровка даёт Riff измерение, с которым он может выбрать подходящий масштаб. Если на вашем устройстве размеры уже удобны, проходить её необязательно. Экран нужен прежде всего там, где данным прошивки о дисплее доверять нельзя.
Можно ли сделать AI-подбор лучше?
Модель можно улучшать: точнее определять свойства музыки, оценивать уверенность и реже ошибаться. Но точность распознавания — только часть задачи выбора следующего трека.
У автономной магнитолы нет истории прослушивания миллионов людей и данных о том, какие записи они обычно слушают вместе. Даже точное определение EDM ещё не объясняет, какую именно композицию вы захотите услышать сейчас. А постоянный выбор ближайшего совпадения может снова и снова приводить в один и тот же угол большой библиотеки.
Поэтому Riff использует несколько слоёв: модель описывает музыку, оценки пользователя меняют предпочтения, история уменьшает вероятность недавних повторов, а взвешенный случайный выбор даёт шанс другим подходящим трекам. Более точная модель может помочь, но разнообразие всё равно требует отдельного механизма. Случайность не исправляет ошибки распознавания; она помогает не превращать подбор в бесконечное повторение самых близких кандидатов.
Текущая модель остаётся исследовательской бета-моделью. Как из её результатов, оценок и истории получается очередь, объясняет статья о вероятностном подборе.