Skip to content

Commit 511dcab

Browse files
committed
fix: only extend quickloop for next-after-current inserts
1 parent fa42503 commit 511dcab

2 files changed

Lines changed: 246 additions & 2 deletions

File tree

‎packages/job-worker/src/playout/__tests__/adlibUtils.test.ts‎

Lines changed: 207 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,13 @@ import { setupDefaultRundown, setupMockShowStyleCompound } from '../../__mocks__
55
import { defaultRundownPlaylist } from '../../__mocks__/defaultCollectionObjects.js'
66
import { getRandomId } from '@sofie-automation/corelib/dist/lib'
77
import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part'
8-
import { resolveQueuedAdlibInsertTarget } from '../adlibUtils.js'
8+
import { insertQueuedPartWithPieces, resolveQueuedAdlibInsertTarget } from '../adlibUtils.js'
99
import { runJobWithPlayoutModel } from '../lock.js'
1010
import { SegmentOrphanedReason } from '@sofie-automation/corelib/dist/dataModel/Segment'
11+
import { QuickLoopMarkerType } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist/RundownPlaylist'
12+
import { handleActivateRundownPlaylist } from '../activePlaylistJobs.js'
13+
import { handleTakeNextPart } from '../take.js'
14+
import { QueuePartTarget } from '@sofie-automation/blueprints-integration'
1115

