Skip to content

Commit 758af48

Browse files
authored
Merge branch 'nrkno:release52' into release52
2 parents b2e9a12 + 94af13a commit 758af48

File tree

75 files changed

+3068
-469
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

75 files changed

+3068
-469
lines changed

meteor/CHANGELOG.md

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,22 @@
22

33
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.
44

5+
### [1.51.6](///compare/v1.51.5...v1.51.6) (2025-01-14)
6+
7+
8+
### Features
9+
10+
* add more logging 8c16ce8
11+
12+
13+
### Bug Fixes
14+
15+
* Include previousPartInstance in check to orphan segments rather than remove them. 51b7104
16+
* only run onPart/PiecePlaybackStarted/Stopped on current, next or previous parts a9fe401
17+
* **PoGw:** filter log output to ensure that message field in JSONL output is never an object 0d2b844
18+
* set nextPartInstance to null if it's referring to a Segment that has been removed b1045f9
19+
* updatePartInstancesSegmentIds: take into account when multiple segments have been merged into one. b769157
20+
521
### [1.51.5](///compare/v1.51.4...v1.51.5) (2025-01-07)
622

723

meteor/package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040
"@babel/runtime": "^7.23.9",
4141
"@koa/cors": "^5.0.0",
4242
"@koa/router": "^12.0.1",
43-
"@mos-connection/helper": "v4.2.0",
43+
"@mos-connection/helper": "v4.2.2",
4444
"@slack/webhook": "^6.1.0",
4545
"@sofie-automation/blueprints-integration": "portal:../packages/blueprints-integration",
4646
"@sofie-automation/corelib": "portal:../packages/corelib",

meteor/server/__tests__/cronjobs.test.ts

