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

fix: Playout ignores 'Play from here' offset SOFIE-2356 #1063

Merged
merged 2 commits into from
Nov 6, 2023
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: 2 additions & 2 deletions packages/job-worker/src/playout/__tests__/timeline.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -435,15 +435,15 @@ async function doDeactivatePlaylist(context: MockJobContext, playlistId: Rundown
}

/** perform an update of the timeline */
async function doUpdateTimeline(context: MockJobContext, playlistId: RundownPlaylistId, forceNowToTime?: Time) {
async function doUpdateTimeline(context: MockJobContext, playlistId: RundownPlaylistId) {
await runJobWithPlayoutModel(
context,
{
playlistId: playlistId,
},
null,
async (playoutModel) => {
await updateTimeline(context, playoutModel, forceNowToTime)
await updateTimeline(context, playoutModel)
}
)
}
Expand Down
2 changes: 1 addition & 1 deletion packages/job-worker/src/playout/adlibUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -277,7 +277,7 @@ export function innerStopPieces(
}

const resolvedPieces = getResolvedPiecesForCurrentPartInstance(context, sourceLayers, currentPartInstance)
const offsetRelativeToNow = (timeOffset || 0) + (calculateNowOffsetLatency(context, playoutModel, undefined) || 0)
const offsetRelativeToNow = (timeOffset || 0) + (calculateNowOffsetLatency(context, playoutModel) || 0)
const stopAt = getCurrentTime() + offsetRelativeToNow
const relativeStopAt = stopAt - lastStartedPlayback

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -184,11 +184,12 @@ export interface PlayoutPartInstanceModel {
setRank(rank: number): void

/**
* Set the PartInstance as having been taken
* Set the PartInstance as having been taken, if an offset is provided the plannedStartedPlayback of the PartInstance will be set to match,
* to force the PartInstance to have started a certain distance in the past
* @param takeTime The timestamp to record as when it was taken
* @param playOffset The offset into the PartInstance to start playback from
* @param playOffset If set, offset into the PartInstance to start playback from
*/
setTaken(takeTime: number, playOffset: number): void
setTaken(takeTime: number, playOffset: number | null): void

/**
* Define some cached values, to be done when taking the PartInstance
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -476,12 +476,18 @@ export class PlayoutPartInstanceModelImpl implements PlayoutPartInstanceModel {
this.#compareAndSetPartValue('_rank', rank)
}

setTaken(takeTime: number, playOffset: number): void {
setTaken(takeTime: number, playOffset: number | null): void {
this.#compareAndSetPartInstanceValue('isTaken', true)

const timings = { ...this.partInstanceImpl.timings }
timings.take = takeTime
timings.playOffset = playOffset
timings.playOffset = playOffset ?? 0

if (playOffset !== null) {
// Shift the startedPlayback into the past, to cause playout to start a while into the Part:
// Note: We won't use the takeTime here, since the takeTime is when we started executing the take, and we'd rather have the play-time to be Now instead
timings.plannedStartedPlayback = getCurrentTime() - playOffset
}

this.#compareAndSetPartInstanceValue('timings', timings, true)
}
Expand Down
9 changes: 4 additions & 5 deletions packages/job-worker/src/playout/take.ts
Original file line number Diff line number Diff line change
Expand Up @@ -241,7 +241,7 @@ export async function performTakeToNextedPart(

playoutModel.cycleSelectedPartInstances()

takePartInstance.setTaken(now, timeOffset ?? 0)
takePartInstance.setTaken(now, timeOffset)

resetPreviousSegment(playoutModel)

Expand All @@ -255,7 +255,7 @@ export async function performTakeToNextedPart(
) {
startHold(context, currentPartInstance, nextPartInstance)
}
await afterTake(context, playoutModel, takePartInstance, timeOffset)
await afterTake(context, playoutModel, takePartInstance)

// Last:
const takeDoneTime = getCurrentTime()
Expand Down Expand Up @@ -463,14 +463,13 @@ export function updatePartInstanceOnTake(
export async function afterTake(
context: JobContext,
playoutModel: PlayoutModel,
takePartInstance: PlayoutPartInstanceModel,
timeOffsetIntoPart: number | null = null
takePartInstance: PlayoutPartInstanceModel
): Promise<void> {
const span = context.startSpan('afterTake')
// This function should be called at the end of a "take" event (when the Parts have been updated)
// or after a new part has started playing

await updateTimeline(context, playoutModel, timeOffsetIntoPart || undefined)
await updateTimeline(context, playoutModel)

playoutModel.queueNotifyCurrentlyPlayingPartEvent(takePartInstance.partInstance.rundownId, takePartInstance)

Expand Down
8 changes: 2 additions & 6 deletions packages/job-worker/src/playout/timeline/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,11 +143,7 @@ export async function updateStudioTimeline(
if (span) span.end()
}

export async function updateTimeline(
context: JobContext,
playoutModel: PlayoutModel,
timeOffsetIntoPart?: Time
): Promise<void> {
export async function updateTimeline(context: JobContext, playoutModel: PlayoutModel): Promise<void> {
const span = context.startSpan('updateTimeline')
logger.debug('updateTimeline running...')

Expand All @@ -162,7 +158,7 @@ export async function updateTimeline(
preserveOrReplaceNowTimesInObjects(playoutModel, timelineObjs)

if (playoutModel.isMultiGatewayMode) {
deNowifyMultiGatewayTimeline(context, playoutModel, timelineObjs, timeOffsetIntoPart, timingInfo)
deNowifyMultiGatewayTimeline(context, playoutModel, timelineObjs, timingInfo)

logAnyRemainingNowTimes(context, timelineObjs)
}
Expand Down
27 changes: 15 additions & 12 deletions packages/job-worker/src/playout/timeline/multi-gateway.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,24 +25,23 @@ export function deNowifyMultiGatewayTimeline(
context: JobContext,
playoutModel: PlayoutModel,
timelineObjs: TimelineObjRundown[],
timeOffsetIntoPart: Time | undefined,
timingContext: RundownTimelineTimingContext | undefined
): void {
if (!timingContext) return

const timelineObjsMap = normalizeArray(timelineObjs, 'id')

const nowOffsetLatency = calculateNowOffsetLatency(context, playoutModel, timeOffsetIntoPart)
const nowOffsetLatency = calculateNowOffsetLatency(context, playoutModel)
const targetNowTime = getCurrentTime() + (nowOffsetLatency ?? 0)

// Replace `start: 'now'` in currentPartInstance on timeline
const currentPartInstance = playoutModel.currentPartInstance
if (!currentPartInstance) return

const partGroupTimings = updatePartInstancePlannedTimes(
playoutModel,
targetNowTime,
timingContext,
playoutModel.previousPartInstance,
currentPartInstance,
playoutModel.nextPartInstance
)
Expand All @@ -58,10 +57,13 @@ export function deNowifyMultiGatewayTimeline(
updatePlannedTimingsForPieceInstances(playoutModel, currentPartInstance, partGroupTimings, timelineObjsMap)
}

/**
* Calculate an offset to apply to the 'now' value, to compensate for delay in playout-gateway
* The intention is that any concrete value used instead of 'now' should still be just in the future for playout-gateway
*/
export function calculateNowOffsetLatency(
context: JobContext,
studioPlayoutModel: StudioPlayoutModelBase,
timeOffsetIntoPart: Time | undefined
studioPlayoutModel: StudioPlayoutModelBase
): Time | undefined {
/** The timestamp that "now" was set to */
let nowOffsetLatency: Time | undefined
Expand All @@ -76,11 +78,6 @@ export function calculateNowOffsetLatency(
nowOffsetLatency = worstLatency + ADD_SAFE_LATENCY
}

if (timeOffsetIntoPart) {
// Include the requested offset
nowOffsetLatency = (nowOffsetLatency ?? 0) - timeOffsetIntoPart
}

return nowOffsetLatency
}

Expand All @@ -90,10 +87,14 @@ interface PartGroupTimings {
nextStartTime: number | undefined
}

/**
* Update the `plannedStartedPlayback` and `plannedStoppedPlayback` for the PartInstances on the timeline,
* returning the chosen start times for each PartInstance
*/
function updatePartInstancePlannedTimes(
playoutModel: PlayoutModel,
targetNowTime: number,
timingContext: RundownTimelineTimingContext,
previousPartInstance: PlayoutPartInstanceModel | null,
currentPartInstance: PlayoutPartInstanceModel,
nextPartInstance: PlayoutPartInstanceModel | null
): PartGroupTimings {
Expand All @@ -110,7 +111,6 @@ function updatePartInstancePlannedTimes(
}

// Also mark the previous as ended
const previousPartInstance = playoutModel.previousPartInstance
if (previousPartInstance) {
const previousPartEndTime = currentPartGroupStartTime + (timingContext.previousPartOverlap ?? 0)
previousPartInstance.setPlannedStoppedPlayback(previousPartEndTime)
Expand Down Expand Up @@ -142,6 +142,9 @@ function updatePartInstancePlannedTimes(
}
}

/**
* Replace the `now` time in any Pieces on the timeline from the current Part with concrete start times
*/
function deNowifyCurrentPieces(
targetNowTime: number,
timingContext: RundownTimelineTimingContext,
Expand Down
Loading