Skip to content

Commit 4c72f74

Browse files
committed
feat: item statuses
1 parent f21cb5b commit 4c72f74

File tree

6 files changed

+119
-22
lines changed

6 files changed

+119
-22
lines changed

meteor/server/publications/ingestStatus/createIngestRundownStatus.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,11 +121,13 @@ function createIngestPartStatus(
121121

122122
// Determine the ready status from the PartInstance or Part
123123
const isReady = partInstance ? partInstance.part.ingestNotifyPartReady : part?.ingestNotifyPartReady
124+
const itemsReady = partInstance ? partInstance.part.ingestNotifyItemsReady : part?.ingestNotifyItemsReady
124125

125126
return {
126127
externalId: ingestPart.externalId,
127128

128129
isReady: isReady ?? null,
130+
itemsReady: itemsReady ?? {},
129131

130132
playbackStatus,
131133
}

meteor/server/publications/ingestStatus/reactiveContentCache.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,15 @@ export type PartFields =
3131
| 'externalId'
3232
| 'shouldNotifyCurrentPlayingPart'
3333
| 'ingestNotifyPartReady'
34+
| 'ingestNotifyItemsReady'
3435
export const partFieldSpecifier = literal<MongoFieldSpecifierOnesStrict<Pick<DBPart, PartFields>>>({
3536
_id: 1,
3637
rundownId: 1,
3738
segmentId: 1,
3839
externalId: 1,
3940
shouldNotifyCurrentPlayingPart: 1,
4041
ingestNotifyPartReady: 1,
42+
ingestNotifyItemsReady: 1,
4143
})
4244

4345
export type PartInstanceFields = '_id' | 'rundownId' | 'segmentId' | 'part' | 'takeCount'

packages/blueprints-integration/src/documents/part.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ export interface IBlueprintMutatablePart<TPrivateData = unknown, TPublicData = u
6464
/** Whether part should be reported as ready to the ingest-device. Set to undefined/null to disable this reporting */
6565
ingestNotifyPartReady?: boolean | null
6666

67+
/** Report items as ready to the ingest-device. Only named items will be reported, using the boolean value provided */
68+
ingestNotifyItemsReady?: Record<string, boolean | undefined>
69+
6770
/** Classes to set on the TimelineGroupObj for this part */
6871
classes?: string[]
6972
/** Classes to set on the TimelineGroupObj for the following part */

packages/job-worker/src/blueprints/context/lib.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,7 @@ export const PlayoutMutatablePartSampleKeys = allKeysOfObject<PlayoutMutatablePa
118118
holdMode: true,
119119
shouldNotifyCurrentPlayingPart: true,
120120
ingestNotifyPartReady: true,
121+
ingestNotifyItemsReady: true,
121122
classes: true,
122123
classesForNext: true,
123124
displayDurationGroup: true,
@@ -280,6 +281,7 @@ export function convertPartToBlueprints(part: ReadonlyDeep<DBPart>): IBlueprintP
280281
holdMode: part.holdMode,
281282
shouldNotifyCurrentPlayingPart: part.shouldNotifyCurrentPlayingPart,
282283
ingestNotifyPartReady: part.ingestNotifyPartReady,
284+
ingestNotifyItemsReady: clone(part.ingestNotifyItemsReady),
283285
classes: clone<string[] | undefined>(part.classes),
284286
classesForNext: clone<string[] | undefined>(part.classesForNext),
285287
displayDurationGroup: part.displayDurationGroup,

packages/mos-gateway/src/mosStatusHandler.ts

Lines changed: 108 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,15 @@
1-
import { getMosTypes, IMOSObjectStatus, IMOSStoryStatus, MosTypes, type IMOSDevice } from '@mos-connection/connector'
1+
import {
2+
getMosTypes,
3+
IMOSItemStatus,
4+
IMOSObjectStatus,
5+
IMOSStoryStatus,
6+
MosTypes,
7+
type IMOSDevice,
8+
} from '@mos-connection/connector'
29
import type { MosDeviceStatusesConfig } from './generated/devices'
310
import type { CoreMosDeviceHandler } from './CoreMosDeviceHandler'
411
import {
12+
assertNever,
513
type Observer,
614
PeripheralDevicePubSub,
715
PeripheralDevicePubSubCollectionsNames,
@@ -99,22 +107,39 @@ export class MosStatusHandler {
99107

100108
this.#messageQueue
101109
.putOnQueue(async () => {
102-
const newStatus: IMOSStoryStatus = {
103-
RunningOrderId: this.#mosTypes.mosString128.create(status.rundownExternalId),
104-
ID: this.#mosTypes.mosString128.create(status.storyId),
105-
Status: status.mosStatus,
106-
Time: diffTime,
107-
}
108-
this.#logger.info(`Sending Story status: ${JSON.stringify(newStatus)}`)
109-
110110
if (this.#isDeviceConnected()) {
111-
// Send status
112-
await this.#mosDevice.sendStoryStatus(newStatus)
111+
if (status.type === 'item') {
112+
const newStatus: IMOSItemStatus = {
113+
RunningOrderId: this.#mosTypes.mosString128.create(status.rundownExternalId),
114+
StoryId: this.#mosTypes.mosString128.create(status.storyId),
115+
ID: this.#mosTypes.mosString128.create(status.itemId),
116+
Status: status.mosStatus,
117+
Time: diffTime,
118+
}
119+
this.#logger.info(`Sending Story status: ${JSON.stringify(newStatus)}`)
120+
121+
// Send status
122+
await this.#mosDevice.sendItemStatus(newStatus)
123+
} else if (status.type === 'story') {
124+
const newStatus: IMOSStoryStatus = {
125+
RunningOrderId: this.#mosTypes.mosString128.create(status.rundownExternalId),
126+
ID: this.#mosTypes.mosString128.create(status.storyId),
127+
Status: status.mosStatus,
128+
Time: diffTime,
129+
}
130+
this.#logger.info(`Sending Story status: ${JSON.stringify(newStatus)}`)
131+
132+
// Send status
133+
await this.#mosDevice.sendStoryStatus(newStatus)
134+
} else {
135+
this.#logger.debug(`Discarding unknown queued status: ${JSON.stringify(status)}`)
136+
assertNever(status)
137+
}
113138
} else if (this.#config.onlySendPlay) {
114139
// No need to do anything.
115-
this.#logger.info(`Not connected, skipping play status: ${JSON.stringify(newStatus)}`)
140+
this.#logger.info(`Not connected, skipping play status: ${JSON.stringify(status)}`)
116141
} else {
117-
this.#logger.info(`Not connected, discarding status: ${JSON.stringify(newStatus)}`)
142+
this.#logger.info(`Not connected, discarding status: ${JSON.stringify(status)}`)
118143
}
119144
})
120145
.catch((e) => {
@@ -142,7 +167,18 @@ export class MosStatusHandler {
142167
}
143168
}
144169

145-
interface StoryStatusItem {
170+
type SomeStatusEntry = StoryStatusEntry | ItemStatusEntry
171+
172+
interface ItemStatusEntry {
173+
type: 'item'
174+
rundownExternalId: string
175+
storyId: string
176+
itemId: string
177+
mosStatus: IMOSObjectStatus
178+
}
179+
180+
interface StoryStatusEntry {
181+
type: 'story'
146182
rundownExternalId: string
147183
storyId: string
148184
mosStatus: IMOSObjectStatus
@@ -152,43 +188,92 @@ function diffStatuses(
152188
config: MosDeviceStatusesConfig,
153189
previousStatuses: IngestRundownStatus | undefined,
154190
newStatuses: IngestRundownStatus | undefined
155-
): StoryStatusItem[] {
191+
): SomeStatusEntry[] {
156192
const rundownExternalId = previousStatuses?.externalId ?? newStatuses?.externalId
157193

158194
if ((!previousStatuses && !newStatuses) || !rundownExternalId) return []
159195

160-
const statuses: StoryStatusItem[] = []
196+
const statuses: SomeStatusEntry[] = []
161197

162198
const previousStories = buildStoriesMap(previousStatuses)
163199
const newStories = buildStoriesMap(newStatuses)
164200

165201
// Process any removed stories first
166-
for (const storyId of previousStories.keys()) {
202+
for (const [storyId, story] of previousStories) {
167203
if (!newStories.has(storyId)) {
168204
// The story has been removed
169205
statuses.push({
206+
type: 'story',
170207
rundownExternalId,
171208
storyId,
172209
mosStatus: MOS_STATUS_UNKNOWN,
173210
})
211+
212+
// Clear any items too
213+
for (const [itemId, isReady] of Object.entries<boolean | undefined>(story.itemsReady)) {
214+
if (isReady === undefined) continue
215+
216+
statuses.push({
217+
type: 'item',
218+
rundownExternalId,
219+
storyId,
220+
itemId: itemId,
221+
mosStatus: MOS_STATUS_UNKNOWN,
222+
})
223+
}
174224
}
175225
}
176226

177227
// Then any remaining stories in order
178228
for (const [storyId, status] of newStories) {
179229
const previousStatus = previousStories.get(storyId)
180230

181-
const newMosStatus = buildMosStatus(config, status, newStatuses?.active)
231+
const newMosStatus = buildMosStatus(config, status.playbackStatus, status.isReady, newStatuses?.active)
182232
if (
183233
newMosStatus !== null &&
184-
(!previousStatus || buildMosStatus(config, previousStatus, previousStatuses?.active) !== newMosStatus)
234+
(!previousStatus ||
235+
buildMosStatus(
236+
config,
237+
previousStatus.playbackStatus,
238+
previousStatus.isReady,
239+
previousStatuses?.active
240+
) !== newMosStatus)
185241
) {
186242
statuses.push({
243+
type: 'story',
187244
rundownExternalId,
188245
storyId,
189246
mosStatus: newMosStatus,
190247
})
191248
}
249+
250+
// Diff each item in the story
251+
const previousItemStatuses = previousStatus?.itemsReady ?? {}
252+
const allItemIds = new Set<string>([...Object.keys(status.itemsReady), ...Object.keys(previousItemStatuses)])
253+
254+
for (const itemId of allItemIds) {
255+
const newReady = status.itemsReady[itemId]
256+
const previousReady = previousItemStatuses[itemId]
257+
258+
const newMosStatus =
259+
newReady !== undefined
260+
? buildMosStatus(config, status.playbackStatus, newReady, newStatuses?.active)
261+
: null
262+
const previousMosStatus =
263+
previousReady !== undefined && previousStatus
264+
? buildMosStatus(config, previousStatus.playbackStatus, previousReady, previousStatuses?.active)
265+
: null
266+
267+
if (newMosStatus !== null && previousMosStatus !== newMosStatus) {
268+
statuses.push({
269+
type: 'item',
270+
rundownExternalId,
271+
storyId,
272+
itemId,
273+
mosStatus: newMosStatus,
274+
})
275+
}
276+
}
192277
}
193278

194279
return statuses
@@ -210,19 +295,20 @@ function buildStoriesMap(state: IngestRundownStatus | undefined): Map<string, In
210295

211296
function buildMosStatus(
212297
config: MosDeviceStatusesConfig,
213-
story: IngestPartStatus,
298+
playbackStatus: IngestPartPlaybackStatus,
299+
isReady: boolean | null | undefined,
214300
active: IngestRundownStatus['active'] | undefined
215301
): IMOSObjectStatus | null {
216302
if (active === 'inactive') return MOS_STATUS_UNKNOWN
217303
if (active === 'rehearsal' && !config.sendInRehearsal) return null
218304

219-
switch (story.playbackStatus) {
305+
switch (playbackStatus) {
220306
case IngestPartPlaybackStatus.PLAY:
221307
return IMOSObjectStatus.PLAY
222308
case IngestPartPlaybackStatus.STOP:
223309
return IMOSObjectStatus.STOP
224310
default:
225-
switch (story.isReady) {
311+
switch (isReady) {
226312
case true:
227313
return IMOSObjectStatus.READY
228314
case false:

packages/shared-lib/src/ingest/rundownStatus.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ export interface IngestPartStatus {
3030

3131
isReady: boolean | null
3232

33+
itemsReady: Record<string, boolean | undefined>
34+
3335
playbackStatus: IngestPartPlaybackStatus
3436
}
3537

0 commit comments

Comments
 (0)