Skip to content

Commit 9522afe

Browse files
committed
chore: use correct timings
1 parent 64e7ca6 commit 9522afe

File tree

10 files changed

+188
-44
lines changed

10 files changed

+188
-44
lines changed

packages/live-status-gateway-api/api/components/part/extendedPartStatus/extendedPartStatus-example.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ segmentId: 'n1mOVd5_K5tt4sfk6HYfTuwumGQ_'
33
publicData:
44
partType: 'intro'
55
identifier: 'Intro'
6+
instanceId: '156z23fg3fds_1mOVd5_K5tt4sfk6HYfTuwumGQ_'
67
externalId: '1ZIYVYL1aEkNEJbeGsmRXr5s8wtkyxfPRjNSTxZfcoEI'
78
prompterTitle: 'Intro'
89
gap: false

packages/live-status-gateway-api/api/components/part/extendedPartStatus/extendedPartStatus.yaml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,9 @@ $defs:
77
- type: object
88
title: ExtendedPartStatus
99
properties:
10+
instanceId:
11+
description: Unique id of the instance if the current part has one
12+
type: string
1013
segmentId:
1114
description: Unique id of the segment this part belongs to
1215
type: string

packages/live-status-gateway-api/src/generated/asyncapi.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -882,6 +882,9 @@ channels:
882882
- type: object
883883
title: ExtendedPartStatus
884884
properties:
885+
instanceId:
886+
description: Unique id of the instance if the current part has one
887+
type: string
885888
segmentId:
886889
description: Unique id of the segment this part belongs to
887890
type: string
@@ -1051,6 +1054,7 @@ channels:
10511054
publicData:
10521055
partType: intro
10531056
identifier: Intro
1057+
instanceId: 156z23fg3fds_1mOVd5_K5tt4sfk6HYfTuwumGQ_
10541058
externalId: 1ZIYVYL1aEkNEJbeGsmRXr5s8wtkyxfPRjNSTxZfcoEI
10551059
prompterTitle: Intro
10561060
gap: false
@@ -1059,6 +1063,7 @@ channels:
10591063
pieces:
10601064
- *a37
10611065
floated: false
1066+
expectedDuration: 200
10621067
id: H5CBGYjThrMSmaYvRaa5FVKJIzk_
10631068
name: Intro
10641069
autoNext: false

packages/live-status-gateway-api/src/generated/schema.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -599,6 +599,10 @@ interface ExtendedPartStatus {
599599
* If this part will progress to the next automatically
600600
*/
601601
autoNext?: boolean
602+
/**
603+
* Unique id of the instance if the current part has one
604+
*/
605+
instanceId?: string
602606
/**
603607
* Unique id of the segment this part belongs to
604608
*/

packages/live-status-gateway/src/collections/pieceInstancesHandler.ts

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ type PartInstances = PickKeys<SelectedPartInstances, typeof PART_INSTANCES_KEYS>
3939
const SHOW_STYLE_BASE_KEYS = ['sourceLayers'] as const
4040
type ShowStyle = PickKeys<ShowStyleBaseExt, typeof SHOW_STYLE_BASE_KEYS>
4141

42-
export type PieceInstanceMin = Omit<ReadonlyDeep<PieceInstance>, 'reportedStartedPlayback' | 'reportedStoppedPlayback'>
42+
export type PieceInstanceMin = ReadonlyDeep<PieceInstance>
4343

4444
export interface SelectedPieceInstances {
4545
// Pieces reported by the Playout Gateway as active
@@ -145,7 +145,11 @@ export class PieceInstancesHandler extends PublicationCollection<
145145
this._partInstances?.previous?.timings &&
146146
(this._partInstances.previous.timings.plannedStoppedPlayback ?? 0) > Date.now()
147147
) {
148-
active.push(...inPreviousPartInstance)
148+
const carried = inPreviousPartInstance.filter(
149+
(p) => p.infinite && (p.plannedStoppedPlayback ?? Infinity) > Date.now()
150+
)
151+
152+
active.push(...carried)
149153
}
150154

151155
let hasAnythingChanged = false

packages/live-status-gateway/src/topics/extendedActivePlaylistTopic.ts

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@ export class ExtendedActivePlaylistTopic extends WebSocketTopicBase implements W
3434
partsBySegmentId: {},
3535
segmentsById: {},
3636
piecesByPartId: {},
37+
pieceInstancesByPartInstanceId: [],
38+
partInstancesBySegmentId: {},
3739
}
3840

