Skip to content

Commit

Permalink
Move next should respect the QuickLoop bounds (#31)
Browse files Browse the repository at this point in the history
* feat: move next part should respect quickloop bounds

* chore: add defaults
  • Loading branch information
mint-dewit authored Jan 7, 2025
1 parent 8ca5d97 commit df01158
Show file tree
Hide file tree
Showing 17 changed files with 197 additions and 34 deletions.
4 changes: 3 additions & 1 deletion meteor/server/api/userActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -191,7 +191,8 @@ class ServerUserActionAPI
eventTime: Time,
rundownPlaylistId: RundownPlaylistId,
partDelta: number,
segmentDelta: number
segmentDelta: number,
ignoreQuickLoop: boolean | null
) {
return ServerClientAPI.runUserActionInLogForPlaylistOnWorker(
this,
Expand All @@ -208,6 +209,7 @@ class ServerUserActionAPI
playlistId: rundownPlaylistId,
partDelta: partDelta,
segmentDelta: segmentDelta,
ignoreQuickLoop: ignoreQuickLoop ?? undefined,
}
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ export const DEFAULT_CORE_TRIGGERS: IBlueprintDefaultCoreSystemTriggers = {
],
parts: 1,
segments: 0,
ignoreQuickLoop: false,
},
},
triggers: {
Expand All @@ -305,6 +306,7 @@ export const DEFAULT_CORE_TRIGGERS: IBlueprintDefaultCoreSystemTriggers = {
],
parts: 0,
segments: 1,
ignoreQuickLoop: false,
},
},
triggers: {
Expand All @@ -329,6 +331,7 @@ export const DEFAULT_CORE_TRIGGERS: IBlueprintDefaultCoreSystemTriggers = {
],
parts: -1,
segments: 0,
ignoreQuickLoop: false,
},
},
triggers: {
Expand All @@ -353,6 +356,7 @@ export const DEFAULT_CORE_TRIGGERS: IBlueprintDefaultCoreSystemTriggers = {
],
parts: 0,
segments: -1,
ignoreQuickLoop: false,
},
},
triggers: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export interface IActionExecutionContext
// getNextShowStyleConfig(): Readonly<{ [key: string]: ConfigItemValue }>

/** Move the next part through the rundown. Can move by either a number of parts, or segments in either direction. */
moveNextPart(partDelta: number, segmentDelta: number): Promise<void>
moveNextPart(partDelta: number, segmentDelta: number, ignoreQuickloop?: boolean): Promise<void>
/** Set flag to perform take after executing the current action. Returns state of the flag after each call. */
takeAfterExecuteAction(take: boolean): Promise<boolean>
/** Inform core that a take out of the current partinstance should be blocked until the specified time */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -76,5 +76,5 @@ export interface IOnSetAsNextContext extends IShowStyleUserContext, IEventContex
* Multiple calls of this inside one call to `onSetAsNext` will replace earlier calls.
* @returns Whether a new Part was found using the provided offset
*/
moveNextPart(partDelta: number, segmentDelta: number): Promise<boolean>
moveNextPart(partDelta: number, segmentDelta: number, ignoreQuickLoop?: boolean): Promise<boolean>
}
7 changes: 7 additions & 0 deletions packages/blueprints-integration/src/triggers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -230,6 +230,13 @@ export interface IMoveNextAction extends ITriggeredActionBase {
* @memberof IMoveNextAction
*/
parts: number
/**
* When moving the next part it should ignore any of the boundaries set by the QuickLoop feature
*
* @type {boolean}
* @memberof IMoveNextAction
*/
ignoreQuickLoop: boolean
}

