diff --git a/apps/server/src/services/__tests__/rollUtils.test.ts b/apps/server/src/services/__tests__/rollUtils.test.ts index 48fa6c0c7..51f4353b7 100644 --- a/apps/server/src/services/__tests__/rollUtils.test.ts +++ b/apps/server/src/services/__tests__/rollUtils.test.ts @@ -709,6 +709,44 @@ describe('loadRoll() test that roll behaviour multi day event edge cases', () => expect(state).toStrictEqual(expected); }); + it('should recognise a playing event where its schedule spans over midnight, even with previous events', async () => { + vi.useFakeTimers(); + await initRundown( + makeRundown({ + order: ['1', '2'], + entries: { + '1': { + ...mockEvent, + id: '1', + timeStart: 10 * MILLIS_PER_HOUR, + timeEnd: 11 * MILLIS_PER_HOUR, + duration: 1 * MILLIS_PER_HOUR, + }, + '2': { + ...mockEvent, + id: '2', + timeStart: 11 * MILLIS_PER_HOUR, + timeEnd: 2 * MILLIS_PER_HOUR, + duration: 14 * MILLIS_PER_HOUR + }, + }, + }), + {}, + ); + vi.runAllTimers(); + vi.useRealTimers(); + + const { rundown, metadata } = rundownCache.get(); + const now = 1 * MILLIS_PER_HOUR; + const expected = { + event: rundown.entries['2'], + index: 1, + }; + + const state = loadRoll(rundown, metadata, now); + expect(state).toStrictEqual(expected); + }); + it('if the start time is the day after end time, and both are later than now', async () => { vi.useFakeTimers(); await initRundown( diff --git a/apps/server/src/services/rollUtils.ts b/apps/server/src/services/rollUtils.ts index b20d5bb3a..abdfcaa81 100644 --- a/apps/server/src/services/rollUtils.ts +++ b/apps/server/src/services/rollUtils.ts @@ -1,4 +1,4 @@ -import { dayInMs } from 'ontime-utils'; +import { checkIsNow, dayInMs } from 'ontime-utils'; import { MaybeNumber, PlayableEvent, Rundown } from 'ontime-types'; import { normaliseEndTime } from './timerUtils.js'; @@ -36,11 +36,6 @@ export function loadRoll( continue; } - // we check if event crosses midnight - if (event.timeStart > event.timeEnd) { - daySpan++; - } - const correctedDays = dayInMs * daySpan; const correctedStart = event.timeStart + correctedDays; const correctedEnd = event.timeEnd + correctedDays; @@ -49,13 +44,18 @@ export function loadRoll( * there are 3 possible states for an event * 1. event is already finished * 2. event is running - * 3. event is in the future + * 3. event is running (midnight edge case) + * 4. event is in the future */ // 1. event is already finished // when does the event end (handle midnight) const normalEnd = normaliseEndTime(correctedStart, correctedEnd); if (normalEnd <= timeNow) { + // keep track of day progress + if (event.timeStart > event.timeEnd) { + daySpan++; + } continue; } @@ -66,8 +66,25 @@ export function loadRoll( return { event, index: getTimedIndexFromPlayableIndex(metadata, i) }; } - // 3. event will run in the future - // we set the isPending flag to indicate that the event is currently playing + // 3. check if an overnight event that comes later is currently playing + for (let j = i + 1; j < metadata.playableEventOrder.length; j++) { + const futureEventId = metadata.playableEventOrder[j]; + if (!futureEventId) continue; + + const futureEvent = rundown.entries[futureEventId] as PlayableEvent; + if (!futureEvent || futureEvent.duration === 0) continue; + + const crossesMidnight = futureEvent.timeStart > futureEvent.timeEnd; + if (!crossesMidnight) continue; + + // check if timeNow is inside this overnight event + const isRunning = checkIsNow(futureEvent.timeStart, futureEvent.timeEnd, timeNow); + if (isRunning) { + return { event: futureEvent, index: getTimedIndexFromPlayableIndex(metadata, j) }; + } + } + + // 4. event will run in the future return { event, index: getTimedIndexFromPlayableIndex(metadata, i), isPending: true }; } diff --git a/packages/utils/src/date-utils/checkIsNow.test.ts b/packages/utils/src/date-utils/checkIsNow.test.ts index 89ab659a7..1682ba838 100644 --- a/packages/utils/src/date-utils/checkIsNow.test.ts +++ b/packages/utils/src/date-utils/checkIsNow.test.ts @@ -24,6 +24,18 @@ describe('checkIsNow()', () => { }); test('should return true accounting for events that roll over midnight', () => { - expect(checkIsNow(22 * MILLIS_PER_HOUR, 8 * MILLIS_PER_HOUR, 23 * MILLIS_PER_HOUR)).toBe(true); + const timeStart = 22 * MILLIS_PER_HOUR; + const timeEnd = 8 * MILLIS_PER_HOUR; + const now = 23 * MILLIS_PER_HOUR; + + expect(checkIsNow(timeStart, timeEnd, now)).toBe(true); + }); + + test('should return true accounting for events that roll over midnight (2)', () => { + const timeStart = 22 * MILLIS_PER_HOUR; + const timeEnd = 8 * MILLIS_PER_HOUR; + const now = 1 * MILLIS_PER_HOUR; + + expect(checkIsNow(timeStart, timeEnd, now)).toBe(true); }); }); diff --git a/packages/utils/src/date-utils/checkIsNow.ts b/packages/utils/src/date-utils/checkIsNow.ts index f5a4591fa..f941c5516 100644 --- a/packages/utils/src/date-utils/checkIsNow.ts +++ b/packages/utils/src/date-utils/checkIsNow.ts @@ -1,9 +1,10 @@ -import { dayInMs } from './conversionUtils.js'; - /** * Utility function checks whether a given event should be playing now */ export function checkIsNow(timeStart: number, timeEnd: number, clock: number): boolean { - const normalisedEnd = timeEnd < timeStart ? timeEnd + dayInMs : timeEnd; - return timeStart <= clock && clock <= normalisedEnd; + if (timeEnd < timeStart) { + // overnight event: clock is either after start OR before end + return clock >= timeStart || clock <= timeEnd; + } + return timeStart <= clock && clock <= timeEnd; }