Skip to content

Commit 3b36681

Browse files
authored
Merge pull request #1546 from nrkno/fix/resolve-timeline-wrong-time-redux
2 parents de8774a + e5a57e2 commit 3b36681

File tree

14 files changed

+529
-200
lines changed

14 files changed

+529
-200
lines changed

packages/corelib/src/dataModel/PieceInstance.ts

Lines changed: 4 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -61,15 +61,10 @@ export interface PieceInstance {
6161
dynamicallyInserted?: Time
6262

6363
/** This is set when the duration needs to be overriden from some user action */
64-
userDuration?:
65-
| {
66-
/** The time relative to the part (milliseconds since start of part) */
67-
endRelativeToPart: number
68-
}
69-
| {
70-
/** The time relative to 'now' (ms since 'now') */
71-
endRelativeToNow: number
72-
}
64+
userDuration?: {
65+
/** The time relative to the part (milliseconds since start of part) */
66+
endRelativeToPart: number
67+
}
7368

7469
/** The time the system started playback of this part, undefined if not yet played back (milliseconds since epoch) */
7570
reportedStartedPlayback?: Time

packages/corelib/src/playout/__tests__/processAndPrune.test.ts

Lines changed: 0 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -600,34 +600,6 @@ describe('resolvePrunedPieceInstances', () => {
600600
} satisfies ResolvedPieceInstance)
601601
})
602602

603-
test('numeric start, with userDuration.endRelativeToNow', async () => {
604-
const nowInPart = 123
605-
const piece = createPieceInstance({ start: 500 }, undefined, {
606-
endRelativeToNow: 4000,
607-
})
608-
609-
expect(resolvePrunedPieceInstance(nowInPart, clone(piece))).toStrictEqual({
610-
instance: clone(piece),
611-
timelinePriority: piece.priority,
612-
resolvedStart: 500,
613-
resolvedDuration: 4000 - 500 + nowInPart,
614-
} satisfies ResolvedPieceInstance)
615-
})
616-
617-
test('now start, with userDuration.endRelativeToNow', async () => {
618-
const nowInPart = 123
619-
const piece = createPieceInstance({ start: 'now' }, undefined, {
620-
endRelativeToNow: 4000,
621-
})
622-
623-
expect(resolvePrunedPieceInstance(nowInPart, clone(piece))).toStrictEqual({
624-
instance: clone(piece),
625-
timelinePriority: piece.priority,
626-
resolvedStart: nowInPart,
627-
resolvedDuration: 4000,
628-
} satisfies ResolvedPieceInstance)
629-
})
630-
631603
test('now start, with end cap, planned duration and userDuration.endRelativeToPart', async () => {
632604
const nowInPart = 123
633605
const piece = createPieceInstance({ start: 'now', duration: 3000 }, 5000, { endRelativeToPart: 2800 })
@@ -639,16 +611,4 @@ describe('resolvePrunedPieceInstances', () => {
639611
resolvedDuration: 2800 - nowInPart,
640612
} satisfies ResolvedPieceInstance)
641613
})
642-
643-
test('now start, with end cap, planned duration and userDuration.endRelativeToNow', async () => {
644-
const nowInPart = 123
645-
const piece = createPieceInstance({ start: 'now', duration: 3000 }, 5000, { endRelativeToNow: 2800 })
646-
647-
expect(resolvePrunedPieceInstance(nowInPart, clone(piece))).toStrictEqual({
648-
instance: clone(piece),
649-
timelinePriority: piece.priority,
650-
resolvedStart: nowInPart,
651-
resolvedDuration: 2800,
652-
} satisfies ResolvedPieceInstance)
653-
})
654614
})

packages/corelib/src/playout/processAndPrune.ts

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -247,11 +247,7 @@ export function resolvePrunedPieceInstance(
247247

248248
// Consider the playout userDuration
249249
if (pieceInstance.userDuration) {
250-
if ('endRelativeToPart' in pieceInstance.userDuration) {
251-
caps.push(pieceInstance.userDuration.endRelativeToPart - resolvedStart)
252-
} else if ('endRelativeToNow' in pieceInstance.userDuration) {
253-
caps.push(nowInPart + pieceInstance.userDuration.endRelativeToNow - resolvedStart)
254-
}
250+
caps.push(pieceInstance.userDuration.endRelativeToPart - resolvedStart)
255251
}
256252

257253
return {

packages/job-worker/src/playout/__tests__/resolvedPieces.test.ts

Lines changed: 1 addition & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -295,32 +295,6 @@ describe('Resolved Pieces', () => {
295295
] satisfies StrippedResult)
296296
})
297297

