mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-25 23:35:43 +00:00
Restore original zoom motion and shift timing 300ms earlier
This commit is contained in:
@@ -107,7 +107,7 @@ import {
|
||||
getZoomSpringConfig,
|
||||
resetSpringState,
|
||||
type SpringState,
|
||||
stepBoundedZoomSpring,
|
||||
stepSpringValue,
|
||||
} from "./videoPlayback/motionSmoothing";
|
||||
import { updateOverlayIndicator } from "./videoPlayback/overlayUtils";
|
||||
import { supportsPreviewPlaybackRate } from "./videoPlayback/playbackRate";
|
||||
@@ -2094,20 +2094,24 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(
|
||||
let appliedY: number;
|
||||
|
||||
if (motionMode === "spring" && contentAdvanced) {
|
||||
const bounded = stepBoundedZoomSpring(
|
||||
{
|
||||
scale: springScaleRef.current,
|
||||
x: springXRef.current,
|
||||
y: springYRef.current,
|
||||
},
|
||||
projectedTransform,
|
||||
appliedScale = stepSpringValue(
|
||||
springScaleRef.current,
|
||||
projectedTransform.scale,
|
||||
deltaMs,
|
||||
zoomSpringConfig,
|
||||
);
|
||||
appliedX = stepSpringValue(
|
||||
springXRef.current,
|
||||
projectedTransform.x,
|
||||
deltaMs,
|
||||
zoomSpringConfig,
|
||||
);
|
||||
appliedY = stepSpringValue(
|
||||
springYRef.current,
|
||||
projectedTransform.y,
|
||||
deltaMs,
|
||||
zoomSpringConfig,
|
||||
target.progress < 1,
|
||||
);
|
||||
appliedScale = bounded.scale;
|
||||
appliedX = bounded.x;
|
||||
appliedY = bounded.y;
|
||||
} else if (motionMode === "snap") {
|
||||
// Timeline seeks and classic mode intentionally evaluate the exact target.
|
||||
appliedScale = projectedTransform.scale;
|
||||
|
||||
@@ -317,26 +317,3 @@ export function getZoomSpringConfig(
|
||||
tuning,
|
||||
);
|
||||
}
|
||||
|
||||
/** Keep spring lag inside the zoom envelope, including an exact neutral end frame. */
|
||||
export function stepBoundedZoomSpring(
|
||||
springs: { scale: SpringState; x: SpringState; y: SpringState },
|
||||
target: { scale: number; x: number; y: number },
|
||||
deltaMs: number,
|
||||
config: SpringConfig,
|
||||
envelopeActive = true,
|
||||
) {
|
||||
const scale = stepSpringValue(springs.scale, target.scale, deltaMs, config);
|
||||
const x = stepSpringValue(springs.x, target.x, deltaMs, config);
|
||||
const y = stepSpringValue(springs.y, target.y, deltaMs, config);
|
||||
const boundedScale = Math.max(1, envelopeActive ? Math.min(target.scale, scale) : scale);
|
||||
if (target.scale <= 1 || boundedScale !== scale) {
|
||||
const ratio = scale > 1 ? (boundedScale - 1) / (scale - 1) : 0;
|
||||
const result = { scale: boundedScale, x: x * ratio, y: y * ratio };
|
||||
resetSpringState(springs.scale, result.scale);
|
||||
resetSpringState(springs.x, result.x);
|
||||
resetSpringState(springs.y, result.y);
|
||||
return result;
|
||||
}
|
||||
return { scale, x, y };
|
||||
}
|
||||
|
||||
@@ -10,7 +10,6 @@ import {
|
||||
type SpringConfig,
|
||||
type SpringState,
|
||||
stepSpringValue,
|
||||
stepBoundedZoomSpring,
|
||||
} from "./motionSmoothing";
|
||||
import { computeRegionStrength, findDominantRegion } from "./zoomRegionUtils";
|
||||
|
||||
@@ -314,24 +313,29 @@ describe("computeRegionStrength", () => {
|
||||
|
||||
it("reaches full strength during the hold phase", () => {
|
||||
// Mid-region: after zoom-in completes, before zoom-out starts
|
||||
expect(computeRegionStrength(region, 3600)).toBe(1);
|
||||
expect(computeRegionStrength(region, 3500)).toBe(1);
|
||||
});
|
||||
|
||||
it("rises smoothly during zoom-in", () => {
|
||||
const s = computeRegionStrength(region, region.startMs + 200);
|
||||
// Zoom-in transitions from leadInStart .. zoomInEnd
|
||||
// zoomInEnd = startMs + 500, leadInStart = zoomInEnd - 1500 = startMs - 1000
|
||||
// So at startMs the transition is partially done
|
||||
const s = computeRegionStrength(region, region.startMs);
|
||||
expect(s).toBeGreaterThan(0);
|
||||
expect(s).toBeLessThan(1);
|
||||
});
|
||||
|
||||
it("falls smoothly during zoom-out", () => {
|
||||
const s = computeRegionStrength(region, region.endMs - 200);
|
||||
// Sample the original zoom-out ramp shifted 300ms earlier.
|
||||
const zoomOutStart = region.endMs - 150;
|
||||
const s = computeRegionStrength(region, zoomOutStart + 700 - 300);
|
||||
expect(s).toBeGreaterThan(0);
|
||||
expect(s).toBeLessThan(1);
|
||||
});
|
||||
|
||||
it("shifts zoom timing when custom durations are provided", () => {
|
||||
const defaultStrength = computeRegionStrength(region, region.startMs + 200);
|
||||
const fasterStrength = computeRegionStrength(region, region.startMs + 200, {
|
||||
const defaultStrength = computeRegionStrength(region, region.startMs);
|
||||
const fasterStrength = computeRegionStrength(region, region.startMs, {
|
||||
zoomInDurationMs: 300,
|
||||
zoomOutDurationMs: 300,
|
||||
});
|
||||
@@ -356,7 +360,7 @@ describe("findDominantRegion", () => {
|
||||
const regions: ZoomRegion[] = [
|
||||
{ id: "a", startMs: 1000, endMs: 4000, depth: 2, focus: { cx: 0.3, cy: 0.3 } },
|
||||
];
|
||||
const result = findDominantRegion(regions, 2550);
|
||||
const result = findDominantRegion(regions, 2500);
|
||||
expect(result.region).not.toBeNull();
|
||||
expect(result.region!.id).toBe("a");
|
||||
expect(result.strength).toBe(1);
|
||||
@@ -389,7 +393,7 @@ describe("findDominantRegion", () => {
|
||||
{ id: "b", startMs: 3500, endMs: 6000, depth: 3, focus: { cx: 0.8, cy: 0.8 } },
|
||||
];
|
||||
|
||||
const result = findDominantRegion(regions, 2900, { connectZooms: true });
|
||||
const result = findDominantRegion(regions, 2800, { connectZooms: true });
|
||||
expect(result.transition).toBeNull();
|
||||
expect(result.region?.id).toBe("a");
|
||||
expect(result.strength).toBeGreaterThan(0);
|
||||
@@ -424,7 +428,7 @@ describe("findDominantRegion", () => {
|
||||
{ id: "b", startMs: 4300, endMs: 7000, depth: 3, focus: { cx: 0.7, cy: 0.7 } },
|
||||
];
|
||||
|
||||
// Between connected blocks, the next focus remains active.
|
||||
// After transition end (3000-100+1000=3900) but before b starts (4300)
|
||||
const result = findDominantRegion(regions, 4250, { connectZooms: true });
|
||||
expect(result.strength).toBe(1);
|
||||
expect(result.region).not.toBeNull();
|
||||
@@ -548,88 +552,3 @@ describe("spring damping regimes", () => {
|
||||
expect(s.value).toBeCloseTo(1, 2);
|
||||
});
|
||||
});
|
||||
|
||||
describe("zoom block boundaries", () => {
|
||||
it.each([100, 500, 3000])("keeps a %dms block and its springs inside its bounds", (length) => {
|
||||
const region: ZoomRegion = {
|
||||
id: "bounded",
|
||||
startMs: 1000,
|
||||
endMs: 1000 + length,
|
||||
depth: 2,
|
||||
focus: { cx: 0.5, cy: 0.5 },
|
||||
};
|
||||
const springs = {
|
||||
scale: createSpringState(1),
|
||||
x: createSpringState(),
|
||||
y: createSpringState(),
|
||||
};
|
||||
const config = getZoomSpringConfig(0.5);
|
||||
for (let time = 900; time <= region.endMs + 100; time += 10) {
|
||||
const strength = computeRegionStrength(region, time, {
|
||||
zoomInDurationMs: 1500,
|
||||
zoomOutDurationMs: 1000,
|
||||
});
|
||||
const frame = stepBoundedZoomSpring(
|
||||
springs,
|
||||
{ scale: 1 + strength, x: -100 * strength, y: -50 * strength },
|
||||
10,
|
||||
config,
|
||||
);
|
||||
expect(frame.scale).toBeGreaterThanOrEqual(1);
|
||||
expect(frame.scale).toBeLessThanOrEqual(1 + strength);
|
||||
if (time <= region.startMs || time >= region.endMs) {
|
||||
expect(strength).toBe(0);
|
||||
expect(frame.scale).toBe(1);
|
||||
expect(Math.abs(frame.x) + Math.abs(frame.y)).toBe(0);
|
||||
}
|
||||
}
|
||||
const peak = region.startMs + length * 0.6;
|
||||
expect(
|
||||
computeRegionStrength(region, peak, {
|
||||
zoomInDurationMs: 6000,
|
||||
zoomOutDurationMs: 4000,
|
||||
}),
|
||||
).toBeCloseTo(1);
|
||||
expect(
|
||||
computeRegionStrength(region, peak - 0.001, {
|
||||
zoomInDurationMs: 6000,
|
||||
zoomOutDurationMs: 4000,
|
||||
}),
|
||||
).toBeCloseTo(1);
|
||||
expect(
|
||||
computeRegionStrength(region, peak + 0.001, {
|
||||
zoomInDurationMs: 6000,
|
||||
zoomOutDurationMs: 4000,
|
||||
}),
|
||||
).toBeCloseTo(1);
|
||||
});
|
||||
|
||||
it("bounds a connected chain to its first start and last end", () => {
|
||||
const regions: ZoomRegion[] = [
|
||||
{ id: "a", startMs: 1000, endMs: 1100, depth: 2, focus: { cx: 0.5, cy: 0.5 } },
|
||||
{ id: "b", startMs: 1400, endMs: 1500, depth: 2, focus: { cx: 0.5, cy: 0.5 } },
|
||||
];
|
||||
for (const time of [999, 1000, 1500, 1501])
|
||||
expect(findDominantRegion(regions, time, { connectZooms: true }).strength).toBe(0);
|
||||
expect(findDominantRegion(regions, 1100, { connectZooms: true }).strength).toBe(1);
|
||||
expect(findDominantRegion(regions, 1250, { connectZooms: false }).strength).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
it("preserves spring interpolation when connected zooms change depth at full strength", () => {
|
||||
const springs = {
|
||||
scale: createSpringState(3),
|
||||
x: createSpringState(-200),
|
||||
y: createSpringState(-100),
|
||||
};
|
||||
for (const state of Object.values(springs)) state.initialized = true;
|
||||
const frame = stepBoundedZoomSpring(
|
||||
springs,
|
||||
{ scale: 2, x: -100, y: -50 },
|
||||
16,
|
||||
getZoomSpringConfig(0.5),
|
||||
false,
|
||||
);
|
||||
expect(frame.scale).toBeGreaterThan(2);
|
||||
expect(frame.scale).toBeLessThan(3);
|
||||
});
|
||||
|
||||
@@ -1,10 +1,18 @@
|
||||
import type { ZoomFocus, ZoomRegion } from "../types";
|
||||
import { ZOOM_DEPTH_SCALES } from "../types";
|
||||
import { TRANSITION_WINDOW_MS, ZOOM_IN_TRANSITION_WINDOW_MS } from "./constants";
|
||||
import {
|
||||
TRANSITION_WINDOW_MS,
|
||||
ZOOM_IN_TRANSITION_WINDOW_MS,
|
||||
ZOOM_OUT_EARLY_START_MS,
|
||||
} from "./constants";
|
||||
import { clampFocusToScale } from "./focusUtils";
|
||||
import { clamp01, easeOutZoom } from "./mathUtils";
|
||||
|
||||
const CHAINED_ZOOM_PAN_GAP_MS = 1350;
|
||||
const CONNECTED_ZOOM_PAN_DURATION_MS = 1000;
|
||||
const ZOOM_IN_OVERLAP_MS = 1000;
|
||||
// Shift the original animation timing 300ms earlier without changing its motion.
|
||||
const ZOOM_ANIMATION_LEAD_MS = -100;
|
||||
|
||||
type DominantRegionOptions = {
|
||||
connectZooms?: boolean;
|
||||
@@ -16,6 +24,7 @@ type ConnectedRegionPair = {
|
||||
currentRegion: ZoomRegion;
|
||||
nextRegion: ZoomRegion;
|
||||
transitionStart: number;
|
||||
transitionEnd: number;
|
||||
};
|
||||
|
||||
type ConnectedPanTransition = {
|
||||
@@ -33,18 +42,34 @@ export function computeRegionStrength(
|
||||
) {
|
||||
const zoomInDurationMs = Math.max(1, options.zoomInDurationMs ?? ZOOM_IN_TRANSITION_WINDOW_MS);
|
||||
const zoomOutDurationMs = Math.max(1, options.zoomOutDurationMs ?? TRANSITION_WINDOW_MS);
|
||||
const length = region.endMs - region.startMs;
|
||||
if (length <= 0 || timeMs <= region.startMs || timeMs >= region.endMs) return 0;
|
||||
const adjustedTimeMs = timeMs - ZOOM_ANIMATION_LEAD_MS;
|
||||
const leadInStart = region.startMs + ZOOM_IN_OVERLAP_MS - ZOOM_IN_TRANSITION_WINDOW_MS;
|
||||
let zoomOutStart = region.endMs - ZOOM_OUT_EARLY_START_MS;
|
||||
let zoomInEnd = leadInStart + zoomInDurationMs;
|
||||
|
||||
// Short blocks proportionally compress both ramps, with no discontinuity at
|
||||
// their meeting point. Every duration setting stays inside the block.
|
||||
const fit = Math.min(1, length / (zoomInDurationMs + zoomOutDurationMs));
|
||||
const inDuration = zoomInDurationMs * fit;
|
||||
const outDuration = zoomOutDurationMs * fit;
|
||||
if (timeMs < region.startMs + inDuration) {
|
||||
return easeOutZoom((timeMs - region.startMs) / inDuration);
|
||||
if (zoomInEnd > zoomOutStart) {
|
||||
const midpoint = (zoomInEnd + zoomOutStart) / 2;
|
||||
zoomInEnd = midpoint;
|
||||
zoomOutStart = midpoint;
|
||||
}
|
||||
return 1 - easeOutZoom(clamp01((timeMs - (region.endMs - outDuration)) / outDuration));
|
||||
|
||||
const leadOutEnd = zoomOutStart + zoomOutDurationMs;
|
||||
|
||||
if (adjustedTimeMs < leadInStart || adjustedTimeMs > leadOutEnd) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (adjustedTimeMs < zoomInEnd) {
|
||||
const progress = (adjustedTimeMs - leadInStart) / zoomInDurationMs;
|
||||
return easeOutZoom(progress);
|
||||
}
|
||||
|
||||
if (adjustedTimeMs <= zoomOutStart) {
|
||||
return 1;
|
||||
}
|
||||
|
||||
const progress = clamp01((adjustedTimeMs - zoomOutStart) / zoomOutDurationMs);
|
||||
return 1 - easeOutZoom(progress);
|
||||
}
|
||||
|
||||
function getResolvedFocus(region: ZoomRegion, zoomScale: number): ZoomFocus {
|
||||
@@ -60,14 +85,16 @@ function getConnectedRegionPairs(regions: ZoomRegion[]) {
|
||||
const nextRegion = sortedRegions[index + 1];
|
||||
const gapMs = nextRegion.startMs - currentRegion.endMs;
|
||||
|
||||
if (gapMs < 0 || gapMs > CHAINED_ZOOM_PAN_GAP_MS) {
|
||||
if (gapMs > CHAINED_ZOOM_PAN_GAP_MS) {
|
||||
continue;
|
||||
}
|
||||
|
||||
pairs.push({
|
||||
currentRegion,
|
||||
nextRegion,
|
||||
transitionStart: currentRegion.endMs,
|
||||
transitionStart: currentRegion.endMs + ZOOM_ANIMATION_LEAD_MS,
|
||||
transitionEnd:
|
||||
currentRegion.endMs + ZOOM_ANIMATION_LEAD_MS + CONNECTED_ZOOM_PAN_DURATION_MS,
|
||||
});
|
||||
}
|
||||
|
||||
@@ -83,30 +110,33 @@ function getActiveRegion(
|
||||
const activeRegions = regions
|
||||
.map((region) => {
|
||||
const outgoingPair = connectedPairs.find((pair) => pair.currentRegion.id === region.id);
|
||||
if (outgoingPair) {
|
||||
if (timeMs >= outgoingPair.transitionStart) {
|
||||
return { region, strength: 0 };
|
||||
}
|
||||
|
||||
const zoomOutStart =
|
||||
outgoingPair.currentRegion.endMs -
|
||||
ZOOM_OUT_EARLY_START_MS +
|
||||
ZOOM_ANIMATION_LEAD_MS;
|
||||
if (timeMs >= zoomOutStart) {
|
||||
return { region, strength: 1 };
|
||||
}
|
||||
}
|
||||
|
||||
const incomingPair = connectedPairs.find((pair) => pair.nextRegion.id === region.id);
|
||||
const start = incomingPair?.transitionStart ?? region.startMs;
|
||||
if (timeMs < start || (!incomingPair && timeMs === start) || timeMs >= region.endMs)
|
||||
return { region, strength: 0 };
|
||||
if (incomingPair || outgoingPair) {
|
||||
const inDuration = Math.max(
|
||||
1,
|
||||
options.zoomInDurationMs ?? ZOOM_IN_TRANSITION_WINDOW_MS,
|
||||
);
|
||||
const outDuration = Math.max(1, options.zoomOutDurationMs ?? TRANSITION_WINDOW_MS);
|
||||
const total = (incomingPair ? 0 : inDuration) + (outgoingPair ? 0 : outDuration);
|
||||
const fit = total > 0 ? Math.min(1, (region.endMs - start) / total) : 1;
|
||||
const strengthIn = incomingPair
|
||||
? 1
|
||||
: easeOutZoom(clamp01((timeMs - start) / (inDuration * fit)));
|
||||
const strengthOut = outgoingPair
|
||||
? 1
|
||||
: 1 -
|
||||
easeOutZoom(
|
||||
clamp01(
|
||||
(timeMs - (region.endMs - outDuration * fit)) / (outDuration * fit),
|
||||
),
|
||||
);
|
||||
return { region, strength: Math.min(strengthIn, strengthOut) };
|
||||
if (incomingPair) {
|
||||
if (timeMs < incomingPair.transitionStart) {
|
||||
return { region, strength: 0 };
|
||||
}
|
||||
|
||||
const nextRegionZoomOutStart =
|
||||
incomingPair.nextRegion.endMs -
|
||||
ZOOM_OUT_EARLY_START_MS +
|
||||
ZOOM_ANIMATION_LEAD_MS;
|
||||
if (timeMs < nextRegionZoomOutStart) {
|
||||
return { region, strength: 1 };
|
||||
}
|
||||
}
|
||||
|
||||
return { region, strength: computeRegionStrength(region, timeMs, options) };
|
||||
@@ -137,6 +167,24 @@ function getActiveRegion(
|
||||
};
|
||||
}
|
||||
|
||||
function getConnectedRegionHold(timeMs: number, connectedPairs: ConnectedRegionPair[]) {
|
||||
for (const pair of connectedPairs) {
|
||||
if (timeMs >= pair.transitionEnd && timeMs < pair.nextRegion.startMs) {
|
||||
const nextScale = ZOOM_DEPTH_SCALES[pair.nextRegion.depth];
|
||||
return {
|
||||
region: {
|
||||
...pair.nextRegion,
|
||||
focus: getResolvedFocus(pair.nextRegion, nextScale),
|
||||
},
|
||||
strength: 1,
|
||||
blendedScale: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
export function findDominantRegion(
|
||||
regions: ZoomRegion[],
|
||||
timeMs: number,
|
||||
@@ -149,6 +197,13 @@ export function findDominantRegion(
|
||||
} {
|
||||
const connectedPairs = options.connectZooms ? getConnectedRegionPairs(regions) : [];
|
||||
|
||||
if (options.connectZooms) {
|
||||
const connectedHold = getConnectedRegionHold(timeMs, connectedPairs);
|
||||
if (connectedHold) {
|
||||
return { ...connectedHold, transition: null };
|
||||
}
|
||||
}
|
||||
|
||||
const activeRegion = getActiveRegion(regions, timeMs, connectedPairs, options);
|
||||
return activeRegion
|
||||
? { ...activeRegion, transition: null }
|
||||
|
||||
@@ -40,14 +40,11 @@ import {
|
||||
getZoomSpringConfig,
|
||||
resetSpringState,
|
||||
type SpringState,
|
||||
stepBoundedZoomSpring,
|
||||
stepSpringValue,
|
||||
} from "@/components/video-editor/videoPlayback/motionSmoothing";
|
||||
import { getSceneEffectMetrics } from "@/components/video-editor/videoPlayback/sceneEffects";
|
||||
import { resolveSceneZoomTarget } from "@/components/video-editor/videoPlayback/sceneMotion";
|
||||
import {
|
||||
getWebcamMediaTargetTimeSeconds,
|
||||
isWebcamVisibleAtSourceTime,
|
||||
} from "@/components/video-editor/videoPlayback/webcamSync";
|
||||
import { getWebcamMediaTargetTimeSeconds, isWebcamVisibleAtSourceTime } from "@/components/video-editor/videoPlayback/webcamSync";
|
||||
import {
|
||||
applyZoomTransform,
|
||||
computeZoomTransform,
|
||||
@@ -79,6 +76,7 @@ import { renderCaptions } from "./captionRenderer";
|
||||
import { ForwardFrameSource } from "./forwardFrameSource";
|
||||
import { resolveMediaElementSource } from "./localMediaSource";
|
||||
|
||||
|
||||
interface FrameRenderConfig {
|
||||
timelineEffects?: boolean;
|
||||
width: number;
|
||||
@@ -1651,16 +1649,24 @@ export class FrameRenderer {
|
||||
resetSpringState(this.springX, state.x);
|
||||
resetSpringState(this.springY, state.y);
|
||||
} else {
|
||||
const bounded = stepBoundedZoomSpring(
|
||||
{ scale: this.springScale, x: this.springX, y: this.springY },
|
||||
projectedTransform,
|
||||
state.appliedScale = stepSpringValue(
|
||||
this.springScale,
|
||||
projectedTransform.scale,
|
||||
deltaMs,
|
||||
zoomSpringConfig,
|
||||
);
|
||||
state.x = stepSpringValue(
|
||||
this.springX,
|
||||
projectedTransform.x,
|
||||
deltaMs,
|
||||
zoomSpringConfig,
|
||||
);
|
||||
state.y = stepSpringValue(
|
||||
this.springY,
|
||||
projectedTransform.y,
|
||||
deltaMs,
|
||||
zoomSpringConfig,
|
||||
target.progress < 1,
|
||||
);
|
||||
state.appliedScale = bounded.scale;
|
||||
state.x = bounded.x;
|
||||
state.y = bounded.y;
|
||||
}
|
||||
|
||||
return Math.max(
|
||||
@@ -1733,11 +1739,7 @@ export class FrameRenderer {
|
||||
const webcam = this.config.webcam;
|
||||
const webcamDecodedFrame = this.webcamDecodedFrame;
|
||||
const webcamVideo = this.webcamVideoElement;
|
||||
if (
|
||||
!webcam?.enabled ||
|
||||
!isWebcamVisibleAtSourceTime(webcam, this.currentVideoTime) ||
|
||||
(!webcamDecodedFrame && !webcamVideo)
|
||||
) {
|
||||
if (!webcam?.enabled || !isWebcamVisibleAtSourceTime(webcam, this.currentVideoTime) || (!webcamDecodedFrame && !webcamVideo)) {
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -49,14 +49,11 @@ import {
|
||||
getZoomSpringConfig,
|
||||
resetSpringState,
|
||||
type SpringState,
|
||||
stepBoundedZoomSpring,
|
||||
stepSpringValue,
|
||||
} from "@/components/video-editor/videoPlayback/motionSmoothing";
|
||||
import { getSceneEffectMetrics } from "@/components/video-editor/videoPlayback/sceneEffects";
|
||||
import { resolveSceneZoomTarget } from "@/components/video-editor/videoPlayback/sceneMotion";
|
||||
import {
|
||||
getWebcamMediaTargetTimeSeconds,
|
||||
isWebcamVisibleAtSourceTime,
|
||||
} from "@/components/video-editor/videoPlayback/webcamSync";
|
||||
import { getWebcamMediaTargetTimeSeconds, isWebcamVisibleAtSourceTime } from "@/components/video-editor/videoPlayback/webcamSync";
|
||||
import {
|
||||
applyZoomTransform,
|
||||
computeZoomTransform,
|
||||
@@ -2728,12 +2725,7 @@ export class FrameRenderer {
|
||||
|
||||
private updateWebcamOverlay(referenceTimeSeconds = this.currentVideoTime): void {
|
||||
const webcam = this.config.webcam;
|
||||
if (
|
||||
!webcam?.enabled ||
|
||||
!isWebcamVisibleAtSourceTime(webcam, referenceTimeSeconds) ||
|
||||
!this.webcamRootContainer ||
|
||||
!this.webcamMaskGraphics
|
||||
) {
|
||||
if (!webcam?.enabled || !isWebcamVisibleAtSourceTime(webcam, referenceTimeSeconds) || !this.webcamRootContainer || !this.webcamMaskGraphics) {
|
||||
if (this.webcamRootContainer) {
|
||||
this.webcamRootContainer.visible = false;
|
||||
}
|
||||
@@ -3152,16 +3144,24 @@ export class FrameRenderer {
|
||||
resetSpringState(this.springX, state.x);
|
||||
resetSpringState(this.springY, state.y);
|
||||
} else {
|
||||
const bounded = stepBoundedZoomSpring(
|
||||
{ scale: this.springScale, x: this.springX, y: this.springY },
|
||||
projectedTransform,
|
||||
state.appliedScale = stepSpringValue(
|
||||
this.springScale,
|
||||
projectedTransform.scale,
|
||||
deltaMs,
|
||||
zoomSpringConfig,
|
||||
);
|
||||
state.x = stepSpringValue(
|
||||
this.springX,
|
||||
projectedTransform.x,
|
||||
deltaMs,
|
||||
zoomSpringConfig,
|
||||
);
|
||||
state.y = stepSpringValue(
|
||||
this.springY,
|
||||
projectedTransform.y,
|
||||
deltaMs,
|
||||
zoomSpringConfig,
|
||||
target.progress < 1,
|
||||
);
|
||||
state.appliedScale = bounded.scale;
|
||||
state.x = bounded.x;
|
||||
state.y = bounded.y;
|
||||
}
|
||||
|
||||
return Math.max(
|
||||
|
||||
Reference in New Issue
Block a user