3941
constructor(logger: Logger, handlers: CollectionHandlers) {
@@ -57,7 +59,11 @@ export class ExtendedActivePlaylistTopic extends WebSocketTopicBase implements W
5759
return
5860
}
5961

60-
const message = { ...toExtendedPlaylistStatus(this._playlistStatusCache) }
62+
const message = {
63+
...toExtendedPlaylistStatus(this._playlistStatusCache),
64+
pieceInstancesInCurrentPartInstance: this._playlistStatusCache.pieceInstancesInCurrentPartInstance,
65+
pieceInstancesByPartInstanceId: this._playlistStatusCache.pieceInstancesByPartInstanceId,
66+
}
6167

6268
this.sendMessage(subscribers, message)
6369
}
@@ -128,6 +134,7 @@ export class ExtendedActivePlaylistTopic extends WebSocketTopicBase implements W
128134
nextPartInstance: partInstances.next,
129135
firstInstanceInSegmentPlayout: partInstances.firstInSegmentPlayout,
130136
partInstancesInCurrentSegment: partInstances.inCurrentSegment,
137+
partInstancesBySegmentId: _.groupBy(partInstances.inCurrentSegment ?? [], 'segmentId'),
131138
})
132139
}
133140

@@ -137,11 +144,16 @@ export class ExtendedActivePlaylistTopic extends WebSocketTopicBase implements W
137144
this.updateAndNotify({
138145
pieceInstancesInCurrentPartInstance: undefined,
139146
pieceInstancesInNextPartInstance: undefined,
147+
pieceInstancesByPartInstanceId: undefined,
140148
})
141149
else
142150
this.updateAndNotify({
143151
pieceInstancesInCurrentPartInstance: pieceInstances.currentPartInstance,
144152
pieceInstancesInNextPartInstance: pieceInstances.nextPartInstance,
153+
pieceInstancesByPartInstanceId: [
154+
...(pieceInstances.currentPartInstance ?? []),
155+
...(pieceInstances.nextPartInstance ?? []),
156+
],
145157
})
146158
}
147159

packages/live-status-gateway/src/topics/helpers/part/partStatus.ts

Lines changed: 64 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,8 @@ import { toExtendedPieceStatus, toPieceStatus } from '../pieceStatus.js'
1111
import { ExtendedPlaylistStatusCache, PlaylistStatusCache } from '../playlist/playlistStatus.js'
1212
import { interpollateTranslation } from '@sofie-automation/corelib/dist/TranslatableMessage'
1313
import { toNotificationSeverity } from '../notification/toNotificationStatus.js'
14+
import { DBPartInstance } from '@sofie-automation/corelib/dist/dataModel/PartInstance'
15+
import { PieceInstanceMin } from '../../../collections/pieceInstancesHandler.js'
1416

