Skip to content

feat: add availablePostrollDuration to parts#1779

Open
ianshade wants to merge 1 commit into
Sofie-Automation:mainfrom
bbc:contribute/available-postroll-duration
Open

feat: add availablePostrollDuration to parts#1779
ianshade wants to merge 1 commit into
Sofie-Automation:mainfrom
bbc:contribute/available-postroll-duration

Conversation

@ianshade

Copy link
Copy Markdown
Contributor

About the Contributor

This pull request is posted on behalf of the BBC.

Type of Contribution

This is a:

Feature

Current Behavior

When a Part had autoNext and an expectedDuration, any outTransition or next-Part's previousPartKeepaliveDuration would overlap with the end of the Part - the next Part would begin sooner and the transition would run while the current Part's content was still presumed to be playing, consuming time from within the expectedDuration. Completely reasonable as the default behavior for preventing freeze frames from appearing on the output - if e.g. a VT has exactly expectedDuration of playable content, the transition should start earlier so that the VT does not freeze while the transition executes. There are however use cases where it is not desired for a transition to eat into the expectedDuration of the previous part.

New Behavior

Blueprints can now set availablePostrollDuration on a Part to declare how much extra time beyond expectedDuration the Part is allowed to run. When the next Part requires overlap (via outTransition or fromPartKeepalive), Sofie extends the current Part's timeline group by up to availablePostrollDuration, effectively delaying the autoNext - just enough to satisfy the overlap - so in a best case scenario the transition no longer has to borrow time from within the scheduled duration at all.
Setting it to zero (or not setting at all) would be equivalent to the existing behavior - Sofie would execute the take sooner, making sure the part (including its transition keepalive) is gone from the air by the time expectedDuration passes.
Setting it to +Infinity, means that the part is allowed to last past expectedDuration however long the transition is.
Any value in between can be used when the available postroll is known, e.g. if the VT has a defined duration of spare material that is allowed to be shown on the output while the transition executes - any transition longer than that, will start consuming the previous Part's duration.

Testing

  • I have added one or more unit tests for this PR
  • I have updated the relevant unit tests
  • No unit test changes are needed for this PR

Affected areas

Time Frame

Other Information

Status

  • PR is ready to be reviewed.
  • The functionality has been tested by the author.
  • Relevant unit tests has been added / updated.
  • Relevant documentation (code comments, system documentation) has been added / updated.

It signifies how long a Part may continue after its `expectedDuration`, when transitions/keepalive require overlap
@coderabbitai

coderabbitai Bot commented Jun 22, 2026

Copy link
Copy Markdown

Review Change Stack

Walkthrough

Adds an optional availablePostrollDuration field to IBlueprintMutatablePart to cap how much a next part's keepalive can extend the current part. The field is propagated through blueprint conversion, resolved-pieces timing calculations, and timeline autonext group duration logic, with new tests covering all capping scenarios.

Changes

availablePostrollDuration keepalive capping