1216
describe('adlibUtils', () => {
1317
async function setupActivatedPlaylist(rundownId: RundownId, playlistId: RundownPlaylistId) {
@@ -321,4 +325,206 @@ describe('adlibUtils', () => {
321325
).toThrow('Cannot queue part: target is in orphaned segment')
322326
})
323327
})
328+
329+
describe('insertQueuedPartWithPieces quick loop', () => {
330+
async function setupPlayingPlaylist() {
331+
const context = setupDefaultJobEnvironment()
332+
const playlistId: RundownPlaylistId = protectString('playlist0')
333+
const rundownId: RundownId = getRandomId()
334+
335+
await context.mockCollections.RundownPlaylists.insertOne({
336+
...defaultRundownPlaylist(playlistId, context.studioId),
337+
})
338+
339+
const showStyleCompound = await setupMockShowStyleCompound(context)
340+
await setupDefaultRundown(context, showStyleCompound, playlistId, rundownId)
341+
342+
await handleActivateRundownPlaylist(context, { playlistId, rehearsal: true })
343+
await handleTakeNextPart(context, { playlistId, fromPartInstanceId: null })
344+
345+
return { context, playlistId, rundownId }
346+
}
347+
348+
function queuedPart(target?: QueuePartTarget) {
349+
return {
350+
part: {
351+
_id: getRandomId(),
352+
externalId: 'queued_adlib',
353+
title: 'Queued adlib',
354+
expectedDurationWithTransition: undefined,
355+
},
356+
pieces: [],
357+
target,
358+
}
359+
}
360+
361+
test('extends quick loop when inserted immediately after current', async () => {
362+
const { context, playlistId } = await setupPlayingPlaylist()
363+
364+
await runJobWithPlayoutModel(context, { playlistId }, null, async (playoutModel) => {
365+
const currentPartInstance = playoutModel.currentPartInstance
366+
expect(currentPartInstance).toBeTruthy()
367+
if (!currentPartInstance) throw new Error('currentPartInstance not found')
368+
369+
playoutModel.setQuickLoopMarker('start', {
370+
type: QuickLoopMarkerType.PART,
371+
id: currentPartInstance.partInstance.part._id,
372+
})
373+
playoutModel.setQuickLoopMarker('end', {
374+
type: QuickLoopMarkerType.PART,
375+
id: currentPartInstance.partInstance.part._id,
376+
})
377+
378+
const setQuickLoopMarker = jest.spyOn(playoutModel, 'setQuickLoopMarker')
379+
380+
const newPartInstance = await insertQueuedPartWithPieces(
381+
context,
382+
playoutModel,
383+
currentPartInstance,
384+
queuedPart(),
385+
undefined
386+
)
387+
388+
expect(newPartInstance.partInstance.segmentId).toEqual(currentPartInstance.partInstance.segmentId)
389+
expect(newPartInstance.partInstance.part._rank).toBeGreaterThan(
390+
currentPartInstance.partInstance.part._rank
391+
)
392+
expect(playoutModel.playlist.nextPartInfo?.partInstanceId).toEqual(newPartInstance.partInstance._id)
393+
expect(setQuickLoopMarker).toHaveBeenCalledWith(
394+
'end',
395+
expect.objectContaining({
396+
type: QuickLoopMarkerType.PART,
397+
id: newPartInstance.partInstance.part._id,
398+
overridenId: currentPartInstance.partInstance.part._id,
399+
})
400+
)
401+
})
402+
})
403+
404+
test('extends quick loop when explicit target is the next part in the same segment', async () => {
405+
const { context, playlistId } = await setupPlayingPlaylist()
406+
407+
await runJobWithPlayoutModel(context, { playlistId }, null, async (playoutModel) => {
408+
const currentPartInstance = playoutModel.currentPartInstance
409+
expect(currentPartInstance).toBeTruthy()
410+
if (!currentPartInstance) throw new Error('currentPartInstance not found')
411+
412+
const nextPartInSegment = playoutModel
413+
.getAllOrderedParts()
414+
.find(
415+
(part) =>
416+
part.segmentId === currentPartInstance.partInstance.segmentId &&
417+
part._rank > currentPartInstance.partInstance.part._rank
418+
)
419+
expect(nextPartInSegment).toBeTruthy()
420+
if (!nextPartInSegment) throw new Error('nextPartInSegment not found')
421+
422+
playoutModel.setQuickLoopMarker('start', {
423+
type: QuickLoopMarkerType.PART,
424+
id: currentPartInstance.partInstance.part._id,
425+
})
426+
playoutModel.setQuickLoopMarker('end', {
427+
type: QuickLoopMarkerType.PART,
428+
id: currentPartInstance.partInstance.part._id,
429+
})
430+
431+
const setQuickLoopMarker = jest.spyOn(playoutModel, 'setQuickLoopMarker')
432+
433+
const newPartInstance = await insertQueuedPartWithPieces(
434+
context,
435+
playoutModel,
436+
currentPartInstance,
437+
queuedPart({ targetPartId: unprotectString(nextPartInSegment._id) }),
438+
undefined
439+
)
440+
441+
expect(newPartInstance.partInstance.segmentId).toEqual(currentPartInstance.partInstance.segmentId)
442+
expect(playoutModel.playlist.nextPartInfo?.partInstanceId).toEqual(newPartInstance.partInstance._id)
443+
expect(setQuickLoopMarker).toHaveBeenCalledWith(
444+
'end',
445+
expect.objectContaining({
446+
type: QuickLoopMarkerType.PART,
447+
id: newPartInstance.partInstance.part._id,
448+
})
449+
)
450+
})
451+
})
452+
453+
test('skips extending quick loop for explicit target in another segment', async () => {
454+
const { context, playlistId, rundownId } = await setupPlayingPlaylist()
455+
456+
const targetPart = await context.mockCollections.Parts.findOne({
457+
externalId: 'MOCK_PART_1_1',
458+
rundownId,
459+
})
460+
expect(targetPart).toBeTruthy()
461+
if (!targetPart) throw new Error('targetPart not found')
462+
463+
await runJobWithPlayoutModel(context, { playlistId }, null, async (playoutModel) => {
464+
const currentPartInstance = playoutModel.currentPartInstance
465+
expect(currentPartInstance).toBeTruthy()
466+
if (!currentPartInstance) throw new Error('currentPartInstance not found')
467+
468+
playoutModel.setQuickLoopMarker('start', {
469+
type: QuickLoopMarkerType.PART,
470+
id: currentPartInstance.partInstance.part._id,
471+
})
472+
playoutModel.setQuickLoopMarker('end', {
473+
type: QuickLoopMarkerType.PART,
474+
id: currentPartInstance.partInstance.part._id,
475+
})
476+
477+
const setQuickLoopMarker = jest.spyOn(playoutModel, 'setQuickLoopMarker')
478+
479+
const newPartInstance = await insertQueuedPartWithPieces(
480+
context,
481+
playoutModel,
482+
currentPartInstance,
483+
queuedPart({ targetPartId: unprotectString(targetPart._id) }),
484+
undefined
485+
)
486+
487+
expect(newPartInstance.partInstance.segmentId).toEqual(targetPart.segmentId)
488+
expect(newPartInstance.partInstance.part._rank).toBeLessThan(targetPart._rank)
489+
expect(playoutModel.playlist.nextPartInfo?.partInstanceId).toEqual(newPartInstance.partInstance._id)
490+
expect(setQuickLoopMarker).not.toHaveBeenCalled()
491+
})
492+
})
493+
494+
test('skips extending quick loop for explicit target before current', async () => {
495+
const { context, playlistId } = await setupPlayingPlaylist()
496+
497+
await runJobWithPlayoutModel(context, { playlistId }, null, async (playoutModel) => {
498+
const currentPartInstance = playoutModel.currentPartInstance
499+
expect(currentPartInstance).toBeTruthy()
500+
if (!currentPartInstance) throw new Error('currentPartInstance not found')
501+
502+
playoutModel.setQuickLoopMarker('start', {
503+
type: QuickLoopMarkerType.PART,
504+
id: currentPartInstance.partInstance.part._id,
505+
})
506+
playoutModel.setQuickLoopMarker('end', {
507+
type: QuickLoopMarkerType.PART,
508+
id: currentPartInstance.partInstance.part._id,
509+
})
510+
511+
const setQuickLoopMarker = jest.spyOn(playoutModel, 'setQuickLoopMarker')
512+
513+
const newPartInstance = await insertQueuedPartWithPieces(
514+
context,
515+
playoutModel,
516+
currentPartInstance,
517+
queuedPart({ targetPartId: unprotectString(currentPartInstance.partInstance.part._id) }),
518+
undefined
519+
)
520+
521+
expect(newPartInstance.partInstance.segmentId).toEqual(currentPartInstance.partInstance.segmentId)
522+
expect(newPartInstance.partInstance.part._rank).toBeLessThan(
523+
currentPartInstance.partInstance.part._rank
524+
)
525+
expect(playoutModel.playlist.nextPartInfo?.partInstanceId).toEqual(newPartInstance.partInstance._id)
526+
expect(setQuickLoopMarker).not.toHaveBeenCalled()
527+
})
528+
})
529+
})
324530
})

