From f7d012ba7727b1b60c51d9cfa4a12025dc8a6ec3 Mon Sep 17 00:00:00 2001 From: Alice Gaudon Date: Sun, 19 Jul 2020 20:57:07 +0200 Subject: [PATCH] Greatly improve app's lifecycle and add animations --- src/App.ts | 129 ++++++++++++++++++++++++++++++-------- src/Utils.ts | 5 ++ src/main.ts | 9 ++- src/wms/WMSMusicWidget.ts | 4 ++ 4 files changed, 115 insertions(+), 32 deletions(-) create mode 100644 src/Utils.ts diff --git a/src/App.ts b/src/App.ts index 0779972..91d0dac 100644 --- a/src/App.ts +++ b/src/App.ts @@ -6,6 +6,7 @@ import ObsWebSocket from "obs-websocket-js"; import LedState from "./LedState"; import ObsStateTracker from "./obs/ObsStateTracker"; import WMSMusicWidget from "./wms/WMSMusicWidget"; +import {sleep} from "./Utils"; export default class App { private obs: ObsWebSocket = new ObsWebSocket(); @@ -14,39 +15,58 @@ export default class App { private midiIn: any; private pendingStates: LedState[] = []; private readonly wmsMusicWidget: WMSMusicWidget = new WMSMusicWidget(); + private readonly configCallback: () => Promise; + private animationHandler?: NodeJS.Timeout; + private animating: boolean = false; + private animationStopCallback?: () => void; + private animationCounter: number = 0; - public constructor() { - + public constructor(configCallback: () => Promise) { + this.configCallback = configCallback; } public registerControl(control: MidiControl) { this.controls.push(control); } + public async init(): Promise { + await this.initialRainbowAnimation(); + } + public async start(): Promise { + await this.enableInControl(); + + this.obs = new ObsWebSocket(); await this.initObs(); + + this.obsStateTracker = new ObsStateTracker(this.obs); await this.obsStateTracker.init(this); - await this.initMidi(); + + await this.configCallback(); + + await this.initMidiIn(); await this.wmsMusicWidget.start(); } + public async reload(): Promise { + await this.stop(); + await this.start(); + } + public async stop(): Promise { - await this.midiIn.close(); + if (this.midiIn) { + await this.midiIn.close(); + this.midiIn = undefined; + } await this.obs.removeAllListeners(); await this.obs.disconnect(); } - public async reload(): Promise { - await this.obs.removeAllListeners(); - await this.midiIn.close(); - - this.obsStateTracker = new ObsStateTracker(this.obs); - await this.obsStateTracker.init(this); - await this.initMidi(); - } - private async initObs(): Promise { - const connectionRetryListener = async () => { + let retried = false; + this.obs.once('ConnectionClosed', async () => { + if (retried) return; + retried = true; try { console.error('Connection closed or authentication failure. Retrying in 2s...'); await new Promise(resolve => { @@ -54,13 +74,11 @@ export default class App { resolve(); }, 2000); }); - await this.connectObs(); + await this.reload(); } catch (e) { console.error(e); } - }; - this.obs.on('ConnectionClosed', connectionRetryListener); - this.obs.on('AuthenticationFailure', connectionRetryListener); + }); await this.connectObs(); } @@ -72,7 +90,7 @@ export default class App { }); } - private async initMidi(): Promise { + private async initMidiIn(): Promise { this.midiIn = jzz() .openMidiIn(config.get('midi.controller')) .or('Cannot open MIDI In port!') @@ -88,11 +106,13 @@ export default class App { }); await this.midiIn; - await this.enableInControl(); + await this.stopAnimation(); + // Init led controls for (const control of this.controls) { await control.init(this); } + await this.updateControls(); } private async handleMidiMessage(msg: any) { @@ -120,22 +140,77 @@ export default class App { // Enable "in control" this.led(0, 10, 0, 1); this.led(0, 12, 0, 1); - await this.tick(); + await this.loadingMidiAnimation(); + } + + private async initialRainbowAnimation(): Promise { // Led rainbow for (let n = 96; n < 105; n++) { - this.led(0, n, n % 4, (n + 2) % 4, 2000); - this.led(0, n + 16, n % 4, (n + 2) % 4, 2000); + this.led(0, n, n % 4, (n + 2) % 4, 750); + this.led(0, n + 16, n % 4, (n + 2) % 4, 750); await this.tick(); } + await sleep(1000); + } + + private async loadingMidiAnimation(): Promise { + await this.animate(async () => { + let i = this.animationCounter % 18; + let n = 96 + i + (i >= 9 ? 7 : 0); + this.led(0, n, 1, 3, 250); + await this.tick(); + await sleep(250); + }, 0); + } + + private async animate(animation: () => Promise, ms: number): Promise { + await this.stopAnimation(); + + const run = (first: boolean) => { + this.animating = true; + this.animationHandler = setTimeout(async () => { + try { + await animation(); + this.animationCounter++; + if (typeof this.animationHandler !== 'undefined') { + run(false); + } else { + this.signalAnimationEnd(); + } + } catch (e) { + console.error(e); + this.stopAnimation() + .catch(console.error); + this.signalAnimationEnd(); + } + }, first ? 0 : ms); + }; + run(true); + } + + private signalAnimationEnd() { + this.animating = false; + if (this.animationStopCallback) { + this.animationStopCallback(); + this.animationStopCallback = undefined; + } + } + + private async stopAnimation(): Promise { + if (typeof this.animationHandler !== 'undefined') { + clearTimeout(this.animationHandler); + this.animationHandler = undefined; + } await new Promise(resolve => { - setTimeout(resolve, 2000); + if (this.animating) { + this.animationStopCallback = resolve; + } else { + resolve(); + } }); - - // Init led controls - await this.updateControls(); } public async updateControls(): Promise { diff --git a/src/Utils.ts b/src/Utils.ts new file mode 100644 index 0000000..5809175 --- /dev/null +++ b/src/Utils.ts @@ -0,0 +1,5 @@ +export async function sleep(ms: number): Promise { + await new Promise(resolve => { + setTimeout(resolve, ms); + }); +} \ No newline at end of file diff --git a/src/main.ts b/src/main.ts index 8c4d087..0caae9a 100644 --- a/src/main.ts +++ b/src/main.ts @@ -6,12 +6,11 @@ import ObsSourceKnob from "./obs/ObsSourceKnob"; import config from "config"; (async () => { - const app = new App(); + const app = new App(async () => { + await configureApp(app); + }); + await app.init(); await app.start(); - - await configureApp(app); - - await app.reload(); })().catch(console.error); async function configureApp(app: App): Promise { diff --git a/src/wms/WMSMusicWidget.ts b/src/wms/WMSMusicWidget.ts index de7028d..5131fe0 100644 --- a/src/wms/WMSMusicWidget.ts +++ b/src/wms/WMSMusicWidget.ts @@ -12,12 +12,16 @@ export default class WMSMusicWidget { private activeSocket?: WebSocket; private checkInterval?: NodeJS.Timeout; private lastSentData?: string; + private started: boolean = false; public constructor() { } public async start(): Promise { + if (this.started) return; + this.started = true; + if (!this.token || this.token === 'default') { console.warn('WMS music widget not started due to missing token.'); return;