Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: ab playback resolver not allowing sessions to span between segments SUPERFLY-5 #1

Open
wants to merge 10 commits into
base: upstream-release51
Choose a base branch
from
2 changes: 2 additions & 0 deletions packages/corelib/src/dataModel/RundownPlaylist.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ export interface ABSessionInfo {
id: string
/** The name of the session from the blueprints */
name: string
/** Whether the name is treated as globally unique */
isUniqueName: boolean
/** Set if the session is being by lookahead for a future part */
lookaheadForPartId?: PartId
/** Set if the session is being used by an infinite PieceInstance */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,13 @@ export class OnTimelineGenerateContext extends RundownContext implements ITimeli
const partInstanceId = pieceInstance?.partInstanceId
if (!partInstanceId) throw new Error('Missing partInstanceId in call to getPieceABSessionId')

return this.abSessionsHelper.getPieceABSessionId(pieceInstance, sessionName)
return this.abSessionsHelper.getPieceABSessionIdFromSessionName(pieceInstance, sessionName)
}

/**
* @deprecated Use core provided AB resolving
*/
getTimelineObjectAbSessionId(tlObj: OnGenerateTimelineObjExt, sessionName: string): string | undefined {
return this.abSessionsHelper.getTimelineObjectAbSessionId(tlObj, sessionName)
return this.abSessionsHelper.getTimelineObjectAbSessionIdFromSessionName(tlObj, sessionName)
}
}
186 changes: 147 additions & 39 deletions packages/job-worker/src/playout/abPlayback/__tests__/abPlayback.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { ABResolverOptions, IBlueprintPieceType, PieceLifespan } from '@sofie-automation/blueprints-integration'
import {
ABResolverOptions,
IBlueprintPieceType,
PieceAbSessionInfo,
PieceLifespan,
} from '@sofie-automation/blueprints-integration'
import { EmptyPieceTimelineObjectsBlob } from '@sofie-automation/corelib/dist/dataModel/Piece'
import { PieceInstancePiece, ResolvedPieceInstance } from '@sofie-automation/corelib/dist/dataModel/PieceInstance'
import { ABSessionAssignments } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist'
Expand All @@ -21,7 +26,8 @@ function createBasicResolvedPieceInstance(
start: number,
duration: number | undefined,
reqId: string | undefined,
optional?: boolean
optional?: boolean,
uniqueSessionName?: boolean
): ResolvedPieceInstance {
const piece = literal<PieceInstancePiece>({
_id: protectString(id),
Expand All @@ -47,6 +53,7 @@ function createBasicResolvedPieceInstance(
sessionName: reqId,
poolName: POOL_NAME,
optional: optional,
sessionNameIsGloballyUnique: uniqueSessionName,
},
]
}
Expand Down Expand Up @@ -120,7 +127,9 @@ describe('resolveMediaPlayers', () => {
createBasicResolvedPieceInstance('2', 800, 4000, 'ghi'),
]

mockGetPieceSessionId.mockImplementation((piece, name) => `${piece._id}_${name}`)
mockGetPieceSessionId.mockImplementation(
(piece, session) => `${piece._id}_${session.poolName}_${session.sessionName}`
)

const assignments = resolveAbSessions(
abSessionHelper,
Expand All @@ -143,9 +152,18 @@ describe('resolveMediaPlayers', () => {

expect(mockGetPieceSessionId).toHaveBeenCalledTimes(3)
expect(mockGetObjectSessionId).toHaveBeenCalledTimes(0)
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(1, pieces[0].instance, 'clip_abc')
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(2, pieces[1].instance, 'clip_def')
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(3, pieces[2].instance, 'clip_ghi')
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(1, pieces[0].instance, {
poolName: 'clip',
sessionName: 'abc',
} satisfies PieceAbSessionInfo)
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(2, pieces[1].instance, {
poolName: 'clip',
sessionName: 'def',
} satisfies PieceAbSessionInfo)
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(3, pieces[2].instance, {
poolName: 'clip',
sessionName: 'ghi',
} satisfies PieceAbSessionInfo)
})

test('basic pieces - players with string Ids', () => {
Expand All @@ -156,7 +174,9 @@ describe('resolveMediaPlayers', () => {
createBasicResolvedPieceInstance('2', 800, 4000, 'ghi'),
]

mockGetPieceSessionId.mockImplementation((piece, name) => `${piece._id}_${name}`)
mockGetPieceSessionId.mockImplementation(
(piece, session) => `${piece._id}_${session.poolName}_${session.sessionName}`
)

const assignments = resolveAbSessions(
abSessionHelper,
Expand All @@ -179,9 +199,18 @@ describe('resolveMediaPlayers', () => {

expect(mockGetPieceSessionId).toHaveBeenCalledTimes(3)
expect(mockGetObjectSessionId).toHaveBeenCalledTimes(0)
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(1, pieces[0].instance, 'clip_abc')
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(2, pieces[1].instance, 'clip_def')
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(3, pieces[2].instance, 'clip_ghi')
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(1, pieces[0].instance, {
poolName: 'clip',
sessionName: 'abc',
} satisfies PieceAbSessionInfo)
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(2, pieces[1].instance, {
poolName: 'clip',
sessionName: 'def',
} satisfies PieceAbSessionInfo)
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(3, pieces[2].instance, {
poolName: 'clip',
sessionName: 'ghi',
} satisfies PieceAbSessionInfo)
})

test('basic pieces - players with number and string Ids', () => {
Expand All @@ -192,7 +221,9 @@ describe('resolveMediaPlayers', () => {
createBasicResolvedPieceInstance('2', 800, 4000, 'ghi'),
]

mockGetPieceSessionId.mockImplementation((piece, name) => `${piece._id}_${name}`)
mockGetPieceSessionId.mockImplementation(
(piece, session) => `${piece._id}_${session.poolName}_${session.sessionName}`
)

const assignments = resolveAbSessions(
abSessionHelper,
Expand All @@ -215,9 +246,18 @@ describe('resolveMediaPlayers', () => {

expect(mockGetPieceSessionId).toHaveBeenCalledTimes(3)
expect(mockGetObjectSessionId).toHaveBeenCalledTimes(0)
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(1, pieces[0].instance, 'clip_abc')
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(2, pieces[1].instance, 'clip_def')
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(3, pieces[2].instance, 'clip_ghi')
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(1, pieces[0].instance, {
poolName: 'clip',
sessionName: 'abc',
} satisfies PieceAbSessionInfo)
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(2, pieces[1].instance, {
poolName: 'clip',
sessionName: 'def',
} satisfies PieceAbSessionInfo)
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(3, pieces[2].instance, {
poolName: 'clip',
sessionName: 'ghi',
} satisfies PieceAbSessionInfo)
})

test('Multiple pieces same id', () => {
Expand All @@ -229,7 +269,7 @@ describe('resolveMediaPlayers', () => {
createBasicResolvedPieceInstance('3', 6400, 1000, 'abc'), // Gap before
]

mockGetPieceSessionId.mockImplementation((_piece, name) => `tmp_${name}`)
mockGetPieceSessionId.mockImplementation((_piece, session) => `tmp_${session.poolName}_${session.sessionName}`)

const assignments = resolveAbSessions(
abSessionHelper,
Expand All @@ -250,10 +290,22 @@ describe('resolveMediaPlayers', () => {

expect(mockGetPieceSessionId).toHaveBeenCalledTimes(4)
expect(mockGetObjectSessionId).toHaveBeenCalledTimes(0)
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(1, pieces[0].instance, 'clip_abc')
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(2, pieces[1].instance, 'clip_abc')
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(3, pieces[2].instance, 'clip_abc')
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(4, pieces[3].instance, 'clip_abc')
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(1, pieces[0].instance, {
poolName: 'clip',
sessionName: 'abc',
} satisfies PieceAbSessionInfo)
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(2, pieces[1].instance, {
poolName: 'clip',
sessionName: 'abc',
} satisfies PieceAbSessionInfo)
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(3, pieces[2].instance, {
poolName: 'clip',
sessionName: 'abc',
} satisfies PieceAbSessionInfo)
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(4, pieces[3].instance, {
poolName: 'clip',
sessionName: 'abc',
} satisfies PieceAbSessionInfo)
})

test('Reuse after gap', () => {
Expand All @@ -264,7 +316,9 @@ describe('resolveMediaPlayers', () => {
createBasicResolvedPieceInstance('3', 6400, 1000, 'ghi'), // Wait, then reuse first
]

mockGetPieceSessionId.mockImplementation((piece, name) => `${piece._id}_${name}`)
mockGetPieceSessionId.mockImplementation(
(piece, session) => `${piece._id}_${session.poolName}_${session.sessionName}`
)

const assignments = resolveAbSessions(
abSessionHelper,
Expand All @@ -287,9 +341,18 @@ describe('resolveMediaPlayers', () => {

expect(mockGetPieceSessionId).toHaveBeenCalledTimes(3)
expect(mockGetObjectSessionId).toHaveBeenCalledTimes(0)
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(1, pieces[0].instance, 'clip_abc')
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(2, pieces[1].instance, 'clip_def')
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(3, pieces[2].instance, 'clip_ghi')
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(1, pieces[0].instance, {
poolName: 'clip',
sessionName: 'abc',
} satisfies PieceAbSessionInfo)
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(2, pieces[1].instance, {
poolName: 'clip',
sessionName: 'def',
} satisfies PieceAbSessionInfo)
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(3, pieces[2].instance, {
poolName: 'clip',
sessionName: 'ghi',
} satisfies PieceAbSessionInfo)
})

test('Reuse immediately', () => {
Expand All @@ -300,7 +363,9 @@ describe('resolveMediaPlayers', () => {
createBasicResolvedPieceInstance('3', 5400, 1000, 'ghi'), // Wait, then reuse first
]

mockGetPieceSessionId.mockImplementation((piece, name) => `${piece._id}_${name}`)
mockGetPieceSessionId.mockImplementation(
(piece, session) => `${piece._id}_${session.poolName}_${session.sessionName}`
)

const assignments = resolveAbSessions(
abSessionHelper,
Expand All @@ -323,9 +388,18 @@ describe('resolveMediaPlayers', () => {

expect(mockGetPieceSessionId).toHaveBeenCalledTimes(3)
expect(mockGetObjectSessionId).toHaveBeenCalledTimes(0)
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(1, pieces[0].instance, 'clip_abc')
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(2, pieces[1].instance, 'clip_def')
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(3, pieces[2].instance, 'clip_ghi')
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(1, pieces[0].instance, {
poolName: 'clip',
sessionName: 'abc',
} satisfies PieceAbSessionInfo)
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(2, pieces[1].instance, {
poolName: 'clip',
sessionName: 'def',
} satisfies PieceAbSessionInfo)
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(3, pieces[2].instance, {
poolName: 'clip',
sessionName: 'ghi',
} satisfies PieceAbSessionInfo)
})

test('Reuse immediately dense', () => {
Expand All @@ -336,7 +410,9 @@ describe('resolveMediaPlayers', () => {
createBasicResolvedPieceInstance('3', 5400, 1000, 'ghi'), // Wait, then reuse first
]

mockGetPieceSessionId.mockImplementation((piece, name) => `${piece._id}_${name}`)
mockGetPieceSessionId.mockImplementation(
(piece, session) => `${piece._id}_${session.poolName}_${session.sessionName}`
)

const assignments = resolveAbSessions(
abSessionHelper,
Expand All @@ -359,9 +435,18 @@ describe('resolveMediaPlayers', () => {

expect(mockGetPieceSessionId).toHaveBeenCalledTimes(3)
expect(mockGetObjectSessionId).toHaveBeenCalledTimes(0)
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(1, pieces[0].instance, 'clip_abc')
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(2, pieces[1].instance, 'clip_def')
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(3, pieces[2].instance, 'clip_ghi')
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(1, pieces[0].instance, {
poolName: 'clip',
sessionName: 'abc',
} satisfies PieceAbSessionInfo)
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(2, pieces[1].instance, {
poolName: 'clip',
sessionName: 'def',
} satisfies PieceAbSessionInfo)
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(3, pieces[2].instance, {
poolName: 'clip',
sessionName: 'ghi',
} satisfies PieceAbSessionInfo)
})

test('basic reassignment', () => {
Expand All @@ -383,7 +468,9 @@ describe('resolveMediaPlayers', () => {
createBasicResolvedPieceInstance('2', 2800, 4000, 'ghi'),
]

mockGetPieceSessionId.mockImplementation((piece, name) => `${piece._id}_${name}`)
mockGetPieceSessionId.mockImplementation(
(piece, session) => `${piece._id}_${session.poolName}_${session.sessionName}`
)

const assignments = resolveAbSessions(
abSessionHelper,
Expand All @@ -406,9 +493,18 @@ describe('resolveMediaPlayers', () => {

expect(mockGetPieceSessionId).toHaveBeenCalledTimes(3)
expect(mockGetObjectSessionId).toHaveBeenCalledTimes(0)
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(1, pieces[0].instance, 'clip_abc')
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(2, pieces[1].instance, 'clip_def')
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(3, pieces[2].instance, 'clip_ghi')
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(1, pieces[0].instance, {
poolName: 'clip',
sessionName: 'abc',
} satisfies PieceAbSessionInfo)
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(2, pieces[1].instance, {
poolName: 'clip',
sessionName: 'def',
} satisfies PieceAbSessionInfo)
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(3, pieces[2].instance, {
poolName: 'clip',
sessionName: 'ghi',
} satisfies PieceAbSessionInfo)
})

test('optional gets discarded', () => {
Expand All @@ -430,7 +526,9 @@ describe('resolveMediaPlayers', () => {
createBasicResolvedPieceInstance('2', 2800, 4000, 'ghi'),
]

mockGetPieceSessionId.mockImplementation((piece, name) => `${piece._id}_${name}`)
mockGetPieceSessionId.mockImplementation(
(piece, session) => `${piece._id}_${session.poolName}_${session.sessionName}`
)

const assignments = resolveAbSessions(
abSessionHelper,
Expand All @@ -453,9 +551,19 @@ describe('resolveMediaPlayers', () => {

expect(mockGetPieceSessionId).toHaveBeenCalledTimes(3)
expect(mockGetObjectSessionId).toHaveBeenCalledTimes(0)
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(1, pieces[0].instance, 'clip_abc')
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(2, pieces[1].instance, 'clip_def')
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(3, pieces[2].instance, 'clip_ghi')
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(1, pieces[0].instance, {
poolName: 'clip',
sessionName: 'abc',
} satisfies PieceAbSessionInfo)
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(2, pieces[1].instance, {
poolName: 'clip',
sessionName: 'def',
optional: true,
} satisfies PieceAbSessionInfo)
expect(mockGetPieceSessionId).toHaveBeenNthCalledWith(3, pieces[2].instance, {
poolName: 'clip',
sessionName: 'ghi',
} satisfies PieceAbSessionInfo)
})

// TODO add some tests which check lookahead
Expand Down
Loading