mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-13 11:23:50 +00:00
feat: many timers (#706)
* wip: create simple timer class * refactor: service to manage extra timers * wip:ui for controlling the extra timer --------- Co-authored-by: arc-alex <ac@omnivox.dk>
This commit is contained in:
@@ -1,4 +1,4 @@
|
||||
import { HttpSettings, LogOrigin, OSCSettings, Playback } from 'ontime-types';
|
||||
import { HttpSettings, LogOrigin, OSCSettings, Playback, SimpleDirection, SimplePlayback } from 'ontime-types';
|
||||
|
||||
import 'dotenv/config';
|
||||
import express from 'express';
|
||||
@@ -168,6 +168,12 @@ export const startServer = async () => {
|
||||
publicEventNow: state.publicEventNow,
|
||||
eventNext: state.eventNext,
|
||||
publicEventNext: state.publicEventNext,
|
||||
timer1: {
|
||||
duration: null,
|
||||
current: null,
|
||||
playback: SimplePlayback.Stop,
|
||||
direction: SimpleDirection.CountDown,
|
||||
},
|
||||
});
|
||||
|
||||
// load restore point if it exists
|
||||
|
||||
@@ -0,0 +1,78 @@
|
||||
import { SimpleDirection, SimplePlayback, SimpleTimerState } from 'ontime-types';
|
||||
|
||||
export class SimpleTimer {
|
||||
state: SimpleTimerState = {
|
||||
duration: 0,
|
||||
current: 0,
|
||||
playback: SimplePlayback.Stop,
|
||||
direction: SimpleDirection.CountDown,
|
||||
};
|
||||
private startedAt: number | null = null;
|
||||
private pausedAt: number | null = null;
|
||||
|
||||
constructor() {}
|
||||
|
||||
public reset() {
|
||||
this.state = {
|
||||
duration: 0,
|
||||
current: 0,
|
||||
playback: SimplePlayback.Stop,
|
||||
direction: SimpleDirection.CountDown,
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the duration of the timer
|
||||
* @param time - time in milliseconds
|
||||
*/
|
||||
public setTime(time: number): SimpleTimerState {
|
||||
this.state.duration = time;
|
||||
this.state.current = time;
|
||||
return this.state;
|
||||
}
|
||||
|
||||
public setDirection(direction: SimpleDirection): SimpleTimerState {
|
||||
this.state.playback = SimplePlayback.Stop;
|
||||
this.state.current = this.state.duration;
|
||||
this.state.direction = direction;
|
||||
return this.state;
|
||||
}
|
||||
|
||||
public start(timeNow: number): SimpleTimerState {
|
||||
if (this.state.playback === SimplePlayback.Pause) {
|
||||
const elapsedSincePause = this.pausedAt - this.startedAt;
|
||||
this.startedAt = timeNow - elapsedSincePause;
|
||||
} else if (this.state.playback === SimplePlayback.Stop) {
|
||||
this.startedAt = timeNow;
|
||||
}
|
||||
this.state.playback = SimplePlayback.Start;
|
||||
return this.update(timeNow);
|
||||
}
|
||||
|
||||
public pause(timeNow: number): SimpleTimerState {
|
||||
if (this.state.playback !== SimplePlayback.Start) return this.state;
|
||||
this.state.playback = SimplePlayback.Pause;
|
||||
this.pausedAt = timeNow;
|
||||
return this.state;
|
||||
}
|
||||
|
||||
public stop(): SimpleTimerState {
|
||||
this.state.playback = SimplePlayback.Stop;
|
||||
this.state.current = this.state.duration;
|
||||
this.startedAt = null;
|
||||
return this.state;
|
||||
}
|
||||
|
||||
public update(timeNow: number): SimpleTimerState {
|
||||
if (this.state.playback === SimplePlayback.Start) {
|
||||
const elapsed = timeNow - this.startedAt;
|
||||
if (this.state.direction === SimpleDirection.CountDown) {
|
||||
this.state.current = this.state.duration - elapsed;
|
||||
} else if (this.state.direction === SimpleDirection.CountUp) {
|
||||
this.state.current = this.state.duration + elapsed;
|
||||
}
|
||||
}
|
||||
|
||||
return this.state;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,128 @@
|
||||
import { SimpleDirection, SimplePlayback, SimpleTimerState } from 'ontime-types';
|
||||
|
||||
import { SimpleTimer } from '../SimpleTimer.js';
|
||||
|
||||
describe('SimpleTimer count-down', () => {
|
||||
let timer: SimpleTimer;
|
||||
|
||||
describe('normal timer flow', () => {
|
||||
const initialTime = 1000;
|
||||
timer = new SimpleTimer();
|
||||
|
||||
test('setting the timer duration', () => {
|
||||
const newState = timer.setTime(initialTime);
|
||||
const expected: SimpleTimerState = {
|
||||
duration: initialTime,
|
||||
current: initialTime,
|
||||
direction: SimpleDirection.CountDown,
|
||||
playback: SimplePlayback.Stop,
|
||||
};
|
||||
expect(newState).toStrictEqual(expected);
|
||||
});
|
||||
|
||||
test('setting the timer to play', () => {
|
||||
const newState = timer.start(0);
|
||||
const expected: SimpleTimerState = {
|
||||
duration: initialTime,
|
||||
current: initialTime,
|
||||
direction: SimpleDirection.CountDown,
|
||||
playback: SimplePlayback.Start,
|
||||
};
|
||||
expect(newState).toStrictEqual(expected);
|
||||
});
|
||||
|
||||
test('updating the timer', () => {
|
||||
let newState = timer.update(100);
|
||||
const expected: SimpleTimerState = {
|
||||
duration: initialTime,
|
||||
current: initialTime - 100,
|
||||
direction: SimpleDirection.CountDown,
|
||||
playback: SimplePlayback.Start,
|
||||
};
|
||||
expect(newState).toStrictEqual(expected);
|
||||
|
||||
newState = timer.update(500);
|
||||
expected.current = initialTime - 500;
|
||||
expect(newState).toStrictEqual(expected);
|
||||
|
||||
newState = timer.update(1500);
|
||||
expected.current = initialTime - 1500;
|
||||
expect(newState).toStrictEqual(expected);
|
||||
});
|
||||
|
||||
test('pausing the time doesnt affect the current', () => {
|
||||
const pausedTime = 200;
|
||||
let newState = timer.pause(1500);
|
||||
const expected: SimpleTimerState = {
|
||||
duration: initialTime,
|
||||
current: initialTime - 1500,
|
||||
direction: SimpleDirection.CountDown,
|
||||
playback: SimplePlayback.Pause,
|
||||
};
|
||||
expect(newState).toStrictEqual(expected);
|
||||
|
||||
newState = timer.update(1600);
|
||||
expect(newState).toStrictEqual(expected);
|
||||
|
||||
newState = timer.update(1700);
|
||||
expect(newState).toStrictEqual(expected);
|
||||
|
||||
newState = timer.start(1700);
|
||||
expected.playback = SimplePlayback.Start;
|
||||
expect(newState).toStrictEqual(expected);
|
||||
|
||||
newState = timer.update(1800);
|
||||
(expected.current = initialTime - 1800 + pausedTime), expect(newState).toStrictEqual(expected);
|
||||
});
|
||||
|
||||
test('stopping the timer clears the running data', () => {
|
||||
const newState = timer.stop();
|
||||
const expected: SimpleTimerState = {
|
||||
duration: initialTime,
|
||||
current: initialTime,
|
||||
direction: SimpleDirection.CountDown,
|
||||
playback: SimplePlayback.Stop,
|
||||
};
|
||||
expect(newState).toStrictEqual(expected);
|
||||
});
|
||||
|
||||
test('count-up mode', () => {
|
||||
const initialState = timer.setDirection(SimpleDirection.CountUp);
|
||||
expect(initialState.current).toBe(initialTime);
|
||||
|
||||
let newState = timer.start(0);
|
||||
const expected: SimpleTimerState = {
|
||||
duration: initialTime,
|
||||
current: initialTime,
|
||||
direction: SimpleDirection.CountUp,
|
||||
playback: SimplePlayback.Start,
|
||||
};
|
||||
|
||||
newState = timer.update(100);
|
||||
expected.current = initialTime + 100;
|
||||
expect(newState).toStrictEqual(expected);
|
||||
|
||||
expect(newState).toStrictEqual(expected);
|
||||
|
||||
newState = timer.update(500);
|
||||
expected.current = initialTime + 500;
|
||||
expect(newState).toStrictEqual(expected);
|
||||
|
||||
newState = timer.update(1500);
|
||||
expected.current = initialTime + 1500;
|
||||
expect(newState).toStrictEqual(expected);
|
||||
});
|
||||
|
||||
test('changing direction stops the timer', () => {
|
||||
const newState = timer.setDirection(SimpleDirection.CountDown);
|
||||
const expected: SimpleTimerState = {
|
||||
duration: initialTime,
|
||||
current: initialTime,
|
||||
direction: SimpleDirection.CountDown,
|
||||
playback: SimplePlayback.Stop,
|
||||
};
|
||||
|
||||
expect(newState).toStrictEqual(expected);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -1,4 +1,4 @@
|
||||
import { DeepPartial, MessageState } from 'ontime-types';
|
||||
import { DeepPartial, MessageState, SimpleDirection, SimplePlayback } from 'ontime-types';
|
||||
|
||||
// skipcq: JS-C1003 - we like the API
|
||||
import * as assert from '../utils/assert.js';
|
||||
@@ -8,6 +8,7 @@ import { messageService } from '../services/message-service/MessageService.js';
|
||||
import { runtimeService } from '../services/runtime-service/RuntimeService.js';
|
||||
import { eventStore } from '../stores/EventStore.js';
|
||||
import { parse, updateEvent } from './integrationController.config.js';
|
||||
import { extraTimerService } from '../services/extra-timer-service/ExtraTimerService.js';
|
||||
import { validateMessage, validateTimerMessage } from '../services/message-service/messageUtils.js';
|
||||
|
||||
export type ChangeOptions = {
|
||||
@@ -152,6 +153,40 @@ const actionHandlers: Record<string, ActionHandler> = {
|
||||
runtimeService.addTime(time * 1000);
|
||||
return { payload: 'success' };
|
||||
},
|
||||
/* Extra timers */
|
||||
extratimer: (payload) => {
|
||||
if (payload && typeof payload === 'string') {
|
||||
if (payload === SimplePlayback.Start) {
|
||||
const reply = extraTimerService.start();
|
||||
return { payload: reply };
|
||||
}
|
||||
if (payload === SimplePlayback.Pause) {
|
||||
const reply = extraTimerService.pause();
|
||||
return { payload: reply };
|
||||
}
|
||||
if (payload === SimplePlayback.Stop) {
|
||||
const reply = extraTimerService.stop();
|
||||
return { payload: reply };
|
||||
}
|
||||
}
|
||||
|
||||
if (payload && typeof payload === 'object') {
|
||||
if ('settime' in payload) {
|
||||
const time = numberOrError(payload.settime);
|
||||
const reply = extraTimerService.setTime(time);
|
||||
return { payload: reply };
|
||||
}
|
||||
if ('direction' in payload) {
|
||||
if (payload.direction === SimpleDirection.CountUp || payload.direction === SimpleDirection.CountDown) {
|
||||
const reply = extraTimerService.setDirection(payload.direction);
|
||||
return { payload: reply };
|
||||
} else {
|
||||
throw new Error('Invalid direction payload');
|
||||
}
|
||||
}
|
||||
}
|
||||
throw new Error('Invalid extratimer payload');
|
||||
},
|
||||
};
|
||||
/**
|
||||
* Returns a value of type number, converting if necessary
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
import { SimpleDirection, SimpleTimerState } from 'ontime-types';
|
||||
|
||||
import { SimpleTimer } from '../../classes/simple-timer/SimpleTimer.js';
|
||||
import { eventStore } from '../../stores/EventStore.js';
|
||||
|
||||
export type EmitFn = (state: SimpleTimerState) => void;
|
||||
export type GetTimeFn = () => number;
|
||||
|
||||
export class ExtraTimerService {
|
||||
private timer: SimpleTimer;
|
||||
private interval: NodeJS.Timer;
|
||||
private emit: EmitFn;
|
||||
private getTime: GetTimeFn;
|
||||
|
||||
constructor(emit: EmitFn, getTime: GetTimeFn) {
|
||||
this.timer = new SimpleTimer();
|
||||
this.emit = emit;
|
||||
this.getTime = getTime;
|
||||
}
|
||||
|
||||
private startInterval() {
|
||||
this.interval = setInterval(this.update.bind(this), 500);
|
||||
}
|
||||
|
||||
private stopInterval() {
|
||||
clearInterval(this.interval);
|
||||
}
|
||||
|
||||
@broadcastReturn
|
||||
setDirection(direction: SimpleDirection) {
|
||||
return this.timer.setDirection(direction);
|
||||
}
|
||||
|
||||
@broadcastReturn
|
||||
start() {
|
||||
this.startInterval();
|
||||
return this.timer.start(this.getTime());
|
||||
}
|
||||
|
||||
@broadcastReturn
|
||||
pause() {
|
||||
return this.timer.pause(this.getTime());
|
||||
}
|
||||
|
||||
@broadcastReturn
|
||||
stop() {
|
||||
this.stopInterval();
|
||||
return this.timer.stop();
|
||||
}
|
||||
|
||||
@broadcastReturn
|
||||
setTime(duration: number) {
|
||||
return this.timer.setTime(duration);
|
||||
}
|
||||
|
||||
@broadcastReturn
|
||||
private update() {
|
||||
return this.timer.update(this.getTime());
|
||||
}
|
||||
}
|
||||
|
||||
function broadcastReturn(_target: any, _propertyKey: string, descriptor: PropertyDescriptor) {
|
||||
const originalMethod = descriptor.value;
|
||||
|
||||
descriptor.value = function (...args: any[]) {
|
||||
const result = originalMethod.apply(this, args);
|
||||
this.emit(result);
|
||||
return result;
|
||||
};
|
||||
|
||||
return descriptor;
|
||||
}
|
||||
|
||||
const emit = (state: SimpleTimerState) => eventStore.set('timer1', state);
|
||||
const timeNow = () => Date.now();
|
||||
|
||||
export const extraTimerService = new ExtraTimerService(emit, timeNow);
|
||||
Reference in New Issue
Block a user