Skip to content

Commit

Permalink
Add basic OSC support
Browse files Browse the repository at this point in the history
Signed-off-by: Axel Boberg <[email protected]>
  • Loading branch information
axelboberg committed Jan 21, 2024
1 parent d90f1b4 commit 6a35f38
Show file tree
Hide file tree
Showing 9 changed files with 402 additions and 9 deletions.
24 changes: 15 additions & 9 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,21 @@

Welcome to the full documentation for Bridge. Whether you're want to build your own extension or are looking for more information about the internals, this is the place to find it.

**This project is still in early development and more content will be added periodically**
## Bundled plugins
- [Inspector](/plugins/inspector/README.md)
- [Rundown](/plugins/rundown/README.md)
- [State](/plugins/state/README.md)
- Clock
- [Caspar](/plugins/caspar/README.md)
- [OSC](/plugins/osc/README.md)

## Developing plugins
- [Guide](/docs/plugins/README.md)
- [API reference](/docs/api/README.md)

## Internals
- [Architecture](/docs/architecture.md)
- [Project structure](/docs/structure.md)

## Terminology
![Methodology](/media/docs/architecture/methodology.png)
Expand All @@ -18,11 +32,3 @@ Widgets are web views hosted by plugins. They provide a user interface that can

### Plugin
Plugins are extensions to Bridge that add specific functionality. They are primarily run in the main process and have access to the full Bridge and Nodejs apis. A plugin can register none, one or multiple widgets that are available in the workspace. They can also react to events and provide their own functionality through commands.

## Plugins
- [Guide](/docs/plugins/README.md)
- [API reference](/docs/api/README.md)

## Internals
- [Architecture](/docs/architecture.md)
- [Project structure](/docs/structure.md)
37 changes: 37 additions & 0 deletions plugins/osc/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# OSC plugin
Bridge's default OSC plugin

## Description
This plugis allows external services to communicate to Bridge using the Open Sound Control protocol (OSC).

## Table of contents
- [Description](#description)
- [Reference](#reference)

## Reference
The following list is a reference of the OSC paths that are available within this plugin.

### `/api/commands/executeCommand`
Execute a command

#### Arguments
| Index | Type | Description |
| --- | --- | --- |
| 0 | String | The id of the command to execute |
| 1...n | any | Arguments that will be passed to the command |

### `/api/items/playItem`
Play an item

#### Arguments
| Index | Type | Description |
| --- | --- | --- |
| 0 | String | The id of the item to play |

### `/api/items/stopItem`
Stop an item

#### Arguments
| Index | Type | Description |
| --- | --- | --- |
| 0 | String | The id of the item to stop |
126 changes: 126 additions & 0 deletions plugins/osc/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// SPDX-FileCopyrightText: 2024 Sveriges Television AB
//
// SPDX-License-Identifier: MIT

/**
* @type { import('../../api').Api }
*/
const bridge = require('bridge')

const manifest = require('./package.json')

const Server = require('./lib/Server')
const UDPTransport = require('./lib/UDPTransport')

const handlers = require('./lib/handlers')

const Router = require('obj-router')
const router = new Router(handlers)

/**
* The default server port,
*
* this will be used as the default
* settings value if no other is provided
*
* @type { Number }
*/
const DEFAULT_SERVER_PORT = 8080

exports.activate = async () => {
/**
* A reference to the current server
* @type { Server | undefined }
*/
let server

/**
* Set up the server and start listen
* on a specific port
* @param { Number } port
*/
function setupServer (port = DEFAULT_SERVER_PORT, address) {
teardownServer()

const transport = new UDPTransport()
transport.listen(port, address)

server = new Server(transport)
server.on('message', async osc => {
try {
await router.execute(osc.address, osc)
} catch (e) {
console.log(e)
}
})
}

/**
* Tear down the
* current server
*/
function teardownServer () {
if (!server) {
return
}
server.teardown()
server = undefined
}

/**
* A snapshot of the current
* server configuration used
* for diffing against state
* updates
* @type { String }
*/
let serverConfigSnapshot

/*
Listen to state changes and compare
the current server configuration
Only set up the server if the
configuration has changed
*/
bridge.events.on('state.change', newState => {
const serverConfig = newState?.plugins?.[manifest.name]?.settings.server
if (serverConfigSnapshot !== JSON.stringify(serverConfig)) {
serverConfigSnapshot = JSON.stringify(serverConfig)

if (!serverConfig?.active) {
teardownServer()
} else {
setupServer(serverConfig?.port, serverConfig?.bindToAll ? '0.0.0.0' : '127.0.0.1')
}
}
})

/*
Set up the server on
startup if active
*/
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')
}

/*
Set defaults
if missing
*/
if (!serverConfig?.port) {
bridge.state.apply({
plugins: {
[manifest.name]: {
settings: {
server: {
port: DEFAULT_SERVER_PORT
}
}
}
}
})
}
}
43 changes: 43 additions & 0 deletions plugins/osc/lib/Server.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
// SPDX-FileCopyrightText: 2024 Sveriges Television AB
//
// SPDX-License-Identifier: MIT

const EventEmitter = require('events')

const osc = require('osc-min')

class Server extends EventEmitter {
/**
* @private
* @type { import('./Transport') }
*/
_transport

/**
* @param { import('./Transport') } transport
*/
constructor (transport) {
super()

this._transport = transport
this._transport.on('message', msg => {
const processed = this._process(msg)
this.emit('message', processed)
})
}

/**
* @private
* @param { Buffer } buffer
*/
_process (buffer) {
return osc.fromBuffer(buffer)
}

teardown () {
this._transport.teardown()
this.removeAllListeners()
}
}

module.exports = Server
20 changes: 20 additions & 0 deletions plugins/osc/lib/Transport.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-FileCopyrightText: 2024 Sveriges Television AB
//
// SPDX-License-Identifier: MIT

const EventEmitter = require('events')

/**
* A base class for transports
* for the Server class
*/
class Transport extends EventEmitter {
/**
* Tear down this transport
*/
teardown () {
this.removeAllListeners()
}
}

module.exports = Transport
52 changes: 52 additions & 0 deletions plugins/osc/lib/UDPTransport.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
// SPDX-FileCopyrightText: 2024 Sveriges Television AB
//
// SPDX-License-Identifier: MIT

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

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

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

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

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

module.exports = UDPTransport
20 changes: 20 additions & 0 deletions plugins/osc/lib/handlers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// SPDX-FileCopyrightText: 2024 Sveriges Television AB
//
// SPDX-License-Identifier: MIT

const bridge = require('bridge')

/*
Define any available osc paths
*/
module.exports = {
'/api': {
'/commands': {
'/executeCommand': message => bridge.commands.executeCommand(...message.args.map(arg => arg.value))
},
'/items': {
'/playItem': message => bridge.items.playItem(message?.args?.[0].value),
'/stopItem': message => bridge.items.stopItem(message?.args?.[0].value)
}
}
}
26 changes: 26 additions & 0 deletions plugins/osc/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading

0 comments on commit 6a35f38

Please sign in to comment.