Skip to content

Commit 22f7aa6

Browse files
committed
SOFIE-252 | make timing of rehearsal end relative to the first TAKE
1 parent b4e79c4 commit 22f7aa6

4 files changed

Lines changed: 157 additions & 67 deletions

File tree

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

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,7 @@ export class RundownPlaylistClientUtil {
9696
currentPartInstance: PartInstance | undefined
9797
nextPartInstance: PartInstance | undefined
9898
previousPartInstance: PartInstance | undefined
99+
partInstanceToCountTimeFrom: PartInstance | undefined
99100
} {
100101
let unorderedRundownIds = rundownIds0
101102
if (!unorderedRundownIds) {
@@ -116,10 +117,26 @@ export class RundownPlaylistClientUtil {
116117
}).fetch()
117118
: []
118119

120+
const areAllPartsTimed = !!UIPartInstances.findOne({
121+
rundownId: { $in: unorderedRundownIds },
122+
['part.untimed']: { $ne: true },
123+
})
124+
125+
const partInstanceToCountTimeFrom = UIPartInstances.findOne(
126+
{
127+
rundownId: { $in: unorderedRundownIds },
128+
reset: { $ne: true },
129+
takeCount: { $exists: true },
130+
['part.untimed']: { $ne: areAllPartsTimed },
131+
},
132+
{ sort: { takeCount: 1 } }
133+
)
134+
119135
return {
120136
currentPartInstance: instances.find((inst) => inst._id === playlist.currentPartInfo?.partInstanceId),
121137
nextPartInstance: instances.find((inst) => inst._id === playlist.nextPartInfo?.partInstanceId),
122138
previousPartInstance: instances.find((inst) => inst._id === playlist.previousPartInfo?.partInstanceId),
139+
partInstanceToCountTimeFrom,
123140
}
124141
}
125142

packages/webui/src/client/ui/ClockView/ClockView.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import { RundownTimingProvider } from '../RundownView/RundownTiming/RundownTimin
55

66
import { StudioScreenSaver } from '../StudioScreenSaver/StudioScreenSaver.js'
77
import { PresenterScreen } from './PresenterScreen.js'
8-
import { DirectorScreen } from './DirectorScreen.js'
8+
import { DirectorScreen } from './DirectorScreen/DirectorScreen'
99
import { OverlayScreen } from './OverlayScreen.js'
1010
import { OverlayScreenSaver } from './OverlayScreenSaver.js'
1111
import { RundownPlaylists } from '../../collections/index.js'

packages/webui/src/client/ui/ClockView/DirectorScreen.tsx renamed to packages/webui/src/client/ui/ClockView/DirectorScreen/DirectorScreen.tsx

