Skip to content

Commit

Permalink
Merge branch 'upstream/fix-postroll-and-autonext-timing' into bbc-rel…
Browse files Browse the repository at this point in the history
…ease52
  • Loading branch information
mint-dewit committed Oct 2, 2024
2 parents 9ffb1bc + 89df068 commit 273029b
Show file tree
Hide file tree
Showing 3 changed files with 282 additions and 3 deletions.
4 changes: 2 additions & 2 deletions packages/corelib/src/playout/timings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -158,8 +158,8 @@ export function getPartTimingsOrDefaults(
}

function calculateExpectedDurationWithTransition(rawDuration: number, timings: PartCalculatedTimings): number {
// toPartDelay needs to be subtracted, because it is added to `fromPartRemaining` when the `fromPartRemaining` value is calculated.
return Math.max(0, rawDuration - (timings.fromPartRemaining - timings.toPartDelay))
// toPartDelay and fromPartPostroll needs to be subtracted, because it is added to `fromPartRemaining` when the `fromPartRemaining` value is calculated.
return Math.max(0, rawDuration - (timings.fromPartRemaining - timings.toPartDelay - timings.fromPartPostroll))
}

export type CalculateExpectedDurationPart = Pick<DBPart, 'inTransition' | 'expectedDuration'>
Expand Down
275 changes: 275 additions & 0 deletions packages/webui/src/client/lib/__tests__/rundownTiming.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1405,6 +1405,281 @@ describe('rundown Timing Calculator', () => {
)
})

it('Handles part with autonext', () => {
const timing = new RundownTimingCalculator()
const playlist: DBRundownPlaylist = makeMockPlaylist()
playlist.timing = {
type: 'forward-time' as any,
expectedStart: 0,
expectedDuration: 40000,
}
const rundownId1 = 'rundown1'
const segmentId1 = 'segment1'
const segmentId2 = 'segment2'
const segmentsMap: Map<SegmentId, DBSegment> = new Map()
segmentsMap.set(protectString<SegmentId>(segmentId1), makeMockSegment(segmentId1, 0, rundownId1))
segmentsMap.set(protectString<SegmentId>(segmentId2), makeMockSegment(segmentId2, 0, rundownId1))
const parts: DBPart[] = []
parts.push(
makeMockPart('part1', 0, rundownId1, segmentId1, {
budgetDuration: 2000,
expectedDuration: 1000,
})
)
parts.push(
makeMockPart('part2', 0, rundownId1, segmentId1, {
budgetDuration: 3000,
expectedDuration: 1000,
})
)
parts.push(
makeMockPart('part3', 0, rundownId1, segmentId2, {
budgetDuration: 3000,
expectedDuration: 1000,
})
)
parts.push(makeMockPart('part4', 0, rundownId1, segmentId2, { expectedDuration: 1000 }))
// set autonext and create partInstances
parts[0].autoNext = true
const partInstance1 = wrapPartToTemporaryInstance(protectString(''), parts[0])
partInstance1.isTemporary = false
partInstance1.timings = {
plannedStartedPlayback: 0,
}
const partInstance2 = wrapPartToTemporaryInstance(protectString(''), parts[1])
partInstance2.isTemporary = false
partInstance2.timings = {
plannedStartedPlayback: 1000, // start after part1's expectedDuration
}
const partInstances = [partInstance1, partInstance2, ...convertPartsToPartInstances([parts[2], parts[3]])]
const partInstancesMap: Map<PartId, PartInstance> = new Map()
const rundown = makeMockRundown(rundownId1, playlist)
const rundowns = [rundown]
// at t = 0
const result = timing.updateDurations(
0,
false,
playlist,
rundowns,
undefined,
partInstances,
partInstancesMap,
segmentsMap,
DEFAULT_DURATION,
{}
)
expect(result).toEqual(
literal<RundownTimingContext>({
currentPartInstanceId: null,
isLowResolution: false,
asDisplayedPlaylistDuration: 4000,
asPlayedPlaylistDuration: 8000,
currentPartWillAutoNext: false,
currentTime: 0,
rundownExpectedDurations: {
[rundownId1]: 4000,
},
rundownAsPlayedDurations: {
[rundownId1]: 8000,
},
partCountdown: {
part1: 0,
part2: 1000,
part3: 5000,
part4: 6000,
},
partDisplayDurations: {
part1_tmp_instance: 1000,
part2_tmp_instance: 1000,
part3: 1000,
part4: 1000,
},
partDisplayStartsAt: {
part1_tmp_instance: 0,
part2_tmp_instance: 1000,
part3: 2000,
part4: 3000,
},
partDurations: {
part1_tmp_instance: 1000,
part2_tmp_instance: 1000,
part3: 1000,
part4: 1000,
},
partExpectedDurations: {
part1_tmp_instance: 1000,
part2_tmp_instance: 1000,
part3: 1000,
part4: 1000,
},
partPlayed: {
part1_tmp_instance: 0,
part2_tmp_instance: 0,
part3: 0,
part4: 0,
},
partStartsAt: {
part1_tmp_instance: 0,
part2_tmp_instance: 1000,
part3: 2000,
part4: 3000,
},
remainingPlaylistDuration: 8000,
totalPlaylistDuration: 8000,
breakIsLastRundown: undefined,
remainingTimeOnCurrentPart: undefined,
rundownsBeforeNextBreak: undefined,
segmentStartedPlayback: {},
})
)
})

