OSC bind address
This commit is contained in:
@@ -141,6 +141,7 @@ Current native test coverage includes:
|
|||||||
{
|
{
|
||||||
"shaderLibrary": "shaders",
|
"shaderLibrary": "shaders",
|
||||||
"serverPort": 8080,
|
"serverPort": 8080,
|
||||||
|
"oscBindAddress": "127.0.0.1",
|
||||||
"oscPort": 9000,
|
"oscPort": 9000,
|
||||||
"inputVideoFormat": "1080p",
|
"inputVideoFormat": "1080p",
|
||||||
"inputFrameRate": "59.94",
|
"inputFrameRate": "59.94",
|
||||||
@@ -203,13 +204,13 @@ runtime/screenshots/
|
|||||||
|
|
||||||
## OSC Control
|
## 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
|
```text
|
||||||
/VideoShaderToys/{LayerNameOrID}/{ParameterNameOrID}
|
/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
|
## Shader Packages
|
||||||
|
|
||||||
|
|||||||
@@ -55,7 +55,7 @@ OscServer::~OscServer()
|
|||||||
Stop();
|
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)
|
if (port == 0)
|
||||||
return true;
|
return true;
|
||||||
@@ -78,11 +78,15 @@ bool OscServer::Start(unsigned short port, const Callbacks& callbacks, std::stri
|
|||||||
|
|
||||||
sockaddr_in address = {};
|
sockaddr_in address = {};
|
||||||
address.sin_family = AF_INET;
|
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));
|
address.sin_port = htons(static_cast<u_short>(port));
|
||||||
if (bind(mSocket.get(), reinterpret_cast<sockaddr*>(&address), sizeof(address)) != 0)
|
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();
|
mSocket.reset();
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@@ -92,6 +96,24 @@ bool OscServer::Start(unsigned short port, const Callbacks& callbacks, std::stri
|
|||||||
return true;
|
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()
|
void OscServer::Stop()
|
||||||
{
|
{
|
||||||
mRunning = false;
|
mRunning = false;
|
||||||
|
|||||||
@@ -20,7 +20,7 @@ public:
|
|||||||
OscServer();
|
OscServer();
|
||||||
~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();
|
void Stop();
|
||||||
|
|
||||||
unsigned short GetPort() const { return mPort; }
|
unsigned short GetPort() const { return mPort; }
|
||||||
@@ -37,6 +37,7 @@ private:
|
|||||||
void ServerLoop();
|
void ServerLoop();
|
||||||
bool DecodeMessage(const char* data, int byteCount, OscMessage& message, std::string& error) const;
|
bool DecodeMessage(const char* data, int byteCount, OscMessage& message, std::string& error) const;
|
||||||
bool DispatchMessage(const 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 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 ReadPaddedString(const char* data, int byteCount, int& offset, std::string& value);
|
||||||
static bool ReadInt32(const char* data, int byteCount, int& offset, int& value);
|
static bool ReadInt32(const char* data, int byteCount, int& offset, int& value);
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ bool StartRuntimeControlServices(
|
|||||||
oscCallbacks.updateParameter = [&composite](const std::string& layerKey, const std::string& parameterKey, const std::string& valueJson, std::string& actionError) {
|
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);
|
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 false;
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -60,6 +60,7 @@ public:
|
|||||||
bool RequestScreenshot(std::string& error);
|
bool RequestScreenshot(std::string& error);
|
||||||
unsigned short GetControlServerPort() const;
|
unsigned short GetControlServerPort() const;
|
||||||
unsigned short GetOscPort() const;
|
unsigned short GetOscPort() const;
|
||||||
|
std::string GetOscBindAddress() const;
|
||||||
std::string GetControlUrl() const;
|
std::string GetControlUrl() const;
|
||||||
std::string GetDocsUrl() const;
|
std::string GetDocsUrl() const;
|
||||||
std::string GetOscAddress() const;
|
std::string GetOscAddress() const;
|
||||||
|
|||||||
@@ -15,6 +15,11 @@ unsigned short OpenGLComposite::GetOscPort() const
|
|||||||
return mRuntimeHost ? mRuntimeHost->GetOscPort() : 0;
|
return mRuntimeHost ? mRuntimeHost->GetOscPort() : 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
std::string OpenGLComposite::GetOscBindAddress() const
|
||||||
|
{
|
||||||
|
return mRuntimeHost ? mRuntimeHost->GetOscBindAddress() : "127.0.0.1";
|
||||||
|
}
|
||||||
|
|
||||||
std::string OpenGLComposite::GetControlUrl() const
|
std::string OpenGLComposite::GetControlUrl() const
|
||||||
{
|
{
|
||||||
return "http://127.0.0.1:" + std::to_string(GetControlServerPort()) + "/";
|
return "http://127.0.0.1:" + std::to_string(GetControlServerPort()) + "/";
|
||||||
@@ -27,7 +32,7 @@ std::string OpenGLComposite::GetDocsUrl() const
|
|||||||
|
|
||||||
std::string OpenGLComposite::GetOscAddress() 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)
|
bool OpenGLComposite::AddLayer(const std::string& shaderId, std::string& error)
|
||||||
|
|||||||
@@ -1474,6 +1474,8 @@ bool RuntimeHost::LoadConfig(std::string& error)
|
|||||||
mConfig.serverPort = static_cast<unsigned short>(serverPortValue->asNumber(mConfig.serverPort));
|
mConfig.serverPort = static_cast<unsigned short>(serverPortValue->asNumber(mConfig.serverPort));
|
||||||
if (const JsonValue* oscPortValue = configJson.find("oscPort"))
|
if (const JsonValue* oscPortValue = configJson.find("oscPort"))
|
||||||
mConfig.oscPort = static_cast<unsigned short>(oscPortValue->asNumber(mConfig.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"))
|
if (const JsonValue* autoReloadValue = configJson.find("autoReload"))
|
||||||
mConfig.autoReload = autoReloadValue->asBoolean(mConfig.autoReload);
|
mConfig.autoReload = autoReloadValue->asBoolean(mConfig.autoReload);
|
||||||
if (const JsonValue* maxTemporalHistoryFramesValue = configJson.find("maxTemporalHistoryFrames"))
|
if (const JsonValue* maxTemporalHistoryFramesValue = configJson.find("maxTemporalHistoryFrames"))
|
||||||
@@ -1870,6 +1872,7 @@ JsonValue RuntimeHost::BuildStateValue() const
|
|||||||
JsonValue app = JsonValue::MakeObject();
|
JsonValue app = JsonValue::MakeObject();
|
||||||
app.set("serverPort", JsonValue(static_cast<double>(mServerPort)));
|
app.set("serverPort", JsonValue(static_cast<double>(mServerPort)));
|
||||||
app.set("oscPort", JsonValue(static_cast<double>(mConfig.oscPort)));
|
app.set("oscPort", JsonValue(static_cast<double>(mConfig.oscPort)));
|
||||||
|
app.set("oscBindAddress", JsonValue(mConfig.oscBindAddress));
|
||||||
app.set("autoReload", JsonValue(mAutoReloadEnabled));
|
app.set("autoReload", JsonValue(mAutoReloadEnabled));
|
||||||
app.set("maxTemporalHistoryFrames", JsonValue(static_cast<double>(mConfig.maxTemporalHistoryFrames)));
|
app.set("maxTemporalHistoryFrames", JsonValue(static_cast<double>(mConfig.maxTemporalHistoryFrames)));
|
||||||
app.set("previewFps", JsonValue(static_cast<double>(mConfig.previewFps)));
|
app.set("previewFps", JsonValue(static_cast<double>(mConfig.previewFps)));
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ public:
|
|||||||
const std::filesystem::path& GetRuntimeRoot() const { return mRuntimeRoot; }
|
const std::filesystem::path& GetRuntimeRoot() const { return mRuntimeRoot; }
|
||||||
unsigned short GetServerPort() const { return mServerPort; }
|
unsigned short GetServerPort() const { return mServerPort; }
|
||||||
unsigned short GetOscPort() const { return mConfig.oscPort; }
|
unsigned short GetOscPort() const { return mConfig.oscPort; }
|
||||||
|
const std::string& GetOscBindAddress() const { return mConfig.oscBindAddress; }
|
||||||
unsigned GetMaxTemporalHistoryFrames() const { return mConfig.maxTemporalHistoryFrames; }
|
unsigned GetMaxTemporalHistoryFrames() const { return mConfig.maxTemporalHistoryFrames; }
|
||||||
unsigned GetPreviewFps() const { return mConfig.previewFps; }
|
unsigned GetPreviewFps() const { return mConfig.previewFps; }
|
||||||
bool ExternalKeyingEnabled() const { return mConfig.enableExternalKeying; }
|
bool ExternalKeyingEnabled() const { return mConfig.enableExternalKeying; }
|
||||||
@@ -80,6 +81,7 @@ private:
|
|||||||
std::string shaderLibrary = "shaders";
|
std::string shaderLibrary = "shaders";
|
||||||
unsigned short serverPort = 8080;
|
unsigned short serverPort = 8080;
|
||||||
unsigned short oscPort = 9000;
|
unsigned short oscPort = 9000;
|
||||||
|
std::string oscBindAddress = "127.0.0.1";
|
||||||
bool autoReload = true;
|
bool autoReload = true;
|
||||||
unsigned maxTemporalHistoryFrames = 4;
|
unsigned maxTemporalHistoryFrames = 4;
|
||||||
unsigned previewFps = 30;
|
unsigned previewFps = 30;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
{
|
{
|
||||||
"shaderLibrary": "shaders",
|
"shaderLibrary": "shaders",
|
||||||
"serverPort": 8080,
|
"serverPort": 8080,
|
||||||
|
"oscBindAddress": "127.0.0.1",
|
||||||
"oscPort": 9000,
|
"oscPort": 9000,
|
||||||
"inputVideoFormat": "1080p",
|
"inputVideoFormat": "1080p",
|
||||||
"inputFrameRate": "59.94",
|
"inputFrameRate": "59.94",
|
||||||
|
|||||||
@@ -8,11 +8,13 @@ Set the UDP port in `config/runtime-host.json`:
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
|
"oscBindAddress": "127.0.0.1",
|
||||||
"oscPort": 9000
|
"oscPort": 9000
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
Set `oscPort` to `0` to disable the OSC listener.
|
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
|
## Address Pattern
|
||||||
|
|
||||||
@@ -114,10 +116,21 @@ send('127.0.0.1:9000', '/VideoShaderToys/fisheye-reproject/tiltDegrees', {type:
|
|||||||
|
|
||||||
## Network
|
## Network
|
||||||
|
|
||||||
The listener binds to localhost only:
|
By default the listener binds to localhost only:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
127.0.0.1:<oscPort>
|
127.0.0.1:<oscPort>
|
||||||
```
|
```
|
||||||
|
|
||||||
This keeps the control surface local to the machine running Video Shader Toys.
|
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.
|
||||||
|
|||||||
@@ -74,6 +74,11 @@ struct OscServerTestAccess
|
|||||||
return server.DispatchMessage(message, error);
|
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(
|
static void SetUpdateParameterCallback(
|
||||||
OscServer& server,
|
OscServer& server,
|
||||||
const std::function<bool(const std::string&, const std::string&, const std::string&, std::string&)>& callback)
|
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(!called, "unsupported OSC namespace does not invoke callback");
|
||||||
Expect(!error.empty(), "unsupported OSC address reports an error");
|
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()
|
int main()
|
||||||
@@ -201,6 +223,7 @@ int main()
|
|||||||
TestDecodeIntStringAndBoolMessages();
|
TestDecodeIntStringAndBoolMessages();
|
||||||
TestDispatchValidAddress();
|
TestDispatchValidAddress();
|
||||||
TestRejectsUnsupportedAddress();
|
TestRejectsUnsupportedAddress();
|
||||||
|
TestParsesOscBindAddress();
|
||||||
|
|
||||||
if (gFailures != 0)
|
if (gFailures != 0)
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user