298-
test('userDuration.endRelativeToNow', async () => {
299-
const sourceLayerId = Object.keys(sourceLayers)[0]
300-
expect(sourceLayerId).toBeTruthy()
301-
302-
const piece0 = createPieceInstance(
303-
sourceLayerId,
304-
{ start: 1000 },
305-
{},
306-
{
307-
userDuration: {
308-
endRelativeToNow: 2000,
309-
},
310-
}
311-
)
312-
313-
const resolvedPieces = getResolvedPiecesInner(sourceLayers, 2500, [piece0])
314-
315-
expect(stripResult(resolvedPieces)).toEqual([
316-
{
317-
_id: piece0._id,
318-
resolvedStart: 1000,
319-
resolvedDuration: 3500,
320-
},
321-
] satisfies StrippedResult)
322-
})
323-
324298
test('preroll has no effect', async () => {
325299
const sourceLayerId = Object.keys(sourceLayers)[0]
326300
expect(sourceLayerId).toBeTruthy()
@@ -645,41 +619,6 @@ describe('Resolved Pieces', () => {
645619
] satisfies StrippedResult)
646620
})
647621

648-
test('userDuration.endRelativeToNow', async () => {
649-
const sourceLayerId = Object.keys(sourceLayers)[0]
650-
expect(sourceLayerId).toBeTruthy()
651-
652-
const piece001 = createPieceInstance(
653-
sourceLayerId,
654-
{ start: 4000 },
655-
{},
656-
{
657-
userDuration: {
658-
endRelativeToNow: 1300,
659-
},
660-
}
661-
)
662-
663-
const now = 990000
664-
const nowInPart = 7000
665-
const partStarted = now - nowInPart
666-
667-
const currentPartInfo = createPartInstanceInfo(partStarted, nowInPart, createPartInstance(), [piece001])
668-
669-
const simpleResolvedPieces = getResolvedPiecesForPartInstancesOnTimeline(
670-
context,
671-
{ current: currentPartInfo },
672-
now
673-
)
674-
expect(stripResult(simpleResolvedPieces)).toEqual([
675-
{
676-
_id: piece001._id,
677-
resolvedStart: partStarted + 4000,
678-
resolvedDuration: -4000 + 7000 + 1300,
679-
},
680-
] satisfies StrippedResult)
681-
})
682-
683622
test('basic previousPart', async () => {
684623
const sourceLayerId = Object.keys(sourceLayers)[0]
685624
expect(sourceLayerId).toBeTruthy()
@@ -809,7 +748,7 @@ describe('Resolved Pieces', () => {
809748
},
810749
{
811750
userDuration: {
812-
endRelativeToNow: 3400,
751+
endRelativeToPart: 5400,
813752
},
814753
}
815754
)

packages/job-worker/src/playout/adlibUtils.ts

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -21,7 +21,6 @@ import { PieceLifespan } from '@sofie-automation/blueprints-integration'
2121
import { SourceLayers } from '@sofie-automation/corelib/dist/dataModel/ShowStyleBase'
2222
import { updatePartInstanceRanksAfterAdlib } from '../updatePartInstanceRanksAndOrphanedState.js'
2323
import { setNextPart } from './setNext.js'
24-
import { calculateNowOffsetLatency } from './timeline/multi-gateway.js'
2524
import { logger } from '../logging.js'
2625
import { ReadonlyDeep } from 'type-fest'
2726
import { PlayoutRundownModel } from './model/PlayoutRundownModel.js'
@@ -279,8 +278,7 @@ export function innerStopPieces(
279278
}
280279

281280
const resolvedPieces = getResolvedPiecesForCurrentPartInstance(context, sourceLayers, currentPartInstance)
282-
const offsetRelativeToNow = (timeOffset || 0) + (calculateNowOffsetLatency(context, playoutModel) || 0)
283-
const stopAt = getCurrentTime() + offsetRelativeToNow
281+
const stopAt = playoutModel.getNowInPlayout() + (timeOffset ?? 0)
284282
const relativeStopAt = stopAt - lastStartedPlayback
285283

286284
for (const resolvedPieceInstance of resolvedPieces) {
@@ -310,15 +308,9 @@ export function innerStopPieces(
310308

311309
const pieceInstanceModel = playoutModel.findPieceInstance(pieceInstance._id)
312310
if (pieceInstanceModel) {
313-
const newDuration: Required<PieceInstance>['userDuration'] = playoutModel.isMultiGatewayMode
314-
? {
315-
endRelativeToNow: offsetRelativeToNow,
316-
}
317-
: {
318-
endRelativeToPart: relativeStopAt,
319-
}
320-
321-
pieceInstanceModel.pieceInstance.setDuration(newDuration)
311+
pieceInstanceModel.pieceInstance.setDuration({
312+
endRelativeToPart: relativeStopAt,
313+
})
322314

323315
stoppedInstances.push(pieceInstance._id)
324316
} else {

packages/job-worker/src/playout/model/PlayoutModel.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@ import { PlayoutPieceInstanceModel } from './PlayoutPieceInstanceModel.js'
3232
import { PieceInstanceWithTimings } from '@sofie-automation/corelib/dist/playout/processAndPrune'
3333
import { PartCalculatedTimings } from '@sofie-automation/corelib/dist/playout/timings'
3434
import type { INotificationsModel } from '../../notifications/NotificationsModel.js'
35+
import { Time } from '@sofie-automation/blueprints-integration'
3536

3637
export type DeferredFunction = (playoutModel: PlayoutModel) => void | Promise<void>
3738
export type DeferredAfterSaveFunction = (playoutModel: PlayoutModelReadonly) => void | Promise<void>
@@ -386,6 +387,13 @@ export interface PlayoutModel extends PlayoutModelReadonly, StudioPlayoutModelBa
386387
toPieceInstances: PieceInstanceWithTimings[]
387388
): PartCalculatedTimings
388389

390+
/**
391+
* Return an expected "now" value (i.e. the closest moment in time that can be safely addressed),
392+
* considering any playout latency. Every call will return a value greater or equal than previous,
393+
* meaning that this function is monotonic.
394+
*/
395+
getNowInPlayout(): Time
396+
389397
/** Lifecycle */
390398

391399
/**

packages/job-worker/src/playout/model/implementation/PlayoutModelImpl.ts

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,7 @@ import { calculatePartTimings, PartCalculatedTimings } from '@sofie-automation/c
6262
import { PieceInstanceWithTimings } from '@sofie-automation/corelib/dist/playout/processAndPrune'
6363
import { stringifyError } from '@sofie-automation/shared-lib/dist/lib/stringifyError'
6464
import { NotificationsModelHelper } from '../../../notifications/NotificationsModelHelper.js'
65+
import { getExpectedLatency } from '@sofie-automation/corelib/dist/studio/playout'
6566

6667
export class PlayoutModelReadonlyImpl implements PlayoutModelReadonly {
6768
public readonly playlistId: RundownPlaylistId
@@ -264,6 +265,31 @@ export class PlayoutModelReadonlyImpl implements PlayoutModelReadonly {
264265
}
265266
return this.#isMultiGatewayMode
266267
}
268+
269+
public get multiGatewayNowSafeLatency(): number | undefined {
270+
return this.context.studio.settings.multiGatewayNowSafeLatency
271+
}
272+
273+
/**
274+
* Calculate an offset to apply to the 'now' value, to compensate for delay in playout-gateway
275+
* The intention is that any concrete value used instead of 'now' should still be just in the future for playout-gateway
276+
*/
277+
protected getNowOffsetLatency(): number | undefined {
278+
/** The timestamp that "now" was set to */
279+
let nowOffsetLatency: number | undefined
280+
281+
if (this.isMultiGatewayMode) {
282+
const playoutDevices = this.peripheralDevices.filter(
283+
(device) => device.type === PeripheralDeviceType.PLAYOUT
284+
)
285+
const worstLatency = Math.max(0, ...playoutDevices.map((device) => getExpectedLatency(device).safe))
286+
/** Add a little more latency, to account for network latency variability */
287+
const ADD_SAFE_LATENCY = this.multiGatewayNowSafeLatency || 30
288+
nowOffsetLatency = worstLatency + ADD_SAFE_LATENCY
289+
}
290+
291+
return nowOffsetLatency
292+
}
267293
}
268294

269295
/**
@@ -859,6 +885,15 @@ export class PlayoutModelImpl extends PlayoutModelReadonlyImpl implements Playou
859885
this.#playlistHasChanged = true
860886
}
861887

888+
#lastMonotonicNowInPlayout = getCurrentTime()
889+
getNowInPlayout(): number {
890+
const nowOffsetLatency = this.getNowOffsetLatency() ?? 0
891+
const targetNowTime = getCurrentTime() + nowOffsetLatency
892+
const result = Math.max(this.#lastMonotonicNowInPlayout, targetNowTime)
893+
this.#lastMonotonicNowInPlayout = result
894+
return result
895+
}
896+
862897
/** Notifications */
863898

864899
async getAllNotifications(

0 commit comments

Comments
 (0)