UNPKG

vtally

Version:

An affordable and reliable Tally Light that works via WiFi based on NodeMCU / ESP8266. Supports multiple video mixers.

336 lines (335 loc) 16.1 kB
"use strict"; var __importDefault = (this && this.__importDefault) || function (mod) { return (mod && mod.__esModule) ? mod : { "default": mod }; }; Object.defineProperty(exports, "__esModule", { value: true }); // set NODE_ENV from argument to enable portability to windows const yargs_1 = __importDefault(require("yargs")); const TallyContainer_1 = __importDefault(require("./tally/TallyContainer")); const UdpTallyDriver_1 = __importDefault(require("./tally/UdpTallyDriver")); const AppConfiguration_1 = require("./lib/AppConfiguration"); const MixerDriver_1 = require("./lib/MixerDriver"); const express_1 = __importDefault(require("express")); const http_proxy_middleware_1 = require("http-proxy-middleware"); const http_1 = require("http"); const socket_io_1 = __importDefault(require("socket.io")); const SocketAwareEvent_1 = require("./lib/SocketAwareEvent"); const ServerEventEmitter_1 = __importDefault(require("./lib/ServerEventEmitter")); const AppConfigurationPersistence_1 = __importDefault(require("./lib/AppConfigurationPersistence")); const AtemConfiguration_1 = __importDefault(require("./mixer/atem/AtemConfiguration")); const VmixConfiguration_1 = __importDefault(require("./mixer/vmix/VmixConfiguration")); const ObsConfiguration_1 = __importDefault(require("./mixer/obs/ObsConfiguration")); const RolandV8HDConfiguration_1 = __importDefault(require("./mixer/rolandV8HD/RolandV8HDConfiguration")); const RolandV60HDConfiguration_1 = __importDefault(require("./mixer/rolandV60HD/RolandV60HDConfiguration")); const MockConfiguration_1 = __importDefault(require("./mixer/mock/MockConfiguration")); const TestConnector_1 = __importDefault(require("./mixer/test/TestConnector")); const TestConfiguration_1 = __importDefault(require("./mixer/test/TestConfiguration")); const WebTallyDriver_1 = __importDefault(require("./tally/WebTallyDriver")); const TallyConfiguration_1 = require("./tally/TallyConfiguration"); const NodeMcuConnector_1 = __importDefault(require("./flasher/NodeMcuConnector")); const argv = yargs_1.default.argv; if (argv.env !== undefined) { // @ts-ignore @TODO: setting the env is not nice, but the easiest way to be cross-platform compatible // @see https://github.com/wifi-tally/wifi-tally/issues/18 process.env.NODE_ENV = argv.env; } if (argv['with-test'] !== undefined) { process.env.HUB_WITH_TEST = "true"; } const app = (0, express_1.default)(); const server = new http_1.Server(app); const io = (0, socket_io_1.default)(server); const myEmitter = new ServerEventEmitter_1.default(); myEmitter.setMaxListeners(99); const myConfiguration = new AppConfiguration_1.AppConfiguration(myEmitter); if (myConfiguration.isTest()) { console.log("Starting test environment"); myConfiguration.setMixerSelection(TestConnector_1.default.ID); myConfiguration.setTallyTimeoutMissing(1000); myConfiguration.setTallyTimeoutDisconnected(3000); } else { new AppConfigurationPersistence_1.default(myConfiguration, myEmitter); } const myTallyContainer = new TallyContainer_1.default(myConfiguration, myEmitter); new UdpTallyDriver_1.default(myConfiguration, myTallyContainer); const myWebTallyDriver = new WebTallyDriver_1.default(myConfiguration, myTallyContainer); const myMixerDriver = new MixerDriver_1.MixerDriver(myConfiguration, myEmitter); const myNodeMcuConnector = new NodeMcuConnector_1.default(); // log stuff myEmitter.on('tally.logged', ({ tally, log }) => { let fn = console.info; if (log.isError()) { fn = console.error; } else if (log.isWarning()) { fn = console.warn; } fn(`${tally.name}: ${log.message}`); }); myEmitter.on('program.changed', ({ programs, previews }) => { console.info("Program/Preview was changed to ", programs, previews); }); // socket.io server io.on('connection', (socket) => { socket.setMaxListeners(99); const mixerEvents = [ // @TODO: use event objects instead of repeating the same structure again and again new SocketAwareEvent_1.SocketAwareEvent(myEmitter, 'mixer.connected', socket, (socket) => { socket.emit('mixer.state', { isConnected: true }); }), new SocketAwareEvent_1.SocketAwareEvent(myEmitter, 'mixer.disconnected', socket, (socket) => { socket.emit('mixer.state', { isConnected: false }); }), ]; socket.on('events.mixer.subscribe', () => { mixerEvents.forEach(pipe => pipe.register()); socket.emit('mixer.state', { isConnected: myMixerDriver.isConnected() }); }); socket.on('events.mixer.unsubscribe', () => { // @TODO: not used yet mixerEvents.forEach(pipe => pipe.unregister()); }); const programEvents = [ new SocketAwareEvent_1.SocketAwareEvent(myEmitter, 'program.changed', socket, (socket, { programs, previews }) => { socket.emit('program.state', { programs: programs, previews: previews, }); }) ]; socket.on('events.program.subscribe', () => { programEvents.forEach(pipe => pipe.register()); socket.emit('program.state', { programs: myMixerDriver.getCurrentPrograms(), previews: myMixerDriver.getCurrentPreviews(), }); }); socket.on('events.program.unsubscribe', () => { // @TODO: not used yet programEvents.forEach(pipe => pipe.unregister()); }); const configEvents = [ new SocketAwareEvent_1.SocketAwareEvent(myEmitter, 'config.changed.atem', socket, (socket, atemConfiguration) => { socket.emit('config.state.atem', atemConfiguration.toJson()); }), new SocketAwareEvent_1.SocketAwareEvent(myEmitter, 'config.changed.mock', socket, (socket, mockConfiguration) => { socket.emit('config.state.mock', mockConfiguration.toJson()); }), new SocketAwareEvent_1.SocketAwareEvent(myEmitter, 'config.changed.obs', socket, (socket, obsConfiguration) => { socket.emit('config.state.obs', obsConfiguration.toJson()); }), new SocketAwareEvent_1.SocketAwareEvent(myEmitter, 'config.changed.vmix', socket, (socket, vmixConfiguration) => { socket.emit('config.state.vmix', vmixConfiguration.toJson()); }), new SocketAwareEvent_1.SocketAwareEvent(myEmitter, 'config.changed.tallyconfig', socket, (socket, tallyConfiguration) => { socket.emit('config.state.tallyconfig', tallyConfiguration.toJson()); }), new SocketAwareEvent_1.SocketAwareEvent(myEmitter, 'config.changed.mixer', socket, (socket, mixerName) => { socket.emit('config.state.mixer', { mixerName, allowedMixers: MixerDriver_1.MixerDriver.getAllowedMixers(myConfiguration.isDev(), myConfiguration.isTest()) }); }), ]; socket.on('events.config.subscribe', () => { configEvents.forEach(pipe => pipe.register()); socket.emit('config.state.atem', myConfiguration.getAtemConfiguration().toJson()); socket.emit('config.state.mixer', { mixerName: myConfiguration.getMixerSelection() || "", allowedMixers: MixerDriver_1.MixerDriver.getAllowedMixers(myConfiguration.isDev(), myConfiguration.isTest()) }); socket.emit('config.state.mock', myConfiguration.getMockConfiguration().toJson()); socket.emit('config.state.obs', myConfiguration.getObsConfiguration().toJson()); socket.emit('config.state.rolandV8HD', myConfiguration.getRolandV8HDConfiguration().toJson()); socket.emit('config.state.rolandV60HD', myConfiguration.getRolandV60HDConfiguration().toJson()); socket.emit('config.state.vmix', myConfiguration.getVmixConfiguration().toJson()); socket.emit('config.state.tallyconfig', myConfiguration.getTallyConfiguration().toJson()); }); socket.on('events.program.unsubscribe', () => { // @TODO: not used yet configEvents.forEach(pipe => pipe.unregister()); }); const tallyEvents = [ new SocketAwareEvent_1.SocketAwareEvent(myEmitter, 'tally.changed', socket, (socket) => { socket.emit('tally.state', { tallies: myTallyContainer.getTalliesAsJson() }); }), new SocketAwareEvent_1.SocketAwareEvent(myEmitter, 'tally.removed', socket, (socket) => { socket.emit('tally.state', { tallies: myTallyContainer.getTalliesAsJson() }); }), ]; socket.on('events.tally.subscribe', () => { tallyEvents.forEach(pipe => pipe.register()); socket.emit('tally.state', { tallies: myTallyContainer.getTalliesAsJson() }); }); socket.on('events.tally.unsubscribe', () => { // @TODO: not used yet tallyEvents.forEach(pipe => pipe.unregister()); }); socket.on('tally.patch', (tallyName, tallyType, channelId) => { myTallyContainer.patch(tallyName, tallyType, channelId); }); socket.on('tally.highlight', (tallyName, tallyType) => { myTallyContainer.highlight(tallyName, tallyType); }); socket.on('tally.remove', (tallyName, tallyType) => { myTallyContainer.remove(tallyName, tallyType); }); socket.on('tally.create', (tallyName, channelId) => { myWebTallyDriver.create(tallyName, channelId); }); socket.on('tally.settings', (tallyName, tallyType, settings) => { const configuration = new TallyConfiguration_1.TallyConfiguration(); configuration.fromJson(settings); myTallyContainer.updateSettings(tallyName, tallyType, configuration); }); socket.on('events.webTally.subscribe', tallyName => myWebTallyDriver.subscribe(tallyName, socket)); socket.on('events.webTally.unsubscribe', tallyName => myWebTallyDriver.unsubscribe(tallyName, socket)); const tallyLogEvents = [ new SocketAwareEvent_1.SocketAwareEvent(myEmitter, 'tally.logged', socket, (socket, { tally, log }) => { socket.emit('tally.log', { tallyId: tally.getId(), log: log.toJson() }); }), ]; socket.on('events.tallyLog.subscribe', () => { tallyLogEvents.forEach(pipe => pipe.register()); const logs = myTallyContainer.getTallies().map(tally => { return { tallyId: tally.getId(), logs: tally.getLogs().map(log => log.toJson()) }; }); socket.emit('tally.log.state', logs); }); socket.on('events.tallyLog.unsubscribe', () => { // @TODO: not used yet tallyLogEvents.forEach(pipe => pipe.unregister()); }); const channelEvents = [ new SocketAwareEvent_1.SocketAwareEvent(myEmitter, 'config.changed.channels', socket, (socket, channels) => { socket.emit('channel.state', { channels: channels.map(channel => channel.toJson()) }); }), ]; socket.on('events.channel.subscribe', () => { channelEvents.forEach(pipe => pipe.register()); socket.emit('channel.state', { channels: myConfiguration.getChannelsAsJson() }); }); socket.on('events.channel.unsubscribe', () => { // @TODO: not used yet channelEvents.forEach(pipe => pipe.unregister()); }); socket.on('config.change.atem', (newAtemConfiguration, newMixerName) => { const atem = new AtemConfiguration_1.default(); atem.fromJson(newAtemConfiguration); myConfiguration.setAtemConfiguration(atem); if (newMixerName) { myConfiguration.setMixerSelection(newMixerName); } }); socket.on('config.change.mock', (newMockConfiguration, newMixerName) => { const mock = new MockConfiguration_1.default(); mock.fromJson(newMockConfiguration); myConfiguration.setMockConfiguration(mock); if (newMixerName) { myConfiguration.setMixerSelection(newMixerName); } }); socket.on('config.change.null', newMixerName => { if (newMixerName) { myConfiguration.setMixerSelection(newMixerName); } }); socket.on('config.change.obs', (newObsConfiguration, newMixerName) => { const obs = new ObsConfiguration_1.default(); obs.fromJson(newObsConfiguration); myConfiguration.setObsConfiguration(obs); if (newMixerName) { myConfiguration.setMixerSelection(newMixerName); } }); socket.on('config.change.rolandV8HD', (newRolandV8HDConfiguration, newMixerName) => { const rolandV8HD = new RolandV8HDConfiguration_1.default(); rolandV8HD.fromJson(newRolandV8HDConfiguration); myConfiguration.setRolandV8HDConfiguration(rolandV8HD); if (newMixerName) { myConfiguration.setMixerSelection(newMixerName); } }); socket.on('config.change.rolandV60HD', (newRolandV60HDConfiguration, newMixerName) => { const rolandV60HD = new RolandV60HDConfiguration_1.default(); rolandV60HD.fromJson(newRolandV60HDConfiguration); myConfiguration.setRolandV60HDConfiguration(rolandV60HD); if (newMixerName) { myConfiguration.setMixerSelection(newMixerName); } }); socket.on('config.change.test', (newTestConfiguration, newMixerName) => { const test = new TestConfiguration_1.default(); test.fromJson(newTestConfiguration); myConfiguration.setTestConfiguration(test); if (newMixerName) { myConfiguration.setMixerSelection(newMixerName); } }); socket.on('config.change.vmix', (newVmixConfiguration, newMixerName) => { const vmix = new VmixConfiguration_1.default(); vmix.fromJson(newVmixConfiguration); myConfiguration.setVmixConfiguration(vmix); if (newMixerName) { myConfiguration.setMixerSelection(newMixerName); } }); socket.on('config.change.tallyconfig', (conf) => { const configuration = new TallyConfiguration_1.DefaultTallyConfiguration(); configuration.fromJson(conf); myConfiguration.setTallyConfiguration(configuration); }); socket.on('flasher.device.get', () => { myNodeMcuConnector.getDevice().then(device => { socket.emit('flasher.device', device.toJson()); }); }); socket.on('flasher.settingsIni', (path, settingsIniString) => { myNodeMcuConnector.writeTallySettingsIni(path, settingsIniString, (state) => { socket.emit('flasher.settingsIni.progress', state); }); }); socket.on('flasher.program', (path) => { myNodeMcuConnector.program(path, (state) => { socket.emit('flasher.program.progress', state); }); }); }); if (myConfiguration.isDev()) { const proxyPort = process.env.DEV_PROXY_PORT || 3001; console.info(`Serving frontend via proxy. The React dev server is expected to run on port ${proxyPort}.`); app.use('/', (0, http_proxy_middleware_1.createProxyMiddleware)({ target: `http://localhost:${proxyPort}`, changeOrigin: true, ws: true, onError: (err, req, res) => { if (typeof res.writeHead === "function") { res.writeHead(500, { 'Content-Type': 'text/plain', }); if (err && err.code === "ECONNREFUSED") { res.end(`Could not connect to server on http://localhost:${proxyPort}. Is the react dev server running?\nTo fix it run:\n npm run start:frontend`); } else { res.end("The proxy reported an error: \n" + err.toString()); } } }, })); } else { const publicDirName = "frontend"; console.info(`Serving frontend from directory ${publicDirName}`); app.use('/', express_1.default.static(`${__dirname}/${publicDirName}/`)); // fetch all route to allow deep-links into the application app.get('*', function (req, res) { res.sendFile(`${__dirname}/${publicDirName}/index.html`); }); } server.listen(myConfiguration.getHttpPort(), () => { console.log(`Web Server available on http://localhost:${myConfiguration.getHttpPort()}`); });