Skip to content

Commit

Permalink
Merge remote-tracking branch 'origin/fix/quickloop-timers' into bbc-r…
Browse files Browse the repository at this point in the history
…elease52
  • Loading branch information
PeterC89 committed Oct 8, 2024
2 parents cf26853 + c677704 commit 86fbbc4
Show file tree
Hide file tree
Showing 5 changed files with 137 additions and 8 deletions.
2 changes: 2 additions & 0 deletions packages/job-worker/src/playout/model/PlayoutModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -343,6 +343,8 @@ export interface PlayoutModel extends PlayoutModelReadonly, StudioPlayoutModelBa
*/
setQuickLoopMarker(type: 'start' | 'end', marker: QuickLoopMarker | null): void

getSegmentsBetweenQuickLoopMarker(start: QuickLoopMarker, end: QuickLoopMarker): SegmentId[]

calculatePartTimings(
fromPartInstance: PlayoutPartInstanceModel | null,
toPartInstance: PlayoutPartInstanceModel,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -594,7 +594,9 @@ export class PlayoutModelImpl extends PlayoutModelReadonlyImpl implements Playou

if (regenerateActivationId) this.playlistImpl.activationId = getRandomId()

if (this.playlistImpl.quickLoop?.running) this.playlistImpl.quickLoop.running = false
// reset quickloop:
this.setQuickLoopMarker('start', null)
this.setQuickLoopMarker('end', null)

this.#playlistHasChanged = true
}
Expand Down Expand Up @@ -793,6 +795,10 @@ export class PlayoutModelImpl extends PlayoutModelReadonlyImpl implements Playou
this.#playlistHasChanged = true
}

getSegmentsBetweenQuickLoopMarker(start: QuickLoopMarker, end: QuickLoopMarker): SegmentId[] {
return this.quickLoopService.getSegmentsBetweenMarkers(start, end)
}

/** Lifecycle */

/** @deprecated */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
} from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist'
import { ReadonlyObjectDeep } from 'type-fest/source/readonly-deep'
import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part'
import { RundownId } from '@sofie-automation/corelib/dist/dataModel/Ids'
import { RundownId, SegmentId } from '@sofie-automation/corelib/dist/dataModel/Ids'
import { DBSegment } from '@sofie-automation/corelib/dist/dataModel/Segment'
import { PlayoutPartInstanceModel } from '../PlayoutPartInstanceModel'
import { JobContext } from '../../../jobs'
Expand Down Expand Up @@ -149,6 +149,38 @@ export class QuickLoopService {
return quickLoopProps
}

getSegmentsBetweenMarkers(startMarker: QuickLoopMarker, endMarker: QuickLoopMarker): SegmentId[] {
const orderedParts = this.playoutModel.getAllOrderedParts()
const rundownIds = this.playoutModel.getRundownIds()

const start = this.findQuickLoopMarkerPosition(startMarker, 'start', orderedParts, rundownIds)
const end = this.findQuickLoopMarkerPosition(endMarker, 'end', orderedParts, rundownIds)

if (this.areMarkersFlipped(start, end)) return []

const segmentIds: Set<SegmentId> = new Set()

for (const part of orderedParts) {
const currentSegment = this.playoutModel.findSegment(part.segmentId)?.segment
const currentRundownIndex = rundownIds.findIndex((id) => id === part.rundownId)

if (!currentSegment) continue // ???

if (
currentRundownIndex >= start.rundownRank &&
currentRundownIndex <= end.rundownRank &&
currentSegment._rank >= start.segmentRank &&
currentSegment._rank <= end.segmentRank &&
part._rank >= start.partRank &&
part._rank <= start.partRank
) {
segmentIds.add(currentSegment._id)
}
}

return Array.from(segmentIds.values())
}

private areMarkersFlipped(startPosition: MarkerPosition, endPosition: MarkerPosition) {
return compareMarkerPositions(startPosition, endPosition) < 0
}
Expand Down
70 changes: 69 additions & 1 deletion packages/job-worker/src/playout/quickLoopMarkers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ import { runJobWithPlayoutModel } from './lock'
import { updateTimeline } from './timeline/generate'
import { selectNextPart } from './selectNextPart'
import { setNextPart } from './setNext'
import { resetPartInstancesWithPieceInstances } from './lib'
import { QuickLoopMarker, QuickLoopMarkerType } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist'
import { SegmentId } from '@sofie-automation/corelib/dist/dataModel/Ids'

