diff --git a/packages/blueprints-integration/src/documents/playlistTiming.ts b/packages/blueprints-integration/src/documents/playlistTiming.ts index 672701d2ba..41ae942dfa 100644 --- a/packages/blueprints-integration/src/documents/playlistTiming.ts +++ b/packages/blueprints-integration/src/documents/playlistTiming.ts @@ -4,6 +4,7 @@ export enum PlaylistTimingType { None = 'none', ForwardTime = 'forward-time', BackTime = 'back-time', + Duration = 'duration', } export interface PlaylistTimingBase { @@ -49,4 +50,29 @@ export interface PlaylistTimingBackTime extends PlaylistTimingBase { expectedEnd: Time } -export type RundownPlaylistTiming = PlaylistTimingNone | PlaylistTimingForwardTime | PlaylistTimingBackTime +/** + * This mode is intended for shows with a "floating start", + * meaning they will start based on when the show before them on the channel ends. + * In this mode, we will preserve the Duration and automatically calculate the expectedEnd + * based on the _actual_ start of the show (playlist.startedPlayback). + * + * The optional expectedStart property allows setting a start property of the show that will not affect + * timing calculations, only purpose is to drive UI and inform the users about the preliminary plan as + * planned in the editorial planning tool. + */ +export interface PlaylistTimingDuration extends PlaylistTimingBase { + type: PlaylistTimingType.Duration + /** A stipulated start time, to drive UIs pre-show, but not affecting calculations during the show. + */ + expectedStart?: Time + /** Planned duration of the rundown playlist + * When the show starts, an expectedEnd gets automatically calculated with this as an offset from that starting point + */ + expectedDuration: number +} + +export type RundownPlaylistTiming = + | PlaylistTimingNone + | PlaylistTimingForwardTime + | PlaylistTimingBackTime + | PlaylistTimingDuration diff --git a/packages/corelib/src/playout/rundownTiming.ts b/packages/corelib/src/playout/rundownTiming.ts index ea2ec02e9b..f304196fc2 100644 --- a/packages/corelib/src/playout/rundownTiming.ts +++ b/packages/corelib/src/playout/rundownTiming.ts @@ -13,6 +13,7 @@ import { PlaylistTimingBackTime, + PlaylistTimingDuration, PlaylistTimingForwardTime, PlaylistTimingNone, PlaylistTimingType, @@ -34,6 +35,10 @@ export namespace PlaylistTiming { return timing.type === PlaylistTimingType.BackTime } + export function isPlaylistDurationTimed(timing: RundownPlaylistTiming): timing is PlaylistTimingDuration { + return timing.type === PlaylistTimingType.Duration + } + export function getExpectedStart(timing: RundownPlaylistTiming): number | undefined { if (PlaylistTiming.isPlaylistTimingForwardTime(timing)) { return timing.expectedStart @@ -42,12 +47,14 @@ export namespace PlaylistTiming { timing.expectedStart || (timing.expectedDuration ? timing.expectedEnd - timing.expectedDuration : undefined) ) + } else if (PlaylistTiming.isPlaylistDurationTimed(timing)) { + return timing.expectedStart } else { return undefined } } - export function getExpectedEnd(timing: RundownPlaylistTiming): number | undefined { + export function getExpectedEnd(timing: RundownPlaylistTiming, startedPlayback?: number): number | undefined { if (PlaylistTiming.isPlaylistTimingBackTime(timing)) { return timing.expectedEnd } else if (PlaylistTiming.isPlaylistTimingForwardTime(timing)) { @@ -55,6 +62,14 @@ export namespace PlaylistTiming { timing.expectedEnd || (timing.expectedDuration ? timing.expectedStart + timing.expectedDuration : undefined) ) + } else if (PlaylistTiming.isPlaylistDurationTimed(timing)) { + if (!startedPlayback) { + // before the show, estimate the end from start + dur + return timing.expectedStart ? timing.expectedStart + timing.expectedDuration : undefined + } else { + // after starting the show, project the end from startedPlayback + dur + return startedPlayback + timing.expectedDuration + } } else { return undefined } @@ -70,6 +85,20 @@ export namespace PlaylistTiming { } } + export function getEstimatedEnd( + timing: RundownPlaylistTiming, + now: number, + remainingPlaylistDuration?: number, + startedPlayback?: number + ): number | undefined { + if (remainingPlaylistDuration !== undefined) { + const frontAnchor = startedPlayback ? now : Math.max(now, PlaylistTiming.getExpectedStart(timing) ?? now) + + return frontAnchor + remainingPlaylistDuration + } + return undefined + } + export function sortTimings( a: ReadonlyDeep<{ timing: RundownPlaylistTiming }>, b: ReadonlyDeep<{ timing: RundownPlaylistTiming }> diff --git a/packages/live-status-gateway-api/api/components/playlist/activePlaylistEvent/activePlaylistEvent-example.yaml b/packages/live-status-gateway-api/api/components/playlist/activePlaylistEvent/activePlaylistEvent-example.yaml index d09a8222ef..58c596432a 100644 --- a/packages/live-status-gateway-api/api/components/playlist/activePlaylistEvent/activePlaylistEvent-example.yaml +++ b/packages/live-status-gateway-api/api/components/playlist/activePlaylistEvent/activePlaylistEvent-example.yaml @@ -15,3 +15,38 @@ timing: $ref: '../../timing/activePlaylistTiming/activePlaylistTiming-example.yaml' quickLoop: $ref: '../../quickLoop/activePlaylistQuickLoop/activePlaylistQuickLoop-example.yaml' +tTimers: + - index: 1 + label: 'Segment Timer' + configured: true + mode: + type: countdown + duration: 120000 + stopAtZero: true + state: + paused: false + zeroTime: 1706371920000 + pauseTime: 1706371900000 + projected: + paused: false + zeroTime: 1706371925000 + pauseTime: null + anchorPartId: 'part_break_1' + - index: 2 + label: 'Show Timer' + configured: true + mode: + type: freeRun + state: + paused: false + zeroTime: 1706371800000 + pauseTime: null + projected: null + anchorPartId: null + - index: 3 + label: '' + configured: false + mode: null + state: null + projected: null + anchorPartId: null diff --git a/packages/live-status-gateway-api/api/components/playlist/activePlaylistEvent/activePlaylistEvent.yaml b/packages/live-status-gateway-api/api/components/playlist/activePlaylistEvent/activePlaylistEvent.yaml index 21e7277c14..fbaf22ffe4 100644 --- a/packages/live-status-gateway-api/api/components/playlist/activePlaylistEvent/activePlaylistEvent.yaml +++ b/packages/live-status-gateway-api/api/components/playlist/activePlaylistEvent/activePlaylistEvent.yaml @@ -46,7 +46,14 @@ $defs: $ref: '../../timing/activePlaylistTiming/activePlaylistTiming.yaml#/$defs/activePlaylistTiming' quickLoop: $ref: '../../quickLoop/activePlaylistQuickLoop/activePlaylistQuickLoop.yaml#/$defs/activePlaylistQuickLoop' - required: [event, id, externalId, name, rundownIds, currentPart, currentSegment, nextPart, timing] + tTimers: + description: Status of the 3 T-timers in the playlist + type: array + items: + $ref: '../../tTimers/tTimerStatus.yaml#/$defs/tTimerStatus' + minItems: 3 + maxItems: 3 + required: [event, id, externalId, name, rundownIds, currentPart, currentSegment, nextPart, timing, tTimers] additionalProperties: false examples: - $ref: './activePlaylistEvent-example.yaml' diff --git a/packages/live-status-gateway-api/api/components/tTimers/tTimerIndex.yaml b/packages/live-status-gateway-api/api/components/tTimers/tTimerIndex.yaml new file mode 100644 index 0000000000..aab940cecd --- /dev/null +++ b/packages/live-status-gateway-api/api/components/tTimers/tTimerIndex.yaml @@ -0,0 +1,6 @@ +$defs: + tTimerIndex: + type: integer + title: TTimerIndex + description: Timer index (1-3). The playlist always has 3 T-timer slots. + enum: [1, 2, 3] diff --git a/packages/live-status-gateway-api/api/components/tTimers/tTimerStatus.yaml b/packages/live-status-gateway-api/api/components/tTimers/tTimerStatus.yaml new file mode 100644 index 0000000000..25fc49b491 --- /dev/null +++ b/packages/live-status-gateway-api/api/components/tTimers/tTimerStatus.yaml @@ -0,0 +1,45 @@ +$defs: + tTimerStatus: + type: object + title: TTimerStatus + description: Status of a single T-timer in the playlist + properties: + index: + $ref: './tTimerIndex.yaml#/$defs/tTimerIndex' + label: + type: string + description: User-defined label for the timer + configured: + type: boolean + description: Whether the timer has been configured (mode and state are not null) + mode: + description: >- + Timer configuration/mode defining the timer's behavior. + Null if not configured. + oneOf: + - type: 'null' + - $ref: './timerMode.yaml#/$defs/timerMode' + state: + description: >- + Current runtime state of the timer. + Null if not configured. + oneOf: + - type: 'null' + - $ref: './timerState.yaml#/$defs/timerState' + projected: + description: >- + Projected timing for when we expect to reach an anchor part. + Used to calculate over/under diff. Has the same structure as state. + Running means progressing towards anchor, paused means pushing/delaying anchor. + oneOf: + - type: 'null' + - $ref: './timerState.yaml#/$defs/timerState' + anchorPartId: + description: >- + The ID of the target Part that this timer is counting towards (the "timing anchor"). + Optional - null if no anchor is set. + oneOf: + - type: string + - type: 'null' + required: [index, label, configured, mode, state] + additionalProperties: false diff --git a/packages/live-status-gateway-api/api/components/tTimers/timerMode.yaml b/packages/live-status-gateway-api/api/components/tTimers/timerMode.yaml new file mode 100644 index 0000000000..fbd30ca48d --- /dev/null +++ b/packages/live-status-gateway-api/api/components/tTimers/timerMode.yaml @@ -0,0 +1,57 @@ +$defs: + timerModeCountdown: + type: object + title: TimerModeCountdown + description: Countdown timer mode - counts down from a duration to zero + properties: + type: + type: string + const: countdown + duration: + type: number + description: The original countdown duration in milliseconds (used for reset) + stopAtZero: + type: boolean + description: Whether timer stops at zero or continues into negative values + required: [type, duration, stopAtZero] + additionalProperties: false + + timerModeFreeRun: + type: object + title: TimerModeFreeRun + description: Free-running timer mode - counts up from start time + properties: + type: + type: string + const: freeRun + required: [type] + additionalProperties: false + + timerModeTimeOfDay: + type: object + title: TimerModeTimeOfDay + description: Time-of-day timer mode - counts down/up to a specific time + properties: + type: + type: string + const: timeOfDay + targetRaw: + description: >- + The raw target string as provided when setting the timer + (e.g. "14:30", "2023-12-31T23:59:59Z", or a timestamp number) + oneOf: + - type: string + - type: number + stopAtZero: + type: boolean + description: Whether timer stops at zero or continues into negative values + required: [type, targetRaw, stopAtZero] + additionalProperties: false + + timerMode: + title: TimerMode + description: Configuration/mode of a T-timer (defines behavior type) + oneOf: + - $ref: '#/$defs/timerModeCountdown' + - $ref: '#/$defs/timerModeFreeRun' + - $ref: '#/$defs/timerModeTimeOfDay' diff --git a/packages/live-status-gateway-api/api/components/tTimers/timerState.yaml b/packages/live-status-gateway-api/api/components/tTimers/timerState.yaml new file mode 100644 index 0000000000..10f3477ae9 --- /dev/null +++ b/packages/live-status-gateway-api/api/components/tTimers/timerState.yaml @@ -0,0 +1,61 @@ +$defs: + timerStateRunning: + type: object + title: TimerStateRunning + description: Timer state when running (progressing with real time) + properties: + paused: + type: boolean + const: false + description: Whether the timer is paused + zeroTime: + type: number + description: >- + Unix timestamp (ms) when the timer reaches/reached zero. + For countdown timers, this is when time runs out. + For free-run timers, this is when the timer started. + Client calculates current value relative to this timestamp. + pauseTime: + description: >- + Optional timestamp when the timer should automatically pause + (e.g., when current part ends and overrun begins). + oneOf: + - type: number + - type: 'null' + required: [paused, zeroTime] + additionalProperties: false + + timerStatePaused: + type: object + title: TimerStatePaused + description: Timer state when paused (frozen at a specific duration) + properties: + paused: + type: boolean + const: true + description: Whether the timer is paused + duration: + type: number + description: >- + Frozen duration value in milliseconds. + For countdown timers, this is remaining time. + For free-run timers, this is elapsed time. + pauseTime: + description: >- + Optional timestamp when the timer should pause. + Typically null when already paused. + oneOf: + - type: number + - type: 'null' + required: [paused, duration] + additionalProperties: false + + timerState: + title: TimerState + description: >- + Runtime state of a timer, optimized for efficient client rendering. + When running, the client calculates current time from zeroTime. + When paused, the duration is frozen and sent directly. + oneOf: + - $ref: '#/$defs/timerStateRunning' + - $ref: '#/$defs/timerStatePaused' diff --git a/packages/live-status-gateway-api/api/components/timing/activePlaylistTiming/activePlaylistTimingMode.yaml b/packages/live-status-gateway-api/api/components/timing/activePlaylistTiming/activePlaylistTimingMode.yaml index 90520f52a0..73dbf67dcb 100644 --- a/packages/live-status-gateway-api/api/components/timing/activePlaylistTiming/activePlaylistTimingMode.yaml +++ b/packages/live-status-gateway-api/api/components/timing/activePlaylistTiming/activePlaylistTimingMode.yaml @@ -5,3 +5,4 @@ enum: - none - forward-time - back-time + - duration diff --git a/packages/live-status-gateway-api/src/generated/asyncapi.yaml b/packages/live-status-gateway-api/src/generated/asyncapi.yaml index 71d5675007..b15aa30b9a 100644 --- a/packages/live-status-gateway-api/src/generated/asyncapi.yaml +++ b/packages/live-status-gateway-api/src/generated/asyncapi.yaml @@ -401,7 +401,7 @@ channels: pieces: description: All pieces in this part type: array - items: &a30 + items: &a31 type: object title: PieceStatus properties: @@ -475,7 +475,7 @@ channels: - segmentId - pieces examples: - - &a26 + - &a27 segmentId: n1mOVd5_K5tt4sfk6HYfTuwumGQ_ pieces: &a15 - *a13 @@ -523,7 +523,7 @@ channels: required: - timing examples: - - &a24 + - &a25 timing: *a14 segmentId: n1mOVd5_K5tt4sfk6HYfTuwumGQ_ pieces: *a15 @@ -537,7 +537,7 @@ channels: - type: object title: CurrentSegment allOf: - - &a32 + - &a33 title: SegmentBase type: object properties: @@ -556,7 +556,7 @@ channels: title: CurrentSegmentTiming description: Timing information about the current segment allOf: - - &a33 + - &a34 type: object title: SegmentTiming properties: @@ -632,7 +632,7 @@ channels: - timing - parts examples: - - &a25 + - &a26 timing: *a19 parts: - *a20 @@ -660,6 +660,7 @@ channels: - none - forward-time - back-time + - duration startedPlayback: description: Unix timestamp of when the playlist started (milliseconds) type: number @@ -680,7 +681,7 @@ channels: - timingMode additionalProperties: false examples: - - &a27 + - &a28 timingMode: forward-time expectedStart: 1728895750727 expectedDurationMs: 180000 @@ -734,11 +735,174 @@ channels: - locked - running examples: - - &a28 + - &a29 locked: false running: true start: *a23 end: *a23 + tTimers: + description: Status of the 3 T-timers in the playlist + type: array + items: + type: object + title: TTimerStatus + description: Status of a single T-timer in the playlist + properties: + index: + type: integer + title: TTimerIndex + description: Timer index (1-3). The playlist always has 3 T-timer slots. + enum: + - 1 + - 2 + - 3 + label: + type: string + description: User-defined label for the timer + configured: + type: boolean + description: Whether the timer has been configured (mode and state are not null) + mode: + description: Timer configuration/mode defining the timer's behavior. Null if not + configured. + oneOf: + - type: "null" + - title: TimerMode + description: Configuration/mode of a T-timer (defines behavior type) + oneOf: + - type: object + title: TimerModeCountdown + description: Countdown timer mode - counts down from a duration to zero + properties: + type: + type: string + const: countdown + duration: + type: number + description: The original countdown duration in milliseconds (used for reset) + stopAtZero: + type: boolean + description: Whether timer stops at zero or continues into negative values + required: + - type + - duration + - stopAtZero + additionalProperties: false + - type: object + title: TimerModeFreeRun + description: Free-running timer mode - counts up from start time + properties: + type: + type: string + const: freeRun + required: + - type + additionalProperties: false + - type: object + title: TimerModeTimeOfDay + description: Time-of-day timer mode - counts down/up to a specific time + properties: + type: + type: string + const: timeOfDay + targetRaw: + description: The raw target string as provided when setting the timer (e.g. + "14:30", "2023-12-31T23:59:59Z", or a + timestamp number) + oneOf: + - type: string + - type: number + stopAtZero: + type: boolean + description: Whether timer stops at zero or continues into negative values + required: + - type + - targetRaw + - stopAtZero + additionalProperties: false + state: + description: Current runtime state of the timer. Null if not configured. + oneOf: + - type: "null" + - &a24 + title: TimerState + description: Runtime state of a timer, optimized for efficient client rendering. + When running, the client calculates current time + from zeroTime. When paused, the duration is frozen + and sent directly. + oneOf: + - type: object + title: TimerStateRunning + description: Timer state when running (progressing with real time) + properties: + paused: + type: boolean + const: false + description: Whether the timer is paused + zeroTime: + type: number + description: Unix timestamp (ms) when the timer reaches/reached zero. For + countdown timers, this is when time runs + out. For free-run timers, this is when the + timer started. Client calculates current + value relative to this timestamp. + pauseTime: + description: Optional timestamp when the timer should automatically pause (e.g., + when current part ends and overrun + begins). + oneOf: + - type: number + - type: "null" + required: + - paused + - zeroTime + additionalProperties: false + - type: object + title: TimerStatePaused + description: Timer state when paused (frozen at a specific duration) + properties: + paused: + type: boolean + const: true + description: Whether the timer is paused + duration: + type: number + description: Frozen duration value in milliseconds. For countdown timers, this + is remaining time. For free-run timers, + this is elapsed time. + pauseTime: + description: Optional timestamp when the timer should pause. Typically null when + already paused. + oneOf: + - type: number + - type: "null" + required: + - paused + - duration + additionalProperties: false + projected: + description: Projected timing for when we expect to reach an anchor part. Used + to calculate over/under diff. Has the same structure + as state. Running means progressing towards anchor, + paused means pushing/delaying anchor. + oneOf: + - type: "null" + - *a24 + anchorPartId: + description: The ID of the target Part that this timer is counting towards (the + "timing anchor"). Optional - null if no anchor is set. + oneOf: + - type: string + - type: "null" + required: + - index + - label + - configured + - mode + - state + additionalProperties: false + minItems: 3 + maxItems: 3 required: - event - id @@ -749,24 +913,60 @@ channels: - currentSegment - nextPart - timing + - tTimers additionalProperties: false examples: - - &a29 + - &a30 event: activePlaylist id: OKAgZmZ0Buc99lE_2uPPSKVbMrQ_ externalId: 1ZIYVYL1aEkNEJbeGsmRXr5s8wtkyxfPRjNSTxZfcoEI name: Playlist 0 rundownIds: - y9HauyWkcxQS3XaAOsW40BRLLsI_ - currentPart: *a24 - currentSegment: *a25 - nextPart: *a26 + currentPart: *a25 + currentSegment: *a26 + nextPart: *a27 publicData: category: Evening News - timing: *a27 - quickLoop: *a28 + timing: *a28 + quickLoop: *a29 + tTimers: + - index: 1 + label: Segment Timer + configured: true + mode: + type: countdown + duration: 120000 + stopAtZero: true + state: + paused: false + zeroTime: 1706371920000 + pauseTime: 1706371900000 + projected: + paused: false + zeroTime: 1706371925000 + pauseTime: null + anchorPartId: part_break_1 + - index: 2 + label: Show Timer + configured: true + mode: + type: freeRun + state: + paused: false + zeroTime: 1706371800000 + pauseTime: null + projected: null + anchorPartId: null + - index: 3 + label: "" + configured: false + mode: null + state: null + projected: null + anchorPartId: null examples: - - payload: *a29 + - payload: *a30 activePieces: description: Topic for active pieces updates subscribe: @@ -791,20 +991,20 @@ channels: activePieces: description: Pieces that are currently active (on air) type: array - items: *a30 + items: *a31 required: - event - rundownPlaylistId - activePieces additionalProperties: false examples: - - &a31 + - &a32 event: activePieces rundownPlaylistId: OKAgZmZ0Buc99lE_2uPPSKVbMrQ_ activePieces: - *a13 examples: - - payload: *a31 + - payload: *a32 segments: description: Topic for Segment updates subscribe: @@ -833,7 +1033,7 @@ channels: type: object title: Segment allOf: - - *a32 + - *a33 - type: object title: Segment properties: @@ -847,7 +1047,7 @@ channels: name: description: Name of the segment type: string - timing: *a33 + timing: *a34 publicData: description: Optional arbitrary data required: @@ -860,7 +1060,7 @@ channels: - name - timing examples: - - &a34 + - &a35 identifier: Segment 0 identifier rundownId: y9HauyWkcxQS3XaAOsW40BRLLsI_ name: Segment 0 @@ -876,13 +1076,13 @@ channels: - rundownPlaylistId - segments examples: - - &a35 + - &a36 event: segments rundownPlaylistId: OKAgZmZ0Buc99lE_2uPPSKVbMrQ_ segments: - - *a34 + - *a35 examples: - - payload: *a35 + - payload: *a36 adLibs: description: Topic for AdLibs updates subscribe: @@ -912,7 +1112,7 @@ channels: items: title: AdLibStatus allOf: - - &a40 + - &a41 title: AdLibBase type: object properties: @@ -947,7 +1147,7 @@ channels: - label additionalProperties: false examples: - - &a36 + - &a37 name: pvw label: Preview tags: @@ -967,15 +1167,15 @@ channels: - sourceLayer - actionType examples: - - &a41 + - &a42 id: C6K_yIMuGFUk8X_L9A9_jRT6aq4_ name: Music video clip sourceLayer: Video Clip - actionType: &a37 - - *a36 - tags: &a38 + actionType: &a38 + - *a37 + tags: &a39 - music_video - publicData: &a39 + publicData: &a40 fileName: MV000123.mxf optionsSchema: '{"$schema":"https://json-schema.org/draft/2020-12/schema","title":"Play Video @@ -1007,15 +1207,15 @@ channels: - segmentId - partId examples: - - &a42 + - &a43 segmentId: HsD8_QwE1ZmR5vN3XcK_Ab7y partId: JkL3_OpR6WxT1bF8Vq2_Zy9u id: C6K_yIMuGFUk8X_L9A9_jRT6aq4_ name: Music video clip sourceLayer: Video Clip - actionType: *a37 - tags: *a38 - publicData: *a39 + actionType: *a38 + tags: *a39 + publicData: *a40 optionsSchema: '{"$schema":"https://json-schema.org/draft/2020-12/schema","title":"Play Video Clip","type":"object","properties":{"type":"adlib_action_video_clip","label":{"type":"string"},"clipId":{"type":"string"},"vo":{"type":"boolean"},"target":{"$schema":"https://json-schema.org/draft/2020-12/schema","title":"Object @@ -1039,9 +1239,9 @@ channels: items: title: GlobalAdLibStatus allOf: - - *a40 - examples: - *a41 + examples: + - *a42 required: - event - rundownPlaylistId @@ -1049,15 +1249,15 @@ channels: - globalAdLibs additionalProperties: false examples: - - &a43 + - &a44 event: adLibs rundownPlaylistId: OKAgZmZ0Buc99lE_2uPPSKVbMrQ_ adLibs: - - *a42 + - *a43 globalAdLibs: - - *a41 + - *a42 examples: - - payload: *a43 + - payload: *a44 packages: description: Packages topic for websocket subscriptions. Packages are assets that need to be prepared by Sofie Package Manager or third-party systems @@ -1165,7 +1365,7 @@ channels: - pieceOrAdLibId additionalProperties: false examples: - - &a44 + - &a45 packageName: MV000123.mxf status: ok rundownId: y9HauyWkcxQS3XaAOsW40BRLLsI_ @@ -1183,7 +1383,7 @@ channels: - event: packages rundownPlaylistId: y9HauyWkcxQS3XaAOsW40BRLLsI_ packages: - - *a44 + - *a45 buckets: description: Buckets schema for websocket subscriptions subscribe: @@ -1219,7 +1419,7 @@ channels: items: title: BucketAdLibStatus allOf: - - *a40 + - *a41 - type: object title: BucketAdLibStatus properties: @@ -1230,14 +1430,14 @@ channels: required: - externalId examples: - - &a45 + - &a46 externalId: C6K_yIMuGFUk8X_L9A9_jRT6aq4_ id: C6K_yIMuGFUk8X_L9A9_jRT6aq4_ name: Music video clip sourceLayer: Video Clip - actionType: *a37 - tags: *a38 - publicData: *a39 + actionType: *a38 + tags: *a39 + publicData: *a40 optionsSchema: '{"$schema":"https://json-schema.org/draft/2020-12/schema","title":"Play Video Clip","type":"object","properties":{"type":"adlib_action_video_clip","label":{"type":"string"},"clipId":{"type":"string"},"vo":{"type":"boolean"},"target":{"$schema":"https://json-schema.org/draft/2020-12/schema","title":"Object @@ -1261,22 +1461,22 @@ channels: - adLibs additionalProperties: false examples: - - &a46 + - &a47 id: C6K_yIMuGFUk8X_L9A9_jRT6aq4_ name: My Bucket adLibs: - - *a45 + - *a46 required: - event - buckets additionalProperties: false examples: - - &a47 + - &a48 event: buckets buckets: - - *a46 + - *a47 examples: - - payload: *a47 + - payload: *a48 notifications: description: Notifications topic for websocket subscriptions. subscribe: @@ -1340,7 +1540,7 @@ channels: type: string title: NotificationTargetType description: Possible NotificationTarget types - enum: &a48 + enum: &a49 - rundown - playlist - partInstance @@ -1352,7 +1552,7 @@ channels: type: string additionalProperties: false examples: - - &a49 + - &a50 type: rundown studioId: studio01 rundownId: rd123 @@ -1368,14 +1568,14 @@ channels: type: string title: NotificationTargetType description: Possible NotificationTarget types - enum: *a48 + enum: *a49 studioId: type: string playlistId: type: string additionalProperties: false examples: - - &a50 + - &a51 type: playlist studioId: studio01 playlistId: pl456 @@ -1392,7 +1592,7 @@ channels: type: string title: NotificationTargetType description: Possible NotificationTarget types - enum: *a48 + enum: *a49 studioId: type: string rundownId: @@ -1401,7 +1601,7 @@ channels: type: string additionalProperties: false examples: - - &a51 + - &a52 type: partInstance studioId: studio01 rundownId: rd123 @@ -1420,7 +1620,7 @@ channels: type: string title: NotificationTargetType description: Possible NotificationTarget types - enum: *a48 + enum: *a49 studioId: type: string rundownId: @@ -1431,7 +1631,7 @@ channels: type: string additionalProperties: false examples: - - &a52 + - &a53 type: pieceInstance studioId: studio01 rundownId: rd123 @@ -1447,17 +1647,17 @@ channels: type: string title: NotificationTargetType description: Possible NotificationTarget types - enum: *a48 + enum: *a49 additionalProperties: false examples: - - &a53 + - &a54 type: unknown examples: - - *a49 - *a50 - *a51 - *a52 - *a53 + - *a54 created: type: integer format: int64 @@ -1468,11 +1668,11 @@ channels: description: Unix timestamp of last modification additionalProperties: false examples: - - &a54 + - &a55 _id: notif123 severity: error message: disk.space.low - relatedTo: *a52 + relatedTo: *a53 created: 1694784932 modified: 1694784950 required: @@ -1480,9 +1680,9 @@ channels: - activeNotifications additionalProperties: false examples: - - &a55 + - &a56 event: notifications activeNotifications: - - *a54 + - *a55 examples: - - payload: *a55 + - payload: *a56 diff --git a/packages/live-status-gateway-api/src/generated/schema.ts b/packages/live-status-gateway-api/src/generated/schema.ts index dca47bd84f..64c31f4411 100644 --- a/packages/live-status-gateway-api/src/generated/schema.ts +++ b/packages/live-status-gateway-api/src/generated/schema.ts @@ -190,6 +190,10 @@ interface ActivePlaylistEvent { * Information about the current quickLoop, if any */ quickLoop?: ActivePlaylistQuickLoop + /** + * Status of the 3 T-timers in the playlist + */ + tTimers: TTimerStatus[] } interface CurrentPartStatus { @@ -419,6 +423,7 @@ enum ActivePlaylistTimingMode { NONE = 'none', FORWARD_MINUS_TIME = 'forward-time', BACK_MINUS_TIME = 'back-time', + DURATION = 'duration', } /** @@ -477,6 +482,122 @@ enum QuickLoopMarkerType { PART = 'part', } +/** + * Status of a single T-timer in the playlist + */ +interface TTimerStatus { + /** + * Timer index (1-3). The playlist always has 3 T-timer slots. + */ + index: TTimerIndex + /** + * User-defined label for the timer + */ + label: string + /** + * Whether the timer has been configured (mode and state are not null) + */ + configured: boolean + /** + * Timer configuration/mode defining the timer's behavior. Null if not configured. + */ + mode: TimerModeCountdown | TimerModeFreeRun | TimerModeTimeOfDay | null + /** + * Current runtime state of the timer. Null if not configured. + */ + state: TimerStateRunning | TimerStatePaused | null + /** + * Projected timing for when we expect to reach an anchor part. Used to calculate over/under diff. Has the same structure as state. Running means progressing towards anchor, paused means pushing/delaying anchor. + */ + projected?: TimerStateRunning | TimerStatePaused | null + /** + * The ID of the target Part that this timer is counting towards (the "timing anchor"). Optional - null if no anchor is set. + */ + anchorPartId?: string | null +} + +/** + * Timer index (1-3). The playlist always has 3 T-timer slots. + */ +enum TTimerIndex { + NUMBER_1 = 1, + NUMBER_2 = 2, + NUMBER_3 = 3, +} + +/** + * Countdown timer mode - counts down from a duration to zero + */ +interface TimerModeCountdown { + type: 'countdown' + /** + * The original countdown duration in milliseconds (used for reset) + */ + duration: number + /** + * Whether timer stops at zero or continues into negative values + */ + stopAtZero: boolean +} + +/** + * Free-running timer mode - counts up from start time + */ +interface TimerModeFreeRun { + type: 'freeRun' +} + +/** + * Time-of-day timer mode - counts down/up to a specific time + */ +interface TimerModeTimeOfDay { + type: 'timeOfDay' + /** + * The raw target string as provided when setting the timer (e.g. "14:30", "2023-12-31T23:59:59Z", or a timestamp number) + */ + targetRaw: string | number + /** + * Whether timer stops at zero or continues into negative values + */ + stopAtZero: boolean +} + +/** + * Timer state when running (progressing with real time) + */ +interface TimerStateRunning { + /** + * Whether the timer is paused + */ + paused: boolean + /** + * Unix timestamp (ms) when the timer reaches/reached zero. For countdown timers, this is when time runs out. For free-run timers, this is when the timer started. Client calculates current value relative to this timestamp. + */ + zeroTime: number + /** + * Optional timestamp when the timer should automatically pause (e.g., when current part ends and overrun begins). + */ + pauseTime?: number | null +} + +/** + * Timer state when paused (frozen at a specific duration) + */ +interface TimerStatePaused { + /** + * Whether the timer is paused + */ + paused: boolean + /** + * Frozen duration value in milliseconds. For countdown timers, this is remaining time. For free-run timers, this is elapsed time. + */ + duration: number + /** + * Optional timestamp when the timer should pause. Typically null when already paused. + */ + pauseTime?: number | null +} + interface ActivePiecesEvent { event: 'activePieces' /** @@ -948,6 +1069,13 @@ export { ActivePlaylistQuickLoop, QuickLoopMarker, QuickLoopMarkerType, + TTimerStatus, + TTimerIndex, + TimerModeCountdown, + TimerModeFreeRun, + TimerModeTimeOfDay, + TimerStateRunning, + TimerStatePaused, ActivePiecesEvent, SegmentsEvent, Segment, diff --git a/packages/live-status-gateway/sample-client/index.html b/packages/live-status-gateway/sample-client/index.html index bcf2f8a303..b1771636cc 100644 --- a/packages/live-status-gateway/sample-client/index.html +++ b/packages/live-status-gateway/sample-client/index.html @@ -8,6 +8,10 @@