Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,9 @@ export interface IActionExecutionContext
/** Insert a queued part to follow the current part */
queuePart(part: IBlueprintPart, pieces: IBlueprintPiece[]): Promise<IBlueprintPartInstance>

/** Insert a queued part to follow the taken part */
queuePartAfterTake(part: IBlueprintPart, pieces: IBlueprintPiece[]): void

/** Misc actions */
// updateAction(newManifest: Pick<IBlueprintAdLibActionManifest, 'description' | 'payload'>): void // only updates itself. to allow for the next one to do something different
// executePeripheralDeviceAction(deviceId: string, functionName: string, args: any[]): Promise<any>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { IEventContext, IShowStyleUserContext, Time } from '../index.js'
import { IBlueprintPart, IBlueprintPiece, IEventContext, IShowStyleUserContext, Time } from '../index.js'
import { IPartAndPieceActionContext } from './partsAndPieceActionContext.js'
import { IExecuteTSRActionsContext } from './executeTsrActionContext.js'

Expand All @@ -18,4 +18,6 @@ export interface IOnTakeContext
* but the next part will not be taken.
*/
abortTake(): void
/** Insert a queued part to follow the taken part */
queuePartAfterTake(part: IBlueprintPart, pieces: IBlueprintPiece[]): void
}
20 changes: 19 additions & 1 deletion packages/job-worker/src/blueprints/context/OnTakeContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,11 +23,16 @@ import { WatchedPackagesHelper } from './watchedPackages.js'
import { getCurrentTime } from '../../lib/index.js'
import { JobContext, ProcessedShowStyleCompound } from '../../jobs/index.js'
import { executePeripheralDeviceAction, listPlayoutDevices } from '../../peripheralDevice.js'
import { ActionPartChange, PartAndPieceInstanceActionService } from './services/PartAndPieceInstanceActionService.js'
import {
ActionPartChange,
PartAndPieceInstanceActionService,
QueueablePartAndPieces,
} from './services/PartAndPieceInstanceActionService.js'
import { BlueprintQuickLookInfo } from '@sofie-automation/blueprints-integration/dist/context/quickLoopInfo'

export class OnTakeContext extends ShowStyleUserContext implements IOnTakeContext, IEventContext {
public isTakeAborted: boolean
public partToQueueAfterTake: QueueablePartAndPieces | undefined

public get quickLoopInfo(): BlueprintQuickLookInfo | null {
return this.partAndPieceInstanceService.quickLoopInfo
Expand Down Expand Up @@ -153,6 +158,19 @@ export class OnTakeContext extends ShowStyleUserContext implements IOnTakeContex
return executePeripheralDeviceAction(this._context, deviceId, null, actionId, payload)
}

queuePartAfterTake(rawPart: IBlueprintPart, rawPieces: IBlueprintPiece[]): void {
const currentPartInstance = this._playoutModel.currentPartInstance
if (!currentPartInstance) {
throw new Error('Cannot queue part when no current partInstance')
}
this.partToQueueAfterTake = this.partAndPieceInstanceService.processPartAndPiecesToQueueOrFail(
rawPart,
rawPieces,
this._playoutModel.currentPartInstance.partInstance.rundownId,
this._playoutModel.currentPartInstance.partInstance.segmentId
)
}

getCurrentTime(): number {
return getCurrentTime()
}
Expand Down
21 changes: 20 additions & 1 deletion packages/job-worker/src/blueprints/context/adlibActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,11 @@ import { ProcessedShowStyleConfig } from '../config.js'
import { DatastorePersistenceMode } from '@sofie-automation/shared-lib/dist/core/model/TimelineDatastore'
import { removeTimelineDatastoreValue, setTimelineDatastoreValue } from '../../playout/datastore.js'
import { executePeripheralDeviceAction, listPlayoutDevices } from '../../peripheralDevice.js'
import { ActionPartChange, PartAndPieceInstanceActionService } from './services/PartAndPieceInstanceActionService.js'
import {
ActionPartChange,
PartAndPieceInstanceActionService,
QueueablePartAndPieces,
} from './services/PartAndPieceInstanceActionService.js'
import { BlueprintQuickLookInfo } from '@sofie-automation/blueprints-integration/dist/context/quickLoopInfo'
import { setNextPartFromPart } from '../../playout/setNext.js'

Expand Down Expand Up @@ -74,6 +78,8 @@ export class ActionExecutionContext extends ShowStyleUserContext implements IAct
*/
public forceRegenerateTimeline = false

public partToQueueAfterTake: QueueablePartAndPieces | undefined

public get quickLoopInfo(): BlueprintQuickLookInfo | null {
return this.partAndPieceInstanceService.quickLoopInfo
}
Expand Down Expand Up @@ -162,6 +168,19 @@ export class ActionExecutionContext extends ShowStyleUserContext implements IAct
return this.partAndPieceInstanceService.queuePart(rawPart, rawPieces)
}

