diff --git a/apps/server/src/services/__tests__/rollUtils.test.ts b/apps/server/src/services/__tests__/rollUtils.test.ts index c555fd847..48fa6c0c7 100644 --- a/apps/server/src/services/__tests__/rollUtils.test.ts +++ b/apps/server/src/services/__tests__/rollUtils.test.ts @@ -1,10 +1,12 @@ import { dayInMs, MILLIS_PER_HOUR, MILLIS_PER_MINUTE } from 'ontime-utils'; -import { loadRoll } from '../rollUtils.js'; -import { makeRundown } from '../../api-data/rundown/__mocks__/rundown.mocks.js'; import { PlayableEvent } from 'ontime-types'; + import { initRundown } from '../../api-data/rundown/rundown.service.js'; -import { rundownCache } from '../../api-data/rundown/rundown.dao.js'; +import { processRundown, rundownCache } from '../../api-data/rundown/rundown.dao.js'; +import { makeOntimeEvent, makeRundown } from '../../api-data/rundown/__mocks__/rundown.mocks.js'; + +import { loadRoll } from '../rollUtils.js'; beforeAll(() => { vi.mock('../../classes/data-provider/DataProvider.js', () => { @@ -198,6 +200,24 @@ describe('loadRoll()', () => { const state = loadRoll(rundown, metadata, now); expect(state).toStrictEqual(expected); }); + + it('should ignore events with 0 duration', () => { + const rundown = makeRundown({ + entries: { + '1': makeOntimeEvent({ id: '1', timeStart: 70, timeEnd: 70, duration: 0 }), + '2': makeOntimeEvent({ id: '2', timeStart: 70, timeEnd: 170, duration: 100 }), + }, + order: ['1', '2'], + }); + const metadata = processRundown(rundown, {}); + const now = 70; + + const state = loadRoll(rundown, metadata, now); + expect(state).toStrictEqual({ + event: rundown.entries['2'], + index: 1, + }); + }); }); describe('loadRoll() handle edge cases with midnight', () => { diff --git a/apps/server/src/services/rollUtils.ts b/apps/server/src/services/rollUtils.ts index c40a64747..5c075d5cb 100644 --- a/apps/server/src/services/rollUtils.ts +++ b/apps/server/src/services/rollUtils.ts @@ -29,6 +29,7 @@ export function loadRoll( for (let i = 0; i < metadata.playableEventOrder.length; i++) { const event = rundown.entries[metadata.playableEventOrder[i]] as PlayableEvent; + // rolling into events of 0 duration would make the playback be stuck if (event.duration === 0) { continue; } diff --git a/apps/server/src/services/runtime-service/runtime.service.ts b/apps/server/src/services/runtime-service/runtime.service.ts index 1611921f7..5584bc1cd 100644 --- a/apps/server/src/services/runtime-service/runtime.service.ts +++ b/apps/server/src/services/runtime-service/runtime.service.ts @@ -1,5 +1,6 @@ import { EndAction, + EntryId, isOntimeEvent, isPlayableEvent, LogOrigin, @@ -386,11 +387,12 @@ class RuntimeService { * startSelected being a private function does not trigger emits * and pass on runtime offset in case of roll mode */ - private handleLoadNext(): boolean { + private handleLoadNext(fromId?: EntryId): boolean { const state = runtimeState.getState(); const { playableEventOrder } = getRundownMetadata(); - const nextId = findNextPlayableId(playableEventOrder, state.eventNow?.id); + const nextId = findNextPlayableId(playableEventOrder, fromId ?? state.eventNow?.id); + if (nextId) { const nextEvent = getEntryWithId(nextId); if (!nextEvent || !isOntimeEvent(nextEvent)) { @@ -398,6 +400,14 @@ class RuntimeService { } if (state.timer.playback === Playback.Roll) { + if (nextEvent.duration === 0) { + /** + * when loading next in roll mode, + * we need to prevent loading events of 0 duration since + * this would make the playback be stuck + */ + return this.handleLoadNext(nextId); + } return this.loadEvent(nextEvent, { firstStart: state.rundown.actualStart }); } return this.loadEvent(nextEvent); diff --git a/apps/server/src/stores/runtimeState.ts b/apps/server/src/stores/runtimeState.ts index ff3fbd510..77aacf19a 100644 --- a/apps/server/src/stores/runtimeState.ts +++ b/apps/server/src/stores/runtimeState.ts @@ -626,7 +626,7 @@ export function roll( : runtimeState.eventNow.timeEnd; runtimeState.timer.expectedFinish = normalisedEndTime; - //account for offset + // account for offset const offsetClock = runtimeState.clock - runtimeState.offset.absolute; // state catch up @@ -672,7 +672,7 @@ export function roll( // we need to persist the current group state across loads clearEventData(); - //account for offset but we only keep it if passed to us + // account for offset but we only keep it if passed to us runtimeState.offset.absolute = offset; const offsetClock = runtimeState.clock - runtimeState.offset.absolute;