Skip to content

Commit

Permalink
feat: speed and re-rendering optimisations
Browse files Browse the repository at this point in the history
  • Loading branch information
olzzon committed Jun 7, 2021
1 parent 6df9784 commit a0a0d6c
Show file tree
Hide file tree
Showing 8 changed files with 94 additions and 71 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,9 @@ export const RundownTimingProvider = withTracker<
private temporaryPartInstances: Map<PartId, PartInstance> = new Map<PartId, PartInstance>()

private linearParts: Array<[PartId, number | null]> = []
private prevPartId: string = ''
private lastTakeAt: number | undefined = undefined

// look at the comments on RundownTimingContext to understand what these do
private partDurations: Record<string, number> = {}
private partExpectedDurations: Record<string, number> = {}
Expand All @@ -145,8 +148,12 @@ export const RundownTimingProvider = withTracker<
}
}

calmDownTiming = (time: number) => {
return Math.round(time / 40) * 40
}

onRefreshTimer = () => {
const now = getCurrentTime()
const now = this.calmDownTiming(getCurrentTime())
const isLowResolution = this.refreshDecimator % LOW_RESOLUTION_TIMING_DECIMATOR === 0
this.updateDurations(now, isLowResolution)
this.dispatchHREvent(now)
Expand Down Expand Up @@ -412,7 +419,22 @@ export const RundownTimingProvider = withTracker<
this.partDisplayDurations[partInstancePartId] = partDisplayDuration
this.partDisplayDurationsNoPlayback[partInstancePartId] = partDisplayDurationNoPlayback
startsAtAccumulator += this.partDurations[partInstancePartId]
displayStartsAtAccumulator += this.partDisplayDurations[partInstancePartId] // || this.props.defaultDuration || 3000

if (playlist.previousPartInstanceId !== partInstance._id) {
displayStartsAtAccumulator += this.partDisplayDurations[partInstancePartId]
} else {
if (this.prevPartId !== unprotectString(playlist.previousPartInstanceId)) {
this.lastTakeAt = now
this.prevPartId = unprotectString(playlist.previousPartInstanceId) || ''
}
let durationToTake =
this.lastTakeAt && lastStartedPlayback
? this.lastTakeAt - lastStartedPlayback
: this.partDisplayDurations[partInstancePartId]
this.partDisplayDurations[partInstancePartId] = durationToTake
displayStartsAtAccumulator += durationToTake
}

// waitAccumulator is used to calculate the countdowns for Parts relative to the current Part
// always add the full duration, in case by some manual intervention this segment should play twice
if (memberOfDisplayDurationGroup) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -141,7 +141,7 @@ export class CustomLayerItemRenderer<
style={{
left: this.props.relative
? (((vtContent.sourceDuration - seek) / (this.getItemDuration() || 1)) * 100).toString() + '%'
: ((vtContent.sourceDuration - seek) * this.props.timeScale).toString() + 'px',
: Math.round((vtContent.sourceDuration - seek) * this.props.timeScale).toString() + 'px',
}}
></div>
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,10 +46,10 @@ export const MicSourceRenderer = withTranslation()(
this.itemPosition = this.itemElement.offsetLeft
const content = this.props.piece.instance.piece.content as ScriptContent | undefined
if (content && content.sourceDuration) {
const scriptReadTime = content.sourceDuration * this.props.timeScale
const scriptReadTime = Math.round(content.sourceDuration * this.props.timeScale)
this.readTime = content.sourceDuration
const positionByReadTime = this.itemPosition + scriptReadTime
const positionByPartEnd = this.props.partDuration * this.props.timeScale
const positionByPartEnd = Math.round(this.props.partDuration * this.props.timeScale)

if (
positionByReadTime !== this.linePosition ||
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -593,7 +593,7 @@ export class VTSourceRendererBase extends CustomLayerItemRenderer<IProps & WithT
<span
className="segment-timeline__piece__scene-marker"
key={i}
style={{ left: ((i - seek) * this.props.timeScale).toString() + 'px' }}
style={{ left: Math.round((i - seek) * this.props.timeScale).toString() + 'px' }}
></span>
)
)}
Expand All @@ -606,9 +606,9 @@ export class VTSourceRendererBase extends CustomLayerItemRenderer<IProps & WithT
className="segment-timeline__piece__anomaly-marker"
key={i.start}
style={{
left: ((i.start - seek) * this.props.timeScale).toString() + 'px',
left: Math.round((i.start - seek) * this.props.timeScale).toString() + 'px',
width:
(Math.min(itemDuration - i.start + seek, i.duration) * this.props.timeScale).toString() +
Math.round(Math.min(itemDuration - i.start + seek, i.duration) * this.props.timeScale).toString() +
'px',
}}
></span>
Expand Down
15 changes: 12 additions & 3 deletions meteor/client/ui/SegmentTimeline/SegmentTimeline.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,10 @@ const SegmentTimelineZoom = class SegmentTimelineZoom extends React.Component<
return this.props.isLiveSegment ? this.calculateSegmentDuration() : this.state.totalSegmentDuration
}

