From 7895349e30197c1104aadce5a9586c4b821a3d12 Mon Sep 17 00:00:00 2001 From: Johan Nyman Date: Wed, 13 Nov 2024 09:00:17 +0100 Subject: [PATCH 01/19] chore: improve logging in logAnyRemainingNowTimes --- .../src/playout/timeline/generate.ts | 25 +++++++++---------- 1 file changed, 12 insertions(+), 13 deletions(-) diff --git a/packages/job-worker/src/playout/timeline/generate.ts b/packages/job-worker/src/playout/timeline/generate.ts index 9487b87553..66edbd0b79 100644 --- a/packages/job-worker/src/playout/timeline/generate.ts +++ b/packages/job-worker/src/playout/timeline/generate.ts @@ -202,32 +202,31 @@ function preserveOrReplaceNowTimesInObjects( } function logAnyRemainingNowTimes(_context: JobContext, timelineObjs: Array): void { - const ids: string[] = [] - - const hasNow = (obj: TimelineEnableExt | TimelineEnableExt[]) => { - let res = false - applyToArray(obj, (enable) => { - if (enable.start === 'now' || enable.end === 'now') res = true - }) - return res - } + const badTimelineObjs: any[] = [] for (const obj of timelineObjs) { if (hasNow(obj.enable)) { - ids.push(obj.id) + badTimelineObjs.push(obj) } for (const kf of obj.keyframes || []) { if (hasNow(kf.enable)) { - ids.push(kf.id) + badTimelineObjs.push(kf) } } } - if (ids.length) { - logger.error(`Some timeline objects have unexpected now times!: ${JSON.stringify(ids)}`) + if (badTimelineObjs.length) { + logger.error(`Some timeline objects have unexpected now times!: ${JSON.stringify(badTimelineObjs)}`) } } +function hasNow(obj: TimelineEnableExt | TimelineEnableExt[]) { + let res = false + applyToArray(obj, (enable) => { + if (enable.start === 'now' || enable.end === 'now') res = true + }) + return res +} /** Store the timelineobjects into the model, and perform any post-save actions */ export function saveTimeline( From a9fe40167c32c0af40ee7b7b39d6eca405f184d8 Mon Sep 17 00:00:00 2001 From: Johan Nyman Date: Wed, 13 Nov 2024 09:12:07 +0100 Subject: [PATCH 02/19] fix: only run onPart/PiecePlaybackStarted/Stopped on current, next or previous parts Since these are the only ones loaded into the PlayoutModel, we should not try to update any others --- .../src/playout/timings/partPlayback.ts | 24 +++++++++++++ .../src/playout/timings/piecePlayback.ts | 34 +++++++++++++++++-- 2 files changed, 56 insertions(+), 2 deletions(-) diff --git a/packages/job-worker/src/playout/timings/partPlayback.ts b/packages/job-worker/src/playout/timings/partPlayback.ts index 8c3fc07d16..d7bd943c60 100644 --- a/packages/job-worker/src/playout/timings/partPlayback.ts +++ b/packages/job-worker/src/playout/timings/partPlayback.ts @@ -26,6 +26,18 @@ export async function onPartPlaybackStarted( startedPlayback: Time } ): Promise { + if ( + // We only expect to be able to update the current, next, or previous part: + data.partInstanceId !== playoutModel.playlist.currentPartInfo?.partInstanceId || + data.partInstanceId !== playoutModel.playlist.nextPartInfo?.partInstanceId || + data.partInstanceId !== playoutModel.playlist.previousPartInfo?.partInstanceId + ) { + logger.debug( + `onPartPlaybackStarted PartInstance "${data.partInstanceId}" is neither current, next nor previous (current: ${playoutModel.playlist.currentPartInfo?.partInstanceId}, next: ${playoutModel.playlist.nextPartInfo?.partInstanceId}, previous: ${playoutModel.playlist.previousPartInfo?.partInstanceId}, timestamp: ${data.startedPlayback}` + ) + return + } + const playingPartInstance = playoutModel.getPartInstance(data.partInstanceId) if (!playingPartInstance) throw new Error( @@ -136,6 +148,18 @@ export function onPartPlaybackStopped( stoppedPlayback: Time } ): void { + if ( + // We only expect to be able to update the current, next, or previous part: + data.partInstanceId !== playoutModel.playlist.currentPartInfo?.partInstanceId || + data.partInstanceId !== playoutModel.playlist.nextPartInfo?.partInstanceId || + data.partInstanceId !== playoutModel.playlist.previousPartInfo?.partInstanceId + ) { + logger.debug( + `onPartPlaybackStopped PartInstance "${data.partInstanceId}" is neither current, next nor previous (current: ${playoutModel.playlist.currentPartInfo?.partInstanceId}, next: ${playoutModel.playlist.nextPartInfo?.partInstanceId}, previous: ${playoutModel.playlist.previousPartInfo?.partInstanceId}, timestamp: ${data.stoppedPlayback}` + ) + return + } + const playlist = playoutModel.playlist const partInstance = playoutModel.getPartInstance(data.partInstanceId) diff --git a/packages/job-worker/src/playout/timings/piecePlayback.ts b/packages/job-worker/src/playout/timings/piecePlayback.ts index bd1d76b8a3..ebd56a1f12 100644 --- a/packages/job-worker/src/playout/timings/piecePlayback.ts +++ b/packages/job-worker/src/playout/timings/piecePlayback.ts @@ -23,6 +23,18 @@ export function onPiecePlaybackStarted( ): void { const playlist = playoutModel.playlist + if ( + // We only expect to be able to update the current, next, or previous part: + data.partInstanceId !== playoutModel.playlist.currentPartInfo?.partInstanceId || + data.partInstanceId !== playoutModel.playlist.nextPartInfo?.partInstanceId || + data.partInstanceId !== playoutModel.playlist.previousPartInfo?.partInstanceId + ) { + logger.debug( + `onPiecePlaybackStarted PartInstance "${data.partInstanceId}" is neither current, next nor previous (current: ${playoutModel.playlist.currentPartInfo?.partInstanceId}, next: ${playoutModel.playlist.nextPartInfo?.partInstanceId}, previous: ${playoutModel.playlist.previousPartInfo?.partInstanceId}), timestamp: ${data.startedPlayback}` + ) + return + } + const partInstance = playoutModel.getPartInstance(data.partInstanceId) if (!partInstance) { if (!playlist.activationId) { @@ -38,7 +50,10 @@ export function onPiecePlaybackStarted( if (!playlist.activationId) { logger.warn(`onPiecePlaybackStarted: Received for inactive RundownPlaylist "${playlist._id}"`) } else { - throw new Error(`PieceInstance "${data.partInstanceId}" in RundownPlaylist "${playlist._id}" not found!`) + const otherPieceInstanceIds = partInstance.pieceInstances.map((p) => p.pieceInstance._id) + throw new Error( + `PieceInstance "${data.pieceInstanceId}" in PartInstance "${partInstance.partInstance._id}" in RundownPlaylist "${playlist._id}" not found! (other pieceInstanceIds in PartInstance: ${otherPieceInstanceIds})` + ) } return } @@ -75,6 +90,18 @@ export function onPiecePlaybackStopped( ): void { const playlist = playoutModel.playlist + if ( + // We only expect to be able to update the current, next, or previous part: + data.partInstanceId !== playoutModel.playlist.currentPartInfo?.partInstanceId || + data.partInstanceId !== playoutModel.playlist.nextPartInfo?.partInstanceId || + data.partInstanceId !== playoutModel.playlist.previousPartInfo?.partInstanceId + ) { + logger.debug( + `onPiecePlaybackStarted PartInstance "${data.partInstanceId}" is neither current, next nor previous (current: ${playoutModel.playlist.currentPartInfo?.partInstanceId}, next: ${playoutModel.playlist.nextPartInfo?.partInstanceId}, previous: ${playoutModel.playlist.previousPartInfo?.partInstanceId}), timestamp: ${data.stoppedPlayback}` + ) + return + } + const partInstance = playoutModel.getPartInstance(data.partInstanceId) if (!partInstance) { // PartInstance not found, so we can rely on the onPartPlaybackStopped callback erroring @@ -86,7 +113,10 @@ export function onPiecePlaybackStopped( if (!playlist.activationId) { logger.warn(`onPiecePlaybackStopped: Received for inactive RundownPlaylist "${playlist._id}"`) } else { - throw new Error(`PieceInstance "${data.partInstanceId}" in RundownPlaylist "${playlist._id}" not found!`) + const otherPieceInstanceIds = partInstance.pieceInstances.map((p) => p.pieceInstance._id) + throw new Error( + `PieceInstance "${data.pieceInstanceId}" in PartInstance "${partInstance.partInstance._id}" in RundownPlaylist "${playlist._id}" not found! (other pieceInstanceIds in PartInstance: ${otherPieceInstanceIds})` + ) } return } From a09c8ed16c88f98ae16467a2d48bf8e83787c382 Mon Sep 17 00:00:00 2001 From: Johan Nyman Date: Wed, 13 Nov 2024 10:37:19 +0100 Subject: [PATCH 03/19] chore: improve logging, for troubleshooting --- packages/job-worker/src/playout/infinites.ts | 22 ++++++++++++++- .../model/implementation/LoadPlayoutModel.ts | 28 +++++++++++++------ 2 files changed, 41 insertions(+), 9 deletions(-) diff --git a/packages/job-worker/src/playout/infinites.ts b/packages/job-worker/src/playout/infinites.ts index 5924cb4a5c..222791d0f1 100644 --- a/packages/job-worker/src/playout/infinites.ts +++ b/packages/job-worker/src/playout/infinites.ts @@ -23,6 +23,7 @@ import { SegmentOrphanedReason } from '@sofie-automation/corelib/dist/dataModel/ import { sortRundownIDsInPlaylist } from '@sofie-automation/corelib/dist/playout/playlist' import { mongoWhere } from '@sofie-automation/corelib/dist/mongo' import { PlayoutRundownModel } from './model/PlayoutRundownModel' +import { logger } from '../logging' /** When we crop a piece, set the piece as "it has definitely ended" this far into the future. */ export const DEFINITELY_ENDED_FUTURE_DURATION = 1 * 1000 @@ -330,7 +331,26 @@ export function getPieceInstancesForPart( if (!playingRundown) throw new Error(`Rundown "${playingPartInstance.partInstance.rundownId}" not found!`) playingSegment = playingRundown.getSegment(playingPartInstance.partInstance.segmentId) - if (!playingSegment) throw new Error(`Segment "${playingPartInstance.partInstance.segmentId}" not found!`) + if (!playingSegment) { + const rundownId = playingRundown.rundown._id + context.directCollections.Segments.findFetch({ + rundownId: rundownId, + }) + .then((segment) => { + logger.error( + `TROUBLESHOOT: Segment not found, rundown "${rundownId}", segments in db: ${JSON.stringify( + segment.map((s) => s._id) + )}` + ) + }) + .catch((e) => logger.error(e)) + + throw new Error( + `Segment "${playingPartInstance.partInstance.segmentId}" in Rundown "${ + playingRundown.rundown._id + }" not found! (other segments: ${JSON.stringify(playingRundown.segments.map((s) => s.segment._id))})` + ) + } } const segment = rundown.getSegment(part.segmentId) diff --git a/packages/job-worker/src/playout/model/implementation/LoadPlayoutModel.ts b/packages/job-worker/src/playout/model/implementation/LoadPlayoutModel.ts index 20db14e685..8f0fcb70ca 100644 --- a/packages/job-worker/src/playout/model/implementation/LoadPlayoutModel.ts +++ b/packages/job-worker/src/playout/model/implementation/LoadPlayoutModel.ts @@ -23,6 +23,7 @@ import { PlayoutModel, PlayoutModelPreInit } from '../PlayoutModel' import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part' import { RundownBaselineObj } from '@sofie-automation/corelib/dist/dataModel/RundownBaselineObj' import { sortRundownsWithinPlaylist } from '@sofie-automation/corelib/dist/playout/playlist' +import { logger } from '../../../logging' /** * Load a PlayoutModelPreInit for the given RundownPlaylist @@ -188,7 +189,7 @@ async function loadRundowns( context.directCollections.Segments.findFetch({ $or: [ { - // In a different rundown + // Either in rundown when ingestModel === null or not available in ingestModel rundownId: { $in: loadRundownIds }, }, { @@ -233,14 +234,25 @@ async function loadRundowns( } } - return rundowns.map( - (rundown) => - new PlayoutRundownModelImpl( - rundown, - groupedSegmentsWithParts.get(rundown._id) ?? [], - groupedBaselineObjects.get(rundown._id) ?? [] + return rundowns.map((rundown) => { + const groupedSegmentsWithPartsForRundown = groupedSegmentsWithParts.get(rundown._id) + if (!groupedSegmentsWithPartsForRundown) { + logger.debug( + `groupedSegmentsWithPartsForRundown for Rundown "${rundown._id}" is undefined (has the rundown no segments?)` ) - ) + } + const groupedBaselineObjectsForRundown = groupedBaselineObjects.get(rundown._id) + if (!groupedBaselineObjectsForRundown) + logger.debug( + `groupedBaselineObjectsForRundown for Rundown "${rundown._id}" is undefined (has the rundown no baseline objects?)` + ) + + return new PlayoutRundownModelImpl( + rundown, + groupedSegmentsWithPartsForRundown ?? [], + groupedBaselineObjectsForRundown ?? [] + ) + }) } async function loadPartInstances( From 51b71040df4b2b4147b3768cbed80db0d7dd3061 Mon Sep 17 00:00:00 2001 From: Johan Nyman Date: Wed, 13 Nov 2024 11:59:48 +0100 Subject: [PATCH 04/19] fix: Include previousPartInstance in check to orphan segments rather than remove them. This is because previousPartInstances are used in syncChangesToPartInstances --- packages/job-worker/src/ingest/commit.ts | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/packages/job-worker/src/ingest/commit.ts b/packages/job-worker/src/ingest/commit.ts index f315ee77cd..257044362f 100644 --- a/packages/job-worker/src/ingest/commit.ts +++ b/packages/job-worker/src/ingest/commit.ts @@ -259,10 +259,16 @@ export async function CommitIngestOperation( } function canRemoveSegment( + prevPartInstance: ReadonlyDeep | undefined, currentPartInstance: ReadonlyDeep | undefined, nextPartInstance: ReadonlyDeep | undefined, segmentId: SegmentId ): boolean { + if (prevPartInstance?.segmentId === segmentId) { + // Don't allow removing an active rundown + logger.warn(`Not allowing removal of previous playing segment "${segmentId}", making segment unsynced instead`) + return false + } if ( currentPartInstance?.segmentId === segmentId || (nextPartInstance?.segmentId === segmentId && isTooCloseToAutonext(currentPartInstance, false)) @@ -662,7 +668,7 @@ async function removeSegments( _changedSegmentIds: ReadonlyDeep, removedSegmentIds: ReadonlyDeep ) { - const { currentPartInstance, nextPartInstance } = await getSelectedPartInstances( + const { previousPartInstance, currentPartInstance, nextPartInstance } = await getSelectedPartInstances( context, newPlaylist, rundownsInPlaylist.map((r) => r._id) @@ -672,7 +678,7 @@ async function removeSegments( const orphanDeletedSegmentIds = new Set() const orphanHiddenSegmentIds = new Set() for (const segmentId of removedSegmentIds) { - if (canRemoveSegment(currentPartInstance, nextPartInstance, segmentId)) { + if (canRemoveSegment(previousPartInstance, currentPartInstance, nextPartInstance, segmentId)) { purgeSegmentIds.add(segmentId) } else { logger.warn( @@ -685,7 +691,7 @@ async function removeSegments( for (const segment of ingestModel.getAllSegments()) { const segmentId = segment.segment._id if (segment.segment.isHidden) { - if (!canRemoveSegment(currentPartInstance, nextPartInstance, segmentId)) { + if (!canRemoveSegment(previousPartInstance, currentPartInstance, nextPartInstance, segmentId)) { // Protect live segment from being hidden logger.warn(`Cannot hide live segment ${segmentId}, it will be orphaned`) switch (segment.segment.orphaned) { @@ -706,7 +712,7 @@ async function removeSegments( } else if (!orphanDeletedSegmentIds.has(segmentId) && segment.parts.length === 0) { // No parts in segment - if (!canRemoveSegment(currentPartInstance, nextPartInstance, segmentId)) { + if (!canRemoveSegment(previousPartInstance, currentPartInstance, nextPartInstance, segmentId)) { // Protect live segment from being hidden logger.warn(`Cannot hide live segment ${segmentId}, it will be orphaned`) orphanHiddenSegmentIds.add(segmentId) From 31f08c383581abe854ea3bd4b4b572ce44c7fe1b Mon Sep 17 00:00:00 2001 From: Johan Nyman Date: Wed, 13 Nov 2024 12:00:19 +0100 Subject: [PATCH 05/19] chore: log deleted documents, for troubleshooting --- .../implementation/DocumentChangeTracker.ts | 4 ++++ .../model/implementation/SaveIngestModel.ts | 20 +++++++++++++++++++ 2 files changed, 24 insertions(+) diff --git a/packages/job-worker/src/ingest/model/implementation/DocumentChangeTracker.ts b/packages/job-worker/src/ingest/model/implementation/DocumentChangeTracker.ts index 7cf7d4bfbf..377784191c 100644 --- a/packages/job-worker/src/ingest/model/implementation/DocumentChangeTracker.ts +++ b/packages/job-worker/src/ingest/model/implementation/DocumentChangeTracker.ts @@ -94,6 +94,10 @@ export class DocumentChangeTracker }> { } } + getDeletedIds(): TDoc['_id'][] { + return Array.from(this.#deletedIds.values()) + } + /** * Generate the mongodb BulkWrite operations for the documents known to this tracker * @returns mongodb BulkWrite operations diff --git a/packages/job-worker/src/ingest/model/implementation/SaveIngestModel.ts b/packages/job-worker/src/ingest/model/implementation/SaveIngestModel.ts index c2995869cf..20995c9b38 100644 --- a/packages/job-worker/src/ingest/model/implementation/SaveIngestModel.ts +++ b/packages/job-worker/src/ingest/model/implementation/SaveIngestModel.ts @@ -11,6 +11,9 @@ import { JobContext } from '../../../jobs' import { ExpectedPackagesStore } from './ExpectedPackagesStore' import { IngestSegmentModelImpl } from './IngestSegmentModelImpl' import { DocumentChangeTracker } from './DocumentChangeTracker' +import { logger } from '../../../logging' +import { AnyBulkWriteOperation } from 'mongodb' +import { ProtectedString } from '@sofie-automation/corelib/dist/protectedString' export class SaveIngestModelHelper { #expectedPackages = new DocumentChangeTracker() @@ -55,6 +58,23 @@ export class SaveIngestModelHelper { } commit(context: JobContext): Array> { + // Log deleted ids: + const deletedIds: { [key: string]: ProtectedString[] } = { + expectedPackages: this.#expectedPackages.getDeletedIds(), + expectedPlayoutItems: this.#expectedPlayoutItems.getDeletedIds(), + expectedMediaItems: this.#expectedMediaItems.getDeletedIds(), + segments: this.#segments.getDeletedIds(), + parts: this.#parts.getDeletedIds(), + pieces: this.#pieces.getDeletedIds(), + adLibPieces: this.#adLibPieces.getDeletedIds(), + adLibActions: this.#adLibActions.getDeletedIds(), + } + for (const [key, ids] of Object.entries[]>(deletedIds)) { + if (ids.length > 0) { + logger.debug(`Deleted ${key}: ${JSON.stringify(ids)} `) + } + } + return [ context.directCollections.ExpectedPackages.bulkWrite(this.#expectedPackages.generateWriteOps()), context.directCollections.ExpectedPlayoutItems.bulkWrite(this.#expectedPlayoutItems.generateWriteOps()), From dce18708cecdbf67af93122ece679ee7196078b7 Mon Sep 17 00:00:00 2001 From: Johan Nyman Date: Wed, 13 Nov 2024 13:25:04 +0100 Subject: [PATCH 06/19] chore: lint fix --- .../src/ingest/model/implementation/SaveIngestModel.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/job-worker/src/ingest/model/implementation/SaveIngestModel.ts b/packages/job-worker/src/ingest/model/implementation/SaveIngestModel.ts index 20995c9b38..63dbd7fd7f 100644 --- a/packages/job-worker/src/ingest/model/implementation/SaveIngestModel.ts +++ b/packages/job-worker/src/ingest/model/implementation/SaveIngestModel.ts @@ -12,7 +12,6 @@ import { ExpectedPackagesStore } from './ExpectedPackagesStore' import { IngestSegmentModelImpl } from './IngestSegmentModelImpl' import { DocumentChangeTracker } from './DocumentChangeTracker' import { logger } from '../../../logging' -import { AnyBulkWriteOperation } from 'mongodb' import { ProtectedString } from '@sofie-automation/corelib/dist/protectedString' export class SaveIngestModelHelper { From 9ef7d5e771509bdaf1d1f75a3584b5ce43c38881 Mon Sep 17 00:00:00 2001 From: Johan Nyman Date: Wed, 13 Nov 2024 13:39:49 +0100 Subject: [PATCH 07/19] Revert "fix: only run onPart/PiecePlaybackStarted/Stopped on current, next or previous parts" This reverts commit a9fe40167c32c0af40ee7b7b39d6eca405f184d8. --- .../src/playout/timings/partPlayback.ts | 24 ------------- .../src/playout/timings/piecePlayback.ts | 34 ++----------------- 2 files changed, 2 insertions(+), 56 deletions(-) diff --git a/packages/job-worker/src/playout/timings/partPlayback.ts b/packages/job-worker/src/playout/timings/partPlayback.ts index d7bd943c60..8c3fc07d16 100644 --- a/packages/job-worker/src/playout/timings/partPlayback.ts +++ b/packages/job-worker/src/playout/timings/partPlayback.ts @@ -26,18 +26,6 @@ export async function onPartPlaybackStarted( startedPlayback: Time } ): Promise { - if ( - // We only expect to be able to update the current, next, or previous part: - data.partInstanceId !== playoutModel.playlist.currentPartInfo?.partInstanceId || - data.partInstanceId !== playoutModel.playlist.nextPartInfo?.partInstanceId || - data.partInstanceId !== playoutModel.playlist.previousPartInfo?.partInstanceId - ) { - logger.debug( - `onPartPlaybackStarted PartInstance "${data.partInstanceId}" is neither current, next nor previous (current: ${playoutModel.playlist.currentPartInfo?.partInstanceId}, next: ${playoutModel.playlist.nextPartInfo?.partInstanceId}, previous: ${playoutModel.playlist.previousPartInfo?.partInstanceId}, timestamp: ${data.startedPlayback}` - ) - return - } - const playingPartInstance = playoutModel.getPartInstance(data.partInstanceId) if (!playingPartInstance) throw new Error( @@ -148,18 +136,6 @@ export function onPartPlaybackStopped( stoppedPlayback: Time } ): void { - if ( - // We only expect to be able to update the current, next, or previous part: - data.partInstanceId !== playoutModel.playlist.currentPartInfo?.partInstanceId || - data.partInstanceId !== playoutModel.playlist.nextPartInfo?.partInstanceId || - data.partInstanceId !== playoutModel.playlist.previousPartInfo?.partInstanceId - ) { - logger.debug( - `onPartPlaybackStopped PartInstance "${data.partInstanceId}" is neither current, next nor previous (current: ${playoutModel.playlist.currentPartInfo?.partInstanceId}, next: ${playoutModel.playlist.nextPartInfo?.partInstanceId}, previous: ${playoutModel.playlist.previousPartInfo?.partInstanceId}, timestamp: ${data.stoppedPlayback}` - ) - return - } - const playlist = playoutModel.playlist const partInstance = playoutModel.getPartInstance(data.partInstanceId) diff --git a/packages/job-worker/src/playout/timings/piecePlayback.ts b/packages/job-worker/src/playout/timings/piecePlayback.ts index ebd56a1f12..bd1d76b8a3 100644 --- a/packages/job-worker/src/playout/timings/piecePlayback.ts +++ b/packages/job-worker/src/playout/timings/piecePlayback.ts @@ -23,18 +23,6 @@ export function onPiecePlaybackStarted( ): void { const playlist = playoutModel.playlist - if ( - // We only expect to be able to update the current, next, or previous part: - data.partInstanceId !== playoutModel.playlist.currentPartInfo?.partInstanceId || - data.partInstanceId !== playoutModel.playlist.nextPartInfo?.partInstanceId || - data.partInstanceId !== playoutModel.playlist.previousPartInfo?.partInstanceId - ) { - logger.debug( - `onPiecePlaybackStarted PartInstance "${data.partInstanceId}" is neither current, next nor previous (current: ${playoutModel.playlist.currentPartInfo?.partInstanceId}, next: ${playoutModel.playlist.nextPartInfo?.partInstanceId}, previous: ${playoutModel.playlist.previousPartInfo?.partInstanceId}), timestamp: ${data.startedPlayback}` - ) - return - } - const partInstance = playoutModel.getPartInstance(data.partInstanceId) if (!partInstance) { if (!playlist.activationId) { @@ -50,10 +38,7 @@ export function onPiecePlaybackStarted( if (!playlist.activationId) { logger.warn(`onPiecePlaybackStarted: Received for inactive RundownPlaylist "${playlist._id}"`) } else { - const otherPieceInstanceIds = partInstance.pieceInstances.map((p) => p.pieceInstance._id) - throw new Error( - `PieceInstance "${data.pieceInstanceId}" in PartInstance "${partInstance.partInstance._id}" in RundownPlaylist "${playlist._id}" not found! (other pieceInstanceIds in PartInstance: ${otherPieceInstanceIds})` - ) + throw new Error(`PieceInstance "${data.partInstanceId}" in RundownPlaylist "${playlist._id}" not found!`) } return } @@ -90,18 +75,6 @@ export function onPiecePlaybackStopped( ): void { const playlist = playoutModel.playlist - if ( - // We only expect to be able to update the current, next, or previous part: - data.partInstanceId !== playoutModel.playlist.currentPartInfo?.partInstanceId || - data.partInstanceId !== playoutModel.playlist.nextPartInfo?.partInstanceId || - data.partInstanceId !== playoutModel.playlist.previousPartInfo?.partInstanceId - ) { - logger.debug( - `onPiecePlaybackStarted PartInstance "${data.partInstanceId}" is neither current, next nor previous (current: ${playoutModel.playlist.currentPartInfo?.partInstanceId}, next: ${playoutModel.playlist.nextPartInfo?.partInstanceId}, previous: ${playoutModel.playlist.previousPartInfo?.partInstanceId}), timestamp: ${data.stoppedPlayback}` - ) - return - } - const partInstance = playoutModel.getPartInstance(data.partInstanceId) if (!partInstance) { // PartInstance not found, so we can rely on the onPartPlaybackStopped callback erroring @@ -113,10 +86,7 @@ export function onPiecePlaybackStopped( if (!playlist.activationId) { logger.warn(`onPiecePlaybackStopped: Received for inactive RundownPlaylist "${playlist._id}"`) } else { - const otherPieceInstanceIds = partInstance.pieceInstances.map((p) => p.pieceInstance._id) - throw new Error( - `PieceInstance "${data.pieceInstanceId}" in PartInstance "${partInstance.partInstance._id}" in RundownPlaylist "${playlist._id}" not found! (other pieceInstanceIds in PartInstance: ${otherPieceInstanceIds})` - ) + throw new Error(`PieceInstance "${data.partInstanceId}" in RundownPlaylist "${playlist._id}" not found!`) } return } From b76915791b2e0b38e7cc1adf963d772236a45017 Mon Sep 17 00:00:00 2001 From: Johan Nyman Date: Thu, 14 Nov 2024 10:07:51 +0100 Subject: [PATCH 08/19] fix: updatePartInstancesSegmentIds: take into account when multiple segments have been merged into one. Also ensure that the bulkWrite uses a db index --- packages/job-worker/src/ingest/commit.ts | 88 ++++++++++++++++-------- 1 file changed, 61 insertions(+), 27 deletions(-) diff --git a/packages/job-worker/src/ingest/commit.ts b/packages/job-worker/src/ingest/commit.ts index 257044362f..7eccb296d6 100644 --- a/packages/job-worker/src/ingest/commit.ts +++ b/packages/job-worker/src/ingest/commit.ts @@ -43,6 +43,7 @@ import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part' import { DatabasePersistedModel } from '../modelBase' import { updateSegmentIdsForAdlibbedPartInstances } from './commit/updateSegmentIdsForAdlibbedPartInstances' import { stringifyError } from '@sofie-automation/shared-lib/dist/lib/stringifyError' +import { AnyBulkWriteOperation } from 'mongodb' export type BeforePartMapItem = { id: PartId; rank: number } export type BeforeIngestOperationPartMap = ReadonlyMap> @@ -301,26 +302,32 @@ async function updatePartInstancesSegmentIds( renamedSegments: ReadonlyMap | null, beforePartMap: BeforeIngestOperationPartMap ) { - // A set of rules which can be translated to mongo queries for PartInstances to update + /** + * Maps new SegmentId -> + * A set of rules which can be translated to mongo queries for PartInstances to update + */ const renameRules = new Map< SegmentId, { + /** Parts that have been moved to the new SegmentId */ partIds: PartId[] - fromSegmentId: SegmentId | null + /** Segments that have been renamed to the new SegmentId */ + fromSegmentIds: SegmentId[] } >() // Add whole segment renames to the set of rules if (renamedSegments) { for (const [fromSegmentId, toSegmentId] of renamedSegments) { - const rule = renameRules.get(toSegmentId) ?? { partIds: [], fromSegmentId: null } + const rule = renameRules.get(toSegmentId) ?? { partIds: [], fromSegmentIds: [] } renameRules.set(toSegmentId, rule) - rule.fromSegmentId = fromSegmentId + rule.fromSegmentIds.push(fromSegmentId) } } - // Reverse the structure + // Reverse the Map structure + /** Maps Part -> SegmentId-of-the-part-before-ingest-changes */ const beforePartSegmentIdMap = new Map() for (const [segmentId, partItems] of beforePartMap.entries()) { for (const partItem of partItems) { @@ -331,8 +338,11 @@ async function updatePartInstancesSegmentIds( // Some parts may have gotten a different segmentId to the base rule, so track those seperately in the rules for (const partModel of ingestModel.getAllOrderedParts()) { const oldSegmentId = beforePartSegmentIdMap.get(partModel.part._id) + if (oldSegmentId && oldSegmentId !== partModel.part.segmentId) { - const rule = renameRules.get(partModel.part.segmentId) ?? { partIds: [], fromSegmentId: null } + // The part has moved to another segment, add a rule to update its corresponding PartInstances: + + const rule = renameRules.get(partModel.part.segmentId) ?? { partIds: [], fromSegmentIds: [] } renameRules.set(partModel.part.segmentId, rule) rule.partIds.push(partModel.part._id) @@ -341,30 +351,52 @@ async function updatePartInstancesSegmentIds( // Perform a mongo update to modify the PartInstances if (renameRules.size > 0) { - await context.directCollections.PartInstances.bulkWrite( - Array.from(renameRules.entries()).map(([newSegmentId, rule]) => ({ - updateMany: { - filter: { - $or: _.compact([ - rule.fromSegmentId - ? { - segmentId: rule.fromSegmentId, - } - : undefined, - { - 'part._id': { $in: rule.partIds }, + const rulesInOrder = Array.from(renameRules.entries()).sort((a, b) => { + // Ensure that the ones with partIds are processed last, + // as that should take precedence. + + if (a[1].partIds.length && !b[1].partIds.length) return 1 + if (!a[1].partIds.length && b[1].partIds.length) return -1 + return 0 + }) + + const writeOps: AnyBulkWriteOperation[] = [] + + for (const [newSegmentId, rule] of rulesInOrder) { + if (rule.fromSegmentIds.length) { + writeOps.push({ + updateMany: { + filter: { + rundownId: ingestModel.rundownId, + segmentId: { $in: rule.fromSegmentIds }, + }, + update: { + $set: { + segmentId: newSegmentId, + 'part.segmentId': newSegmentId, }, - ]), + }, }, - update: { - $set: { - segmentId: newSegmentId, - 'part.segmentId': newSegmentId, + }) + } + if (rule.partIds.length) { + writeOps.push({ + updateMany: { + filter: { + rundownId: ingestModel.rundownId, + 'part._id': { $in: rule.partIds }, + }, + update: { + $set: { + segmentId: newSegmentId, + 'part.segmentId': newSegmentId, + }, }, }, - }, - })) - ) + }) + } + } + if (writeOps.length) await context.directCollections.PartInstances.bulkWrite(writeOps) } } @@ -691,8 +723,10 @@ async function removeSegments( for (const segment of ingestModel.getAllSegments()) { const segmentId = segment.segment._id if (segment.segment.isHidden) { + // Blueprints want to hide the Segment + if (!canRemoveSegment(previousPartInstance, currentPartInstance, nextPartInstance, segmentId)) { - // Protect live segment from being hidden + // The Segment is live, so we need to protect it from being hidden logger.warn(`Cannot hide live segment ${segmentId}, it will be orphaned`) switch (segment.segment.orphaned) { case SegmentOrphanedReason.DELETED: From e752e23de4d0b0e13ceafe393c3f6698311423df Mon Sep 17 00:00:00 2001 From: Johan Nyman Date: Thu, 14 Nov 2024 10:08:25 +0100 Subject: [PATCH 09/19] chore: doc and logging for troubleshooting --- packages/corelib/src/dataModel/Segment.ts | 2 +- packages/job-worker/src/ingest/commit.ts | 31 +++++++++++++++++++ .../src/ingest/model/IngestSegmentModel.ts | 2 +- 3 files changed, 33 insertions(+), 2 deletions(-) diff --git a/packages/corelib/src/dataModel/Segment.ts b/packages/corelib/src/dataModel/Segment.ts index 76996af431..a5f7c26e30 100644 --- a/packages/corelib/src/dataModel/Segment.ts +++ b/packages/corelib/src/dataModel/Segment.ts @@ -5,7 +5,7 @@ import { SegmentNote } from './Notes' export enum SegmentOrphanedReason { /** Segment is deleted from the NRCS but we still need it */ DELETED = 'deleted', - /** Segment should be hidden, but it is still playing */ + /** Blueprints want the Segment to be hidden, but it is still playing so is must not be hidden right now. */ HIDDEN = 'hidden', /** Segment is owned by playout, and is for AdlibTesting in its rundown */ ADLIB_TESTING = 'adlib-testing', diff --git a/packages/job-worker/src/ingest/commit.ts b/packages/job-worker/src/ingest/commit.ts index 7eccb296d6..83e09b6085 100644 --- a/packages/job-worker/src/ingest/commit.ts +++ b/packages/job-worker/src/ingest/commit.ts @@ -178,6 +178,9 @@ export async function CommitIngestOperation( // Ensure any adlibbed parts are updated to follow the segmentId of the previous part await updateSegmentIdsForAdlibbedPartInstances(context, ingestModel, beforePartMap) + if (data.renamedSegments && data.renamedSegments.size > 0) { + logger.debug(`Renamed segments: ${JSON.stringify(Array.from(data.renamedSegments.entries()))}`) + } // ensure instances have matching segmentIds with the parts await updatePartInstancesSegmentIds(context, ingestModel, data.renamedSegments, beforePartMap) @@ -397,6 +400,34 @@ async function updatePartInstancesSegmentIds( } } if (writeOps.length) await context.directCollections.PartInstances.bulkWrite(writeOps) + + // Double check that there are no parts using the old segment ids: + const oldSegmentIds = Array.from(renameRules.keys()) + const [badPartInstances, badParts] = await Promise.all([ + await context.directCollections.PartInstances.findFetch({ + rundownId: ingestModel.rundownId, + segmentId: { $in: oldSegmentIds }, + }), + await context.directCollections.Parts.findFetch({ + rundownId: ingestModel.rundownId, + segmentId: { $in: oldSegmentIds }, + }), + ]) + if (badPartInstances.length > 0) { + logger.error( + `updatePartInstancesSegmentIds: Failed to update all PartInstances using old SegmentIds "${JSON.stringify( + oldSegmentIds + )}": ${JSON.stringify(badPartInstances)}, writeOps: ${JSON.stringify(writeOps)}` + ) + } + + if (badParts.length > 0) { + logger.error( + `updatePartInstancesSegmentIds: Failed to update all Parts using old SegmentIds "${JSON.stringify( + oldSegmentIds + )}": ${JSON.stringify(badParts)}, writeOps: ${JSON.stringify(writeOps)}` + ) + } } } diff --git a/packages/job-worker/src/ingest/model/IngestSegmentModel.ts b/packages/job-worker/src/ingest/model/IngestSegmentModel.ts index d708cb5228..74010848d0 100644 --- a/packages/job-worker/src/ingest/model/IngestSegmentModel.ts +++ b/packages/job-worker/src/ingest/model/IngestSegmentModel.ts @@ -67,7 +67,7 @@ export interface IngestSegmentModel extends IngestSegmentModelReadonly { setOrphaned(orphaned: SegmentOrphanedReason | undefined): void /** - * Mark this Part as being hidden + * Mark this Segment as being hidden * @param hidden New hidden state */ setHidden(hidden: boolean): void From 9e242997ca0bff68f90c857e140f210283dc920a Mon Sep 17 00:00:00 2001 From: Johan Nyman Date: Wed, 4 Dec 2024 08:59:49 +0100 Subject: [PATCH 10/19] chore: fix bad logic in troubleshooting code --- packages/job-worker/src/ingest/commit.ts | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/packages/job-worker/src/ingest/commit.ts b/packages/job-worker/src/ingest/commit.ts index 83e09b6085..e91025c625 100644 --- a/packages/job-worker/src/ingest/commit.ts +++ b/packages/job-worker/src/ingest/commit.ts @@ -402,7 +402,10 @@ async function updatePartInstancesSegmentIds( if (writeOps.length) await context.directCollections.PartInstances.bulkWrite(writeOps) // Double check that there are no parts using the old segment ids: - const oldSegmentIds = Array.from(renameRules.keys()) + const oldSegmentIds: SegmentId[] = [] + for (const renameRule of renameRules.values()) { + oldSegmentIds.push(...renameRule.fromSegmentIds) + } const [badPartInstances, badParts] = await Promise.all([ await context.directCollections.PartInstances.findFetch({ rundownId: ingestModel.rundownId, From b20367b2f40b4813ae9a4da545ea1d485e8eb114 Mon Sep 17 00:00:00 2001 From: Johan Nyman Date: Wed, 4 Dec 2024 09:41:38 +0100 Subject: [PATCH 11/19] chore: add logging for troubleshooting --- packages/job-worker/src/ingest/commit.ts | 2 ++ packages/job-worker/src/playout/infinites.ts | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+) diff --git a/packages/job-worker/src/ingest/commit.ts b/packages/job-worker/src/ingest/commit.ts index e91025c625..2c04e9f7c4 100644 --- a/packages/job-worker/src/ingest/commit.ts +++ b/packages/job-worker/src/ingest/commit.ts @@ -365,6 +365,8 @@ async function updatePartInstancesSegmentIds( const writeOps: AnyBulkWriteOperation[] = [] + logger.debug(`updatePartInstancesSegmentIds: renameRules: ${JSON.stringify(renameRules)}`) + for (const [newSegmentId, rule] of rulesInOrder) { if (rule.fromSegmentIds.length) { writeOps.push({ diff --git a/packages/job-worker/src/playout/infinites.ts b/packages/job-worker/src/playout/infinites.ts index 222791d0f1..d02566521a 100644 --- a/packages/job-worker/src/playout/infinites.ts +++ b/packages/job-worker/src/playout/infinites.ts @@ -345,6 +345,24 @@ export function getPieceInstancesForPart( }) .catch((e) => logger.error(e)) + context.directCollections.PartInstances.findOne({ + _id: playingPartInstance.partInstance._id, + }) + .then((partInstance) => { + if (!partInstance) { + logger.error( + `TROUBLESHOOT: Segment not found, no partInstance "${playingPartInstance.partInstance._id}" in DB` + ) + } else { + logger.error( + `TROUBLESHOOT: Segment not found, partInstance "${partInstance._id}": ${JSON.stringify( + partInstance + )}` + ) + } + }) + .catch((e) => logger.error(e)) + throw new Error( `Segment "${playingPartInstance.partInstance.segmentId}" in Rundown "${ playingRundown.rundown._id From 8c16ce81558b7b04a5ee528e2c3c6681c29779ee Mon Sep 17 00:00:00 2001 From: Jan Starzak Date: Wed, 4 Dec 2024 14:12:53 +0100 Subject: [PATCH 12/19] feat: add more logging Co-authored-by: Johan Nyman --- packages/job-worker/src/ingest/commit.ts | 3 ++- .../job-worker/src/ingest/mosDevice/diff.ts | 3 +++ .../model/implementation/PlayoutModelImpl.ts | 27 +++++++++++++++---- packages/job-worker/src/playout/setNext.ts | 4 ++- 4 files changed, 30 insertions(+), 7 deletions(-) diff --git a/packages/job-worker/src/ingest/commit.ts b/packages/job-worker/src/ingest/commit.ts index 2c04e9f7c4..717eca7db6 100644 --- a/packages/job-worker/src/ingest/commit.ts +++ b/packages/job-worker/src/ingest/commit.ts @@ -179,7 +179,7 @@ export async function CommitIngestOperation( await updateSegmentIdsForAdlibbedPartInstances(context, ingestModel, beforePartMap) if (data.renamedSegments && data.renamedSegments.size > 0) { - logger.debug(`Renamed segments: ${JSON.stringify(Array.from(data.renamedSegments.entries()))}`) + logger.verbose(`Renamed segments: ${JSON.stringify(Array.from(data.renamedSegments.entries()))}`) } // ensure instances have matching segmentIds with the parts await updatePartInstancesSegmentIds(context, ingestModel, data.renamedSegments, beforePartMap) @@ -820,6 +820,7 @@ async function removeSegments( }) } for (const segmentId of purgeSegmentIds) { + logger.debug(`IngestModel: Removing segment "${segmentId}"`) ingestModel.removeSegment(segmentId) } } diff --git a/packages/job-worker/src/ingest/mosDevice/diff.ts b/packages/job-worker/src/ingest/mosDevice/diff.ts index 32f42a7d5d..e498419f7a 100644 --- a/packages/job-worker/src/ingest/mosDevice/diff.ts +++ b/packages/job-worker/src/ingest/mosDevice/diff.ts @@ -11,6 +11,7 @@ import { IngestSegment } from '@sofie-automation/blueprints-integration' import { SegmentOrphanedReason } from '@sofie-automation/corelib/dist/dataModel/Segment' import { CommitIngestData } from '../lock' import { IngestSegmentModel } from '../model/IngestSegmentModel' +import { logger } from '../../logging' /** * Update the Ids of Segments based on new Ingest data @@ -158,9 +159,11 @@ function applyExternalIdDiff( } // Remove the old Segment and it's contents, the new one will be generated shortly + logger.debug(`applyExternalIdDiff: Marking Segment for removing "${oldSegmentId}"`) ingestModel.removeSegment(oldSegmentId) } else { // Perform the rename + logger.debug(`applyExternalIdDiff: Marking Segment for renaming "${oldSegmentId}" -> "${newSegmentId}"`) ingestModel.changeSegmentId(oldSegmentId, newSegmentId) } } diff --git a/packages/job-worker/src/playout/model/implementation/PlayoutModelImpl.ts b/packages/job-worker/src/playout/model/implementation/PlayoutModelImpl.ts index 6916022f36..cb2882c6f1 100644 --- a/packages/job-worker/src/playout/model/implementation/PlayoutModelImpl.ts +++ b/packages/job-worker/src/playout/model/implementation/PlayoutModelImpl.ts @@ -751,12 +751,29 @@ export class PlayoutModelImpl extends PlayoutModelReadonlyImpl implements Playou if (this.rundownsImpl.find((rd) => rd.AdlibTestingSegmentHasChanged)) logOrThrowError(new Error(`Failed no changes in model assertion, an AdlibTesting Segment has been changed`)) - if ( - Array.from(this.allPartInstances.values()).find( - (part) => !part || part.partInstanceHasChanges || part.changedPieceInstanceIds().length > 0 - ) + const changedPartInstances = Array.from(this.allPartInstances.entries()).filter( + ([_, partInstance]) => + partInstance !== null && // null values mean a PartInstance was removed, which doesn't need to be tracked + (partInstance.partInstanceHasChanges || partInstance.changedPieceInstanceIds().length > 0) ) - logOrThrowError(new Error(`Failed no changes in model assertion, a PartInstance has been changed`)) + + if (changedPartInstances.length > 0) { + logOrThrowError( + new Error( + `Failed no changes in model assertion, PartInstances has been changed: ${JSON.stringify( + changedPartInstances.map( + ([id, pi]) => + `${id}: ` + + (!pi + ? 'null' + : `partInstanceHasChanges: ${ + pi.partInstanceHasChanges + }, changedPieceInstanceIds: ${JSON.stringify(pi.changedPieceInstanceIds())}`) + ) + )}` + ) + ) + } if (span) span.end() } diff --git a/packages/job-worker/src/playout/setNext.ts b/packages/job-worker/src/playout/setNext.ts index ab6fcf7514..5614299031 100644 --- a/packages/job-worker/src/playout/setNext.ts +++ b/packages/job-worker/src/playout/setNext.ts @@ -279,9 +279,11 @@ async function cleanupOrphanedItems(context: JobContext, playoutModel: PlayoutMo const selectedPartInstancesSegmentIds = new Set() + const previousPartInstance = playoutModel.previousPartInstance?.partInstance const currentPartInstance = playoutModel.currentPartInstance?.partInstance const nextPartInstance = playoutModel.nextPartInstance?.partInstance + if (previousPartInstance) selectedPartInstancesSegmentIds.add(previousPartInstance.segmentId) if (currentPartInstance) selectedPartInstancesSegmentIds.add(currentPartInstance.segmentId) if (nextPartInstance) selectedPartInstancesSegmentIds.add(nextPartInstance.segmentId) @@ -291,7 +293,7 @@ async function cleanupOrphanedItems(context: JobContext, playoutModel: PlayoutMo const alterSegmentsFromRundowns = new Map() for (const segment of segments) { - // If the segment is orphaned and not the segment for the next or current partinstance + // If the segment is orphaned and not the segment for the previous, current or next partInstance if (!selectedPartInstancesSegmentIds.has(segment.segment._id)) { let rundownSegments = alterSegmentsFromRundowns.get(segment.segment.rundownId) if (!rundownSegments) { From 5e53676ab1777ac7bc08379e9b8ff810db38da8d Mon Sep 17 00:00:00 2001 From: Jan Starzak Date: Mon, 9 Dec 2024 15:57:38 +0100 Subject: [PATCH 13/19] chore: improve code comments around the added logging --- packages/job-worker/src/ingest/commit.ts | 4 ++++ packages/job-worker/src/ingest/lock.ts | 7 +++++-- packages/job-worker/src/playout/adlibJobs.ts | 1 + packages/job-worker/src/playout/infinites.ts | 4 ++++ 4 files changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/job-worker/src/ingest/commit.ts b/packages/job-worker/src/ingest/commit.ts index 89810dc692..461dcf844e 100644 --- a/packages/job-worker/src/ingest/commit.ts +++ b/packages/job-worker/src/ingest/commit.ts @@ -178,9 +178,13 @@ export async function CommitIngestOperation( // Ensure any adlibbed parts are updated to follow the segmentId of the previous part await updateSegmentIdsForAdlibbedPartInstances(context, ingestModel, beforePartMap) + // TODO: This whole section can probably be removed later, it's really unneccessary in the grand scheme of + // things, it's here only to debug some problems if (data.renamedSegments && data.renamedSegments.size > 0) { logger.verbose(`Renamed segments: ${JSON.stringify(Array.from(data.renamedSegments.entries()))}`) } + // End of temporary section + // ensure instances have matching segmentIds with the parts await updatePartInstancesSegmentIds(context, ingestModel, data.renamedSegments, beforePartMap) diff --git a/packages/job-worker/src/ingest/lock.ts b/packages/job-worker/src/ingest/lock.ts index 67999b33be..544b4e8dce 100644 --- a/packages/job-worker/src/ingest/lock.ts +++ b/packages/job-worker/src/ingest/lock.ts @@ -22,8 +22,11 @@ export interface CommitIngestData { removedSegmentIds: SegmentId[] /** * Segments that had their ids changed. This helps then be orphaned in the correct place - * eg, whole segment is renamed and middle part deleted - * Note: Only supported for MOS, not 'normal' ingest operations + * eg, whole segment is renamed and middle part deleted. + * + * Maps fromSegmentId to toSegmentId. + * + * _Note: Only supported for MOS, not 'normal' ingest operations_ */ renamedSegments: Map | null diff --git a/packages/job-worker/src/playout/adlibJobs.ts b/packages/job-worker/src/playout/adlibJobs.ts index 31eeb8382c..b7659e385b 100644 --- a/packages/job-worker/src/playout/adlibJobs.ts +++ b/packages/job-worker/src/playout/adlibJobs.ts @@ -438,6 +438,7 @@ export async function handleDisableNextPiece(context: JobContext, data: DisableN return sortedPieces.find((piece) => { return ( + piece.pieceInstance.piece.enable.start !== 'now' && piece.pieceInstance.piece.enable.start >= nowInPart && ((!data.undo && !piece.pieceInstance.disabled) || (data.undo && piece.pieceInstance.disabled)) ) diff --git a/packages/job-worker/src/playout/infinites.ts b/packages/job-worker/src/playout/infinites.ts index 222791d0f1..9761a1315f 100644 --- a/packages/job-worker/src/playout/infinites.ts +++ b/packages/job-worker/src/playout/infinites.ts @@ -332,6 +332,9 @@ export function getPieceInstancesForPart( playingSegment = playingRundown.getSegment(playingPartInstance.partInstance.segmentId) if (!playingSegment) { + // Double check that there are no parts using the old segment ids: + // TODO: This whole section can probably be removed later, it's really unneccessary in the grand scheme of + // things, it's here only to debug some problems const rundownId = playingRundown.rundown._id context.directCollections.Segments.findFetch({ rundownId: rundownId, @@ -344,6 +347,7 @@ export function getPieceInstancesForPart( ) }) .catch((e) => logger.error(e)) + // End of temporary section throw new Error( `Segment "${playingPartInstance.partInstance.segmentId}" in Rundown "${ From a56c1df8cf585ffea2d11b54220882e79dace691 Mon Sep 17 00:00:00 2001 From: Johan Nyman Date: Mon, 16 Dec 2024 13:53:09 +0100 Subject: [PATCH 14/19] chore: improve logging, for troubleshooting --- packages/job-worker/src/ingest/commit.ts | 34 ++++++++++++++++--- .../src/ingest/syncChangesToPartInstance.ts | 30 +++++++++++----- packages/job-worker/src/playout/infinites.ts | 2 +- 3 files changed, 52 insertions(+), 14 deletions(-) diff --git a/packages/job-worker/src/ingest/commit.ts b/packages/job-worker/src/ingest/commit.ts index 461dcf844e..2f5fc7b38a 100644 --- a/packages/job-worker/src/ingest/commit.ts +++ b/packages/job-worker/src/ingest/commit.ts @@ -674,10 +674,27 @@ async function getSelectedPartInstances( }) : [] + const currentPartInstance = instances.find((inst) => inst._id === playlist.currentPartInfo?.partInstanceId) + const nextPartInstance = instances.find((inst) => inst._id === playlist.nextPartInfo?.partInstanceId) + const previousPartInstance = instances.find((inst) => inst._id === playlist.previousPartInfo?.partInstanceId) + + if (playlist.currentPartInfo?.partInstanceId && !currentPartInstance) + logger.error( + `playlist.currentPartInfo is set, but PartInstance "${playlist.currentPartInfo?.partInstanceId}" was not found!` + ) + if (playlist.nextPartInfo?.partInstanceId && !nextPartInstance) + logger.error( + `playlist.nextPartInfo is set, but PartInstance "${playlist.nextPartInfo?.partInstanceId}" was not found!` + ) + if (playlist.previousPartInfo?.partInstanceId && !previousPartInstance) + logger.error( + `playlist.previousPartInfo is set, but PartInstance "${playlist.previousPartInfo?.partInstanceId}" was not found!` + ) + return { - currentPartInstance: instances.find((inst) => inst._id === playlist.currentPartInfo?.partInstanceId), - nextPartInstance: instances.find((inst) => inst._id === playlist.nextPartInfo?.partInstanceId), - previousPartInstance: instances.find((inst) => inst._id === playlist.previousPartInfo?.partInstanceId), + currentPartInstance, + nextPartInstance, + previousPartInstance, } } @@ -827,7 +844,16 @@ async function removeSegments( }) } for (const segmentId of purgeSegmentIds) { - logger.debug(`IngestModel: Removing segment "${segmentId}"`) + logger.debug( + `IngestModel: Removing segment "${segmentId}" (` + + `previousPartInfo?.partInstanceId: ${newPlaylist.previousPartInfo?.partInstanceId},` + + `currentPartInfo?.partInstanceId: ${newPlaylist.currentPartInfo?.partInstanceId},` + + `nextPartInfo?.partInstanceId: ${newPlaylist.nextPartInfo?.partInstanceId},` + + `previousPartInstance.segmentId: ${!previousPartInstance ? 'N/A' : previousPartInstance.segmentId},` + + `currentPartInstance.segmentId: ${!currentPartInstance ? 'N/A' : currentPartInstance.segmentId},` + + `nextPartInstance.segmentId: ${!nextPartInstance ? 'N/A' : nextPartInstance.segmentId}` + + `)` + ) ingestModel.removeSegment(segmentId) } } diff --git a/packages/job-worker/src/ingest/syncChangesToPartInstance.ts b/packages/job-worker/src/ingest/syncChangesToPartInstance.ts index 08beafdf9b..e83b9bcd46 100644 --- a/packages/job-worker/src/ingest/syncChangesToPartInstance.ts +++ b/packages/job-worker/src/ingest/syncChangesToPartInstance.ts @@ -33,6 +33,7 @@ import { validateAdlibTestingPartInstanceProperties } from '../playout/adlibTest import { ReadonlyDeep } from 'type-fest' import { convertIngestModelToPlayoutRundownWithSegments } from './commit' import { PlayoutRundownModel } from '../playout/model/PlayoutRundownModel' +import { PieceInstance } from '@sofie-automation/corelib/dist/dataModel/PieceInstance' type PlayStatus = 'previous' | 'current' | 'next' type SyncedInstance = { @@ -143,15 +144,26 @@ export async function syncChangesToPartInstances( if (!playoutRundownModelForPart) throw new Error(`Internal Error: playoutRundownModelForPart is undefined (it should never be)`) - const proposedPieceInstances = getPieceInstancesForPart( - context, - playoutModel, - previousPartInstance, - playoutRundownModelForPart, - part, - await piecesThatMayBeActive, - existingPartInstance.partInstance._id - ) + // TMP: wrap in try/catch for troubleshooting: + let proposedPieceInstances: PieceInstance[] = [] + try { + proposedPieceInstances = getPieceInstancesForPart( + context, + playoutModel, + previousPartInstance, + playoutRundownModelForPart, + part, + await piecesThatMayBeActive, + existingPartInstance.partInstance._id + ) + } catch (e) { + + logger.error(`TROUBLESHOOTING: currentPartInstance: ${JSON.stringify(playoutModel.currentPartInstance)}`) + logger.error(`TROUBLESHOOTING: nextPartInstance: ${JSON.stringify(playoutModel.nextPartInstance)}`) + logger.error(`TROUBLESHOOTING: previousPartInstance: ${JSON.stringify(playoutModel.previousPartInstance)}`) + + throw e + } logger.info(`Syncing ingest changes for part: ${partId} (orphaned: ${!!newPart})`) diff --git a/packages/job-worker/src/playout/infinites.ts b/packages/job-worker/src/playout/infinites.ts index 9761a1315f..daf9093381 100644 --- a/packages/job-worker/src/playout/infinites.ts +++ b/packages/job-worker/src/playout/infinites.ts @@ -352,7 +352,7 @@ export function getPieceInstancesForPart( throw new Error( `Segment "${playingPartInstance.partInstance.segmentId}" in Rundown "${ playingRundown.rundown._id - }" not found! (other segments: ${JSON.stringify(playingRundown.segments.map((s) => s.segment._id))})` + }" not found! (partInstanceId: "${playingPartInstance.partInstance._id}", other segments: ${JSON.stringify(playingRundown.segments.map((s) => s.segment._id))})` ) } } From 0d2b84451751a859d6a3d67e0f3083c735905578 Mon Sep 17 00:00:00 2001 From: Jan Starzak Date: Thu, 19 Dec 2024 16:11:43 +0100 Subject: [PATCH 15/19] fix(PoGw): filter log output to ensure that message field in JSONL output is never an object --- packages/live-status-gateway/src/index.ts | 10 ++++++++++ packages/playout-gateway/src/index.ts | 10 ++++++++++ 2 files changed, 20 insertions(+) diff --git a/packages/live-status-gateway/src/index.ts b/packages/live-status-gateway/src/index.ts index 89094e926c..b5fdb0d900 100644 --- a/packages/live-status-gateway/src/index.ts +++ b/packages/live-status-gateway/src/index.ts @@ -12,6 +12,16 @@ const myFormat = combine( splat(), printf((obj) => { obj.localTimestamp = new Date().toISOString() + // Prevent errors and other objects to be inserted as Objects into message field + if (typeof obj.message === 'object') { + if (obj.message instanceof Error) { + const errorObj = obj.message + obj.message = errorObj.message + obj.details = errorObj + } else { + obj.message = JSON.stringify(obj.message) + } + } return JSON.stringify(obj) // make single line }) ) diff --git a/packages/playout-gateway/src/index.ts b/packages/playout-gateway/src/index.ts index b47a3baaac..1f6d955d42 100644 --- a/packages/playout-gateway/src/index.ts +++ b/packages/playout-gateway/src/index.ts @@ -12,6 +12,16 @@ const myFormat = combine( splat(), printf((obj) => { obj.localTimestamp = new Date().toISOString() + // Prevent errors and other objects to be inserted as Objects into message field + if (typeof obj.message === 'object') { + if (obj.message instanceof Error) { + const errorObj = obj.message + obj.message = errorObj.message + obj.details = errorObj + } else { + obj.message = JSON.stringify(obj.message) + } + } return JSON.stringify(obj) // make single line }) ) From b1045f992d975f747abe038047c0c0912ab88598 Mon Sep 17 00:00:00 2001 From: Johan Nyman Date: Mon, 13 Jan 2025 14:35:57 +0100 Subject: [PATCH 16/19] fix: set nextPartInstance to null if it's referring to a Segment that has been removed --- packages/job-worker/src/ingest/commit.ts | 23 +++++++++++++++---- .../src/ingest/syncChangesToPartInstance.ts | 9 +++++--- 2 files changed, 25 insertions(+), 7 deletions(-) diff --git a/packages/job-worker/src/ingest/commit.ts b/packages/job-worker/src/ingest/commit.ts index 2f5fc7b38a..f39ecc7d47 100644 --- a/packages/job-worker/src/ingest/commit.ts +++ b/packages/job-worker/src/ingest/commit.ts @@ -224,6 +224,8 @@ export async function CommitIngestOperation( const pSaveIngest = ingestModel.saveAllToDatabase() pSaveIngest.catch(() => null) // Ensure promise isn't reported as unhandled + ensureNextPartInstanceIsNotDeleted(playoutModel) + await validateAdlibTestingSegment(context, playoutModel) try { @@ -277,14 +279,18 @@ function canRemoveSegment( logger.warn(`Not allowing removal of previous playing segment "${segmentId}", making segment unsynced instead`) return false } - if ( - currentPartInstance?.segmentId === segmentId || - (nextPartInstance?.segmentId === segmentId && isTooCloseToAutonext(currentPartInstance, false)) - ) { + if (currentPartInstance?.segmentId === segmentId) { // Don't allow removing an active rundown logger.warn(`Not allowing removal of current playing segment "${segmentId}", making segment unsynced instead`) return false } + if (nextPartInstance?.segmentId === segmentId && isTooCloseToAutonext(currentPartInstance, false)) { + // Don't allow removing an active rundown + logger.warn( + `Not allowing removal of nexted segment "${segmentId}", because it's too close to an auto-next, making segment unsynced instead` + ) + return false + } if (nextPartInstance?.segmentId === segmentId && nextPartInstance.orphaned === 'adlib-part') { // Don't allow removing an active rundown @@ -863,3 +869,12 @@ async function validateAdlibTestingSegment(_context: JobContext, playoutModel: P rundown.updateAdlibTestingSegmentRank() } } +function ensureNextPartInstanceIsNotDeleted(playoutModel: PlayoutModel) { + if (playoutModel.nextPartInstance) { + // Check if the segment of the nextPartInstance exists + if (!playoutModel.findSegment(playoutModel.nextPartInstance.partInstance.segmentId)) { + // The segment doesn't exist, set nextPartInstance to null, it'll be set by ensureNextPartIsValid() later. + playoutModel.setPartInstanceAsNext(null, false, false) + } + } +} diff --git a/packages/job-worker/src/ingest/syncChangesToPartInstance.ts b/packages/job-worker/src/ingest/syncChangesToPartInstance.ts index e83b9bcd46..79ec267122 100644 --- a/packages/job-worker/src/ingest/syncChangesToPartInstance.ts +++ b/packages/job-worker/src/ingest/syncChangesToPartInstance.ts @@ -157,10 +157,13 @@ export async function syncChangesToPartInstances( existingPartInstance.partInstance._id ) } catch (e) { - - logger.error(`TROUBLESHOOTING: currentPartInstance: ${JSON.stringify(playoutModel.currentPartInstance)}`) + logger.error( + `TROUBLESHOOTING: currentPartInstance: ${JSON.stringify(playoutModel.currentPartInstance)}` + ) logger.error(`TROUBLESHOOTING: nextPartInstance: ${JSON.stringify(playoutModel.nextPartInstance)}`) - logger.error(`TROUBLESHOOTING: previousPartInstance: ${JSON.stringify(playoutModel.previousPartInstance)}`) + logger.error( + `TROUBLESHOOTING: previousPartInstance: ${JSON.stringify(playoutModel.previousPartInstance)}` + ) throw e } From b7645d7f076c53c13bbb47c54a869594f7556617 Mon Sep 17 00:00:00 2001 From: Jan Starzak Date: Tue, 14 Jan 2025 12:40:45 +0100 Subject: [PATCH 17/19] chore: log output is based on an incorrect understanding of the flow, and is misleading --- packages/job-worker/src/ingest/commit.ts | 22 ++++------------------ 1 file changed, 4 insertions(+), 18 deletions(-) diff --git a/packages/job-worker/src/ingest/commit.ts b/packages/job-worker/src/ingest/commit.ts index f39ecc7d47..c82f633430 100644 --- a/packages/job-worker/src/ingest/commit.ts +++ b/packages/job-worker/src/ingest/commit.ts @@ -420,16 +420,10 @@ async function updatePartInstancesSegmentIds( for (const renameRule of renameRules.values()) { oldSegmentIds.push(...renameRule.fromSegmentIds) } - const [badPartInstances, badParts] = await Promise.all([ - await context.directCollections.PartInstances.findFetch({ - rundownId: ingestModel.rundownId, - segmentId: { $in: oldSegmentIds }, - }), - await context.directCollections.Parts.findFetch({ - rundownId: ingestModel.rundownId, - segmentId: { $in: oldSegmentIds }, - }), - ]) + const badPartInstances = await context.directCollections.PartInstances.findFetch({ + rundownId: ingestModel.rundownId, + segmentId: { $in: oldSegmentIds }, + }) if (badPartInstances.length > 0) { logger.error( `updatePartInstancesSegmentIds: Failed to update all PartInstances using old SegmentIds "${JSON.stringify( @@ -437,14 +431,6 @@ async function updatePartInstancesSegmentIds( )}": ${JSON.stringify(badPartInstances)}, writeOps: ${JSON.stringify(writeOps)}` ) } - - if (badParts.length > 0) { - logger.error( - `updatePartInstancesSegmentIds: Failed to update all Parts using old SegmentIds "${JSON.stringify( - oldSegmentIds - )}": ${JSON.stringify(badParts)}, writeOps: ${JSON.stringify(writeOps)}` - ) - } // End of the temporary section } } From 872c5282401d33b769e3b8e9009c7e784e18c2ef Mon Sep 17 00:00:00 2001 From: Jan Starzak Date: Tue, 14 Jan 2025 13:02:52 +0100 Subject: [PATCH 18/19] chore: fix linter issue --- packages/job-worker/src/playout/infinites.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/job-worker/src/playout/infinites.ts b/packages/job-worker/src/playout/infinites.ts index daf9093381..daec9a439b 100644 --- a/packages/job-worker/src/playout/infinites.ts +++ b/packages/job-worker/src/playout/infinites.ts @@ -352,7 +352,9 @@ export function getPieceInstancesForPart( throw new Error( `Segment "${playingPartInstance.partInstance.segmentId}" in Rundown "${ playingRundown.rundown._id - }" not found! (partInstanceId: "${playingPartInstance.partInstance._id}", other segments: ${JSON.stringify(playingRundown.segments.map((s) => s.segment._id))})` + }" not found! (partInstanceId: "${ + playingPartInstance.partInstance._id + }", other segments: ${JSON.stringify(playingRundown.segments.map((s) => s.segment._id))})` ) } } From baacf0e11d18563b55fe9590cd208acd45559a56 Mon Sep 17 00:00:00 2001 From: Jan Starzak Date: Tue, 14 Jan 2025 13:12:23 +0100 Subject: [PATCH 19/19] chore(release): 1.51.6 --- meteor/CHANGELOG.md | 16 ++++++++ meteor/package.json | 2 +- meteor/yarn.lock | 12 +++--- packages/blueprints-integration/CHANGELOG.md | 8 ++++ packages/blueprints-integration/package.json | 4 +- packages/corelib/package.json | 6 +-- packages/documentation/package.json | 2 +- packages/job-worker/package.json | 8 ++-- packages/lerna.json | 2 +- packages/live-status-gateway/package.json | 10 ++--- packages/mos-gateway/CHANGELOG.md | 8 ++++ packages/mos-gateway/package.json | 6 +-- packages/openapi/package.json | 2 +- packages/playout-gateway/CHANGELOG.md | 11 ++++++ packages/playout-gateway/package.json | 6 +-- packages/server-core-integration/CHANGELOG.md | 8 ++++ packages/server-core-integration/package.json | 4 +- packages/shared-lib/package.json | 2 +- packages/yarn.lock | 38 +++++++++---------- 19 files changed, 103 insertions(+), 52 deletions(-) diff --git a/meteor/CHANGELOG.md b/meteor/CHANGELOG.md index f0e4b48d77..145ebba4a8 100644 --- a/meteor/CHANGELOG.md +++ b/meteor/CHANGELOG.md @@ -2,6 +2,22 @@ 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.51.6](///compare/v1.51.5...v1.51.6) (2025-01-14) + + +### Features + +* add more logging 8c16ce8 + + +### Bug Fixes + +* Include previousPartInstance in check to orphan segments rather than remove them. 51b7104 +* only run onPart/PiecePlaybackStarted/Stopped on current, next or previous parts a9fe401 +* **PoGw:** filter log output to ensure that message field in JSONL output is never an object 0d2b844 +* set nextPartInstance to null if it's referring to a Segment that has been removed b1045f9 +* updatePartInstancesSegmentIds: take into account when multiple segments have been merged into one. b769157 + ### [1.51.5](///compare/v1.51.4...v1.51.5) (2025-01-07) diff --git a/meteor/package.json b/meteor/package.json index 9ffa5aae53..8373e876d1 100644 --- a/meteor/package.json +++ b/meteor/package.json @@ -1,6 +1,6 @@ { "name": "automation-core", - "version": "1.51.5", + "version": "1.51.6", "private": true, "engines": { "node": ">=14.19.1" diff --git a/meteor/yarn.lock b/meteor/yarn.lock index 65328eed31..dd10b030b4 100644 --- a/meteor/yarn.lock +++ b/meteor/yarn.lock @@ -1321,7 +1321,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.51.5 + "@sofie-automation/shared-lib": 1.51.6 tslib: ^2.6.2 type-fest: ^3.13.1 languageName: node @@ -1362,8 +1362,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.51.5 - "@sofie-automation/shared-lib": 1.51.5 + "@sofie-automation/blueprints-integration": 1.51.6 + "@sofie-automation/shared-lib": 1.51.6 fast-clone: ^1.5.13 i18next: ^21.10.0 influx: ^5.9.3 @@ -1394,9 +1394,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.51.5 - "@sofie-automation/corelib": 1.51.5 - "@sofie-automation/shared-lib": 1.51.5 + "@sofie-automation/blueprints-integration": 1.51.6 + "@sofie-automation/corelib": 1.51.6 + "@sofie-automation/shared-lib": 1.51.6 amqplib: ^0.10.3 deepmerge: ^4.3.1 elastic-apm-node: ^3.51.0 diff --git a/packages/blueprints-integration/CHANGELOG.md b/packages/blueprints-integration/CHANGELOG.md index 6402ee17d3..f46099f577 100644 --- a/packages/blueprints-integration/CHANGELOG.md +++ b/packages/blueprints-integration/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.51.6](https://github.com/nrkno/sofie-core/compare/v1.51.5...v1.51.6) (2025-01-14) + +**Note:** Version bump only for package @sofie-automation/blueprints-integration + + + + + ## [1.51.5](https://github.com/nrkno/sofie-core/compare/v1.51.4...v1.51.5) (2025-01-07) **Note:** Version bump only for package @sofie-automation/blueprints-integration diff --git a/packages/blueprints-integration/package.json b/packages/blueprints-integration/package.json index 6bc28f61b3..127591a3b6 100644 --- a/packages/blueprints-integration/package.json +++ b/packages/blueprints-integration/package.json @@ -1,6 +1,6 @@ { "name": "@sofie-automation/blueprints-integration", - "version": "1.51.5", + "version": "1.51.6", "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.51.5", + "@sofie-automation/shared-lib": "1.51.6", "tslib": "^2.6.2", "type-fest": "^3.13.1" }, diff --git a/packages/corelib/package.json b/packages/corelib/package.json index d4030a8f9c..6d6c680328 100644 --- a/packages/corelib/package.json +++ b/packages/corelib/package.json @@ -1,6 +1,6 @@ { "name": "@sofie-automation/corelib", - "version": "1.51.5", + "version": "1.51.6", "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.51.5", - "@sofie-automation/shared-lib": "1.51.5", + "@sofie-automation/blueprints-integration": "1.51.6", + "@sofie-automation/shared-lib": "1.51.6", "fast-clone": "^1.5.13", "i18next": "^21.10.0", "influx": "^5.9.3", diff --git a/packages/documentation/package.json b/packages/documentation/package.json index 4512f72057..f94f2d44a0 100644 --- a/packages/documentation/package.json +++ b/packages/documentation/package.json @@ -1,6 +1,6 @@ { "name": "sofie-documentation", - "version": "1.51.5", + "version": "1.51.6", "private": true, "scripts": { "docusaurus": "docusaurus", diff --git a/packages/job-worker/package.json b/packages/job-worker/package.json index 8029fc1d6d..906574ee20 100644 --- a/packages/job-worker/package.json +++ b/packages/job-worker/package.json @@ -1,6 +1,6 @@ { "name": "@sofie-automation/job-worker", - "version": "1.51.5", + "version": "1.51.6", "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.51.5", - "@sofie-automation/corelib": "1.51.5", - "@sofie-automation/shared-lib": "1.51.5", + "@sofie-automation/blueprints-integration": "1.51.6", + "@sofie-automation/corelib": "1.51.6", + "@sofie-automation/shared-lib": "1.51.6", "amqplib": "^0.10.3", "deepmerge": "^4.3.1", "elastic-apm-node": "^3.51.0", diff --git a/packages/lerna.json b/packages/lerna.json index 819bc12178..e196d9792d 100644 --- a/packages/lerna.json +++ b/packages/lerna.json @@ -1,5 +1,5 @@ { - "version": "1.51.5", + "version": "1.51.6", "npmClient": "yarn", "useWorkspaces": true } diff --git a/packages/live-status-gateway/package.json b/packages/live-status-gateway/package.json index 3c1ddd1d5d..c48ef7de44 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.51.5", + "version": "1.51.6", "private": true, "description": "Provides state from Sofie over sockets", "license": "MIT", @@ -53,10 +53,10 @@ "production" ], "dependencies": { - "@sofie-automation/blueprints-integration": "1.51.5", - "@sofie-automation/corelib": "1.51.5", - "@sofie-automation/server-core-integration": "1.51.5", - "@sofie-automation/shared-lib": "1.51.5", + "@sofie-automation/blueprints-integration": "1.51.6", + "@sofie-automation/corelib": "1.51.6", + "@sofie-automation/server-core-integration": "1.51.6", + "@sofie-automation/shared-lib": "1.51.6", "debug": "^4.3.4", "fast-clone": "^1.5.13", "influx": "^5.9.3", diff --git a/packages/mos-gateway/CHANGELOG.md b/packages/mos-gateway/CHANGELOG.md index 4cac07fa5a..ba2b3d50dc 100644 --- a/packages/mos-gateway/CHANGELOG.md +++ b/packages/mos-gateway/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.51.6](https://github.com/nrkno/sofie-core/compare/v1.51.5...v1.51.6) (2025-01-14) + +**Note:** Version bump only for package mos-gateway + + + + + ## [1.51.5](https://github.com/nrkno/sofie-core/compare/v1.51.4...v1.51.5) (2025-01-07) **Note:** Version bump only for package mos-gateway diff --git a/packages/mos-gateway/package.json b/packages/mos-gateway/package.json index 3f6d2e949b..cf527bb496 100644 --- a/packages/mos-gateway/package.json +++ b/packages/mos-gateway/package.json @@ -1,6 +1,6 @@ { "name": "mos-gateway", - "version": "1.51.5", + "version": "1.51.6", "private": true, "description": "MOS-Gateway for the Sofie project", "license": "MIT", @@ -66,8 +66,8 @@ ], "dependencies": { "@mos-connection/connector": "4.1.1", - "@sofie-automation/server-core-integration": "1.51.5", - "@sofie-automation/shared-lib": "1.51.5", + "@sofie-automation/server-core-integration": "1.51.6", + "@sofie-automation/shared-lib": "1.51.6", "tslib": "^2.6.2", "type-fest": "^3.13.1", "underscore": "^1.13.6", diff --git a/packages/openapi/package.json b/packages/openapi/package.json index a887c805e0..d1fcf21a73 100644 --- a/packages/openapi/package.json +++ b/packages/openapi/package.json @@ -1,6 +1,6 @@ { "name": "@sofie-automation/openapi", - "version": "1.51.5", + "version": "1.51.6", "license": "MIT", "repository": { "type": "git", diff --git a/packages/playout-gateway/CHANGELOG.md b/packages/playout-gateway/CHANGELOG.md index 2fadf7cf31..3b8b085180 100644 --- a/packages/playout-gateway/CHANGELOG.md +++ b/packages/playout-gateway/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.51.6](https://github.com/nrkno/sofie-core/compare/v1.51.5...v1.51.6) (2025-01-14) + + +### Bug Fixes + +* **PoGw:** filter log output to ensure that message field in JSONL output is never an object ([0d2b844](https://github.com/nrkno/sofie-core/commit/0d2b84451751a859d6a3d67e0f3083c735905578)) + + + + + ## [1.51.5](https://github.com/nrkno/sofie-core/compare/v1.51.4...v1.51.5) (2025-01-07) **Note:** Version bump only for package playout-gateway diff --git a/packages/playout-gateway/package.json b/packages/playout-gateway/package.json index 7d99add74d..c788480565 100644 --- a/packages/playout-gateway/package.json +++ b/packages/playout-gateway/package.json @@ -1,6 +1,6 @@ { "name": "playout-gateway", - "version": "1.51.5", + "version": "1.51.6", "private": true, "description": "Connect to Core, play stuff", "license": "MIT", @@ -56,8 +56,8 @@ "production" ], "dependencies": { - "@sofie-automation/server-core-integration": "1.51.5", - "@sofie-automation/shared-lib": "1.51.5", + "@sofie-automation/server-core-integration": "1.51.6", + "@sofie-automation/shared-lib": "1.51.6", "debug": "^4.3.4", "influx": "^5.9.3", "timeline-state-resolver": "9.2.1", diff --git a/packages/server-core-integration/CHANGELOG.md b/packages/server-core-integration/CHANGELOG.md index 915d072c8f..5352f64727 100644 --- a/packages/server-core-integration/CHANGELOG.md +++ b/packages/server-core-integration/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +## [1.51.6](https://github.com/nrkno/sofie-core/compare/v1.51.5...v1.51.6) (2025-01-14) + +**Note:** Version bump only for package @sofie-automation/server-core-integration + + + + + ## [1.51.5](https://github.com/nrkno/sofie-core/compare/v1.51.4...v1.51.5) (2025-01-07) **Note:** Version bump only for package @sofie-automation/server-core-integration diff --git a/packages/server-core-integration/package.json b/packages/server-core-integration/package.json index 60e0be8345..73b21005c9 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.51.5", + "version": "1.51.6", "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.51.5", + "@sofie-automation/shared-lib": "1.51.6", "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 50e78219f8..d76734eb13 100644 --- a/packages/shared-lib/package.json +++ b/packages/shared-lib/package.json @@ -1,6 +1,6 @@ { "name": "@sofie-automation/shared-lib", - "version": "1.51.5", + "version": "1.51.6", "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 0134d3c2ca..9cfa3f78df 100644 --- a/packages/yarn.lock +++ b/packages/yarn.lock @@ -4565,11 +4565,11 @@ __metadata: languageName: node linkType: hard -"@sofie-automation/blueprints-integration@1.51.5, @sofie-automation/blueprints-integration@workspace:blueprints-integration": +"@sofie-automation/blueprints-integration@1.51.6, @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.51.5 + "@sofie-automation/shared-lib": 1.51.6 tslib: ^2.6.2 type-fest: ^3.13.1 languageName: unknown @@ -4606,12 +4606,12 @@ __metadata: languageName: node linkType: hard -"@sofie-automation/corelib@1.51.5, @sofie-automation/corelib@workspace:corelib": +"@sofie-automation/corelib@1.51.6, @sofie-automation/corelib@workspace:corelib": version: 0.0.0-use.local resolution: "@sofie-automation/corelib@workspace:corelib" dependencies: - "@sofie-automation/blueprints-integration": 1.51.5 - "@sofie-automation/shared-lib": 1.51.5 + "@sofie-automation/blueprints-integration": 1.51.6 + "@sofie-automation/shared-lib": 1.51.6 fast-clone: ^1.5.13 i18next: ^21.10.0 influx: ^5.9.3 @@ -4642,9 +4642,9 @@ __metadata: resolution: "@sofie-automation/job-worker@workspace:job-worker" dependencies: "@slack/webhook": ^6.1.0 - "@sofie-automation/blueprints-integration": 1.51.5 - "@sofie-automation/corelib": 1.51.5 - "@sofie-automation/shared-lib": 1.51.5 + "@sofie-automation/blueprints-integration": 1.51.6 + "@sofie-automation/corelib": 1.51.6 + "@sofie-automation/shared-lib": 1.51.6 amqplib: ^0.10.3 deepmerge: ^4.3.1 elastic-apm-node: ^3.51.0 @@ -4674,11 +4674,11 @@ __metadata: languageName: unknown linkType: soft -"@sofie-automation/server-core-integration@1.51.5, @sofie-automation/server-core-integration@workspace:server-core-integration": +"@sofie-automation/server-core-integration@1.51.6, @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.51.5 + "@sofie-automation/shared-lib": 1.51.6 ejson: ^2.2.3 eventemitter3: ^4.0.7 faye-websocket: ^0.11.4 @@ -4688,7 +4688,7 @@ __metadata: languageName: unknown linkType: soft -"@sofie-automation/shared-lib@1.51.5, @sofie-automation/shared-lib@workspace:shared-lib": +"@sofie-automation/shared-lib@1.51.6, @sofie-automation/shared-lib@workspace:shared-lib": version: 0.0.0-use.local resolution: "@sofie-automation/shared-lib@workspace:shared-lib" dependencies: @@ -15334,10 +15334,10 @@ asn1@evs-broadcast/node-asn1: "@asyncapi/generator": ^1.17.25 "@asyncapi/html-template": ^2.3.9 "@asyncapi/nodejs-ws-template": ^0.9.36 - "@sofie-automation/blueprints-integration": 1.51.5 - "@sofie-automation/corelib": 1.51.5 - "@sofie-automation/server-core-integration": 1.51.5 - "@sofie-automation/shared-lib": 1.51.5 + "@sofie-automation/blueprints-integration": 1.51.6 + "@sofie-automation/corelib": 1.51.6 + "@sofie-automation/server-core-integration": 1.51.6 + "@sofie-automation/shared-lib": 1.51.6 debug: ^4.3.4 fast-clone: ^1.5.13 influx: ^5.9.3 @@ -17410,8 +17410,8 @@ asn1@evs-broadcast/node-asn1: resolution: "mos-gateway@workspace:mos-gateway" dependencies: "@mos-connection/connector": 4.1.1 - "@sofie-automation/server-core-integration": 1.51.5 - "@sofie-automation/shared-lib": 1.51.5 + "@sofie-automation/server-core-integration": 1.51.6 + "@sofie-automation/shared-lib": 1.51.6 tslib: ^2.6.2 type-fest: ^3.13.1 underscore: ^1.13.6 @@ -19397,8 +19397,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.51.5 - "@sofie-automation/shared-lib": 1.51.5 + "@sofie-automation/server-core-integration": 1.51.6 + "@sofie-automation/shared-lib": 1.51.6 debug: ^4.3.4 influx: ^5.9.3 timeline-state-resolver: 9.2.1