Lines changed: 53 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,25 +1,24 @@
11
import ClassNames from 'classnames'
22
import { DBSegment } from '@sofie-automation/corelib/dist/dataModel/Segment'
3-
import { PartUi } from '../SegmentTimeline/SegmentTimelineContainer.js'
3+
import { PartUi } from '../../SegmentTimeline/SegmentTimelineContainer.js'
44
import { DBRundownPlaylist, ABSessionAssignment } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist'
55
import { Rundown } from '@sofie-automation/corelib/dist/dataModel/Rundown'
6-
import { useTiming } from '../RundownView/RundownTiming/withTiming.js'
76
import {
87
useSubscription,
98
useSubscriptions,
109
useTracker,
1110
withTracker,
12-
} from '../../lib/ReactMeteorData/ReactMeteorData.js'
13-
import { getCurrentTime } from '../../lib/systemTime.js'
11+
} from '../../../lib/ReactMeteorData/ReactMeteorData.js'
12+
import { getCurrentTime } from '../../../lib/systemTime.js'
1413
import { PartInstance } from '@sofie-automation/meteor-lib/dist/collections/PartInstances'
1514
import { MeteorPubSub } from '@sofie-automation/meteor-lib/dist/api/pubsub'
16-
import { PieceIconContainer } from './ClockViewPieceIcons/ClockViewPieceIcon.js'
17-
import { PieceNameContainer } from './ClockViewPieceIcons/ClockViewPieceName.js'
18-
import { Timediff } from './Timediff.js'
19-
import { RundownUtils } from '../../lib/rundown.js'
15+
import { PieceIconContainer } from '../ClockViewPieceIcons/ClockViewPieceIcon.js'
16+
import { PieceNameContainer } from '../ClockViewPieceIcons/ClockViewPieceName.js'
17+
import { Timediff } from '../Timediff.js'
18+
import { RundownUtils } from '../../../lib/rundown.js'
2019
import { PieceLifespan, SourceLayerType } from '@sofie-automation/blueprints-integration'
2120
import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part'
22-
import { PieceFreezeContainer } from './ClockViewPieceIcons/ClockViewFreezeCount.js'
21+
import { PieceFreezeContainer } from '../ClockViewPieceIcons/ClockViewFreezeCount.js'
2322
import { PlaylistTiming } from '@sofie-automation/corelib/dist/playout/rundownTiming'
2423
import {
2524
RundownId,
@@ -30,28 +29,24 @@ import {
3029
} from '@sofie-automation/corelib/dist/dataModel/Ids'
3130
import { DBShowStyleVariant } from '@sofie-automation/corelib/dist/dataModel/ShowStyleVariant'
3231
import { calculatePartInstanceExpectedDurationWithTransition } from '@sofie-automation/corelib/dist/playout/timings'
33-
import { getPlaylistTimingDiff } from '../../lib/rundownTiming.js'
3432
import { UIShowStyleBase } from '@sofie-automation/meteor-lib/dist/api/showStyles'
35-
import { UIShowStyleBases, UIStudios } from '../Collections.js'
33+
import { UIShowStyleBases, UIStudios } from '../../Collections.js'
3634
import { UIStudio } from '@sofie-automation/meteor-lib/dist/api/studios'
37-
import { PieceInstances, RundownPlaylists, Rundowns, ShowStyleVariants } from '../../collections/index.js'
38-
import { RundownPlaylistCollectionUtil } from '../../collections/rundownPlaylistUtil.js'
35+
import { PieceInstances, RundownPlaylists, Rundowns, ShowStyleVariants } from '../../../collections/index.js'
36+
import { RundownPlaylistCollectionUtil } from '../../../collections/rundownPlaylistUtil.js'
3937
import { CorelibPubSub } from '@sofie-automation/corelib/dist/pubsub'
40-
import { useSetDocumentClass } from '../util/useSetDocumentClass.js'
41-
import { useRundownAndShowStyleIdsForPlaylist } from '../util/useRundownAndShowStyleIdsForPlaylist.js'
42-
import { RundownPlaylistClientUtil } from '../../lib/rundownPlaylistUtil.js'
43-
import { CurrentPartOrSegmentRemaining } from '../RundownView/RundownTiming/CurrentPartOrSegmentRemaining.js'
44-
import {
45-
OverUnderClockComponent,
46-
PlannedEndComponent,
47-
TimeSincePlannedEndComponent,
48-
TimeToPlannedEndComponent,
49-
} from '../../lib/Components/CounterComponents.js'
50-
import { AdjustLabelFit } from '../util/AdjustLabelFit.js'
51-
import { AutoNextStatus } from '../RundownView/RundownTiming/AutoNextStatus.js'
38+
import { useSetDocumentClass } from '../../util/useSetDocumentClass.js'
39+
import { useRundownAndShowStyleIdsForPlaylist } from '../../util/useRundownAndShowStyleIdsForPlaylist.js'
40+
import { RundownPlaylistClientUtil } from '../../../lib/rundownPlaylistUtil.js'
41+
import { CurrentPartOrSegmentRemaining } from '../../RundownView/RundownTiming/CurrentPartOrSegmentRemaining.js'
42+
43+
import { AdjustLabelFit } from '../../util/AdjustLabelFit.js'
44+
import { AutoNextStatus } from '../../RundownView/RundownTiming/AutoNextStatus.js'
5245
import { useTranslation } from 'react-i18next'
5346
import { DBShowStyleBase } from '@sofie-automation/corelib/dist/dataModel/ShowStyleBase'
5447
import { PieceInstance } from '@sofie-automation/corelib/dist/dataModel/PieceInstance.js'
48+
import { DirectorScreenTop } from './DirectorScreenTop.js'
49+
import { useTiming } from '../../RundownView/RundownTiming/withTiming.js'
5550

5651
interface SegmentUi extends DBSegment {
5752
items: Array<PartUi>
@@ -143,6 +138,7 @@ export interface DirectorScreenTrackedProps {
143138
nextShowStyleBaseId: ShowStyleBaseId | undefined
144139
showStyleBaseIds: ShowStyleBaseId[]
145140
rundownIds: RundownId[]
141+
partInstanceToCountTimeFrom: PartInstance | undefined
146142
}
147143

148144
function getShowStyleBaseIdSegmentPartUi(
@@ -248,6 +244,7 @@ const getDirectorScreenReactive = (props: DirectorScreenProps): DirectorScreenTr
248244
restoredFromSnapshotId: 0,
249245
},
250246
})
247+
251248
const segments: Array<SegmentUi> = []
252249
let showStyleBaseIds: ShowStyleBaseId[] = []
253250
let rundowns: Rundown[] = []
@@ -263,17 +260,24 @@ const getDirectorScreenReactive = (props: DirectorScreenProps): DirectorScreenTr
263260
let nextSegment: SegmentUi | undefined = undefined
264261
let nextPartInstanceUi: PartUi | undefined = undefined
265262
let nextShowStyleBaseId: ShowStyleBaseId | undefined = undefined
263+
let partInstanceToCountTimeFromUi: PartInstance | undefined = undefined
266264