‎packages/job-worker/src/playout/adlibUtils.ts‎

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ import { PlayoutSegmentModel } from './model/PlayoutSegmentModel.js'
3535
import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part'
3636
import { protectString } from '@sofie-automation/corelib/dist/protectedString'
3737
import { QuickLoopMarkerType } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist/RundownPlaylist'
38+
import { compareMarkerPositions, MarkerPosition } from '@sofie-automation/corelib/dist/playout/playlist'
3839
import { QueueablePartAndPieces } from '../blueprints/context/services/PartAndPieceInstanceActionService.js'
3940

4041
export type QueuedAdlibInsertTarget = {
@@ -360,13 +361,50 @@ export async function insertQueuedPartWithPieces(
360361

361362
await setNextPart(context, playoutModel, newPartInstance, false)
362363

363-
temporarilyExtendQuickLoop(playoutModel, currentPartInstance, newPartInstance)
364+
if (queuedPartFollowsCurrentInQuickLoopOrder(playoutModel, currentPartInstance, newPartInstance)) {
365+
temporarilyExtendQuickLoop(playoutModel, currentPartInstance, newPartInstance)
366+
}
364367

365368
if (span) span.end()
366369

367370
return newPartInstance
368371
}
369372

373+
function getPartQuickLoopPosition(playoutModel: PlayoutModel, part: ReadonlyDeep<DBPart>): MarkerPosition {
374+
const rundownIds = playoutModel.getRundownIds()
375+
const segment = playoutModel.findSegment(part.segmentId)?.segment
376+
377+
return {
378+
partRank: part._rank,
379+
segmentRank: segment?._rank ?? 0,
380+
rundownRank: rundownIds.indexOf(part.rundownId),
381+
}
382+
}
383+
384+
function queuedPartFollowsCurrentInQuickLoopOrder(
385+
playoutModel: PlayoutModel,
386+
currentPartInstance: PlayoutPartInstanceModel,
387+
newPartInstance: PlayoutPartInstanceModel
388+
): boolean {
389+
const currentPart = currentPartInstance.partInstance.part
390+
const newPart = newPartInstance.partInstance.part
391+
392+
if (currentPart.segmentId !== newPart.segmentId) {
393+
return false
394+
}
395+
396+
const currentPosition = getPartQuickLoopPosition(playoutModel, currentPart)
397+
const newPosition = getPartQuickLoopPosition(playoutModel, newPart)
398+
if (compareMarkerPositions(currentPosition, newPosition) <= 0) {
399+
return false
400+
}
401+
402+
const partsInSegment = getAllPartsInSegment(playoutModel, currentPart.segmentId)
403+
return !partsInSegment.some(
404+
(part) => part._id !== newPart._id && part._rank > currentPart._rank && part._rank < newPart._rank
405+
)
406+
}
407+
370408
function temporarilyExtendQuickLoop(
371409
playoutModel: PlayoutModel,
372410
currentPartInstance: PlayoutPartInstanceModel,

0 commit comments

Comments
 (0)