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'
29import type { MosDeviceStatusesConfig } from './generated/devices'
310import type { CoreMosDeviceHandler } from './CoreMosDeviceHandler'
411import {
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
211296function 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 :
0 commit comments