Skip to content

Commit 38fae25

Browse files
Merge pull request Sofie-Automation#1721 from bbc/feat/SOFIE-449-duration-based-timing-mode
Feat/sofie 449 duration based timing mode
2 parents 72e40f6 + 4c2ee62 commit 38fae25

8 files changed

Lines changed: 85 additions & 16 deletions

File tree

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

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ export enum PlaylistTimingType {
44
None = 'none',
55
ForwardTime = 'forward-time',
66
BackTime = 'back-time',
7+
Duration = 'duration',
78
}
89

910
export interface PlaylistTimingBase {
@@ -49,4 +50,29 @@ export interface PlaylistTimingBackTime extends PlaylistTimingBase {
4950
expectedEnd: Time
5051
}
5152

52-
export type RundownPlaylistTiming = PlaylistTimingNone | PlaylistTimingForwardTime | PlaylistTimingBackTime
53+
/**
54+
* This mode is intended for shows with a "floating start",
55+
* meaning they will start based on when the show before them on the channel ends.
56+
* In this mode, we will preserve the Duration and automatically calculate the expectedEnd
57+
* based on the _actual_ start of the show (playlist.startedPlayback).
58+
*
59+
* The optional expectedStart property allows setting a start property of the show that will not affect
60+
* timing calculations, only purpose is to drive UI and inform the users about the preliminary plan as
61+
* planned in the editorial planning tool.
62+
*/
63+
export interface PlaylistTimingDuration extends PlaylistTimingBase {
64+
type: PlaylistTimingType.Duration
65+
/** A stipulated start time, to drive UIs pre-show, but not affecting calculations during the show.
66+
*/
67+
expectedStart?: Time
68+
/** Planned duration of the rundown playlist
69+
* When the show starts, an expectedEnd gets automatically calculated with this as an offset from that starting point
70+
*/
71+
expectedDuration: number
72+
}
73+
74+
export type RundownPlaylistTiming =
75+
| PlaylistTimingNone
76+
| PlaylistTimingForwardTime
77+
| PlaylistTimingBackTime
78+
| PlaylistTimingDuration

packages/corelib/src/playout/rundownTiming.ts

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313

1414
import {
1515
PlaylistTimingBackTime,
16+
PlaylistTimingDuration,
1617
PlaylistTimingForwardTime,
1718
PlaylistTimingNone,
1819
PlaylistTimingType,
@@ -34,6 +35,10 @@ export namespace PlaylistTiming {
3435
return timing.type === PlaylistTimingType.BackTime
3536
}
3637

38+
export function isPlaylistDurationTimed(timing: RundownPlaylistTiming): timing is PlaylistTimingDuration {
39+
return timing.type === PlaylistTimingType.Duration
40+
}
41+
3742
export function getExpectedStart(timing: RundownPlaylistTiming): number | undefined {
3843
if (PlaylistTiming.isPlaylistTimingForwardTime(timing)) {
3944
return timing.expectedStart
@@ -42,19 +47,29 @@ export namespace PlaylistTiming {
4247
timing.expectedStart ||
4348
(timing.expectedDuration ? timing.expectedEnd - timing.expectedDuration : undefined)
4449
)
50+
} else if (PlaylistTiming.isPlaylistDurationTimed(timing)) {
51+
return timing.expectedStart
4552
} else {
4653
return undefined
4754
}
4855
}
4956

50-
export function getExpectedEnd(timing: RundownPlaylistTiming): number | undefined {
57+
export function getExpectedEnd(timing: RundownPlaylistTiming, startedPlayback?: number): number | undefined {
5158
if (PlaylistTiming.isPlaylistTimingBackTime(timing)) {
5259
return timing.expectedEnd
5360
} else if (PlaylistTiming.isPlaylistTimingForwardTime(timing)) {
5461
return (
5562
timing.expectedEnd ||
5663
(timing.expectedDuration ? timing.expectedStart + timing.expectedDuration : undefined)
5764
)
65+
} else if (PlaylistTiming.isPlaylistDurationTimed(timing)) {
66+
if (!startedPlayback) {
67+
// before the show, estimate the end from start + dur
68+
return timing.expectedStart ? timing.expectedStart + timing.expectedDuration : undefined
69+
} else {
70+
// after starting the show, project the end from startedPlayback + dur
71+
return startedPlayback + timing.expectedDuration
72+
}
5873
} else {
5974
return undefined
6075
}
@@ -70,6 +85,20 @@ export namespace PlaylistTiming {
7085
}
7186
}
7287

88+
export function getEstimatedEnd(
89+
timing: RundownPlaylistTiming,
90+
now: number,
91+
remainingPlaylistDuration?: number,
92+
startedPlayback?: number
93+
): number | undefined {
94+
if (remainingPlaylistDuration !== undefined) {
95+
const frontAnchor = startedPlayback ? now : Math.max(now, PlaylistTiming.getExpectedStart(timing) ?? now)
96+
97+
return frontAnchor + remainingPlaylistDuration
98+
}
99+
return undefined
100+
}
101+
73102
export function sortTimings(
74103
a: ReadonlyDeep<{ timing: RundownPlaylistTiming }>,
75104
b: ReadonlyDeep<{ timing: RundownPlaylistTiming }>

packages/live-status-gateway-api/api/components/timing/activePlaylistTiming/activePlaylistTimingMode.yaml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,4 @@ enum:
55
- none
66
- forward-time
77
- back-time
8+
- duration

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -660,6 +660,7 @@ channels:
660660
- none
661661
- forward-time
662662
- back-time
663+
- duration
663664
startedPlayback:
664665
description: Unix timestamp of when the playlist started (milliseconds)
665666
type: number

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -423,6 +423,7 @@ enum ActivePlaylistTimingMode {
423423
NONE = 'none',
424424
FORWARD_MINUS_TIME = 'forward-time',
425425
BACK_MINUS_TIME = 'back-time',
426+
DURATION = 'duration',
426427
}
427428

428429
/**

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,8 @@ export class ActivePlaylistTopic extends WebSocketTopicBase implements WebSocket
181181
? this._activePlaylist.timing.expectedStart
182182
: undefined,
183183
expectedEnd:
184-
this._activePlaylist.timing.type !== PlaylistTimingType.None
184+
this._activePlaylist.timing.type !== PlaylistTimingType.None &&
185+
this._activePlaylist.timing.type !== PlaylistTimingType.Duration
185186
? this._activePlaylist.timing.expectedEnd
186187
: undefined,
187188
},
@@ -402,6 +403,8 @@ function translatePlaylistTimingType(type: PlaylistTimingType): ActivePlaylistTi
402403
return ActivePlaylistTimingMode.BACK_MINUS_TIME
403404
case PlaylistTimingType.ForwardTime:
404405
return ActivePlaylistTimingMode.FORWARD_MINUS_TIME
406+
case PlaylistTimingType.Duration:
407+
return ActivePlaylistTimingMode.DURATION
405408
default:
406409
assertNever(type)
407410
// Cast and return the value anyway, so that the application works

packages/webui/src/client/lib/rundownTiming.ts

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -855,6 +855,13 @@ export function getPlaylistTimingDiff(
855855
frontAnchor = Math.max(currentTime, playlist.startedPlayback ?? Math.max(timing.expectedStart, currentTime))
856856
} else if (PlaylistTiming.isPlaylistTimingBackTime(timing)) {
857857
backAnchor = timingContext.nextRundownAnchor ?? timing.expectedEnd
858+
} else if (PlaylistTiming.isPlaylistDurationTimed(timing)) {
859+
const backAnchorTimeWithoutBreaks =
860+
timingContext.nextRundownAnchor ??
861+
PlaylistTiming.getExpectedEnd(timing, playlist.startedPlayback) ??
862+
currentTime + timing.expectedDuration
863+
backAnchor = timingContext.nextRundownAnchor ?? backAnchorTimeWithoutBreaks
864+
frontAnchor = Math.max(currentTime, playlist.startedPlayback || PlaylistTiming.getExpectedStart(timing) || 0)
858865
}
859866

860867
let diff = PlaylistTiming.isPlaylistTimingNone(timing)
@@ -880,9 +887,11 @@ export function getPlaylistTimingDiff(
880887
diff =
881888
(timingContext.asPlayedPlaylistDuration || 0) -
882889
(timing.expectedDuration ?? timingContext.totalPlaylistDuration ?? 0)
883-
} else if (PlaylistTiming.isPlaylistTimingBackTime(timing)) {
884-
// we want to see how late we've ended compared to the expectedEnd
885-
diff = startedPlayback + (timingContext.asPlayedPlaylistDuration || 0) - timing.expectedEnd
890+
} else if (PlaylistTiming.isPlaylistDurationTimed(timing)) {
891+
// we want to know how heavy/light we were compared to the original plan
892+
diff =
893+
(timingContext.asPlayedPlaylistDuration || 0) -
894+
(timing.expectedDuration ?? timingContext.totalPlaylistDuration ?? 0)
886895
}
887896
}
888897

packages/webui/src/client/ui/RundownView/RundownHeader/RundownHeaderExpectedEnd.tsx

Lines changed: 9 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -14,24 +14,23 @@ export function RundownHeaderExpectedEnd({
1414
const { t } = useTranslation()
1515
const timingDurations = useTiming()
1616

17-
const expectedStart = PlaylistTiming.getExpectedStart(playlist.timing)
18-
const expectedEnd = PlaylistTiming.getExpectedEnd(playlist.timing)
1917
const now = timingDurations.currentTime ?? Date.now()
18+
const expectedEnd = PlaylistTiming.getExpectedEnd(playlist.timing, playlist.startedPlayback)
19+
const estEnd = PlaylistTiming.getEstimatedEnd(
20+
playlist.timing,
21+
now,
22+
timingDurations.remainingPlaylistDuration,
23+
playlist.startedPlayback
24+
)
2025

21-
// Use remainingPlaylistDuration which includes current part's remaining time
22-
const estEnd =
23-
timingDurations.remainingPlaylistDuration !== undefined
24-
? Math.max(now, expectedStart ?? now) + timingDurations.remainingPlaylistDuration
25-
: null
26-
27-
if (expectedEnd === undefined && estEnd === null) return null
26+
if (expectedEnd === undefined && estEnd === undefined) return null
2827

2928
return (
3029
<div className="rundown-header__show-timers-endtimes">
3130
{!simplified && expectedEnd !== undefined ? (
3231
<Countdown label={t('Plan. End')} time={expectedEnd} className="rundown-header__show-timers-countdown" />
3332
) : null}
34-
{estEnd !== null ? (
33+
{estEnd !== undefined ? (
3534
<Countdown label={t('Est. End')} time={estEnd} className="rundown-header__show-timers-countdown" />
3635
) : null}
3736
</div>

0 commit comments

Comments
 (0)