diff --git a/packages/blueprints-integration/src/context/index.ts b/packages/blueprints-integration/src/context/index.ts index a1cba0ab9f..28e9a4ed2d 100644 --- a/packages/blueprints-integration/src/context/index.ts +++ b/packages/blueprints-integration/src/context/index.ts @@ -11,3 +11,4 @@ export * from './rundownContext.js' export * from './showStyleContext.js' export * from './studioContext.js' export * from './syncIngestChangesContext.js' +export * from './tTimersContext.js' diff --git a/packages/blueprints-integration/src/context/rundownContext.ts b/packages/blueprints-integration/src/context/rundownContext.ts index cb57cbd569..702d885bee 100644 --- a/packages/blueprints-integration/src/context/rundownContext.ts +++ b/packages/blueprints-integration/src/context/rundownContext.ts @@ -5,11 +5,15 @@ import type { IShowStyleContext } from './showStyleContext.js' import type { IExecuteTSRActionsContext } from './executeTsrActionContext.js' import type { IDataStoreMethods } from './adlibActionContext.js' import { ITTimersContext } from './tTimersContext.js' +import type { Time } from '../common.js' export interface IRundownContext extends IShowStyleContext { readonly rundownId: string readonly playlistId: string readonly rundown: Readonly + + /** Actual time of playback starting for the playlist (undefined if not started) */ + readonly startedPlayback?: Time } export interface IRundownUserContext extends IUserNotesContext, IRundownContext {} diff --git a/packages/blueprints-integration/src/context/tTimersContext.ts b/packages/blueprints-integration/src/context/tTimersContext.ts index 7b00d9258a..aba8e72221 100644 --- a/packages/blueprints-integration/src/context/tTimersContext.ts +++ b/packages/blueprints-integration/src/context/tTimersContext.ts @@ -1,5 +1,75 @@ export type IPlaylistTTimerIndex = 1 | 2 | 3 +export type RundownTTimerMode = RundownTTimerModeFreeRun | RundownTTimerModeCountdown | RundownTTimerModeTimeOfDay + +export interface RundownTTimerModeFreeRun { + readonly type: 'freeRun' +} +export interface RundownTTimerModeCountdown { + readonly type: 'countdown' + /** + * The original duration of the countdown in milliseconds, so that we know what value to reset to + */ + readonly duration: number + + /** + * If the countdown should stop at zero, or continue into negative values + */ + readonly stopAtZero: boolean +} +export interface RundownTTimerModeTimeOfDay { + readonly type: 'timeOfDay' + + /** + * The raw target string of the timer, as provided when setting the timer + * (e.g. "14:30", "2023-12-31T23:59:59Z", or a timestamp number) + */ + readonly targetRaw: string | number + + /** + * If the countdown should stop at zero, or continue into negative values + */ + readonly stopAtZero: boolean +} + +/** + * Timing state for a timer, optimized for efficient client rendering. + * When running, the client calculates current time from zeroTime. + * When paused, the duration is frozen and sent directly. + * pauseTime indicates when the timer should automatically pause (when current part ends and overrun begins). + * + * Client rendering logic: + * ```typescript + * if (state.paused === true) { + * // Manually paused by user or already pushing/overrun + * duration = state.duration + * } else if (state.pauseTime && now >= state.pauseTime) { + * // Auto-pause at overrun (current part ended) + * duration = state.zeroTime - state.pauseTime + * } else { + * // Running normally + * duration = state.zeroTime - now + * } + * ``` + */ +export type TimerState = + | { + /** Whether the timer is paused */ + paused: false + /** The absolute timestamp (ms) when the timer reaches/reached zero */ + zeroTime: number + /** Optional timestamp when the timer should pause (when current part ends) */ + pauseTime?: number | null + } + | { + /** Whether the timer is paused */ + paused: true + /** The frozen duration value in milliseconds */ + duration: number + /** Optional timestamp when the timer should pause (null when already paused/pushing) */ + pauseTime?: number | null + } + export interface ITTimersContext { /** * Get a T-timer by its index @@ -20,11 +90,19 @@ export interface IPlaylistTTimer { /** The label of the T-timer */ readonly label: string + /** + * The current mode of the T-timer + * Null if the T-timer is not initialized + * This defines how the timer behaves + */ + readonly mode: RundownTTimerMode | null + /** * The current state of the T-timer * Null if the T-timer is not initialized + * This contains the timing information needed to calculate the current time of the timer */ - readonly state: IPlaylistTTimerState | null + readonly state: TimerState | null /** Set the label of the T-timer */ setLabel(label: string): void @@ -72,6 +150,29 @@ export interface IPlaylistTTimer { */ restart(): boolean + /** + * Set the duration of a countdown timer + * This resets both the original duration (what restart() resets to) and the current countdown value. + * @param duration New duration in milliseconds + * @throws If timer is not in countdown mode or not initialized + */ + setDuration(duration: number): void + + /** + * Update the original duration (reset-to value) and/or current duration of a countdown timer + * This allows you to independently update: + * - `original`: The duration the timer resets to when restart() is called + * - `current`: The current countdown value (what's displayed now) + * + * If only `original` is provided, the current duration is recalculated to preserve elapsed time. + * If only `current` is provided, just the current countdown is updated. + * If both are provided, both values are updated independently. + * + * @param options Object with optional `original` and/or `current` duration in milliseconds + * @throws If timer is not in countdown mode or not initialized + */ + setDuration(options: { original?: number; current?: number }): void + /** * Clear any projection (manual or anchor-based) for this timer * This removes both manual projections set via setProjectedTime/setProjectedDuration @@ -116,49 +217,38 @@ export interface IPlaylistTTimer { * If false (default), we're progressing normally (projection counts down in real-time). */ setProjectedDuration(duration: number, paused?: boolean): void -} -export type IPlaylistTTimerState = - | IPlaylistTTimerStateCountdown - | IPlaylistTTimerStateFreeRun - | IPlaylistTTimerStateTimeOfDay - -export interface IPlaylistTTimerStateCountdown { - /** The mode of the T-timer */ - readonly mode: 'countdown' - /** The current time of the countdown, in milliseconds */ - readonly currentTime: number - /** The total duration of the countdown, in milliseconds */ - readonly duration: number - /** Whether the timer is currently paused */ - readonly paused: boolean - - /** If the countdown is set to stop at zero, or continue into negative values */ - readonly stopAtZero: boolean -} -export interface IPlaylistTTimerStateFreeRun { - /** The mode of the T-timer */ - readonly mode: 'freeRun' - /** The current time of the freerun, in milliseconds */ - readonly currentTime: number - /** Whether the timer is currently paused */ - readonly paused: boolean -} + /** + * Get the current duration of the timer in milliseconds + * For countdown timers, this returns how much time is remaining (can be negative if past zero) + * For timeOfDay timers, this returns time until/since the target time + * For freeRun timers, this returns how much time has elapsed + * @returns Current duration in milliseconds, or null if timer is not initialized + */ + getDuration(): number | null -export interface IPlaylistTTimerStateTimeOfDay { - /** The mode of the T-timer */ - readonly mode: 'timeOfDay' - /** The current remaining time of the timer, in milliseconds */ - readonly currentTime: number - /** The target timestamp of the timer, in milliseconds */ - readonly targetTime: number + /** + * Get the zero time (reference point) for the timer + * - For countdown/timeOfDay timers: the absolute timestamp when the timer reaches zero + * - For freeRun timers: the absolute timestamp when the timer started (what it counts from) + * For paused timers, calculates when zero would be if resumed now. + * @returns Unix timestamp in milliseconds, or null if timer is not initialized + */ + getZeroTime(): number | null /** - * The raw target string of the timer, as provided when setting the timer - * (e.g. "14:30", "2023-12-31T23:59:59Z", or a timestamp number) + * Get the projected duration in milliseconds + * This returns the projected timer value when we expect to reach the anchor part. + * Used to calculate over/under (how far ahead or behind schedule we are). + * @returns Projected duration in milliseconds, or null if no projection is set */ - readonly targetRaw: string | number + getProjectedDuration(): number | null - /** If the countdown is set to stop at zero, or continue into negative values */ - readonly stopAtZero: boolean + /** + * Get the projected zero time (reference point) + * This returns when we project the timer will reach zero based on scheduled durations. + * For paused projections (when pushing/delayed), calculates when zero would be if resumed now. + * @returns Unix timestamp in milliseconds, or null if no projection is set + */ + getProjectedZeroTime(): number | null } diff --git a/packages/blueprints-integration/src/documents/rundown.ts b/packages/blueprints-integration/src/documents/rundown.ts index 9daa30383a..f8c0840573 100644 --- a/packages/blueprints-integration/src/documents/rundown.ts +++ b/packages/blueprints-integration/src/documents/rundown.ts @@ -55,6 +55,9 @@ export interface IBlueprintRundownDBData { export interface IBlueprintSegmentRundown { externalId: string + /** Rundown timing information */ + timing: RundownPlaylistTiming + /** Arbitraty data storage for internal use in the blueprints */ privateData?: TPrivateData /** Arbitraty data relevant for other systems, made available to them through APIs */ diff --git a/packages/corelib/src/dataModel/RundownPlaylist.ts b/packages/corelib/src/dataModel/RundownPlaylist.ts index 06cf6d3ff5..6522b9bdba 100644 --- a/packages/corelib/src/dataModel/RundownPlaylist.ts +++ b/packages/corelib/src/dataModel/RundownPlaylist.ts @@ -1,4 +1,10 @@ -import { Time, TimelinePersistentState, RundownPlaylistTiming } from '@sofie-automation/blueprints-integration' +import { + Time, + TimelinePersistentState, + RundownPlaylistTiming, + RundownTTimerMode, + TimerState, +} from '@sofie-automation/blueprints-integration' import { PartId, PieceInstanceInfiniteId, @@ -94,76 +100,6 @@ export interface QuickLoopProps { forceAutoNext: ForceQuickLoopAutoNext } -export type RundownTTimerMode = RundownTTimerModeFreeRun | RundownTTimerModeCountdown | RundownTTimerModeTimeOfDay - -export interface RundownTTimerModeFreeRun { - readonly type: 'freeRun' -} -export interface RundownTTimerModeCountdown { - readonly type: 'countdown' - /** - * The original duration of the countdown in milliseconds, so that we know what value to reset to - */ - readonly duration: number - - /** - * If the countdown should stop at zero, or continue into negative values - */ - readonly stopAtZero: boolean -} -export interface RundownTTimerModeTimeOfDay { - readonly type: 'timeOfDay' - - /** - * The raw target string of the timer, as provided when setting the timer - * (e.g. "14:30", "2023-12-31T23:59:59Z", or a timestamp number) - */ - readonly targetRaw: string | number - - /** - * If the countdown should stop at zero, or continue into negative values - */ - readonly stopAtZero: boolean -} - -/** - * Timing state for a timer, optimized for efficient client rendering. - * When running, the client calculates current time from zeroTime. - * When paused, the duration is frozen and sent directly. - * pauseTime indicates when the timer should automatically pause (when current part ends and overrun begins). - * - * Client rendering logic: - * ```typescript - * if (state.paused === true) { - * // Manually paused by user or already pushing/overrun - * duration = state.duration - * } else if (state.pauseTime && now >= state.pauseTime) { - * // Auto-pause at overrun (current part ended) - * duration = state.zeroTime - state.pauseTime - * } else { - * // Running normally - * duration = state.zeroTime - now - * } - * ``` - */ -export type TimerState = - | { - /** Whether the timer is paused */ - paused: false - /** The absolute timestamp (ms) when the timer reaches/reached zero */ - zeroTime: number - /** Optional timestamp when the timer should pause (when current part ends) */ - pauseTime?: number | null - } - | { - /** Whether the timer is paused */ - paused: true - /** The frozen duration value in milliseconds */ - duration: number - /** Optional timestamp when the timer should pause (null when already paused/pushing) */ - pauseTime?: number | null - } - /** * Calculate the current duration for a timer state. * Handles paused, auto-pause (pauseTime), and running states. @@ -185,6 +121,29 @@ export function timerStateToDuration(state: TimerState, now: number): number { } } +/** + * Get the zero time (reference timestamp) for a timer state. + * - For countdown/timeOfDay timers: when the timer reaches zero + * - For freeRun timers: when the timer started (what it counts from) + * For paused timers, calculates when zero would be if resumed now. + * + * @param state The timer state + * @param now Current timestamp in milliseconds + * @returns The zero time timestamp in milliseconds + */ +export function timerStateToZeroTime(state: TimerState, now: number): number { + if (state.paused) { + // Calculate when zero would be if we resumed now + return now + state.duration + } else if (state.pauseTime && now >= state.pauseTime) { + // Auto-pause at overrun (current part ended) + return state.zeroTime - state.pauseTime + now + } else { + // Already have the zero time + return state.zeroTime + } +} + export type RundownTTimerIndex = 1 | 2 | 3 export interface RundownTTimer { diff --git a/packages/job-worker/src/blueprints/context/RundownActivationContext.ts b/packages/job-worker/src/blueprints/context/RundownActivationContext.ts index 5335d041bc..71cd3bab1e 100644 --- a/packages/job-worker/src/blueprints/context/RundownActivationContext.ts +++ b/packages/job-worker/src/blueprints/context/RundownActivationContext.ts @@ -4,6 +4,7 @@ import { IRundownActivationContext, IRundownActivationContextState, TSR, + Time, } from '@sofie-automation/blueprints-integration' import { PeripheralDeviceId } from '@sofie-automation/shared-lib/dist/core/model/Ids' import { ReadonlyDeep } from 'type-fest' @@ -58,6 +59,10 @@ export class RundownActivationContext extends RundownEventContext implements IRu return this._currentState } + get startedPlayback(): Time | undefined { + return this._playoutModel.playlist.startedPlayback + } + async listPlayoutDevices(): Promise { return listPlayoutDevices(this._context, this._playoutModel) } diff --git a/packages/job-worker/src/blueprints/context/SyncIngestUpdateToPartInstanceContext.ts b/packages/job-worker/src/blueprints/context/SyncIngestUpdateToPartInstanceContext.ts index 61e2dcb486..6e44cb8771 100644 --- a/packages/job-worker/src/blueprints/context/SyncIngestUpdateToPartInstanceContext.ts +++ b/packages/job-worker/src/blueprints/context/SyncIngestUpdateToPartInstanceContext.ts @@ -17,6 +17,7 @@ import { IBlueprintPartInstance, SomeContent, WithTimeline, + Time, } from '@sofie-automation/blueprints-integration' import { postProcessPieces, postProcessTimelineObjects } from '../postProcess.js' import { @@ -61,6 +62,10 @@ export class SyncIngestUpdateToPartInstanceContext return Array.from(this.#changedTTimers.values()) } + public get startedPlayback(): Time | undefined { + return this.#playoutModel.playlist.startedPlayback + } + constructor( context: JobContext, playoutModel: PlayoutModel, diff --git a/packages/job-worker/src/blueprints/context/lib.ts b/packages/job-worker/src/blueprints/context/lib.ts index f16ee424c0..513ca6bd12 100644 --- a/packages/job-worker/src/blueprints/context/lib.ts +++ b/packages/job-worker/src/blueprints/context/lib.ts @@ -443,6 +443,7 @@ export function convertRundownToBlueprintSegmentRundown( ): IBlueprintSegmentRundown { const obj: Complete = { externalId: rundown.externalId, + timing: rundown.timing, privateData: skipClone ? rundown.privateData : clone(rundown.privateData), publicData: skipClone ? rundown.publicData : clone(rundown.publicData), } diff --git a/packages/job-worker/src/blueprints/context/services/TTimersService.ts b/packages/job-worker/src/blueprints/context/services/TTimersService.ts index ab0a67452d..94933cf5cd 100644 --- a/packages/job-worker/src/blueprints/context/services/TTimersService.ts +++ b/packages/job-worker/src/blueprints/context/services/TTimersService.ts @@ -1,14 +1,12 @@ import type { IPlaylistTTimer, - IPlaylistTTimerState, -} from '@sofie-automation/blueprints-integration/dist/context/tTimersContext' -import type { - RundownTTimer, - RundownTTimerIndex, + RundownTTimerMode, TimerState, -} from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist' +} from '@sofie-automation/blueprints-integration/dist/context/tTimersContext' +import type { RundownTTimer, RundownTTimerIndex } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist' +import { timerStateToDuration, timerStateToZeroTime } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist' import type { PartId } from '@sofie-automation/corelib/dist/dataModel/Ids' -import { assertNever, literal } from '@sofie-automation/corelib/dist/lib' +import { literal } from '@sofie-automation/corelib/dist/lib' import { protectString, unprotectString } from '@sofie-automation/corelib/dist/protectedString' import type { PlayoutModel } from '../../../playout/model/PlayoutModel.js' import { ReadonlyDeep } from 'type-fest' @@ -76,41 +74,11 @@ export class PlaylistTTimerImpl implements IPlaylistTTimer { get label(): string { return this.#timer.label } - get state(): IPlaylistTTimerState | null { - const rawMode = this.#timer.mode - const rawState = this.#timer.state - - if (!rawMode || !rawState) return null - - const currentTime = rawState.paused ? rawState.duration : rawState.zeroTime - getCurrentTime() - - switch (rawMode.type) { - case 'countdown': - return { - mode: 'countdown', - currentTime, - duration: rawMode.duration, - paused: rawState.paused, - stopAtZero: rawMode.stopAtZero, - } - case 'freeRun': - return { - mode: 'freeRun', - currentTime, - paused: rawState.paused, - } - case 'timeOfDay': - return { - mode: 'timeOfDay', - currentTime, - targetTime: rawState.paused ? 0 : rawState.zeroTime, - targetRaw: rawMode.targetRaw, - stopAtZero: rawMode.stopAtZero, - } - default: - assertNever(rawMode) - return null - } + get mode(): RundownTTimerMode | null { + return this.#timer.mode + } + get state(): TimerState | null { + return this.#timer.state } constructor( @@ -195,6 +163,77 @@ export class PlaylistTTimerImpl implements IPlaylistTTimer { return true } + setDuration(durationOrOptions: number | { original?: number; current?: number }): void { + // Handle overloaded signatures + if (typeof durationOrOptions === 'number') { + // Simple case: reset timer to this duration + return this.setDuration({ original: durationOrOptions, current: durationOrOptions }) + } + + // Options case: independently update original and/or current + const options = durationOrOptions + + if (options.original !== undefined && options.original <= 0) { + throw new Error('Original duration must be greater than zero') + } + if (options.current !== undefined && options.current <= 0) { + throw new Error('Current duration must be greater than zero') + } + + if (!this.#timer.mode || this.#timer.mode.type !== 'countdown') { + throw new Error('Timer must be in countdown mode to update duration') + } + + if (!this.#timer.state) { + throw new Error('Timer is not initialized') + } + + if (!options.original && !options.current) { + throw new Error('At least one of original or current duration must be provided') + } + + const now = getCurrentTime() + const state = this.#timer.state + + // Calculate current elapsed time using built-in function (handles pauseTime correctly) + const remaining = timerStateToDuration(state, now) + const elapsed = this.#timer.mode.duration - remaining + + let newOriginalDuration: number + let newCurrentRemaining: number + + if (options.original !== undefined && options.current !== undefined) { + // Both specified: use both values independently + newOriginalDuration = options.original + newCurrentRemaining = options.current + } else if (options.original !== undefined) { + // Only original specified: preserve elapsed time + newOriginalDuration = options.original + newCurrentRemaining = Math.max(0, newOriginalDuration - elapsed) + } else if (options.current !== undefined) { + // Only current specified: keep original unchanged + newOriginalDuration = this.#timer.mode.duration + newCurrentRemaining = options.current + } else { + // This should be unreachable due to earlier check + throw new Error('Invalid duration update options') + } + + // Update both mode and state + this.#timer = { + ...this.#timer, + mode: { + ...this.#timer.mode, + duration: newOriginalDuration, + }, + state: state.paused + ? { paused: true, duration: newCurrentRemaining } + : { paused: false, zeroTime: now + newCurrentRemaining }, + } + + this.#emitChange(this.#timer) + } + clearProjected(): void { this.#timer = { ...this.#timer, @@ -248,4 +287,36 @@ export class PlaylistTTimerImpl implements IPlaylistTTimer { } this.#emitChange(this.#timer) } + + getDuration(): number | null { + if (!this.#timer.state) { + return null + } + + return timerStateToDuration(this.#timer.state, getCurrentTime()) + } + + getZeroTime(): number | null { + if (!this.#timer.state) { + return null + } + + return timerStateToZeroTime(this.#timer.state, getCurrentTime()) + } + + getProjectedDuration(): number | null { + if (!this.#timer.projectedState) { + return null + } + + return timerStateToDuration(this.#timer.projectedState, getCurrentTime()) + } + + getProjectedZeroTime(): number | null { + if (!this.#timer.projectedState) { + return null + } + + return timerStateToZeroTime(this.#timer.projectedState, getCurrentTime()) + } } diff --git a/packages/job-worker/src/blueprints/context/services/__tests__/TTimersService.test.ts b/packages/job-worker/src/blueprints/context/services/__tests__/TTimersService.test.ts index 72236e2d51..d7f5237eb7 100644 --- a/packages/job-worker/src/blueprints/context/services/__tests__/TTimersService.test.ts +++ b/packages/job-worker/src/blueprints/context/services/__tests__/TTimersService.test.ts @@ -193,127 +193,6 @@ describe('PlaylistTTimerImpl', () => { expect(timer.state).toBeNull() }) - - it('should return running freeRun state', () => { - const tTimers = createEmptyTTimers() - tTimers[0].mode = { type: 'freeRun' } - tTimers[0].state = { paused: false, zeroTime: 15000 } - const updateFn = jest.fn() - const mockPlayoutModel = createMockPlayoutModel(tTimers) - const mockJobContext = createMockJobContext() - const timer = new PlaylistTTimerImpl(tTimers[0], updateFn, mockPlayoutModel, mockJobContext) - - expect(timer.state).toEqual({ - mode: 'freeRun', - currentTime: 5000, // 10000 - 5000 - paused: false, // pauseTime is null = running - }) - }) - - it('should return paused freeRun state', () => { - const tTimers = createEmptyTTimers() - tTimers[0].mode = { type: 'freeRun' } - tTimers[0].state = { paused: true, duration: 3000 } - const updateFn = jest.fn() - const mockPlayoutModel = createMockPlayoutModel(tTimers) - const mockJobContext = createMockJobContext() - const timer = new PlaylistTTimerImpl(tTimers[0], updateFn, mockPlayoutModel, mockJobContext) - - expect(timer.state).toEqual({ - mode: 'freeRun', - currentTime: 3000, // 8000 - 5000 - paused: true, // pauseTime is set = paused - }) - }) - - it('should return running countdown state', () => { - const tTimers = createEmptyTTimers() - tTimers[0].mode = { - type: 'countdown', - duration: 60000, - stopAtZero: true, - } - tTimers[0].state = { paused: false, zeroTime: 15000 } - const updateFn = jest.fn() - const mockPlayoutModel = createMockPlayoutModel(tTimers) - const mockJobContext = createMockJobContext() - const timer = new PlaylistTTimerImpl(tTimers[0], updateFn, mockPlayoutModel, mockJobContext) - - expect(timer.state).toEqual({ - mode: 'countdown', - currentTime: 5000, // 10000 - 5000 - duration: 60000, - paused: false, // pauseTime is null = running - stopAtZero: true, - }) - }) - - it('should return paused countdown state', () => { - const tTimers = createEmptyTTimers() - tTimers[0].mode = { - type: 'countdown', - duration: 60000, - stopAtZero: false, - } - tTimers[0].state = { paused: true, duration: 2000 } - const updateFn = jest.fn() - const mockPlayoutModel = createMockPlayoutModel(tTimers) - const mockJobContext = createMockJobContext() - const timer = new PlaylistTTimerImpl(tTimers[0], updateFn, mockPlayoutModel, mockJobContext) - - expect(timer.state).toEqual({ - mode: 'countdown', - currentTime: 2000, // 7000 - 5000 - duration: 60000, - paused: true, // pauseTime is set = paused - stopAtZero: false, - }) - }) - - it('should return timeOfDay state', () => { - const tTimers = createEmptyTTimers() - tTimers[0].mode = { - type: 'timeOfDay', - targetRaw: '15:30', - stopAtZero: true, - } - tTimers[0].state = { paused: false, zeroTime: 20000 } // 10 seconds in the future - const updateFn = jest.fn() - const mockPlayoutModel = createMockPlayoutModel(tTimers) - const mockJobContext = createMockJobContext() - const timer = new PlaylistTTimerImpl(tTimers[0], updateFn, mockPlayoutModel, mockJobContext) - - expect(timer.state).toEqual({ - mode: 'timeOfDay', - currentTime: 10000, // targetTime - getCurrentTime() = 20000 - 10000 - targetTime: 20000, - targetRaw: '15:30', - stopAtZero: true, - }) - }) - - it('should return timeOfDay state with numeric targetRaw', () => { - const tTimers = createEmptyTTimers() - const targetTimestamp = 1737331200000 - tTimers[0].mode = { - type: 'timeOfDay', - targetRaw: targetTimestamp, - stopAtZero: false, - } - tTimers[0].state = { paused: false, zeroTime: targetTimestamp } - const updateFn = jest.fn() - const mockPlayoutModel = createMockPlayoutModel(tTimers) - const mockJobContext = createMockJobContext() - const timer = new PlaylistTTimerImpl(tTimers[0], updateFn, mockPlayoutModel, mockJobContext) - - expect(timer.state).toEqual({ - mode: 'timeOfDay', - currentTime: targetTimestamp - 10000, // targetTime - getCurrentTime() - targetTime: targetTimestamp, - targetRaw: targetTimestamp, - stopAtZero: false, - }) - }) }) describe('setLabel', () => { diff --git a/packages/job-worker/src/playout/tTimers.ts b/packages/job-worker/src/playout/tTimers.ts index dc6d9524a0..09e9dd0057 100644 --- a/packages/job-worker/src/playout/tTimers.ts +++ b/packages/job-worker/src/playout/tTimers.ts @@ -1,9 +1,5 @@ -import type { - RundownTTimerIndex, - RundownTTimerMode, - RundownTTimer, - TimerState, -} from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist' +import type { RundownTTimerIndex, RundownTTimer } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist' +import type { RundownTTimerMode, TimerState } from '@sofie-automation/blueprints-integration' import { literal } from '@sofie-automation/corelib/dist/lib' import { getCurrentTime } from '../lib/index.js' import type { ReadonlyDeep } from 'type-fest'