Layer / File(s) Summary
Blueprint interface and conversion wiring
packages/blueprints-integration/src/documents/part.ts, packages/job-worker/src/blueprints/context/lib.ts
Adds availablePostrollDuration?: number to IBlueprintMutatablePart, registers it in PlayoutMutatablePartSampleKeys, and maps it through convertPartToBlueprints.
Resolved-pieces timing math with postroll extension
packages/job-worker/src/playout/resolvedPieces.ts
Adds getAutoNextExpectedDurationExtension helper computing keepalive minus outTransition duration, clamped by availablePostrollDuration. Integrates the result into currentPartDuration and currentPartEnd, and changes piece duration cap from nextPartStarted to currentPartEnd.
Timeline autonext group duration with extension
packages/job-worker/src/playout/timeline/rundown.ts
Adds the same getAutoNextExpectedDurationExtension helper locally, updates createCurrentPartGroupEnable to accept `nextPartTimings: PartCalculatedTimings
Tests for resolved-pieces and timeline keepalive capping
packages/job-worker/src/playout/__tests__/resolvedPieces.test.ts, packages/job-worker/src/playout/timeline/__tests__/rundown.test.ts
Extends createPartInstance to accept outTransition and availablePostrollDuration; adds scenarios for keepalive not shortening current part, outTransition not shortening current part, preroll-only no extension, and keepalive capped at 0, undefined, partial, and uncapped values.

Sequence Diagram(s)

sequenceDiagram
  participant Blueprint as Blueprint/Part DB
  participant convertPart as convertPartToBlueprints
  participant resolvedPieces as getResolvedPiecesForPartInstancesOnTimeline
  participant ext as getAutoNextExpectedDurationExtension
  participant rundown as createCurrentPartGroupEnable

  Blueprint->>convertPart: part.availablePostrollDuration
  convertPart-->>Blueprint: IBlueprintPartDB.availablePostrollDuration

  resolvedPieces->>ext: currentPartInfo, nextPartTimings (fromPartKeepalive, outTransition)
  ext-->>resolvedPieces: extension capped by availablePostrollDuration
  resolvedPieces->>resolvedPieces: currentPartDuration = expectedDuration + extension + toPartDelay + toPartPostroll
  resolvedPieces->>resolvedPieces: cap piece durations at currentPartEnd

  rundown->>ext: currentPartInfo, nextPartTimings
  ext-->>rundown: expectedDurationExtension
  rundown->>rundown: currentPartEnable.duration = expectedDuration + extension + toPartDelay + toPartPostroll
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related issues

Suggested labels

Contribution from BBC

Suggested reviewers

  • Julusian
  • jstarpl
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 14.29% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: add availablePostrollDuration to parts' accurately and concisely describes the main feature addition introduced in this PR.
Description check ✅ Passed The description comprehensively explains the current behavior, new behavior, motivation, and testing approach for the availablePostrollDuration feature.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@codecov

codecov Bot commented Jun 22, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (4)
packages/job-worker/src/playout/timeline/rundown.ts (1)

54-70: 🧹 Nitpick | 🔵 Trivial | 🏗️ Heavy lift

Consolidate duplicate extension math into one shared helper.

getAutoNextExpectedDurationExtension now exists in timeline code while similar logic is also used in resolved-pieces timing. Keeping two copies risks subtle divergence between currentPartGroup.enable.duration and resolved piece capping behavior.

Refactor direction
- function getAutoNextExpectedDurationExtension(...) { ... } // local copy
+ import { getAutoNextExpectedDurationExtension } from '../shared/autoNextDuration'

Then reuse the same helper from both:

  • packages/job-worker/src/playout/timeline/rundown.ts
  • packages/job-worker/src/playout/resolvedPieces.ts
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/job-worker/src/playout/timeline/rundown.ts` around lines 54 - 70,
Extract the duration extension calculation logic from
getAutoNextExpectedDurationExtension in rundown.ts into a shared utility helper
function that can be imported and reused by both the timeline code
(packages/job-worker/src/playout/timeline/rundown.ts) and the resolved-pieces
timing code (packages/job-worker/src/playout/resolvedPieces.ts). This
consolidates the duplicate extension math into one authoritative location,
preventing subtle divergence between currentPartGroup.enable.duration and
resolved piece capping behavior.
packages/job-worker/src/playout/__tests__/resolvedPieces.test.ts (1)

970-1358: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Add an explicit availablePostrollDuration: Infinity test case.

