commit
830b4e3d06
|
|
@ -0,0 +1,8 @@
|
|||
node_modules/
|
||||
package-lock.json
|
||||
/pkg
|
||||
/pkg.tgz
|
||||
/dist
|
||||
DEBUG-*
|
||||
/.yarn
|
||||
version_javascript/
|
||||
|
|
@ -0,0 +1,2 @@
|
|||
package.json
|
||||
/LICENSE.md
|
||||
|
|
@ -0,0 +1 @@
|
|||
nodeLinker: node-modules
|
||||
|
|
@ -0,0 +1,21 @@
|
|||
MIT License
|
||||
|
||||
Copyright (c) 2022 Bitfocus AS - Open Source
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
|
|
@ -0,0 +1,11 @@
|
|||
# companion-module-[replace with module name]
|
||||
|
||||
See [HELP.md](./companion/HELP.md) and [LICENSE](./LICENSE)
|
||||
|
||||
## Getting started
|
||||
|
||||
Executing a `yarn` command should perform all necessary steps to develop the module, if it does not then follow the steps below.
|
||||
|
||||
The module can be built once with `yarn build`. This should be enough to get the module to be loadable by companion.
|
||||
|
||||
While developing the module, by using `yarn dev` the compiler will be run in watch mode to recompile the files on change.
|
||||
|
|
@ -0,0 +1,3 @@
|
|||
## Your module
|
||||
|
||||
Write some help for your users here!
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
{
|
||||
"id": "modulopi-moduloplayer",
|
||||
"name": "modulopi-moduloplayer",
|
||||
"shortname": "Modulo Player",
|
||||
"description": "Modulo Player plugin for Companion",
|
||||
"version": "4.0.0",
|
||||
"license": "MIT",
|
||||
"repository": "git+https://github.com/bitfocus/companion-module-modulopi-moduloplayer.git",
|
||||
"bugs": "https://github.com/bitfocus/companion-module-modulopi-moduloplayer/issues",
|
||||
"maintainers": [
|
||||
{
|
||||
"name": "Nicolas Keesst",
|
||||
"email": "nkeesst@pixap.fr"
|
||||
},
|
||||
{
|
||||
"name": "Per Røine",
|
||||
"email": "per.roine@gmail.com"
|
||||
}
|
||||
],
|
||||
"runtime": {
|
||||
"type": "node22",
|
||||
"api": "nodejs-ipc",
|
||||
"apiVersion": "0.0.0",
|
||||
"entrypoint": "../dist/main.js"
|
||||
},
|
||||
"legacyIds": [
|
||||
"modulo"
|
||||
],
|
||||
"manufacturer": "Modulo Pi",
|
||||
"products": [
|
||||
"Modulo Player"
|
||||
],
|
||||
"keywords": [
|
||||
"Software",
|
||||
"Media Server"
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
import { generateEslintConfig } from '@companion-module/tools/eslint/config.mjs'
|
||||
|
||||
export default generateEslintConfig({
|
||||
enableTypescript: true,
|
||||
})
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
{
|
||||
"name": "modulopi-moduloplayer",
|
||||
"version": "4.0.0",
|
||||
"main": "dist/main.js",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"postinstall": "husky",
|
||||
"format": "prettier -w .",
|
||||
"package": "run build && companion-module-build",
|
||||
"build": "rimraf dist && run build:main",
|
||||
"build:main": "tsc -p tsconfig.build.json",
|
||||
"dev": "tsc -p tsconfig.build.json --watch",
|
||||
"lint:raw": "eslint",
|
||||
"lint": "run lint:raw ."
|
||||
},
|
||||
"license": "MIT",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "git+https://github.com/bitfocus/companion-module-modulopi-moduloplayer.git"
|
||||
},
|
||||
"engines": {
|
||||
"node": "^22.14",
|
||||
"yarn": "^4"
|
||||
},
|
||||
"dependencies": {
|
||||
"@companion-module/base": "~1.11.3",
|
||||
"ws": "^8.18.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@companion-module/tools": "^2.3.0",
|
||||
"@types/node": "^22.14.1",
|
||||
"@types/ws": "^8.18.1",
|
||||
"eslint": "^9.24.0",
|
||||
"husky": "^9.1.7",
|
||||
"lint-staged": "^15.5.1",
|
||||
"prettier": "^3.5.3",
|
||||
"rimraf": "^6.0.1",
|
||||
"typescript": "~5.8.3",
|
||||
"typescript-eslint": "^8.30.1"
|
||||
},
|
||||
"prettier": "@companion-module/tools/.prettierrc.json",
|
||||
"lint-staged": {
|
||||
"*.{css,json,md,scss}": [
|
||||
"prettier --write"
|
||||
],
|
||||
"*.{ts,tsx,js,jsx}": [
|
||||
"yarn lint:raw --fix"
|
||||
]
|
||||
},
|
||||
"packageManager": "yarn@4.9.1"
|
||||
}
|
||||
|
|
@ -0,0 +1,88 @@
|
|||
import type { MPinstance } from './main.js'
|
||||
|
||||
export function UpdateActions(self: MPinstance): void {
|
||||
self.setActionDefinitions({
|
||||
// LAUNCH TASK
|
||||
launch_task: {
|
||||
name: 'Launch Task (ID)',
|
||||
options: [
|
||||
{
|
||||
id: 'task',
|
||||
type: 'textinput',
|
||||
label: 'Task ID',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
callback: async (event) => {
|
||||
console.log('Launch Task ID: ' + event.options.task)
|
||||
self.mpConnection.sendMessageLunchTask(event.options.task, 2)
|
||||
},
|
||||
},
|
||||
|
||||
// GOTO CUE
|
||||
goto_cue: {
|
||||
name: 'Launch Cue (ID) on Playlist (ID)',
|
||||
options: [
|
||||
{
|
||||
id: 'index',
|
||||
type: 'number',
|
||||
label: 'Cue ID',
|
||||
default: 1,
|
||||
min: 1,
|
||||
max: 10000,
|
||||
},
|
||||
{
|
||||
id: 'cue',
|
||||
type: 'textinput',
|
||||
label: 'Cue UUID',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
id: 'pl',
|
||||
type: 'textinput',
|
||||
label: 'Playlist ID',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
callback: async (event) => {
|
||||
console.log(`Launch Cue ID: ${event.options.index} from Playlist UUID: ${event.options.pl}`)
|
||||
self.moduloplayer?.setGotoCue(event.options.pl, event.options.index)
|
||||
},
|
||||
},
|
||||
|
||||
pl_grand_master_fader: {
|
||||
name: 'GrandMaster Fader on Playlist (ID)',
|
||||
options: [
|
||||
{
|
||||
id: 'pl',
|
||||
type: 'textinput',
|
||||
label: 'Playlist ID',
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
id: 'value',
|
||||
type: 'number',
|
||||
label: 'Value in % (0 to 100)',
|
||||
default: 100,
|
||||
min: 0,
|
||||
max: 100,
|
||||
},
|
||||
{
|
||||
id: 'duration',
|
||||
type: 'number',
|
||||
label: 'Duration in ms (max 3600000 = 1 hour)',
|
||||
default: 2000,
|
||||
min: 0,
|
||||
max: 3600000,
|
||||
},
|
||||
],
|
||||
callback: async (event) => {
|
||||
self.log(
|
||||
'info',
|
||||
`SET GRAND MASTER PL: ${event.options.pl} | value: ${event.options.value} | duration: ${event.options.duration}`,
|
||||
)
|
||||
self.moduloplayer?.setGrandMasterFader(event.options.pl, event.options.value, event.options.duration)
|
||||
},
|
||||
},
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,83 @@
|
|||
import { Regex, type SomeCompanionConfigField } from '@companion-module/base'
|
||||
|
||||
export interface ModuloPlayConfig {
|
||||
host: string
|
||||
mpPort: number
|
||||
sdPort: number
|
||||
sdEnable: boolean
|
||||
pollInterval: number
|
||||
}
|
||||
|
||||
export function GetConfigFields(): SomeCompanionConfigField[] {
|
||||
return [
|
||||
{
|
||||
type: 'static-text',
|
||||
id: 'websocket',
|
||||
width: 12,
|
||||
label: '',
|
||||
value: `<h5>Websockets</h5>
|
||||
Modulo Player Websockets
|
||||
`,
|
||||
},
|
||||
{
|
||||
type: 'textinput',
|
||||
id: 'host',
|
||||
label: 'Target IP',
|
||||
width: 8,
|
||||
regex: Regex.IP,
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
id: 'mpPort',
|
||||
label: 'Player Port',
|
||||
width: 4,
|
||||
min: 1,
|
||||
max: 65535,
|
||||
default: 8080,
|
||||
},
|
||||
{
|
||||
type: 'checkbox',
|
||||
id: 'sdEnable',
|
||||
label: 'Enable Spydog',
|
||||
width: 3,
|
||||
default: true,
|
||||
},
|
||||
{
|
||||
type: 'static-text',
|
||||
id: 'spacer01',
|
||||
label: '',
|
||||
width: 5,
|
||||
value: '',
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
id: 'sdPort',
|
||||
label: 'Spydog Port',
|
||||
width: 4,
|
||||
min: 1,
|
||||
max: 65535,
|
||||
default: 8081,
|
||||
},
|
||||
{
|
||||
type: 'static-text',
|
||||
id: 'pollInfo',
|
||||
width: 12,
|
||||
label: '',
|
||||
value: `<h5>Poll Interval warning</h5>
|
||||
Adjusting the Polling Interval can impact performance.
|
||||
<br>
|
||||
<strong>A lower invterval allows for more responsive feedback, but may impact CPU usage.</strong>
|
||||
<br>
|
||||
`,
|
||||
},
|
||||
{
|
||||
type: 'number',
|
||||
id: 'pollInterval',
|
||||
label: 'Polling interval (ms) (default: 250, min: 100, 0 for disabled)',
|
||||
width: 12,
|
||||
default: 1000,
|
||||
min: 0,
|
||||
max: 60000,
|
||||
},
|
||||
]
|
||||
}
|
||||
|
|
@ -0,0 +1,136 @@
|
|||
import { combineRgb } from '@companion-module/base'
|
||||
import type { MPinstance } from './main.js'
|
||||
|
||||
const colorGreenMP = [88, 201, 23]
|
||||
|
||||
export function UpdateFeedbacks(self: MPinstance): void {
|
||||
self.setFeedbackDefinitions({
|
||||
color_cue: {
|
||||
name: 'PlayList Color Cue',
|
||||
type: 'boolean',
|
||||
defaultStyle: {
|
||||
bgcolor: combineRgb(255, 0, 0),
|
||||
color: combineRgb(0, 0, 0),
|
||||
},
|
||||
options: [
|
||||
{
|
||||
id: 'current_Cue',
|
||||
type: 'number',
|
||||
label: 'ID',
|
||||
default: 1,
|
||||
min: 1,
|
||||
max: 10000,
|
||||
},
|
||||
{
|
||||
id: 'pl',
|
||||
type: 'textinput',
|
||||
label: 'Playlist UUID',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
callback: (feedback) => {
|
||||
console.log(
|
||||
'FEEDBACK | Color Cue: ' +
|
||||
feedback.options.current_Cue + " >>> " +
|
||||
feedback.options.pl + " >>> " +
|
||||
self.states[`pl_${feedback.options.pl}_currentIndex`]
|
||||
+ " >>> " + (self.states[`pl_${feedback.options.pl}_currentIndex`] !== feedback.options.current_Cue)
|
||||
)
|
||||
if (self.states[`pl_${feedback.options.pl}_currentIndex`] !== feedback.options.current_Cue) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
},
|
||||
},
|
||||
current_Cue: {
|
||||
name: 'PlayList Current Cue',
|
||||
type: 'boolean',
|
||||
defaultStyle: {
|
||||
bgcolor: combineRgb(255, 0, 0),
|
||||
color: combineRgb(0, 0, 0),
|
||||
},
|
||||
options: [
|
||||
{
|
||||
id: 'current_Cue',
|
||||
type: 'number',
|
||||
label: 'ID',
|
||||
default: 1,
|
||||
min: 1,
|
||||
max: 10000,
|
||||
},
|
||||
{
|
||||
id: 'pl',
|
||||
type: 'textinput',
|
||||
label: 'Playlist UUID',
|
||||
default: '',
|
||||
},
|
||||
],
|
||||
callback: (feedback) => {
|
||||
// console.log(
|
||||
// 'FEEDBACK | Current Cue ID Change State: ' +
|
||||
// feedback.options.pl +
|
||||
// self.states[`pl_${feedback.options.pl}_currentIndex`] +
|
||||
// ' / id: ' +
|
||||
// feedback.options.current_Cue,
|
||||
// )
|
||||
if (self.states[`pl_${feedback.options.pl}_currentIndex`] === feedback.options.current_Cue) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
},
|
||||
},
|
||||
status: {
|
||||
name: 'Modulo Player Status',
|
||||
type: 'boolean',
|
||||
defaultStyle: {
|
||||
bgcolor: combineRgb(colorGreenMP[0], colorGreenMP[1], colorGreenMP[2]),
|
||||
},
|
||||
options: [
|
||||
{
|
||||
id: 'status',
|
||||
type: 'number',
|
||||
label: 'Modulo Player Status',
|
||||
default: 0,
|
||||
min: 0,
|
||||
max: 2,
|
||||
},
|
||||
],
|
||||
callback: (feedback) => {
|
||||
if (feedback.options.status === self.states[`status`]) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
},
|
||||
},
|
||||
master: {
|
||||
name: 'Modulo Player Master',
|
||||
type: 'boolean',
|
||||
defaultStyle: {
|
||||
text: 'Master',
|
||||
bgcolor: combineRgb(255, 0, 0),
|
||||
color: combineRgb(0, 0, 0),
|
||||
},
|
||||
options: [
|
||||
{
|
||||
id: 'master',
|
||||
type: 'number',
|
||||
label: 'Master',
|
||||
default: 1,
|
||||
min: 0,
|
||||
max: 1,
|
||||
},
|
||||
],
|
||||
callback: async (feedback) => {
|
||||
if (feedback.options.master === self.states[`master`]) {
|
||||
return true
|
||||
} else {
|
||||
return false
|
||||
}
|
||||
},
|
||||
},
|
||||
|
||||
})
|
||||
}
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
import { InstanceBase, runEntrypoint, InstanceStatus, SomeCompanionConfigField } from '@companion-module/base'
|
||||
import { GetConfigFields, type ModuloPlayConfig } from './configFields.js'
|
||||
import { UpdateVariableDefinitions } from './variables.js'
|
||||
import { UpgradeScripts } from './upgrades.js'
|
||||
import { UpdateActions } from './actions.js'
|
||||
import { UpdateFeedbacks } from './feedbacks.js'
|
||||
|
||||
//import { getPresets } from './presets.js'
|
||||
|
||||
import { MPconnection } from './mpconnection.js'
|
||||
import { SDconnection } from './sdconnection.js'
|
||||
import { ModuloPlayer } from './moduloplayer.js'
|
||||
import { SpyDog } from './spydog.js'
|
||||
|
||||
interface IStringIndex {
|
||||
[key: string]: any
|
||||
}
|
||||
|
||||
export class MPinstance extends InstanceBase<ModuloPlayConfig> {
|
||||
config!: ModuloPlayConfig // Setup in init()
|
||||
/** reference to the connection with the device */
|
||||
public mpConnection!: MPconnection
|
||||
public sdConnection!: SDconnection
|
||||
public mpConnected = false
|
||||
public sdConnected = false
|
||||
|
||||
moduloplayer: ModuloPlayer | undefined
|
||||
spydog: SpyDog | undefined
|
||||
|
||||
public pollAPI: NodeJS.Timer | null = null
|
||||
|
||||
// MODULO PLAYER DATA
|
||||
public tasksList = []
|
||||
public playLists = []
|
||||
public states: IStringIndex = {}
|
||||
|
||||
// CONTRUCTOR
|
||||
constructor(internal: unknown) {
|
||||
super(internal)
|
||||
}
|
||||
|
||||
async init(config: ModuloPlayConfig): Promise<void> {
|
||||
this.mpConnection = new MPconnection(this)
|
||||
this.sdConnection = new SDconnection(this)
|
||||
this.moduloplayer = new ModuloPlayer(this)
|
||||
this.spydog = new SpyDog(this)
|
||||
await this.configUpdated(config)
|
||||
|
||||
this.updateActions() // export actions
|
||||
this.updateFeedbacks() // export feedbacks
|
||||
this.updateVariableDefinitions()
|
||||
|
||||
// SET SPYDOG STATIC INFO
|
||||
this.setVariableValues({ clusterId: 0 })
|
||||
this.setVariableValues({ color: "" })
|
||||
this.setVariableValues({ cpuTemperature: 0 })
|
||||
this.setVariableValues({ cpuUse: 0 })
|
||||
this.setVariableValues({ detacastTemperature: "" })
|
||||
this.setVariableValues({ fps: 0 })
|
||||
this.setVariableValues({ fpsOk: false })
|
||||
this.setVariableValues({ gpuTemperature: 0 })
|
||||
|
||||
// SET SPYDOG DYNAMIC VARIABLE
|
||||
this.setVariableValues({ clusterId: 0 })
|
||||
this.setVariableValues({ color: "" })
|
||||
this.setVariableValues({ cpuTemperature: 0 })
|
||||
this.setVariableValues({ cpuUse: 0 })
|
||||
this.setVariableValues({ detacastTemperature: "" })
|
||||
this.setVariableValues({ fps: 0 })
|
||||
this.setVariableValues({ fpsOk: false })
|
||||
this.setVariableValues({ gpuTemperature: 0 })
|
||||
this.setVariableValues({ lockStatus: 0 })
|
||||
this.setVariableValues({ master: true })
|
||||
this.setVariableValues({ maxAutocalibOutputs: 0 })
|
||||
this.setVariableValues({ maxOutputs: 0 })
|
||||
this.setVariableValues({ memoryUse: 0 })
|
||||
this.setVariableValues({ motherboardTemperature: 0 })
|
||||
this.setVariableValues({ serverIp: "" })
|
||||
this.setVariableValues({ serverName: "" })
|
||||
this.setVariableValues({ serverTime: "" })
|
||||
this.setVariableValues({ status: 0 })
|
||||
this.setVariableValues({ upTime: "" })
|
||||
}
|
||||
|
||||
// When module gets deleted
|
||||
async destroy(): Promise<void> {
|
||||
this.mpConnection.destroy()
|
||||
this.sdConnection.destroy()
|
||||
this.log('debug', 'destroy')
|
||||
}
|
||||
|
||||
async configUpdated(config: ModuloPlayConfig): Promise<void> {
|
||||
this.updateStatus(InstanceStatus.Disconnected, 'Update')
|
||||
this.config = config
|
||||
this.mpConnection.disconnect()
|
||||
this.sdConnection.disconnect()
|
||||
this.updateStatus(InstanceStatus.Connecting, `Init Connection`)
|
||||
await this.mpConnection.connect(this.config.host, this.config.mpPort)
|
||||
await this.sdConnection.connect(this.config.host, this.config.sdPort)
|
||||
}
|
||||
|
||||
async isConnected() {
|
||||
if (this.mpConnected && this.sdConnected) {
|
||||
this.updateStatus(InstanceStatus.Ok, `Connected`)
|
||||
if (this.mpConnected) this.moduloplayer?.getTaskListModuloPlayer()
|
||||
if (this.mpConnected) this.moduloplayer?.getPlaylistModuloPlayer()
|
||||
if (this.sdConnected) this.spydog?.getStaticInfo()
|
||||
if (this.sdConnected) this.spydog?.getDynamicInfo()
|
||||
} else if (!this.mpConnected && this.sdConnected) {
|
||||
this.updateStatus(InstanceStatus.Connecting, `Spydog Connected | Start Modulo Player`)
|
||||
if (this.mpConnected) this.moduloplayer?.getTaskListModuloPlayer()
|
||||
if (this.mpConnected) this.moduloplayer?.getPlaylistModuloPlayer()
|
||||
if (this.sdConnected) this.spydog?.getStaticInfo()
|
||||
if (this.sdConnected) this.spydog?.getDynamicInfo()
|
||||
} else {
|
||||
this.updateStatus(InstanceStatus.Connecting, `Init Connection`)
|
||||
}
|
||||
}
|
||||
|
||||
async updateInstance() {
|
||||
this.updateActions() // export actions
|
||||
this.updateFeedbacks() // export feedbacks
|
||||
this.updateVariableDefinitions()
|
||||
}
|
||||
|
||||
updatPolling() {
|
||||
if (this.mpConnected) this.moduloplayer?.getPlaylistsCurrentCues()
|
||||
if (this.mpConnected) this.moduloplayer?.getPlaylistModuloPlayer()
|
||||
if (this.sdConnected) this.spydog?.getDynamicInfo()
|
||||
}
|
||||
|
||||
// Return config fields for web config
|
||||
getConfigFields(): SomeCompanionConfigField[] {
|
||||
return GetConfigFields()
|
||||
}
|
||||
|
||||
updateActions(): void {
|
||||
UpdateActions(this)
|
||||
}
|
||||
|
||||
updateFeedbacks(): void {
|
||||
UpdateFeedbacks(this)
|
||||
}
|
||||
|
||||
updateVariableDefinitions(): void {
|
||||
UpdateVariableDefinitions(this)
|
||||
}
|
||||
}
|
||||
|
||||
runEntrypoint(MPinstance, UpgradeScripts)
|
||||
|
|
@ -0,0 +1,150 @@
|
|||
import { MPinstance } from './main.js'
|
||||
import { getPresets } from './presets.js'
|
||||
|
||||
// JSON ID
|
||||
// 1 = list Tasks,
|
||||
// 2 = Launch Task,
|
||||
// 3 = list Playlist
|
||||
|
||||
// 100 = CURRENT CUE LIST
|
||||
// 110 = ACTION GOTO
|
||||
// 200 = SPYDOG DYNAMIC INFO
|
||||
// 201 = SPYDOG STATIC INFO
|
||||
|
||||
export class ModuloPlayer {
|
||||
instance: MPinstance
|
||||
|
||||
constructor(instance: MPinstance) {
|
||||
this.instance = instance
|
||||
}
|
||||
|
||||
public messageManager(data: String): void {
|
||||
const datas = JSON.parse(data.toString())
|
||||
this.instance.log('debug', 'MODULO PLAYER | MESSAGE MANAGER | DATA ID >>> ' + datas['id'])
|
||||
if (datas['id'] == 1) {
|
||||
//console.log('debug', 'MODULO PLAYER | MESSAGE MANAGER | DATA >>> ' + data.toString())
|
||||
this.tasksListManager(datas['result'])
|
||||
} else if (datas['id'] == 3) {
|
||||
//console.log('debug', 'MODULO PLAYER | MESSAGE MANAGER | DATA >>> ' + data.toString())
|
||||
this.playListCuesManager(datas['result'])
|
||||
} else if (datas['id'] == 100) {
|
||||
//console.log('debug', 'MODULO PLAYER | MESSAGE MANAGER | DATA >>> ' + data.toString())
|
||||
this.setPlayListCurrentCueIndex(datas['result'])
|
||||
}
|
||||
}
|
||||
|
||||
// TASK LIST
|
||||
public tasksListManager(obj: any): void {
|
||||
this.instance.tasksList = obj
|
||||
//this.instance.log('info', 'MODULO PLAYER | TESK LIST MANAGER| TESKS LIST >>> ' + JSON.stringify(this.instance.tasksList))
|
||||
this.instance.setPresetDefinitions(getPresets(this.instance))
|
||||
}
|
||||
|
||||
// PLAY LISTS CUES
|
||||
public playListCuesManager(obj: any): void {
|
||||
const plInstance: any[] = this.instance.playLists
|
||||
const plNew: any[] = obj
|
||||
const checkPL = areJsonArraysEqual(plInstance, plNew)
|
||||
if (checkPL) {
|
||||
|
||||
} else {
|
||||
this.instance.playLists = obj
|
||||
this.instance.setPresetDefinitions(getPresets(this.instance))
|
||||
this.instance.updateInstance()
|
||||
}
|
||||
|
||||
|
||||
// this.instance.log(
|
||||
// 'warn',
|
||||
// 'MODULO PLAYER |PLAYLISTS & CUES MANAGER| PLAYLISTS >>> ' + JSON.stringify(this.instance.playLists, null, 4),
|
||||
// )
|
||||
// for (let plID = 0; plID < obj.length; plID++) {
|
||||
// const cuesList: any[] = obj[plID]["cues"]
|
||||
// // this.instance.log(
|
||||
// // 'warn',
|
||||
// // 'MODULO PLAYER |PLAYLISTS & CUES MANAGER | CUES LIST >>> ' + JSON.stringify(cuesList, null, 4),
|
||||
// // )
|
||||
// for (let cuesID = 0; cuesID < cuesList.length; cuesID++) {
|
||||
// //const cue = cuesList[cuesID]
|
||||
// // this.instance.log(
|
||||
// // 'warn',
|
||||
// // 'MODULO PLAYER |PLAYLISTS & CUES MANAGER | CUES >>> ' + JSON.stringify(cue, null, 4),
|
||||
// // )
|
||||
// // this.instance.setVariableDefinitions([
|
||||
// // { variableId: `cue_${cue["uuid"]}_color`, name: 'Cue Color' },
|
||||
// // // { variableId: 'variable2', name: 'My second variable' },
|
||||
// // // { variableId: 'variable3', name: 'Another variable' },
|
||||
|
||||
// // ])
|
||||
// }
|
||||
// }
|
||||
|
||||
// this.instance.setPresetDefinitions(getPresets(this.instance))
|
||||
// this.instance.updateInstance()
|
||||
}
|
||||
|
||||
// GET CURRENT CUE INDEX
|
||||
async getPlaylistsCurrentCues() {
|
||||
this.instance.log('info', `MODULO PLAYER | GET PLAYLISTS CURRENT CUE !`)
|
||||
this.instance.mpConnection?.sendMessage('get.list.playlists', 100)
|
||||
}
|
||||
|
||||
async setPlayListCurrentCueIndex(obj: any) {
|
||||
const pls: any[] = obj
|
||||
for (let playlist = 0; playlist < pls.length; playlist++) {
|
||||
let uuid: String = pls[playlist]['uuid']//.slice(1, -1)
|
||||
this.instance.log('warn', `MODULO PLAYER | GET CURRENT INDEX >>> ${uuid} >>> ` + pls[playlist]['index'])
|
||||
var obj: any = {
|
||||
[`pl_${uuid}_currentIndex`]: parseInt(pls[playlist]['index']),
|
||||
}
|
||||
this.instance.states[`pl_${uuid}_currentIndex`] = parseInt(pls[playlist]['index'])
|
||||
this.instance.setVariableValues(obj)
|
||||
this.instance.checkFeedbacks()
|
||||
}
|
||||
}
|
||||
|
||||
async setGotoCue(plUUID: any, cueID: any) {
|
||||
var m = `{
|
||||
"jsonrpc": "2.0",
|
||||
"method": "doaction.playlist",
|
||||
"params": {
|
||||
"uuid": "${plUUID}",
|
||||
"action": "goto",
|
||||
"cue": ${cueID}
|
||||
},
|
||||
"id": 110
|
||||
}`
|
||||
this.instance.mpConnection.sendJsonMessage(m)
|
||||
}
|
||||
|
||||
async setGrandMasterFader(_pl: any, _value: any, _duration: any) {
|
||||
var m = `{"jsonrpc":"2.0","method":"doaction.playlist",
|
||||
"params": {
|
||||
"uuid": "${_pl}",
|
||||
"action": "grandMasterFader",
|
||||
"value": ${_value},
|
||||
"duration": ${_duration}
|
||||
},"id": ${110}}`
|
||||
this.instance.mpConnection.sendJsonMessage(m)
|
||||
}
|
||||
|
||||
async getTaskListModuloPlayer() {
|
||||
this.instance.log('info', 'GET TASKS LIST')
|
||||
this.instance.mpConnection?.sendMessage('get.list.tasks', 1)
|
||||
}
|
||||
|
||||
async getPlaylistModuloPlayer() {
|
||||
this.instance.log('info', 'GET PLAY LIST')
|
||||
this.instance.mpConnection?.sendMessagePlaylistsCues()
|
||||
}
|
||||
}
|
||||
|
||||
function areJsonArraysEqual(a: any[], b: any[]): boolean {
|
||||
if (a.length !== b.length) return false
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
if (JSON.stringify(a[i]) !== JSON.stringify(b[i])) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
|
@ -0,0 +1,168 @@
|
|||
import { MPinstance } from './main.js'
|
||||
//import { InstanceStatus } from '@companion-module/base'
|
||||
|
||||
import WebSocket from 'ws'
|
||||
|
||||
export class MPconnection {
|
||||
instance: MPinstance
|
||||
private websocket: WebSocket | undefined | null
|
||||
private wsTimeout: NodeJS.Timeout | undefined
|
||||
private mpAddr: string | undefined
|
||||
private mpPort: any | undefined | null
|
||||
private readonly reconnectmin = 100
|
||||
private readonly reconnectmax = 16_500
|
||||
private reconnectinterval = this.reconnectmin
|
||||
private shouldBeConnected: boolean
|
||||
|
||||
//private pollAPI: NodeJS.Timeout | undefined
|
||||
|
||||
constructor(instance: MPinstance) {
|
||||
this.instance = instance
|
||||
this.shouldBeConnected = false
|
||||
}
|
||||
|
||||
async connect(addr: string | undefined, port: any): Promise<void> {
|
||||
this.mpAddr = addr
|
||||
this.mpPort = port
|
||||
this.instance!.log('debug', `WEBSOCKET MP CONNECT ${this.mpAddr} ${this.mpPort}`)
|
||||
if (this.mpAddr === undefined || this.mpPort == undefined) return
|
||||
this.shouldBeConnected = true
|
||||
|
||||
const urlObj = `ws://${this.mpAddr}:${this.mpPort}`
|
||||
if (urlObj === null) return
|
||||
|
||||
try {
|
||||
const setupMP = async () => {
|
||||
this.websocket = new WebSocket(urlObj)
|
||||
|
||||
this.websocket.on('open', async () => {
|
||||
this.reconnectinterval = this.reconnectmin
|
||||
this.instance!.log('info', 'WEBSOCKET MP OPENED ' + this.websocket?.readyState)
|
||||
this.instance.mpConnected = true
|
||||
this.instance.isConnected()
|
||||
//this.initPolling()
|
||||
})
|
||||
|
||||
this.websocket.on('close', (ev: { toString: () => any }) => {
|
||||
console.log(
|
||||
'ws closed',
|
||||
ev.toString(),
|
||||
this.shouldBeConnected ? 'should be connected' : 'should not be connected',
|
||||
)
|
||||
if (this.shouldBeConnected) {
|
||||
//this.instance.updateStatus(InstanceStatus.Disconnected)
|
||||
this.instance.isConnected()
|
||||
if (this.wsTimeout) clearTimeout(this.wsTimeout)
|
||||
this.wsTimeout = setTimeout(() => {
|
||||
this.connect(this.mpAddr, this.mpPort)
|
||||
}, this.reconnectinterval)
|
||||
this.reconnectinterval *= 1.2
|
||||
if (this.reconnectinterval > this.reconnectmax) this.reconnectinterval = this.reconnectmax
|
||||
}
|
||||
})
|
||||
|
||||
this.websocket.on('error', (error: string) => {
|
||||
this.instance.log('error', 'Socket ' + error)
|
||||
this.instance.log('warn', 'Check if Modulo Player is started ?')
|
||||
//this.instance.updateStatus(InstanceStatus.ConnectionFailure)
|
||||
})
|
||||
|
||||
this.websocket.on('message', (data: { toString: () => string }) => {
|
||||
//console.log('debug', 'incoming MP message ' + data.toString())
|
||||
this.instance.moduloplayer?.messageManager(data.toString())
|
||||
})
|
||||
}
|
||||
|
||||
await setupMP()
|
||||
} catch (error) {
|
||||
this.disconnect()
|
||||
if (this.wsTimeout) clearTimeout(this.wsTimeout)
|
||||
this.wsTimeout = setTimeout(() => {
|
||||
this.connect(this.mpAddr, this.mpPort)
|
||||
}, this.reconnectinterval)
|
||||
this.reconnectinterval *= 1.2
|
||||
if (this.reconnectinterval > this.reconnectmax) this.reconnectinterval = this.reconnectmax
|
||||
}
|
||||
}
|
||||
|
||||
sendMessage(method: string, id: any): void {
|
||||
if (this.websocket?.readyState === 1) {
|
||||
var m = `{"jsonrpc":"2.0","method":"${method}","id": ${id}}`
|
||||
this.websocket?.send(m)
|
||||
this.instance.log('debug', 'SENDING WS MESSAGE ' + this.websocket.url + ' ' + m)
|
||||
}
|
||||
}
|
||||
|
||||
sendMessageLunchTask(uuid: any, id: any): void {
|
||||
if (this.websocket?.readyState === 1) {
|
||||
var m = `{"jsonrpc":"2.0","method":"doaction.task", "params": {
|
||||
"uuid": "${uuid}",
|
||||
"action": "launch"
|
||||
},"id": ${id}}`
|
||||
this.websocket?.send(m)
|
||||
this.instance.log('debug', 'SENDING WS MESSAGE LAUNCH TASK ' + this.websocket.url + ' ' + m)
|
||||
}
|
||||
}
|
||||
|
||||
sendJsonMessage(message: String) {
|
||||
if (this.websocket?.readyState === 1 && message !== '') {
|
||||
this.websocket?.send(message)
|
||||
this.instance.log('debug', 'SENDING WS MESSAGE LAUNCH TASK ' + this.websocket.url + ' ' + message)
|
||||
}
|
||||
}
|
||||
|
||||
sendMessagePlaylistsCues(): void {
|
||||
if (this.websocket?.readyState === 1) {
|
||||
var m = `{"jsonrpc":"2.0","method":"get.list.playlists",
|
||||
"params": {
|
||||
"level": "cue"},
|
||||
"id": 3}`
|
||||
this.websocket?.send(m)
|
||||
this.instance.log('debug', 'SENDING WS MESSAGE GET PLAYLISTS CUES ' + this.websocket.url + ' ' + m)
|
||||
}
|
||||
}
|
||||
|
||||
disconnect(): void {
|
||||
clearTimeout(this.wsTimeout)
|
||||
// if (this.pollAPI !== undefined) {
|
||||
// clearInterval(this.pollAPI)
|
||||
// }
|
||||
this.shouldBeConnected = false
|
||||
this.websocket?.close()
|
||||
this.instance.mpConnected = false
|
||||
this.instance.isConnected()
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
clearTimeout(this.wsTimeout)
|
||||
// if (this.pollAPI !== undefined) {
|
||||
// clearInterval(this.pollAPI)
|
||||
// }
|
||||
this.shouldBeConnected = false
|
||||
this.websocket = null
|
||||
this.instance.mpConnected = false
|
||||
this.instance.isConnected()
|
||||
this.instance!.log('debug', 'Connection has been destroyed due to removal or disable by user')
|
||||
}
|
||||
|
||||
// public readonly initPolling = (): void => {
|
||||
// //this.instance.log('warn', `CONNECTION| INIT POLLING >>> ${this.pollAPI}`)
|
||||
// if (this.pollAPI !== undefined) {
|
||||
// clearInterval(this.pollAPI)
|
||||
// }
|
||||
|
||||
// const pollAPI = () => {
|
||||
// if (this.websocket?.readyState == 1) {
|
||||
// this.instance.updatPolling()
|
||||
// }
|
||||
// }
|
||||
|
||||
// pollAPI()
|
||||
|
||||
// // Check if API Polling is disabled
|
||||
// if (this.instance.config.pollInterval != 0) {
|
||||
// const pollInterval = this.instance.config.pollInterval < 100 ? 100 : this.instance.config.pollInterval
|
||||
// this.pollAPI = setInterval(pollAPI, pollInterval)
|
||||
// }
|
||||
// }
|
||||
}
|
||||
|
|
@ -0,0 +1,299 @@
|
|||
import {
|
||||
combineRgb,
|
||||
CompanionButtonPresetDefinition,
|
||||
CompanionTextPresetDefinition,
|
||||
CompanionPresetDefinitions,
|
||||
} from '@companion-module/base'
|
||||
import type { MPinstance } from './main.js'
|
||||
|
||||
export type PresetCategory = 'Tasks List' | 'PL'
|
||||
|
||||
const colorGrayLight = [100, 100, 100]
|
||||
const textSize = 18
|
||||
|
||||
const colorOrangeMP = [255, 165, 0]
|
||||
const colorGreenMP = [88, 201, 23]
|
||||
|
||||
export function hexToRgb(hex: any) {
|
||||
const normal = hex.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i)
|
||||
if (normal) return normal.slice(1).map((e: string) => parseInt(e, 16))
|
||||
|
||||
const shorthand = hex.match(/^#([0-9a-f])([0-9a-f])([0-9a-f])$/i)
|
||||
if (shorthand) return shorthand.slice(1).map((e: string) => 0x11 * parseInt(e, 16))
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export type mpPreset = CompanionButtonPresetDefinition | CompanionTextPresetDefinition
|
||||
type mpPresetArray = mpPreset[] | any
|
||||
|
||||
export function getPresets(instance: MPinstance): CompanionPresetDefinitions {
|
||||
// TASK
|
||||
const getTasksPresets = (): mpPresetArray => {
|
||||
const tasksPresets: mpPresetArray = []
|
||||
//instance.log("warn", "GET TASKS PRESETS 0 >>> " + instance.tasksList.length)
|
||||
for (let task = 0; task < instance.tasksList.length; task++) {
|
||||
let color = [0, 0, 0]
|
||||
if (instance.tasksList[task]['uiColor'] !== '') {
|
||||
color = hexToRgb(`${instance.tasksList[task]['uiColor']}`)
|
||||
}
|
||||
//instance.log("warn", "GET TASKS PRESETS COLOR >>> " + color)
|
||||
tasksPresets.push({
|
||||
category: `Tasks List` as PresetCategory,
|
||||
name: `${instance.tasksList[task]['name']}`,
|
||||
type: 'button',
|
||||
style: {
|
||||
text: `${instance.tasksList[task]['name']}`,
|
||||
size: '24',
|
||||
color: combineRgb(255, 255, 255),
|
||||
bgcolor: combineRgb(color[0], color[1], color[2]),
|
||||
},
|
||||
steps: [
|
||||
{
|
||||
down: [
|
||||
{
|
||||
actionId: 'launch_task',
|
||||
options: { task: `${instance.tasksList[task]['uuid']}` },
|
||||
},
|
||||
],
|
||||
up: [],
|
||||
},
|
||||
],
|
||||
feedbacks: [
|
||||
// {
|
||||
// feedbackId: 'inputPreview',
|
||||
// options: {
|
||||
// mix: (mix - 1) as MixOptionEntry,
|
||||
// mixVariable: '',
|
||||
// input: input.toString(),
|
||||
// fg: combineRgb(255, 255, 255),
|
||||
// bg: combineRgb(0, 255, 0),
|
||||
// tally: '',
|
||||
// },
|
||||
// },
|
||||
],
|
||||
})
|
||||
}
|
||||
//instance.log("warn", "GET TASKS PRESETS 1 >>> " + tasksPresets.length)
|
||||
return tasksPresets
|
||||
}
|
||||
|
||||
// PLAYLISTS CUES
|
||||
const getPlayListsCuesPresets = (): mpPresetArray => {
|
||||
const playlistsPresets: mpPresetArray = []
|
||||
const pls: any[] = instance.playLists
|
||||
//instance.log('warn', 'GET PLAYLISTS PRESETS >>> ' + JSON.stringify(pls, null, 4))
|
||||
for (let playlist = 0; playlist < pls.length; playlist++) {
|
||||
let cl: any[] = pls[playlist]['cues']
|
||||
let uuid: String = pls[playlist]['uuid']//.slice(1, -1)
|
||||
let plName = pls[playlist]['name']
|
||||
//instance.log('warn', 'GET CUES PRESETS >>> ' + uuid)
|
||||
for (let cue = 0; cue < cl.length; cue++) {
|
||||
// COLOR
|
||||
let color = [0, 0, 0]
|
||||
if (cl[cue]['uiColor'] !== '') {
|
||||
color = hexToRgb(`${cl[cue]['uiColor']}`)
|
||||
}
|
||||
//instance.log('warn', 'GET CUES PRESETS COLOR >>> ' + color)
|
||||
// NAME
|
||||
let n = cl[cue]['name']
|
||||
if (n === '') {
|
||||
n = `Cue ${cue + 1}`
|
||||
}
|
||||
|
||||
//instance.log('warn', 'GET CUES PRESETS NAME >>> ' + n)
|
||||
playlistsPresets.push({
|
||||
category: `${pls[playlist]['name']}`,
|
||||
name: n, //`${cl[cue]['name']}`,
|
||||
type: 'button',
|
||||
style: {
|
||||
text: n, //`${cl[cue]['name']}`,
|
||||
size: textSize,
|
||||
color: combineRgb(255, 255, 255),
|
||||
bgcolor: combineRgb(color[0], color[1], color[2]),
|
||||
},
|
||||
steps: [
|
||||
{
|
||||
down: [
|
||||
{
|
||||
actionId: 'goto_cue',
|
||||
options: { cue: `${cl[cue]['uuid']}`, pl: `${pls[playlist]['uuid']}`, index: `${cue + 1}` },
|
||||
},
|
||||
],
|
||||
up: [],
|
||||
},
|
||||
],
|
||||
feedbacks: [
|
||||
{
|
||||
feedbackId: 'color_cue',
|
||||
options: {
|
||||
current_Cue: cue + 1,
|
||||
pl: uuid,
|
||||
},
|
||||
style: {
|
||||
bgcolor: combineRgb(color[0], color[1], color[2]),
|
||||
},
|
||||
},
|
||||
{
|
||||
feedbackId: 'current_Cue',
|
||||
options: {
|
||||
current_Cue: cue + 1, // pls[playlist]['index'],
|
||||
pl: uuid,
|
||||
},
|
||||
style: {
|
||||
bgcolor: combineRgb(colorOrangeMP[0], colorOrangeMP[1], colorOrangeMP[2]),
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
}
|
||||
|
||||
// GRAND MASTER 0%
|
||||
playlistsPresets.push({
|
||||
category: `${pls[playlist]['name']}`,
|
||||
name: `${plName} GM 0`,
|
||||
type: 'button',
|
||||
style: {
|
||||
text: `${plName} GM\n00`,
|
||||
size: textSize,
|
||||
color: combineRgb(255, 255, 255),
|
||||
bgcolor: combineRgb(colorGrayLight[0], colorGrayLight[1], colorGrayLight[2]),
|
||||
},
|
||||
steps: [
|
||||
{
|
||||
down: [
|
||||
{
|
||||
actionId: 'pl_grand_master_fader',
|
||||
options: { value: 0, duration: 2000, pl: `${pls[playlist]['uuid']}` },
|
||||
},
|
||||
],
|
||||
up: [],
|
||||
},
|
||||
],
|
||||
feedbacks: [],
|
||||
})
|
||||
|
||||
// GRAND MASTER 100%
|
||||
playlistsPresets.push({
|
||||
category: `${pls[playlist]['name']}`,
|
||||
name: `${plName} GM`,
|
||||
type: 'button',
|
||||
style: {
|
||||
text: `${plName} GM\nFF`,
|
||||
size: textSize,
|
||||
color: combineRgb(255, 255, 255),
|
||||
bgcolor: combineRgb(colorGrayLight[0], colorGrayLight[1], colorGrayLight[2]),
|
||||
},
|
||||
steps: [
|
||||
{
|
||||
down: [
|
||||
{
|
||||
actionId: 'pl_grand_master_fader',
|
||||
options: { value: 100, duration: 2000, pl: `${pls[playlist]['uuid']}` },
|
||||
},
|
||||
],
|
||||
up: [],
|
||||
},
|
||||
],
|
||||
feedbacks: [],
|
||||
})
|
||||
}
|
||||
//instance.log("warn", "GET TASKS PRESETS 1 >>> " + tasksPresets.length)
|
||||
return playlistsPresets
|
||||
}
|
||||
|
||||
// VARIABLES CUES
|
||||
const getVariablesPresets = (): mpPresetArray => {
|
||||
const variablesPresets: mpPresetArray = []
|
||||
|
||||
variablesPresets.push({
|
||||
category: `Variables`,
|
||||
name: `$(Modulo_Player:serverName) Master`,
|
||||
type: 'button',
|
||||
style: {
|
||||
text: `$(Modulo_Player:serverName) Master`,
|
||||
size: textSize,
|
||||
color: combineRgb(255, 255, 255),
|
||||
bgcolor: combineRgb(colorGreenMP[0], colorGreenMP[1], colorGreenMP[2]),
|
||||
},
|
||||
steps: [
|
||||
{
|
||||
down: [],
|
||||
up: [],
|
||||
},
|
||||
],
|
||||
feedbacks: [
|
||||
{
|
||||
feedbackId: 'master',
|
||||
options: {
|
||||
master: true,
|
||||
},
|
||||
style: {
|
||||
text: `$(Modulo_Player:serverName) Master`,
|
||||
},
|
||||
},
|
||||
{
|
||||
feedbackId: 'master',
|
||||
options: {
|
||||
master: false,
|
||||
},
|
||||
style: {
|
||||
text: `$(Modulo_Player:serverName) Slave`,
|
||||
},
|
||||
},
|
||||
{
|
||||
feedbackId: 'status',
|
||||
options: {
|
||||
status: 0,
|
||||
},
|
||||
style: {
|
||||
bgcolor: combineRgb(255, 0, 0),
|
||||
},
|
||||
},
|
||||
{
|
||||
feedbackId: 'status',
|
||||
options: {
|
||||
status: 1,
|
||||
},
|
||||
style: {
|
||||
bgcolor: combineRgb(colorOrangeMP[0], colorOrangeMP[1], colorOrangeMP[2]),
|
||||
},
|
||||
},
|
||||
{
|
||||
feedbackId: 'status',
|
||||
options: {
|
||||
status: 2,
|
||||
},
|
||||
style: {
|
||||
bgcolor: combineRgb(colorGreenMP[0], colorGreenMP[1], colorGreenMP[2]),
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
|
||||
variablesPresets.push({
|
||||
category: `Variables`,
|
||||
name: `$(Modulo_Player:serverName)`,
|
||||
type: 'button',
|
||||
style: {
|
||||
text: `$(Modulo_Player:serverName)`,
|
||||
size: textSize,
|
||||
color: combineRgb(255, 255, 255),
|
||||
bgcolor: combineRgb(instance.states['color'][0], instance.states['color'][1], instance.states['color'][2]),
|
||||
},
|
||||
steps: [
|
||||
{
|
||||
down: [],
|
||||
up: [],
|
||||
},
|
||||
],
|
||||
feedbacks: [],
|
||||
})
|
||||
|
||||
return variablesPresets
|
||||
}
|
||||
|
||||
const presets: mpPresetArray = [...getTasksPresets(), ...getPlayListsCuesPresets(), ...getVariablesPresets()]
|
||||
|
||||
return presets as unknown as CompanionPresetDefinitions
|
||||
}
|
||||
|
|
@ -0,0 +1,139 @@
|
|||
import { MPinstance } from './main.js'
|
||||
import { InstanceStatus } from '@companion-module/base'
|
||||
|
||||
import WebSocket from 'ws'
|
||||
|
||||
export class SDconnection {
|
||||
instance: MPinstance
|
||||
private websocket: WebSocket | undefined | null
|
||||
private wsTimeout: NodeJS.Timeout | undefined
|
||||
private mpAddr: string | undefined
|
||||
private sdPort: any | null
|
||||
private readonly reconnectmin = 100
|
||||
private readonly reconnectmax = 16_500
|
||||
private reconnectinterval = this.reconnectmin
|
||||
private shouldBeConnected: boolean
|
||||
|
||||
private pollAPI: NodeJS.Timeout | undefined
|
||||
|
||||
constructor(instance: MPinstance) {
|
||||
this.instance = instance
|
||||
this.shouldBeConnected = false
|
||||
}
|
||||
|
||||
async connect(addr: string | undefined, port: any): Promise<void> {
|
||||
this.mpAddr = addr
|
||||
this.sdPort = port
|
||||
this.instance!.log('debug', `WEBSOCKET SD CONNECT ${this.mpAddr} ${this.sdPort}`)
|
||||
if (this.mpAddr === undefined || this.sdPort == null) return
|
||||
this.shouldBeConnected = true
|
||||
|
||||
const urlObj = `ws://${this.mpAddr}:${this.sdPort}`
|
||||
if (urlObj === null) return
|
||||
|
||||
this.instance.updateStatus(InstanceStatus.Connecting, `Init Connection`)
|
||||
|
||||
try {
|
||||
const setupMP = async () => {
|
||||
this.websocket = new WebSocket(urlObj)
|
||||
|
||||
this.websocket.on('open', async () => {
|
||||
this.reconnectinterval = this.reconnectmin
|
||||
this.instance!.log('info', 'WEBSOCKET SPYDOG OPENED ' + this.websocket?.readyState)
|
||||
this.instance.sdConnected = true
|
||||
this.instance.isConnected()
|
||||
this.initPolling()
|
||||
})
|
||||
|
||||
this.websocket.on('close', (ev: { toString: () => any }) => {
|
||||
console.log(
|
||||
'ws closed',
|
||||
ev.toString(),
|
||||
this.shouldBeConnected ? 'should be connected' : 'should not be connected',
|
||||
)
|
||||
if (this.shouldBeConnected) {
|
||||
this.instance.updateStatus(InstanceStatus.Disconnected)
|
||||
if (this.wsTimeout) clearTimeout(this.wsTimeout)
|
||||
this.wsTimeout = setTimeout(() => {
|
||||
this.connect(this.mpAddr, this.sdPort)
|
||||
}, this.reconnectinterval)
|
||||
this.reconnectinterval *= 1.2
|
||||
if (this.reconnectinterval > this.reconnectmax) this.reconnectinterval = this.reconnectmax
|
||||
}
|
||||
})
|
||||
|
||||
this.websocket.on('error', (error: string) => {
|
||||
this.instance.log('error', 'Socket ' + error)
|
||||
this.instance.log('error', 'Check if Modulo Player is started ?')
|
||||
this.instance.updateStatus(InstanceStatus.ConnectionFailure)
|
||||
})
|
||||
|
||||
this.websocket.on('message', (data: { toString: () => string }) => {
|
||||
//this.instance.log('debug', 'INCOMMING SPYDOG MESSAGE ' + data.toString())
|
||||
this.instance.spydog?.messageManager(data.toString())
|
||||
})
|
||||
}
|
||||
|
||||
await setupMP()
|
||||
} catch (error) {
|
||||
this.disconnect()
|
||||
if (this.wsTimeout) clearTimeout(this.wsTimeout)
|
||||
this.wsTimeout = setTimeout(() => {
|
||||
this.connect(this.mpAddr, this.sdPort)
|
||||
}, this.reconnectinterval)
|
||||
this.reconnectinterval *= 1.2
|
||||
if (this.reconnectinterval > this.reconnectmax) this.reconnectinterval = this.reconnectmax
|
||||
}
|
||||
}
|
||||
|
||||
sendJsonMessage(message: String) {
|
||||
if (this.websocket?.readyState === 1 && message !== '') {
|
||||
this.websocket?.send(message)
|
||||
this.instance.log('debug', 'SENDING WS MESSAGE LAUNCH TASK ' + this.websocket.url + ' ' + message)
|
||||
}
|
||||
}
|
||||
|
||||
disconnect(): void {
|
||||
clearTimeout(this.wsTimeout)
|
||||
if (this.pollAPI !== undefined) {
|
||||
clearInterval(this.pollAPI)
|
||||
}
|
||||
this.shouldBeConnected = false
|
||||
this.websocket?.close()
|
||||
this.instance.sdConnected = false
|
||||
this.instance.isConnected()
|
||||
}
|
||||
|
||||
destroy(): void {
|
||||
clearTimeout(this.wsTimeout)
|
||||
if (this.pollAPI !== undefined) {
|
||||
clearInterval(this.pollAPI)
|
||||
}
|
||||
this.shouldBeConnected = false
|
||||
this.websocket = null
|
||||
this.instance.sdConnected = false
|
||||
this.instance.isConnected()
|
||||
this.instance!.log('debug', 'Connection has been destroyed due to removal or disable by user')
|
||||
}
|
||||
|
||||
public readonly initPolling = (): void => {
|
||||
//this.instance.log('warn', `CONNECTION| INIT POLLING >>> ${this.pollAPI}`)
|
||||
if (this.pollAPI !== undefined) {
|
||||
clearInterval(this.pollAPI)
|
||||
}
|
||||
|
||||
const pollAPI = () => {
|
||||
if (this.websocket?.readyState == 1) {
|
||||
this.instance.updatPolling()
|
||||
}
|
||||
}
|
||||
|
||||
pollAPI()
|
||||
|
||||
// Check if API Polling is disabled
|
||||
if (this.instance.config.pollInterval != 0) {
|
||||
const pollInterval = this.instance.config.pollInterval < 100 ? 100 : this.instance.config.pollInterval
|
||||
this.pollAPI = setInterval(pollAPI, pollInterval)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,100 @@
|
|||
import { MPinstance } from './main.js'
|
||||
//import { getPresets } from './presets.js'
|
||||
|
||||
// JSON ID
|
||||
// 1 = list Tasks,
|
||||
// 2 = Launch Task,
|
||||
// 3 = list Playlist
|
||||
|
||||
// 100 = CURRENT CUE LIST
|
||||
// 110 = ACTION GOTO
|
||||
// 200 = SPYDOG DYNAMIC INFO
|
||||
// 201 = SPYDOG STATIC INFO
|
||||
|
||||
export function hexToRgb(hex: any) {
|
||||
const normal = hex.match(/^#([0-9a-f]{2})([0-9a-f]{2})([0-9a-f]{2})$/i)
|
||||
if (normal) return normal.slice(1).map((e: string) => parseInt(e, 16))
|
||||
|
||||
const shorthand = hex.match(/^#([0-9a-f])([0-9a-f])([0-9a-f])$/i)
|
||||
if (shorthand) return shorthand.slice(1).map((e: string) => 0x11 * parseInt(e, 16))
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
export class SpyDog {
|
||||
instance: MPinstance
|
||||
|
||||
constructor(instance: MPinstance) {
|
||||
this.instance = instance
|
||||
}
|
||||
|
||||
public messageManager(data: String): void {
|
||||
const datas = JSON.parse(data.toString())
|
||||
//this.instance.log('debug', 'MODULO SPYDOG | MESSAGE MANAGER | DATA ID >>> ' + datas['id'])
|
||||
if (datas['id'] == 200) {
|
||||
this.setDynamicInfo(datas['result'])
|
||||
} else if (datas['id'] == 201) {
|
||||
this.setStaticInfo(datas['result'])
|
||||
}
|
||||
}
|
||||
|
||||
async getStaticInfo() {
|
||||
//this.instance.log('info', 'SPYDOG | GET STATIC INFO')
|
||||
var m = `{"jsonrpc":"2.0", "method":"get.computer.static.info", "id": ${201}}`
|
||||
this.instance.sdConnection.sendJsonMessage(m)
|
||||
}
|
||||
|
||||
async getDynamicInfo() {
|
||||
//this.instance.log('info', 'SPYDOG | GET DYNAMIC INFO')
|
||||
var m = `{"jsonrpc":"2.0", "method":"get.computer.dynamic.info", "id": ${200}}`
|
||||
this.instance.sdConnection.sendJsonMessage(m)
|
||||
}
|
||||
|
||||
async setStaticInfo(objs: any) {
|
||||
for (var key in objs[0]) {
|
||||
// this.instance.log(
|
||||
// 'info',
|
||||
// `MODULO SPYDOG | SET STATIC INFO | ELEMENTS >>> ${key}: ${objs[0][key]} ${typeof objs[0][key]}`,
|
||||
// )
|
||||
var objTemp: any = {}
|
||||
if (typeof objs[0][key] === "number") {
|
||||
objTemp = { [`${key}`]: parseInt(objs[0][key]), }
|
||||
} else {
|
||||
objTemp = { [`${key}`]: objs[0][key], }
|
||||
}
|
||||
this.instance.states[`${key}`] = objs[0][key]
|
||||
this.instance.setVariableValues(objTemp)
|
||||
this.instance.checkFeedbacks(`${key}`)
|
||||
}
|
||||
this.instance.updateInstance()
|
||||
}
|
||||
|
||||
async setDynamicInfo(objs: any) {
|
||||
for (var key in objs[0]) {
|
||||
// this.instance.log(
|
||||
// 'info',
|
||||
// `MODULO SPYDOG | SET DYNAMIC INFO | ELEMENTS >>> ${key}: ${objs[0][key]} ${typeof objs[0][key]}`,
|
||||
// )
|
||||
var objTemp: any = {}
|
||||
if (typeof objs[0][key] === "number") {
|
||||
objTemp = { [`${key}`]: parseInt(objs[0][key]), }
|
||||
} else {
|
||||
objTemp = { [`${key}`]: objs[0][key], }
|
||||
}
|
||||
|
||||
if (`${key}` === "color") {
|
||||
if (objs[0][key] == "transparent"){
|
||||
objTemp = { [`${key}`]: hexToRgb("#000000"), }
|
||||
this.instance.states[`${key}`] = hexToRgb("#000000")
|
||||
} else {
|
||||
objTemp = { [`${key}`]: hexToRgb(objs[0][key]), }
|
||||
this.instance.states[`${key}`] = hexToRgb(objs[0][key])
|
||||
}
|
||||
} else {
|
||||
this.instance.states[`${key}`] = objs[0][key]
|
||||
}
|
||||
this.instance.setVariableValues(objTemp)
|
||||
this.instance.checkFeedbacks(`${key}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,16 @@
|
|||
import type { CompanionStaticUpgradeScript } from '@companion-module/base'
|
||||
import type { ModuloPlayConfig } from './configFields.js'
|
||||
|
||||
export const UpgradeScripts: CompanionStaticUpgradeScript<ModuloPlayConfig>[] = [
|
||||
/*
|
||||
* Place your upgrade scripts here
|
||||
* Remember that once it has been added it cannot be removed!
|
||||
*/
|
||||
// function (context, props) {
|
||||
// return {
|
||||
// updatedConfig: null,
|
||||
// updatedActions: [],
|
||||
// updatedFeedbacks: [],
|
||||
// }
|
||||
// },
|
||||
]
|
||||
|
|
@ -0,0 +1,51 @@
|
|||
import type { MPinstance } from './main.js'
|
||||
|
||||
export function UpdateVariableDefinitions(instance: MPinstance): void {
|
||||
instance.log('info', 'VARIABLES DEFINITIONS !')
|
||||
const pls: any[] = instance.playLists
|
||||
const variables = []
|
||||
for (let playlist = 0; playlist < pls.length; playlist++) {
|
||||
let uuid: String = pls[playlist]['uuid']//.slice(1, -1)
|
||||
//instance.log('warn', `VARIABLES DEFINITIONS | GET CURRENT INDEX >>> ${uuid} >>> ` + pls[playlist]['index'])
|
||||
variables.push({ variableId: `pl_${uuid}_currentIndex`, name: `${pls[playlist]['name']} Current Cue ` })
|
||||
}
|
||||
|
||||
// SPYDOG STATIC INFO
|
||||
variables.push({ variableId: 'CPU', name: 'CPU' })
|
||||
variables.push({ variableId: 'GpuBrand', name: 'GPU Brand' })
|
||||
variables.push({ variableId: 'GpuDriver', name: 'GPU Driver' })
|
||||
variables.push({ variableId: 'GpuName', name: 'GPU Name' })
|
||||
variables.push({ variableId: 'ModuloPlayer', name: 'ModuloPlayer' })
|
||||
variables.push({ variableId: 'OS', name: 'OS' })
|
||||
variables.push({ variableId: 'processorCount', name: 'Processor Count' })
|
||||
variables.push({ variableId: 'totalMemory', name: 'Total Memory' })
|
||||
|
||||
// SPYDOG DYNAMIC INFO
|
||||
variables.push({ variableId: 'clusterId', name: 'Cluster Id' })
|
||||
variables.push({ variableId: 'color', name: 'Color' })
|
||||
variables.push({ variableId: 'cpuTemperature', name: 'CPU Temperature' })
|
||||
variables.push({ variableId: 'cpuUse', name: 'CPU Use' })
|
||||
variables.push({ variableId: 'detacastTemperature', name: 'Detacast Temperature' })
|
||||
variables.push({ variableId: 'fps', name: 'FPS' })
|
||||
variables.push({ variableId: 'fpsOk', name: 'FPS Ok' })
|
||||
variables.push({ variableId: 'gpuTemperature', name: 'GPU TEMPARATURE' })
|
||||
variables.push({ variableId: 'lockStatus', name: 'Lock Status' })
|
||||
variables.push({ variableId: 'master', name: 'Master' })
|
||||
variables.push({ variableId: 'maxAutocalibOutputs', name: 'Max Autocalib Outputs' })
|
||||
variables.push({ variableId: 'maxOutputs', name: 'Max Outputs' })
|
||||
variables.push({ variableId: 'memoryUse', name: 'Memory Use' })
|
||||
variables.push({ variableId: 'motherboardTemperature', name: 'Motherboard Temperature' })
|
||||
variables.push({ variableId: 'serverIp', name: 'Server Ip' })
|
||||
variables.push({ variableId: 'serverName', name: 'Server Name' })
|
||||
variables.push({ variableId: 'serverTime', name: 'Server Time' })
|
||||
variables.push({ variableId: 'status', name: 'Status' })
|
||||
variables.push({ variableId: 'upTime', name: 'UP Time' })
|
||||
|
||||
instance.setVariableDefinitions(variables)
|
||||
// instance.setVariableDefinitions([
|
||||
// { variableId: 'variable1', name: 'My first variable' },
|
||||
// { variableId: 'variable2', name: 'My second variable' },
|
||||
// { variableId: 'variable3', name: 'Another variable' },
|
||||
|
||||
// ])
|
||||
}
|
||||
|
|
@ -0,0 +1,14 @@
|
|||
{
|
||||
"extends": "@companion-module/tools/tsconfig/node18/recommended",
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["node_modules/**", "src/**/*spec.ts", "src/**/__tests__/*", "src/**/__mocks__/*"],
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist",
|
||||
"baseUrl": "./",
|
||||
"paths": {
|
||||
"*": ["./node_modules/*"]
|
||||
},
|
||||
"module": "Node16",
|
||||
"moduleResolution": "Node16"
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,8 @@
|
|||
{
|
||||
"extends": "./tsconfig.build.json",
|
||||
"include": ["src/**/*.ts"],
|
||||
"exclude": ["node_modules/**"],
|
||||
"compilerOptions": {
|
||||
"types": ["jest", "node"]
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue