категории | RSS

Энтузиаст показал реверс-инжиниринг API колонок для управления громкостью

Энтузиаст Джеймс Воган поделился, что он приобрел несколько колонок со встроенными стриминговыми сервисами, но остался недоволен их системой регулировки громкости. Он решил настроить их так, чтобы получить более точный контроль в комфортном диапазоне воспроизведения.

Обычно Воган использует около 10% от диапазона громкости, на который способны колонки. Это затрудняет для него регулировку звука, так как крошечный ползунок можно использовать только на 10% или около 15 шагов, где переход от шага 3 к шагу 4 переводит динамики с уровня «немного тихо» на уровень «определённо беспокоит соседей».

Энтузиаст изучил недокументированные веб-интерфейсы динамиков, найдя их локальный IP-адрес через свой маршрутизатор.

Он обнаружил, что динамики предоставляют довольно простой HTTP API, включая GET/api/getData и POST/api/setData, которые позволяют читать и записывать текущий уровень громкости.

Затем Воган нашёл исходный код плагина Hombridge для динамиков KEF, которые, как и JBL, используют StreamSDK. Он обнаружил, что в веб-интерфейсе динамиков есть страница, позволяющая загружать системные журналы с копией части файловой системы, в которой хранятся текущие настройки. Это помогло отследить два конкретных пути конфигурации: player/attenuation и hostlink/maxVolume. $ curl --url 'http://192.168.1.239/api/getData?path=settings:/hostlink/maxVolume&roles=@all' | jq { "timestamp": 1711309370908, "title": "Max volume setting (ARCAM-project specific)", "modifiable": true, "type": "value", "path": "settings:/hostlink/maxVolume", "defaultValue": { "type": "i32_", "i32_": 99 }, "value": { "type": "i32_", "i32_": 46 } }

Энтузиаст создал небольшую веб-страницу, которая включает полноэкранный ползунок для настройки громкости.

Для его обслуживания он создал небольшой сервер с помощью Bun. Благодаря этому удалось ограничиться одним файлом TypeScript без каких-либо зависимостей, кроме самого Bun. Веб-сервер обслуживает страницу с ползунком и пересылает запросы на динамики.// server.ts // Run this via `bun --hot server.ts` const MAX_VOLUME = 25; const SPEAKER_URL = "http://192.168.1.239"; const UPDATE_INTERVAL_SECONDS = 10; const getVolumeUrl = `${SPEAKER_URL}/api/getData?path=player:volume&roles=@all`; function html(strings: TemplateStringsArray, ...values: any[]) { return strings.reduce((result, string, i) => { return result + string + (values[i] || ""); }, ""); } const pageHtml = html`<html> <head> <title>volume</title> <style> body { margin: 0; } #volume { -webkit-appearance: none; appearance: none; margin: 0; width: 100%; height: 100%; cursor: pointer; outline: none; background: linear-gradient(to right, blue var(--volume), white 0); } /* Hide the thumb */ #volume::-webkit-slider-thumb { -webkit-appearance: none; appearance: none; width: 0; height: 0; } #volume::-moz-range-thumb { width: 0; height: 0; } </style> </head> <body> <input type="range" min="0" max="${MAX_VOLUME}" value="0" id="volume" disabled /> <script> // Yes, this is javascript embedded in HTML embedded in TypeScript. function setBackgroundGradient() { const percentage = (volume.value / ${MAX_VOLUME}) * 100; document.body.style.setProperty("--volume", `${percentage}%`); } // I only recently learned that you can reference elements by ID this way. // It's kind of horrible but also I love it on tiny pages like this. volume.oninput = async function setVolume() { fetch("volume", { method: "POST", body: JSON.stringify({ volume: volume.value }), }); setBackgroundGradient(); }; async function getVolume() { const response = await fetch("volume"); const body = await response.text(); volume.value = body; volume.disabled = false; setBackgroundGradient(); } getVolume(); setInterval(getVolume, ${UPDATE_INTERVAL_SECONDS * 1000}); </script> </body> </html>`; const server = Bun.serve({ async fetch(request) { const url = new URL(request.url); switch (url.pathname) { case "/": return new Response(pageHtml, { headers: { "Content-Type": "text/html" }, }); case "/volume": switch (request.method) { case "GET": { const response = await fetch(getVolumeUrl); const body = await response.json(); return new Response(body.value.i32_); } case "POST": { const { volume } = await request.json(); console.log(`Setting volume to ${volume}.`); // I don't want to blow out the speakers or go deaf because of a bug // somewhere else in this code, so I check for high volumes here. if (volume > MAX_VOLUME) { console.error("That's too high!", volume); return new Response("Volume too high", { status: 500 }); } return fetch(`${SPEAKER_URL}/api/setData`, { method: "POST", headers: { "Content-Type": "application/json", }, body: JSON.stringify({ path: "player:volume", role: "value", value: { type: "i32_", i32_: volume }, _nocache: new Date().getTime(), }), }); } } } console.error("Not found:", url.pathname); return new Response("Not Found", { status: 404 }); }, }); console.log(`Server running on http://localhost:${server.port}.`);

Теперь Воган намерен создать физическую ручку громкости с применением платы ESP32. 



Источник новости: habr.com

DimonVideo
2024-07-29T10:50:03Z

Здесь находятся
всего 0. За сутки здесь было 0 человек
Яндекс.Метрика