Skip to content
Merged
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
19 changes: 16 additions & 3 deletions packages/job-worker/src/blueprints/context/OnTakeContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,12 +22,16 @@ import { WatchedPackagesHelper } from './watchedPackages'
import { getCurrentTime } from '../../lib'
import { JobContext, ProcessedShowStyleCompound } from '../../jobs'
import { executePeripheralDeviceAction, listPlayoutDevices } from '../../peripheralDevice'
import { ActionPartChange, PartAndPieceInstanceActionService } from './services/PartAndPieceInstanceActionService'
import {
ActionPartChange,
PartAndPieceInstanceActionService,
QueueablePartAndPieces,
} from './services/PartAndPieceInstanceActionService'
import { BlueprintQuickLookInfo } from '@sofie-automation/blueprints-integration/dist/context/quickLoopInfo'

export class OnTakeContext extends ShowStyleUserContext implements IOnTakeContext, IEventContext {
public isTakeAborted: boolean
public partToQueue: { rawPart: IBlueprintPart; rawPieces: IBlueprintPiece[] } | undefined
public partToQueueAfterTake: QueueablePartAndPieces | undefined

public get quickLoopInfo(): BlueprintQuickLookInfo | null {
return this.partAndPieceInstanceService.quickLoopInfo
Expand Down Expand Up @@ -152,7 +156,16 @@ export class OnTakeContext extends ShowStyleUserContext implements IOnTakeContex
}

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

getCurrentTime(): number {
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 @@ -28,7 +28,11 @@ import { ProcessedShowStyleConfig } from '../config'
import { DatastorePersistenceMode } from '@sofie-automation/shared-lib/dist/core/model/TimelineDatastore'
import { removeTimelineDatastoreValue, setTimelineDatastoreValue } from '../../playout/datastore'
import { executePeripheralDeviceAction, listPlayoutDevices } from '../../peripheralDevice'
import { ActionPartChange, PartAndPieceInstanceActionService } from './services/PartAndPieceInstanceActionService'
import {
ActionPartChange,
PartAndPieceInstanceActionService,
QueueablePartAndPieces,
} from './services/PartAndPieceInstanceActionService'
import { BlueprintQuickLookInfo } from '@sofie-automation/blueprints-integration/dist/context/quickLoopInfo'
import { setNextPartFromPart } from '../../playout/setNext'

Expand Down Expand Up @@ -73,6 +77,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 @@ -157,6 +163,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,
currentPartInstance.partInstance.rundownId,
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 @@ -42,7 +42,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 @@ -68,6 +68,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 @@ -380,11 +385,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 @@ -400,31 +435,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 @@ -276,7 +276,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
56 changes: 32 additions & 24 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'
import { OnTakeContext, PartEventContext, RundownContext } from '../blueprints/context'
import { WrappedShowStyleBlueprint } from '../blueprints/cache'
import { innerStopPieces } from './adlibUtils'
import { innerStopPieces, insertQueuedPartWithPieces } from './adlibUtils'
import { reportPartInstanceHasStarted, reportPartInstanceHasStopped } from './timings/partPlayback'
import { convertPartInstanceToBlueprints, convertResolvedPieceInstanceToBlueprints } from '../blueprints/context/lib'
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'
import {
PartAndPieceInstanceActionService,
QueueablePartAndPieces,
applyActionSideEffects,
} from '../blueprints/context/services/PartAndPieceInstanceActionService'
import { PlayoutRundownModel } from './model/PlayoutRundownModel'
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,14 +200,16 @@ export async function performTakeToNextedPart(
const showStyle = await pShowStyle
const blueprint = await context.getShowStyleBlueprint(showStyle._id)

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

partToQueueAfterTake = partToQueueAfterTake ?? partToQueueFromOnTake

if (isTakeAborted) {
await updateTimeline(context, playoutModel)
return
Expand Down Expand Up @@ -254,26 +258,33 @@ 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)
}

if (queuePart) {
await queuePart()
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)
}

Expand All @@ -299,11 +310,11 @@ async function executeOnTakeCallback(
showStyle: ReadonlyObjectDeep<ProcessedShowStyleCompound>,
blueprint: ReadonlyObjectDeep<WrappedShowStyleBlueprint>,
currentRundown: PlayoutRundownModel
): Promise<{ isTakeAborted: boolean; queuePart: (() => Promise<void>) | undefined }> {
): Promise<{ isTakeAborted: boolean; partToQueueAfterTake: QueueablePartAndPieces | undefined }> {
const NOTIFICATION_CATEGORY = 'onTake'

let isTakeAborted = false
let queuePart: (() => Promise<void>) | undefined = undefined
let partToQueueAfterTake: QueueablePartAndPieces | undefined = undefined
if (blueprint.blueprint.onTake) {
const rundownId = currentRundown.rundown._id
const partInstanceId = playoutModel.playlist.nextPartInfo?.partInstanceId
Expand Down Expand Up @@ -331,11 +342,8 @@ async function executeOnTakeCallback(
await blueprint.blueprint.onTake(onSetAsNextContext)
await applyOnTakeSideEffects(context, playoutModel, onSetAsNextContext)
isTakeAborted = onSetAsNextContext.isTakeAborted
if (onSetAsNextContext.partToQueue) {
const partToQueue = onSetAsNextContext.partToQueue
queuePart = async () => {
await actionService.queuePart(partToQueue.rawPart, partToQueue.rawPieces)
}
if (onSetAsNextContext.partToQueueAfterTake) {
partToQueueAfterTake = onSetAsNextContext.partToQueueAfterTake
}

for (const note of onSetAsNextContext.notes) {
Expand Down Expand Up @@ -364,7 +372,7 @@ async function executeOnTakeCallback(
})
}
}
return { isTakeAborted, queuePart }
return { isTakeAborted, partToQueueAfterTake }
}

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