Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 3 additions & 3 deletions meteor/__mocks__/defaultCollectionObjects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,9 @@ export function defaultRundownPlaylist(_id: RundownPlaylistId, studioId: StudioI
},
rundownIdsInOrder: [],
tTimers: [
{ index: 1, label: '', mode: null },
{ index: 2, label: '', mode: null },
{ index: 3, label: '', mode: null },
{ index: 1, label: '', mode: null, state: null },
{ index: 2, label: '', mode: null, state: null },
{ index: 3, label: '', mode: null, state: null },
],
}
}
Expand Down
8 changes: 4 additions & 4 deletions meteor/server/migration/X_X_X.ts
Original file line number Diff line number Diff line change
Expand Up @@ -205,13 +205,13 @@ export const addSteps = addMigrationSteps(CURRENT_SYSTEM_VERSION, [
},
migrate: async () => {
await RundownPlaylists.mutableCollection.updateAsync(
{ tTimers: { $exists: false } },
{ tTimers: { $exists: false } } as any,
{
$set: {
tTimers: [
{ index: 1, label: '', mode: null },
{ index: 2, label: '', mode: null },
{ index: 3, label: '', mode: null },
{ index: 1, label: '', mode: null, state: null },
{ index: 2, label: '', mode: null, state: null },
{ index: 3, label: '', mode: null, state: null },
],
},
},
Expand Down
57 changes: 30 additions & 27 deletions packages/corelib/src/dataModel/RundownPlaylist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -98,33 +98,11 @@ export type RundownTTimerMode = RundownTTimerModeFreeRun | RundownTTimerModeCoun

export interface RundownTTimerModeFreeRun {
readonly type: 'freeRun'
/**
* Starting time (unix timestamp)
* This may not be the original start time, if the timer has been paused/resumed
*/
startTime: number
/**
* Set to a timestamp to pause the timer at that timestamp
* When unpausing, the `startTime` should be adjusted to account for the paused duration
*/
pauseTime: number | null
/** The direction to count */
// direction: 'up' | 'down' // TODO: does this make sense?
}
export interface RundownTTimerModeCountdown {
readonly type: 'countdown'
/**
* Starting time (unix timestamp)
* This may not be the original start time, if the timer has been paused/resumed
*/
startTime: number
/**
* Set to a timestamp to pause the timer at that timestamp
* When unpausing, the `targetTime` should be adjusted to account for the paused duration
*/
pauseTime: number | null
/**
* The duration of the countdown in milliseconds
* The original duration of the countdown in milliseconds, so that we know what value to reset to
*/
readonly duration: number

Expand All @@ -136,9 +114,6 @@ export interface RundownTTimerModeCountdown {
export interface RundownTTimerModeTimeOfDay {
readonly type: 'timeOfDay'

/** The target timestamp of the timer, in milliseconds */
targetTime: number

/**
* 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)
Expand All @@ -151,6 +126,25 @@ export interface RundownTTimerModeTimeOfDay {
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.
*/
export type TimerState =
| {
/** Whether the timer is paused */
paused: false
/** The absolute timestamp (ms) when the timer reaches/reached zero */
zeroTime: number
}
| {
/** Whether the timer is paused */
paused: true
/** The frozen duration value in milliseconds */
duration: number
}

export type RundownTTimerIndex = 1 | 2 | 3

export interface RundownTTimer {
Expand All @@ -159,9 +153,18 @@ export interface RundownTTimer {
/** A label for the timer */
label: string

/** The current mode of the timer, or null if not configured */
/** The current mode of the timer, or null if not configured
*
* This defines how the timer behaves
*/
mode: RundownTTimerMode | null

/** The current state of the timer, or null if not configured
*
* This contains the information needed to calculate the current time of the timer
*/
state: TimerState | null

/*
* Future ideas:
* allowUiControl: boolean
Expand Down
6 changes: 3 additions & 3 deletions packages/job-worker/src/__mocks__/defaultCollectionObjects.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,9 +46,9 @@ export function defaultRundownPlaylist(_id: RundownPlaylistId, studioId: StudioI
rundownIdsInOrder: [],

tTimers: [
{ index: 1, label: '', mode: null },
{ index: 2, label: '', mode: null },
{ index: 3, label: '', mode: null },
{ index: 1, label: '', mode: null, state: null },
{ index: 2, label: '', mode: null, state: null },
{ index: 3, label: '', mode: null, state: null },
],
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@ import { assertNever } from '@sofie-automation/corelib/dist/lib'
import type { PlayoutModel } from '../../../playout/model/PlayoutModel.js'
import { ReadonlyDeep } from 'type-fest'
import {
calculateTTimerCurrentTime,
createCountdownTTimer,
createFreeRunTTimer,
createTimeOfDayTTimer,
Expand Down Expand Up @@ -60,31 +59,35 @@ export class PlaylistTTimerImpl implements IPlaylistTTimer {
}
get state(): IPlaylistTTimerState | null {
const rawMode = this.#modelTimer.mode
switch (rawMode?.type) {
const rawState = this.#modelTimer.state

if (!rawMode || !rawState) return null

const currentTime = rawState.paused ? rawState.duration : rawState.zeroTime - getCurrentTime()

switch (rawMode.type) {
case 'countdown':
return {
mode: 'countdown',
currentTime: calculateTTimerCurrentTime(rawMode.startTime, rawMode.pauseTime),
currentTime,
duration: rawMode.duration,
paused: !!rawMode.pauseTime,
paused: rawState.paused,
stopAtZero: rawMode.stopAtZero,
}
case 'freeRun':
return {
mode: 'freeRun',
currentTime: calculateTTimerCurrentTime(rawMode.startTime, rawMode.pauseTime),
paused: !!rawMode.pauseTime,
currentTime,
paused: rawState.paused,
}
case 'timeOfDay':
return {
mode: 'timeOfDay',
currentTime: rawMode.targetTime - getCurrentTime(),
targetTime: rawMode.targetTime,
currentTime,
targetTime: rawState.paused ? 0 : rawState.zeroTime,
targetRaw: rawMode.targetRaw,
stopAtZero: rawMode.stopAtZero,
}
case undefined:
return null
default:
assertNever(rawMode)
return null
Expand All @@ -108,12 +111,13 @@ export class PlaylistTTimerImpl implements IPlaylistTTimer {
this.#playoutModel.updateTTimer({
...this.#modelTimer,
mode: null,
state: null,
})
}
startCountdown(duration: number, options?: { stopAtZero?: boolean; startPaused?: boolean }): void {
this.#playoutModel.updateTTimer({
...this.#modelTimer,
mode: createCountdownTTimer(duration, {
...createCountdownTTimer(duration, {
stopAtZero: options?.stopAtZero ?? true,
startPaused: options?.startPaused ?? false,
}),
Expand All @@ -122,47 +126,38 @@ export class PlaylistTTimerImpl implements IPlaylistTTimer {
startTimeOfDay(targetTime: string | number, options?: { stopAtZero?: boolean }): void {
this.#playoutModel.updateTTimer({
...this.#modelTimer,
mode: createTimeOfDayTTimer(targetTime, {
...createTimeOfDayTTimer(targetTime, {
stopAtZero: options?.stopAtZero ?? true,
}),
})
}
startFreeRun(options?: { startPaused?: boolean }): void {
this.#playoutModel.updateTTimer({
...this.#modelTimer,
mode: createFreeRunTTimer({
...createFreeRunTTimer({
startPaused: options?.startPaused ?? false,
}),
})
}
pause(): boolean {
const newTimer = pauseTTimer(this.#modelTimer.mode)
const newTimer = pauseTTimer(this.#modelTimer)
if (!newTimer) return false

this.#playoutModel.updateTTimer({
...this.#modelTimer,
mode: newTimer,
})
this.#playoutModel.updateTTimer(newTimer)
return true
}
resume(): boolean {
const newTimer = resumeTTimer(this.#modelTimer.mode)
const newTimer = resumeTTimer(this.#modelTimer)
if (!newTimer) return false

this.#playoutModel.updateTTimer({
...this.#modelTimer,
mode: newTimer,
})
this.#playoutModel.updateTTimer(newTimer)
return true
}
restart(): boolean {
const newTimer = restartTTimer(this.#modelTimer.mode)
const newTimer = restartTTimer(this.#modelTimer)
if (!newTimer) return false

this.#playoutModel.updateTTimer({
...this.#modelTimer,
mode: newTimer,
})
this.#playoutModel.updateTTimer(newTimer)
return true
}
}
Loading
Loading