diff --git a/apps/server/src/services/__tests__/timerUtils.test.ts b/apps/server/src/services/__tests__/timerUtils.test.ts index 1a07ab792..5900d6a1f 100644 --- a/apps/server/src/services/__tests__/timerUtils.test.ts +++ b/apps/server/src/services/__tests__/timerUtils.test.ts @@ -1,5 +1,5 @@ import { dayInMs } from 'ontime-utils'; -import { OntimeEvent, TimerType } from 'ontime-types'; +import { EndAction, OntimeEvent, Playback, TimeStrategy, TimerType } from 'ontime-types'; import { getCurrent, @@ -205,6 +205,10 @@ describe('getExpectedFinish()', () => { _timer: { pausedAt: null, }, + runtime: { + actualStart: 79200000, + plannedEnd: 600000, + }, } as RuntimeState; const calculatedFinish = getExpectedFinish(state); @@ -356,6 +360,9 @@ describe('getCurrent()', () => { startedAt: null, finishedAt: null, }, + runtime: { + plannedEnd: null, + }, _timer: { pausedAt: null, }, @@ -378,6 +385,9 @@ describe('getCurrent()', () => { startedAt: 10, finishedAt: null, }, + runtime: { + plannedEnd: 100, + }, _timer: { pausedAt: null, }, @@ -386,6 +396,7 @@ describe('getCurrent()', () => { const current = getCurrent(state); expect(current).toBe(70); }); + it('current time is the time to end + added time', () => { const state = { eventNow: { @@ -399,6 +410,9 @@ describe('getCurrent()', () => { startedAt: 10, finishedAt: null, }, + runtime: { + plannedEnd: 100, + }, _timer: { pausedAt: null, }, @@ -407,9 +421,40 @@ describe('getCurrent()', () => { const current = getCurrent(state); expect(current).toBe(77); }); + + it('handles events that start the day after', () => { + const state = { + eventNow: { + timeStart: 60000, // 00:01:00 + timeEnd: 600000, // 00:10:00 + timerType: TimerType.TimeToEnd, + }, + clock: 79500000, // 22:05:00 + timer: { + addedTime: 0, + duration: Infinity, // not relevant, + startedAt: 79200000, // 22:00:00 + finishedAt: null, + }, + runtime: { + plannedStart: 60000, // 00:01:00 + plannedEnd: 79200000, // 22:00:00 + }, + _timer: { + pausedAt: null, + }, + } as RuntimeState; + + const current = getCurrent(state); + // day - clock + start time + const expectedCurrent = dayInMs - 79500000 + 60000; + expect(current).toBe(expectedCurrent); + }); + it('handles events that finish the day after', () => { const state = { eventNow: { + timeStart: 79200000, // 22:00:00 timeEnd: 600000, // 00:10:00 timerType: TimerType.TimeToEnd, }, @@ -420,26 +465,9 @@ describe('getCurrent()', () => { startedAt: 79200000, // 22:00:00 finishedAt: null, }, - _timer: { - pausedAt: null, - }, - } as RuntimeState; - - const current = getCurrent(state); - expect(current).toBe(600000 + dayInMs - 79500000); - }); - it('does not update ', () => { - const state = { - eventNow: { - timeEnd: 600000, // 00:10:00 - timerType: TimerType.TimeToEnd, - }, - clock: 79500000, // 22:05:00 - timer: { - addedTime: 0, - duration: Infinity, // not relevant, - startedAt: 79200000, // 22:00:00 - finishedAt: null, + runtime: { + actualStart: 79200000, + plannedEnd: 600000, }, _timer: { pausedAt: null, @@ -447,7 +475,7 @@ describe('getCurrent()', () => { } as RuntimeState; const current = getCurrent(state); - expect(current).toBe(600000 + dayInMs - 79500000); + expect(current).toBe(dayInMs - 79500000 + 600000); }); }); }); @@ -1548,4 +1576,108 @@ describe('getRuntimeOffset()', () => { const offset = getRuntimeOffset(state); expect(offset).toBe(79521653 - 81000000); }); + + it('handles time-to-end', () => { + const state = { + clock: 80000000, // 22:13:20 + eventNow: { + id: 'd6a2ce', + type: 'event', + title: '', + timeStart: 77400000, // 21:30:00 + timeEnd: 81000000, // 22:30:00 + duration: 3600000, // 01:00:00 + timeStrategy: TimeStrategy.LockEnd, + linkStart: null, + endAction: EndAction.None, + timerType: TimerType.TimeToEnd, + isPublic: true, + skip: false, + note: '', + colour: '', + cue: '1', + revision: 0, + timeWarning: 120000, + timeDanger: 60000, + custom: {}, + delay: 0, + }, + runtime: { + selectedEventIndex: 0, + numEvents: 1, + offset: null, + plannedStart: 77400000, // 21:30:00 + plannedEnd: 81000000, // 22:30:00 + actualStart: 78000000, // 21:40:00 + expectedEnd: 81600000, // 22:40:00 + }, + timer: { + addedTime: 0, + current: 1600000, + duration: 3600000, + elapsed: 2000000, + expectedFinish: 81600000, + finishedAt: null, + playback: Playback.Play, + secondaryTimer: null, + startedAt: 78000000, + }, + _timer: { pausedAt: null, secondaryTarget: null, finishedNow: false }, + } as RuntimeState; + + const offset = getRuntimeOffset(state); + expect(offset).toBe(0); + }); + + it('handles time-to-end with delays', () => { + const state = { + clock: 82000000, // 22:46:40 + eventNow: { + id: 'd6a2ce', + type: 'event', + title: '', + timeStart: 77400000, // 21:30:00 + timeEnd: 81000000, // 22:30:00 + duration: 3600000, // 01:00:00 + timeStrategy: TimeStrategy.LockEnd, + linkStart: null, + endAction: EndAction.None, + timerType: TimerType.TimeToEnd, + isPublic: true, + skip: false, + note: '', + colour: '', + cue: '1', + revision: 0, + timeWarning: 120000, + timeDanger: 60000, + custom: {}, + delay: 0, + }, + runtime: { + selectedEventIndex: 0, + numEvents: 1, + offset: null, + plannedStart: 77400000, // 21:30:00 + plannedEnd: 81000000, // 22:30:00 + actualStart: 78000000, // 21:40:00 + expectedEnd: 81600000, // 22:40:00 + }, + timer: { + addedTime: 0, + current: -400000, + duration: 3600000, + elapsed: 4000000, + expectedFinish: 81600000, + finishedAt: null, + playback: Playback.Play, + secondaryTimer: null, + startedAt: 78000000, + }, + _timer: { pausedAt: null, secondaryTarget: null, finishedNow: false }, + } as RuntimeState; + + const offset = getRuntimeOffset(state); + expect(offset).toBe(-400000); + }); }); diff --git a/apps/server/src/services/timerUtils.ts b/apps/server/src/services/timerUtils.ts index 3cbd4d2a7..5a57a160c 100644 --- a/apps/server/src/services/timerUtils.ts +++ b/apps/server/src/services/timerUtils.ts @@ -53,16 +53,23 @@ export function getExpectedFinish(state: RuntimeState): MaybeNumber { * @param {RuntimeState} state runtime state * @returns {number} current time for timer */ + export function getCurrent(state: RuntimeState): number { const { startedAt, duration, addedTime } = state.timer; - const { timerType, timeEnd } = state.eventNow; + const { timerType, timeStart, timeEnd } = state.eventNow; const { pausedAt } = state._timer; const { clock } = state; if (timerType === TimerType.TimeToEnd) { - const isNextDay = startedAt > timeEnd; - const correctDay = isNextDay ? dayInMs : 0; - return timeEnd + addedTime + correctDay - clock; + const isEventOverMidnight = timeStart > timeEnd; + const hasFinishedRundownForToday = state.runtime.plannedEnd && clock > state.runtime.plannedEnd; + + if (hasFinishedRundownForToday && !isEventOverMidnight) { + return dayInMs - clock + state.eventNow.timeStart + addedTime; + } + + const correctDay = isEventOverMidnight ? dayInMs : 0; + return correctDay - clock + timeEnd + addedTime; } if (startedAt === null) { @@ -301,8 +308,9 @@ export function getRuntimeOffset(state: RuntimeState): MaybeNumber { if (state.runtime.actualStart === null) { return null; } + const { clock } = state; - const { timeStart } = state.eventNow; + const { timeStart, timerType } = state.eventNow; const { addedTime, current, startedAt } = state.timer; // if we havent started, the offset is the difference to the schedule @@ -311,6 +319,11 @@ export function getRuntimeOffset(state: RuntimeState): MaybeNumber { } const overtime = Math.min(current, 0); + // in time-to-end, offset is overtime + if (timerType === TimerType.TimeToEnd) { + return overtime; + } + const startOffset = startedAt - timeStart; const pausedTime = state._timer.pausedAt === null ? 0 : clock - state._timer.pausedAt;