queuePartAfterTake(rawPart: IBlueprintPart, rawPieces: IBlueprintPiece[]): void {
const currentPartInstance = this._playoutModel.currentPartInstance
if (!currentPartInstance) {
throw new Error('Cannot queue part when no current partInstance')
}
this.partToQueueAfterTake = this.partAndPieceInstanceService.processPartAndPiecesToQueueOrFail(
rawPart,
rawPieces,
this._playoutModel.currentPartInstance.partInstance.rundownId,
this._playoutModel.currentPartInstance.partInstance.segmentId
)
}

async moveNextPart(partDelta: number, segmentDelta: number, ignoreQuickloop?: boolean): Promise<void> {
const selectedPart = selectNewPartWithOffsets(
this._context,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ import {
PieceTimelineObjectsBlob,
serializePieceTimelineObjectsBlob,
} from '@sofie-automation/corelib/dist/dataModel/Piece'
import { PartInstanceId, PieceInstanceId } from '@sofie-automation/corelib/dist/dataModel/Ids'
import { PartInstanceId, PieceInstanceId, RundownId, SegmentId } from '@sofie-automation/corelib/dist/dataModel/Ids'
import {
protectString,
unprotectString,
Expand All @@ -70,6 +70,11 @@ export interface IPartAndPieceInstanceActionContext {
readonly nextPartState: ActionPartChange
}

export interface QueueablePartAndPieces {
part: Omit<DBPart, 'segmentId' | 'rundownId' | '_rank'>
pieces: Piece[]
}

export class PartAndPieceInstanceActionService {
private readonly _context: JobContext
private readonly _playoutModel: PlayoutModel
Expand Down Expand Up @@ -387,11 +392,41 @@ export class PartAndPieceInstanceActionService {
throw new Error('Too close to an autonext to queue a part')
}

const { part, pieces } = this.processPartAndPiecesToQueueOrFail(
rawPart,
rawPieces,
currentPartInstance.partInstance.rundownId,
currentPartInstance.partInstance.segmentId
)

// Do the work
const newPartInstance = await insertQueuedPartWithPieces(
this._context,
this._playoutModel,
this._rundown,
currentPartInstance,
part,
pieces,
undefined
)

this.nextPartState = ActionPartChange.SAFE_CHANGE
this.queuedPartInstanceId = newPartInstance.partInstance._id

return convertPartInstanceToBlueprints(newPartInstance.partInstance)
}

public processPartAndPiecesToQueueOrFail(
rawPart: IBlueprintPart<unknown, unknown>,
rawPieces: IBlueprintPiece<unknown, unknown>[],
rundownId: RundownId,
segmentId: SegmentId
): QueueablePartAndPieces {
if (rawPieces.length === 0) {
throw new Error('New part must contain at least one piece')
}

const newPart: Omit<DBPart, 'segmentId' | 'rundownId' | '_rank'> = {
const part: Omit<DBPart, 'segmentId' | 'rundownId' | '_rank'> = {
...rawPart,
_id: getRandomId(),
notes: [],
Expand All @@ -407,31 +442,17 @@ export class PartAndPieceInstanceActionService {
this._context,
rawPieces,
this.showStyleCompound.blueprintId,
currentPartInstance.partInstance.rundownId,
currentPartInstance.partInstance.segmentId,
newPart._id,
rundownId,
segmentId,
part._id,
false
)

if (!isPartPlayable(newPart)) {
if (!isPartPlayable(part)) {
throw new Error('Cannot queue a part which is not playable')
}

// Do the work
const newPartInstance = await insertQueuedPartWithPieces(
this._context,
this._playoutModel,
this._rundown,
currentPartInstance,
newPart,
pieces,
undefined
)

this.nextPartState = ActionPartChange.SAFE_CHANGE
this.queuedPartInstanceId = newPartInstance.partInstance._id

return convertPartInstanceToBlueprints(newPartInstance.partInstance)
return { part, pieces }
}

async stopPiecesOnLayers(sourceLayerIds: string[], timeOffset: number | undefined): Promise<string[]> {
Expand Down
2 changes: 1 addition & 1 deletion packages/job-worker/src/playout/adlibAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -278,7 +278,7 @@ async function applyAnyExecutionSideEffects(
await applyActionSideEffects(context, playoutModel, actionContext)

if (actionContext.takeAfterExecute) {
await performTakeToNextedPart(context, playoutModel, now)
await performTakeToNextedPart(context, playoutModel, now, actionContext.partToQueueAfterTake)
} else if (
actionContext.forceRegenerateTimeline ||
actionContext.currentPartState !== ActionPartChange.NONE ||
Expand Down
2 changes: 1 addition & 1 deletion packages/job-worker/src/playout/adlibTesting.ts
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ export async function handleActivateAdlibTesting(context: JobContext, data: Acti
playoutModel.setPartInstanceAsNext(newPartInstance, true, false)

// Take into the newly created Part
await performTakeToNextedPart(context, playoutModel, getCurrentTime())
await performTakeToNextedPart(context, playoutModel, getCurrentTime(), undefined)
}
)
}
Expand Down
64 changes: 45 additions & 19 deletions packages/job-worker/src/playout/take.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import { stringifyError } from '@sofie-automation/shared-lib/dist/lib/stringifyE
import { updateTimeline } from './timeline/generate.js'
import { OnTakeContext, PartEventContext, RundownContext } from '../blueprints/context/index.js'
import { WrappedShowStyleBlueprint } from '../blueprints/cache.js'
import { innerStopPieces } from './adlibUtils.js'
import { innerStopPieces, insertQueuedPartWithPieces } from './adlibUtils.js'
import { reportPartInstanceHasStarted, reportPartInstanceHasStopped } from './timings/partPlayback.js'
import { convertPartInstanceToBlueprints, convertResolvedPieceInstanceToBlueprints } from '../blueprints/context/lib.js'
import { processAndPrunePieceInstanceTimings } from '@sofie-automation/corelib/dist/playout/processAndPrune'
Expand All @@ -30,6 +30,7 @@ import { ReadonlyObjectDeep } from 'type-fest/source/readonly-deep'
import { WatchedPackagesHelper } from '../blueprints/context/watchedPackages.js'
import {
PartAndPieceInstanceActionService,
QueueablePartAndPieces,
applyActionSideEffects,
} from '../blueprints/context/services/PartAndPieceInstanceActionService.js'
import { PlayoutRundownModel } from './model/PlayoutRundownModel.js'
Expand Down Expand Up @@ -83,7 +84,7 @@ export async function handleTakeNextPart(context: JobContext, data: TakeNextPart
})
}

return performTakeToNextedPart(context, playoutModel, now)
return performTakeToNextedPart(context, playoutModel, now, undefined)
}
)
}
Expand All @@ -97,7 +98,8 @@ export async function handleTakeNextPart(context: JobContext, data: TakeNextPart
export async function performTakeToNextedPart(
context: JobContext,
playoutModel: PlayoutModel,
now: number
now: number,
partToQueueAfterTake: QueueablePartAndPieces | undefined
): Promise<void> {
const span = context.startSpan('takeNextPartInner')

Expand Down Expand Up @@ -198,7 +200,15 @@ export async function performTakeToNextedPart(
const showStyle = await pShowStyle
const blueprint = await context.getShowStyleBlueprint(showStyle._id)

const { isTakeAborted } = await executeOnTakeCallback(context, playoutModel, showStyle, blueprint, currentRundown)
const { isTakeAborted, partToQueueAfterTake: partToQueueFromOnTake } = await executeOnTakeCallback(
context,
playoutModel,
showStyle,
blueprint,
currentRundown
)

partToQueueAfterTake = partToQueueAfterTake ?? partToQueueFromOnTake

if (isTakeAborted) {
await updateTimeline(context, playoutModel)
Expand Down Expand Up @@ -248,24 +258,35 @@ export async function performTakeToNextedPart(
const wasLooping = playoutModel.playlist.quickLoop?.running
playoutModel.updateQuickLoopState()

const nextPart = selectNextPart(
context,
playoutModel.playlist,
takePartInstance.partInstance,
null,
playoutModel.getAllOrderedSegments(),
playoutModel.getAllOrderedParts(),
{ ignoreUnplayable: true, ignoreQuickLoop: false }
)

takePartInstance.setTaken(now, timeOffset)

if (wasLooping) {
resetPreviousSegmentIfLooping(context, playoutModel)
}

// Once everything is synced, we can choose the next part
await setNextPart(context, playoutModel, nextPart, false)
if (partToQueueAfterTake) {
await insertQueuedPartWithPieces(
context,
playoutModel,
takeRundown,
takePartInstance,
partToQueueAfterTake.part,
partToQueueAfterTake.pieces,
undefined
)
} else {
// Once everything is synced, we can choose the next part
const nextPart = selectNextPart(
context,
playoutModel.playlist,
takePartInstance.partInstance,
null,
playoutModel.getAllOrderedSegments(),
playoutModel.getAllOrderedParts(),
{ ignoreUnplayable: true, ignoreQuickLoop: false }
)
await setNextPart(context, playoutModel, nextPart, false)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The definition of nextPart would be good to move into this else block, so that it is only run when needed and make sure it doesnt get used elsewhere accidentally

}

// If the Hold is PENDING, make it active
if (playoutModel.playlist.holdState === RundownHoldState.PENDING) {
Expand All @@ -289,17 +310,19 @@ async function executeOnTakeCallback(
showStyle: ReadonlyObjectDeep<ProcessedShowStyleCompound>,
blueprint: ReadonlyObjectDeep<WrappedShowStyleBlueprint>,
currentRundown: PlayoutRundownModel
): Promise<{ isTakeAborted: boolean }> {
): Promise<{ isTakeAborted: boolean; partToQueueAfterTake: QueueablePartAndPieces | undefined }> {
const NOTIFICATION_CATEGORY = 'onTake'

let isTakeAborted = false
let partToQueueAfterTake: QueueablePartAndPieces | undefined = undefined
if (blueprint.blueprint.onTake) {
const rundownId = currentRundown.rundown._id
const partInstanceId = playoutModel.playlist.nextPartInfo?.partInstanceId
if (!partInstanceId) throw new Error('Cannot call blueprint onTake when there is no next partInstance!')

// Clear any existing notifications for this partInstance. This will clear any from the previous take
playoutModel.clearAllNotifications(NOTIFICATION_CATEGORY)
const actionService = new PartAndPieceInstanceActionService(context, playoutModel, showStyle, currentRundown)

const watchedPackagesHelper = WatchedPackagesHelper.empty(context)
const onSetAsNextContext = new OnTakeContext(
Expand All @@ -313,7 +336,7 @@ async function executeOnTakeCallback(
playoutModel,
showStyle,
watchedPackagesHelper,
new PartAndPieceInstanceActionService(context, playoutModel, showStyle, currentRundown)
actionService
)
try {
const blueprintPersistentState = new PersistentPlayoutStateStore(
Expand All @@ -323,6 +346,9 @@ async function executeOnTakeCallback(
await blueprint.blueprint.onTake(onSetAsNextContext, blueprintPersistentState)
await applyOnTakeSideEffects(context, playoutModel, onSetAsNextContext)
isTakeAborted = onSetAsNextContext.isTakeAborted
if (onSetAsNextContext.partToQueueAfterTake) {
partToQueueAfterTake = onSetAsNextContext.partToQueueAfterTake
}

if (blueprintPersistentState.hasChanges) {
playoutModel.setBlueprintPersistentState(blueprintPersistentState.getAll())
Expand Down Expand Up @@ -354,7 +380,7 @@ async function executeOnTakeCallback(
})
}
}
return { isTakeAborted }
return { isTakeAborted, partToQueueAfterTake }
}

async function applyOnTakeSideEffects(context: JobContext, playoutModel: PlayoutModel, onTakeContext: OnTakeContext) {
Expand Down
Loading