Skip to content

Commit

Permalink
fix: ab playback resolver not allowing sessions to span between segments
Browse files Browse the repository at this point in the history
  • Loading branch information
Julusian committed Apr 5, 2024
1 parent 5cb987e commit b3b4043
Show file tree
Hide file tree
Showing 6 changed files with 79 additions and 10 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { convertPartInstanceToBlueprints } from './lib'
import { RundownContext } from './RundownContext'
import { AbSessionHelper } from '../../playout/abPlayback/abSessionHelper'
import { protectString } from '@sofie-automation/corelib/dist/protectedString'
import { PieceInstanceId } from '@sofie-automation/corelib/dist/dataModel/Ids'
import { PieceInstanceId, SegmentId } from '@sofie-automation/corelib/dist/dataModel/Ids'

export class OnTimelineGenerateContext extends RundownContext implements ITimelineEventContext {
readonly currentPartInstance: Readonly<IBlueprintPartInstance> | undefined
Expand All @@ -36,6 +36,7 @@ export class OnTimelineGenerateContext extends RundownContext implements ITimeli
showStyleBlueprintConfig: ProcessedShowStyleConfig,
playlist: ReadonlyDeep<DBRundownPlaylist>,
rundown: ReadonlyDeep<DBRundown>,
orderedSegmentIds: ReadonlyDeep<Array<SegmentId>>,
previousPartInstance: ReadonlyDeep<DBPartInstance> | undefined,
currentPartInstance: ReadonlyDeep<DBPartInstance> | undefined,
nextPartInstance: ReadonlyDeep<DBPartInstance> | undefined,
Expand Down Expand Up @@ -64,6 +65,7 @@ export class OnTimelineGenerateContext extends RundownContext implements ITimeli
}