267265
if (playlist) {
268266
rundowns = RundownPlaylistCollectionUtil.getRundownsOrdered(playlist)
267+
269268
const orderedSegmentsAndParts = RundownPlaylistClientUtil.getSegmentsAndPartsSync(playlist)
270269
rundownIds = rundowns.map((rundown) => rundown._id)
271270
const rundownsToShowstyles: Map<RundownId, ShowStyleBaseId> = new Map()
272271
for (const rundown of rundowns) {
273272
rundownsToShowstyles.set(rundown._id, rundown.showStyleBaseId)
274273
}
274+
275275
showStyleBaseIds = rundowns.map((rundown) => rundown.showStyleBaseId)
276-
const { currentPartInstance, nextPartInstance } = RundownPlaylistClientUtil.getSelectedPartInstances(playlist)
276+
const { currentPartInstance, nextPartInstance, partInstanceToCountTimeFrom } =
277+
RundownPlaylistClientUtil.getSelectedPartInstances(playlist)
278+
279+
partInstanceToCountTimeFromUi = partInstanceToCountTimeFrom
280+
277281
const partInstance = currentPartInstance ?? nextPartInstance
278282
if (partInstance) {
279283
// This is to register a reactive dependency on Rundown-spanning PieceInstances, that we may miss otherwise.
@@ -325,6 +329,7 @@ const getDirectorScreenReactive = (props: DirectorScreenProps): DirectorScreenTr
325329
}
326330
}
327331
}
332+
328333
return {
329334
studio,
330335
segments,
@@ -341,6 +346,7 @@ const getDirectorScreenReactive = (props: DirectorScreenProps): DirectorScreenTr
341346
nextSegment,
342347
nextPartInstance: nextPartInstanceUi,
343348
nextShowStyleBaseId,
349+
partInstanceToCountTimeFrom: partInstanceToCountTimeFromUi,
344350
}
345351
}
346352

