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
3 changes: 2 additions & 1 deletion packages/blueprints-integration/src/api/studio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import type {
IngestRundown,
MutableIngestRundown,
UserOperationChange,
PlayoutOperationChange,
} from '../ingest.js'
import type {
ExpectedPlayoutItemGeneric,
Expand Down Expand Up @@ -137,7 +138,7 @@ export interface StudioBlueprintManifest<
mutableIngestRundown: MutableIngestRundown<any, any, any>,
nrcsIngestRundown: IngestRundown,
previousNrcsIngestRundown: IngestRundown | undefined,
changes: NrcsIngestChangeDetails | UserOperationChange
changes: NrcsIngestChangeDetails | UserOperationChange | PlayoutOperationChange
) => Promise<void>
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import type { DatastorePersistenceMode, Time } from '../common.js'
import type { IEventContext } from './index.js'
import type { IShowStyleUserContext } from './showStyleContext.js'
import { IPartAndPieceActionContext } from './partsAndPieceActionContext.js'
import { IExecuteTSRActionsContext } from './executeTsrActionContext.js'
import { IExecuteTSRActionsContext, ITriggerIngestChangeContext } from './executeTsrActionContext.js'
import { IBlueprintPart, IBlueprintPartInstance, IBlueprintPiece } from '../index.js'
import { IRouteSetMethods } from './routeSetContext.js'

Expand All @@ -27,6 +27,7 @@ export interface IActionExecutionContext
IDataStoreMethods,
IPartAndPieceActionContext,
IExecuteTSRActionsContext,
ITriggerIngestChangeContext,
IRouteSetMethods {
/** Fetch the showstyle config for the specified part */
// getNextShowStyleConfig(): Readonly<{ [key: string]: ConfigItemValue }>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,3 +13,14 @@ export interface IExecuteTSRActionsContext {
timeoutMs?: number
): Promise<TSR.ActionExecutionResult>
}

