mirror of
https://github.com/cpvalente/ontime.git
synced 2026-08-12 10:53:51 +00:00
End pause (#832)
* refactor: trigger ahead * refactor: refresh often * fix: handle empty rundown updates * fix: handle falsy values * style: avoid overlap of components * refactor: freeze end option
This commit is contained in:
@@ -17,11 +17,12 @@ export async function postViewSettings(req: Request, res: Response<ViewSettings
|
||||
|
||||
try {
|
||||
const newData = {
|
||||
overrideStyles: req.body.overrideStyles,
|
||||
endMessage: req.body?.endMessage || '',
|
||||
normalColor: req.body.normalColor,
|
||||
warningColor: req.body.warningColor,
|
||||
dangerColor: req.body.dangerColor,
|
||||
endMessage: req.body?.endMessage ?? '',
|
||||
freezeEnd: req.body.freezeEnd,
|
||||
normalColor: req.body.normalColor,
|
||||
overrideStyles: req.body.overrideStyles,
|
||||
warningColor: req.body.warningColor,
|
||||
};
|
||||
await DataProvider.setViewSettings(newData);
|
||||
res.status(200).send(newData);
|
||||
|
||||
@@ -23,6 +23,7 @@ describe('safeMerge', () => {
|
||||
},
|
||||
viewSettings: {
|
||||
overrideStyles: false,
|
||||
freezeEnd: false,
|
||||
endMessage: 'existing endMessage',
|
||||
normalColor: '#ffffffcc',
|
||||
warningColor: '#FFAB33',
|
||||
|
||||
@@ -2,4 +2,5 @@ export const timerConfig = {
|
||||
skipLimit: 1000, // threshold of skip for recalculating
|
||||
updateRate: 32, // how often do we update the timer
|
||||
notificationRate: 1000, // how often do we notify clients and integrations
|
||||
triggerAhead: 16, // how far ahead do we trigger the end event
|
||||
};
|
||||
|
||||
@@ -25,6 +25,7 @@ export const dbModel: DatabaseModel = {
|
||||
normalColor: '#ffffffcc',
|
||||
warningColor: '#FFAB33',
|
||||
dangerColor: '#ED3333',
|
||||
freezeEnd: false,
|
||||
endMessage: '',
|
||||
},
|
||||
urlPresets: [],
|
||||
|
||||
@@ -48,7 +48,7 @@ export class TimerService {
|
||||
this.onUpdateCallback = timerConfig.onUpdateCallback;
|
||||
this._interval = setInterval(() => {
|
||||
this.update();
|
||||
}, TimerService._updateInterval);
|
||||
}, TimerService._refreshInterval);
|
||||
}
|
||||
|
||||
@broadcastResult
|
||||
@@ -58,7 +58,8 @@ export class TimerService {
|
||||
}
|
||||
|
||||
const state = runtimeState.getState();
|
||||
this.endCallback = setTimeout(() => this.update(), state.timer.expectedFinish);
|
||||
const endTime = state.timer.current - 10;
|
||||
this.endCallback = setTimeout(() => this.update(), endTime);
|
||||
return true;
|
||||
}
|
||||
|
||||
@@ -107,7 +108,6 @@ export class TimerService {
|
||||
@broadcastResult
|
||||
update() {
|
||||
const updateResult = runtimeState.update();
|
||||
|
||||
// pass the result to the parent
|
||||
this.onUpdateCallback(updateResult);
|
||||
}
|
||||
@@ -143,6 +143,7 @@ function broadcastResult(_target: any, _propertyKey: string, descriptor: Propert
|
||||
|
||||
// some changes need an immediate update
|
||||
const hasNewLoaded = state.eventNow?.id !== TimerService.previousState?.eventNow?.id;
|
||||
|
||||
const hasSkippedBack = state.clock < TimerService.previousUpdate;
|
||||
const justStarted = !TimerService.previousState?.timer;
|
||||
const hasChangedPlayback = TimerService.previousState.timer?.playback !== state.timer.playback;
|
||||
@@ -175,7 +176,27 @@ function broadcastResult(_target: any, _propertyKey: string, descriptor: Propert
|
||||
|
||||
// Helper function to update an event if it has changed
|
||||
function updateEventIfChanged(eventKey: keyof RuntimeStore, state: RuntimeState) {
|
||||
const previous = TimerService.previousState?.[eventKey];
|
||||
const now = state[eventKey];
|
||||
|
||||
// if there was nothing, and there is nothing, noop
|
||||
if (!previous?.id && !now?.id) {
|
||||
return;
|
||||
}
|
||||
|
||||
// if load status changed, save new
|
||||
if (previous?.id !== now?.id) {
|
||||
storeKey(eventKey);
|
||||
return;
|
||||
}
|
||||
|
||||
// maybe the event itself has changed
|
||||
if (!deepEqual(TimerService.previousState?.[eventKey], state[eventKey])) {
|
||||
storeKey(eventKey);
|
||||
return;
|
||||
}
|
||||
|
||||
function storeKey(eventKey: keyof RuntimeStore) {
|
||||
eventStore.set(eventKey, state[eventKey]);
|
||||
TimerService.previousState[eventKey] = { ...state[eventKey] };
|
||||
}
|
||||
|
||||
@@ -1219,7 +1219,7 @@ describe('updateRoll()', () => {
|
||||
clock: 11,
|
||||
timer: {
|
||||
current: 10,
|
||||
expectedFinish: 15,
|
||||
expectedFinish: 100,
|
||||
secondaryTimer: null,
|
||||
startedAt: 1,
|
||||
},
|
||||
@@ -1229,7 +1229,7 @@ describe('updateRoll()', () => {
|
||||
} as RuntimeState;
|
||||
|
||||
const expected = {
|
||||
updatedTimer: 15 - 11,
|
||||
updatedTimer: 100 - 11,
|
||||
updatedSecondaryTimer: null, // usually clock - expectedFinish
|
||||
doRollLoad: false,
|
||||
isFinished: false,
|
||||
|
||||
@@ -100,8 +100,8 @@ export async function deleteAllEvents() {
|
||||
// notify event loader that rundown has changed
|
||||
updateRuntimeOnChange();
|
||||
|
||||
// no need to modify timer since we will reset
|
||||
notifyChanges({ external: true });
|
||||
// notify timer and external services of change
|
||||
notifyChanges({ timer: true, external: true });
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -213,10 +213,15 @@ function updateRuntimeOnChange() {
|
||||
export function notifyChanges(options: { timer?: boolean | string[]; external?: boolean }) {
|
||||
if (options.timer) {
|
||||
const playableEvents = getPlayableEvents();
|
||||
// notify timer service of changed events
|
||||
// timer can be true or an array of changed IDs
|
||||
const affected = Array.isArray(options.timer) ? options.timer : undefined;
|
||||
runtimeService.maybeUpdate(playableEvents, affected);
|
||||
|
||||
if (playableEvents.length === 0) {
|
||||
runtimeService.stop();
|
||||
} else {
|
||||
// notify timer service of changed events
|
||||
// timer can be true or an array of changed IDs
|
||||
const affected = Array.isArray(options.timer) ? options.timer : undefined;
|
||||
runtimeService.maybeUpdate(playableEvents, affected);
|
||||
}
|
||||
}
|
||||
|
||||
if (options.external) {
|
||||
|
||||
@@ -70,7 +70,7 @@ class RuntimeService {
|
||||
this.eventTimer = new TimerService({
|
||||
refresh: timerConfig.updateRate,
|
||||
updateInterval: timerConfig.notificationRate,
|
||||
onUpdateCallback: () => this.checkTimerUpdate,
|
||||
onUpdateCallback: (updateResult) => this.checkTimerUpdate(updateResult),
|
||||
});
|
||||
|
||||
if (resumable) {
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { MaybeNumber, MaybeString, OntimeEvent, TimerType } from 'ontime-types';
|
||||
import { dayInMs, sortArrayByProperty } from 'ontime-utils';
|
||||
import { RuntimeState } from '../stores/runtimeState.js';
|
||||
import { timerConfig } from '../config/config.js';
|
||||
|
||||
/**
|
||||
* handle events that span over midnight
|
||||
@@ -272,7 +273,7 @@ export const updateRoll = (state: RuntimeState) => {
|
||||
updatedTimer -= dayInMs;
|
||||
}
|
||||
|
||||
if (updatedTimer < 0) {
|
||||
if (updatedTimer <= timerConfig.triggerAhead) {
|
||||
isPrimaryFinished = true;
|
||||
// we need a new event
|
||||
doRollLoad = true;
|
||||
|
||||
@@ -407,7 +407,8 @@ export function update(): UpdateResult {
|
||||
function onPlayUpdate() {
|
||||
let isFinished = false;
|
||||
runtimeState.timer.current = getCurrent(runtimeState);
|
||||
const finishedNow = runtimeState.timer.current <= 0 && runtimeState.timer.finishedAt === null;
|
||||
const finishedNow =
|
||||
runtimeState.timer.current <= timerConfig.triggerAhead && runtimeState.timer.finishedAt === null;
|
||||
|
||||
if (runtimeState.timer.playback === Playback.Play && finishedNow) {
|
||||
runtimeState.timer.finishedAt = runtimeState.clock;
|
||||
|
||||
Reference in New Issue
Block a user