Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Move next should respect the QuickLoop bounds #31

Merged
merged 2 commits into from
Jan 7, 2025
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
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
Loading