@@ -5,6 +5,7 @@ import { TimelineObjRundown } from '@sofie-automation/corelib/dist/dataModel/Tim
55import { normalizeArray } from '@sofie-automation/corelib/dist/lib'
66import { PieceTimelineMetadata } from './pieceGroup'
77import { StudioPlayoutModelBase } from '../../studio/model/StudioPlayoutModel'
8+ import { logger } from '../../logging'
89import { JobContext } from '../../jobs'
910import { getCurrentTime } from '../../lib'
1011import { PlayoutModel } from '../model/PlayoutModel'
@@ -46,7 +47,7 @@ export function deNowifyMultiGatewayTimeline(
4647 playoutModel . nextPartInstance
4748 )
4849
49- deNowifyCurrentPieces (
50+ const { objectsNotDeNowified } = deNowifyCurrentPieces (
5051 targetNowTime ,
5152 timingContext ,
5253 currentPartInstance ,
@@ -55,6 +56,9 @@ export function deNowifyMultiGatewayTimeline(
5556 )
5657
5758 updatePlannedTimingsForPieceInstances ( playoutModel , currentPartInstance , partGroupTimings , timelineObjsMap )
59+
60+ // Because updatePlannedTimingsForPieceInstances changes start times of infinites, we can now run deNowifyInfinites()
61+ deNowifyInfinites ( targetNowTime , objectsNotDeNowified , timelineObjsMap )
5862}
5963
6064/**
@@ -142,16 +146,83 @@ function updatePartInstancePlannedTimes(
142146 }
143147}
144148
149+ /**
150+ * Replace the `now` time in any timeline objects in freshly-placed infinites.
151+ *
152+ * This is effectively only needed when a new item has been placed on the timeline just now and changes made by
153+ * `updatePlannedTimingsForPieceInstances` haven't been taken into account when generating the timeline. On the next
154+ * regeneration, items will already use the timestamps persited by `updatePlannedTimingsForPieceInstances` and will not
155+ * be included in `infiniteObjs`.
156+ */
157+ function deNowifyInfinites (
158+ targetNowTime : number ,
159+ /** A list of objects that need to be updated */
160+ infiniteObjs : TimelineObjRundown [ ] ,
161+ timelineObjsMap : Record < string , TimelineObjRundown >
162+ ) {
163+ /**
164+ * Recursively look up the absolute starttime of a timeline object
165+ * taking into account its parent's times.
166+ * Note: This only supports timeline objects that have absolute enable.start times.
167+ */
168+ const getStartTime = ( obj : TimelineObjRundown ) : number | undefined => {
169+ if ( Array . isArray ( obj . enable ) ) return undefined
170+
171+ const myStartTime = typeof obj . enable . start === 'number' ? obj . enable . start : undefined
172+
173+ if ( ! obj . inGroup ) return myStartTime
174+
175+ if ( myStartTime === undefined ) return undefined
176+
177+ const parentObject = timelineObjsMap [ obj . inGroup ]
178+ if ( ! parentObject ) return undefined
179+
180+ const parentStartTime = getStartTime ( parentObject )
181+ if ( parentStartTime === undefined ) return undefined
182+
183+ return parentStartTime + myStartTime
184+ }
185+
186+ for ( const obj of infiniteObjs ) {
187+ if ( Array . isArray ( obj . enable ) || obj . enable . start !== 'now' ) continue
188+
189+ if ( ! obj . inGroup ) {
190+ obj . enable = { start : targetNowTime }
191+ continue
192+ }
193+
194+ const parentObject = timelineObjsMap [ obj . inGroup ]
195+ if ( ! parentObject ) {
196+ logger . error ( `deNowifyInfinites: Parent obj "${ obj . inGroup } " not found of object "${ obj . id } "` )
197+ continue
198+ }
199+
200+ const parentStartTime = getStartTime ( parentObject )
201+ if ( parentStartTime === undefined ) {
202+ logger . error (
203+ `deNowifyInfinites: Unable to derive an absolute start time of parent "${ obj . inGroup } " for object "${ obj . id } "`
204+ )
205+ continue
206+ }
207+
208+ obj . enable = { start : targetNowTime - parentStartTime }
209+ logger . silly (
210+ `deNowifyInfinites: Setting "${ obj . id } " enable.start = ${ obj . enable . start } , ${ targetNowTime } ${ parentStartTime } parentObject: "${ parentObject . id } "`
211+ )
212+ }
213+ }
145214/**
146215 * Replace the `now` time in any Pieces on the timeline from the current Part with concrete start times
216+ * @returns a list of object that couldn't be updated at this time.
147217 */
148218function deNowifyCurrentPieces (
149219 targetNowTime : number ,
150220 timingContext : RundownTimelineTimingContext ,
151221 currentPartInstance : PlayoutPartInstanceModel ,
152222 currentPartGroupStartTime : number ,
153223 timelineObjsMap : Record < string , TimelineObjRundown >
154- ) {
224+ ) : { objectsNotDeNowified : TimelineObjRundown [ ] } {
225+ const objectsNotDeNowified : TimelineObjRundown [ ] = [ ]
155226 // The relative time for 'now' to be resolved to, inside of the part group
156227 const nowInPart = targetNowTime - currentPartGroupStartTime
157228
@@ -176,6 +247,8 @@ function deNowifyCurrentPieces(
176247 obj . enable = { start : nowInPart }
177248 } else if ( ! obj . inGroup ) {
178249 obj . enable = { start : targetNowTime }
250+ } else {
251+ objectsNotDeNowified . push ( obj )
179252 }
180253 }
181254 }
@@ -203,6 +276,7 @@ function deNowifyCurrentPieces(
203276 }
204277 }
205278 }
279+ return { objectsNotDeNowified }
206280}
207281
208282function updatePlannedTimingsForPieceInstances (
0 commit comments