mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-08 00:43:54 +00:00
197 lines
5.7 KiB
TypeScript
197 lines
5.7 KiB
TypeScript
import { OntimeEvent, RuntimeStore } from 'ontime-types';
|
|
|
|
import { deepEqual } from 'fast-equals';
|
|
|
|
import { eventStore } from '../stores/EventStore.js';
|
|
import * as runtimeState from '../stores/runtimeState.js';
|
|
import type { RuntimeState, UpdateResult } from '../stores/runtimeState.js';
|
|
|
|
import { restoreService } from './RestoreService.js';
|
|
|
|
/**
|
|
* Service manages Ontime's main timer
|
|
* It is responsible for streaming the data to the event store
|
|
*/
|
|
export class TimerService {
|
|
private readonly _interval: NodeJS.Timer;
|
|
/** how often we update the socket */
|
|
static _updateInterval: number;
|
|
/** how often we recalculate */
|
|
static _refreshInterval: number;
|
|
/** last time we updated the socket */
|
|
static previousUpdate: number;
|
|
/** last known state */
|
|
static previousState: RuntimeState;
|
|
|
|
/** when timer will be finished */
|
|
private endCallback: NodeJS.Timer;
|
|
|
|
private onUpdateCallback: (updateResult: UpdateResult) => void;
|
|
|
|
/**
|
|
* @constructor
|
|
* @param {number} [timerConfig.refresh] how often we recalculate
|
|
* @param {number} [timerConfig.updateInterval] how often we update the socket
|
|
* @param {function} [timerConfig.onUpdateCallback] how often we update the socket
|
|
*/
|
|
constructor(timerConfig: {
|
|
refresh: number;
|
|
updateInterval: number;
|
|
onUpdateCallback: (updateResult: UpdateResult) => void;
|
|
}) {
|
|
TimerService.previousUpdate = -1;
|
|
TimerService.previousState = {} as RuntimeState;
|
|
|
|
TimerService._updateInterval = timerConfig.updateInterval;
|
|
TimerService._refreshInterval = timerConfig.refresh;
|
|
|
|
this.onUpdateCallback = timerConfig.onUpdateCallback;
|
|
this._interval = setInterval(() => {
|
|
this.update();
|
|
}, TimerService._updateInterval);
|
|
}
|
|
|
|
@broadcastResult
|
|
start() {
|
|
if (!runtimeState.start()) {
|
|
return false;
|
|
}
|
|
|
|
const state = runtimeState.getState();
|
|
this.endCallback = setTimeout(this.update, state.timer.expectedFinish);
|
|
return true;
|
|
}
|
|
|
|
@broadcastResult
|
|
pause() {
|
|
if (!runtimeState.pause()) {
|
|
return false;
|
|
}
|
|
|
|
// cancel end callback
|
|
clearTimeout(this.endCallback);
|
|
return true;
|
|
}
|
|
|
|
@broadcastResult
|
|
stop() {
|
|
if (!runtimeState.stop()) {
|
|
return false;
|
|
}
|
|
|
|
// cancel end callback
|
|
clearTimeout(this.endCallback);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Adds time to running timer by given amount
|
|
* @param {number} amount
|
|
*/
|
|
@broadcastResult
|
|
addTime(amount: number): boolean {
|
|
if (!runtimeState.addTime(amount)) {
|
|
return false;
|
|
}
|
|
|
|
// renew end callback
|
|
clearTimeout(this.endCallback);
|
|
const state = runtimeState.getState();
|
|
this.endCallback = setTimeout(this.update, state.timer.expectedFinish);
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Update the app at regular intervals
|
|
*/
|
|
@broadcastResult
|
|
update() {
|
|
const updateResult = runtimeState.update();
|
|
|
|
// pass the result to the parent
|
|
this.onUpdateCallback(updateResult);
|
|
}
|
|
|
|
/**
|
|
* Loads roll information into timer service
|
|
* @param {OntimeEvent[]} rundown -- list of events to run
|
|
*/
|
|
@broadcastResult
|
|
roll(rundown: OntimeEvent[]) {
|
|
runtimeState.roll(rundown);
|
|
}
|
|
|
|
shutdown() {
|
|
clearInterval(this._interval);
|
|
clearTimeout(this.endCallback);
|
|
}
|
|
}
|
|
|
|
function broadcastResult(_target: any, _propertyKey: string, descriptor: PropertyDescriptor) {
|
|
const originalMethod = descriptor.value;
|
|
|
|
descriptor.value = function (...args: any[]) {
|
|
// call the original method and get the state
|
|
const result = originalMethod.apply(this, args);
|
|
const state = runtimeState.getState();
|
|
|
|
// we do the comparison by explicitly for each property
|
|
// to apply custom logic for different datasets
|
|
|
|
// some of the data, we only update at intervals
|
|
const isTimeToUpdate = state.clock - TimerService.previousUpdate >= TimerService._updateInterval;
|
|
|
|
// some changes need an immediate update
|
|
const hasSkippedBack = state.clock < TimerService.previousUpdate;
|
|
const justStarted = !TimerService.previousState?.timer;
|
|
const hasChangedPlayback = TimerService.previousState.timer?.playback !== state.timer.playback;
|
|
const hasImmediateChanges = hasSkippedBack || justStarted || hasChangedPlayback;
|
|
|
|
if (hasImmediateChanges || (isTimeToUpdate && !deepEqual(TimerService.previousState?.timer, state.timer))) {
|
|
eventStore.set('timer', state.timer);
|
|
TimerService.previousState.timer = { ...state.timer };
|
|
}
|
|
|
|
if (isTimeToUpdate && !deepEqual(TimerService.previousState?.runtime, state.runtime)) {
|
|
eventStore.set('runtime', state.runtime);
|
|
TimerService.previousState.runtime = { ...state.runtime };
|
|
}
|
|
|
|
// Update the events if they have changed
|
|
updateEventIfChanged('eventNow', state);
|
|
updateEventIfChanged('publicEventNow', state);
|
|
updateEventIfChanged('eventNext', state);
|
|
updateEventIfChanged('publicEventNext', state);
|
|
|
|
if (isTimeToUpdate) {
|
|
TimerService.previousUpdate = state.clock;
|
|
eventStore.set('clock', state.clock);
|
|
saveRestoreState(state);
|
|
}
|
|
|
|
// Helper function to update an event if it has changed
|
|
function updateEventIfChanged(eventKey: keyof RuntimeStore, state: RuntimeState) {
|
|
if (!deepEqual(TimerService.previousState?.[eventKey], state[eventKey])) {
|
|
eventStore.set(eventKey, state[eventKey]);
|
|
TimerService.previousState[eventKey] = { ...state[eventKey] };
|
|
}
|
|
}
|
|
|
|
// Helper function to save the restore state
|
|
function saveRestoreState(state: RuntimeState) {
|
|
restoreService.save({
|
|
playback: state.timer.playback,
|
|
selectedEventId: state.eventNow?.id ?? null,
|
|
startedAt: state.timer.startedAt,
|
|
addedTime: state.timer.addedTime,
|
|
pausedAt: state._timer.pausedAt,
|
|
firstStart: state.runtime.actualStart,
|
|
});
|
|
}
|
|
|
|
return result;
|
|
};
|
|
|
|
return descriptor;
|
|
}
|