Lines changed: 115 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,13 @@ import { MeteorMock } from '../../__mocks__/meteor'
44
import { logger } from '../logging'
55
import { getRandomId, getRandomString, literal, protectString } from '../lib/tempLib'
66
import { SnapshotType } from '@sofie-automation/meteor-lib/dist/collections/Snapshots'
7-
import { IBlueprintPieceType, PieceLifespan, StatusCode, TSR } from '@sofie-automation/blueprints-integration'
7+
import {
8+
IBlueprintPieceType,
9+
PieceLifespan,
10+
PlaylistTimingType,
11+
StatusCode,
12+
TSR,
13+
} from '@sofie-automation/blueprints-integration'
814
import {
915
PeripheralDeviceType,
1016
PeripheralDeviceCategory,
@@ -27,6 +33,8 @@ import {
2733
SegmentId,
2834
SnapshotId,
2935
UserActionsLogItemId,
36+
StudioId,
37+
RundownPlaylistId,
3038
} from '@sofie-automation/corelib/dist/dataModel/Ids'
3139

3240
// Set up mocks for tests in this suite
@@ -53,6 +61,8 @@ import {
5361
UserActionsLog,
5462
Segments,
5563
SofieIngestDataCache,
64+
Studios,
65+
RundownPlaylists,
5666
} from '../collections'
5767
import { NrcsIngestCacheType } from '@sofie-automation/corelib/dist/dataModel/NrcsIngestDataCache'
5868
import { JSONBlobStringify } from '@sofie-automation/shared-lib/dist/lib/JSONBlob'
@@ -64,7 +74,7 @@ import {
6474
import { DBSegment } from '@sofie-automation/corelib/dist/dataModel/Segment'
6575
import { Settings } from '../Settings'
6676
import { SofieIngestCacheType } from '@sofie-automation/corelib/dist/dataModel/SofieIngestDataCache'
67-
import { ObjectOverrideSetOp } from '@sofie-automation/corelib/dist/settings/objectWithOverrides'
77+
import { ObjectOverrideSetOp, ObjectWithOverrides } from '@sofie-automation/corelib/dist/settings/objectWithOverrides'
6878

6979
describe('cronjobs', () => {
7080
let env: DefaultEnvironment
@@ -476,7 +486,10 @@ describe('cronjobs', () => {
476486
expect(await Snapshots.findOneAsync(snapshot1)).toBeUndefined()
477487
})
478488
async function insertPlayoutDevice(
479-
props: Pick<PeripheralDevice, 'subType' | 'deviceName' | 'lastSeen' | 'parentDeviceId'> &
489+
props: Pick<
490+
PeripheralDevice,
491+
'subType' | 'deviceName' | 'lastSeen' | 'parentDeviceId' | 'studioAndConfigId'
492+
> &
480493
Partial<Pick<PeripheralDevice, 'token'>>
481494
): Promise<PeripheralDeviceId> {
482495
const deviceId = protectString<PeripheralDeviceId>(getRandomString())
@@ -504,7 +517,10 @@ describe('cronjobs', () => {
504517
return deviceId
505518
}
506519

507-
async function createMockPlayoutGatewayAndDevices(lastSeen: number): Promise<{
520+
async function createMockPlayoutGatewayAndDevices(
521+
lastSeen: number,
522+
studioId?: StudioId
523+
): Promise<{
508524
deviceToken: string
509525
mockPlayoutGw: PeripheralDeviceId
510526
mockCasparCg: PeripheralDeviceId
@@ -516,6 +532,12 @@ describe('cronjobs', () => {
516532
lastSeen: lastSeen,
517533
subType: PERIPHERAL_SUBTYPE_PROCESS,
518534
token: deviceToken,
535+
studioAndConfigId: studioId
536+
? {
537+
configId: '',
538+
studioId,
539+
}
540+
: undefined,
519541
})
520542
const mockCasparCg = await insertPlayoutDevice({
521543
deviceName: 'CasparCG',
@@ -540,6 +562,73 @@ describe('cronjobs', () => {
540562
}
541563
}
542564

565+
async function createMockStudioAndRundown(): Promise<{
566+
studioId: StudioId
567+
rundownPlaylistId: RundownPlaylistId
568+
}> {
569+
function newObjectWithOverrides<T extends {}>(defaults: T): ObjectWithOverrides<T> {
570+
return {
571+
defaults,
572+
overrides: [],
573+
}
574+
}
575+
const studioId = protectString<StudioId>(getRandomString())
576+
await Studios.insertAsync({
577+
_id: studioId,
578+
organizationId: null,
579+
name: 'Studio',
580+
blueprintConfigWithOverrides: newObjectWithOverrides({}),
581+
_rundownVersionHash: '',
582+
lastBlueprintConfig: undefined,
583+
lastBlueprintFixUpHash: undefined,
584+
mappingsWithOverrides: newObjectWithOverrides({}),
585+
supportedShowStyleBase: [],
586+
settingsWithOverrides: newObjectWithOverrides({
587+
allowHold: true,
588+
allowPieceDirectPlay: true,
589+
enableBuckets: true,
590+
enableEvaluationForm: true,
591+
frameRate: 25,
592+
mediaPreviewsUrl: '',
593+
minimumTakeSpan: 1000,
594+
}),
595+
routeSetsWithOverrides: newObjectWithOverrides({}),
596+
routeSetExclusivityGroupsWithOverrides: newObjectWithOverrides({}),
597+
packageContainersWithOverrides: newObjectWithOverrides({}),
598+
previewContainerIds: [],
599+
thumbnailContainerIds: [],
600+
peripheralDeviceSettings: {
601+
deviceSettings: newObjectWithOverrides({}),
602+
ingestDevices: newObjectWithOverrides({}),
603+
inputDevices: newObjectWithOverrides({}),
604+
playoutDevices: newObjectWithOverrides({}),
605+
},
606+
})
607+
608+
const rundownPlaylistId = protectString<RundownPlaylistId>(getRandomString())
609+
await RundownPlaylists.mutableCollection.insertAsync({
610+
_id: rundownPlaylistId,
611+
created: Date.now(),
612+
currentPartInfo: null,
613+
nextPartInfo: null,
614+
externalId: '',
615+
modified: Date.now(),
616+
name: 'Rundown',
617+
previousPartInfo: null,
618+
rundownIdsInOrder: [],
619+
studioId,
620+
timing: {
621+
type: PlaylistTimingType.None,
622+
},
623+
activationId: protectString(''),
624+
})
625+
626+
return {
627+
studioId,
628+
rundownPlaylistId,
629+
}
630+
}
631+
543632
test('Attempts to restart CasparCG when job is enabled', async () => {
544633
const { mockCasparCg, deviceToken } = await createMockPlayoutGatewayAndDevices(Date.now()) // Some time after the threshold
545634

@@ -605,6 +694,28 @@ describe('cronjobs', () => {
605694
expect(logger.info).toHaveBeenLastCalledWith('Nightly cronjob: done')
606695
}, MAX_WAIT_TIME)
607696
})
697+
test('Skips CasparCG in Studios with active Playlists when job is enabled', async () => {
698+
const { studioId } = await createMockStudioAndRundown()
699+
await createMockPlayoutGatewayAndDevices(Date.now(), studioId) // Some time after the threshold
700+
;(logger.info as jest.Mock).mockClear()
701+
// set time to 2020/07/{date} 04:05 Local Time, should be more than 24 hours after 2020/07/19 00:00 UTC
702+
mockCurrentTime = new Date(2020, 6, date++, 4, 5, 0).getTime()
703+
// cronjob is checked every 5 minutes, so advance 6 minutes
704+
await jest.advanceTimersByTimeAsync(6 * 60 * 1000)
705+
706+
await waitUntil(async () => {
707+
// Run timers, so that all promises in the cronjob has a chance to resolve:
708+
const pendingCommands = await PeripheralDeviceCommands.findFetchAsync({})
709+
expect(pendingCommands).toHaveLength(0)
710+
}, MAX_WAIT_TIME)
711+
712+
// make sure that the cronjob ends
713+
await waitUntil(async () => {
714+
// Run timers, so that all promises in the cronjob has a chance to resolve:
715+
await runAllTimers()
716+
expect(logger.info).toHaveBeenLastCalledWith('Nightly cronjob: done')
717+
}, MAX_WAIT_TIME)
718+
})
608719
test('Does not attempt to restart CasparCG when job is disabled', async () => {
609720
await createMockPlayoutGatewayAndDevices(Date.now()) // Some time after the threshold
610721
await setCasparCGCronEnabled(false)

meteor/server/api/rest/v1/typeConversion.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -367,6 +367,7 @@ export function studioSettingsFrom(apiStudioSettings: APIStudioSettings): Comple
367367
enableQuickLoop: apiStudioSettings.enableQuickLoop,
368368
forceQuickLoopAutoNext: forceQuickLoopAutoNextFrom(apiStudioSettings.forceQuickLoopAutoNext),
369369
fallbackPartDuration: apiStudioSettings.fallbackPartDuration ?? DEFAULT_FALLBACK_PART_DURATION,
370+
enableUserEdits: apiStudioSettings.enableUserEdits,
370371
allowAdlibTestingSegment: apiStudioSettings.allowAdlibTestingSegment,
371372
allowHold: apiStudioSettings.allowHold ?? true, // Backwards compatible
372373
allowPieceDirectPlay: apiStudioSettings.allowPieceDirectPlay ?? true, // Backwards compatible
@@ -391,6 +392,7 @@ export function APIStudioSettingsFrom(settings: IStudioSettings): Complete<APISt
391392
enableQuickLoop: settings.enableQuickLoop,
392393
forceQuickLoopAutoNext: APIForceQuickLoopAutoNextFrom(settings.forceQuickLoopAutoNext),
393394
fallbackPartDuration: settings.fallbackPartDuration,
395+
enableUserEdits: settings.enableUserEdits,
394396
allowAdlibTestingSegment: settings.allowAdlibTestingSegment,
395397
allowHold: settings.allowHold,
396398
allowPieceDirectPlay: settings.allowPieceDirectPlay,

meteor/server/cronjobs.ts

Lines changed: 40 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,8 @@ import {
2626
translateMessage,
2727
} from '@sofie-automation/corelib/dist/TranslatableMessage'
2828
import { applyAndValidateOverrides } from '@sofie-automation/corelib/dist/settings/objectWithOverrides'
29+
import { DBRundownPlaylist } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist'
30+
import { StudioId } from '@sofie-automation/corelib/dist/dataModel/Ids'
2931

3032
const lowPrioFcn = (fcn: () => any) => {
3133
// Do it at a random time in the future:
@@ -101,9 +103,35 @@ async function restartCasparCG(systemSettings: ICoreSystemSettings | undefined,
101103
subType: 1,
102104
parentDeviceId: 1,
103105
lastSeen: 1,
106+
studioAndConfigId: 1,
104107
},
105108
}
106-
)) as Array<Pick<PeripheralDevice, '_id' | 'subType' | 'parentDeviceId' | 'lastSeen'>>
109+
)) as Array<Pick<PeripheralDevice, '_id' | 'subType' | 'parentDeviceId' | 'lastSeen' | 'studioAndConfigId'>>
110+
111+
const relevantStudioIds = Array.from(
112+
new Set(
113+
casparcgAndParentDevices
114+
.map((device) => device.studioAndConfigId?.studioId)
115+
.filter((id) => id !== undefined)
116+
)
117+
) as StudioId[]
118+
119+
const activePlaylists = (await RundownPlaylists.findFetchAsync(
120+
{
121+
activationId: {
122+
$exists: true,
123+
},
124+
studioId: {
125+
$in: relevantStudioIds,
126+
},
127+
},
128+
{
129+
projection: {
130+
_id: 1,
131+
studioId: 1,
132+
},
133+
}
134+
)) as Array<Pick<DBRundownPlaylist, '_id' | 'studioId'>>
107135

108136
const deviceMap = normalizeArrayToMap(casparcgAndParentDevices, '_id')
109137

@@ -128,6 +156,17 @@ async function restartCasparCG(systemSettings: ICoreSystemSettings | undefined,
128156
continue
129157
}
130158

159+
const activePlaylistUsingDevice = activePlaylists.find(
160+
(playlist) => playlist.studioId === parentDevice.studioAndConfigId?.studioId
161+
)
162+
if (activePlaylistUsingDevice) {
163+
logger.info(
164+
`Cronjob: Skipping CasparCG device "${device._id}" with a parent device belonging to a Studio ("${activePlaylistUsingDevice.studioId}") with an active RundownPlaylist: "${activePlaylistUsingDevice._id}"`
165+
)
166+
// If a Rundown is active during "low season", it's proably best to just let it go until next "low season" the following day, don't retry
167+
continue
168+
}
169+
131170
if (parentDevice.lastSeen < getCurrentTime() - CASPARCG_LAST_SEEN_PERIOD_MS) {
132171
logger.info(`Cronjob: Skipping CasparCG device "${device._id}" with offline parent device`)
133172
shouldRetryAttempt = true

meteor/server/lib/rest/v1/studios.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,6 +216,7 @@ export interface APIStudioSettings {
216216
forceQuickLoopAutoNext?: 'disabled' | 'enabled_when_valid_duration' | 'enabled_forcing_min_duration'
217217
minimumTakeSpan?: number
218218
fallbackPartDuration?: number
219+
enableUserEdits?: boolean
219220
allowAdlibTestingSegment?: boolean
220221
allowHold?: boolean
221222
allowPieceDirectPlay?: boolean

meteor/yarn.lock

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -935,23 +935,23 @@ __metadata:
935935
languageName: node
936936
linkType: hard
937937

938-
"@mos-connection/helper@npm:v4.2.0":
939-
version: 4.2.0
940-
resolution: "@mos-connection/helper@npm:4.2.0"
938+
"@mos-connection/helper@npm:v4.2.2":
939+
version: 4.2.2
940+
resolution: "@mos-connection/helper@npm:4.2.2"
941941
dependencies:
942-
"@mos-connection/model": 4.2.0
942+
"@mos-connection/model": 4.2.2
943943
iconv-lite: ^0.6.3
944944
tslib: ^2.5.3
945945
xml-js: ^1.6.11
946946
xmlbuilder: ^15.1.1
947-
checksum: 2f0f049bb6b4323f6b3ff8ed10ed92bea3ad01b7b69ae18414a72bda00a3600ae40e73906663c1274ac3d380b9875b59c971a3aaa2b871114a516da4c7a510c8
947+
checksum: c3fedae9c628cdffa6e0d9fa098fd6efb086b1c5cb7365f21a5a5c9a86c35090cd35cd23099e758acb90eb567c36f7355fc5fc23d2a576db26d21ff51b618f78
948948
languageName: node
949949
linkType: hard
950950

951-
"@mos-connection/model@npm:4.2.0, @mos-connection/model@npm:v4.2.0":
952-
version: 4.2.0
953-
resolution: "@mos-connection/model@npm:4.2.0"
954-
checksum: dda98d14d498c7680aefb2aa143bbe5506f89673e072d84076b77226671b01a4452313c8aed1194e06dbb3d3cbe6561615536c96bb2e34e6223ebd4ee8eb548f
951+
"@mos-connection/model@npm:4.2.2, @mos-connection/model@npm:v4.2.2":
952+
version: 4.2.2
953+
resolution: "@mos-connection/model@npm:4.2.2"
954+
checksum: 242dddf7d6804de941ae2eb0cb20467b716b80fbb726e173615279fa6ae6ad9c3e168db0f3e6516d25e6ca6e7a44f8762cefd7439be7da24691732e050acfbb6
955955
languageName: node
956956
linkType: hard
957957

@@ -1221,7 +1221,7 @@ __metadata:
12211221
version: 0.0.0-use.local
12221222
resolution: "@sofie-automation/meteor-lib@portal:../packages/meteor-lib::locator=automation-core%40workspace%3A."
12231223
dependencies:
1224-
"@mos-connection/helper": v4.2.0
1224+
"@mos-connection/helper": v4.2.2
12251225
"@sofie-automation/blueprints-integration": 1.52.0-in-development
12261226
"@sofie-automation/corelib": 1.52.0-in-development
12271227
"@sofie-automation/shared-lib": 1.52.0-in-development
@@ -1239,7 +1239,7 @@ __metadata:
12391239
version: 0.0.0-use.local
12401240
resolution: "@sofie-automation/shared-lib@portal:../packages/shared-lib::locator=automation-core%40workspace%3A."
12411241
dependencies:
1242-
"@mos-connection/model": v4.2.0
1242+
"@mos-connection/model": v4.2.2
12431243
timeline-state-resolver-types: 9.2.0-nightly-release52-20241219-123204-90290cef1.0
12441244
tslib: ^2.6.2
12451245
type-fest: ^3.13.1
@@ -2271,7 +2271,7 @@ __metadata:
22712271
"@babel/runtime": ^7.23.9
22722272
"@koa/cors": ^5.0.0
22732273
"@koa/router": ^12.0.1
2274-
"@mos-connection/helper": v4.2.0
2274+
"@mos-connection/helper": v4.2.2
22752275
"@shopify/jest-koa-mocks": ^5.1.1
22762276
"@slack/webhook": ^6.1.0
22772277
"@sofie-automation/blueprints-integration": "portal:../packages/blueprints-integration"

packages/blueprints-integration/CHANGELOG.md

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,14 @@
33
All notable changes to this project will be documented in this file.
44
See [Conventional Commits](https://conventionalcommits.org) for commit guidelines.
55

6+
## [1.51.6](https://github.com/nrkno/sofie-core/compare/v1.51.5...v1.51.6) (2025-01-14)
7+
8+
**Note:** Version bump only for package @sofie-automation/blueprints-integration
9+
10+
11+
12+
13+
614
## [1.51.5](https://github.com/nrkno/sofie-core/compare/v1.51.4...v1.51.5) (2025-01-07)
715

816
**Note:** Version bump only for package @sofie-automation/blueprints-integration

packages/blueprints-integration/src/content.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,10 @@ export interface VTContent extends BaseContent {
5555
/** Duration of extra content past sourceDuration. Not planned to play back but present on the media and playable. */
5656
postrollDuration?: number
5757
editable?: VTEditableParameters
58+
/** This is for the VT's in out words */
59+
firstWords?: string
60+
lastWords?: string
61+
fullScript?: string
5862
}
5963

6064
export interface GraphicsContent extends BaseContent {

0 commit comments

Comments
 (0)