Skip to content

Commit f4e61fa

Browse files
committed
fix: ensure ab sessions get reordered when adlibbing a fixed duration piece
1 parent 02be272 commit f4e61fa

File tree

3 files changed

+92
-51
lines changed

3 files changed

+92
-51
lines changed

packages/job-worker/src/playout/abPlayback/__tests__/abPlaybackResolver.spec.ts

Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,47 @@ describe('resolveAbAssignmentsFromRequests', () => {
394394
expectGotPlayer(res, 'c', undefined)
395395
expectGotPlayer(res, 'd', undefined)
396396
})
397+
test('Run timed adlib bts', () => {
398+
const requests: SessionRequest[] = [
399+
// current part
400+
{
401+
id: 'a',
402+
start: 1000,
403+
end: 10500,
404+
playerId: 2,
405+
},
406+
// adlib
407+
{
408+
id: 'b',
409+
start: 10000,
410+
end: 15000,
411+
},
412+
// lookaheads (in order of future use)
413+
{
414+
id: 'c',
415+
start: Number.POSITIVE_INFINITY,
416+
end: undefined,
417+
playerId: 1,
418+
lookaheadRank: 1,
419+
},
420+
{
421+
id: 'd',
422+
start: Number.POSITIVE_INFINITY,
423+
end: undefined,
424+
playerId: 2,
425+
lookaheadRank: 2,
426+
},
427+
]
428+
429+
const res = resolveAbAssignmentsFromRequests(resolverOptions, TWO_SLOTS, requests, 10000)
430+
expect(res).toBeTruthy()
431+
expect(res.failedOptional).toEqual([])
432+
expect(res.failedRequired).toEqual([])
433+
expectGotPlayer(res, 'a', 2)
434+
expectGotPlayer(res, 'b', 1)
435+
expectGotPlayer(res, 'c', 2) // moved so that it alternates
436+
expectGotPlayer(res, 'd', 1)
437+
})
397438

