1- import { BlueprintId , TimelineHash } from '@sofie-automation/corelib/dist/dataModel/Ids'
1+ import { BlueprintId , RundownPlaylistId , TimelineHash } from '@sofie-automation/corelib/dist/dataModel/Ids'
22import { JobContext , JobStudio } from '../../jobs'
33import { ReadonlyDeep } from 'type-fest'
44import {
@@ -16,10 +16,11 @@ import {
1616 TimelineObjGeneric ,
1717 TimelineObjRundown ,
1818 TimelineObjType ,
19+ TimelineObjRegenerateTrigger ,
1920} from '@sofie-automation/corelib/dist/dataModel/Timeline'
2021import { RundownBaselineObj } from '@sofie-automation/corelib/dist/dataModel/RundownBaselineObj'
2122import { DBPartInstance } from '@sofie-automation/corelib/dist/dataModel/PartInstance'
22- import { applyToArray , clone , literal , normalizeArray , omit } from '@sofie-automation/corelib/dist/lib'
23+ import { applyToArray , clone , getHash , literal , normalizeArray , omit } from '@sofie-automation/corelib/dist/lib'
2324import { PlayoutModel } from '../model/PlayoutModel'
2425import { logger } from '../../logging'
2526import { getCurrentTime , getSystemVersion } from '../../lib'
@@ -46,6 +47,7 @@ import { getPartTimingsOrDefaults, PartCalculatedTimings } from '@sofie-automati
4647import { applyAbPlaybackForTimeline } from '../abPlayback'
4748import { stringifyError } from '@sofie-automation/shared-lib/dist/lib/stringifyError'
4849import { PlayoutPartInstanceModel } from '../model/PlayoutPartInstanceModel'
50+ import { PlayoutChangedType } from '@sofie-automation/shared-lib/dist/peripheralDevice/peripheralDeviceAPI'
4951
5052function isModelForStudio ( model : StudioPlayoutModelBase ) : model is StudioPlayoutModel {
5153 const tmp = model as StudioPlayoutModel
@@ -126,7 +128,7 @@ export async function updateStudioTimeline(
126128 logAnyRemainingNowTimes ( context , baselineObjects )
127129 }
128130
129- const timelineHash = saveTimeline ( context , playoutModel , baselineObjects , versions )
131+ const timelineHash = saveTimeline ( context , playoutModel , baselineObjects , versions , undefined )
130132
131133 if ( studioBaseline ) {
132134 updateBaselineExpectedPackagesOnStudio ( context , playoutModel , studioBaseline )
@@ -144,7 +146,12 @@ export async function updateTimeline(context: JobContext, playoutModel: PlayoutM
144146 throw new Error ( `RundownPlaylist ("${ playoutModel . playlist . _id } ") is not active")` )
145147 }
146148
147- const { versions, objs : timelineObjs , timingContext : timingInfo } = await getTimelineRundown ( context , playoutModel )
149+ const {
150+ versions,
151+ objs : timelineObjs ,
152+ timingContext : timingInfo ,
153+ regenerateTimelineToken,
154+ } = await getTimelineRundown ( context , playoutModel )
148155
149156 flattenAndProcessTimelineObjects ( context , timelineObjs )
150157
@@ -156,7 +163,7 @@ export async function updateTimeline(context: JobContext, playoutModel: PlayoutM
156163 logAnyRemainingNowTimes ( context , timelineObjs )
157164 }
158165
159- const timelineHash = saveTimeline ( context , playoutModel , timelineObjs , versions )
166+ const timelineHash = saveTimeline ( context , playoutModel , timelineObjs , versions , regenerateTimelineToken )
160167 logger . verbose ( `updateTimeline done, hash: "${ timelineHash } "` )
161168
162169 if ( span ) span . end ( )
@@ -227,9 +234,10 @@ export function saveTimeline(
227234 context : JobContext ,
228235 studioPlayoutModel : StudioPlayoutModelBase ,
229236 timelineObjs : TimelineObjGeneric [ ] ,
230- generationVersions : TimelineCompleteGenerationVersions
237+ generationVersions : TimelineCompleteGenerationVersions ,
238+ regenerateTimelineToken : string | undefined
231239) : TimelineHash {
232- const newTimeline = studioPlayoutModel . setTimeline ( timelineObjs , generationVersions )
240+ const newTimeline = studioPlayoutModel . setTimeline ( timelineObjs , generationVersions , regenerateTimelineToken )
233241
234242 // Also do a fast-track for the timeline to be published faster:
235243 context . hackPublishTimelineToFastTrack ( newTimeline )
@@ -248,6 +256,7 @@ export interface SelectedPartInstanceTimelineInfo {
248256 partInstance : ReadonlyDeep < DBPartInstance >
249257 pieceInstances : PieceInstanceWithTimings [ ]
250258 calculatedTimings : PartCalculatedTimings
259+ regenerateTimelineAt : number | undefined
251260}
252261
253262function getPartInstanceTimelineInfo (
@@ -273,6 +282,7 @@ function getPartInstanceTimelineInfo(
273282 partStarted,
274283 // Approximate `calculatedTimings`, for the partInstances which already have it cached
275284 calculatedTimings : getPartTimingsOrDefaults ( partInstanceWithOverrides , pieceInstances ) ,
285+ regenerateTimelineAt : undefined , // Future use
276286 }
277287}
278288
@@ -286,6 +296,7 @@ async function getTimelineRundown(
286296 objs : Array < TimelineObjRundown >
287297 versions : TimelineCompleteGenerationVersions
288298 timingContext : RundownTimelineTimingContext | undefined
299+ regenerateTimelineToken : string | undefined
289300} > {
290301 const span = context . startSpan ( 'getTimelineRundown' )
291302 try {
@@ -341,6 +352,9 @@ async function getTimelineRundown(
341352 timelineObjs = timelineObjs . concat ( rundownTimelineResult . timeline )
342353 timelineObjs = timelineObjs . concat ( await pLookaheadObjs )
343354
355+ const regenerateTimelineObj = createRegenerateTimelineObj ( playoutModel . playlistId , partInstancesInfo )
356+ if ( regenerateTimelineObj ) timelineObjs . push ( regenerateTimelineObj . obj )
357+
344358 const blueprint = await context . getShowStyleBlueprint ( showStyle . _id )
345359 timelineVersions = generateTimelineVersions (
346360 context . studio ,
@@ -438,6 +452,7 @@ async function getTimelineRundown(
438452 } ) ,
439453 versions : timelineVersions ?? generateTimelineVersions ( context . studio , undefined , '-' ) ,
440454 timingContext : rundownTimelineResult . timingContext ,
455+ regenerateTimelineToken : regenerateTimelineObj ?. token ,
441456 }
442457 } else {
443458 if ( span ) span . end ( )
@@ -446,6 +461,7 @@ async function getTimelineRundown(
446461 objs : [ ] ,
447462 versions : generateTimelineVersions ( context . studio , undefined , '-' ) ,
448463 timingContext : undefined ,
464+ regenerateTimelineToken : undefined ,
449465 }
450466 }
451467 } catch ( e ) {
@@ -455,10 +471,49 @@ async function getTimelineRundown(
455471 objs : [ ] ,
456472 versions : generateTimelineVersions ( context . studio , undefined , '-' ) ,
457473 timingContext : undefined ,
474+ regenerateTimelineToken : undefined ,
458475 }
459476 }
460477}
461478
479+ function createRegenerateTimelineObj (
480+ playlistId : RundownPlaylistId ,
481+ partInstancesInfo : SelectedPartInstancesTimelineInfo
482+ ) {
483+ const regenerateTimelineAt = Math . min (
484+ partInstancesInfo . current ?. regenerateTimelineAt ?? Number . POSITIVE_INFINITY ,
485+ partInstancesInfo . next ?. regenerateTimelineAt ?? Number . POSITIVE_INFINITY
486+ )
487+ if ( regenerateTimelineAt < Number . POSITIVE_INFINITY ) {
488+ // The timeline has requested a regeneration at a specific time
489+ const token = getHash ( `regenerate-${ playlistId } -${ getCurrentTime ( ) } ` )
490+ const obj = literal < TimelineObjRegenerateTrigger & OnGenerateTimelineObjExt > ( {
491+ id : `regenerate_${ token } ` ,
492+ enable : {
493+ start : regenerateTimelineAt ,
494+ } ,
495+ layer : '__timeline_regeneration_trigger__' , // Some unique name, as callbacks need to be on a layer
496+ priority : 1 ,
497+ content : {
498+ deviceType : TSR . DeviceType . ABSTRACT ,
499+ type : 'callback' ,
500+ callBack : PlayoutChangedType . TRIGGER_REGENERATION ,
501+ callBackData : {
502+ rundownPlaylistId : playlistId ,
503+ regenerationToken : token ,
504+ } ,
505+ } ,
506+ objectType : TimelineObjType . RUNDOWN ,
507+ metaData : undefined ,
508+ partInstanceId : null ,
509+ } )
510+
511+ return { token, obj }
512+ } else {
513+ return null
514+ }
515+ }
516+
462517/**
463518 * Process the timeline objects, to provide some basic validation. Also flattens the nested objects into a single array
464519 * Note: Input array is mutated in place
0 commit comments