1517
export function toCurrentPartStatus(cache: PlaylistStatusCache, part: DBPart | null): CurrentPartStatus | null {
1618
if (!cache.currentPartInstance) return null
@@ -29,31 +31,69 @@ export function toCurrentPartStatus(cache: PlaylistStatusCache, part: DBPart | n
2931

3032
export function toPartStatus(
3133
{ pieceInstancesInCurrentPartInstance, showStyleBaseExt }: PlaylistStatusCache,
32-
part: DBPart | null
34+
partOrInstance: DBPart | DBPartInstance | null
3335
): PartStatus | null {
34-
if (!part) return null
36+
if (!partOrInstance) return null
37+
const { part } = getPartData(partOrInstance)
3538

36-
const base = literal<PartStatus>({
39+
return literal<PartStatus>({
3740
id: unprotectString(part._id),
3841
name: part.title,
3942
autoNext: part.autoNext ?? false,
4043
segmentId: unprotectString(part.segmentId),
44+
// Note: if it's an instance, we should ideally use its specific piece instances
4145
pieces: (pieceInstancesInCurrentPartInstance ?? []).map((piece) => toPieceStatus(piece, showStyleBaseExt)),
4246
publicData: part.publicData,
4347
expectedDuration: part.expectedDuration,
4448
})
45-
46-
return literal<PartStatus>(base)
4749
}
4850

4951
export function toExtendedPartStatus(
50-
{ showStyleBaseExt, piecesByPartId }: ExtendedPlaylistStatusCache,
51-
part: DBPart | null
52+
cache: ExtendedPlaylistStatusCache,
53+
partOrInstance: DBPart | DBPartInstance | null
5254
): ExtendedPartStatus | null {
53-
if (!part) return null
55+
if (!partOrInstance) return null
56+
57+
const { showStyleBaseExt, piecesByPartId, pieceInstancesByPartInstanceId } = cache
58+
59+
// Determine if we are looking at an Instance or a raw Part
60+
const isInstance = 'part' in partOrInstance
61+
const part = isInstance ? partOrInstance.part : partOrInstance
62+
const partId = unprotectString(part._id)
63+
const partInstanceId = isInstance ? unprotectString(partOrInstance._id) : undefined
64+
65+
const blueprintPieces = piecesByPartId[partId] ?? []
66+
const instancePieces = partInstanceId
67+
? (pieceInstancesByPartInstanceId ?? []).filter((p) => unprotectString(p.partInstanceId) === partInstanceId)
68+
: []
69+
70+
const instanceByPieceId = new Map<string, PieceInstanceMin>()
71+
for (const inst of instancePieces) {
72+
instanceByPieceId.set(unprotectString(inst.piece._id), inst)
73+
}
74+
75+
const mergedRawPieces: Array<PieceInstanceMin> = []
76+
77+
for (const bp of blueprintPieces) {
78+
const id = unprotectString(bp._id)
79+
const inst = instanceByPieceId.get(id)
80+
if (inst) {
81+
mergedRawPieces.push(inst)
82+
instanceByPieceId.delete(id)
83+
} else {
84+
mergedRawPieces.push(bp as unknown as PieceInstanceMin)
85+
}
86+
}
87+
88+
for (const inst of instanceByPieceId.values()) {
89+
mergedRawPieces.push(inst)
90+
}
91+
92+
const pieces = mergedRawPieces.map((p) => toExtendedPieceStatus(p, showStyleBaseExt, part.expectedDuration ?? 0))
5493

5594
return literal<ExtendedPartStatus>({
56-
id: unprotectString(part._id),
95+
id: partId,
96+
instanceId: partInstanceId,
5797
externalId: part.externalId,
5898
name: part.title,
5999
identifier: part.identifier,
@@ -64,14 +104,11 @@ export function toExtendedPartStatus(
64104
floated: part.floated,
65105
autoNext: part.autoNext ?? false,
66106
segmentId: unprotectString(part.segmentId),
67-
pieces: (piecesByPartId[unprotectString(part._id)] ?? []).map((piece) =>
68-
toExtendedPieceStatus(piece, showStyleBaseExt)
69-
),
107+
pieces: pieces,
70108
publicData: part.publicData,
71109
expectedDuration: part.expectedDuration,
72110
})
73111
}
74-
75112
export function toPartInvalidReason(
76113
invalidReason: PartInvalidReason | undefined = undefined
77114
): InvalidReason | undefined {
@@ -83,3 +120,17 @@ export function toPartInvalidReason(
83120
severity: invalidReason.severity ? toNotificationSeverity(invalidReason.severity) : undefined,
84121
})
85122
}
123+
function getPartData(partOrInstance: DBPart | DBPartInstance) {
124+
if ('part' in partOrInstance) {
125+
return {
126+
part: partOrInstance.part,
127+
id: unprotectString(partOrInstance.part._id),
128+
instanceId: unprotectString(partOrInstance._id),
129+
}
130+
}
131+
return {
132+
part: partOrInstance,
133+
id: unprotectString(partOrInstance._id),
134+
instanceId: undefined,
135+
}
136+
}

packages/live-status-gateway/src/topics/helpers/pieceStatus.ts

Lines changed: 49 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -36,41 +36,68 @@ export function toPieceStatus(
3636

3737
export function toExtendedPieceStatus(
3838
pieceInstance: PieceInstanceMin | Piece,
39-
showStyleBaseExt: ShowStyleBaseExt | undefined
39+
showStyleBaseExt: ShowStyleBaseExt | undefined,
40+
partDurationMs: number
4041
): ExtendedPieceStatus {
41-
if ('piece' in pieceInstance) {
42-
return toPieceStatus(pieceInstance, showStyleBaseExt)
43-
}
42+
const base = toPieceStatus(pieceInstance, showStyleBaseExt)
4443

45-
const sourceLayerName = pieceInstance.sourceLayerId
46-
? showStyleBaseExt?.sourceLayerNamesById.get(pieceInstance.sourceLayerId)
47-
: undefined
48-
const outputLayerName = pieceInstance.outputLayerId
49-
? showStyleBaseExt?.outputLayerNamesById.get(pieceInstance.outputLayerId)
50-
: undefined
44+
const piece = 'piece' in pieceInstance ? pieceInstance.piece : pieceInstance
45+
46+
const timing =
47+
'piece' in pieceInstance
48+
? (toPieceTimingStatusFromInstance(pieceInstance.piece as Piece, pieceInstance, partDurationMs) ??
49+
toPieceTimingStatus(pieceInstance.piece as Piece, partDurationMs))
50+
: toPieceTimingStatus(piece as Piece, partDurationMs)
5151

5252
return {
53-
id: unprotectString(pieceInstance._id),
54-
name: pieceInstance.name,
55-
sourceLayer: sourceLayerName ?? 'invalid',
56-
outputLayer: outputLayerName ?? 'invalid',
57-
tags: clone<string[] | undefined>(pieceInstance.tags),
58-
publicData: pieceInstance.publicData,
59-
notInVision: pieceInstance.notInVision,
60-
pieceType: pieceInstance.pieceType,
61-
timing: toPieceTimingStatus(pieceInstance),
53+
...base,
54+
timing,
6255
}
6356
}
6457

65-
export function toPieceTimingStatus(piece: Piece): PieceTiming {
58+
export function toPieceTimingStatus(piece: Piece, partDurationMs: number): PieceTiming {
59+
const startMs = piece.enable.start === 'now' ? 0 : piece.enable.start
60+
const durationMs = piece.enable.duration ?? Math.max(0, partDurationMs - startMs)
61+
6662
return literal<PieceTiming>({
67-
startMs: piece.enable.start == 'now' ? 0 : piece.enable.start,
68-
durationMs: piece.enable.duration,
63+
startMs,
64+
durationMs,
6965
lifespan: toPieceLifespan(piece.lifespan),
7066
isAbsolute: piece.enable.isAbsolute,
7167
})
7268
}
7369

70+
export function toPieceTimingStatusFromInstance(
71+
piece: Piece,
72+
instance: PieceInstanceMin,
73+
partDurationMs: number
74+
): PieceTiming | undefined {
75+
const started = instance.reportedStartedPlayback ?? instance.plannedStartedPlayback
76+
const stopped = instance.reportedStoppedPlayback ?? instance.plannedStoppedPlayback
77+
78+
if (!started && !stopped && !instance.userDuration) {
79+
return undefined
80+
}
81+
82+
const timing = toPieceTimingStatus(piece, partDurationMs)
83+
84+
if (instance.userDuration) {
85+
return {
86+
...timing,
87+
durationMs: instance.userDuration.endRelativeToPart - timing.startMs,
88+
}
89+
}
90+
91+
if (started && stopped) {
92+
return {
93+
...timing,
94+
durationMs: stopped - started,
95+
}
96+
}
97+
98+
return timing
99+
}
100+
74101
function toPieceLifespan(lifespan: PieceLifespan): PieceLifespanStatus {
75102
switch (lifespan) {
76103
case PieceLifespan.WithinPart:

packages/live-status-gateway/src/topics/helpers/playlist/playlistStatus.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,8 +40,11 @@ export interface PlaylistStatusCache {
4040
export interface ExtendedPlaylistStatusCache extends PlaylistStatusCache {
4141
//rundowns
4242
rundownsInCurrentPlaylist: DBRundown[]
43+
// TODO: only have one array for part instances, replace partInstancesInCurrentSegment
44+
partInstancesBySegmentId: Record<string, DBPartInstance[]>
4345
// pieces
4446
piecesByPartId: Record<string, Piece[]>
47+
pieceInstancesByPartInstanceId: PieceInstanceMin[]
4548
}
4649

4750
export const PLAYLIST_KEYS = [
@@ -64,7 +67,7 @@ export type Playlist = PickKeys<DBRundownPlaylist, typeof PLAYLIST_KEYS>
6467
export const PART_INSTANCES_KEYS = ['current', 'next', 'inCurrentSegment', 'firstInSegmentPlayout'] as const
6568
export type PartInstances = PickKeys<SelectedPartInstances, typeof PART_INSTANCES_KEYS>
6669

67-
export const PIECE_INSTANCES_KEYS = ['currentPartInstance', 'nextPartInstance'] as const
70+
export const PIECE_INSTANCES_KEYS = ['active', 'currentPartInstance', 'nextPartInstance'] as const
6871
export type PieceInstances = PickKeys<SelectedPieceInstances, typeof PIECE_INSTANCES_KEYS>
6972

7073
export const SEGMENT_KEYS = ['_id', 'segmentTiming'] as const

0 commit comments

Comments
 (0)