Радио 2.0
Радио 2.0

Всем привет! Приемник интернет-радио, собранный в предыдущей статье Интернет-радио на базе ESP32 и ЦАП UDA1334A , имел некоторые недостатки. А именно: были частые потери сигнала wifi, медленная и глючная работа в целом, случайные перезагрузки. Короче, он перестал меня устраивать и я решил продолжить тему проигрывателя интернет радио, но на базе другой аппаратной платформы.

Варианты аппаратной части

В первую очередь, как я думал, основные проблемы связаны с использованием ESP32, ее или не хватало по мощности, или код karadio не слишком оптимизирован... Но неважно, надо искать что -то более мощное, как мне на тот момент казалось. Подключать будущее устройство планировалось так же через miniJack 3.5 к колонкам на столе.

Сначала я решил попробовать одноплатники, но тест на том же Orange Pi Zero показал удовлетворительные качества по встроенному ЦАП, решил смотреть куда то еще. Итого, сформировался примерно такой список критериев к будущей аппаратной основе:

  • скорее всего ARM

  • нормальный встроенный ЦАП

  • разьем minijack 3.5

  • питание от microUsb или usb type C

  • желательно, экран

Иии под эти критерии подходит идеально... Обычный бюджетный смартфон на Android! Уже все собрано, причем в компактном плоском корпусе. И с проигрыванием проблем нет, мультимедиа возможности - один из основных параметров более менее современного смартфона. Вопрос только к программной части, но про это дальше. К сожалению, у меня доступного для опытов смартфона не было, поэтому было решено купить просто новый бюджетный аппарат.

Выбор в итоге пал на Oppo A83. Я покупал за 3300Р версию с 4 Гб, на момент написания статьи вариант с 6 Гб (хотя, это и не важно) стоит 2300-3000Р на маркетплейсах. Более чем бюджетно, на мой взгляд.

Oppo A83
Oppo A83

Сразу оговорюсь, что телефон планировалось использовать исключительно как проигрыватель радио и дополнительно ничего там не хранить и не использовать. Итого, как тест были установлены пару приложений интернет радио из .apk, они работали, радио играли, но не те, что я хотел, и хотелось какой-то гибкой настройки. В идеале, хотелось взаимодействия со смартфоном примерно так же, как с ESP32 прошитой karadio. Когда интерфейс чисто для базовых манипуляций (поменять громкость, переключить станцию), а вся глубокая и тонкая настройка выполняется через веб интерфейс, к которому я могу подключиться с любого устройства в той же сети. Решено двигаться в этом направлении.

Приложение

Ничего одновременно и простого (чисто под интернет радио) и с наличием веб интерфейса я не нашел. Были попытки использовать Plex, но, по мне, это мегакомбайн, который, к тому же, не сразу завелся на телефоне (на Oppo A83 относительно старый Android 9.1). В итоге я немного забросил эту идею, пока не появилось чуть больше времени и подписка gpt plus :) . Решено полностью навайбкодить решение под свои нужны! Сразу оговорюсь, я не являюсь разработчиком под Android или IOT, поэтому не могу оценить качество полученного говнокода результата. По итогу, функционально, он меня более чем устроил.

Писать проект будем полностью через диалог с chatgpt. Сначала я решил определиться с решением и планируемым в использовании языков/фреймворков.

Варианты реализации

Сравнительная таблица анализа по выбору.

Вариант

Фон надёжность

Android Auto/гарнитура

Скорость разработки

Кроссплатформ

Проблемы CORS

PWA

средняя

базово (Media Session)

высокая

да (веб)

да

TWA

как PWA

как PWA

высокая

да (веб)

да

Capacitor/Cordova

средняя/высокая*

да (через плагины)

высокая

да (iOS/Android)

нет (ч/з натив)

React Native

высокая

да

средняя

да

нет

Flutter

высокая

да

средняя

да

нет

Нативный Kotlin

макс

да (лучше всего)

ниже

нет

нет