export interface ITriggerIngestChangeContext {
/**
* Execute an ingest operation
* This dispatches the operation but does not wait for it to be processed
* Note: This should be used with caution, it will not be good for performance to trigger a lot of ingest operations
* during playout of a rundown, especially if they need to make changes to many segments
* @param operation The blueprint defined payload for the operation
*/
emitIngestOperation(operation: unknown): Promise<void>
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,14 +10,15 @@ import {
IEventContext,
IShowStyleUserContext,
} from '../index.js'
import { ITriggerIngestChangeContext } from './executeTsrActionContext.js'
import { BlueprintQuickLookInfo } from './quickLoopInfo.js'
import { ReadonlyDeep } from 'type-fest'

/**
* Context in which 'current' is the part currently on air, and 'next' is the partInstance being set as Next
* This is similar to `IPartAndPieceActionContext`, but has more limits on what is allowed to be changed.
*/
export interface IOnSetAsNextContext extends IShowStyleUserContext, IEventContext {
export interface IOnSetAsNextContext extends IShowStyleUserContext, IEventContext, ITriggerIngestChangeContext {
/** Information about the current loop, if there is one */
readonly quickLoopInfo: BlueprintQuickLookInfo | null

Expand Down
9 changes: 7 additions & 2 deletions packages/blueprints-integration/src/context/onTakeContext.ts
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import { IBlueprintPart, IBlueprintPiece, IEventContext, IShowStyleUserContext, Time } from '../index.js'
import { IPartAndPieceActionContext } from './partsAndPieceActionContext.js'
import { IExecuteTSRActionsContext } from './executeTsrActionContext.js'
import { IExecuteTSRActionsContext, ITriggerIngestChangeContext } from './executeTsrActionContext.js'

/**
* Context in which 'current' is the partInstance we're leaving, and 'next' is the partInstance we're taking
*/
export interface IOnTakeContext
extends IPartAndPieceActionContext, IShowStyleUserContext, IEventContext, IExecuteTSRActionsContext {
extends
IPartAndPieceActionContext,
IShowStyleUserContext,
IEventContext,
IExecuteTSRActionsContext,
ITriggerIngestChangeContext {
/** Inform core that a take out of the taken partinstance should be blocked until the specified time */
blockTakeUntil(time: Time | null): Promise<void>
/**
Expand Down
15 changes: 15 additions & 0 deletions packages/blueprints-integration/src/ingest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,8 @@ export enum IngestChangeType {
Ingest = 'ingest',
/** Indicate that this change is from user operations */
User = 'user',
/** Indicate that this change is from playout operations */
Playout = 'playout',
}

/**
Expand Down Expand Up @@ -191,6 +193,19 @@ export interface UserOperationChange<TCustomBlueprintOperations extends { id: st
operationTarget: UserOperationTarget
operation: DefaultUserOperations | TCustomBlueprintOperations
}
export interface PlayoutOperationChange {
/** Indicate that this change is from playout operations */
source: IngestChangeType.Playout

/** If known and valid, the id of the segment when this operation occurred */
currentSegmentId: string | null
/** If known and valid, the id of the part when this operation occurred */
currentPartId: string | null

/** The blueprint defined payload for the operation */
operation: unknown
}

/**
* The MutableIngestRundown is used to modify the contents of an IngestRundown during ingest.
* The public properties and methods are used i blueprints to selectively apply incoming
Expand Down
13 changes: 13 additions & 0 deletions packages/corelib/src/worker/ingest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
BucketAdLibId,
BucketId,
ExpectedPackageId,
PartId,
RundownId,
SegmentId,
ShowStyleBaseId,
Expand Down Expand Up @@ -123,6 +124,11 @@ export enum IngestJobs {
*/
UserExecuteChangeOperation = 'userExecuteChangeOperation',

/**
* Playout executed a change operation
*/
PlayoutExecuteChangeOperation = 'playoutExecuteChangeOperation',

// For now these are in this queue, but if this gets split up to be per rundown, then a single bucket queue will be needed
BucketItemImport = 'bucketItemImport',
BucketItemRegenerate = 'bucketItemRegenerate',
Expand Down Expand Up @@ -242,6 +248,12 @@ export interface UserExecuteChangeOperationProps extends IngestPropsBase {
operation: { id: string; [key: string]: any }
}

export interface PlayoutExecuteChangeOperationProps extends IngestPropsBase {
segmentId: SegmentId | null
partId: PartId | null
operation: unknown
}

export interface BucketItemImportProps {
bucketId: BucketId
showStyleBaseId: ShowStyleBaseId
Expand Down Expand Up @@ -310,6 +322,7 @@ export type IngestJobFunc = {
[IngestJobs.UserRemoveRundown]: (data: UserRemoveRundownProps) => void
[IngestJobs.UserUnsyncRundown]: (data: UserUnsyncRundownProps) => void
[IngestJobs.UserExecuteChangeOperation]: (data: UserExecuteChangeOperationProps) => void
[IngestJobs.PlayoutExecuteChangeOperation]: (data: PlayoutExecuteChangeOperationProps) => void

[IngestJobs.BucketItemImport]: (data: BucketItemImportProps) => void
[IngestJobs.BucketItemRegenerate]: (data: BucketItemRegenerateProps) => void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ import { BlueprintQuickLookInfo } from '@sofie-automation/blueprints-integration
import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part'
import { selectNewPartWithOffsets } from '../../playout/moveNextPart.js'
import { getOrderedPartsAfterPlayhead } from '../../playout/lookahead/util.js'
import { convertPartToBlueprints } from './lib.js'
import { convertPartToBlueprints, emitIngestOperation } from './lib.js'

export class OnSetAsNextContext
extends ShowStyleUserContext
Expand Down Expand Up @@ -156,6 +156,10 @@ export class OnSetAsNextContext
return !!this.pendingMoveNextPart.selectedPart
}

async emitIngestOperation(operation: unknown): Promise<void> {
await emitIngestOperation(this.jobContext, this.playoutModel, operation)
}

getCurrentTime(): number {
return getCurrentTime()
}
Expand Down
6 changes: 5 additions & 1 deletion packages/job-worker/src/blueprints/context/OnTakeContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import {
} from './services/PartAndPieceInstanceActionService.js'
import { BlueprintQuickLookInfo } from '@sofie-automation/blueprints-integration/dist/context/quickLoopInfo'
import { getOrderedPartsAfterPlayhead } from '../../playout/lookahead/util.js'
import { convertPartToBlueprints } from './lib.js'
import { convertPartToBlueprints, emitIngestOperation } from './lib.js'

export class OnTakeContext extends ShowStyleUserContext implements IOnTakeContext, IEventContext {
public isTakeAborted: boolean
Expand Down Expand Up @@ -182,6 +182,10 @@ export class OnTakeContext extends ShowStyleUserContext implements IOnTakeContex
)
}

async emitIngestOperation(operation: unknown): Promise<void> {
await emitIngestOperation(this._context, this._playoutModel, operation)
}

getCurrentTime(): number {
return getCurrentTime()
}
Expand Down
6 changes: 5 additions & 1 deletion packages/job-worker/src/blueprints/context/adlibActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ import {
import { BlueprintQuickLookInfo } from '@sofie-automation/blueprints-integration/dist/context/quickLoopInfo'
import { setNextPartFromPart } from '../../playout/setNext.js'
import { getOrderedPartsAfterPlayhead } from '../../playout/lookahead/util.js'
import { convertPartToBlueprints } from './lib.js'
import { convertPartToBlueprints, emitIngestOperation } from './lib.js'

export class DatastoreActionExecutionContext
extends ShowStyleUserContext
Expand Down Expand Up @@ -278,6 +278,10 @@ export class ActionExecutionContext extends ShowStyleUserContext implements IAct
})
}

async emitIngestOperation(operation: unknown): Promise<void> {
await emitIngestOperation(this._context, this._playoutModel, operation)
}

getCurrentTime(): number {
return getCurrentTime()
}
Expand Down
29 changes: 29 additions & 0 deletions packages/job-worker/src/blueprints/context/lib.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,10 @@ import {
import type { PlayoutMutatablePart } from '../../playout/model/PlayoutPartInstanceModel.js'
import { BlueprintQuickLookInfo } from '@sofie-automation/blueprints-integration/dist/context/quickLoopInfo'
import { IngestPartNotifyItemReady } from '@sofie-automation/shared-lib/dist/ingest/rundownStatus'
import { PlayoutModel } from '../../playout/model/PlayoutModel.js'
import { IngestJobs } from '@sofie-automation/corelib/dist/worker/ingest'
import { stringifyError } from '@sofie-automation/shared-lib/dist/lib/stringifyError'
import { logger } from '../../logging.js'

/**
* Convert an object to have all the values of all keys (including optionals) be 'true'
Expand Down Expand Up @@ -715,3 +719,28 @@ export function createBlueprintQuickLoopInfo(playlist: ReadonlyDeep<DBRundownPla
locked: playlistLoopProps.locked,
}
}

export async function emitIngestOperation(
context: JobContext,
playoutModel: PlayoutModel,
operation: unknown
): Promise<void> {
const refPartInstance = playoutModel.currentPartInstance ?? playoutModel.nextPartInstance
if (!refPartInstance) throw new Error('Cannot emit ingest operation when there is no current or next partInstance')

const rundown = playoutModel.getRundown(refPartInstance.partInstance.rundownId)
if (!rundown) throw new Error('Cannot emit ingest operation when the partInstance has no rundown')

await context
.queueIngestJob(IngestJobs.PlayoutExecuteChangeOperation, {
rundownExternalId: rundown.rundown.externalId,
segmentId: refPartInstance.partInstance.segmentId ?? null,
partId: refPartInstance.partInstance.part._id ?? null,
operation,
})
.catch((e) => {
logger.warn(`Failed to queue ingest operation: ${stringifyError(e)}`)

throw new Error('Internal error while queueing ingest operation')
})
}
3 changes: 2 additions & 1 deletion packages/job-worker/src/ingest/runOperation.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import {
UserOperationChange,
SofieIngestSegment,
IngestChangeType,
PlayoutOperationChange,
} from '@sofie-automation/blueprints-integration'
import { MutableIngestRundownImpl } from '../blueprints/ingest/MutableIngestRundownImpl.js'
import { ProcessIngestDataContext } from '../blueprints/context/index.js'
Expand All @@ -37,7 +38,7 @@ export enum ComputedIngestChangeAction {

export interface UpdateIngestRundownChange {
ingestRundown: IngestRundownWithSource
changes: NrcsIngestChangeDetails | UserOperationChange
changes: NrcsIngestChangeDetails | UserOperationChange | PlayoutOperationChange
}

export type UpdateIngestRundownResult = UpdateIngestRundownChange | ComputedIngestChangeAction
Expand Down
26 changes: 25 additions & 1 deletion packages/job-worker/src/ingest/userOperation.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,11 @@
import { UserExecuteChangeOperationProps } from '@sofie-automation/corelib/dist/worker/ingest'
import {
PlayoutExecuteChangeOperationProps,
UserExecuteChangeOperationProps,
} from '@sofie-automation/corelib/dist/worker/ingest'
import { JobContext } from '../jobs/index.js'
import { UpdateIngestRundownResult, runIngestUpdateOperationBase } from './runOperation.js'
import { IngestChangeType } from '@sofie-automation/blueprints-integration'
import { unprotectString } from '@sofie-automation/corelib/dist/protectedString'

export async function handleUserExecuteChangeOperation(
context: JobContext,
Expand All @@ -21,3 +25,23 @@ export async function handleUserExecuteChangeOperation(
} satisfies UpdateIngestRundownResult
})
}

export async function handlePlayoutExecuteChangeOperation(
context: JobContext,
data: PlayoutExecuteChangeOperationProps
): Promise<void> {
await runIngestUpdateOperationBase(context, data, async (nrcsIngestObjectCache) => {
const nrcsIngestRundown = nrcsIngestObjectCache.fetchRundown()
if (!nrcsIngestRundown) throw new Error(`Rundown "${data.rundownExternalId}" not found`)

return {
ingestRundown: nrcsIngestRundown,
changes: {
source: IngestChangeType.Playout,
currentSegmentId: unprotectString(data.segmentId),
currentPartId: unprotectString(data.partId),
operation: data.operation,
},
} satisfies UpdateIngestRundownResult
})
}
3 changes: 2 additions & 1 deletion packages/job-worker/src/workers/ingest/jobs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ import {
handleBucketRemoveAdlibPiece,
} from '../../ingest/bucket/bucketAdlibs.js'
import { handleBucketItemImport, handleBucketItemRegenerate } from '../../ingest/bucket/import.js'
import { handleUserExecuteChangeOperation } from '../../ingest/userOperation.js'
import { handlePlayoutExecuteChangeOperation, handleUserExecuteChangeOperation } from '../../ingest/userOperation.js'
import {
wrapCustomIngestJob,
wrapGenericIngestJob,
Expand Down Expand Up @@ -86,6 +86,7 @@ export const ingestJobHandlers: IngestJobHandlers = {
[IngestJobs.UserRemoveRundown]: handleUserRemoveRundown,
[IngestJobs.UserUnsyncRundown]: handleUserUnsyncRundown,
[IngestJobs.UserExecuteChangeOperation]: handleUserExecuteChangeOperation,
[IngestJobs.PlayoutExecuteChangeOperation]: handlePlayoutExecuteChangeOperation,

[IngestJobs.BucketItemImport]: handleBucketItemImport,
[IngestJobs.BucketItemRegenerate]: handleBucketItemRegenerate,
Expand Down
Loading