this.abSessionsHelper = new AbSessionHelper(
orderedSegmentIds,
partInstances,
clone<ABSessionInfo[]>(playlist.trackedAbSessions ?? [])
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,7 @@ function resolveAbSessions(

describe('resolveMediaPlayers', () => {
// TODO - rework this to use an interface instead of mocking the methods
const abSessionHelper = new AbSessionHelper([], [])
const abSessionHelper = new AbSessionHelper([], [], [])

const mockGetPieceSessionId: jest.MockedFunction<typeof abSessionHelper.getPieceABSessionId> = jest.fn()
const mockGetObjectSessionId: jest.MockedFunction<typeof abSessionHelper.getTimelineObjectAbSessionId> = jest.fn()
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
import { PartInstanceId, PieceInstanceInfiniteId, PartId } from '@sofie-automation/corelib/dist/dataModel/Ids'
import {
PartInstanceId,
PieceInstanceInfiniteId,
PartId,
SegmentId,
} from '@sofie-automation/corelib/dist/dataModel/Ids'
import { DBPartInstance } from '@sofie-automation/corelib/dist/dataModel/PartInstance'
import { DBRundown } from '@sofie-automation/corelib/dist/dataModel/Rundown'
import { ABSessionInfo } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist'
Expand Down Expand Up @@ -28,7 +33,13 @@ describe('AbSessionHelper', () => {
) {
const partInstances = _.compact([previousPartInstance, currentPartInstance, nextPartInstance])

const abSessionHelper = new AbSessionHelper(partInstances, clone<ABSessionInfo[]>(trackedAbSessions ?? []))
const orderedSegmentIds: SegmentId[] = [protectString('segment0'), protectString('segment1')]

const abSessionHelper = new AbSessionHelper(
orderedSegmentIds,
partInstances,
clone<ABSessionInfo[]>(trackedAbSessions ?? [])
)

let nextId = 0
abSessionHelper.getNewSessionId = () => getSessionId(nextId++)
Expand Down Expand Up @@ -71,12 +82,21 @@ describe('AbSessionHelper', () => {
isLookahead: !!isLookahead,
} as any
}
function createPartInstance(id: string, partId: string, rank: number): DBPartInstance {
function createPartInstance(
id: string,
partId: string,
rank: number,
segmentId?: SegmentId | string
): DBPartInstance {
segmentId = segmentId ?? 'segment0'

// This defines only the minimum required values for the method we are calling
return {
_id: id,
segmentId,
part: {
_id: partId,
segmentId,
_rank: rank,
},
} as any
Expand Down Expand Up @@ -243,6 +263,25 @@ describe('AbSessionHelper', () => {
expect(abSessionHelper.getPieceABSessionId(piece2, 'name0')).toEqual(sessionId)
expect(abSessionHelper.knownSessions).toHaveLength(1)
})
test('getPieceABSessionId - continue normal session from previous segment', async () => {
const { rundownId } = await setupDefaultRundownPlaylist(jobContext)
const rundown = (await jobContext.mockCollections.Rundowns.findOne(rundownId)) as DBRundown
expect(rundown).toBeTruthy()

const nextPartInstance = createPartInstance('abcdef', 'aaa', 0, 'segment1')
const currentPartInstance = createPartInstance('12345', 'bbb', 0, 'segment0')

const abSessionHelper = getSessionHelper([], undefined, currentPartInstance, nextPartInstance)

const sessionId = getSessionId(0)
const piece0 = createPieceInstance(currentPartInstance._id)
expect(abSessionHelper.getPieceABSessionId(piece0, 'name0')).toEqual(sessionId)
expect(abSessionHelper.knownSessions).toHaveLength(1)

const piece2 = createPieceInstance(nextPartInstance._id)
expect(abSessionHelper.getPieceABSessionId(piece2, 'name0')).toEqual(sessionId)
expect(abSessionHelper.knownSessions).toHaveLength(1)
})

test('getPieceABSessionId - promote lookahead session from previous part', async () => {
const { rundownId } = await setupDefaultRundownPlaylist(jobContext)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { applyAbPlayerObjectAssignments } from '../applyAssignments'
const POOL_NAME = 'clip'

describe('applyMediaPlayersAssignments', () => {
const abSessionHelper = new AbSessionHelper([], [])
const abSessionHelper = new AbSessionHelper([], [], [])

const mockGetPieceSessionId: jest.MockedFunction<typeof abSessionHelper.getPieceABSessionId> = jest.fn()
const mockGetObjectSessionId: jest.MockedFunction<typeof abSessionHelper.getTimelineObjectAbSessionId> = jest.fn()
Expand Down
35 changes: 31 additions & 4 deletions packages/job-worker/src/playout/abPlayback/abSessionHelper.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { AB_MEDIA_PLAYER_AUTO } from '@sofie-automation/blueprints-integration'
import { PartId, PieceInstanceId } from '@sofie-automation/corelib/dist/dataModel/Ids'
import { PartId, PieceInstanceId, SegmentId } from '@sofie-automation/corelib/dist/dataModel/Ids'
import { DBPartInstance } from '@sofie-automation/corelib/dist/dataModel/PartInstance'
import { PieceInstance } from '@sofie-automation/corelib/dist/dataModel/PieceInstance'
import { ABSessionInfo } from '@sofie-automation/corelib/dist/dataModel/RundownPlaylist'
Expand All @@ -18,11 +18,17 @@ interface ABSessionInfoExt extends ABSessionInfo {
* A helper class for generating unique and persistent AB-playback sessionIds
*/
export class AbSessionHelper {
readonly #orderedSegmentIds: ReadonlyDeep<Array<SegmentId>>
readonly #partInstances: ReadonlyDeep<Array<DBPartInstance>>

readonly #knownSessions: ABSessionInfoExt[]

constructor(partInstances: ReadonlyDeep<Array<DBPartInstance>>, knownSessions: ABSessionInfo[]) {
constructor(
orderedSegmentIds: ReadonlyDeep<Array<SegmentId>>,
partInstances: ReadonlyDeep<Array<DBPartInstance>>,
knownSessions: ABSessionInfo[]
) {
this.#orderedSegmentIds = orderedSegmentIds
this.#partInstances = partInstances
this.#knownSessions = knownSessions
}
Expand Down Expand Up @@ -76,8 +82,7 @@ export class AbSessionHelper {
}

// Check if we can continue sessions from the part before, or if we should create new ones
const canReuseFromPartInstanceBefore =
partInstanceIndex > 0 && this.#partInstances[partInstanceIndex - 1].part._rank < partInstance.part._rank
const canReuseFromPartInstanceBefore = this.#canReuseFromPartInstanceBefore(partInstanceIndex, partInstance)

if (canReuseFromPartInstanceBefore) {
// Try and find a session from the part before that we can use
Expand Down Expand Up @@ -117,6 +122,28 @@ export class AbSessionHelper {
return sessionId
}

#canReuseFromPartInstanceBefore(partInstanceIndex: number, partInstance: ReadonlyDeep<DBPartInstance>) {
if (partInstanceIndex <= 0) return false

const previousPartInstance = this.#partInstances[partInstanceIndex - 1]

// Check if the previous instance is in the same segment, and is positioned before this one
if (
previousPartInstance.segmentId === partInstance.segmentId &&
previousPartInstance.part._rank < partInstance.part._rank
)
return true

// Check if the previous instance is in an earlier segment
if (
this.#orderedSegmentIds.indexOf(previousPartInstance.segmentId) <
this.#orderedSegmentIds.indexOf(partInstance.segmentId)
)
return true

return false
}

/**
* Get the full session id for a timelineobject that belongs to an ab playback session
* sessionName should also be used in calls to getPieceABSessionId for the owning piece
Expand Down
1 change: 1 addition & 0 deletions packages/job-worker/src/playout/timeline/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -385,6 +385,7 @@ async function getTimelineRundown(
context.getShowStyleBlueprintConfig(showStyle),
playoutModel.playlist,
activeRundown.rundown,
activeRundown.segments.map((s) => s.segment._id),
previousPartInstance?.partInstance,
currentPartInstance?.partInstance,
nextPartInstance?.partInstance,
Expand Down

0 comments on commit b3b4043

Please sign in to comment.