Как вариант, я рассматривал как нативное приложение, так и чисто веб, которое открывается в браузере телефона. Но в итоге решил пойти все же в натив. Не скажу точно почему (скорее просто из любопытства) решил в итоге выбрать вариант с flutter. Ну и посмотреть на dart.

Flutter Radio

Так, спустя пару вечеров вайбкодинга родилось приложение flutter radio. Я решил не менять изначальное название и не особо менял какие-то дефолтные настройки проекта, они меня более чем устроили. Сам проект доступен на github, и через github release выложил уже собранный apk для установки ни телефон.

Основные возможности приложения:

  • Тёмная тема, полноэкранный режим.

  • Поддержка MP3/AAC потоков

  • Плей/пауза, след/пред станция, громкость (слайдер).

  • Редактирование станций: добавление, удаление, изменение порядка (перетаскивание).

  • Отображение текущей играющей композиции (ICY-метаданные, если их отдаёт станция).

  • Встроенный веб-пульт по Wi-Fi:

  • Удалённое управление со страницы в локальной сети.

  • Локальный REST API (OpenAPI спецификация).

Требования (Android):

  • Android 7.0+ (API 24+) — рекомендуется Android 8.0+.

  • Подключение к интернету (для потоков).

Использованные технологии:

  • Flutter (Material 3).

  • just_audio — плеер для работы с потоками.

  • audio_service — уведомление/сервис воспроизведения и интеграция с системными контролами.

  • Чистый dart:io для HTTP-сервера (без внешних зависимостей).

  • ICY-метаданные из just_audio (icyMetadataStream).

Домашняя страница
Домашняя страница

Как предустановленные значения станций я добавил несколько станций моей любимой площадки 1.FM, значения станций можно поменять после запуска приложения. Вот, например, вариант ссылки на список доступных станций, тысячи их! Выбирайте на свой вкус и цвет.

Как базовые настройки (кроме редактирования списка самих станций) я добавил тоггл для старта веб интерфейса и тоггл по логике паузы.

Т.к. радиостанции - это бесконечный прогрессивный поток (MP3/AAC по HTTP), то плеер (ExoPlayer внутри just_audio) ведёт себя так:

  • при Pause он просто останавливает воспроизведение и оставляет уже загруженный буфер;

  • при Play он продолжает с того места буфера, где остановился (получается эффект «тайм-шифта»), а не «переподключение на живой край».

Решено сделать настройкой - или обеспечить проигрывание с живого края, или с места паузы. Кому как больше нравится. Мне больше нравится «живой край».

Из известных багов в приложении осталось неявное переключение тогглов. При первичном старте и нажатии на тоггл он не отображается переключенным. Но, если закрыть меню настроек (шестерня), и открыть снова, то он будет отображаться включенным. Попытки пофиксить это приводили gpt в цикл ошибок и генерации явно некомпилируемого кода. В итоге я решил, что поведение не критично. Тогллы активируются (обычно) один раз при старте и кривотой можно пренебречь. После активации тоггла веб интерфейса отображается адрес в локальной сети (предполагаем, что устройство в локальной сети роутера) для подключения к нему.

Меню настроек
Меню настроек

Ну, и основное, это веб интерфейс по управлению! Если не предполагается частого переключения или пауз проигрывания, то само устройство можно вообще спрятать за колонкой и забыть про него, управляя чисто через веб интерфейс.

Отображение веб интерфейса в мобильном Safari
Отображение веб интерфейса в мобильном Safari

Итого, со стабильностью работы с сетью и воспроизведением самих радиостанций проблем нет. Это основное, ради чего все затевалось. Но, были некоторые неудобства по интерфейсу.

Изначально я пытался использовать сам телефон как интерфейс для управления, поэтому явно занес в требования к приложению иммерсив, но, в итоге оказалось, что тыкать в экран стоящего на столе смартфона не так уж удобно.

Поэтому родилась идея сделать пульт для управления. По сути, это клиент, который по http будет использовать то же API веба, но при этом он будет аналоговым по интерфейсам управления. Телефон, в итоге, стоит в напечатанной клипсе за монитором.