The new suite covers finite caps well, but it does not lock in the documented uncapped behavior for +Infinity. A focused assertion here would protect the contract.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/job-worker/src/playout/__tests__/resolvedPieces.test.ts` around
lines 970 - 1358, Add a new test case that explicitly sets
availablePostrollDuration to Infinity to document and verify the uncapped
behavior. Create a test following the same pattern as the existing
availablePostrollDuration tests (like the ones capped by 0, 700, and 5000), but
explicitly pass availablePostrollDuration: Infinity in the createPartInstance
call for the currentPartInfo. The test should verify that when
availablePostrollDuration is Infinity, the fromPartKeepalive is not capped and
the resolved duration extends fully by the keepalive amount, ensuring the
uncapped behavior is locked in by an explicit assertion.
packages/job-worker/src/playout/timeline/__tests__/rundown.test.ts (1)

388-425: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Rename the first two test titles to match their assertions.

At Line 388 and Line 427, both test names say “extends current part duration”, but expected duration stays 5000. Renaming to “does not extend … when postroll budget is unset/zero” will make intent unambiguous.

Also applies to: 427-465

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/job-worker/src/playout/timeline/__tests__/rundown.test.ts` around
lines 388 - 425, The test titles for the test blocks starting at line 388 and
line 427 are misleading. Both tests are currently titled "autonext with
keepalive extends current part duration" but the assertions verify that the
duration remains at 5000 and does not extend. Rename both test titles (the
strings passed to the it() function calls) to accurately reflect that the
duration does NOT extend when postroll budget is unset or zero, such as
"autonext with keepalive does not extend current part duration when postroll
budget is unset" or similar wording that makes the test intent unambiguous.
packages/job-worker/src/playout/resolvedPieces.ts (1)

14-26: 🧹 Nitpick | 🔵 Trivial | ⚡ Quick win

Centralize the auto-next extension calculation.

This helper duplicates the same timing contract used in timeline/rundown.ts; if one copy changes later, resolved pieces and timeline group durations can diverge. Consider extracting a shared helper that both call sites use.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/job-worker/src/playout/resolvedPieces.ts` around lines 14 - 26, The
function getAutoNextExpectedDurationExtension contains timing calculation logic
that is duplicated in timeline/rundown.ts, creating a risk that changes to one
copy could cause the resolved pieces and timeline group durations to diverge.
Extract the shared auto-next extension calculation logic into a single
centralized helper function (in a utilities or common module) and update both
the resolvedPieces.ts file and the timeline/rundown.ts file to call this shared
helper instead of duplicating the logic.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/job-worker/src/playout/resolvedPieces.ts`:
- Around line 72-83: The zero-duration guard applied to the `nextPartStarted`
calculation (checking `expectedDuration !== 0`) is not applied to the
`currentPartEnd` calculation. Add the same `expectedDuration !== 0` condition to
the `currentPartEnd` ternary expression so that it returns null when the
expected duration is zero, preventing the truncation of zero-duration auto-next
parts that occurs downstream when this value is used at line 105.

---

Nitpick comments:
In `@packages/job-worker/src/playout/__tests__/resolvedPieces.test.ts`:
- Around line 970-1358: Add a new test case that explicitly sets
availablePostrollDuration to Infinity to document and verify the uncapped
behavior. Create a test following the same pattern as the existing
availablePostrollDuration tests (like the ones capped by 0, 700, and 5000), but
explicitly pass availablePostrollDuration: Infinity in the createPartInstance
call for the currentPartInfo. The test should verify that when
availablePostrollDuration is Infinity, the fromPartKeepalive is not capped and
the resolved duration extends fully by the keepalive amount, ensuring the
uncapped behavior is locked in by an explicit assertion.

In `@packages/job-worker/src/playout/resolvedPieces.ts`:
- Around line 14-26: The function getAutoNextExpectedDurationExtension contains
timing calculation logic that is duplicated in timeline/rundown.ts, creating a
risk that changes to one copy could cause the resolved pieces and timeline group
durations to diverge. Extract the shared auto-next extension calculation logic
into a single centralized helper function (in a utilities or common module) and
update both the resolvedPieces.ts file and the timeline/rundown.ts file to call
this shared helper instead of duplicating the logic.

In `@packages/job-worker/src/playout/timeline/__tests__/rundown.test.ts`:
- Around line 388-425: The test titles for the test blocks starting at line 388
and line 427 are misleading. Both tests are currently titled "autonext with
keepalive extends current part duration" but the assertions verify that the
duration remains at 5000 and does not extend. Rename both test titles (the
strings passed to the it() function calls) to accurately reflect that the
duration does NOT extend when postroll budget is unset or zero, such as
"autonext with keepalive does not extend current part duration when postroll
budget is unset" or similar wording that makes the test intent unambiguous.

