Skip to content

Commit f1f77fa

Browse files
committed
feat: quickloop improvements
1 parent d2aa933 commit f1f77fa

File tree

26 files changed

+472
-93
lines changed

26 files changed

+472
-93
lines changed

meteor/server/api/userActions.ts

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1269,6 +1269,26 @@ class ServerUserActionAPI
12691269
)
12701270
}
12711271

1272+
async clearQuickLoop(
1273+
userEvent: string,
1274+
eventTime: number,
1275+
playlistId: RundownPlaylistId
1276+
): Promise<ClientAPI.ClientResponse<void>> {
1277+
return ServerClientAPI.runUserActionInLogForPlaylistOnWorker(
1278+
this,
1279+
userEvent,
1280+
eventTime,
1281+
playlistId,
1282+
() => {
1283+
check(playlistId, String)
1284+
},
1285+
StudioJobs.ClearQuickLoopMarkers,
1286+
{
1287+
playlistId,
1288+
}
1289+
)
1290+
}
1291+
12721292
async createAdlibTestingRundownForShowStyleVariant(
12731293
userEvent: string,
12741294
eventTime: number,

packages/blueprints-integration/src/context/eventContext.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type { OnGenerateTimelineObj, TSR } from '../timeline'
22
import type { IBlueprintPartInstance, IBlueprintPieceInstance, IBlueprintSegmentDB } from '../documents'
33
import type { IRundownContext } from './rundownContext'
44
import type { IBlueprintExternalMessageQueueObj } from '../message'
5+
import { BlueprintQuickLookInfo } from './quickLoopInfo'
56

67
export interface IEventContext {
78
getCurrentTime(): number
@@ -12,6 +13,9 @@ export interface ITimelineEventContext extends IEventContext, IRundownContext {
1213
readonly nextPartInstance: Readonly<IBlueprintPartInstance> | undefined
1314
readonly previousPartInstance: Readonly<IBlueprintPartInstance> | undefined
1415

16+
/** Information about the current loop, if there is one */
17+
readonly quickLoopInfo: BlueprintQuickLookInfo | null
18+
1519
/**
1620
* Get the full session id for an ab playback session.
1721
* Note: sessionName should be unique within the segment unless pieces want to share a session

packages/blueprints-integration/src/context/onSetAsNextContext.ts

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,12 +9,16 @@ import {
99
IEventContext,
1010
IShowStyleUserContext,
1111
} from '..'
12+
import { BlueprintQuickLookInfo } from './quickLoopInfo'
1213

1314
/**
1415
* Context in which 'current' is the part currently on air, and 'next' is the partInstance being set as Next
1516
* This is similar to `IPartAndPieceActionContext`, but has more limits on what is allowed to be changed.
1617
*/
1718
export interface IOnSetAsNextContext extends IShowStyleUserContext, IEventContext {
19+
/** Information about the current loop, if there is one */
20+
readonly quickLoopInfo: BlueprintQuickLookInfo | null
21+
1822
/**
1923
* Data fetching
2024
*/
@@ -65,4 +69,12 @@ export interface IOnSetAsNextContext extends IShowStyleUserContext, IEventContex
6569
*/
6670
/** Remove piecesInstances by id. Returns ids of piecesInstances that were removed. Note: For now we only allow removing from the next, but this might change to include current if there is justification */
6771
removePieceInstances(part: 'next', pieceInstanceIds: string[]): Promise<string[]>
72+
73+
/**
74+
* Move the next part through the rundown. Can move by either a number of parts, or segments in either direction.
75+
* This will result in the `onSetAsNext` callback being called again following the current call, with the new PartInstance.
76+
* Multiple calls of this inside one call to `onSetAsNext` will replace earlier calls.
77+
* @returns Whether a new Part was found using the provided offset
78+
*/
79+
moveNextPart(partDelta: number, segmentDelta: number): Promise<boolean>
6880
}

packages/blueprints-integration/src/context/partsAndPieceActionContext.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,12 @@ import {
88
IBlueprintResolvedPieceInstance,
99
Time,
1010
} from '..'
11+
import { BlueprintQuickLookInfo } from './quickLoopInfo'
1112

1213
export interface IPartAndPieceActionContext {
14+
/** Information about the current loop, if there is one */
15+
readonly quickLoopInfo: BlueprintQuickLookInfo | null
16+
1317
/**
1418
* Data fetching
1519
*/
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
export type BlueprintQuickLookInfo = Readonly<{
2+
/** Whether there is a loop running */
3+
running: boolean
4+
/** Whether the loop is locked from user editing */
5+
locked: boolean
6+
}>

packages/corelib/src/worker/studio.ts

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -194,6 +194,11 @@ export enum StudioJobs {
194194
*/
195195
SetQuickLoopMarker = 'setQuickLoopMarker',
196196

197+
/**
198+
* Clear all QuickLoop markers
199+
*/
200+
ClearQuickLoopMarkers = 'clearQuickLoopMarkers',
201+
197202
/**
198203
* Switch the route of the studio
199204
* for use in ad.lib actions and other triggers
@@ -350,6 +355,7 @@ export interface SetQuickLoopMarkerProps extends RundownPlayoutPropsBase {
350355
type: 'start' | 'end'
351356
marker: QuickLoopMarker | null
352357
}
358+
export type ClearQuickLoopMarkersProps = RundownPlayoutPropsBase
353359

354360
export interface SwitchRouteSetProps {
355361
routeSetId: string
@@ -409,6 +415,7 @@ export type StudioJobFunc = {
409415
[StudioJobs.ActivateAdlibTesting]: (data: ActivateAdlibTestingProps) => void
410416

411417
[StudioJobs.SetQuickLoopMarker]: (data: SetQuickLoopMarkerProps) => void
418+
[StudioJobs.ClearQuickLoopMarkers]: (data: ClearQuickLoopMarkersProps) => void
412419

413420
[StudioJobs.SwitchRouteSet]: (data: SwitchRouteSetProps) => void
414421
}

packages/job-worker/src/blueprints/context/OnSetAsNextContext.ts

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,16 @@ import { PlayoutModel } from '../../playout/model/PlayoutModel'
2222
import { ReadonlyDeep } from 'type-fest'
2323
import { getCurrentTime } from '../../lib'
2424
import { protectString } from '@sofie-automation/corelib/dist/protectedString'
25+
import { BlueprintQuickLookInfo } from '@sofie-automation/blueprints-integration/dist/context/quickLoopInfo'
26+
import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part'
27+
import { selectNewPartWithOffsets } from '../../playout/moveNextPart'
2528

2629
export class OnSetAsNextContext
2730
extends ShowStyleUserContext
2831
implements IOnSetAsNextContext, IEventContext, IPartAndPieceInstanceActionContext
2932
{
33+
public pendingMoveNextPart: { selectedPart: ReadonlyDeep<DBPart> | null } | undefined = undefined
34+
3035
constructor(
3136
contextInfo: UserContextInfo,
3237
context: JobContext,
@@ -38,6 +43,10 @@ export class OnSetAsNextContext
3843
super(contextInfo, context, showStyle, watchedPackages)
3944
}
4045

46+
public get quickLoopInfo(): BlueprintQuickLookInfo | null {
47+
return this.partAndPieceInstanceService.quickLoopInfo
48+
}
49+
4150
public get nextPartState(): ActionPartChange {
4251
return this.partAndPieceInstanceService.nextPartState
4352
}
@@ -112,6 +121,23 @@ export class OnSetAsNextContext
112121
return this.partAndPieceInstanceService.removePieceInstances('next', pieceInstanceIds)
113122
}
114123

124+
async moveNextPart(partDelta: number, segmentDelta: number): Promise<boolean> {
125+
if (typeof partDelta !== 'number') throw new Error('partDelta must be a number')
126+
if (typeof segmentDelta !== 'number') throw new Error('segmentDelta must be a number')
127+
128+
// Values of 0 mean discard the pending change
129+
if (partDelta === 0 && segmentDelta === 0) {
130+
this.pendingMoveNextPart = undefined
131+
return true
132+
}
133+
134+
this.pendingMoveNextPart = {
135+
selectedPart: selectNewPartWithOffsets(this.jobContext, this.playoutModel, partDelta, segmentDelta),
136+
}
137+
138+
return !!this.pendingMoveNextPart.selectedPart
139+
}
140+
115141
getCurrentTime(): number {
116142
return getCurrentTime()
117143
}

packages/job-worker/src/blueprints/context/OnTakeContext.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,10 +23,15 @@ import { getCurrentTime } from '../../lib'
2323
import { JobContext, ProcessedShowStyleCompound } from '../../jobs'
2424
import { executePeripheralDeviceAction, listPlayoutDevices } from '../../peripheralDevice'
2525
import { ActionPartChange, PartAndPieceInstanceActionService } from './services/PartAndPieceInstanceActionService'
26+
import { BlueprintQuickLookInfo } from '@sofie-automation/blueprints-integration/dist/context/quickLoopInfo'
2627

2728
export class OnTakeContext extends ShowStyleUserContext implements IOnTakeContext, IEventContext {
2829
public isTakeAborted: boolean
2930

31+
public get quickLoopInfo(): BlueprintQuickLookInfo | null {
32+
return this.partAndPieceInstanceService.quickLoopInfo
33+
}
34+
3035
public get currentPartState(): ActionPartChange {
3136
return this.partAndPieceInstanceService.currentPartState
3237
}

packages/job-worker/src/blueprints/context/OnTimelineGenerateContext.ts

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,17 +15,20 @@ import { PieceInstance, ResolvedPieceInstance } from '@sofie-automation/corelib/
1515
import { ProcessedStudioConfig, ProcessedShowStyleConfig } from '../config'
1616
import _ = require('underscore')
1717
import { ProcessedShowStyleCompound } from '../../jobs'
18-
import { convertPartInstanceToBlueprints } from './lib'
18+
import { convertPartInstanceToBlueprints, createBlueprintQuickLoopInfo } from './lib'
1919
import { RundownContext } from './RundownContext'
2020
import { AbSessionHelper } from '../../playout/abPlayback/abSessionHelper'
2121
import { protectString } from '@sofie-automation/corelib/dist/protectedString'
2222
import { PieceInstanceId } from '@sofie-automation/corelib/dist/dataModel/Ids'
23+
import { BlueprintQuickLookInfo } from '@sofie-automation/blueprints-integration/dist/context/quickLoopInfo'
2324

2425
export class OnTimelineGenerateContext extends RundownContext implements ITimelineEventContext {
2526
readonly currentPartInstance: Readonly<IBlueprintPartInstance> | undefined
2627
readonly nextPartInstance: Readonly<IBlueprintPartInstance> | undefined
2728
readonly previousPartInstance: Readonly<IBlueprintPartInstance> | undefined
2829

30+
readonly quickLoopInfo: BlueprintQuickLookInfo | null
31+
2932
readonly abSessionsHelper: AbSessionHelper
3033
readonly #pieceInstanceCache = new Map<PieceInstanceId, ReadonlyDeep<PieceInstance>>()
3134

@@ -57,6 +60,8 @@ export class OnTimelineGenerateContext extends RundownContext implements ITimeli
5760
this.nextPartInstance = nextPartInstance && convertPartInstanceToBlueprints(nextPartInstance)
5861
this.previousPartInstance = previousPartInstance && convertPartInstanceToBlueprints(previousPartInstance)
5962

63+
this.quickLoopInfo = createBlueprintQuickLoopInfo(playlist)
64+
6065
const partInstances = _.compact([previousPartInstance, currentPartInstance, nextPartInstance])
6166

6267
for (const pieceInstance of pieceInstances) {

packages/job-worker/src/blueprints/context/adlibActions.ts

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,15 @@ import { ShowStyleUserContext } from './ShowStyleUserContext'
2323
import { WatchedPackagesHelper } from './watchedPackages'
2424
import { getCurrentTime } from '../../lib'
2525
import { JobContext, ProcessedShowStyleCompound } from '../../jobs'
26-
import { moveNextPart } from '../../playout/moveNextPart'
26+
import { selectNewPartWithOffsets } from '../../playout/moveNextPart'
2727
import { ProcessedShowStyleConfig } from '../config'
2828
import { DatastorePersistenceMode } from '@sofie-automation/shared-lib/dist/core/model/TimelineDatastore'
2929
import { removeTimelineDatastoreValue, setTimelineDatastoreValue } from '../../playout/datastore'
3030
import { executePeripheralDeviceAction, listPlayoutDevices } from '../../peripheralDevice'
3131
import { ActionPartChange, PartAndPieceInstanceActionService } from './services/PartAndPieceInstanceActionService'
3232
import { applyAndValidateOverrides } from '@sofie-automation/corelib/dist/settings/objectWithOverrides'
33+
import { BlueprintQuickLookInfo } from '@sofie-automation/blueprints-integration/dist/context/quickLoopInfo'
34+
import { setNextPartFromPart } from '../../playout/setNext'
3335

3436
export class DatastoreActionExecutionContext
3537
extends ShowStyleUserContext
@@ -72,6 +74,10 @@ export class ActionExecutionContext extends ShowStyleUserContext implements IAct
7274
*/
7375
public forceRegenerateTimeline = false
7476

77+
public get quickLoopInfo(): BlueprintQuickLookInfo | null {
78+
return this.partAndPieceInstanceService.quickLoopInfo
79+
}
80+
7581
public get currentPartState(): ActionPartChange {
7682
return this.partAndPieceInstanceService.currentPartState
7783
}
@@ -153,7 +159,8 @@ export class ActionExecutionContext extends ShowStyleUserContext implements IAct
153159
}
154160

155161
async moveNextPart(partDelta: number, segmentDelta: number): Promise<void> {
156-
await moveNextPart(this._context, this._playoutModel, partDelta, segmentDelta)
162+
const selectedPart = selectNewPartWithOffsets(this._context, this._playoutModel, partDelta, segmentDelta)
163+
if (selectedPart) await setNextPartFromPart(this._context, this._playoutModel, selectedPart, true)
157164
}
158165

159166
async updatePartInstance(

0 commit comments

Comments
 (0)