ESP32 Radio Remote

Идея сделать пульт была реализована как связанный проект, также выложен на gihub. Аналоговый пульт, очень похожий внешне на первое решение, но внутри которого только плата ESP32. Мне очень нравилась работа экрана 2.42" OLED и отображение информации на нем, управление через энкодер. Поэтому компоновка будет примерно такой же. Запасные детали от старого проекта еще были в наличии (ESP32, экран, энкодер), поэтому решил все сделать на их базе. Также в процессе эксплуатации я решил, что переключение станций удобнее сделать на отдельные кнопки, поэтому в финальной версии предполагается использование двух кнопок без фиксации.

Кстати по кнопкам. Сначала я купил просто обычные кнопки без фиксации, но они были очень тугими, и поразмыслив, понял, что идеально на роль кнопок без фиксации подходят клавиатурные свитчи! Они бывают разные с разной тактильной отдачей, я решил остановится на cherry MX Brown (как раз они были в наличии). В итоге финальная версия корпуса и решения именно на них.

Схема сборки

Основные компоненты:

  • ESP 32 38P

  • Цифровой энкодер

  • Дисплей 2.42" OLED 4 pin

  • Кнопка без фиксации (2 шт)

Модели для печати находятся в папке /stl проекта. Я печатал корпус полностью из PLA Carbon Fiber, очень уж понравилось делать из него корпуса. Рекомендации по печати и расположению деталей аналогичны предыдущему проекту. Плату решил расположить с выводом питания слева и немного уменьшить размеры корпуса. Сборка также аналогична, требуется 10 болтов M2.5x5 мм. Единственное, что я поменял при сборке - полностью срезал изоляцию с крайних ножек ESP32. Так она не мешает фиксации ESP32 болтами на площадке корпуса.

Из основных фишек самого пульта, которые я решил реализовать:

1) Тройное нажатие энкодера - отображение текущего адреса для доступа к веб интерфейсу

2) Двойное нажатие - переключение уровня яркости. Варианты значение (0.255) можно настроить как массив значений в веб интерфейсе

3) Инверсия энкодера по громкости. Думаю, это или косяк кода, или сборки, но, для удобства стоит инвертировать поворот. Хотя, кому-то может, наоборот, удобнее как по умолчанию.

4) Ускорение при вращении громкости. Можно установить его, и при долгом вращении шаг будет сам собой увеличиваться. Мне показалось удобным. За меньший поворот делать больше громкость

5) Ночной режим. При прошествии (300с по умолчанию) и при отсутствии взаимодействия с пультом экран снижает яркость до значения (настраивается, по умолчанию 10).

6) Выключение экрана - спустя значение (3600с по умолчанию) при отсутствии взаимодействия с пультом экран вообще выключается.

7) Информация об исполнителе и композиции. В виде бегущей строки или (если помещается в 2 строки) без.

8) Настройки, которые остались как легаси в режиме добавления фичей (режим STA/AP, сохранять уровень громкости при перезагрузке), но решил их оставить.

Более подробная инструкция по заливке прошивке есть в readme проекта, не вижу смысла ее тут дублировать.

После заливки прошивки и первого запуска через какое то время (не сразу, не пофиксил этот некритичный баг) будет переход в режим AP, те плата станет точкой доступа, к которой можно подключиться и выполнить базовую настройку SSID и пароль целевого WIFI, ip адрес телефона с flutter_radio. Дополнительно можно указать токен, но его же нужно задавать в flutter_radio. Это предварительный функционал по аутентификации запросов, который пока решил не реализовывать. Для домашней сети считаю это излишним.

AP режим
AP режим

Выводы

Наверное, основной вывод, который я для себя вынес в рамках данного проекта - вайбкодинг, при должном подходе, работает! Не особо разбираясь в определенных технологиях, можно на них построить вполне рабочие решения под конкретно свои требования и задачи. Смог бы я такое сделать без LLM самостоятельно? Скорее всего да, но потратив на порядки больше времени, погружаясь в детали каждой технологии.