calcTimeScale = (time: number) => {
return Math.round(this.props.timeScale * time)
}

renderZoomTimeline() {
return this.props.parts.map((part, index, array) => {
return (
Expand Down Expand Up @@ -360,6 +364,10 @@ export class SegmentTimelineClass extends React.Component<Translated<IProps>, IS
this.timeline = el
}

calcTimeScale = (time: number) => {
return Math.round(this.props.timeScale * time)
}

onTimelineResize = (size: number[]) => {
this.setState({
timelineWidth: size[0],
Expand Down Expand Up @@ -627,7 +635,7 @@ export class SegmentTimelineClass extends React.Component<Translated<IProps>, IS
timelineStyle() {
return {
transform:
'translate3d(-' + Math.floor(this.props.scrollLeft * this.props.timeScale).toString() + 'px, 0, 0.1px)',
'translate3d(-' + this.calcTimeScale(this.props.scrollLeft).toString() + 'px, 0, 0.1px)',
willChange: 'transform',
}
}
Expand All @@ -638,9 +646,10 @@ export class SegmentTimelineClass extends React.Component<Translated<IProps>, IS
if (this.props.isLiveSegment) {
const historyTimeDuration = this.props.liveLineHistorySize / this.props.timeScale


let pixelPostion = Math.floor(
this.props.livePosition * this.props.timeScale -
(!this.props.followLiveLine ? this.props.scrollLeft * this.props.timeScale : 0)
this.calcTimeScale(this.props.livePosition) -
(!this.props.followLiveLine ? this.calcTimeScale(this.props.scrollLeft) : 0)
)
let lineStyle = {
left:
Expand Down
23 changes: 5 additions & 18 deletions meteor/client/ui/SegmentTimeline/SegmentTimelineContainer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -780,15 +780,13 @@ export const SegmentTimelineContainer = translateWithTracker<IProps, IState, ITr
const currentLivePartInstance = this.state.currentLivePart.instance
const currentLivePart = currentLivePartInstance.part

let simulationPercentage = this.playbackSimulationPercentage
const partOffset =
(this.context.durations &&
this.context.durations.partDisplayStartsAt &&
this.context.durations.partDisplayStartsAt[unprotectString(currentLivePart._id)] -
this.context.durations.partDisplayStartsAt[unprotectString(this.props.parts[0].instance.part._id)]) ||
0

let isExpectedToPlay = !!currentLivePartInstance.timings?.startedPlayback
const lastTake = currentLivePartInstance.timings?.take
const lastStartedPlayback = currentLivePartInstance.timings?.startedPlayback
const lastTakeOffset = currentLivePartInstance.timings?.playOffset || 0
Expand All @@ -798,27 +796,12 @@ export const SegmentTimelineContainer = translateWithTracker<IProps, IState, ITr
: lastStartedPlayback
? lastStartedPlayback - lastTakeOffset
: undefined
if (lastTake && lastTake + SIMULATED_PLAYBACK_HARD_MARGIN > e.detail.currentTime) {
isExpectedToPlay = true

// If we are between the SOFT_MARGIN and HARD_MARGIN and the take timing has already flowed through
if (lastStartedPlayback && lastTake + SIMULATED_PLAYBACK_SOFT_MARGIN < e.detail.currentTime) {
if (lastTake < lastStartedPlayback && simulationPercentage < 1) {
virtualStartedPlayback =
simulationPercentage * lastStartedPlayback + (1 - simulationPercentage) * lastTake
}
}
}

let newLivePosition =
isExpectedToPlay && virtualStartedPlayback
virtualStartedPlayback
? partOffset + e.detail.currentTime - virtualStartedPlayback + lastTakeOffset
: partOffset + lastTakeOffset

if (lastStartedPlayback && simulationPercentage < 1) {
this.playbackSimulationPercentage = Math.min(simulationPercentage + SIMULATED_PLAYBACK_CROSSFADE_STEP, 1)
}

this.setState({
livePosition: newLivePosition,
scrollLeft: this.state.followLiveLine
Expand All @@ -837,6 +820,10 @@ export const SegmentTimelineContainer = translateWithTracker<IProps, IState, ITr
}
}

calcTimeScale = (time: number) => {
return Math.round(this.props.timeScale * time)
}

startLive = () => {
window.addEventListener(RundownTiming.Events.timeupdateHR, this.onAirLineRefresh)
// As of Chrome 76, IntersectionObserver rootMargin works in screen pixels when root
Expand Down
58 changes: 31 additions & 27 deletions meteor/client/ui/SegmentTimeline/SegmentTimelinePart.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -436,6 +436,7 @@ interface IState {
isLive: boolean
isNext: boolean
isDurationSettling: boolean
durationSettlingStartsAt: number
liveDuration: number

isInsideViewport: boolean
Expand Down Expand Up @@ -468,6 +469,7 @@ export class SegmentTimelinePartClass extends React.Component<Translated<WithTim
isLive,
isNext,
isDurationSettling: false,
durationSettlingStartsAt: 0,
isInsideViewport: false,
isTooSmallForText: false,
isTooSmallForDisplay: false,
Expand Down Expand Up @@ -509,6 +511,15 @@ export class SegmentTimelinePartClass extends React.Component<Translated<WithTim
!!startedPlayback &&
!nextProps.part.instance.timings?.duration

let durationSettlingStartsAt = nextState.durationSettlingStartsAt
if (!nextState.isDurationSettling && isDurationSettling) {
durationSettlingStartsAt = SegmentTimelinePartClass.getCurrentLiveLinePosition(
nextProps.part,
nextProps.timingDurations.currentTime || getCurrentTime()
)
//console.log('Start Duration Settling in Part : ', nextState.partId)
}

let liveDuration = 0
if (!isDurationSettling) {
// if the duration isn't settling, calculate the live line postion and add some liveLive time padding
Expand All @@ -532,26 +543,10 @@ export class SegmentTimelinePartClass extends React.Component<Translated<WithTim
: 0
)
}
} else {
// if the duration is settling, just calculate the current liveLine position and show without any padding
if (!nextProps.autoNextPart && !nextPartInner.autoNext) {
liveDuration = Math.max(
(startedPlayback &&
nextProps.timingDurations.partDurations &&
SegmentTimelinePartClass.getCurrentLiveLinePosition(
nextProps.part,
nextProps.timingDurations.currentTime || getCurrentTime()
)) ||
0,
nextProps.timingDurations.partDurations
? nextPartInner.displayDuration ||
nextProps.timingDurations.partDurations[unprotectString(nextPartInner._id)]
: 0
)
}
durationSettlingStartsAt = 0
}

const partDisplayDuration = SegmentTimelinePartClass.getPartDuration(nextProps, liveDuration)
const partDisplayDuration = SegmentTimelinePartClass.getPartDuration(nextProps, liveDuration, isDurationSettling, durationSettlingStartsAt)

const isInsideViewport =
nextProps.relative ||
Expand All @@ -572,6 +567,7 @@ export class SegmentTimelinePartClass extends React.Component<Translated<WithTim
isLive,
isNext,
isDurationSettling,
durationSettlingStartsAt,
liveDuration,
isInsideViewport,
isTooSmallForText,
Expand Down Expand Up @@ -621,7 +617,7 @@ export class SegmentTimelinePartClass extends React.Component<Translated<WithTim
this.props.onPartTooSmallChanged &&
this.props.onPartTooSmallChanged(
this.props.part,
SegmentTimelinePartClass.getPartDuration(this.props, this.state.liveDuration)
SegmentTimelinePartClass.getPartDuration(this.props, this.state.liveDuration, this.state.isDurationSettling, this.state.durationSettlingStartsAt)
)
}
}
Expand All @@ -648,13 +644,13 @@ export class SegmentTimelinePartClass extends React.Component<Translated<WithTim
this.props.onPartTooSmallChanged &&
this.props.onPartTooSmallChanged(
this.props.part,
tooSmallState ? SegmentTimelinePartClass.getPartDuration(this.props, this.state.liveDuration) : false
tooSmallState ? SegmentTimelinePartClass.getPartDuration(this.props, this.state.liveDuration, this.state.isDurationSettling, this.state.durationSettlingStartsAt) : false
)
}
}

getLayerStyle() {
const actualPartDuration = SegmentTimelinePartClass.getPartDuration(this.props, this.state.liveDuration)
const actualPartDuration = SegmentTimelinePartClass.getPartDuration(this.props, this.state.liveDuration, this.state.isDurationSettling, this.state.durationSettlingStartsAt)
const partDuration = this.props.cropDuration
? Math.min(this.props.cropDuration, actualPartDuration)
: actualPartDuration
Expand All @@ -665,7 +661,7 @@ export class SegmentTimelinePartClass extends React.Component<Translated<WithTim
}
} else {
return {
minWidth: Math.floor(partDuration * this.props.timeScale).toString() + 'px',
minWidth: Math.round(partDuration * this.props.timeScale).toString() + 'px',
willChange: this.state.isLive ? 'minWidth' : undefined,
}
}
Expand All @@ -681,7 +677,15 @@ export class SegmentTimelinePartClass extends React.Component<Translated<WithTim
)
}

static getPartDuration(props: WithTiming<IProps>, liveDuration: number): number {
static getPartDuration(
props: WithTiming<IProps>,
liveDuration: number,
isDurationSettling: boolean,
durationSettlingStartsAt: number
): number {
if (isDurationSettling) {
return durationSettlingStartsAt
}
return Math.max(
!props.isPreview ? liveDuration : 0,
SegmentTimelinePartClass.getPartDisplayDuration(props.part, props.timingDurations)
Expand Down Expand Up @@ -756,9 +760,9 @@ export class SegmentTimelinePartClass extends React.Component<Translated<WithTim
this.props.cropDuration
? Math.min(
this.props.cropDuration,
SegmentTimelinePartClass.getPartDuration(this.props, this.state.liveDuration)
SegmentTimelinePartClass.getPartDuration(this.props, this.state.liveDuration, this.state.isDurationSettling, this.state.durationSettlingStartsAt)
)
: SegmentTimelinePartClass.getPartDuration(this.props, this.state.liveDuration)
: SegmentTimelinePartClass.getPartDuration(this.props, this.state.liveDuration, this.state.isDurationSettling, this.state.durationSettlingStartsAt)
}
expectedDuration={SegmentTimelinePartClass.getPartExpectedDuration(this.props)}
isLiveLine={this.props.playlist.currentPartInstanceId === part.instance._id}
Expand Down Expand Up @@ -947,10 +951,10 @@ export class SegmentTimelinePartClass extends React.Component<Translated<WithTim
style={{
left: this.props.relative
? (this.props.playlist.nextTimeOffset /
(SegmentTimelinePartClass.getPartDuration(this.props, this.state.liveDuration) || 1)) *
(SegmentTimelinePartClass.getPartDuration(this.props, this.state.liveDuration, this.state.isDurationSettling, this.state.durationSettlingStartsAt) || 1)) *
100 +
'%'
: this.props.playlist.nextTimeOffset * this.props.timeScale + 'px',
: Math.round(this.props.playlist.nextTimeOffset * this.props.timeScale) + 'px',
}}
>
<div
Expand Down
Loading

0 comments on commit a0a0d6c

Please sign in to comment.