Skip to content

Commit

Permalink
Add support for listening to OSC over TCP
Browse files Browse the repository at this point in the history
Signed-off-by: Axel Boberg <[email protected]>
  • Loading branch information
axelboberg committed Feb 19, 2024
1 parent 29dbb9f commit 81a50da
Show file tree
Hide file tree
Showing 10 changed files with 139 additions and 39 deletions.
2 changes: 2 additions & 0 deletions app/components/Preferences/shared.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { PreferencesClearStateInput } from '../PreferencesClearStateInput'
import { PreferencesSegmentedInput } from '../PreferencesSegmentedInput'
import { PreferencesShortcutsInput } from '../PreferencesShortcutsInput'
import { PreferencesVersionInput } from '../PreferencesVersionInput'
import { PreferencesBooleanInput } from '../PreferencesBooleanInput'
Expand All @@ -15,6 +16,7 @@ import { PreferencesFrameInput } from '../PreferencesFrameInput'
*/
export const inputComponents = {
shortcuts: PreferencesShortcutsInput,
segmented: PreferencesSegmentedInput,
boolean: PreferencesBooleanInput,
version: PreferencesVersionInput,
string: PreferencesStringInput,
Expand Down
19 changes: 19 additions & 0 deletions app/components/PreferencesSegmentedInput/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import React from 'react'
import './style.css'

import { SegmentedControl } from '../SegmentedControl'

export function PreferencesSegmentedInput ({ label, value, segments = [], onChange = () => {} }) {
return (
<div className='PreferencesSegmentedInput'>
<label>{label}</label>
<div className='PreferencesSegmentedInput-controlWrapper'>
<SegmentedControl
values={segments}
value={segments[value] || segments[0]}
onChange={newValue => onChange(segments.indexOf(newValue))}
/>
</div>
</div>
)
}
3 changes: 3 additions & 0 deletions app/components/PreferencesSegmentedInput/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.PreferencesSegmentedInput-controlWrapper {
margin-top: 10px;
}
24 changes: 24 additions & 0 deletions app/components/SegmentedControl/index.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import React from 'react'
import './style.css'

export function SegmentedControl ({ className = '', value: _value, values = [], onChange = () => {} }) {
return (
<div className={`SegmentedControl ${className}`}>
{
(Array.isArray(values) ? values : [])
.map(value => {
const isActive = value === _value
return (
<div
key={value}
className={`SegmentedControl-segment ${isActive ? 'is-active' : ''}`}
onClick={() => !isActive && onChange(value)}
>
{value}
</div>
)
})
}
</div>
)
}
21 changes: 21 additions & 0 deletions app/components/SegmentedControl/style.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
.SegmentedControl {
display: inline-block;
padding: 4px;

border-radius: 8px;
background: var(--base-color--shade1);
white-space: nowrap;
}

.SegmentedControl-segment {
display: inline-block;
padding: 0.75em 1em;
color: var(--base-color);

border-radius: 5px;
}

.SegmentedControl-segment.is-active {
background: var(--base-color--background);
box-shadow: 0 2px 2px rgba(0, 0, 0, 0.1);
}
5 changes: 1 addition & 4 deletions lib/schemas/setting.schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,14 @@
"properties": {
"type": {
"type": "string",
"enum": ["boolean", "theme", "number", "string", "frame", "select"]
"enum": ["boolean", "theme", "number", "string", "frame", "select", "segmented"]
},
"bind": {
"type": "string"
},
"label": {
"type": "string"
},
"repeating": {
"type": "boolean"
},
"uri": {
"type": "string",
"description": "A uri that should be used in a frame in place of the inputs"
Expand Down
24 changes: 18 additions & 6 deletions plugins/osc/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ const manifest = require('./package.json')
const Server = require('./lib/Server')
const UDPClient = require('./lib/UDPClient')
const UDPTransport = require('./lib/UDPTransport')
const TCPTransport = require('./lib/TCPTransport')

const handlers = require('./lib/handlers')
const commands = require('./lib/commands')
Expand Down Expand Up @@ -60,6 +61,11 @@ const DEFAULT_SERVER_PORT = 8080
*/
const VALID_OSC_ARG_TYPES = ['string', 'integer', 'float', 'boolean']

const SERVER_MODES = {
1: 'udp',
2: 'tcp'
}

async function initWidget () {
const cssPath = `${assets.hash}.${manifest.name}.bundle.css`
const jsPath = `${assets.hash}.${manifest.name}.bundle.js`
Expand Down Expand Up @@ -138,10 +144,16 @@ let server
* on a specific port
* @param { Number } port
*/
function setupServer (port = DEFAULT_SERVER_PORT, address) {
function setupServer (port = DEFAULT_SERVER_PORT, address, mode) {
teardownServer()

const transport = new UDPTransport()
let transport
if (mode === 'udp') {
transport = new UDPTransport()
} else if (mode === 'tcp') {
transport = new TCPTransport()
}

transport.listen(port, address)

server = new Server(transport)
Expand Down Expand Up @@ -269,10 +281,10 @@ exports.activate = async () => {
if (serverConfigSnapshot !== JSON.stringify(serverConfig)) {
serverConfigSnapshot = JSON.stringify(serverConfig)

if (!serverConfig?.active) {
if (!serverConfig?.mode) {
teardownServer()
} else {
setupServer(serverConfig?.port, serverConfig?.bindToAll ? '0.0.0.0' : '127.0.0.1')
setupServer(serverConfig?.port, serverConfig?.bindToAll ? '0.0.0.0' : '127.0.0.1', SERVER_MODES[serverConfig?.mode])
}
}
})
Expand All @@ -283,8 +295,8 @@ exports.activate = async () => {
*/
const serverConfig = await bridge.state.get(`plugins.${manifest.name}.settings.server`)
serverConfigSnapshot = JSON.stringify(serverConfig)
if (serverConfig?.active) {
setupServer(serverConfig?.port, serverConfig?.bindToAll ? '0.0.0.0' : '127.0.0.1')
if (serverConfig?.mode) {
setupServer(serverConfig?.port, serverConfig?.bindToAll ? '0.0.0.0' : '127.0.0.1', SERVER_MODES[serverConfig?.mode])
}

/*
Expand Down
38 changes: 38 additions & 0 deletions plugins/osc/lib/TCPTransport.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
// SPDX-FileCopyrightText: 2024 Sveriges Television AB
//
// SPDX-License-Identifier: MIT

const Transport = require('./Transport')

const net = require('node:net')

class TCPTransport extends Transport {
/**
* @private
* @type { net.Server }
*/
#server

constructor () {
super()
this.#server = net.createServer(socket => {
socket.on('data', data => {
this.emit('message', data)
})
socket.on('close', () => {
socket.removeAllListeners()
})
})
}

teardown () {
super.teardown()
this.#server.close()
}

listen (port, address) {
this.#server.listen(port, address)
}
}

module.exports = TCPTransport
30 changes: 7 additions & 23 deletions plugins/osc/lib/UDPTransport.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,46 +6,30 @@ const Transport = require('./Transport')

const dgram = require('node:dgram')

/**
* @typedef {{
* ipAddress: String,
* port: String
* }} UDPTransportOptions
*/
class UDPTransport extends Transport {
/**
* @private
* @type { UDPTransportOptions }
*/
_opts

/**
* @private
* @type { dgram.Socket }
*/
_socket
#socket

/**
* @param { UDPTransportOptions } opts
*/
constructor (opts = {}) {
constructor () {
super()
this._opts = opts
this._socket = dgram.createSocket('udp4')
this.#socket = dgram.createSocket('udp4')

this._socket.on('message', (msg, rinfo) => {
this.#socket.on('message', msg => {
this.emit('message', msg)
})
}

teardown () {
super.teardown()
this._socket.close()
this._socket.removeAllListeners()
this.#socket.close()
this.#socket.removeAllListeners()
}

listen (port, address) {
this._socket.bind(port, address)
this.#socket.bind(port, address)
}
}

Expand Down
12 changes: 6 additions & 6 deletions plugins/osc/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,19 +19,19 @@
"settings": [
{
"group": "OSC",
"title": "Server (UDP)",
"description": "Activate the OSC server",
"title": "Server mode",
"inputs": [
{
"type": "boolean",
"bind": "shared.plugins.bridge-plugin-osc.settings.server.active",
"label": "Active"
"type": "segmented",
"bind": "shared.plugins.bridge-plugin-osc.settings.server.mode",
"default": 0,
"segments": [ "Off", "UDP", "TCP" ]
}
]
},
{
"group": "OSC",
"title": "Server port (UDP)",
"title": "Server port",
"inputs": [
{
"type": "number",
Expand Down

0 comments on commit 81a50da

Please sign in to comment.