@@ -372,7 +378,11 @@ function useDirectorScreenSubscriptions(props: DirectorScreenProps): void {
372378
useSubscription(CorelibPubSub.showStyleVariants, null, showStyleVariantIds)
373379
useSubscription(MeteorPubSub.rundownLayouts, showStyleBaseIds)
374380

375-
const { currentPartInstance, nextPartInstance } = useTracker(
381+
const {
382+
currentPartInstance,
383+
nextPartInstance,
384+
partInstanceToCountTimeFrom: firstTakenPartInstance,
385+
} = useTracker(
376386
() => {
377387
const playlist = RundownPlaylists.findOne(props.playlistId, {
378388
fields: {
@@ -386,16 +396,27 @@ function useDirectorScreenSubscriptions(props: DirectorScreenProps): void {
386396
if (playlist) {
387397
return RundownPlaylistClientUtil.getSelectedPartInstances(playlist)
388398
} else {
389-
return { currentPartInstance: undefined, nextPartInstance: undefined, previousPartInstance: undefined }
399+
return {
400+
currentPartInstance: undefined,
401+
nextPartInstance: undefined,
402+
previousPartInstance: undefined,
403+
partInstanceToCountTimeFrom: undefined,
404+
}
390405
}
391406
},
392407
[props.playlistId],
393-
{ currentPartInstance: undefined, nextPartInstance: undefined, previousPartInstance: undefined }
408+
{
409+
currentPartInstance: undefined,
410+
nextPartInstance: undefined,
411+
previousPartInstance: undefined,
412+
partInstanceToCountTimeFrom: undefined,
413+
}
394414
)
395415

396416
useSubscriptions(CorelibPubSub.pieceInstances, [
397417
currentPartInstance && [[currentPartInstance.rundownId], [currentPartInstance._id], {}],
398418
nextPartInstance && [[nextPartInstance.rundownId], [nextPartInstance._id], {}],
419+
firstTakenPartInstance && [[firstTakenPartInstance.rundownId], [firstTakenPartInstance._id], {}],
399420
])
400421
}
401422

@@ -417,11 +438,12 @@ function DirectorScreenRender({
417438
nextPartInstance,
418439
nextSegment,
419440
rundownIds,
441+
partInstanceToCountTimeFrom,
420442
}: Readonly<DirectorScreenProps & DirectorScreenTrackedProps>) {
421443
useSetDocumentClass('dark', 'xdark')
422444
const { t } = useTranslation()
423445

424-
const timingDurations = useTiming()
446+
useTiming()
425447

426448
// Compute current and next clip player ids (for pieces with AB sessions)
427449
const currentClipPlayer: string | undefined = useTracker(() => {
@@ -487,11 +509,6 @@ function DirectorScreenRender({
487509

488510
if (playlist && playlistId && segments) {
489511
const expectedStart = PlaylistTiming.getExpectedStart(playlist.timing) || 0
490-
const expectedEnd = PlaylistTiming.getExpectedEnd(playlist.timing)
491-
const expectedDuration = PlaylistTiming.getExpectedDuration(playlist.timing) || 0
492-
const now = timingDurations.currentTime ?? getCurrentTime()
493-
494-
const overUnderClock = getPlaylistTimingDiff(playlist, timingDurations) ?? 0
495512

496513
// Show countdown if it is the first segment and the current part is untimed:
497514
const currentSegmentIsFirst = currentSegment?._rank === 0
@@ -548,37 +565,7 @@ function DirectorScreenRender({
548565

549566
return (
550567
<div className="director-screen">
551-
<div className="director-screen__top">
552-
{expectedEnd ? (
553-
<div className="director-screen__top__planned-end">
554-
<div>
555-
<PlannedEndComponent value={expectedEnd} />
556-
</div>
557-
{t('Planned End')}
558-
</div>
559-
) : null}
560-
{expectedEnd ? (
561-
<div className="director-screen__top__time-to">
562-
<div>
563-
<TimeToPlannedEndComponent value={now - expectedEnd} />
564-
</div>
565-
<span className="director-screen__top__planned-to">{t('Time to planned end')}</span>
566-
</div>
567-
) : (
568-
<div>
569-
<div>
570-
<TimeSincePlannedEndComponent value={getCurrentTime() - (expectedStart + expectedDuration)} />
571-
<span className="director-screen__top__planned-since">{t('Time since planned end')}</span>
572-
</div>
573-
</div>
574-
)}
575-
<div>
576-
<div>
577-
<OverUnderClockComponent value={overUnderClock} />
578-
</div>
579-
<span className="director-screen__top__over-under">{t('Over/Under')}</span>
580-
</div>
581-
</div>
568+
<DirectorScreenTop partInstanceToCountTimeFrom={partInstanceToCountTimeFrom} playlist={playlist} />
582569
<div className="director-screen__body">
583570
{
584571
// Current Part:
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
import {
2+
OverUnderClockComponent,
3+
PlannedEndComponent,
4+
TimeSincePlannedEndComponent,
5+
TimeToPlannedEndComponent,
6+
} from '../../../lib/Components/CounterComponents'
7+
import { useTiming } from '../../RundownView/RundownTiming/withTiming'
8+
import { getPlaylistTimingDiff } from '../../../lib/rundownTiming'
9+
import { DBRundownPlaylist } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist'
10+
import { getCurrentTime } from '../../../lib/systemTime'
11+
import { useTranslation } from 'react-i18next'
12+
import { PlaylistTiming } from '@sofie-automation/corelib/dist/playout/rundownTiming'
13+
import { PartInstance } from '@sofie-automation/meteor-lib/dist/collections/PartInstances'
14+
15+
export interface DirectorScreenTopProps {
16+
playlist: DBRundownPlaylist
17+
partInstanceToCountTimeFrom: PartInstance | undefined
18+
}
19+
20+
export function DirectorScreenTop({
21+
playlist,
22+
partInstanceToCountTimeFrom,
23+
}: Readonly<DirectorScreenTopProps>): JSX.Element {
24+
const timingDurations = useTiming()
25+
26+
const rehearsalInProgress = Boolean(playlist.rehearsal && partInstanceToCountTimeFrom?.timings?.take)
27+
28+
const expectedStart = rehearsalInProgress
29+
? partInstanceToCountTimeFrom?.timings?.take || 0
30+
: PlaylistTiming.getExpectedStart(playlist.timing) || 0
31+
const expectedDuration = PlaylistTiming.getExpectedDuration(playlist.timing) || 0
32+
33+
const expectedEnd = rehearsalInProgress
34+
? (partInstanceToCountTimeFrom?.timings?.take || 0) + expectedDuration
35+
: PlaylistTiming.getExpectedEnd(playlist.timing)
36+
37+
const now = timingDurations.currentTime ?? getCurrentTime()
38+
39+
const overUnderClock = getPlaylistTimingDiff(playlist, timingDurations) ?? 0
40+
41+
const { t } = useTranslation()
42+
43+
return (
44+
<div className="director-screen__top">
45+
{expectedEnd ? (
46+
<div className="director-screen__top__planned-end">
47+
<div>
48+
<PlannedEndComponent value={expectedEnd} />
49+
</div>
50+
{t('Planned End')}
51+
</div>
52+
) : null}
53+
{expectedEnd && expectedEnd > now ? (
54+
<div className="director-screen__top__time-to director-screen__top__planned-container">
55+
<div>
56+
<TimeToPlannedEndComponent value={now - expectedEnd} />
57+
</div>
58+
<span className="director-screen__top__planned-to">
59+
{rehearsalInProgress ? t('Time to rehearsal end') : t('Time to planned end')}
60+
</span>
61+
</div>
62+
) : (
63+
<div>
64+
<div className="director-screen__top__planned-container">
65+
<TimeSincePlannedEndComponent
66+
value={
67+
rehearsalInProgress
68+
? (partInstanceToCountTimeFrom?.timings?.take || 0) + expectedDuration - now
69+
: getCurrentTime() - (expectedStart + expectedDuration)
70+
}
71+
/>
72+
<span className="director-screen__top__planned-since">
73+
{rehearsalInProgress ? t('Time since rehearsal end') : t('Time since planned end')}
74+
</span>
75+
</div>
76+
</div>
77+
)}
78+
<div>
79+
<div>
80+
<OverUnderClockComponent value={overUnderClock} />
81+
</div>
82+
<span className="director-screen__top__over-under">{t('Over/Under')}</span>
83+
</div>
84+
</div>
85+
)
86+
}

0 commit comments

Comments
 (0)