export interface ICreateSnapshotForDebugAction extends ITriggeredActionBase {
Expand Down
1 change: 1 addition & 0 deletions packages/corelib/src/worker/studio.ts
Original file line number Diff line number Diff line change
Expand Up @@ -231,6 +231,7 @@ export interface StopPiecesOnSourceLayersProps extends RundownPlayoutPropsBase {
export interface MoveNextPartProps extends RundownPlayoutPropsBase {
partDelta: number
segmentDelta: number
ignoreQuickLoop?: boolean
}
export type ActivateHoldProps = RundownPlayoutPropsBase
export type DeactivateHoldProps = RundownPlayoutPropsBase
Expand Down
10 changes: 8 additions & 2 deletions packages/job-worker/src/blueprints/context/OnSetAsNextContext.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ export class OnSetAsNextContext
return this.partAndPieceInstanceService.removePieceInstances('next', pieceInstanceIds)
}

async moveNextPart(partDelta: number, segmentDelta: number): Promise<boolean> {
async moveNextPart(partDelta: number, segmentDelta: number, ignoreQuickLoop?: boolean): Promise<boolean> {
if (typeof partDelta !== 'number') throw new Error('partDelta must be a number')
if (typeof segmentDelta !== 'number') throw new Error('segmentDelta must be a number')

Expand All @@ -132,7 +132,13 @@ export class OnSetAsNextContext
}

this.pendingMoveNextPart = {
selectedPart: selectNewPartWithOffsets(this.jobContext, this.playoutModel, partDelta, segmentDelta),
selectedPart: selectNewPartWithOffsets(
this.jobContext,
this.playoutModel,
partDelta,
segmentDelta,
ignoreQuickLoop
),
}

return !!this.pendingMoveNextPart.selectedPart
Expand Down
10 changes: 8 additions & 2 deletions packages/job-worker/src/blueprints/context/adlibActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,8 +157,14 @@ export class ActionExecutionContext extends ShowStyleUserContext implements IAct
return this.partAndPieceInstanceService.queuePart(rawPart, rawPieces)
}

async moveNextPart(partDelta: number, segmentDelta: number): Promise<void> {
const selectedPart = selectNewPartWithOffsets(this._context, this._playoutModel, partDelta, segmentDelta)
async moveNextPart(partDelta: number, segmentDelta: number, ignoreQuickloop?: boolean): Promise<void> {
const selectedPart = selectNewPartWithOffsets(
this._context,
this._playoutModel,
partDelta,
segmentDelta,
ignoreQuickloop
)
if (selectedPart) await setNextPartFromPart(this._context, this._playoutModel, selectedPart, true)
}

Expand Down
27 changes: 19 additions & 8 deletions packages/job-worker/src/playout/model/PlayoutModel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,25 @@ export interface PlayoutModelReadonly extends StudioPlayoutModelBaseReadonly {
*/
getRundownIds(): RundownId[]

/**
* Returns any segmentId's that are found between 2 quickloop markers, none will be returned if
* the end is before the start.
* @param start A quickloop marker
* @param end A quickloop marker
*/
getSegmentsBetweenQuickLoopMarker(start: QuickLoopMarker, end: QuickLoopMarker): SegmentId[]

/**
* Returns any segmentId's that are found between 2 quickloop markers, none will be returned if
* the end is before the start.
* @param start A quickloop marker
* @param end A quickloop marker
*/
getPartsBetweenQuickLoopMarker(
start: QuickLoopMarker,
end: QuickLoopMarker
): { parts: PartId[]; segments: SegmentId[] }

/**
* Search for a PieceInstance in the RundownPlaylist
* @param id Id of the PieceInstance
Expand Down Expand Up @@ -345,14 +364,6 @@ export interface PlayoutModel extends PlayoutModelReadonly, StudioPlayoutModelBa
*/
setQuickLoopMarker(type: 'start' | 'end', marker: QuickLoopMarker | null): void

/**
* Returns any segmentId's that are found between 2 quickloop markers, none will be returned if
* the end is before the start.
* @param start A quickloop marker
* @param end A quickloop marker
*/
getSegmentsBetweenQuickLoopMarker(start: QuickLoopMarker, end: QuickLoopMarker): SegmentId[]

calculatePartTimings(
fromPartInstance: PlayoutPartInstanceModel | null,
toPartInstance: PlayoutPartInstanceModel,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -240,6 +240,16 @@ export class PlayoutModelReadonlyImpl implements PlayoutModelReadonly {
return undefined
}

getSegmentsBetweenQuickLoopMarker(start: QuickLoopMarker, end: QuickLoopMarker): SegmentId[] {
return this.quickLoopService.getSegmentsBetweenMarkers(start, end)
}
getPartsBetweenQuickLoopMarker(
start: QuickLoopMarker,
end: QuickLoopMarker
): { parts: PartId[]; segments: SegmentId[] } {
return this.quickLoopService.getPartsBetweenMarkers(start, end)
}

#isMultiGatewayMode: boolean | undefined = undefined
public get isMultiGatewayMode(): boolean {
if (this.#isMultiGatewayMode === undefined) {
Expand Down Expand Up @@ -830,10 +840,6 @@ export class PlayoutModelImpl extends PlayoutModelReadonlyImpl implements Playou
this.#playlistHasChanged = true
}

getSegmentsBetweenQuickLoopMarker(start: QuickLoopMarker, end: QuickLoopMarker): SegmentId[] {
return this.quickLoopService.getSegmentsBetweenMarkers(start, end)
}

/** Notifications */

async getAllNotifications(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
import { ForceQuickLoopAutoNext } from '@sofie-automation/shared-lib/dist/core/model/StudioSettings'
import { ReadonlyObjectDeep } from 'type-fest/source/readonly-deep'
import { DBPart } from '@sofie-automation/corelib/dist/dataModel/Part'
import { RundownId, SegmentId } from '@sofie-automation/corelib/dist/dataModel/Ids'
import { PartId, RundownId, SegmentId } from '@sofie-automation/corelib/dist/dataModel/Ids'
import { DBSegment } from '@sofie-automation/corelib/dist/dataModel/Segment'
import { PlayoutPartInstanceModel } from '../PlayoutPartInstanceModel'
import { JobContext } from '../../../jobs'
Expand Down Expand Up @@ -150,6 +150,7 @@ export class QuickLoopService {
}

getSegmentsBetweenMarkers(startMarker: QuickLoopMarker, endMarker: QuickLoopMarker): SegmentId[] {
// note - this function could be refactored to call getPartsBetweenMarkers instead but it will be less efficient
const segments = this.playoutModel.getAllOrderedSegments()
const segmentIds: SegmentId[] = []

Expand Down Expand Up @@ -201,6 +202,71 @@ export class QuickLoopService {
return segmentIds
}

getPartsBetweenMarkers(
startMarker: QuickLoopMarker,
endMarker: QuickLoopMarker
): { parts: PartId[]; segments: SegmentId[] } {
const parts = this.playoutModel.getAllOrderedParts()
const segmentIds: SegmentId[] = []
const partIds: PartId[] = []

let passedStart = false
let seenLastRundown = false
let seenLastSegment = false

for (const p of parts) {
if (
!passedStart &&
((startMarker.type === QuickLoopMarkerType.PART && p._id === startMarker.id) ||
(startMarker.type === QuickLoopMarkerType.SEGMENT && p.segmentId === startMarker.id) ||
(startMarker.type === QuickLoopMarkerType.RUNDOWN && p.rundownId === startMarker.id) ||
startMarker.type === QuickLoopMarkerType.PLAYLIST)
) {
// the start marker is this part, this is the first part in the loop, or this is the first segment that is in the loop
// segments from here on are included in the loop
passedStart = true
}

if (endMarker.type === QuickLoopMarkerType.RUNDOWN) {
// last rundown needs to be inclusive so we need to break once the rundownId is not equal to segment's rundownId
if (p.rundownId === endMarker.id) {
if (!passedStart) {
// we hit the end before the start so quit now:
break
}
seenLastRundown = true
} else if (seenLastRundown) {
// we have passed the last rundown
break
}
} else if (endMarker.type === QuickLoopMarkerType.SEGMENT) {
// last segment needs to be inclusive so we need to break once the segmentId changes but not before
if (p.segmentId === endMarker.id) {
if (!passedStart) {
// we hit the end before the start so quit now:
break
}
seenLastSegment = true
} else if (seenLastSegment) {
// we have passed the last rundown
break
}
}

if (passedStart) {
if (segmentIds.slice(-1)[0] !== p.segmentId) segmentIds.push(p.segmentId)
partIds.push(p._id)
}

if (endMarker.type === QuickLoopMarkerType.PART && p._id === endMarker.id) {
// the endMarker is this part so we can quit now
break
}
}

return { parts: partIds, segments: segmentIds }
}

private areMarkersFlipped(startPosition: MarkerPosition, endPosition: MarkerPosition) {
return compareMarkerPositions(startPosition, endPosition) < 0
}
Expand Down
36 changes: 30 additions & 6 deletions packages/job-worker/src/playout/moveNextPart.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ export function selectNewPartWithOffsets(
_context: JobContext,
playoutModel: PlayoutModelReadonly,
partDelta: number,
segmentDelta: number
segmentDelta: number,
ignoreQuickLoop = false
): ReadonlyDeep<DBPart> | null {
const playlist = playoutModel.playlist

Expand All @@ -23,8 +24,21 @@ export function selectNewPartWithOffsets(
if (!refPart || !refPartInstance)
throw new Error(`RundownPlaylist "${playlist._id}" has no next and no current part!`)

const rawSegments = playoutModel.getAllOrderedSegments()
const rawParts = playoutModel.getAllOrderedParts()
let rawSegments = playoutModel.getAllOrderedSegments()
let rawParts = playoutModel.getAllOrderedParts()
let allowWrap = false // whether we should wrap to the first part if the curIndex + delta exceeds the total number of parts

if (!ignoreQuickLoop && playlist.quickLoop?.start && playlist.quickLoop.end) {
const partsInQuickloop = playoutModel.getPartsBetweenQuickLoopMarker(
playlist.quickLoop.start,
playlist.quickLoop.end
)
if (partsInQuickloop.parts.includes(refPart._id)) {
rawParts = rawParts.filter((p) => partsInQuickloop.parts.includes(p._id))
rawSegments = rawSegments.filter((s) => partsInQuickloop.segments.includes(s.segment._id))
allowWrap = true
}
}

if (segmentDelta) {
// Ignores horizontalDelta
Expand All @@ -37,7 +51,14 @@ export function selectNewPartWithOffsets(
const refSegmentIndex = considerSegments.findIndex((s) => s.segment._id === refPart.segmentId)
if (refSegmentIndex === -1) throw new Error(`Segment "${refPart.segmentId}" not found!`)

const targetSegmentIndex = refSegmentIndex + segmentDelta
let targetSegmentIndex = refSegmentIndex + segmentDelta
if (allowWrap) {
targetSegmentIndex = targetSegmentIndex % considerSegments.length
if (targetSegmentIndex < 0) {
// -1 becomes last segment
targetSegmentIndex = considerSegments.length + targetSegmentIndex
}
}
const targetSegment = considerSegments[targetSegmentIndex]
if (!targetSegment) return null

Expand All @@ -64,7 +85,6 @@ export function selectNewPartWithOffsets(
}
}

// TODO - looping playlists
if (selectedPart) {
// Switch to that part
return selectedPart
Expand All @@ -88,7 +108,11 @@ export function selectNewPartWithOffsets(
}

// Get the past we are after
const targetPartIndex = refPartIndex + partDelta
let targetPartIndex = allowWrap ? (refPartIndex + partDelta) % playabaleParts.length : refPartIndex + partDelta
if (allowWrap) {
targetPartIndex = targetPartIndex % playabaleParts.length
if (targetPartIndex < 0) targetPartIndex = playabaleParts.length + targetPartIndex // -1 becomes last part
}
let targetPart = playabaleParts[targetPartIndex]
if (targetPart && targetPart._id === currentPartInstance?.part._id) {
// Cant go to the current part (yet)
Expand Down
8 changes: 7 additions & 1 deletion packages/job-worker/src/playout/setNextJobs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,13 @@ export async function handleMoveNextPart(context: JobContext, data: MoveNextPart
}
},
async (playoutModel) => {
const selectedPart = selectNewPartWithOffsets(context, playoutModel, data.partDelta, data.segmentDelta)
const selectedPart = selectNewPartWithOffsets(
context,
playoutModel,
data.partDelta,
data.segmentDelta,
data.ignoreQuickLoop
)
if (!selectedPart) return null

await setNextPartFromPart(context, playoutModel, selectedPart, true)
Expand Down
3 changes: 2 additions & 1 deletion packages/meteor-lib/src/api/userActions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,8 @@ export interface NewUserActionAPI {
eventTime: Time,
rundownPlaylistId: RundownPlaylistId,
partDelta: number,
segmentDelta: number
segmentDelta: number,
ignoreQuickLoop?: boolean
): Promise<ClientAPI.ClientResponse<PartId | null>>
prepareForBroadcast(
userEvent: string,
Expand Down
Loading

0 comments on commit df01158

Please sign in to comment.