Будни вайбкодинга
Будни вайбкодинга

Какие основные рекомендации я вынес для себя:

  1. Выбрать фреймворк и язык. Не находил таких явных бенчмарков, но, более чем уверен, что разные модели пишут с разным качеством на разных языках. Стоит выбирать что-то, в чем используемая LLM сильна. В своем проекте я положился на выбор chatgpt.

  2. Если вы вообще не знаете с чего начать и как подступить к задаче - запустите глубокое исследование. Тот же chatgpt даже в бесплатной версии позволяет 5 исследований в мес., в рамках которых может проработать какие вообще варианты решения задачи существуют (готовые или нет).

  3. Нельзя пытаться сделать решение сразу. "Сделай приложение с фичами X, Y, Z" не работает. Нужно разбивать процесс на итерации "Сделай приложение с фичой X", "Добавь фичу Y", "Добавь фичу Z".

  4. В режиме вайбкодинга через диалог (да, пока я делал именно так) стоит сохранять промежуточные варианты, постоянно верифицируя их на ошибки. Если после очередной итерации что-то сломалось, присылать обратно текст вроде "Решение не сработало, давай добавим фичу Y, вот предыдущая работающая версия скрипта <source_code>".

  5. Очень часто надо понимать, что вообще реализуется и какими алгоритмами. Иногда LLM выбирает не самые очевидные и простые способы реализации, и нужно явно просить сделать по другому. Пример из проекта: при реализации мьюта в лайв режиме изначально планировалось прям все тормозить и при снятии паузы переподключаться к потоку. Это время и задержки. Я предложил: "А давай вместо стопа и переподключения просто будем делать громкость 0, сохраняя подключение к потоку. Тогда снятие паузы будет быстрым (возврат громкости к предыдущему значению)." - "Отличная идея, так и сделаем..."). Так вот, часто именно вы и есть генератор отличных идей :)

  6. Разделение контекстов. Если проект пилится на независимые решения,то стоит явно их разделить. При написании flutter_radio я попросл сделать скрипт под ESP32 и потом его скопировал в другой диалог, дальнейшее развитие и доработки вел уже в нем независимо от проекта по приложению на телефон. Это улучшает быстродействие и поиск ошибок.

  7. Идеального результата добиться (по крайней мере, по моему мнению) очень сложно или невозможно. Галлюцинации с некомпилируемым кодом, изменение по структуре, изменение имен переменных и т.д. Поэтому стоит вовремя остановится и решить, что этого достаточно.

    Иллюстрация
    Реакция после очередной версии кода
    Реакция после очередной версии кода
  8. В будущем точно буду попробовать режим работы в IDE, а не режим диалога, Context Engineering, MCP, вот это все.

Спасибо за внимание!

Комментарии (4)


  1. user-book
    05.11.2025 05:35

    так надо было юзать или ESP32 WROVER или хотя бы S3

    ну или к чистой ESP32 сделать обвязку с оперативкой и тд

    В интернете хватает проектов радио на ESP32 и они работают. Единый плюс для вас в том что вы сами в это влезли, разобрались и добились результата)


    1. IgnatF
      05.11.2025 05:35

      Кучу вариантов ESP 32 проверял. Во всех есть проблемы с WiFI особенно в человейниках. Как итог cделал просто веб телеграмм бота. Так как пару станций только слушаю, сделал просто четыре кнопки включить отключить. можно конечно проще, но делал под себя. Минус телеграмм бота в этом случае, нужен поток на https.


      1. user-book
        05.11.2025 05:35

        а ошибки какие валило? или просто битрейт жевало и скорость медленная была?


  1. GidraVydra
    05.11.2025 05:35

    2.3 косаря за заготовку под радио это бюджетно? На алике за эти деньги, и даже дешевле, продают готовые wifi радио. Не говоря уже о том, насколько это лютый оверинжиниринг.