it('Handles part with postroll', () => {
const timing = new RundownTimingCalculator()
const playlist: DBRundownPlaylist = makeMockPlaylist()
playlist.timing = {
type: 'forward-time' as any,
expectedStart: 0,
expectedDuration: 40000,
}
const rundownId1 = 'rundown1'
const segmentId1 = 'segment1'
const segmentId2 = 'segment2'
const segmentsMap: Map<SegmentId, DBSegment> = new Map()
segmentsMap.set(protectString<SegmentId>(segmentId1), makeMockSegment(segmentId1, 0, rundownId1))
segmentsMap.set(protectString<SegmentId>(segmentId2), makeMockSegment(segmentId2, 0, rundownId1))
const parts: DBPart[] = []
parts.push(
makeMockPart('part1', 0, rundownId1, segmentId1, {
budgetDuration: 2000,
expectedDuration: 2000,
})
)
parts.push(
makeMockPart('part2', 0, rundownId1, segmentId1, {
budgetDuration: 3000,
expectedDuration: 2000,
})
)
parts.push(
makeMockPart('part3', 0, rundownId1, segmentId2, {
budgetDuration: 3000,
expectedDuration: 1000,
})
)
parts.push(makeMockPart('part4', 0, rundownId1, segmentId2, { expectedDuration: 1000 }))
// set autonext and create partInstances
parts[0].autoNext = true
const partInstance1 = wrapPartToTemporaryInstance(protectString(''), parts[0])
partInstance1.isTemporary = false
partInstance1.timings = {
plannedStartedPlayback: 0,
reportedStartedPlayback: 0,
reportedStoppedPlayback: 2000,
}
partInstance1.partPlayoutTimings = {
inTransitionStart: 0,
toPartDelay: 0,
toPartPostroll: 500,
fromPartRemaining: 0,
fromPartPostroll: 0,
}
const partInstance2 = wrapPartToTemporaryInstance(protectString(''), parts[1])
partInstance2.isTemporary = false
partInstance2.timings = {
plannedStartedPlayback: 2000, // start after part1's expectedDuration
reportedStartedPlayback: 2000,
}
partInstance2.partPlayoutTimings = {
inTransitionStart: 0,
toPartDelay: 0,
toPartPostroll: 0,
fromPartRemaining: 500,
fromPartPostroll: 500,
}
const partInstances = [partInstance1, partInstance2, ...convertPartsToPartInstances([parts[2], parts[3]])]
const partInstancesMap: Map<PartId, PartInstance> = new Map()
const rundown = makeMockRundown(rundownId1, playlist)
const rundowns = [rundown]
// at t = 0
const result = timing.updateDurations(
3000,
false,
playlist,
rundowns,
undefined,
partInstances,
partInstancesMap,
segmentsMap,
DEFAULT_DURATION,
{}
)
expect(result).toEqual(
literal<RundownTimingContext>({
currentPartInstanceId: null,
isLowResolution: false,
asDisplayedPlaylistDuration: 6000,
asPlayedPlaylistDuration: 8000,
currentPartWillAutoNext: false,
currentTime: 3000,
rundownExpectedDurations: {
[rundownId1]: 6000,
},
rundownAsPlayedDurations: {
[rundownId1]: 8000,
},
partCountdown: {
part1: 4000,
part2: 6000,
part3: 6000,
part4: 7000,
},
partDisplayDurations: {
part1_tmp_instance: 2000,
part2_tmp_instance: 2000,
part3: 1000,
part4: 1000,
},
partDisplayStartsAt: {
part1_tmp_instance: 0,
part2_tmp_instance: 2000,
part3: 4000,
part4: 5000,
},
partDurations: {
part1_tmp_instance: 2000,
part2_tmp_instance: 2000,
part3: 1000,
part4: 1000,
},
partExpectedDurations: {
part1_tmp_instance: 2000,
part2_tmp_instance: 2000,
part3: 1000,
part4: 1000,
},
partPlayed: {
part1_tmp_instance: 0,
part2_tmp_instance: 1000,
part3: 0,
part4: 0,
},
partStartsAt: {
part1_tmp_instance: 0,
part2_tmp_instance: 2000,
part3: 4000,
part4: 5000,
},
remainingPlaylistDuration: 8000,
totalPlaylistDuration: 8000,
breakIsLastRundown: undefined,
remainingTimeOnCurrentPart: undefined,
rundownsBeforeNextBreak: undefined,
segmentStartedPlayback: {},
})
)
})

it('Back-time: Can find the next expectedStart rundown anchor when it is in a future segment', () => {
const timing = new RundownTimingCalculator()
const playlist: DBRundownPlaylist = makeMockPlaylist()
Expand Down
6 changes: 5 additions & 1 deletion packages/webui/src/client/lib/rundownTiming.ts
Original file line number Diff line number Diff line change
Expand Up @@ -154,7 +154,11 @@ export class RundownTimingCalculator {
const partsSegment = segmentsMap.get(partInstance.segmentId)
const segmentBudget = partsSegment?.segmentTiming?.budgetDuration
const segmentUsesBudget = segmentBudget !== undefined
const lastStartedPlayback = partInstance.timings?.plannedStartedPlayback
// note: lastStartedPlayback that lies in the future means it hasn't started yet (like from autonext)
const lastStartedPlayback =
(partInstance.timings?.plannedStartedPlayback ?? 0) <= now
? partInstance.timings?.plannedStartedPlayback
: undefined

if (partInstance.segmentId !== lastSegmentId) {
this.untimedSegments.add(partInstance.segmentId)
Expand Down

0 comments on commit 273029b

Please sign in to comment.