Greatly improve app's lifecycle and add animations

This commit is contained in:
Alice Gaudon 2020-07-19 20:57:07 +02:00
parent 347e5348b4
commit f7d012ba77
4 changed files with 115 additions and 32 deletions

View File

@ -6,6 +6,7 @@ import ObsWebSocket from "obs-websocket-js";
import LedState from "./LedState"; import LedState from "./LedState";
import ObsStateTracker from "./obs/ObsStateTracker"; import ObsStateTracker from "./obs/ObsStateTracker";
import WMSMusicWidget from "./wms/WMSMusicWidget"; import WMSMusicWidget from "./wms/WMSMusicWidget";
import {sleep} from "./Utils";
export default class App { export default class App {
private obs: ObsWebSocket = new ObsWebSocket(); private obs: ObsWebSocket = new ObsWebSocket();
@ -14,39 +15,58 @@ export default class App {
private midiIn: any; private midiIn: any;
private pendingStates: LedState[] = []; private pendingStates: LedState[] = [];
private readonly wmsMusicWidget: WMSMusicWidget = new WMSMusicWidget(); private readonly wmsMusicWidget: WMSMusicWidget = new WMSMusicWidget();
private readonly configCallback: () => Promise<void>;
private animationHandler?: NodeJS.Timeout;
private animating: boolean = false;
private animationStopCallback?: () => void;
private animationCounter: number = 0;
public constructor() { public constructor(configCallback: () => Promise<void>) {
this.configCallback = configCallback;
} }
public registerControl(control: MidiControl) { public registerControl(control: MidiControl) {
this.controls.push(control); this.controls.push(control);
} }
public async init(): Promise<void> {
await this.initialRainbowAnimation();
}
public async start(): Promise<void> { public async start(): Promise<void> {
await this.enableInControl();
this.obs = new ObsWebSocket();
await this.initObs(); await this.initObs();
this.obsStateTracker = new ObsStateTracker(this.obs);
await this.obsStateTracker.init(this); await this.obsStateTracker.init(this);
await this.initMidi();
await this.configCallback();
await this.initMidiIn();
await this.wmsMusicWidget.start(); await this.wmsMusicWidget.start();
} }
public async reload(): Promise<void> {
await this.stop();
await this.start();
}
public async stop(): Promise<void> { public async stop(): Promise<void> {
await this.midiIn.close(); if (this.midiIn) {
await this.midiIn.close();
this.midiIn = undefined;
}
await this.obs.removeAllListeners(); await this.obs.removeAllListeners();
await this.obs.disconnect(); await this.obs.disconnect();
} }
public async reload(): Promise<void> {
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<void> { private async initObs(): Promise<void> {
const connectionRetryListener = async () => { let retried = false;
this.obs.once('ConnectionClosed', async () => {
if (retried) return;
retried = true;
try { try {
console.error('Connection closed or authentication failure. Retrying in 2s...'); console.error('Connection closed or authentication failure. Retrying in 2s...');
await new Promise(resolve => { await new Promise(resolve => {
@ -54,13 +74,11 @@ export default class App {
resolve(); resolve();
}, 2000); }, 2000);
}); });
await this.connectObs(); await this.reload();
} catch (e) { } catch (e) {
console.error(e); console.error(e);
} }
}; });
this.obs.on('ConnectionClosed', connectionRetryListener);
this.obs.on('AuthenticationFailure', connectionRetryListener);
await this.connectObs(); await this.connectObs();
} }
@ -72,7 +90,7 @@ export default class App {
}); });
} }
private async initMidi(): Promise<void> { private async initMidiIn(): Promise<void> {
this.midiIn = jzz() this.midiIn = jzz()
.openMidiIn(config.get<string>('midi.controller')) .openMidiIn(config.get<string>('midi.controller'))
.or('Cannot open MIDI In port!') .or('Cannot open MIDI In port!')
@ -88,11 +106,13 @@ export default class App {
}); });
await this.midiIn; await this.midiIn;
await this.enableInControl(); await this.stopAnimation();
// Init led controls
for (const control of this.controls) { for (const control of this.controls) {
await control.init(this); await control.init(this);
} }
await this.updateControls();
} }
private async handleMidiMessage(msg: any) { private async handleMidiMessage(msg: any) {
@ -120,22 +140,77 @@ export default class App {
// Enable "in control" // Enable "in control"
this.led(0, 10, 0, 1); this.led(0, 10, 0, 1);
this.led(0, 12, 0, 1); this.led(0, 12, 0, 1);
await this.tick(); await this.tick();
await this.loadingMidiAnimation();
}
private async initialRainbowAnimation(): Promise<void> {
// Led rainbow // Led rainbow
for (let n = 96; n < 105; n++) { for (let n = 96; n < 105; n++) {
this.led(0, n, 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, 2000); this.led(0, n + 16, n % 4, (n + 2) % 4, 750);
await this.tick(); await this.tick();
} }
await sleep(1000);
}
private async loadingMidiAnimation(): Promise<void> {
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<void>, ms: number): Promise<void> {
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<void> {
if (typeof this.animationHandler !== 'undefined') {
clearTimeout(this.animationHandler);
this.animationHandler = undefined;
}
await new Promise(resolve => { 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<void> { public async updateControls(): Promise<void> {

5
src/Utils.ts Normal file
View File

@ -0,0 +1,5 @@
export async function sleep(ms: number): Promise<void> {
await new Promise(resolve => {
setTimeout(resolve, ms);
});
}

View File

@ -6,12 +6,11 @@ import ObsSourceKnob from "./obs/ObsSourceKnob";
import config from "config"; import config from "config";
(async () => { (async () => {
const app = new App(); const app = new App(async () => {
await configureApp(app);
});
await app.init();
await app.start(); await app.start();
await configureApp(app);
await app.reload();
})().catch(console.error); })().catch(console.error);
async function configureApp(app: App): Promise<void> { async function configureApp(app: App): Promise<void> {

View File

@ -12,12 +12,16 @@ export default class WMSMusicWidget {
private activeSocket?: WebSocket; private activeSocket?: WebSocket;
private checkInterval?: NodeJS.Timeout; private checkInterval?: NodeJS.Timeout;
private lastSentData?: string; private lastSentData?: string;
private started: boolean = false;
public constructor() { public constructor() {
} }
public async start(): Promise<void> { public async start(): Promise<void> {
if (this.started) return;
this.started = true;
if (!this.token || this.token === 'default') { if (!this.token || this.token === 'default') {
console.warn('WMS music widget not started due to missing token.'); console.warn('WMS music widget not started due to missing token.');
return; return;