398439
test('Autonext run bts', () => {
399440
const requests: SessionRequest[] = [

packages/job-worker/src/playout/abPlayback/abPlaybackResolver.ts

Lines changed: 46 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -2,13 +2,15 @@ import { ABResolverOptions } from '@sofie-automation/blueprints-integration'
22
import { clone } from '@sofie-automation/corelib/dist/lib'
33
import * as _ from 'underscore'
44

5+
export type PlayerId = number | string
6+
57
export interface SessionRequest {
68
readonly id: string
79
readonly start: number
810
readonly end: number | undefined
911
readonly optional?: boolean
1012
readonly lookaheadRank?: number
11-
playerId?: number | string
13+
playerId?: PlayerId
1214
}
1315

1416
export interface AssignmentResult {
@@ -21,7 +23,7 @@ export interface AssignmentResult {
2123
}
2224

2325
interface SlotAvailability {
24-
id: number | string
26+
id: PlayerId
2527
before: (SessionRequest & { end: number }) | null
2628
after: SessionRequest | null
2729
clashes: SessionRequest[]
@@ -55,7 +57,7 @@ function safeMin<T>(arr: T[], func: (val: T) => number): T | undefined {
5557
*/
5658
export function resolveAbAssignmentsFromRequests(
5759
resolverOptions: ABResolverOptions,
58-
playerIds: Array<number | string>,
60+
playerIds: PlayerId[],
5961
rawRequests: SessionRequest[],
6062
now: number // Current time
6163
): AssignmentResult {
@@ -80,7 +82,7 @@ export function resolveAbAssignmentsFromRequests(
8082
return res
8183
}
8284

83-
const originalLookaheadAssignments: Record<string, number | string> = {}
85+
const originalLookaheadAssignments: Record<string, PlayerId> = {}
8486
for (const req of rawRequests) {
8587
if (req.lookaheadRank !== undefined && req.playerId !== undefined) {
8688
originalLookaheadAssignments[req.id] = req.playerId
@@ -104,7 +106,7 @@ export function resolveAbAssignmentsFromRequests(
104106
pendingRequests = grouped[undefined as any]
105107

106108
// build map of slots and what they already have assigned
107-
const slots: Map<number | string, SessionRequest[]> = new Map()
109+
const slots = new Map<PlayerId, SessionRequest[]>()
108110
_.each(playerIds, (id) => slots.set(id, grouped[id] || []))
109111

110112
const beforeHasGap = (p: SlotAvailability, req: SessionRequest): boolean =>
@@ -311,73 +313,67 @@ export function resolveAbAssignmentsFromRequests(
311313
}
312314
}
313315

316+
assignPlayersForLookahead(slots, res, originalLookaheadAssignments, safeNow)
317+
318+
return res
319+
}
320+
321+
function assignPlayersForLookahead(
322+
slots: Map<PlayerId, SessionRequest[]>,
323+
res: AssignmentResult,
324+
originalLookaheadAssignments: Record<string, PlayerId>,
325+
safeNow: number
326+
) {
314327
// Ensure lookahead gets assigned based on priority not some randomness
315328
// Includes slots which have either no sessions, or the last has a known end time
316-
const lastSessionPerSlot: Record<number | string, number | undefined> = {} // playerId, end
329+
const lastSessionPerSlot = new Map<PlayerId, number>() // playerId, end
317330
for (const [playerId, sessions] of slots) {
318331
const last = _.last(sessions.filter((s) => s.lookaheadRank === undefined))
319332
if (!last) {
320-
lastSessionPerSlot[playerId] = Number.NEGATIVE_INFINITY
333+
lastSessionPerSlot.set(playerId, Number.NEGATIVE_INFINITY)
321334
} else if (last.end !== undefined) {
322335
// If there is a defined end, then it can be useful after that point of time
323-
lastSessionPerSlot[playerId] = last.end
336+
lastSessionPerSlot.set(playerId, last.end)
324337
}
325338
}
326339

327340
// Filter down to the lookaheads that we should try to assign
328341
const lookaheadsToAssign = _.sortBy(
329342
res.requests.filter((r) => r.lookaheadRank !== undefined),
330343
(r) => r.lookaheadRank
331-
).slice(0, Object.keys(lastSessionPerSlot).length)
332-
333-
// Persist previous players if possible
334-
const remainingLookaheads: SessionRequest[] = []
335-
for (const req of lookaheadsToAssign) {
336-
delete req.playerId
337-
344+
).slice(0, lastSessionPerSlot.size)
345+
346+
const [playersClearNow, playersClearSoon] = _.partition(
347+
Array.from(lastSessionPerSlot.entries()),
348+
(session) => session[1] < safeNow
349+
)
350+
351+
// Assign the players which are clear right now
352+
const playersClearNowIds = new Set(playersClearNow.map((p) => p[0]))
353+
// First persist any previous players
354+
const lookaheadsToAssignNow = lookaheadsToAssign.slice(0, playersClearNow.length)
355+
for (const req of lookaheadsToAssignNow) {
338356
const prevPlayer = originalLookaheadAssignments[req.id]
339-
if (prevPlayer === undefined) {
340-
remainingLookaheads.push(req)
341-
} else {
342-
// Ensure the assignment is ok
343-
const slotEnd = lastSessionPerSlot[prevPlayer]
344-
if (slotEnd === undefined || slotEnd >= safeNow) {
345-
// It isnt available for this lookahead, or isnt visible yet
346-
remainingLookaheads.push(req)
347-
} else {
348-
// It is ours, so remove the player from the pool
349-
req.playerId = prevPlayer
350-
delete lastSessionPerSlot[req.playerId]
351-
}
352-
}
353-
}
354-
355-
// Assign any remaining lookaheads
356-
const sortedSlots = _.sortBy(Object.entries<number | undefined>(lastSessionPerSlot), (s) => s[1] ?? 0)
357-
for (let i = 0; i < remainingLookaheads.length; i++) {
358-
const slot = sortedSlots[i]
359-
const req = remainingLookaheads[i]
360-
361-
if (slot) {
362-
// Check if we were originally given a player index rather than a string Id
363-
if (playerIds.find((id) => typeof id === 'number' && id === Number(slot[0]))) {
364-
req.playerId = Number(slot[0])
365-
} else {
366-
req.playerId = slot[0]
367-
}
357+
if (prevPlayer !== undefined && playersClearNowIds.delete(prevPlayer)) {
358+
req.playerId = prevPlayer
368359
} else {
369360
delete req.playerId
370361
}
371362
}
363+
// Then fill in the blanks
364+
const playersClearNowRemainingIds = Array.from(playersClearNowIds)
365+
for (const req of lookaheadsToAssignNow) {
366+
if (req.playerId === undefined) req.playerId = playersClearNowRemainingIds.shift()
367+
}
372368

373-
return res
369+
// Assign the players which are clear soon. These aren't visible, so don't need to preserve anything
370+
const lookaheadsToAssignSoon = lookaheadsToAssign.slice(playersClearNow.length)
371+
for (const req of lookaheadsToAssignSoon) {
372+
req.playerId = playersClearSoon.shift()?.[0]
373+
}
374374
}
375375

376-
function getAvailability(
377-
id: number | string,
378-
thisReq: SessionRequest,
379-
orderedRequests: SessionRequest[]
380-
): SlotAvailability {
376+
function getAvailability(id: PlayerId, thisReq: SessionRequest, orderedRequests: SessionRequest[]): SlotAvailability {
381377
const res: SlotAvailability = {
382378
id,
383379
before: null,

packages/job-worker/src/playout/abPlayback/index.ts

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import { ResolvedPieceInstance } from '@sofie-automation/corelib/dist/dataModel/PieceInstance'
2-
import { ABSessionAssignments, DBRundownPlaylist } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist'
2+
import {
3+
ABSessionAssignment,
4+
ABSessionAssignments,
5+
DBRundownPlaylist,
6+
} from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist'
37
import { OnGenerateTimelineObjExt } from '@sofie-automation/corelib/dist/dataModel/Timeline'
48
import { endTrace, sendTrace, startTrace } from '@sofie-automation/corelib/dist/influxdb'
59
import { WrappedShowStyleBlueprint } from '../../blueprints/cache'

0 commit comments

Comments
 (0)