mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-19 22:24:11 +00:00
feat: implement multiple aux timers
This commit is contained in:
committed by
Carlos Valente
parent
d1c58712ae
commit
bf8ed8d017
@@ -1,91 +1,165 @@
|
||||
import { SimpleDirection, SimplePlayback, SimpleTimerState } from 'ontime-types';
|
||||
import { RuntimeStore, SimpleDirection, SimplePlayback } from 'ontime-types';
|
||||
|
||||
import { SimpleTimer } from '../../classes/simple-timer/SimpleTimer.js';
|
||||
import { eventStore } from '../../stores/EventStore.js';
|
||||
import { timerConfig } from '../../setup/config.js';
|
||||
|
||||
type EmitFn = (state: SimpleTimerState) => void;
|
||||
type AuxTimerStateUpdate = Partial<Pick<RuntimeStore, 'auxtimer1' | 'auxtimer2' | 'auxtimer3'>>;
|
||||
type EmitFn = (state: AuxTimerStateUpdate) => void;
|
||||
type GetTimeFn = () => number;
|
||||
|
||||
export class AuxTimerService {
|
||||
private timer: SimpleTimer;
|
||||
private aux1: SimpleTimer;
|
||||
private aux2: SimpleTimer;
|
||||
private aux3: SimpleTimer;
|
||||
private interval: NodeJS.Timeout | null = null;
|
||||
private emit: EmitFn;
|
||||
protected emit: EmitFn;
|
||||
private getTime: GetTimeFn;
|
||||
|
||||
constructor(emit: EmitFn, getTime: GetTimeFn) {
|
||||
this.timer = new SimpleTimer(timerConfig.auxTimerDefault);
|
||||
this.aux1 = new SimpleTimer(timerConfig.auxTimerDefault);
|
||||
this.aux2 = new SimpleTimer(timerConfig.auxTimerDefault);
|
||||
this.aux3 = new SimpleTimer(timerConfig.auxTimerDefault);
|
||||
this.emit = emit;
|
||||
this.getTime = getTime;
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether any of the aux timers are currently running
|
||||
*/
|
||||
private hasActiveTimers(): boolean {
|
||||
return (
|
||||
this.aux1.state.playback === SimplePlayback.Start ||
|
||||
this.aux2.state.playback === SimplePlayback.Start ||
|
||||
this.aux3.state.playback === SimplePlayback.Start
|
||||
);
|
||||
}
|
||||
|
||||
private startInterval() {
|
||||
this.interval = setInterval(this.update.bind(this), 500);
|
||||
if (!this.interval) {
|
||||
this.interval = setInterval(this.update.bind(this), 500);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Utility simplifies guarding against multiple intervals being set
|
||||
*/
|
||||
private stopInterval() {
|
||||
if (this.interval) {
|
||||
if (this.interval && !this.hasActiveTimers()) {
|
||||
clearInterval(this.interval);
|
||||
this.interval = null;
|
||||
}
|
||||
}
|
||||
|
||||
@broadcastReturn
|
||||
setDirection(direction: SimpleDirection) {
|
||||
return this.timer.setDirection(direction, this.getTime());
|
||||
setDirection(direction: SimpleDirection, index: number) {
|
||||
if (index === 1) return this.aux1.setDirection(direction, this.getTime());
|
||||
if (index === 2) return this.aux2.setDirection(direction, this.getTime());
|
||||
return this.aux3.setDirection(direction, this.getTime());
|
||||
}
|
||||
|
||||
@broadcastReturn
|
||||
start() {
|
||||
start(index: number) {
|
||||
this.startInterval();
|
||||
return this.timer.start(this.getTime());
|
||||
if (index === 1) return this.aux1.start(this.getTime());
|
||||
if (index === 2) return this.aux2.start(this.getTime());
|
||||
return this.aux3.start(this.getTime());
|
||||
}
|
||||
|
||||
@broadcastReturn
|
||||
pause() {
|
||||
this.stopInterval();
|
||||
return this.timer.pause(this.getTime());
|
||||
}
|
||||
pause(index: number) {
|
||||
// First pause the timer
|
||||
let result;
|
||||
if (index === 1) result = this.aux1.pause(this.getTime());
|
||||
else if (index === 2) result = this.aux2.pause(this.getTime());
|
||||
else result = this.aux3.pause(this.getTime());
|
||||
|
||||
@broadcastReturn
|
||||
stop() {
|
||||
this.stopInterval();
|
||||
return this.timer.stop();
|
||||
}
|
||||
|
||||
@broadcastReturn
|
||||
setTime(duration: number) {
|
||||
return this.timer.setTime(duration);
|
||||
}
|
||||
|
||||
@broadcastReturn
|
||||
addTime(millis: number) {
|
||||
if (this.timer.state.playback === SimplePlayback.Start) {
|
||||
this.timer.addTime(millis);
|
||||
return this.timer.update(this.getTime());
|
||||
// Then check if we need to keep the interval running
|
||||
if (!this.hasActiveTimers()) {
|
||||
this.stopInterval();
|
||||
}
|
||||
return this.timer.addTime(millis);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@broadcastReturn
|
||||
stop(index: number) {
|
||||
// First stop the timer
|
||||
let result;
|
||||
if (index === 1) result = this.aux1.stop();
|
||||
else if (index === 2) result = this.aux2.stop();
|
||||
else result = this.aux3.stop();
|
||||
|
||||
// Then check if we need to keep the interval running
|
||||
if (!this.hasActiveTimers()) {
|
||||
this.stopInterval();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@broadcastReturn
|
||||
setTime(duration: number, index: number) {
|
||||
if (index === 1) return this.aux1.setTime(duration);
|
||||
if (index === 2) return this.aux2.setTime(duration);
|
||||
return this.aux3.setTime(duration);
|
||||
}
|
||||
|
||||
@broadcastReturn
|
||||
addTime(millis: number, index: number) {
|
||||
const aux = index === 1 ? this.aux1 : index === 2 ? this.aux2 : this.aux3;
|
||||
if (aux.state.playback === SimplePlayback.Start) {
|
||||
aux.addTime(millis);
|
||||
return aux.update(this.getTime());
|
||||
}
|
||||
return aux.addTime(millis);
|
||||
}
|
||||
|
||||
private update() {
|
||||
return this.timer.update(this.getTime());
|
||||
/**
|
||||
* The update function affects any running timers,
|
||||
* so we decide to emit a patch object rather
|
||||
* than using the decorator which would emit individual updates.
|
||||
*/
|
||||
const patch: AuxTimerStateUpdate = {};
|
||||
|
||||
const timeNow = this.getTime();
|
||||
if (this.aux1.state.playback === SimplePlayback.Start) {
|
||||
patch.auxtimer1 = this.aux1.update(timeNow);
|
||||
}
|
||||
|
||||
if (this.aux2.state.playback === SimplePlayback.Start) {
|
||||
patch.auxtimer2 = this.aux2.update(timeNow);
|
||||
}
|
||||
|
||||
if (this.aux3.state.playback === SimplePlayback.Start) {
|
||||
patch.auxtimer3 = this.aux3.update(timeNow);
|
||||
}
|
||||
|
||||
if (Object.keys(patch).length > 0) {
|
||||
this.emit(patch);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
function broadcastReturn(_target: any, _propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
function broadcastReturn(_target: object, _propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
const originalMethod = descriptor.value;
|
||||
|
||||
descriptor.value = function (...args: any[]) {
|
||||
descriptor.value = function (this: AuxTimerService, ...args: unknown[]) {
|
||||
const result = originalMethod.apply(this, args);
|
||||
// @ts-expect-error -- we can access private properties from the decorator
|
||||
(this as AuxTimerService).emit(result);
|
||||
const index = args[args.length - 1] as number;
|
||||
this.emit({ [`auxtimer${index}`]: result });
|
||||
return result;
|
||||
};
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
const emit = (state: SimpleTimerState) => eventStore.set('auxtimer1', state);
|
||||
const emit = (state: AuxTimerStateUpdate) => {
|
||||
for (const [key, value] of Object.entries(state)) {
|
||||
eventStore.set(key as keyof RuntimeStore, value);
|
||||
}
|
||||
};
|
||||
const timeNow = () => Date.now();
|
||||
|
||||
export const auxTimerService = new AuxTimerService(emit, timeNow);
|
||||
|
||||
@@ -33,7 +33,7 @@ export function validateTimerMessage(message: unknown): Partial<TimerMessage> {
|
||||
* Asserts that the secondary value is one of the permitted values
|
||||
*/
|
||||
function assertSecondary(source: unknown): source is TimerMessage['secondarySource'] {
|
||||
return source === 'aux' || source === 'secondary' || source === null;
|
||||
return source === 'aux1' || source === 'aux2' || source === 'aux3' || source === 'secondary' || source === null;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
Reference in New Issue
Block a user