Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 27 additions & 1 deletion packages/blueprints-integration/src/documents/playlistTiming.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ export enum PlaylistTimingType {
None = 'none',
ForwardTime = 'forward-time',
BackTime = 'back-time',
Duration = 'duration',
}

export interface PlaylistTimingBase {
Expand Down Expand Up @@ -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
31 changes: 30 additions & 1 deletion packages/corelib/src/playout/rundownTiming.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import {
PlaylistTimingBackTime,
PlaylistTimingDuration,
PlaylistTimingForwardTime,
PlaylistTimingNone,
PlaylistTimingType,
Expand All @@ -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
Expand All @@ -42,19 +47,29 @@ 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)) {
return (
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
}
Expand All @@ -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 }>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Original file line number Diff line number Diff line change
@@ -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]
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
@@ -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'
Original file line number Diff line number Diff line change
@@ -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'
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,4 @@ enum:
- none
- forward-time
- back-time
- duration
Loading
Loading