OSC bind address
All checks were successful
CI / React UI Build (push) Successful in 11s
CI / Native Windows Build And Tests (push) Successful in 2m41s
CI / Windows Release Package (push) Successful in 2m30s

This commit is contained in:
Aiden
2026-05-10 17:23:28 +10:00
parent a3635b5d31
commit f11d531e0c
11 changed files with 81 additions and 9 deletions

View File

@@ -141,6 +141,7 @@ Current native test coverage includes:
{
"shaderLibrary": "shaders",
"serverPort": 8080,
"oscBindAddress": "127.0.0.1",
"oscPort": 9000,
"inputVideoFormat": "1080p",
"inputFrameRate": "59.94",
@@ -203,13 +204,13 @@ runtime/screenshots/
## OSC Control
The native host also listens for local OSC parameter control on the configured `oscPort`:
The native host also listens for OSC parameter control on the configured `oscBindAddress` and `oscPort`:
```text
/VideoShaderToys/{LayerNameOrID}/{ParameterNameOrID}
```
For example, `/VideoShaderToys/VHS/intensity` updates the `intensity` parameter on the first matching `VHS` layer. The listener accepts float, integer, string, and boolean OSC values, and validates them through the same shader parameter path as the REST API. See `docs/OSC_CONTROL.md` for details.
For example, `/VideoShaderToys/VHS/intensity` updates the `intensity` parameter on the first matching `VHS` layer. The listener accepts float, integer, string, and boolean OSC values, and validates them through the same shader parameter path as the REST API. The default bind address is `127.0.0.1`; set `oscBindAddress` to `0.0.0.0` to accept OSC on all IPv4 interfaces. See `docs/OSC_CONTROL.md` for details.
## Shader Packages

View File

@@ -55,7 +55,7 @@ OscServer::~OscServer()
Stop();
}
bool OscServer::Start(unsigned short port, const Callbacks& callbacks, std::string& error)
bool OscServer::Start(const std::string& bindAddress, unsigned short port, const Callbacks& callbacks, std::string& error)
{
if (port == 0)
return true;
@@ -78,11 +78,15 @@ bool OscServer::Start(unsigned short port, const Callbacks& callbacks, std::stri
sockaddr_in address = {};
address.sin_family = AF_INET;
address.sin_addr.s_addr = htonl(INADDR_LOOPBACK);
if (!TryParseBindAddress(bindAddress, address.sin_addr, error))
{
mSocket.reset();
return false;
}
address.sin_port = htons(static_cast<u_short>(port));
if (bind(mSocket.get(), reinterpret_cast<sockaddr*>(&address), sizeof(address)) != 0)
{
error = "Could not bind OSC listener to UDP port " + std::to_string(port) + ".";
error = "Could not bind OSC listener to " + bindAddress + ":" + std::to_string(port) + ".";
mSocket.reset();
return false;
}
@@ -92,6 +96,24 @@ bool OscServer::Start(unsigned short port, const Callbacks& callbacks, std::stri
return true;
}
bool OscServer::TryParseBindAddress(const std::string& bindAddress, in_addr& address, std::string& error)
{
if (bindAddress.empty())
{
error = "OSC bind address must not be empty.";
return false;
}
address = {};
if (InetPtonA(AF_INET, bindAddress.c_str(), &address) != 1)
{
error = "Invalid OSC bind address '" + bindAddress + "'. Use an IPv4 address such as 127.0.0.1 or 0.0.0.0.";
return false;
}
return true;
}
void OscServer::Stop()
{
mRunning = false;

View File

@@ -20,7 +20,7 @@ public:
OscServer();
~OscServer();
bool Start(unsigned short port, const Callbacks& callbacks, std::string& error);
bool Start(const std::string& bindAddress, unsigned short port, const Callbacks& callbacks, std::string& error);
void Stop();
unsigned short GetPort() const { return mPort; }
@@ -37,6 +37,7 @@ private:
void ServerLoop();
bool DecodeMessage(const char* data, int byteCount, OscMessage& message, std::string& error) const;
bool DispatchMessage(const OscMessage& message, std::string& error) const;
static bool TryParseBindAddress(const std::string& bindAddress, in_addr& address, std::string& error);
static bool DecodeArgument(const char* data, int byteCount, int& offset, char valueType, std::string& valueJson);
static bool ReadPaddedString(const char* data, int byteCount, int& offset, std::string& value);
static bool ReadInt32(const char* data, int byteCount, int& offset, int& value);

View File

@@ -44,7 +44,7 @@ bool StartRuntimeControlServices(
oscCallbacks.updateParameter = [&composite](const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& actionError) {
return composite.UpdateLayerParameterByControlKeyJson(layerKey, parameterKey, valueJson, actionError);
};
if (runtimeHost.GetOscPort() > 0 && !oscServer.Start(runtimeHost.GetOscPort(), oscCallbacks, error))
if (runtimeHost.GetOscPort() > 0 && !oscServer.Start(runtimeHost.GetOscBindAddress(), runtimeHost.GetOscPort(), oscCallbacks, error))
return false;
return true;

View File

@@ -60,6 +60,7 @@ public:
bool RequestScreenshot(std::string& error);
unsigned short GetControlServerPort() const;
unsigned short GetOscPort() const;
std::string GetOscBindAddress() const;
std::string GetControlUrl() const;
std::string GetDocsUrl() const;
std::string GetOscAddress() const;

View File

@@ -15,6 +15,11 @@ unsigned short OpenGLComposite::GetOscPort() const
return mRuntimeHost ? mRuntimeHost->GetOscPort() : 0;
}
std::string OpenGLComposite::GetOscBindAddress() const
{
return mRuntimeHost ? mRuntimeHost->GetOscBindAddress() : "127.0.0.1";
}
std::string OpenGLComposite::GetControlUrl() const
{
return "http://127.0.0.1:" + std::to_string(GetControlServerPort()) + "/";
@@ -27,7 +32,7 @@ std::string OpenGLComposite::GetDocsUrl() const
std::string OpenGLComposite::GetOscAddress() const
{
return "udp://127.0.0.1:" + std::to_string(GetOscPort()) + " /VideoShaderToys/{Layer}/{Parameter}";
return "udp://" + GetOscBindAddress() + ":" + std::to_string(GetOscPort()) + " /VideoShaderToys/{Layer}/{Parameter}";
}
bool OpenGLComposite::AddLayer(const std::string& shaderId, std::string& error)

View File

@@ -1474,6 +1474,8 @@ bool RuntimeHost::LoadConfig(std::string& error)
mConfig.serverPort = static_cast<unsigned short>(serverPortValue->asNumber(mConfig.serverPort));
if (const JsonValue* oscPortValue = configJson.find("oscPort"))
mConfig.oscPort = static_cast<unsigned short>(oscPortValue->asNumber(mConfig.oscPort));
if (const JsonValue* oscBindAddressValue = configJson.find("oscBindAddress"))
mConfig.oscBindAddress = oscBindAddressValue->asString();
if (const JsonValue* autoReloadValue = configJson.find("autoReload"))
mConfig.autoReload = autoReloadValue->asBoolean(mConfig.autoReload);
if (const JsonValue* maxTemporalHistoryFramesValue = configJson.find("maxTemporalHistoryFrames"))
@@ -1870,6 +1872,7 @@ JsonValue RuntimeHost::BuildStateValue() const
JsonValue app = JsonValue::MakeObject();
app.set("serverPort", JsonValue(static_cast<double>(mServerPort)));
app.set("oscPort", JsonValue(static_cast<double>(mConfig.oscPort)));
app.set("oscBindAddress", JsonValue(mConfig.oscBindAddress));
app.set("autoReload", JsonValue(mAutoReloadEnabled));
app.set("maxTemporalHistoryFrames", JsonValue(static_cast<double>(mConfig.maxTemporalHistoryFrames)));
app.set("previewFps", JsonValue(static_cast<double>(mConfig.previewFps)));

View File

@@ -64,6 +64,7 @@ public:
const std::filesystem::path& GetRuntimeRoot() const { return mRuntimeRoot; }
unsigned short GetServerPort() const { return mServerPort; }
unsigned short GetOscPort() const { return mConfig.oscPort; }
const std::string& GetOscBindAddress() const { return mConfig.oscBindAddress; }
unsigned GetMaxTemporalHistoryFrames() const { return mConfig.maxTemporalHistoryFrames; }
unsigned GetPreviewFps() const { return mConfig.previewFps; }
bool ExternalKeyingEnabled() const { return mConfig.enableExternalKeying; }
@@ -80,6 +81,7 @@ private:
std::string shaderLibrary = "shaders";
unsigned short serverPort = 8080;
unsigned short oscPort = 9000;
std::string oscBindAddress = "127.0.0.1";
bool autoReload = true;
unsigned maxTemporalHistoryFrames = 4;
unsigned previewFps = 30;

View File

@@ -1,6 +1,7 @@
{
"shaderLibrary": "shaders",
"serverPort": 8080,
"oscBindAddress": "127.0.0.1",
"oscPort": 9000,
"inputVideoFormat": "1080p",
"inputFrameRate": "59.94",

View File

@@ -8,11 +8,13 @@ Set the UDP port in `config/runtime-host.json`:
```json
{
"oscBindAddress": "127.0.0.1",
"oscPort": 9000
}
```
Set `oscPort` to `0` to disable the OSC listener.
Set `oscBindAddress` to `127.0.0.1` to keep OSC local to the host, or `0.0.0.0` to listen on all IPv4 interfaces.
## Address Pattern
@@ -114,10 +116,21 @@ send('127.0.0.1:9000', '/VideoShaderToys/fisheye-reproject/tiltDegrees', {type:
## Network
The listener binds to localhost only:
By default the listener binds to localhost only:
```text
127.0.0.1:<oscPort>
```
This keeps the control surface local to the machine running Video Shader Toys.
To accept OSC from other machines on the network, set:
```json
{
"oscBindAddress": "0.0.0.0",
"oscPort": 9000
}
```
That listens on all IPv4 interfaces, so make sure your firewall and network are configured appropriately.

View File

@@ -74,6 +74,11 @@ struct OscServerTestAccess
return server.DispatchMessage(message, error);
}
static bool TryParseBindAddress(const std::string& bindAddress, in_addr& address, std::string& error)
{
return OscServer::TryParseBindAddress(bindAddress, address, error);
}
static void SetUpdateParameterCallback(
OscServer& server,
const std::function<bool(const std::string&, const std::string&, const std::string&, std::string&)>& callback)
@@ -191,6 +196,23 @@ void TestRejectsUnsupportedAddress()
Expect(!called, "unsupported OSC namespace does not invoke callback");
Expect(!error.empty(), "unsupported OSC address reports an error");
}
void TestParsesOscBindAddress()
{
in_addr loopback = {};
std::string error;
Expect(OscServerTestAccess::TryParseBindAddress("127.0.0.1", loopback, error), "loopback OSC bind address parses");
Expect(loopback.S_un.S_addr != 0, "loopback OSC bind address produces a socket address");
in_addr wildcard = {};
error.clear();
Expect(OscServerTestAccess::TryParseBindAddress("0.0.0.0", wildcard, error), "wildcard OSC bind address parses");
in_addr invalid = {};
error.clear();
Expect(!OscServerTestAccess::TryParseBindAddress("localhost", invalid, error), "hostname OSC bind address is rejected");
Expect(!error.empty(), "invalid OSC bind address reports an error");
}
}
int main()
@@ -201,6 +223,7 @@ int main()
TestDecodeIntStringAndBoolMessages();
TestDispatchValidAddress();
TestRejectsUnsupportedAddress();
TestParsesOscBindAddress();
if (gFailures != 0)
{