diff --git a/packages/blueprints-integration/CHANGELOG.md b/packages/blueprints-integration/CHANGELOG.md index 1c3adfcbfe..2c8414a48b 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.1](https://github.com/nrkno/sofie-core/compare/v1.51.1-2...v1.51.1) (2024-11-13) + +**Note:** Version bump only for package @sofie-automation/blueprints-integration + + + + + ## [1.51.1-2](https://github.com/nrkno/sofie-core/compare/v1.51.1-1...v1.51.1-2) (2024-10-24) **Note:** Version bump only for package @sofie-automation/blueprints-integration diff --git a/packages/corelib/src/playout/playlist.ts b/packages/corelib/src/playout/playlist.ts index 702e4c3eca..7a9cced811 100644 --- a/packages/corelib/src/playout/playlist.ts +++ b/packages/corelib/src/playout/playlist.ts @@ -109,8 +109,8 @@ export function compareMarkerPositions(a: MarkerPosition, b: MarkerPosition): nu export function sortRundownsWithinPlaylist( sortedPossibleIds: ReadonlyDeep, - unsortedRundowns: DBRundown[] -): DBRundown[] { + unsortedRundowns: ReadonlyDeep +): ReadonlyDeep { return unsortedRundowns.slice().sort((a, b) => { const indexA = sortedPossibleIds.indexOf(a._id) const indexB = sortedPossibleIds.indexOf(b._id) diff --git a/packages/job-worker/src/playout/model/PlayoutModel.ts b/packages/job-worker/src/playout/model/PlayoutModel.ts index a48d17ce9f..420209a4a6 100644 --- a/packages/job-worker/src/playout/model/PlayoutModel.ts +++ b/packages/job-worker/src/playout/model/PlayoutModel.ts @@ -59,7 +59,7 @@ export interface PlayoutModelPreInit { */ readonly playlist: ReadonlyDeep /** - * The unwrapped Rundowns in this RundownPlaylist + * The unwrapped Rundowns in this RundownPlaylist, sorted in order specified by RundownPlaylist */ readonly rundowns: ReadonlyDeep diff --git a/packages/job-worker/src/playout/model/implementation/LoadPlayoutModel.ts b/packages/job-worker/src/playout/model/implementation/LoadPlayoutModel.ts index 24a2e0fc4c..41af650925 100644 --- a/packages/job-worker/src/playout/model/implementation/LoadPlayoutModel.ts +++ b/packages/job-worker/src/playout/model/implementation/LoadPlayoutModel.ts @@ -89,7 +89,7 @@ export async function createPlayoutModelFromIngestModel( const [{ partInstances, groupedPieceInstances }, rundownsWithContent, timeline] = await Promise.all([ loadPartInstances(context, loadedPlaylist, rundownIds), - loadRundowns(context, ingestModel, rundowns), + loadRundowns(context, ingestModel, sortRundownsWithinPlaylist(playlist.rundownIdsInOrder, rundowns)), loadTimeline(context), ]) diff --git a/packages/job-worker/src/playout/model/implementation/__tests__/LoadPlayoutModel.spec.ts b/packages/job-worker/src/playout/model/implementation/__tests__/LoadPlayoutModel.spec.ts index 76f136429e..2766d1017f 100644 --- a/packages/job-worker/src/playout/model/implementation/__tests__/LoadPlayoutModel.spec.ts +++ b/packages/job-worker/src/playout/model/implementation/__tests__/LoadPlayoutModel.spec.ts @@ -7,8 +7,11 @@ import { MockJobContext, setupDefaultJobEnvironment } from '../../../../__mocks_ import { ProcessedShowStyleCompound } from '../../../../jobs' import { ReadonlyDeep } from 'type-fest' import { protectString } from '@sofie-automation/corelib/dist/protectedString' -import { loadPlayoutModelPreInit } from '../LoadPlayoutModel' +import { createPlayoutModelFromIngestModel, loadPlayoutModelPreInit } from '../LoadPlayoutModel' import { runWithPlaylistLock } from '../../../../playout/lock' +import { loadIngestModelFromRundown } from '../../../../ingest/model/implementation/LoadIngestModel' +import { runWithRundownLock } from '../../../../ingest/lock' +import { IngestModelReadonly } from '../../../../ingest/model/IngestModel' describe('LoadPlayoutModel', () => { let context: MockJobContext @@ -94,4 +97,63 @@ describe('LoadPlayoutModel', () => { }) }) }) + + describe('createPlayoutModelFromIngestModel', () => { + afterEach(async () => + Promise.all([ + context.mockCollections.RundownBaselineAdLibPieces.remove({}), + context.mockCollections.RundownBaselineAdLibActions.remove({}), + context.mockCollections.RundownBaselineObjects.remove({}), + context.mockCollections.AdLibActions.remove({}), + context.mockCollections.AdLibPieces.remove({}), + context.mockCollections.Pieces.remove({}), + context.mockCollections.Parts.remove({}), + context.mockCollections.Segments.remove({}), + context.mockCollections.Rundowns.remove({}), + context.mockCollections.RundownPlaylists.remove({}), + ]) + ) + + test('Rundowns are in order specified in RundownPlaylist', async () => { + // Set up a playlist: + const { rundownId: rundownId00, playlistId: playlistId0 } = await setupDefaultRundownPlaylist( + context, + showStyleCompound, + protectString('rundown00') + ) + const rundownId01 = protectString('rundown01') + await setupDefaultRundown(context, showStyleCompound, playlistId0, rundownId01) + const rundownId02 = protectString('rundown02') + await setupDefaultRundown(context, showStyleCompound, playlistId0, rundownId02) + + const rundownIdsInOrder = [rundownId01, rundownId02, rundownId00] + + await context.mockCollections.RundownPlaylists.update(playlistId0, { + rundownIdsInOrder, + }) + + const playlist0 = await context.mockCollections.RundownPlaylists.findOne(playlistId0) + expect(playlist0).toBeTruthy() + + if (!playlist0) throw new Error(`Playlist "${playlistId0}" not found!`) + + let ingestModel: IngestModelReadonly | undefined + + await runWithRundownLock(context, rundownId01, async (rundown, lock) => { + if (!rundown) throw new Error(`Rundown "${rundownId01}" not found!`) + + ingestModel = await loadIngestModelFromRundown(context, lock, rundown) + }) + + await runWithPlaylistLock(context, playlistId0, async (lock) => { + if (!ingestModel) throw new Error('Ingest model could not be created!') + + const rundowns = await context.mockCollections.Rundowns.findFetch({}) + + const model = await createPlayoutModelFromIngestModel(context, lock, playlist0, rundowns, ingestModel) + + expect(model.rundowns.map((r) => r.rundown._id)).toMatchObject([rundownId01, rundownId02, rundownId00]) + }) + }) + }) }) diff --git a/packages/job-worker/src/playout/resolvedPieces.ts b/packages/job-worker/src/playout/resolvedPieces.ts index 089ea5f83f..f13ce67c72 100644 --- a/packages/job-worker/src/playout/resolvedPieces.ts +++ b/packages/job-worker/src/playout/resolvedPieces.ts @@ -46,8 +46,10 @@ export function getResolvedPiecesForPartInstancesOnTimeline( if (!partInstancesInfo.current) return [] const currentPartStarted = partInstancesInfo.current.partStarted ?? now + const nextPartStarted = partInstancesInfo.current.partInstance.part.autoNext && + partInstancesInfo.current.partInstance.part.expectedDuration !== 0 && partInstancesInfo.current.partInstance.part.expectedDuration !== undefined ? currentPartStarted + partInstancesInfo.current.partInstance.part.expectedDuration : null diff --git a/packages/job-worker/src/playout/timeline/generate.ts b/packages/job-worker/src/playout/timeline/generate.ts index 77856edf60..fca78d76fa 100644 --- a/packages/job-worker/src/playout/timeline/generate.ts +++ b/packages/job-worker/src/playout/timeline/generate.ts @@ -1,4 +1,4 @@ -import { BlueprintId } from '@sofie-automation/corelib/dist/dataModel/Ids' +import { BlueprintId, TimelineHash } from '@sofie-automation/corelib/dist/dataModel/Ids' import { JobContext } from '../../jobs' import { ReadonlyDeep } from 'type-fest' import { @@ -127,13 +127,13 @@ export async function updateStudioTimeline( logAnyRemainingNowTimes(context, baselineObjects) } - saveTimeline(context, playoutModel, baselineObjects, versions) + const timelineHash = saveTimeline(context, playoutModel, baselineObjects, versions) if (studioBaseline) { updateBaselineExpectedPackagesOnStudio(context, playoutModel, studioBaseline) } - logger.debug('updateStudioTimeline done!') + logger.verbose(`updateStudioTimeline done, hash: "${timelineHash}"`) if (span) span.end() } @@ -157,9 +157,8 @@ export async function updateTimeline(context: JobContext, playoutModel: PlayoutM logAnyRemainingNowTimes(context, timelineObjs) } - saveTimeline(context, playoutModel, timelineObjs, versions) - - logger.debug('updateTimeline done!') + const timelineHash = saveTimeline(context, playoutModel, timelineObjs, versions) + logger.verbose(`updateTimeline done, hash: "${timelineHash}"`) if (span) span.end() } @@ -231,11 +230,13 @@ export function saveTimeline( studioPlayoutModel: StudioPlayoutModelBase, timelineObjs: TimelineObjGeneric[], generationVersions: TimelineCompleteGenerationVersions -): void { +): TimelineHash { const newTimeline = studioPlayoutModel.setTimeline(timelineObjs, generationVersions) // Also do a fast-track for the timeline to be published faster: context.hackPublishTimelineToFastTrack(newTimeline) + + return newTimeline.timelineHash } export interface SelectedPartInstancesTimelineInfo { diff --git a/packages/job-worker/src/playout/timings/partPlayback.ts b/packages/job-worker/src/playout/timings/partPlayback.ts index c0dcdd9852..d76db7910f 100644 --- a/packages/job-worker/src/playout/timings/partPlayback.ts +++ b/packages/job-worker/src/playout/timings/partPlayback.ts @@ -29,7 +29,7 @@ export async function onPartPlaybackStarted( const playingPartInstance = playoutModel.getPartInstance(data.partInstanceId) if (!playingPartInstance) throw new Error( - `PartInstance "${data.partInstanceId}" in RundownPlayst "${playoutModel.playlistId}" not found!` + `PartInstance "${data.partInstanceId}" in RundownPlaylist "${playoutModel.playlistId}" not found!` ) // make sure we don't run multiple times, even if TSR calls us multiple times @@ -178,33 +178,32 @@ export function reportPartInstanceHasStarted( partInstance: PlayoutPartInstanceModel, timestamp: Time ): void { - if (partInstance) { - const timestampUpdated = partInstance.setReportedStartedPlayback(timestamp) - if (timestamp && !playoutModel.isMultiGatewayMode) { + const timestampUpdated = partInstance.setReportedStartedPlayback(timestamp) + + if (!playoutModel.isMultiGatewayMode) { + if (timestamp) { partInstance.setPlannedStartedPlayback(timestamp) } - const previousPartInstance = playoutModel.previousPartInstance - if (timestampUpdated && !playoutModel.isMultiGatewayMode && previousPartInstance) { + if (timestampUpdated && previousPartInstance) { // Ensure the plannedStoppedPlayback is set for the previous partinstance too previousPartInstance.setPlannedStoppedPlayback(timestamp) } + } - // Update the playlist: - if (!partInstance.partInstance.part.untimed) { - playoutModel.setRundownStartedPlayback(partInstance.partInstance.rundownId, timestamp) - } + // Update the playlist: + if (!partInstance.partInstance.part.untimed) { + playoutModel.setRundownStartedPlayback(partInstance.partInstance.rundownId, timestamp) + } - if ( - partInstance.partInstance.segmentPlayoutId !== - playoutModel.previousPartInstance?.partInstance.segmentPlayoutId - ) { - playoutModel.setSegmentStartedPlayback(partInstance.partInstance.segmentPlayoutId, timestamp) - } + if ( + partInstance.partInstance.segmentPlayoutId !== playoutModel.previousPartInstance?.partInstance.segmentPlayoutId + ) { + playoutModel.setSegmentStartedPlayback(partInstance.partInstance.segmentPlayoutId, timestamp) + } - if (timestampUpdated) { - playoutModel.queuePartInstanceTimingEvent(partInstance.partInstance._id) - } + if (timestampUpdated) { + playoutModel.queuePartInstanceTimingEvent(partInstance.partInstance._id) } } diff --git a/packages/job-worker/src/playout/timings/timelineTriggerTime.ts b/packages/job-worker/src/playout/timings/timelineTriggerTime.ts index e3c585f0ca..eccac86df8 100644 --- a/packages/job-worker/src/playout/timings/timelineTriggerTime.ts +++ b/packages/job-worker/src/playout/timings/timelineTriggerTime.ts @@ -181,7 +181,9 @@ function timelineTriggerTimeInner( } } if (tlChanged) { - saveTimeline(context, studioPlayoutModel, timelineObjs, timeline.generationVersions) + const timelineHash = saveTimeline(context, studioPlayoutModel, timelineObjs, timeline.generationVersions) + + logger.verbose(`timelineTriggerTime: Updated Timeline, hash: "${timelineHash}"`) } } diff --git a/packages/mos-gateway/CHANGELOG.md b/packages/mos-gateway/CHANGELOG.md index 097ebaf45e..c8adfc4ea2 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.1](https://github.com/nrkno/sofie-core/compare/v1.51.1-2...v1.51.1) (2024-11-13) + +**Note:** Version bump only for package mos-gateway + + + + + ## [1.51.1-2](https://github.com/nrkno/sofie-core/compare/v1.51.1-1...v1.51.1-2) (2024-10-24) **Note:** Version bump only for package mos-gateway diff --git a/packages/playout-gateway/CHANGELOG.md b/packages/playout-gateway/CHANGELOG.md index 334fc18216..00c009e113 100644 --- a/packages/playout-gateway/CHANGELOG.md +++ b/packages/playout-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.1](https://github.com/nrkno/sofie-core/compare/v1.51.1-2...v1.51.1) (2024-11-13) + +**Note:** Version bump only for package playout-gateway + + + + + ## [1.51.1-2](https://github.com/nrkno/sofie-core/compare/v1.51.1-1...v1.51.1-2) (2024-10-24) **Note:** Version bump only for package playout-gateway diff --git a/packages/server-core-integration/CHANGELOG.md b/packages/server-core-integration/CHANGELOG.md index 66c7282306..d374832bf1 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.1](https://github.com/nrkno/sofie-core/compare/v1.51.1-2...v1.51.1) (2024-11-13) + +**Note:** Version bump only for package @sofie-automation/server-core-integration + + + + + ## [1.51.1-2](https://github.com/nrkno/sofie-core/compare/v1.51.1-1...v1.51.1-2) (2024-10-24) **Note:** Version bump only for package @sofie-automation/server-core-integration diff --git a/packages/shared-lib/src/package-manager/packageInfo.ts b/packages/shared-lib/src/package-manager/packageInfo.ts index ca036caa2b..4da768ad33 100644 --- a/packages/shared-lib/src/package-manager/packageInfo.ts +++ b/packages/shared-lib/src/package-manager/packageInfo.ts @@ -3,10 +3,11 @@ export namespace PackageInfo { export enum Type { SCAN = 'scan', DEEPSCAN = 'deepScan', + JSON = 'json', OTHER = 'other', } - export type Any = FFProbeScan | FFProbeDeepScan | FFOther + export type Any = FFProbeScan | FFProbeDeepScan | JSONDocument | FFOther export interface Base { type: Type payload: any @@ -20,6 +21,12 @@ export namespace PackageInfo { type: Type.DEEPSCAN payload: FFProbeDeepScanInfo } + + export interface JSONDocument extends Base { + type: Type.JSON + payload: unknown + } + export interface FFOther extends Base { // placeholder type: Type.OTHER diff --git a/packages/webui/src/client/ui/ActiveRundownView.tsx b/packages/webui/src/client/ui/ActiveRundownView.tsx index 0b5cab2624..2ec6a8156c 100644 --- a/packages/webui/src/client/ui/ActiveRundownView.tsx +++ b/packages/webui/src/client/ui/ActiveRundownView.tsx @@ -30,8 +30,6 @@ export function ActiveRundownView({ studioId }: Readonly<{ studioId: StudioId }> [studioId] ) - useSetDocumentClass('dark', 'vertical-overflow-only') - if (!subsReady) { return (
@@ -61,6 +59,8 @@ export function ActiveRundownView({ studioId }: Readonly<{ studioId: StudioId }> function NotFoundMessage({ message }: Readonly<{ message: string }>) { const { t } = useTranslation() + useSetDocumentClass('dark', 'vertical-overflow-only') + return (
diff --git a/packages/webui/src/client/ui/Settings/components/triggeredActions/triggerEditors/DeviceEditor.tsx b/packages/webui/src/client/ui/Settings/components/triggeredActions/triggerEditors/DeviceEditor.tsx index 69557a76be..e205b3d668 100644 --- a/packages/webui/src/client/ui/Settings/components/triggeredActions/triggerEditors/DeviceEditor.tsx +++ b/packages/webui/src/client/ui/Settings/components/triggeredActions/triggerEditors/DeviceEditor.tsx @@ -6,7 +6,7 @@ import { MeteorPubSub } from '@sofie-automation/meteor-lib/dist/api/pubsub' import { Studios } from '../../../../../collections' import { getCurrentTime } from '../../../../../lib/systemTime' import { UIDeviceTriggerPreview } from '@sofie-automation/meteor-lib/dist/api/MountedTriggers' -import { useSubscription, useTracker } from '../../../../../lib/ReactMeteorData/ReactMeteorData' +import { useSubscriptionIfEnabled, useTracker } from '../../../../../lib/ReactMeteorData/ReactMeteorData' import { DeviceTriggersPreviews } from '../../../../Collections' import { DeviceTrigger } from './DeviceTrigger' @@ -33,7 +33,7 @@ export const DeviceEditor = function DeviceEditor({ trigger, modified, readonly, ) const studio = useTracker(() => Studios.findOne(), [], undefined) - useSubscription(MeteorPubSub.deviceTriggersPreview, studio?._id ?? protectString('')) + useSubscriptionIfEnabled(MeteorPubSub.deviceTriggersPreview, studio !== undefined, studio?._id ?? protectString('')) return ( <> diff --git a/packages/webui/src/client/ui/util/useSetDocumentClass.ts b/packages/webui/src/client/ui/util/useSetDocumentClass.ts index e63ba88b88..b3639b4031 100644 --- a/packages/webui/src/client/ui/util/useSetDocumentClass.ts +++ b/packages/webui/src/client/ui/util/useSetDocumentClass.ts @@ -1,11 +1,11 @@ -import { useEffect } from 'react' +import { useLayoutEffect } from 'react' /** * Adds the provided classes to `document.body` upon mount, and removes them when unmounted * @param classNames Classnames to add */ export function useSetDocumentClass(...classNames: string[]): void { - useEffect(() => { + useLayoutEffect(() => { document.body.classList.add(...classNames) return () => {