diff --git a/meteor/CHANGELOG.md b/meteor/CHANGELOG.md index 99888a30a1..ea3ef43c22 100644 --- a/meteor/CHANGELOG.md +++ b/meteor/CHANGELOG.md @@ -2,6 +2,23 @@ All notable changes to this project will be documented in this file. See [standard-version](https://github.com/conventional-changelog/standard-version) for commit guidelines. +### [1.50.4](///compare/v1.50.3...v1.50.4) (2024-08-08) + + +### Bug Fixes + +* compensate for piece preroll for adlibbed pieces SOFIE-3369 ([#1236](undefined/undefined/undefined/issues/1236)) c8f7c42 +* rundown timing drifting when playing parts with preroll SOFIE-3291 ([#1234](undefined/undefined/undefined/issues/1234)) a444857 +* unexpected timeline updates while playing final part in rundown SOFIE-3371 ([#1237](undefined/undefined/undefined/issues/1237)) 393f0c1 + +### [1.50.3](https://github.com/nrkno/sofie-core/compare/v1.50.2...v1.50.3) (2024-06-24) + + +### Bug Fixes + +* **DashboardPieceButton:** hover previews are not positioned correctly ([89fd219](https://github.com/nrkno/sofie-core/commit/89fd219d57fb775ed140ed2eaf5326a80194661f)) +* input gateway devices lose rundown actions on reset SOFIE-3134 ([#1190](https://github.com/nrkno/sofie-core/issues/1190)) ([aa69e5d](https://github.com/nrkno/sofie-core/commit/aa69e5d2df512f0e79bb1ada51ed61560258c0cd)) + ### [1.50.2](///compare/v1.49.6...v1.50.2) (2024-05-15) diff --git a/meteor/__mocks__/defaultCollectionObjects.ts b/meteor/__mocks__/defaultCollectionObjects.ts index 3179bde198..373c7ac2c6 100644 --- a/meteor/__mocks__/defaultCollectionObjects.ts +++ b/meteor/__mocks__/defaultCollectionObjects.ts @@ -138,7 +138,7 @@ export function defaultPart(_id: PartId, rundownId: RundownId, segmentId: Segmen _rank: 0, externalId: unprotectString(_id), title: 'Default Part', - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, } } export function defaultPiece(_id: PieceId, rundownId: RundownId, segmentId: SegmentId, partId: PartId): Piece { diff --git a/meteor/__mocks__/helpers/database.ts b/meteor/__mocks__/helpers/database.ts index 23c08c2b98..6acc45f8bd 100644 --- a/meteor/__mocks__/helpers/database.ts +++ b/meteor/__mocks__/helpers/database.ts @@ -662,7 +662,7 @@ export async function setupDefaultRundown( _rank: 0, externalId: 'MOCK_PART_0_0', title: 'Part 0 0', - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, } await Parts.mutableCollection.insertAsync(part00) @@ -730,7 +730,7 @@ export async function setupDefaultRundown( _rank: 1, externalId: 'MOCK_PART_0_1', title: 'Part 0 1', - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, } await Parts.mutableCollection.insertAsync(part01) @@ -771,7 +771,7 @@ export async function setupDefaultRundown( _rank: 0, externalId: 'MOCK_PART_1_0', title: 'Part 1 0', - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, } await Parts.mutableCollection.insertAsync(part10) @@ -782,7 +782,7 @@ export async function setupDefaultRundown( _rank: 1, externalId: 'MOCK_PART_1_1', title: 'Part 1 1', - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, } await Parts.mutableCollection.insertAsync(part11) @@ -793,7 +793,7 @@ export async function setupDefaultRundown( _rank: 2, externalId: 'MOCK_PART_1_2', title: 'Part 1 2', - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, } await Parts.mutableCollection.insertAsync(part12) diff --git a/meteor/client/lib/__tests__/rundownTiming.test.ts b/meteor/client/lib/__tests__/rundownTiming.test.ts index a36aa68f77..c223adb5ce 100644 --- a/meteor/client/lib/__tests__/rundownTiming.test.ts +++ b/meteor/client/lib/__tests__/rundownTiming.test.ts @@ -50,7 +50,7 @@ function makeMockPart( _rank: rank, rundownId: protectString(rundownId), ...durations, - expectedDurationWithPreroll: durations.expectedDuration, + expectedDurationWithTransition: durations.expectedDuration, }) } diff --git a/meteor/client/lib/rundown.ts b/meteor/client/lib/rundown.ts index 0eb58e3043..d3a06e6aec 100644 --- a/meteor/client/lib/rundown.ts +++ b/meteor/client/lib/rundown.ts @@ -36,7 +36,7 @@ import { FindOptions } from '../../lib/collections/lib' import { getShowHiddenSourceLayers } from './localStorage' import { Rundown } from '../../lib/collections/Rundowns' import { IStudioSettings } from '@sofie-automation/corelib/dist/dataModel/Studio' -import { calculatePartInstanceExpectedDurationWithPreroll } from '@sofie-automation/corelib/dist/playout/timings' +import { calculatePartInstanceExpectedDurationWithTransition } from '@sofie-automation/corelib/dist/playout/timings' import { AdLibPieceUi } from './shelf' import { UIShowStyleBase } from '../../lib/api/showStyles' import { PartId, PieceId, RundownId, SegmentId, ShowStyleBaseId } from '@sofie-automation/corelib/dist/dataModel/Ids' @@ -63,7 +63,7 @@ export namespace RundownUtils { return ( memo + (part.instance.timings?.duration || - calculatePartInstanceExpectedDurationWithPreroll(part.instance) || + calculatePartInstanceExpectedDurationWithTransition(part.instance) || part.renderedDuration || (display ? Settings.defaultDisplayDuration : 0)) ) @@ -218,7 +218,7 @@ export namespace RundownUtils { ? part.instance.timings.duration + (part.instance.timings?.playOffset || 0) : (partDuration || part.renderedDuration || - calculatePartInstanceExpectedDurationWithPreroll(part.instance) || + calculatePartInstanceExpectedDurationWithTransition(part.instance) || 0) - (piece.renderedInPoint || 0))) : part.instance.timings?.duration !== undefined ? part.instance.timings.duration + (part.instance.timings?.playOffset || 0) @@ -422,7 +422,7 @@ export namespace RundownUtils { partsE = segmentInfo.partInstances.map((partInstance, itIndex) => { const partTimeline: SuperTimeline.TimelineObject[] = [] - const partExpectedDuration = calculatePartInstanceExpectedDurationWithPreroll(partInstance) + const partExpectedDuration = calculatePartInstanceExpectedDurationWithTransition(partInstance) // extend objects to match the Extended interface const partE = literal({ diff --git a/meteor/client/lib/rundownTiming.ts b/meteor/client/lib/rundownTiming.ts index 5fb0747e0d..421b9b0945 100644 --- a/meteor/client/lib/rundownTiming.ts +++ b/meteor/client/lib/rundownTiming.ts @@ -14,7 +14,7 @@ import { PartId, PartInstanceId, SegmentId } from '@sofie-automation/corelib/dist/dataModel/Ids' import { literal } from '@sofie-automation/corelib/dist/lib' import { PlaylistTiming } from '@sofie-automation/corelib/dist/playout/rundownTiming' -import { calculatePartInstanceExpectedDurationWithPreroll } from '@sofie-automation/corelib/dist/playout/timings' +import { calculatePartInstanceExpectedDurationWithTransition } from '@sofie-automation/corelib/dist/playout/timings' import { protectString, unprotectString } from '@sofie-automation/corelib/dist/protectedString' import { findPartInstanceInMapOrWrapToTemporary, @@ -211,7 +211,7 @@ export class RundownTimingCalculator { // if the Part is using budgetDuration, this budget is calculated when going through all the segments // in the Rundown (see further down) if (!segmentUsesBudget && !partIsUntimed) { - totalRundownDuration += calculatePartInstanceExpectedDurationWithPreroll(partInstance) || 0 + totalRundownDuration += calculatePartInstanceExpectedDurationWithTransition(partInstance) || 0 } const lastStartedPlayback = partInstance.timings?.plannedStartedPlayback @@ -225,7 +225,7 @@ export class RundownTimingCalculator { let displayDurationFromGroup = 0 partExpectedDuration = - calculatePartInstanceExpectedDurationWithPreroll(partInstance) || + calculatePartInstanceExpectedDurationWithTransition(partInstance) || partInstance.timings?.duration || 0 @@ -248,7 +248,7 @@ export class RundownTimingCalculator { ) { this.displayDurationGroups[partInstance.part.displayDurationGroup] = (this.displayDurationGroups[partInstance.part.displayDurationGroup] || 0) + - (calculatePartInstanceExpectedDurationWithPreroll(partInstance) || 0) + (calculatePartInstanceExpectedDurationWithTransition(partInstance) || 0) displayDurationFromGroup = partInstance.part.displayDuration || Math.max( @@ -272,7 +272,7 @@ export class RundownTimingCalculator { : undefined) partDuration = Math.max( - duration || calculatePartInstanceExpectedDurationWithPreroll(partInstance) || 0, + duration || calculatePartInstanceExpectedDurationWithTransition(partInstance) || 0, now - lastStartedPlayback ) - playOffset // because displayDurationGroups have no actual timing on them, we need to have a copy of the @@ -282,7 +282,7 @@ export class RundownTimingCalculator { duration || (memberOfDisplayDurationGroup ? displayDurationFromGroup - : calculatePartInstanceExpectedDurationWithPreroll(partInstance)) || + : calculatePartInstanceExpectedDurationWithTransition(partInstance)) || defaultDuration partDisplayDuration = Math.max(partDisplayDurationNoPlayback, now - lastStartedPlayback) this.partPlayed[unprotectString(partInstance.part._id)] = now - lastStartedPlayback @@ -304,7 +304,7 @@ export class RundownTimingCalculator { (duration || (memberOfDisplayDurationGroup ? displayDurationFromGroup - : calculatePartInstanceExpectedDurationWithPreroll(partInstance)) || + : calculatePartInstanceExpectedDurationWithTransition(partInstance)) || 0) - (now - lastStartedPlayback) ) @@ -312,7 +312,7 @@ export class RundownTimingCalculator { } else { partDuration = (partInstance.timings?.duration || - calculatePartInstanceExpectedDurationWithPreroll(partInstance) || + calculatePartInstanceExpectedDurationWithTransition(partInstance) || 0) - playOffset partDisplayDurationNoPlayback = Math.max( 0, @@ -320,7 +320,7 @@ export class RundownTimingCalculator { displayDurationFromGroup || ensureMinimumDefaultDurationIfNotAuto( partInstance, - calculatePartInstanceExpectedDurationWithPreroll(partInstance), + calculatePartInstanceExpectedDurationWithTransition(partInstance), defaultDuration ) ) @@ -344,7 +344,7 @@ export class RundownTimingCalculator { valToAddToAsPlayedDuration = partInstance.timings.duration } else if (partCounts) { valToAddToAsPlayedDuration = - calculatePartInstanceExpectedDurationWithPreroll(partInstance) || 0 + calculatePartInstanceExpectedDurationWithTransition(partInstance) || 0 } asPlayedRundownDuration += valToAddToAsPlayedDuration @@ -381,15 +381,15 @@ export class RundownTimingCalculator { memberOfDisplayDurationGroup ? Math.max( partExpectedDuration, - calculatePartInstanceExpectedDurationWithPreroll(partInstance) || 0 + calculatePartInstanceExpectedDurationWithTransition(partInstance) || 0 ) - : calculatePartInstanceExpectedDurationWithPreroll(partInstance) || 0, + : calculatePartInstanceExpectedDurationWithTransition(partInstance) || 0, now - lastStartedPlayback ) } else { asDisplayedRundownDuration += partInstance.timings?.duration || - calculatePartInstanceExpectedDurationWithPreroll(partInstance) || + calculatePartInstanceExpectedDurationWithTransition(partInstance) || 0 } @@ -433,12 +433,12 @@ export class RundownTimingCalculator { waitDuration = partInstance.timings?.duration || partDisplayDuration || - calculatePartInstanceExpectedDurationWithPreroll(partInstance) || + calculatePartInstanceExpectedDurationWithTransition(partInstance) || 0 } else { waitDuration = partInstance.timings?.duration || - calculatePartInstanceExpectedDurationWithPreroll(partInstance) || + calculatePartInstanceExpectedDurationWithTransition(partInstance) || 0 } if (segmentUsesBudget) { @@ -462,7 +462,8 @@ export class RundownTimingCalculator { // this needs to use partInstance.part.expectedDuration as opposed to partExpectedDuration, because // partExpectedDuration is affected by displayGroups, and if it hasn't played yet then it shouldn't // add any duration to the "remaining" time pool - remainingRundownDuration += calculatePartInstanceExpectedDurationWithPreroll(partInstance) || 0 + remainingRundownDuration += + calculatePartInstanceExpectedDurationWithTransition(partInstance) || 0 // item is onAir right now, and it's is currently shorter than expectedDuration } else if ( lastStartedPlayback && @@ -599,7 +600,7 @@ export class RundownTimingCalculator { let onAirPartDuration = currentLivePartInstance.timings?.duration || - calculatePartInstanceExpectedDurationWithPreroll(currentLivePartInstance) || + calculatePartInstanceExpectedDurationWithTransition(currentLivePartInstance) || 0 if ( currentLivePart.displayDurationGroup && diff --git a/meteor/client/ui/ClockView/PresenterScreen.tsx b/meteor/client/ui/ClockView/PresenterScreen.tsx index d16bd23d02..928e899177 100644 --- a/meteor/client/ui/ClockView/PresenterScreen.tsx +++ b/meteor/client/ui/ClockView/PresenterScreen.tsx @@ -32,7 +32,7 @@ import { DBShowStyleVariant } from '@sofie-automation/corelib/dist/dataModel/Sho import { RundownLayoutsAPI } from '../../../lib/api/rundownLayouts' import { ShelfDashboardLayout } from '../Shelf/ShelfDashboardLayout' import { parse as queryStringParse } from 'query-string' -import { calculatePartInstanceExpectedDurationWithPreroll } from '@sofie-automation/corelib/dist/playout/timings' +import { calculatePartInstanceExpectedDurationWithTransition } from '@sofie-automation/corelib/dist/playout/timings' import { getPlaylistTimingDiff } from '../../lib/rundownTiming' import { UIShowStyleBase } from '../../../lib/api/showStyles' import { UIShowStyleBases, UIStudios } from '../Collections' @@ -467,7 +467,7 @@ export class PresenterScreenBase extends MeteorReactComponent< showStyleBaseId={currentShowStyleBaseId} rundownIds={this.props.rundownIds} partAutoNext={currentPart.instance.part.autoNext || false} - partExpectedDuration={calculatePartInstanceExpectedDurationWithPreroll(currentPart.instance)} + partExpectedDuration={calculatePartInstanceExpectedDurationWithTransition(currentPart.instance)} partStartedPlayback={currentPart.instance.timings?.plannedStartedPlayback} playlistActivationId={this.props.playlist?.activationId} /> diff --git a/meteor/client/ui/RundownView/RundownTiming/SegmentDuration.tsx b/meteor/client/ui/RundownView/RundownTiming/SegmentDuration.tsx index f802e2ac13..1bae98c3bb 100644 --- a/meteor/client/ui/RundownView/RundownTiming/SegmentDuration.tsx +++ b/meteor/client/ui/RundownView/RundownTiming/SegmentDuration.tsx @@ -4,7 +4,7 @@ import { withTiming, WithTiming } from './withTiming' import { unprotectString } from '../../../../lib/lib' import { RundownUtils } from '../../../lib/rundown' import { PartUi } from '../../SegmentTimeline/SegmentTimelineContainer' -import { calculatePartInstanceExpectedDurationWithPreroll } from '@sofie-automation/corelib/dist/playout/timings' +import { calculatePartInstanceExpectedDurationWithTransition } from '@sofie-automation/corelib/dist/playout/timings' import { SegmentId } from '@sofie-automation/corelib/dist/dataModel/Ids' interface ISegmentDurationProps { @@ -45,7 +45,7 @@ export const SegmentDuration = withTiming()(function budget += part.instance.orphaned || part.instance.part.untimed ? 0 - : calculatePartInstanceExpectedDurationWithPreroll(part.instance) || 0 + : calculatePartInstanceExpectedDurationWithTransition(part.instance) || 0 }) } props.parts.forEach((part) => { diff --git a/meteor/client/ui/SegmentTimeline/Renderers/MicSourceRenderer.tsx b/meteor/client/ui/SegmentTimeline/Renderers/MicSourceRenderer.tsx index fc9c84ad06..527f2da2df 100644 --- a/meteor/client/ui/SegmentTimeline/Renderers/MicSourceRenderer.tsx +++ b/meteor/client/ui/SegmentTimeline/Renderers/MicSourceRenderer.tsx @@ -8,7 +8,7 @@ import * as _ from 'underscore' import { getElementWidth } from '../../../utils/dimensions' import { MicFloatingInspector } from '../../FloatingInspectors/MicFloatingInspector' -import { calculatePartInstanceExpectedDurationWithPreroll } from '@sofie-automation/corelib/dist/playout/timings' +import { calculatePartInstanceExpectedDurationWithTransition } from '@sofie-automation/corelib/dist/playout/timings' import { unprotectString } from '../../../../lib/lib' import { IFloatingInspectorPosition } from '../../FloatingInspectors/IFloatingInspectorPosition' import { logger } from '../../../../lib/logging' @@ -135,8 +135,8 @@ export const MicSourceRenderer = withTranslation()( _forceSizingRecheck = true } - const expectedDuration = calculatePartInstanceExpectedDurationWithPreroll(this.props.part.instance) - const prevExpectedDuration = calculatePartInstanceExpectedDurationWithPreroll(prevProps.part.instance) + const expectedDuration = calculatePartInstanceExpectedDurationWithTransition(this.props.part.instance) + const prevExpectedDuration = calculatePartInstanceExpectedDurationWithTransition(prevProps.part.instance) if ( !_forceSizingRecheck && diff --git a/meteor/client/ui/SegmentTimeline/SegmentTimeline.tsx b/meteor/client/ui/SegmentTimeline/SegmentTimeline.tsx index a8432be189..230c07bcda 100644 --- a/meteor/client/ui/SegmentTimeline/SegmentTimeline.tsx +++ b/meteor/client/ui/SegmentTimeline/SegmentTimeline.tsx @@ -227,7 +227,7 @@ export const BUDGET_GAP_PART = { gap: true, title: 'gap', invalid: true, - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, }), pieces: [], renderedDuration: 0, diff --git a/meteor/client/ui/Shelf/DashboardPieceButton.tsx b/meteor/client/ui/Shelf/DashboardPieceButton.tsx index 9fac4071df..66493237c9 100644 --- a/meteor/client/ui/Shelf/DashboardPieceButton.tsx +++ b/meteor/client/ui/Shelf/DashboardPieceButton.tsx @@ -68,6 +68,7 @@ export class DashboardPieceButtonBase extends MeteorReactComponent< IState > { private element: HTMLDivElement | null = null + /** positionAndSize needs to be in document coordinate space */ private positionAndSize: { top: number left: number @@ -206,8 +207,8 @@ export class DashboardPieceButtonBase extends MeteorReactComponent< if (this.element) { const { top, left, width, height } = this.element.getBoundingClientRect() this.positionAndSize = { - top, - left, + top: window.scrollY + top, + left: window.scrollX + left, width, height, } @@ -224,8 +225,8 @@ export class DashboardPieceButtonBase extends MeteorReactComponent< if (this.element) { const { top, left, width, height } = this.element.getBoundingClientRect() this.positionAndSize = { - top, - left, + top: window.scrollY + top, + left: window.scrollX + left, width, height, } diff --git a/meteor/package.json b/meteor/package.json index 4c89f893c4..5d52683db4 100644 --- a/meteor/package.json +++ b/meteor/package.json @@ -1,6 +1,6 @@ { "name": "automation-core", - "version": "1.50.2", + "version": "1.50.4", "private": true, "engines": { "node": ">=14.19.1" diff --git a/meteor/server/__tests__/cronjobs.test.ts b/meteor/server/__tests__/cronjobs.test.ts index 90288cff7b..4d6798e60e 100644 --- a/meteor/server/__tests__/cronjobs.test.ts +++ b/meteor/server/__tests__/cronjobs.test.ts @@ -220,7 +220,7 @@ describe('cronjobs', () => { segmentId: segment0._id, externalId: '', title: '', - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, } await Parts.mutableCollection.insertAsync(part0) const part1: DBPart = { @@ -230,7 +230,7 @@ describe('cronjobs', () => { segmentId: getRandomId(), // non-existent externalId: '', title: '', - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, } await Parts.mutableCollection.insertAsync(part1) diff --git a/meteor/server/api/__tests__/peripheralDevice.test.ts b/meteor/server/api/__tests__/peripheralDevice.test.ts index 81d3d6dee4..d1ef86c0d1 100644 --- a/meteor/server/api/__tests__/peripheralDevice.test.ts +++ b/meteor/server/api/__tests__/peripheralDevice.test.ts @@ -134,7 +134,7 @@ describe('test peripheralDevice general API methods', () => { segmentId: segmentID, rundownId: rundownID, title: 'Part 000', - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, }) await Pieces.mutableCollection.insertAsync({ _id: protectString('piece0001'), @@ -161,7 +161,7 @@ describe('test peripheralDevice general API methods', () => { segmentId: segmentID, rundownId: rundownID, title: 'Part 001', - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, }) await Segments.mutableCollection.insertAsync({ _id: protectString('segment1'), diff --git a/meteor/server/api/deviceTriggers/RundownContentObserver.ts b/meteor/server/api/deviceTriggers/RundownContentObserver.ts index 4958d2df03..f87c730f3a 100644 --- a/meteor/server/api/deviceTriggers/RundownContentObserver.ts +++ b/meteor/server/api/deviceTriggers/RundownContentObserver.ts @@ -1,10 +1,5 @@ import { Meteor } from 'meteor/meteor' -import { - RundownId, - RundownPlaylistActivationId, - RundownPlaylistId, - ShowStyleBaseId, -} from '@sofie-automation/corelib/dist/dataModel/Ids' +import { RundownId, RundownPlaylistId, ShowStyleBaseId } from '@sofie-automation/corelib/dist/dataModel/Ids' import { PartInstances, Parts, @@ -44,10 +39,9 @@ export class RundownContentObserver { rundownPlaylistId: RundownPlaylistId, showStyleBaseId: ShowStyleBaseId, rundownIds: RundownId[], - activationId: RundownPlaylistActivationId, onChanged: ChangedHandler ) { - logger.silly(`Creating RundownContentObserver for playlist "${rundownPlaylistId}" activation "${activationId}"`) + logger.silly(`Creating RundownContentObserver for playlist "${rundownPlaylistId}"`) const { cache, cancel: cancelCache } = createReactiveContentCache(() => { this.#cleanup = onChanged(cache) if (this.#disposed) this.#cleanup() @@ -93,7 +87,9 @@ export class RundownContentObserver { ), PartInstances.observe( { - playlistActivationId: activationId, + rundownId: { + $in: rundownIds, + }, reset: { $ne: true, }, diff --git a/meteor/server/api/deviceTriggers/StudioObserver.ts b/meteor/server/api/deviceTriggers/StudioObserver.ts index d49dcf43a6..15029b43d7 100644 --- a/meteor/server/api/deviceTriggers/StudioObserver.ts +++ b/meteor/server/api/deviceTriggers/StudioObserver.ts @@ -183,26 +183,19 @@ export class StudioObserver extends EventEmitter { this.#rundownsLiveQuery = undefined const activePlaylistId = this.activePlaylistId - const activationId = this.activationId this.showStyleBaseId = showStyleBaseId let cleanupChanges: (() => void) | undefined = undefined this.#rundownsLiveQuery = new RundownsObserver(activePlaylistId, (rundownIds) => { logger.silly(`Creating new RundownContentObserver`) - const obs1 = new RundownContentObserver( - activePlaylistId, - showStyleBaseId, - rundownIds, - activationId, - (cache) => { - cleanupChanges = this.#changed(showStyleBaseId, cache) - - return () => { - void 0 - } + const obs1 = new RundownContentObserver(activePlaylistId, showStyleBaseId, rundownIds, (cache) => { + cleanupChanges = this.#changed(showStyleBaseId, cache) + + return () => { + void 0 } - ) + }) return () => { obs1.stop() diff --git a/meteor/server/migration/1_39_0.ts b/meteor/server/migration/1_39_0.ts index 6f754eea0d..382063cdd9 100644 --- a/meteor/server/migration/1_39_0.ts +++ b/meteor/server/migration/1_39_0.ts @@ -4,19 +4,19 @@ import { addMigrationSteps } from './databaseMigration' // Release 39 export const addSteps = addMigrationSteps('1.39.0', [ { - id: `Parts.expectedDurationWithPreroll`, + id: `Parts.expectedDurationWithTransition`, canBeRunAutomatically: true, validate: async () => { const objects = await Parts.countDocuments({ expectedDuration: { $exists: true, }, - expectedDurationWithPreroll: { + expectedDurationWithTransition: { $exists: false, }, }) if (objects > 0) { - return `timing is expectedDurationWithPreroll on ${objects} objects` + return `timing is expectedDurationWithTransition on ${objects} objects` } return false }, @@ -25,14 +25,14 @@ export const addSteps = addMigrationSteps('1.39.0', [ expectedDuration: { $exists: true, }, - expectedDurationWithPreroll: { + expectedDurationWithTransition: { $exists: false, }, }) for (const obj of objects) { await Parts.mutableCollection.updateAsync(obj._id, { $set: { - expectedDurationWithPreroll: obj.expectedDuration, + expectedDurationWithTransition: obj.expectedDuration, }, }) } diff --git a/meteor/server/migration/1_50_0.ts b/meteor/server/migration/1_50_0.ts index 6ad55eeeb2..2de2e5c44e 100644 --- a/meteor/server/migration/1_50_0.ts +++ b/meteor/server/migration/1_50_0.ts @@ -3,6 +3,8 @@ import { AdLibActions, AdLibPieces, ExpectedPackages, + PartInstances, + Parts, PeripheralDevices, Pieces, RundownPlaylists, @@ -928,6 +930,71 @@ export const addSteps = addMigrationSteps('1.50.0', [ } }, }, + + { + id: `Part rename 'expectedDurationWithPreroll' to 'expectedDurationWithTransition'`, + canBeRunAutomatically: true, + validate: async () => { + const objectCount = await Parts.countDocuments({ + expectedDurationWithTransition: { $exists: false }, + expectedDurationWithPreroll: { $exists: true }, + }) + + if (objectCount) { + return `object needs to be updated` + } + return false + }, + migrate: async () => { + const objects = await Parts.findFetchAsync({ + expectedDurationWithTransition: { $exists: false }, + expectedDurationWithPreroll: { $exists: true }, + }) + + for (const part of objects) { + await Parts.mutableCollection.updateAsync(part._id, { + $set: { + expectedDurationWithTransition: (part as any).expectedDurationWithPreroll, + }, + $unset: { + expectedDurationWithPreroll: 1, + }, + }) + } + }, + }, + { + id: `PartInstance rename 'part.expectedDurationWithPreroll' to 'part.expectedDurationWithTransition'`, + canBeRunAutomatically: true, + validate: async () => { + const objectCount = await PartInstances.countDocuments({ + 'part.expectedDurationWithTransition': { $exists: false }, + 'part.expectedDurationWithPreroll': { $exists: true }, + }) + + if (objectCount) { + return `object needs to be updated` + } + return false + }, + migrate: async () => { + const objects = await PartInstances.findFetchAsync({ + 'part.expectedDurationWithTransition': { $exists: false }, + 'part.expectedDurationWithPreroll': { $exists: true }, + }) + + for (const partInstance of objects) { + await PartInstances.mutableCollection.updateAsync(partInstance._id, { + $set: { + 'part.expectedDurationWithTransition': (partInstance.part as any).expectedDurationWithPreroll, + }, + $unset: { + 'part.expectedDurationWithPreroll': 1, + }, + }) + } + }, + }, ]) function updatePlayoutDeviceTypeInOverride(op: SomeObjectOverrideOp): ObjectOverrideSetOp | undefined { diff --git a/meteor/yarn.lock b/meteor/yarn.lock index dc218fdee4..a2671dd8e1 100644 --- a/meteor/yarn.lock +++ b/meteor/yarn.lock @@ -2082,7 +2082,7 @@ __metadata: version: 0.0.0-use.local resolution: "@sofie-automation/blueprints-integration@portal:../packages/blueprints-integration::locator=automation-core%40workspace%3A." dependencies: - "@sofie-automation/shared-lib": 1.50.2 + "@sofie-automation/shared-lib": 1.50.4 tslib: ^2.4.0 type-fest: ^2.19.0 languageName: node @@ -2123,8 +2123,8 @@ __metadata: version: 0.0.0-use.local resolution: "@sofie-automation/corelib@portal:../packages/corelib::locator=automation-core%40workspace%3A." dependencies: - "@sofie-automation/blueprints-integration": 1.50.2 - "@sofie-automation/shared-lib": 1.50.2 + "@sofie-automation/blueprints-integration": 1.50.4 + "@sofie-automation/shared-lib": 1.50.4 fast-clone: ^1.5.13 i18next: ^21.9.1 influx: ^5.9.3 @@ -2155,9 +2155,9 @@ __metadata: resolution: "@sofie-automation/job-worker@portal:../packages/job-worker::locator=automation-core%40workspace%3A." dependencies: "@slack/webhook": ^6.1.0 - "@sofie-automation/blueprints-integration": 1.50.2 - "@sofie-automation/corelib": 1.50.2 - "@sofie-automation/shared-lib": 1.50.2 + "@sofie-automation/blueprints-integration": 1.50.4 + "@sofie-automation/corelib": 1.50.4 + "@sofie-automation/shared-lib": 1.50.4 amqplib: ^0.10.3 deepmerge: ^4.3.1 elastic-apm-node: ^3.43.0 diff --git a/packages/blueprints-integration/CHANGELOG.md b/packages/blueprints-integration/CHANGELOG.md index 07588e73e0..fd77fa00dc 100644 --- a/packages/blueprints-integration/CHANGELOG.md +++ b/packages/blueprints-integration/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.50.4](https://github.com/nrkno/tv-automation-server-core/compare/v1.50.3...v1.50.4) (2024-08-08) + +**Note:** Version bump only for package @sofie-automation/blueprints-integration + + + + + +## [1.50.3](https://github.com/nrkno/tv-automation-server-core/compare/v1.50.2...v1.50.3) (2024-06-24) + +**Note:** Version bump only for package @sofie-automation/blueprints-integration + + + + + ## [1.50.2](https://github.com/nrkno/tv-automation-server-core/compare/v1.49.6...v1.50.2) (2024-05-15) diff --git a/packages/blueprints-integration/package.json b/packages/blueprints-integration/package.json index d92fd944e9..fde026582b 100644 --- a/packages/blueprints-integration/package.json +++ b/packages/blueprints-integration/package.json @@ -1,6 +1,6 @@ { "name": "@sofie-automation/blueprints-integration", - "version": "1.50.2", + "version": "1.50.4", "description": "Library to define the interaction between core and the blueprints.", "main": "dist/index.js", "typings": "dist/index.d.ts", @@ -38,7 +38,7 @@ "/LICENSE" ], "dependencies": { - "@sofie-automation/shared-lib": "1.50.2", + "@sofie-automation/shared-lib": "1.50.4", "tslib": "^2.4.0", "type-fest": "^2.19.0" }, diff --git a/packages/corelib/package.json b/packages/corelib/package.json index e311a8867c..0d26799e42 100644 --- a/packages/corelib/package.json +++ b/packages/corelib/package.json @@ -1,6 +1,6 @@ { "name": "@sofie-automation/corelib", - "version": "1.50.2", + "version": "1.50.4", "private": true, "description": "Internal library for some types shared by core and workers", "main": "dist/index.js", @@ -39,8 +39,8 @@ "/LICENSE" ], "dependencies": { - "@sofie-automation/blueprints-integration": "1.50.2", - "@sofie-automation/shared-lib": "1.50.2", + "@sofie-automation/blueprints-integration": "1.50.4", + "@sofie-automation/shared-lib": "1.50.4", "fast-clone": "^1.5.13", "i18next": "^21.9.1", "influx": "^5.9.3", diff --git a/packages/corelib/src/dataModel/Part.ts b/packages/corelib/src/dataModel/Part.ts index 62c722a7dc..e67642fba1 100644 --- a/packages/corelib/src/dataModel/Part.ts +++ b/packages/corelib/src/dataModel/Part.ts @@ -33,8 +33,8 @@ export interface DBPart extends ProtectedStringProperties = _.sortBy( - Array.from(groupByToMapFunc(pieces, (p) => p.piece.enable.start).entries()).map(([k, v]) => + Array.from(groupByToMapFunc(pieces, (p) => getPieceStartTimeWithinPart(p)).entries()).map(([k, v]) => literal<[number | 'now', PieceInstance[]]>([k === 'now' ? 'now' : Number(k), v]) ), ([k]) => (k === 'now' ? nowInPart : k) @@ -120,7 +128,7 @@ function updateWithNewPieces( if (newPiece) { const activePiece = activePieces[key] if (activePiece) { - activePiece.resolvedEndCap = getPieceStartTime(newPiecesStart, newPiece) + activePiece.resolvedEndCap = getPieceStartTimeAsReference(newPiecesStart, newPiece) } // track the new piece activePieces[key] = newPiece @@ -145,7 +153,7 @@ function updateWithNewPieces( (newPiecesStart !== 0 || isCandidateBetterToBeContinued(activePieces.other, newPiece)) ) { // These modes should stop the 'other' when they start if not hidden behind a higher priority onEnd - activePieces.other.resolvedEndCap = getPieceStartTime(newPiecesStart, newPiece) + activePieces.other.resolvedEndCap = getPieceStartTimeAsReference(newPiecesStart, newPiece) activePieces.other = undefined } } diff --git a/packages/corelib/src/playout/timings.ts b/packages/corelib/src/playout/timings.ts index cd04fb0f9a..0e35d3f37f 100644 --- a/packages/corelib/src/playout/timings.ts +++ b/packages/corelib/src/playout/timings.ts @@ -156,15 +156,12 @@ export function getPartTimingsOrDefaults( } } -function calculateExpectedDurationWithPreroll(rawDuration: number, timings: PartCalculatedTimings): number { - // toPartDelay is accounted for twice, because it is added to `fromPartRemaining` when the `fromPartRemaining` value is calculated. - // If we only do `timings.toPartDelay - timings.fromPartRemaining`, the the values cancel out and the 'from' Part will - // count overtime when prerolling into the 'to' Part. - // As a result, the 'to' Part will have a countdown which includes its own preroll. - return Math.max(0, rawDuration + timings.toPartDelay - (timings.fromPartRemaining - timings.toPartDelay)) +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)) } -export function calculatePartExpectedDurationWithPreroll( +export function calculatePartExpectedDurationWithTransition( part: DBPart, pieces: PieceInstancePiece[] ): number | undefined { @@ -172,23 +169,22 @@ export function calculatePartExpectedDurationWithPreroll( const timings = calculatePartTimings(undefined, {}, [], part, pieces) - return calculateExpectedDurationWithPreroll(part.expectedDuration, timings) + return calculateExpectedDurationWithTransition(part.expectedDuration, timings) } -export function calculatePartInstanceExpectedDurationWithPreroll( +export function calculatePartInstanceExpectedDurationWithTransition( partInstance: Pick // pieces: CalculateTimingsPiece[] ): number | undefined { if (partInstance.part.expectedDuration === undefined) return undefined if (partInstance.partPlayoutTimings) { - return calculateExpectedDurationWithPreroll(partInstance.part.expectedDuration, partInstance.partPlayoutTimings) - } else { - const timings = calculatePartTimings(undefined, {}, [], partInstance.part, []) // TODO: Piece timings should be taken into account - - return calculateExpectedDurationWithPreroll( - partInstance.part.expectedDurationWithPreroll ?? partInstance.part.expectedDuration, - timings + // The timings needed are known, we can ensure that live data is used + return calculateExpectedDurationWithTransition( + partInstance.part.expectedDuration, + partInstance.partPlayoutTimings ) + } else { + return partInstance.part.expectedDurationWithTransition } } diff --git a/packages/documentation/package.json b/packages/documentation/package.json index 29aca9da91..d95532143e 100644 --- a/packages/documentation/package.json +++ b/packages/documentation/package.json @@ -1,6 +1,6 @@ { "name": "sofie-documentation", - "version": "1.50.2", + "version": "1.50.4", "private": true, "scripts": { "docusaurus": "docusaurus", diff --git a/packages/job-worker/package.json b/packages/job-worker/package.json index 171db149a4..5ce5041061 100644 --- a/packages/job-worker/package.json +++ b/packages/job-worker/package.json @@ -1,6 +1,6 @@ { "name": "@sofie-automation/job-worker", - "version": "1.50.2", + "version": "1.50.4", "description": "Worker for things", "main": "dist/index.js", "license": "MIT", @@ -41,9 +41,9 @@ ], "dependencies": { "@slack/webhook": "^6.1.0", - "@sofie-automation/blueprints-integration": "1.50.2", - "@sofie-automation/corelib": "1.50.2", - "@sofie-automation/shared-lib": "1.50.2", + "@sofie-automation/blueprints-integration": "1.50.4", + "@sofie-automation/corelib": "1.50.4", + "@sofie-automation/shared-lib": "1.50.4", "amqplib": "^0.10.3", "deepmerge": "^4.3.1", "elastic-apm-node": "^3.43.0", diff --git a/packages/job-worker/src/__mocks__/defaultCollectionObjects.ts b/packages/job-worker/src/__mocks__/defaultCollectionObjects.ts index 6931adad17..bad4e09c26 100644 --- a/packages/job-worker/src/__mocks__/defaultCollectionObjects.ts +++ b/packages/job-worker/src/__mocks__/defaultCollectionObjects.ts @@ -135,7 +135,7 @@ export function defaultPart(_id: PartId, rundownId: RundownId, segmentId: Segmen _rank: 0, externalId: unprotectString(_id), title: 'Default Part', - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, } } export function defaultPiece(_id: PieceId, rundownId: RundownId, segmentId: SegmentId, partId: PartId): Piece { diff --git a/packages/job-worker/src/__mocks__/presetCollections.ts b/packages/job-worker/src/__mocks__/presetCollections.ts index b8e1aeb881..3f76d2a0e2 100644 --- a/packages/job-worker/src/__mocks__/presetCollections.ts +++ b/packages/job-worker/src/__mocks__/presetCollections.ts @@ -232,7 +232,7 @@ export async function setupDefaultRundown( _rank: 0, externalId: 'MOCK_PART_0_0', title: 'Part 0 0', - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, } await context.mockCollections.Parts.insertOne(part00) @@ -300,7 +300,7 @@ export async function setupDefaultRundown( _rank: 1, externalId: 'MOCK_PART_0_1', title: 'Part 0 1', - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, } await context.mockCollections.Parts.insertOne(part01) @@ -340,7 +340,7 @@ export async function setupDefaultRundown( _rank: 0, externalId: 'MOCK_PART_1_0', title: 'Part 1 0', - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, } await context.mockCollections.Parts.insertOne(part10) @@ -351,7 +351,7 @@ export async function setupDefaultRundown( _rank: 1, externalId: 'MOCK_PART_1_1', title: 'Part 1 1', - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, } await context.mockCollections.Parts.insertOne(part11) @@ -362,7 +362,7 @@ export async function setupDefaultRundown( _rank: 2, externalId: 'MOCK_PART_1_2', title: 'Part 1 2', - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, } await context.mockCollections.Parts.insertOne(part12) diff --git a/packages/job-worker/src/__tests__/rundown-updatePartInstanceRanks.test.ts b/packages/job-worker/src/__tests__/rundown-updatePartInstanceRanks.test.ts index 5064c6a68c..b42a40b68d 100644 --- a/packages/job-worker/src/__tests__/rundown-updatePartInstanceRanks.test.ts +++ b/packages/job-worker/src/__tests__/rundown-updatePartInstanceRanks.test.ts @@ -53,7 +53,7 @@ describe('updatePartInstanceRanks', () => { segmentId, externalId: id, title: id, - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, }) } @@ -264,7 +264,7 @@ describe('updatePartInstanceRanks', () => { segmentId, externalId: adlibId, title: adlibId, - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, }, 'adlib-part' ) @@ -376,7 +376,7 @@ describe('updatePartInstanceRanks', () => { segmentId, externalId: adlibId, title: adlibId, - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, }, 'adlib-part' ) @@ -419,7 +419,7 @@ describe('updatePartInstanceRanks', () => { segmentId, externalId: adlibId0, title: adlibId0, - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, }, 'deleted' ) @@ -432,7 +432,7 @@ describe('updatePartInstanceRanks', () => { segmentId, externalId: adlibId1, title: adlibId1, - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, }, 'adlib-part' ) diff --git a/packages/job-worker/src/blueprints/context/adlibActions.ts b/packages/job-worker/src/blueprints/context/adlibActions.ts index ae35a16786..8c33c5f9a3 100644 --- a/packages/job-worker/src/blueprints/context/adlibActions.ts +++ b/packages/job-worker/src/blueprints/context/adlibActions.ts @@ -464,7 +464,7 @@ export class ActionExecutionContext extends ShowStyleUserContext implements IAct invalid: false, invalidReason: undefined, floated: false, - expectedDurationWithPreroll: undefined, // Filled in later + expectedDurationWithTransition: undefined, // Filled in later }, } diff --git a/packages/job-worker/src/cache/__tests__/DatabaseCaches.test.ts b/packages/job-worker/src/cache/__tests__/DatabaseCaches.test.ts index 26bd7389d1..71df9fd10d 100644 --- a/packages/job-worker/src/cache/__tests__/DatabaseCaches.test.ts +++ b/packages/job-worker/src/cache/__tests__/DatabaseCaches.test.ts @@ -33,7 +33,7 @@ describe('DatabaseCaches', () => { rundownId: protectString(''), segmentId: protectString(''), externalId: 'test', - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, }) cache.Parts.updateOne(id, (p) => { p.title = 'Test2' @@ -145,7 +145,7 @@ describe('DatabaseCaches', () => { rundownId: protectString(''), segmentId: protectString(''), externalId: 'test', - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, }) const deferFcn0 = jest.fn(async () => { await expect(context.directCollections.Parts.findOne(id)).resolves.toBeFalsy() @@ -277,7 +277,7 @@ describe('DatabaseCaches', () => { rundownId: protectString(''), segmentId: protectString(''), externalId: 'test', - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, }) cache.Parts.updateOne(id, (p) => { p.title = 'insertthenupdate' diff --git a/packages/job-worker/src/ingest/__tests__/ingest.test.ts b/packages/job-worker/src/ingest/__tests__/ingest.test.ts index d46de0e0f1..7011bbbc12 100644 --- a/packages/job-worker/src/ingest/__tests__/ingest.test.ts +++ b/packages/job-worker/src/ingest/__tests__/ingest.test.ts @@ -2028,7 +2028,7 @@ describe('Test ingest actions for rundowns and segments', () => { segmentId: currentPartInstance.segmentId, externalId: `${partInstanceId}_externalId`, title: 'New part', - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, }, } diff --git a/packages/job-worker/src/ingest/__tests__/updateNext.test.ts b/packages/job-worker/src/ingest/__tests__/updateNext.test.ts index 300eb75457..619dd8f9cd 100644 --- a/packages/job-worker/src/ingest/__tests__/updateNext.test.ts +++ b/packages/job-worker/src/ingest/__tests__/updateNext.test.ts @@ -114,7 +114,7 @@ async function createMockRO(context: MockJobContext): Promise { segmentId: protectString('mock_segment1'), externalId: 'p1', title: 'Part 1', - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, }), }), literal({ @@ -132,7 +132,7 @@ async function createMockRO(context: MockJobContext): Promise { segmentId: protectString('mock_segment1'), externalId: 'p2', title: 'Part 2', - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, }), }), literal({ @@ -150,7 +150,7 @@ async function createMockRO(context: MockJobContext): Promise { segmentId: protectString('mock_segment1'), externalId: 'p3', title: 'Part 3', - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, }), }), // Segment 2 @@ -169,7 +169,7 @@ async function createMockRO(context: MockJobContext): Promise { segmentId: protectString('mock_segment2'), externalId: 'p4', title: 'Part 4', - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, }), }), literal({ @@ -187,7 +187,7 @@ async function createMockRO(context: MockJobContext): Promise { segmentId: protectString('mock_segment2'), externalId: 'p5', title: 'Part 5', - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, }), }), // Segment 3 @@ -206,7 +206,7 @@ async function createMockRO(context: MockJobContext): Promise { segmentId: protectString('mock_segment3'), externalId: 'p6', title: 'Part 6', - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, }), }), // Segment 4 @@ -225,7 +225,7 @@ async function createMockRO(context: MockJobContext): Promise { segmentId: protectString('mock_segment4'), externalId: 'p7', title: 'Part 7', - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, }), }), literal({ @@ -244,7 +244,7 @@ async function createMockRO(context: MockJobContext): Promise { externalId: 'p8', title: 'Part 8', floated: true, - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, }), }), literal({ @@ -262,7 +262,7 @@ async function createMockRO(context: MockJobContext): Promise { segmentId: protectString('mock_segment4'), externalId: 'p9', title: 'Part 9', - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, }), }), @@ -281,7 +281,7 @@ async function createMockRO(context: MockJobContext): Promise { segmentId: protectString('mock_segment4'), externalId: 'o1', title: 'Orphan 1', - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, }), orphaned: 'adlib-part', }), @@ -344,7 +344,7 @@ describe('ensureNextPartIsValid', () => { }) } async function ensureNextPartIsValid() { - await runJobWithPlayoutCache(context, { playlistId: rundownPlaylistId }, null, async (cache) => + return runJobWithPlayoutCache(context, { playlistId: rundownPlaylistId }, null, async (cache) => ensureNextPartIsValidRaw(context, cache) ) } @@ -352,7 +352,7 @@ describe('ensureNextPartIsValid', () => { test('Start with null', async () => { await resetPartIds(null, null) - await ensureNextPartIsValid() + await expect(ensureNextPartIsValid()).resolves.toBeTruthy() expect(setNextPartMock).toHaveBeenCalledTimes(1) expect(setNextPartMock).toHaveBeenCalledWith( @@ -365,7 +365,7 @@ describe('ensureNextPartIsValid', () => { test('Missing next PartInstance', async () => { await resetPartIds('mock_part_instance3', 'fake_part') - await ensureNextPartIsValid() + await expect(ensureNextPartIsValid()).resolves.toBeTruthy() expect(setNextPartMock).toHaveBeenCalledTimes(1) expect(setNextPartMock).toHaveBeenCalledWith( @@ -380,14 +380,14 @@ describe('ensureNextPartIsValid', () => { test('Missing current PartInstance with valid next', async () => { await resetPartIds('fake_part', 'mock_part_instance4') - await ensureNextPartIsValid() + await expect(ensureNextPartIsValid()).resolves.toBeFalsy() expect(setNextPartMock).toHaveBeenCalledTimes(0) }) test('Missing current and next PartInstance', async () => { await resetPartIds('fake_part', 'not_real_either') - await ensureNextPartIsValid() + await expect(ensureNextPartIsValid()).resolves.toBeTruthy() expect(setNextPartMock).toHaveBeenCalledTimes(1) expect(setNextPartMock).toHaveBeenCalledWith( @@ -400,21 +400,21 @@ describe('ensureNextPartIsValid', () => { test('Ensure correct PartInstance doesnt change', async () => { await resetPartIds('mock_part_instance3', 'mock_part_instance4') - await ensureNextPartIsValid() + await expect(ensureNextPartIsValid()).resolves.toBeFalsy() expect(setNextPartMock).not.toHaveBeenCalled() }) test('Ensure manual PartInstance doesnt change', async () => { await resetPartIds('mock_part_instance3', 'mock_part_instance5', true) - await ensureNextPartIsValid() + await expect(ensureNextPartIsValid()).resolves.toBeFalsy() expect(setNextPartMock).not.toHaveBeenCalled() }) test('Ensure non-manual PartInstance does change', async () => { await resetPartIds('mock_part_instance3', 'mock_part_instance5', false) - await ensureNextPartIsValid() + await expect(ensureNextPartIsValid()).resolves.toBeTruthy() expect(setNextPartMock).toHaveBeenCalledTimes(1) expect(setNextPartMock).toHaveBeenCalledWith( @@ -427,7 +427,7 @@ describe('ensureNextPartIsValid', () => { test('Ensure manual but missing PartInstance does change', async () => { await resetPartIds('mock_part_instance3', 'fake_part', true) - await ensureNextPartIsValid() + await expect(ensureNextPartIsValid()).resolves.toBeTruthy() expect(setNextPartMock).toHaveBeenCalledTimes(1) expect(setNextPartMock).toHaveBeenCalledWith( @@ -440,7 +440,7 @@ describe('ensureNextPartIsValid', () => { test('Ensure manual but floated PartInstance does change', async () => { await resetPartIds('mock_part_instance7', 'mock_part_instance8', true) - await ensureNextPartIsValid() + await expect(ensureNextPartIsValid()).resolves.toBeTruthy() expect(setNextPartMock).toHaveBeenCalledTimes(1) expect(setNextPartMock).toHaveBeenCalledWith( @@ -453,7 +453,7 @@ describe('ensureNextPartIsValid', () => { test('Ensure floated PartInstance does change', async () => { await resetPartIds('mock_part_instance7', 'mock_part_instance8', false) - await ensureNextPartIsValid() + await expect(ensureNextPartIsValid()).resolves.toBeTruthy() expect(setNextPartMock).toHaveBeenCalledTimes(1) expect(setNextPartMock).toHaveBeenCalledWith( @@ -482,7 +482,7 @@ describe('ensureNextPartIsValid', () => { segmentId: protectString('mock_segment1'), externalId: 'o1', title: 'Orphan 1', - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, }), orphaned: 'deleted', }) @@ -490,7 +490,7 @@ describe('ensureNextPartIsValid', () => { await resetPartIds(null, instanceId, false) - await ensureNextPartIsValid() + await expect(ensureNextPartIsValid()).resolves.toBeTruthy() expect(setNextPartMock).toHaveBeenCalledTimes(1) expect(setNextPartMock).toHaveBeenCalledWith( @@ -519,7 +519,7 @@ describe('ensureNextPartIsValid', () => { segmentId: protectString('mock_segment1'), externalId: 'o1', title: 'Orphan 1', - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, }), orphaned: 'deleted', }) @@ -527,7 +527,7 @@ describe('ensureNextPartIsValid', () => { await resetPartIds(null, instanceId, true) - await ensureNextPartIsValid() + await expect(ensureNextPartIsValid()).resolves.toBeTruthy() expect(setNextPartMock).toHaveBeenCalledTimes(1) expect(setNextPartMock).toHaveBeenCalledWith( @@ -547,7 +547,7 @@ describe('ensureNextPartIsValid', () => { segmentId: protectString('mock_segment1'), externalId: 'o1', title: 'Orphan 1', - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, }) await context.mockCollections.PartInstances.insertOne( literal({ @@ -568,7 +568,7 @@ describe('ensureNextPartIsValid', () => { await resetPartIds('mock_part_instance1', instanceId, false) - await ensureNextPartIsValid() + await expect(ensureNextPartIsValid()).resolves.toBeFalsy() expect(setNextPartMock).toHaveBeenCalledTimes(0) }) @@ -582,7 +582,7 @@ describe('ensureNextPartIsValid', () => { segmentId: protectString('mock_segment4'), externalId: 'tmp1', title: 'Tmp Part 1', - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, }) await context.mockCollections.PartInstances.insertOne( literal({ @@ -601,7 +601,7 @@ describe('ensureNextPartIsValid', () => { try { // make sure it finds the part we expect await resetPartIds('mock_part_instance9', null, false) - await ensureNextPartIsValid() + await expect(ensureNextPartIsValid()).resolves.toBeTruthy() expect(setNextPartMock).toHaveBeenCalledTimes(1) expect(setNextPartMock).toHaveBeenCalledWith( @@ -619,7 +619,7 @@ describe('ensureNextPartIsValid', () => { await context.mockCollections.Parts.remove(part._id) // make sure the next part gets cleared - await ensureNextPartIsValid() + await expect(ensureNextPartIsValid()).resolves.toBeTruthy() expect(setNextPartMock).toHaveBeenCalledTimes(1) expect(setNextPartMock).toHaveBeenCalledWith( @@ -634,4 +634,26 @@ describe('ensureNextPartIsValid', () => { await context.mockCollections.Parts.remove(part._id) } }) + + test('Current part is last in rundown, next is missing', async () => { + await resetPartIds('mock_part_instance9', 'fake_part_instance', false) + + await expect(ensureNextPartIsValid()).resolves.toBeTruthy() + + expect(setNextPartMock).toHaveBeenCalledTimes(1) + expect(setNextPartMock).toHaveBeenCalledWith( + expect.objectContaining({}), + expect.objectContaining({ PlaylistId: rundownPlaylistId }), + null, + false + ) + }) + + test('Current part is last in rundown, no-op to update', async () => { + await resetPartIds('mock_part_instance9', null, false) + + await expect(ensureNextPartIsValid()).resolves.toBeFalsy() + + expect(setNextPartMock).toHaveBeenCalledTimes(0) + }) }) diff --git a/packages/job-worker/src/ingest/commit.ts b/packages/job-worker/src/ingest/commit.ts index 451dcdf7fb..90137d5d05 100644 --- a/packages/job-worker/src/ingest/commit.ts +++ b/packages/job-worker/src/ingest/commit.ts @@ -241,6 +241,7 @@ export async function CommitIngestOperation( await pSaveIngest // do some final playout checks, which may load back some Parts data + // Note: This should trigger a timeline update, one is already queued in the `deferAfterSave` above await ensureNextPartIsValid(context, playoutCache) // save the final playout changes @@ -510,9 +511,9 @@ export async function updatePlayoutAfterChangingRundownInPlaylist( ) } - await ensureNextPartIsValid(context, playoutCache) + const shouldUpdateTimeline = await ensureNextPartIsValid(context, playoutCache) - if (playoutCache.Playlist.doc.activationId) { + if (playoutCache.Playlist.doc.activationId || shouldUpdateTimeline) { triggerUpdateTimelineAfterIngestData(context, playoutCache.PlaylistId) } }) diff --git a/packages/job-worker/src/ingest/generationSegment.ts b/packages/job-worker/src/ingest/generationSegment.ts index 6520d88800..67e49a40ef 100644 --- a/packages/job-worker/src/ingest/generationSegment.ts +++ b/packages/job-worker/src/ingest/generationSegment.ts @@ -18,7 +18,7 @@ import { getSegmentId, getPartId, getRundown, canSegmentBeUpdated } from './lib' import { JobContext } from '../jobs' import { CommitIngestData } from './lock' import { BlueprintResultSegment, NoteSeverity } from '@sofie-automation/blueprints-integration' -import { calculatePartExpectedDurationWithPreroll } from '@sofie-automation/corelib/dist/playout/timings' +import { calculatePartExpectedDurationWithTransition } from '@sofie-automation/corelib/dist/playout/timings' import { wrapTranslatableMessageFromBlueprints } from '@sofie-automation/corelib/dist/TranslatableMessage' import { ReadOnlyCache } from '../cache/CacheBase' @@ -216,7 +216,7 @@ export async function calculateSegmentsFromIngestData( } : undefined, - expectedDurationWithPreroll: undefined, // Below + expectedDurationWithTransition: undefined, // Below }) res.parts.push(part) @@ -250,7 +250,7 @@ export async function calculateSegmentsFromIngestData( ) ) - part.expectedDurationWithPreroll = calculatePartExpectedDurationWithPreroll(part, processedPieces) + part.expectedDurationWithTransition = calculatePartExpectedDurationWithTransition(part, processedPieces) }) preserveOrphanedSegmentPositionInRundown(context, cache, newSegment) diff --git a/packages/job-worker/src/ingest/syncChangesToPartInstance.ts b/packages/job-worker/src/ingest/syncChangesToPartInstance.ts index 23dd0a62b6..a60e150915 100644 --- a/packages/job-worker/src/ingest/syncChangesToPartInstance.ts +++ b/packages/job-worker/src/ingest/syncChangesToPartInstance.ts @@ -15,7 +15,7 @@ import { getPieceInstancesForPart, syncPlayheadInfinitesForNextPartInstance, } from '../playout/infinites' -import { isTooCloseToAutonext, updateExpectedDurationWithPrerollForPartInstance } from '../playout/lib' +import { isTooCloseToAutonext, updateExpectedDurationWithTransitionForPartInstance } from '../playout/lib' import _ = require('underscore') import { SyncIngestUpdateToPartInstanceContext } from '../blueprints/context' import { @@ -185,7 +185,7 @@ export async function syncChangesToPartInstances( } if (playStatus === 'next') { - updateExpectedDurationWithPrerollForPartInstance(cache, existingPartInstance._id) + updateExpectedDurationWithTransitionForPartInstance(cache, existingPartInstance._id) } // Save notes: diff --git a/packages/job-worker/src/ingest/updateNext.ts b/packages/job-worker/src/ingest/updateNext.ts index 435ee314f5..d5fd8bb8bc 100644 --- a/packages/job-worker/src/ingest/updateNext.ts +++ b/packages/job-worker/src/ingest/updateNext.ts @@ -8,78 +8,91 @@ import { import { JobContext } from '../jobs' import { setNextPart } from '../playout/setNext' import { isPartPlayable } from '@sofie-automation/corelib/dist/dataModel/Part' -import { updateTimeline } from '../playout/timeline/generate' /** * Make sure that the nextPartInstance for the current Playlist is still correct * This will often change the nextPartInstance * @param context Context of the job being run * @param cache Playout Cache to operate on + * @returns Whether the timeline should be updated following this operation */ -export async function ensureNextPartIsValid(context: JobContext, cache: CacheForPlayout): Promise { +export async function ensureNextPartIsValid(context: JobContext, cache: CacheForPlayout): Promise { const span = context.startSpan('api.ingest.ensureNextPartIsValid') // Ensure the next-id is still valid const playlist = cache.Playlist.doc - if (playlist?.activationId) { - const { currentPartInstance, nextPartInstance } = getSelectedPartInstancesFromCache(cache) + if (!playlist?.activationId) { + span?.end() + return false + } + + const { currentPartInstance, nextPartInstance } = getSelectedPartInstancesFromCache(cache) + + if ( + playlist.nextPartInfo?.manuallySelected && + nextPartInstance?.part && + isPartPlayable(nextPartInstance.part) && + nextPartInstance.orphaned !== 'deleted' + ) { + // Manual next part is almost always valid. This includes orphaned (adlib-part) partinstances + span?.end() + return false + } + + // If we are close to an autonext, then leave it to avoid glitches + if (isTooCloseToAutonext(currentPartInstance) && nextPartInstance) { + span?.end() + return false + } + + const allPartsAndSegments = getOrderedSegmentsAndPartsFromPlayoutCache(cache) + + if (currentPartInstance && nextPartInstance) { + // Check if the part is the same + const newNextPart = selectNextPart( + context, + playlist, + currentPartInstance, + nextPartInstance, + allPartsAndSegments + ) if ( - playlist.nextPartInfo?.manuallySelected && - nextPartInstance?.part && - isPartPlayable(nextPartInstance.part) && - nextPartInstance.orphaned !== 'deleted' + // Nothing should be nexted + !newNextPart || + // The nexted-part should be different to what is selected + newNextPart.part._id !== nextPartInstance.part._id || + // The nexted-part Instance is no longer playable + !isPartPlayable(nextPartInstance.part) ) { - // Manual next part is almost always valid. This includes orphaned (adlib-part) partinstances + // The 'new' next part is before the current next, so move the next point + await setNextPart(context, cache, newNextPart ?? null, false) + span?.end() - return + return true } + } else if (!nextPartInstance || nextPartInstance.orphaned === 'deleted') { + // Don't have a nextPart or it has been deleted, so autoselect something + const newNextPart = selectNextPart( + context, + playlist, + currentPartInstance ?? null, + nextPartInstance ?? null, + allPartsAndSegments + ) - // If we are close to an autonext, then leave it to avoid glitches - if (isTooCloseToAutonext(currentPartInstance) && nextPartInstance) { + if (!newNextPart && !cache.Playlist.doc?.nextPartInfo) { + // No currently nexted part, and nothing was selected, so nothing to update span?.end() - return + return false } - const allPartsAndSegments = getOrderedSegmentsAndPartsFromPlayoutCache(cache) - - if (currentPartInstance && nextPartInstance) { - // Check if the part is the same - const newNextPart = selectNextPart( - context, - playlist, - currentPartInstance, - nextPartInstance, - allPartsAndSegments - ) - - if ( - // Nothing should be nexted - !newNextPart || - // The nexted-part should be different to what is selected - newNextPart.part._id !== nextPartInstance.part._id || - // The nexted-part Instance is no longer playable - !isPartPlayable(nextPartInstance.part) - ) { - // The 'new' next part is before the current next, so move the next point - await setNextPart(context, cache, newNextPart ?? null, false) + await setNextPart(context, cache, newNextPart ?? null, false) - await updateTimeline(context, cache) - } - } else if (!nextPartInstance || nextPartInstance.orphaned === 'deleted') { - // Don't have a nextPart or it has been deleted, so autoselect something - const newNextPart = selectNextPart( - context, - playlist, - currentPartInstance ?? null, - nextPartInstance ?? null, - allPartsAndSegments - ) - await setNextPart(context, cache, newNextPart ?? null, false) - - await updateTimeline(context, cache) - } + span?.end() + return true } span?.end() + return false } diff --git a/packages/job-worker/src/playout/__tests__/helpers/rundowns.ts b/packages/job-worker/src/playout/__tests__/helpers/rundowns.ts index a67c06110b..e296006033 100644 --- a/packages/job-worker/src/playout/__tests__/helpers/rundowns.ts +++ b/packages/job-worker/src/playout/__tests__/helpers/rundowns.ts @@ -69,7 +69,7 @@ export async function setupRundownBase( _rank: 0, externalId: 'MOCK_PART_0_0', title: 'Part 0 0', - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, ...partPropsOverride, } @@ -139,7 +139,7 @@ export async function setupPart2( _rank: 1, externalId: 'MOCK_PART_0_1', title: 'Part 0 1', - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, ...partPropsOverride, } diff --git a/packages/job-worker/src/playout/__tests__/timeline.test.ts b/packages/job-worker/src/playout/__tests__/timeline.test.ts index 5c939920b3..f44dd90fbb 100644 --- a/packages/job-worker/src/playout/__tests__/timeline.test.ts +++ b/packages/job-worker/src/playout/__tests__/timeline.test.ts @@ -1439,6 +1439,8 @@ describe('Timeline', () => { // Simulate the piece timing confirmation from playout-gateway await doSimulatePiecePlaybackTimings(playlistId, pieceOffset, 1) + const pieceOffsetWithPreroll = pieceOffset + 340 + // Now we have a concrete time await checkTimings({ previousPart: null, @@ -1446,7 +1448,7 @@ describe('Timeline', () => { piece000: { controlObj: { start: 500, // This one gave the preroll - end: pieceOffset, + end: pieceOffsetWithPreroll, }, childGroup: { preroll: 500, @@ -1465,7 +1467,7 @@ describe('Timeline', () => { [adlibbedPieceId]: { // Our adlibbed piece controlObj: { - start: pieceOffset, + start: pieceOffsetWithPreroll, }, childGroup: { preroll: 340, diff --git a/packages/job-worker/src/playout/adlibAction.ts b/packages/job-worker/src/playout/adlibAction.ts index 0d31ae94f1..013e896c44 100644 --- a/packages/job-worker/src/playout/adlibAction.ts +++ b/packages/job-worker/src/playout/adlibAction.ts @@ -12,7 +12,7 @@ import { getCurrentTime } from '../lib' import { ReadonlyDeep } from 'type-fest' import { CacheForPlayoutPreInit, CacheForPlayout } from './cache' import { syncPlayheadInfinitesForNextPartInstance } from './infinites' -import { updateExpectedDurationWithPrerollForPartInstance } from './lib' +import { updateExpectedDurationWithTransitionForPartInstance } from './lib' import { runJobWithPlaylistLock } from './lock' import { updateTimeline } from './timeline/generate' import { performTakeToNextedPart } from './take' @@ -192,7 +192,7 @@ async function applyAnyExecutionSideEffects( if (actionContext.nextPartState !== ActionPartChange.NONE) { const nextPartInstanceId = cache.Playlist.doc.nextPartInfo?.partInstanceId if (nextPartInstanceId) { - updateExpectedDurationWithPrerollForPartInstance(cache, nextPartInstanceId) + updateExpectedDurationWithTransitionForPartInstance(cache, nextPartInstanceId) } } diff --git a/packages/job-worker/src/playout/adlibUtils.ts b/packages/job-worker/src/playout/adlibUtils.ts index f785fecc0d..00e30d7d56 100644 --- a/packages/job-worker/src/playout/adlibUtils.ts +++ b/packages/job-worker/src/playout/adlibUtils.ts @@ -7,7 +7,7 @@ import { PieceInstance, rewrapPieceToInstance } from '@sofie-automation/corelib/ import { DBRundown } from '@sofie-automation/corelib/dist/dataModel/Rundown' import { assertNever, getRandomId, getRank } from '@sofie-automation/corelib/dist/lib' import { MongoQuery } from '@sofie-automation/corelib/dist/mongo' -import { calculatePartExpectedDurationWithPreroll } from '@sofie-automation/corelib/dist/playout/timings' +import { calculatePartExpectedDurationWithTransition } from '@sofie-automation/corelib/dist/playout/timings' import { getCurrentTime } from '../lib' import { JobContext } from '../jobs' import { CacheForPlayout, getRundownIDsFromCache } from './cache' @@ -56,7 +56,7 @@ export async function innerStartOrQueueAdLibPiece( rundownId: rundown._id, title: adLibPiece.name, expectedDuration: adLibPiece.expectedDuration, - expectedDurationWithPreroll: adLibPiece.expectedDuration, // Filled in later + expectedDurationWithTransition: adLibPiece.expectedDuration, // Filled in later }, } const newPieceInstance = convertAdLibToPieceInstance( @@ -67,7 +67,7 @@ export async function innerStartOrQueueAdLibPiece( queue ) - newPartInstance.part.expectedDurationWithPreroll = calculatePartExpectedDurationWithPreroll( + newPartInstance.part.expectedDurationWithTransition = calculatePartExpectedDurationWithTransition( newPartInstance.part, [newPieceInstance.piece] ) diff --git a/packages/job-worker/src/playout/lib.ts b/packages/job-worker/src/playout/lib.ts index 8f6aef5c1f..69eb1ad8b7 100644 --- a/packages/job-worker/src/playout/lib.ts +++ b/packages/job-worker/src/playout/lib.ts @@ -9,7 +9,7 @@ import { ReadonlyDeep } from 'type-fest' import { CacheForPlayout, getOrderedSegmentsAndPartsFromPlayoutCache, getRundownIDsFromCache } from './cache' import { logger } from '../logging' import { getCurrentTime } from '../lib' -import { calculatePartExpectedDurationWithPreroll } from '@sofie-automation/corelib/dist/playout/timings' +import { calculatePartExpectedDurationWithTransition } from '@sofie-automation/corelib/dist/playout/timings' import { MongoQuery } from '../db' import { mongoWhere } from '@sofie-automation/corelib/dist/mongo' import _ = require('underscore') @@ -305,10 +305,10 @@ export function isTooCloseToAutonext( } /** - * Update the expectedDurationWithPreroll on the specified PartInstance. + * Update the expectedDurationWithTransition on the specified PartInstance. * The value is used by the UI to approximate the duration of a PartInstance as it will be played out */ -export function updateExpectedDurationWithPrerollForPartInstance( +export function updateExpectedDurationWithTransitionForPartInstance( cache: CacheForPlayout, partInstanceId: PartInstanceId ): void { @@ -316,14 +316,14 @@ export function updateExpectedDurationWithPrerollForPartInstance( if (nextPartInstance) { const pieceInstances = cache.PieceInstances.findAll((p) => p.partInstanceId === nextPartInstance._id) - // Update expectedDurationWithPreroll of the next part instance, as it may have changed and is used by the ui until it is taken - const expectedDurationWithPreroll = calculatePartExpectedDurationWithPreroll( + // Update expectedDurationWithTransition of the next part instance, as it may have changed and is used by the ui until it is taken + const expectedDurationWithTransition = calculatePartExpectedDurationWithTransition( nextPartInstance.part, pieceInstances.map((p) => p.piece) ) cache.PartInstances.updateOne(nextPartInstance._id, (doc) => { - doc.part.expectedDurationWithPreroll = expectedDurationWithPreroll + doc.part.expectedDurationWithTransition = expectedDurationWithTransition return doc }) } diff --git a/packages/job-worker/src/playout/lookahead/__tests__/lookahead.test.ts b/packages/job-worker/src/playout/lookahead/__tests__/lookahead.test.ts index d03c78b4ce..740122012e 100644 --- a/packages/job-worker/src/playout/lookahead/__tests__/lookahead.test.ts +++ b/packages/job-worker/src/playout/lookahead/__tests__/lookahead.test.ts @@ -97,7 +97,7 @@ describe('Lookahead', () => { _rank: index, externalId: 'MOCK_PART_' + index, title: 'Part ' + index, - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, } } diff --git a/packages/job-worker/src/playout/lookahead/__tests__/util.test.ts b/packages/job-worker/src/playout/lookahead/__tests__/util.test.ts index e63e1b5173..1702eae1cd 100644 --- a/packages/job-worker/src/playout/lookahead/__tests__/util.test.ts +++ b/packages/job-worker/src/playout/lookahead/__tests__/util.test.ts @@ -115,7 +115,7 @@ describe('getOrderedPartsAfterPlayhead', () => { _rank: index, externalId: 'MOCK_PART_' + index, title: 'Part ' + index, - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, } } diff --git a/packages/job-worker/src/playout/timeline/piece.ts b/packages/job-worker/src/playout/timeline/piece.ts index 69f5d3ad15..72c354d8e9 100644 --- a/packages/job-worker/src/playout/timeline/piece.ts +++ b/packages/job-worker/src/playout/timeline/piece.ts @@ -93,10 +93,16 @@ export function getPieceEnableInsidePart( partGroupId: string ): TSR.Timeline.TimelineEnable { const pieceEnable: TSR.Timeline.TimelineEnable = { ...pieceInstance.piece.enable } - if (typeof pieceEnable.start === 'number' && !pieceInstance.dynamicallyInserted) { - // timed pieces should be offset based on the preroll of the part - pieceEnable.start += partTimings.toPartDelay + if (typeof pieceEnable.start === 'number') { + if (pieceInstance.dynamicallyInserted) { + // timed adlibbed pieces needs to factor in their preroll + pieceEnable.start += pieceInstance.piece.prerollDuration || 0 + } else { + // timed planned pieces should be offset based on the preroll of the part + pieceEnable.start += partTimings.toPartDelay + } } + if (partTimings.toPartPostroll) { if (!pieceEnable.duration) { // make sure that the control object is shortened correctly diff --git a/packages/lerna.json b/packages/lerna.json index 0fa225735b..d9f6ff6141 100644 --- a/packages/lerna.json +++ b/packages/lerna.json @@ -1,5 +1,5 @@ { - "version": "1.50.2", + "version": "1.50.4", "npmClient": "yarn", "useWorkspaces": true } diff --git a/packages/live-status-gateway/package.json b/packages/live-status-gateway/package.json index c58319053c..e0cf67276e 100644 --- a/packages/live-status-gateway/package.json +++ b/packages/live-status-gateway/package.json @@ -1,6 +1,6 @@ { "name": "live-status-gateway", - "version": "1.50.2", + "version": "1.50.4", "private": true, "description": "Provides state from Sofie over sockets", "license": "MIT", @@ -53,10 +53,10 @@ "production" ], "dependencies": { - "@sofie-automation/blueprints-integration": "1.50.2", - "@sofie-automation/corelib": "1.50.2", - "@sofie-automation/server-core-integration": "1.50.2", - "@sofie-automation/shared-lib": "1.50.2", + "@sofie-automation/blueprints-integration": "1.50.4", + "@sofie-automation/corelib": "1.50.4", + "@sofie-automation/server-core-integration": "1.50.4", + "@sofie-automation/shared-lib": "1.50.4", "debug": "^4.3.2", "fast-clone": "^1.5.13", "influx": "^5.9.2", diff --git a/packages/live-status-gateway/src/topics/__tests__/activePlaylist.spec.ts b/packages/live-status-gateway/src/topics/__tests__/activePlaylist.spec.ts index 0ecbd3c81d..4ee2a06ab2 100644 --- a/packages/live-status-gateway/src/topics/__tests__/activePlaylist.spec.ts +++ b/packages/live-status-gateway/src/topics/__tests__/activePlaylist.spec.ts @@ -161,7 +161,7 @@ describe('ActivePlaylistTopic', () => { _id: protectString('PART_1'), title: 'Test Part', segmentId: protectString('SEGMENT_1'), - expectedDurationWithPreroll: 10000, + expectedDurationWithTransition: 10000, expectedDuration: 10000, } const testPartInstances: PartialDeep = { diff --git a/packages/live-status-gateway/src/topics/__tests__/segmentsTopic.spec.ts b/packages/live-status-gateway/src/topics/__tests__/segmentsTopic.spec.ts index fc5851702f..9904f5b6b7 100644 --- a/packages/live-status-gateway/src/topics/__tests__/segmentsTopic.spec.ts +++ b/packages/live-status-gateway/src/topics/__tests__/segmentsTopic.spec.ts @@ -36,7 +36,7 @@ function makeTestPart( _rank: rank, rundownId: protectString(rundownId), segmentId: protectString(segmentId), - expectedDurationWithPreroll: undefined, + expectedDurationWithTransition: undefined, ...partProps, } } @@ -295,25 +295,25 @@ describe('SegmentsTopic', () => { mockSubscriber.send.mockClear() await topic.update(PartsHandler.name, [ makeTestPart('1_2_1', 1, RUNDOWN_1_ID, segment_1_2_id, { - expectedDurationWithPreroll: 10000, + expectedDurationWithTransition: 10000, }), makeTestPart('2_2_1', 1, RUNDOWN_1_ID, segment_2_2_id, { - expectedDurationWithPreroll: 40000, + expectedDurationWithTransition: 40000, }), makeTestPart('1_2_2', 2, RUNDOWN_1_ID, segment_1_2_id, { - expectedDurationWithPreroll: 5000, + expectedDurationWithTransition: 5000, }), makeTestPart('1_1_2', 2, RUNDOWN_1_ID, segment_1_1_id, { - expectedDurationWithPreroll: 1000, + expectedDurationWithTransition: 1000, }), makeTestPart('1_1_1', 1, RUNDOWN_1_ID, segment_1_1_id, { - expectedDurationWithPreroll: 3000, + expectedDurationWithTransition: 3000, }), makeTestPart('2_2_2', 2, RUNDOWN_1_ID, segment_2_2_id, { - expectedDurationWithPreroll: 11000, + expectedDurationWithTransition: 11000, }), makeTestPart('1_1_2', 2, RUNDOWN_1_ID, segment_1_1_id, { - expectedDurationWithPreroll: 1000, + expectedDurationWithTransition: 1000, }), ]) jest.advanceTimersByTime(THROTTLE_PERIOD_MS) diff --git a/packages/live-status-gateway/src/topics/helpers/partTiming.ts b/packages/live-status-gateway/src/topics/helpers/partTiming.ts index 6ef75c355e..949ba89bfb 100644 --- a/packages/live-status-gateway/src/topics/helpers/partTiming.ts +++ b/packages/live-status-gateway/src/topics/helpers/partTiming.ts @@ -21,7 +21,7 @@ export function calculateCurrentPartTiming( (partInstance) => partInstance.part.displayDurationGroup === currentPartInstance.part.displayDurationGroup ) const groupDuration = displayDurationGroup.reduce((sum, partInstance) => { - return sum + (partInstance.part.expectedDurationWithPreroll ?? 0) + return sum + (partInstance.part.expectedDurationWithTransition ?? 0) }, 0) const groupPlayed = displayDurationGroup.reduce((sum, partInstance) => { return (partInstance.timings?.duration ?? 0) + sum diff --git a/packages/live-status-gateway/src/topics/helpers/segmentTiming.ts b/packages/live-status-gateway/src/topics/helpers/segmentTiming.ts index 966354e6d9..bf6c58c0eb 100644 --- a/packages/live-status-gateway/src/topics/helpers/segmentTiming.ts +++ b/packages/live-status-gateway/src/topics/helpers/segmentTiming.ts @@ -42,8 +42,8 @@ export function calculateSegmentTiming(segmentParts: DBPart[]): SegmentTiming { return part.budgetDuration != null && !part.untimed ? (sum ?? 0) + part.budgetDuration : sum }, undefined), expectedDurationMs: segmentParts.reduce((sum, part): number => { - return part.expectedDurationWithPreroll != null && !part.untimed - ? sum + part.expectedDurationWithPreroll + return part.expectedDurationWithTransition != null && !part.untimed + ? sum + part.expectedDurationWithTransition : sum }, 0), } diff --git a/packages/mos-gateway/CHANGELOG.md b/packages/mos-gateway/CHANGELOG.md index dc990ed81e..0625b7888c 100644 --- a/packages/mos-gateway/CHANGELOG.md +++ b/packages/mos-gateway/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.50.4](https://github.com/nrkno/tv-automation-server-core/compare/v1.50.3...v1.50.4) (2024-08-08) + +**Note:** Version bump only for package mos-gateway + + + + + +## [1.50.3](https://github.com/nrkno/tv-automation-server-core/compare/v1.50.2...v1.50.3) (2024-06-24) + +**Note:** Version bump only for package mos-gateway + + + + + ## [1.50.2](https://github.com/nrkno/tv-automation-server-core/compare/v1.49.6...v1.50.2) (2024-05-15) diff --git a/packages/mos-gateway/package.json b/packages/mos-gateway/package.json index ce665fa7a9..76c9eadd35 100644 --- a/packages/mos-gateway/package.json +++ b/packages/mos-gateway/package.json @@ -1,6 +1,6 @@ { "name": "mos-gateway", - "version": "1.50.2", + "version": "1.50.4", "private": true, "description": "MOS-Gateway for the Sofie project", "license": "MIT", @@ -66,8 +66,8 @@ ], "dependencies": { "@mos-connection/connector": "^3.0.4", - "@sofie-automation/server-core-integration": "1.50.2", - "@sofie-automation/shared-lib": "1.50.2", + "@sofie-automation/server-core-integration": "1.50.4", + "@sofie-automation/shared-lib": "1.50.4", "tslib": "^2.4.0", "type-fest": "^2.19.0", "underscore": "^1.13.4", diff --git a/packages/openapi/package.json b/packages/openapi/package.json index 33de9d9379..ca2649a852 100644 --- a/packages/openapi/package.json +++ b/packages/openapi/package.json @@ -1,6 +1,6 @@ { "name": "@sofie-automation/openapi", - "version": "1.50.2", + "version": "1.50.4", "private": true, "license": "MIT", "repository": { diff --git a/packages/playout-gateway/CHANGELOG.md b/packages/playout-gateway/CHANGELOG.md index 195b175037..75e7c2d022 100644 --- a/packages/playout-gateway/CHANGELOG.md +++ b/packages/playout-gateway/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.50.4](https://github.com/nrkno/tv-automation-server-core/compare/v1.50.3...v1.50.4) (2024-08-08) + +**Note:** Version bump only for package playout-gateway + + + + + +## [1.50.3](https://github.com/nrkno/tv-automation-server-core/compare/v1.50.2...v1.50.3) (2024-06-24) + +**Note:** Version bump only for package playout-gateway + + + + + ## [1.50.2](https://github.com/nrkno/tv-automation-server-core/compare/v1.49.6...v1.50.2) (2024-05-15) diff --git a/packages/playout-gateway/package.json b/packages/playout-gateway/package.json index 927bd5640a..8183a95b88 100644 --- a/packages/playout-gateway/package.json +++ b/packages/playout-gateway/package.json @@ -1,6 +1,6 @@ { "name": "playout-gateway", - "version": "1.50.2", + "version": "1.50.4", "private": true, "description": "Connect to Core, play stuff", "license": "MIT", @@ -56,8 +56,8 @@ "production" ], "dependencies": { - "@sofie-automation/server-core-integration": "1.50.2", - "@sofie-automation/shared-lib": "1.50.2", + "@sofie-automation/server-core-integration": "1.50.4", + "@sofie-automation/shared-lib": "1.50.4", "debug": "^4.3.3", "influx": "^5.9.3", "timeline-state-resolver": "9.0.1", diff --git a/packages/server-core-integration/CHANGELOG.md b/packages/server-core-integration/CHANGELOG.md index bb719c4f41..fd686f16de 100644 --- a/packages/server-core-integration/CHANGELOG.md +++ b/packages/server-core-integration/CHANGELOG.md @@ -3,6 +3,22 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.50.4](https://github.com/nrkno/tv-automation-server-core/compare/v1.50.3...v1.50.4) (2024-08-08) + +**Note:** Version bump only for package @sofie-automation/server-core-integration + + + + + +## [1.50.3](https://github.com/nrkno/tv-automation-server-core/compare/v1.50.2...v1.50.3) (2024-06-24) + +**Note:** Version bump only for package @sofie-automation/server-core-integration + + + + + ## [1.50.2](https://github.com/nrkno/tv-automation-server-core/compare/v1.49.6...v1.50.2) (2024-05-15) diff --git a/packages/server-core-integration/package.json b/packages/server-core-integration/package.json index ce6cc50010..ce03404bc0 100644 --- a/packages/server-core-integration/package.json +++ b/packages/server-core-integration/package.json @@ -1,6 +1,6 @@ { "name": "@sofie-automation/server-core-integration", - "version": "1.50.2", + "version": "1.50.4", "description": "Library for connecting to Core", "main": "dist/index.js", "typings": "dist/index.d.ts", @@ -70,7 +70,7 @@ "production" ], "dependencies": { - "@sofie-automation/shared-lib": "1.50.2", + "@sofie-automation/shared-lib": "1.50.4", "ejson": "^2.2.3", "eventemitter3": "^4.0.7", "faye-websocket": "^0.11.4", diff --git a/packages/shared-lib/package.json b/packages/shared-lib/package.json index 2493be9e39..13bcbdd014 100644 --- a/packages/shared-lib/package.json +++ b/packages/shared-lib/package.json @@ -1,6 +1,6 @@ { "name": "@sofie-automation/shared-lib", - "version": "1.50.2", + "version": "1.50.4", "description": "Library for types & values shared by core, workers and gateways", "main": "dist/index.js", "typings": "dist/index.d.ts", diff --git a/packages/yarn.lock b/packages/yarn.lock index c39cc5d3bc..47a05b8d8b 100644 --- a/packages/yarn.lock +++ b/packages/yarn.lock @@ -5030,11 +5030,11 @@ __metadata: languageName: node linkType: hard -"@sofie-automation/blueprints-integration@1.50.2, @sofie-automation/blueprints-integration@workspace:blueprints-integration": +"@sofie-automation/blueprints-integration@1.50.4, @sofie-automation/blueprints-integration@workspace:blueprints-integration": version: 0.0.0-use.local resolution: "@sofie-automation/blueprints-integration@workspace:blueprints-integration" dependencies: - "@sofie-automation/shared-lib": 1.50.2 + "@sofie-automation/shared-lib": 1.50.4 tslib: ^2.4.0 type-fest: ^2.19.0 languageName: unknown @@ -5071,12 +5071,12 @@ __metadata: languageName: node linkType: hard -"@sofie-automation/corelib@1.50.2, @sofie-automation/corelib@workspace:corelib": +"@sofie-automation/corelib@1.50.4, @sofie-automation/corelib@workspace:corelib": version: 0.0.0-use.local resolution: "@sofie-automation/corelib@workspace:corelib" dependencies: - "@sofie-automation/blueprints-integration": 1.50.2 - "@sofie-automation/shared-lib": 1.50.2 + "@sofie-automation/blueprints-integration": 1.50.4 + "@sofie-automation/shared-lib": 1.50.4 fast-clone: ^1.5.13 i18next: ^21.9.1 influx: ^5.9.3 @@ -5107,9 +5107,9 @@ __metadata: resolution: "@sofie-automation/job-worker@workspace:job-worker" dependencies: "@slack/webhook": ^6.1.0 - "@sofie-automation/blueprints-integration": 1.50.2 - "@sofie-automation/corelib": 1.50.2 - "@sofie-automation/shared-lib": 1.50.2 + "@sofie-automation/blueprints-integration": 1.50.4 + "@sofie-automation/corelib": 1.50.4 + "@sofie-automation/shared-lib": 1.50.4 amqplib: ^0.10.3 deepmerge: ^4.3.1 elastic-apm-node: ^3.43.0 @@ -5139,11 +5139,11 @@ __metadata: languageName: unknown linkType: soft -"@sofie-automation/server-core-integration@1.50.2, @sofie-automation/server-core-integration@workspace:server-core-integration": +"@sofie-automation/server-core-integration@1.50.4, @sofie-automation/server-core-integration@workspace:server-core-integration": version: 0.0.0-use.local resolution: "@sofie-automation/server-core-integration@workspace:server-core-integration" dependencies: - "@sofie-automation/shared-lib": 1.50.2 + "@sofie-automation/shared-lib": 1.50.4 ejson: ^2.2.3 eventemitter3: ^4.0.7 faye-websocket: ^0.11.4 @@ -5153,7 +5153,7 @@ __metadata: languageName: unknown linkType: soft -"@sofie-automation/shared-lib@1.50.2, @sofie-automation/shared-lib@workspace:shared-lib": +"@sofie-automation/shared-lib@1.50.4, @sofie-automation/shared-lib@workspace:shared-lib": version: 0.0.0-use.local resolution: "@sofie-automation/shared-lib@workspace:shared-lib" dependencies: @@ -14992,10 +14992,10 @@ asn1@evs-broadcast/node-asn1: "@asyncapi/generator": 1.9.13 "@asyncapi/html-template": 0.26.0 "@asyncapi/nodejs-ws-template": 0.9.25 - "@sofie-automation/blueprints-integration": 1.50.2 - "@sofie-automation/corelib": 1.50.2 - "@sofie-automation/server-core-integration": 1.50.2 - "@sofie-automation/shared-lib": 1.50.2 + "@sofie-automation/blueprints-integration": 1.50.4 + "@sofie-automation/corelib": 1.50.4 + "@sofie-automation/server-core-integration": 1.50.4 + "@sofie-automation/shared-lib": 1.50.4 debug: ^4.3.2 fast-clone: ^1.5.13 influx: ^5.9.2 @@ -16076,8 +16076,8 @@ asn1@evs-broadcast/node-asn1: resolution: "mos-gateway@workspace:mos-gateway" dependencies: "@mos-connection/connector": ^3.0.4 - "@sofie-automation/server-core-integration": 1.50.2 - "@sofie-automation/shared-lib": 1.50.2 + "@sofie-automation/server-core-integration": 1.50.4 + "@sofie-automation/shared-lib": 1.50.4 tslib: ^2.4.0 type-fest: ^2.19.0 underscore: ^1.13.4 @@ -18053,8 +18053,8 @@ asn1@evs-broadcast/node-asn1: version: 0.0.0-use.local resolution: "playout-gateway@workspace:playout-gateway" dependencies: - "@sofie-automation/server-core-integration": 1.50.2 - "@sofie-automation/shared-lib": 1.50.2 + "@sofie-automation/server-core-integration": 1.50.4 + "@sofie-automation/shared-lib": 1.50.4 debug: ^4.3.3 influx: ^5.9.3 timeline-state-resolver: 9.0.1