Skip to content

Commit 8dcd562

Browse files
committed
feat(blueprints): Add blueprint interface methods for T-Timer estimate management
Add three new methods to IPlaylistTTimer interface: - clearEstimate() - Clear both manual estimates and anchor parts - setEstimateAnchorPart(partId) - Set anchor part for automatic calculation - setEstimateTime(time, paused?) - Manually set estimate as timestamp - setEstimateDuration(duration, paused?) - Manually set estimate as duration When anchor part is set, automatically queues RecalculateTTimerEstimates job. Manual estimates clear anchor parts and vice versa. Updated TTimersService to accept JobContext for job queueing capability. Updated all blueprint context instantiations and tests.
1 parent ee0a2a8 commit 8dcd562

File tree

7 files changed

+151
-57
lines changed

7 files changed

+151
-57
lines changed

packages/blueprints-integration/src/context/tTimersContext.ts

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,42 @@ export interface IPlaylistTTimer {
7171
* @returns True if the timer was restarted, false if it could not be restarted
7272
*/
7373
restart(): boolean
74+
75+
/**
76+
* Clear any estimate (manual or anchor-based) for this timer
77+
* This removes both manual estimates set via setEstimateTime/setEstimateDuration
78+
* and automatic estimates based on anchor parts set via setEstimateAnchorPart.
79+
*/
80+
clearEstimate(): void
81+
82+
/**
83+
* Set the anchor part for automatic estimate calculation
84+
* When set, the server automatically calculates when we expect to reach this part
85+
* based on remaining part durations, and updates the estimate accordingly.
86+
* Clears any manual estimate set via setEstimateTime/setEstimateDuration.
87+
* @param partId The ID of the part to use as timing anchor
88+
*/
89+
setEstimateAnchorPart(partId: string): void
90+
91+
/**
92+
* Manually set the estimate as an absolute timestamp
93+
* Use this when you have custom logic for calculating when you expect to reach a timing point.
94+
* Clears any anchor part set via setAnchorPart.
95+
* @param time Unix timestamp (milliseconds) when we expect to reach the timing point
96+
* @param paused If true, we're currently delayed/pushing (estimate won't update with time passing).
97+
* If false (default), we're progressing normally (estimate counts down in real-time).
98+
*/
99+
setEstimateTime(time: number, paused?: boolean): void
100+
101+
/**
102+
* Manually set the estimate as a relative duration from now
103+
* Use this when you want to express the estimate as "X milliseconds from now".
104+
* Clears any anchor part set via setAnchorPart.
105+
* @param duration Milliseconds until we expect to reach the timing point
106+
* @param paused If true, we're currently delayed/pushing (estimate won't update with time passing).
107+
* If false (default), we're progressing normally (estimate counts down in real-time).
108+
*/
109+
setEstimateDuration(duration: number, paused?: boolean): void
74110
}
75111

76112
export type IPlaylistTTimerState =

packages/job-worker/src/blueprints/context/OnSetAsNextContext.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ export class OnSetAsNextContext
5050
public readonly manuallySelected: boolean
5151
) {
5252
super(contextInfo, context, showStyle, watchedPackages)
53-
this.#tTimersService = new TTimersService(playoutModel)
53+
this.#tTimersService = new TTimersService(playoutModel, context)
5454
}
5555

5656
public get quickLoopInfo(): BlueprintQuickLookInfo | null {

packages/job-worker/src/blueprints/context/OnTakeContext.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export class OnTakeContext extends ShowStyleUserContext implements IOnTakeContex
6666
) {
6767
super(contextInfo, _context, showStyle, watchedPackages)
6868
this.isTakeAborted = false
69-
this.#tTimersService = new TTimersService(_playoutModel)
69+
this.#tTimersService = new TTimersService(_playoutModel, _context)
7070
}
7171

7272
async getUpcomingParts(limit: number = 5): Promise<ReadonlyDeep<IBlueprintPart[]>> {

packages/job-worker/src/blueprints/context/RundownActivationContext.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export class RundownActivationContext extends RundownEventContext implements IRu
4848
this._previousState = options.previousState
4949
this._currentState = options.currentState
5050

51-
this.#tTimersService = new TTimersService(this._playoutModel)
51+
this.#tTimersService = new TTimersService(this._playoutModel, this._context)
5252
}
5353

5454
get previousState(): IRundownActivationContextState {

packages/job-worker/src/blueprints/context/adlibActions.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -117,7 +117,7 @@ export class ActionExecutionContext extends ShowStyleUserContext implements IAct
117117
private readonly partAndPieceInstanceService: PartAndPieceInstanceActionService
118118
) {
119119
super(contextInfo, _context, showStyle, watchedPackages)
120-
this.#tTimersService = new TTimersService(_playoutModel)
120+
this.#tTimersService = new TTimersService(_playoutModel, _context)
121121
}
122122

