feat: built-in volume management

- Volume slider on every node (green bar, draggable)
- Mute toggle button (M/m) on every node
- Backend: read volume/mute from PipeWire node props
- Backend: POST /api/volume {node_id, volume} to set volume
- Backend: POST /api/mute {node_id, mute} to toggle mute
- Graph JSON includes volume and mute fields per node
- Slider supports drag-to-adjust with mouse
This commit is contained in:
joren
2026-03-29 23:21:39 +02:00
parent 8c6a1f44c1
commit 65db5daa7c
7 changed files with 206 additions and 5 deletions

View File

@@ -109,6 +109,8 @@ std::string WebServer::buildGraphJson() const {
<< ",\"media_name\":\"" << escapeJson(n.media_name) << "\""
<< ",\"mode\":\"" << portModeStr(n.mode) << "\""
<< ",\"node_type\":\"" << nodeTypeStr(n.node_type) << "\""
<< ",\"volume\":" << n.volume
<< ",\"mute\":" << (n.mute ? "true" : "false")
<< ",\"port_ids\":[";
bool first_p = true;
for (uint32_t pid : n.port_ids) {
@@ -403,6 +405,47 @@ void WebServer::setupRoutes() {
res.set_content(buf, "application/json");
res.set_header("Access-Control-Allow-Origin", "*");
});
// Volume: POST /api/volume {"node_id": N, "volume": 0.0-1.0}
m_http.Post("/api/volume", [this](const httplib::Request &req, httplib::Response &res) {
uint32_t node_id = 0;
float volume = 0;
if (sscanf(req.body.c_str(),
"{\"node_id\":%u,\"volume\":%f}", &node_id, &volume) == 2 ||
sscanf(req.body.c_str(),
"{\"node_id\":%u, \"volume\":%f}", &node_id, &volume) == 2)
{
if (volume < 0) volume = 0;
if (volume > 1.5) volume = 1.5;
bool ok = m_engine.setNodeVolume(node_id, volume);
if (ok) broadcastGraph();
res.set_content(ok ? "{\"ok\":true}" : "{\"ok\":false}", "application/json");
} else {
res.status = 400;
res.set_content("{\"error\":\"invalid json\"}", "application/json");
}
res.set_header("Access-Control-Allow-Origin", "*");
});
// Mute: POST /api/mute {"node_id": N, "mute": true/false}
m_http.Post("/api/mute", [this](const httplib::Request &req, httplib::Response &res) {
uint32_t node_id = 0;
if (sscanf(req.body.c_str(), "{\"node_id\":%u", &node_id) == 1 ||
sscanf(req.body.c_str(), "{\"node_id\": %u", &node_id) == 1)
{
bool mute = req.body.find("true") != std::string::npos;
bool ok = m_engine.setNodeMute(node_id, mute);
if (ok) broadcastGraph();
res.set_content(ok ? "{\"ok\":true}" : "{\"ok\":false}", "application/json");
} else {
res.status = 400;
res.set_content("{\"error\":\"invalid json\"}", "application/json");
}
res.set_header("Access-Control-Allow-Origin", "*");
});
m_http.Options("/api/volume", cors_handler);
m_http.Options("/api/mute", cors_handler);
}
// end of web_server.cpp