feat: create virtual devices (+ Add Device dropdown)
Backend:
- POST /api/create-null-sink {name} - loads null-sink module
- POST /api/create-loopback {name} - loads loopback module
- POST /api/unload-module {module_id} - unloads a module
- Fixed double-proxy-destroy crash in GraphEngine
- Graceful failure when module not available (no crash)
Frontend:
- + Add Device button in toolbar with dropdown menu
- Null Sink option (creates virtual audio output)
- Loopback Device option (creates paired input+output)
- Dropdown closes on outside click
Note: null-sink requires libpipewire-module-null-sink to be installed.
Loopback works on all PipeWire installations.
This commit is contained in:
@@ -446,6 +446,88 @@ void WebServer::setupRoutes() {
|
||||
|
||||
m_http.Options("/api/volume", cors_handler);
|
||||
m_http.Options("/api/mute", cors_handler);
|
||||
m_http.Options("/api/create-null-sink", cors_handler);
|
||||
m_http.Options("/api/create-loopback", cors_handler);
|
||||
m_http.Options("/api/unload-module", cors_handler);
|
||||
|
||||
// Create null sink: POST /api/create-null-sink {"name":"My Sink"}
|
||||
m_http.Post("/api/create-null-sink", [this](const httplib::Request &req, httplib::Response &res) {
|
||||
// Parse name from JSON - simple extraction
|
||||
std::string name = "pwweb-null";
|
||||
size_t pos = req.body.find("\"name\"");
|
||||
if (pos != std::string::npos) {
|
||||
size_t start = req.body.find('"', pos + 6);
|
||||
if (start != std::string::npos) {
|
||||
start++;
|
||||
size_t end = req.body.find('"', start);
|
||||
if (end != std::string::npos) {
|
||||
name = req.body.substr(start, end - start);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string args = "node.name=" + name +
|
||||
" media.class=Audio/Sink " +
|
||||
"audio.position=[FL FR]";
|
||||
|
||||
uint32_t id = m_engine.loadModule(
|
||||
"libpipewire-module-null-sink",
|
||||
args.c_str());
|
||||
|
||||
if (id > 0) broadcastGraph();
|
||||
|
||||
char buf[128];
|
||||
snprintf(buf, sizeof(buf), "{\"ok\":%s,\"module_id\":%u}",
|
||||
id > 0 ? "true" : "false", id);
|
||||
res.set_content(buf, "application/json");
|
||||
res.set_header("Access-Control-Allow-Origin", "*");
|
||||
});
|
||||
|
||||
// Create loopback: POST /api/create-loopback {"name":"My Loopback"}
|
||||
m_http.Post("/api/create-loopback", [this](const httplib::Request &req, httplib::Response &res) {
|
||||
std::string name = "pwweb-loopback";
|
||||
size_t pos = req.body.find("\"name\"");
|
||||
if (pos != std::string::npos) {
|
||||
size_t start = req.body.find('"', pos + 6);
|
||||
if (start != std::string::npos) {
|
||||
start++;
|
||||
size_t end = req.body.find('"', start);
|
||||
if (end != std::string::npos) {
|
||||
name = req.body.substr(start, end - start);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
std::string args = "node.name=" + name +
|
||||
" capture.props=node.name=" + name + "-capture" +
|
||||
" playback.props=node.name=" + name + "-playback";
|
||||
|
||||
uint32_t id = m_engine.loadModule(
|
||||
"libpipewire-module-loopback",
|
||||
args.c_str());
|
||||
|
||||
if (id > 0) broadcastGraph();
|
||||
|
||||
char buf[128];
|
||||
snprintf(buf, sizeof(buf), "{\"ok\":%s,\"module_id\":%u}",
|
||||
id > 0 ? "true" : "false", id);
|
||||
res.set_content(buf, "application/json");
|
||||
res.set_header("Access-Control-Allow-Origin", "*");
|
||||
});
|
||||
|
||||
// Unload module: POST /api/unload-module {"module_id":N}
|
||||
m_http.Post("/api/unload-module", [this](const httplib::Request &req, httplib::Response &res) {
|
||||
uint32_t module_id = 0;
|
||||
if (sscanf(req.body.c_str(), "{\"module_id\":%u}", &module_id) == 1) {
|
||||
bool ok = m_engine.unloadModule(module_id);
|
||||
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", "*");
|
||||
});
|
||||
}
|
||||
|
||||
// end of web_server.cpp
|
||||
|
||||
Reference in New Issue
Block a user