123123
async getUpcomingParts(limit: number = 5): Promise<ReadonlyDeep<IBlueprintPart[]>> {

packages/job-worker/src/blueprints/context/services/TTimersService.ts

Lines changed: 58 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,10 @@ import type {
33
IPlaylistTTimerState,
44
} from '@sofie-automation/blueprints-integration/dist/context/tTimersContext'
55
import type { RundownTTimer, RundownTTimerIndex } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist'
6-
import { assertNever } from '@sofie-automation/corelib/dist/lib'
6+
import type { TimerState } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist'
7+
import type { PartId } from '@sofie-automation/corelib/dist/dataModel/Ids'
8+
import { assertNever, literal } from '@sofie-automation/corelib/dist/lib'
9+
import { protectString } from '@sofie-automation/corelib/dist/protectedString'
710
import type { PlayoutModel } from '../../../playout/model/PlayoutModel.js'
811
import { ReadonlyDeep } from 'type-fest'
912
import {
@@ -14,21 +17,25 @@ import {
1417
restartTTimer,
1518
resumeTTimer,
1619
validateTTimerIndex,
20+
recalculateTTimerEstimates,
1721
} from '../../../playout/tTimers.js'
1822
import { getCurrentTime } from '../../../lib/time.js'
23+
import type { JobContext } from '../../../jobs/index.js'
1924

2025
export class TTimersService {
2126
readonly playoutModel: PlayoutModel
27+
readonly jobContext: JobContext
2228

2329
readonly timers: [PlaylistTTimerImpl, PlaylistTTimerImpl, PlaylistTTimerImpl]
2430

25-
constructor(playoutModel: PlayoutModel) {
31+
constructor(playoutModel: PlayoutModel, jobContext: JobContext) {
2632
this.playoutModel = playoutModel
33+
this.jobContext = jobContext
2734

2835
this.timers = [
29-
new PlaylistTTimerImpl(playoutModel, 1),
30-
new PlaylistTTimerImpl(playoutModel, 2),
31-
new PlaylistTTimerImpl(playoutModel, 3),
36+
new PlaylistTTimerImpl(playoutModel, jobContext, 1),
37+
new PlaylistTTimerImpl(playoutModel, jobContext, 2),
38+
new PlaylistTTimerImpl(playoutModel, jobContext, 3),
3239
]
3340
}
3441

@@ -45,6 +52,7 @@ export class TTimersService {
4552

4653
export class PlaylistTTimerImpl implements IPlaylistTTimer {
4754
readonly #playoutModel: PlayoutModel
55+
readonly #jobContext: JobContext
4856
readonly #index: RundownTTimerIndex
4957

5058
get #modelTimer(): ReadonlyDeep<RundownTTimer> {
@@ -94,8 +102,9 @@ export class PlaylistTTimerImpl implements IPlaylistTTimer {
94102
}
95103
}
96104

97-
constructor(playoutModel: PlayoutModel, index: RundownTTimerIndex) {
105+
constructor(playoutModel: PlayoutModel, jobContext: JobContext, index: RundownTTimerIndex) {
98106
this.#playoutModel = playoutModel
107+
this.#jobContext = jobContext
99108
this.#index = index
100109

101110
validateTTimerIndex(index)
@@ -160,4 +169,47 @@ export class PlaylistTTimerImpl implements IPlaylistTTimer {
160169
this.#playoutModel.updateTTimer(newTimer)
161170
return true
162171
}
172+
173+
clearEstimate(): void {
174+
this.#playoutModel.updateTTimer({
175+
...this.#modelTimer,
176+
anchorPartId: undefined,
177+
estimateState: undefined,
178+
})
179+
}
180+
181+
setEstimateAnchorPart(partId: string): void {
182+
this.#playoutModel.updateTTimer({
183+
...this.#modelTimer,
184+
anchorPartId: protectString<PartId>(partId),
185+
estimateState: undefined, // Clear manual estimate
186+
})
187+
188+
// Recalculate estimates immediately since we already have the playout model
189+
recalculateTTimerEstimates(this.#jobContext, this.#playoutModel)
190+
}
191+
192+
setEstimateTime(time: number, paused: boolean = false): void {
193+
const estimateState: TimerState = paused
194+
? literal<TimerState>({ paused: true, duration: time - getCurrentTime() })
195+
: literal<TimerState>({ paused: false, zeroTime: time })
196+
197+
this.#playoutModel.updateTTimer({
198+
...this.#modelTimer,
199+
anchorPartId: undefined, // Clear automatic anchor
200+
estimateState,
201+
})
202+
}
203+
204+
setEstimateDuration(duration: number, paused: boolean = false): void {
205+
const estimateState: TimerState = paused
206+
? literal<TimerState>({ paused: true, duration })
207+
: literal<TimerState>({ paused: false, zeroTime: getCurrentTime() + duration })
208+
209+
this.#playoutModel.updateTTimer({
210+
...this.#modelTimer,
211+
anchorPartId: undefined, // Clear automatic anchor
212+
estimateState,
213+
})
214+
}
163215
}

0 commit comments

Comments
 (0)