export async function handleSetQuickLoopMarker(context: JobContext, data: SetQuickLoopMarkerProps): Promise<void> {
return runJobWithPlayoutModel(
Expand All @@ -17,9 +20,74 @@ export async function handleSetQuickLoopMarker(context: JobContext, data: SetQui
async (playoutModel) => {
const playlist = playoutModel.playlist
if (!playlist.activationId) throw new Error(`Playlist has no activationId!`)
const wasQuickLoopRunning = playoutModel.playlist.quickLoop?.running
const oldProps = playoutModel.playlist.quickLoop
const wasQuickLoopRunning = oldProps?.running
playoutModel.setQuickLoopMarker(data.type, data.marker)

const markerChanged = (
markerA: QuickLoopMarker | undefined,
markerB: QuickLoopMarker | undefined
): boolean => {
if (!markerA || !markerB) return false

if (
(markerA.type === QuickLoopMarkerType.RUNDOWN ||
markerA.type === QuickLoopMarkerType.SEGMENT ||
markerA.type === QuickLoopMarkerType.PART) &&
(markerB.type === QuickLoopMarkerType.RUNDOWN ||
markerB.type === QuickLoopMarkerType.SEGMENT ||
markerB.type === QuickLoopMarkerType.PART)
) {
return markerA.id !== markerB.id
}

return false
}

if (playlist.currentPartInfo) {
// rundown is on air
let segmentsToReset: SegmentId[] = []

if (
playlist.quickLoop?.start &&
oldProps?.start &&
markerChanged(oldProps.start, playlist.quickLoop.start)
) {
// start marker changed
segmentsToReset = playoutModel.getSegmentsBetweenQuickLoopMarker(
playlist.quickLoop.start,
oldProps.start
)
} else if (
playlist.quickLoop?.end &&
oldProps?.end &&
markerChanged(oldProps.end, playlist.quickLoop.end)
) {
// end marker changed
segmentsToReset = playoutModel.getSegmentsBetweenQuickLoopMarker(
oldProps.end,
playlist.quickLoop.end
)
} else if (playlist.quickLoop?.start && playlist.quickLoop.end && !(oldProps?.start && oldProps.end)) {
// a new loop was created
segmentsToReset = playoutModel.getSegmentsBetweenQuickLoopMarker(
playlist.quickLoop.start,
playlist.quickLoop.end
)
}

// reset segments that have been added to the loop and are not on-air
resetPartInstancesWithPieceInstances(context, playoutModel, {
segmentId: {
$in: segmentsToReset.filter(
(segmentId) =>
segmentId !== playoutModel.currentPartInstance?.partInstance.segmentId &&
segmentId !== playoutModel.nextPartInstance?.partInstance.segmentId
),
},
})
}

if (wasQuickLoopRunning) {
const nextPart = selectNextPart(
context,
Expand Down
31 changes: 26 additions & 5 deletions packages/webui/src/client/lib/rundownTiming.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,8 @@ export class RundownTimingCalculator {
let remainingRundownDuration = 0
let asPlayedRundownDuration = 0
let asDisplayedRundownDuration = 0
// the "wait" for a part is defined as its asPlayedDuration or its displayDuration or its expectedDuration
const waitPerPart: Record<string, number> = {}
let waitAccumulator = 0
let currentRemaining = 0
let startsAtAccumulator = 0
Expand Down Expand Up @@ -436,10 +438,13 @@ export class RundownTimingCalculator {
0
}
if (segmentUsesBudget) {
waitAccumulator += Math.min(waitDuration, Math.max(segmentBudgetDurationLeft, 0))
const wait = Math.min(waitDuration, Math.max(segmentBudgetDurationLeft, 0))
waitAccumulator += wait
segmentBudgetDurationLeft -= waitDuration
waitPerPart[unprotectString(partId)] = wait + Math.max(0, segmentBudgetDurationLeft)
} else {
waitAccumulator += waitDuration
waitPerPart[unprotectString(partId)] = waitDuration
}

// remaining is the sum of unplayed lines + whatever is left of the current segment
Expand Down Expand Up @@ -480,7 +485,9 @@ export class RundownTimingCalculator {
})

// This is where the waitAccumulator-generated data in the linearSegLines is used to calculate the countdowns.
// at this point the "waitAccumulator" should be the total sum of all the "waits" in the rundown
let localAccum = 0
let timeTillEndLoop: undefined | number = undefined
for (let i = 0; i < this.linearParts.length; i++) {
if (i < nextAIndex) {
// this is a line before next line
Expand Down Expand Up @@ -517,6 +524,11 @@ export class RundownTimingCalculator {
// and add the currentRemaining countdown, since we are currentRemaining + diff between next and
// this away from this line.
this.linearParts[i][1] = (this.linearParts[i][1] || 0) - localAccum + currentRemaining

if (!partsInQuickLoop[unprotectString(this.linearParts[i][0])]) {
timeTillEndLoop = timeTillEndLoop ?? this.linearParts[i][1] ?? undefined
}

if (nextRundownAnchor === undefined) {
nextRundownAnchor = getSegmentRundownAnchorFromPart(
this.linearParts[i][0],
Expand All @@ -527,13 +539,22 @@ export class RundownTimingCalculator {
}
}
}
// contiunation of linearParts calculations for looping playlists
// at this point the localAccumulator should be the sum of waits before the next line
// continuation of linearParts calculations for looping playlists
if (isLoopRunning(playlist)) {
// we track the sum of all the "waits" that happen in the loop
let waitInLoop = 0
// if timeTillEndLoop was undefined then we can assume the end of the loop is the last line in the rundown
timeTillEndLoop = timeTillEndLoop ?? waitAccumulator - localAccum + currentRemaining
for (let i = 0; i < nextAIndex; i++) {
if (!partsInQuickLoop[unprotectString(this.linearParts[i][0])]) continue
// offset the parts before the on air line by the countdown for the end of the rundown
this.linearParts[i][1] =
(this.linearParts[i][1] || 0) + waitAccumulator - localAccum + currentRemaining

// this countdown is the wait until the loop ends + whatever waits occur before this part but inside the loop
this.linearParts[i][1] = timeTillEndLoop + waitInLoop

// add the wait from this part to the waitInLoop (the lookup here should still work by the definition of a "wait")
waitInLoop += waitPerPart[unprotectString(this.linearParts[i][0])] ?? 0

if (nextRundownAnchor === undefined) {
nextRundownAnchor = getSegmentRundownAnchorFromPart(
this.linearParts[i][0],
Expand Down

0 comments on commit 86fbbc4

Please sign in to comment.