In `@packages/job-worker/src/playout/timeline/rundown.ts`:
- Around line 54-70: Extract the duration extension calculation logic from
getAutoNextExpectedDurationExtension in rundown.ts into a shared utility helper
function that can be imported and reused by both the timeline code
(packages/job-worker/src/playout/timeline/rundown.ts) and the resolved-pieces
timing code (packages/job-worker/src/playout/resolvedPieces.ts). This
consolidates the duplicate extension math into one authoritative location,
preventing subtle divergence between currentPartGroup.enable.duration and
resolved piece capping behavior.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: d5bbad29-b0ab-47cd-8315-37a97b54e33c

📥 Commits

Reviewing files that changed from the base of the PR and between 97a4d0d and e870200.

📒 Files selected for processing (6)
  • packages/blueprints-integration/src/documents/part.ts
  • packages/job-worker/src/blueprints/context/lib.ts
  • packages/job-worker/src/playout/__tests__/resolvedPieces.test.ts
  • packages/job-worker/src/playout/resolvedPieces.ts
  • packages/job-worker/src/playout/timeline/__tests__/rundown.test.ts
  • packages/job-worker/src/playout/timeline/rundown.ts

Comment on lines 72 to +83
const nextPartStarted =
partInstancesInfo.current.partInstance.part.autoNext &&
partInstancesInfo.current.partInstance.part.expectedDuration !== 0 &&
partInstancesInfo.current.partInstance.part.expectedDuration !== undefined
? currentPartStarted + partInstancesInfo.current.partInstance.part.expectedDuration
currentPartDuration !== null
? currentPartStarted +
currentPartDuration -
(partInstancesInfo.next?.calculatedTimings.fromPartRemaining ?? 0)
: null

const currentPartEnd =
partInstancesInfo.current.partInstance.part.autoNext && currentPartDuration !== null
? currentPartStarted + currentPartDuration

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Apply the zero-duration guard to currentPartEnd too.

Line 74 prevents calculating an auto-next start for expectedDuration === 0, but lines 81-83 still produce a cap that line 105 applies to current pieces. That can unexpectedly truncate zero-duration auto-next parts.

🐛 Proposed fix
+	const hasValidAutoNextDuration =
+		partInstancesInfo.current.partInstance.part.autoNext &&
+		partInstancesInfo.current.partInstance.part.expectedDuration !== 0 &&
+		currentPartDuration !== null
+
 	const nextPartStarted =
-		partInstancesInfo.current.partInstance.part.autoNext &&
-		partInstancesInfo.current.partInstance.part.expectedDuration !== 0 &&
-		currentPartDuration !== null
+		hasValidAutoNextDuration
 			? currentPartStarted +
 				currentPartDuration -
 				(partInstancesInfo.next?.calculatedTimings.fromPartRemaining ?? 0)
 			: null
 
-	const currentPartEnd =
-		partInstancesInfo.current.partInstance.part.autoNext && currentPartDuration !== null
-			? currentPartStarted + currentPartDuration
-			: null
+	const currentPartEnd = hasValidAutoNextDuration ? currentPartStarted + currentPartDuration : null

Also applies to: 105-105

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/job-worker/src/playout/resolvedPieces.ts` around lines 72 - 83, The
zero-duration guard applied to the `nextPartStarted` calculation (checking
`expectedDuration !== 0`) is not applied to the `currentPartEnd` calculation.
Add the same `expectedDuration !== 0` condition to the `currentPartEnd` ternary
expression so that it returns null when the expected duration is zero,
preventing the truncation of zero-duration auto-next parts that occurs
downstream when this value is used at line 105.

@Saftret Saftret added the Contribution from BBC Contributions sponsored by BBC (bbc.co.uk) label Jun 22, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Contribution from BBC Contributions sponsored by BBC (bbc.co.uk)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants