mirror of
https://github.com/webadderallorg/Recordly.git
synced 2026-09-25 07:16:02 +00:00
fix(webcam): correct sync offset direction
This commit is contained in:
@@ -124,7 +124,7 @@ import { clampFocusToStage as clampFocusToStageUtil } from "./videoPlayback/focu
|
||||
import { layoutVideoContent as layoutVideoContentUtil } from "./videoPlayback/layoutUtils";
|
||||
import { updateOverlayIndicator } from "./videoPlayback/overlayUtils";
|
||||
import { createVideoEventHandlers } from "./videoPlayback/videoEventHandlers";
|
||||
import { getWebcamPreviewTargetTimeSeconds } from "./videoPlayback/webcamSync";
|
||||
import { getWebcamMediaTargetTimeSeconds } from "./videoPlayback/webcamSync";
|
||||
import { findDominantRegion } from "./videoPlayback/zoomRegionUtils";
|
||||
import {
|
||||
applyZoomTransform,
|
||||
@@ -1233,7 +1233,7 @@ const VideoPlayback = forwardRef<VideoPlaybackRef, VideoPlaybackProps>(
|
||||
return;
|
||||
}
|
||||
|
||||
const targetTime = getWebcamPreviewTargetTimeSeconds({
|
||||
const targetTime = getWebcamMediaTargetTimeSeconds({
|
||||
currentTime,
|
||||
webcamDuration: Number.isFinite(webcamVideo.duration) ? webcamVideo.duration : null,
|
||||
timeOffsetMs: webcam.timeOffsetMs,
|
||||
|
||||
@@ -1,25 +1,28 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
import { getWebcamPreviewTargetTimeSeconds } from "./webcamSync";
|
||||
import {
|
||||
getWebcamMediaTargetTimeSeconds,
|
||||
getWebcamPreviewTargetTimeSeconds,
|
||||
} from "./webcamSync";
|
||||
|
||||
describe("getWebcamPreviewTargetTimeSeconds", () => {
|
||||
it("applies positive webcam offsets", () => {
|
||||
it("subtracts positive webcam offsets when the webcam started after the main capture", () => {
|
||||
expect(
|
||||
getWebcamPreviewTargetTimeSeconds({
|
||||
currentTime: 10,
|
||||
webcamDuration: 20,
|
||||
timeOffsetMs: 250,
|
||||
}),
|
||||
).toBe(10.25);
|
||||
).toBe(9.75);
|
||||
});
|
||||
|
||||
it("clamps negative webcam offsets to zero", () => {
|
||||
it("adds negative webcam offsets when the webcam started before the main capture", () => {
|
||||
expect(
|
||||
getWebcamPreviewTargetTimeSeconds({
|
||||
currentTime: 0.1,
|
||||
webcamDuration: 20,
|
||||
timeOffsetMs: -250,
|
||||
}),
|
||||
).toBe(0);
|
||||
).toBe(0.35);
|
||||
});
|
||||
|
||||
it("falls back to the unshifted time when the offset is invalid", () => {
|
||||
@@ -37,8 +40,20 @@ describe("getWebcamPreviewTargetTimeSeconds", () => {
|
||||
getWebcamPreviewTargetTimeSeconds({
|
||||
currentTime: 8.9,
|
||||
webcamDuration: 9,
|
||||
timeOffsetMs: 500,
|
||||
timeOffsetMs: -500,
|
||||
}),
|
||||
).toBe(9);
|
||||
});
|
||||
});
|
||||
|
||||
describe("getWebcamMediaTargetTimeSeconds", () => {
|
||||
it("clamps positive offsets at zero when the main timeline is earlier than the webcam start", () => {
|
||||
expect(
|
||||
getWebcamMediaTargetTimeSeconds({
|
||||
currentTime: 0.1,
|
||||
webcamDuration: 20,
|
||||
timeOffsetMs: 250,
|
||||
}),
|
||||
).toBe(0);
|
||||
});
|
||||
});
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { clampMediaTimeToDuration } from "@/lib/mediaTiming";
|
||||
|
||||
export function getWebcamPreviewTargetTimeSeconds({
|
||||
export function getWebcamMediaTargetTimeSeconds({
|
||||
currentTime,
|
||||
webcamDuration,
|
||||
timeOffsetMs,
|
||||
@@ -10,6 +10,8 @@ export function getWebcamPreviewTargetTimeSeconds({
|
||||
timeOffsetMs?: number | null;
|
||||
}): number {
|
||||
const safeOffsetMs = Number.isFinite(timeOffsetMs) ? (timeOffsetMs ?? 0) : 0;
|
||||
const shiftedTime = currentTime + safeOffsetMs / 1000;
|
||||
const shiftedTime = currentTime - safeOffsetMs / 1000;
|
||||
return clampMediaTimeToDuration(shiftedTime, webcamDuration);
|
||||
}
|
||||
|
||||
export const getWebcamPreviewTargetTimeSeconds = getWebcamMediaTargetTimeSeconds;
|
||||
|
||||
@@ -278,6 +278,24 @@ describe("FrameRenderer webcam export path", () => {
|
||||
expect(renderer.webcamSeekPromise).toBeNull();
|
||||
});
|
||||
|
||||
it("subtracts stored webcam offsets during export sync", async () => {
|
||||
const renderer = createRenderer() as unknown as FrameRendererTestAccess & {
|
||||
config: { webcam?: { timeOffsetMs?: number } };
|
||||
};
|
||||
const webcamVideo = new FakeVideoElement({ duration: 10, currentTime: 0.25 });
|
||||
renderer.webcamVideoElement = webcamVideo;
|
||||
renderer.config.webcam = {
|
||||
...(renderer.config.webcam ?? {}),
|
||||
timeOffsetMs: 250,
|
||||
};
|
||||
|
||||
await renderer.syncWebcamFrame(2);
|
||||
|
||||
expect(webcamVideo.currentTime).toBe(1.75);
|
||||
expect(renderer.lastSyncedWebcamTime).toBe(1.75);
|
||||
expect(renderer.webcamSeekPromise).toBeNull();
|
||||
});
|
||||
|
||||
it("falls back to animation frame when requestVideoFrameCallback does not fire", async () => {
|
||||
const renderer = createRenderer() as unknown as FrameRendererTestAccess;
|
||||
const webcamVideo = new FakeVideoElement({
|
||||
|
||||
@@ -50,6 +50,7 @@ import {
|
||||
getWebcamOverlayPosition,
|
||||
getWebcamOverlaySizePx,
|
||||
} from "@/components/video-editor/webcamOverlay";
|
||||
import { getWebcamMediaTargetTimeSeconds } from "@/components/video-editor/videoPlayback/webcamSync";
|
||||
import { getAssetPath, getRenderableAssetUrl } from "@/lib/assetPath";
|
||||
import { extensionHost } from "@/lib/extensions";
|
||||
import {
|
||||
@@ -693,8 +694,16 @@ export class FrameRenderer {
|
||||
}
|
||||
|
||||
private async syncWebcamFrame(targetTime: number): Promise<void> {
|
||||
const webcamTargetTime = getWebcamMediaTargetTimeSeconds({
|
||||
currentTime: targetTime,
|
||||
webcamDuration: Number.isFinite(this.webcamVideoElement?.duration)
|
||||
? this.webcamVideoElement?.duration
|
||||
: null,
|
||||
timeOffsetMs: this.config.webcam?.timeOffsetMs,
|
||||
});
|
||||
|
||||
if (this.webcamForwardFrameSource) {
|
||||
const clampedTime = clampMediaTimeToDuration(targetTime, null);
|
||||
const clampedTime = clampMediaTimeToDuration(webcamTargetTime, null);
|
||||
const decodedFrame = await this.webcamForwardFrameSource.getFrameAtTime(clampedTime);
|
||||
this.closeWebcamDecodedFrame();
|
||||
this.webcamDecodedFrame = decodedFrame;
|
||||
@@ -710,7 +719,7 @@ export class FrameRenderer {
|
||||
}
|
||||
|
||||
const clampedTime = clampMediaTimeToDuration(
|
||||
targetTime,
|
||||
webcamTargetTime,
|
||||
Number.isFinite(webcamVideo.duration) ? webcamVideo.duration : null,
|
||||
);
|
||||
|
||||
|
||||
@@ -56,6 +56,7 @@ import {
|
||||
getWebcamOverlayPosition,
|
||||
getWebcamOverlaySizePx,
|
||||
} from "@/components/video-editor/webcamOverlay";
|
||||
import { getWebcamMediaTargetTimeSeconds } from "@/components/video-editor/videoPlayback/webcamSync";
|
||||
import { getAssetPath, getRenderableAssetUrl } from "@/lib/assetPath";
|
||||
import { extensionHost } from "@/lib/extensions";
|
||||
import {
|
||||
@@ -1850,8 +1851,13 @@ export class FrameRenderer {
|
||||
}
|
||||
|
||||
private async syncWebcamFrame(targetTime: number): Promise<void> {
|
||||
const webcamTimeOffsetSec = (this.config.webcam?.timeOffsetMs ?? 0) / 1000;
|
||||
const webcamTargetTime = Math.max(0, targetTime + webcamTimeOffsetSec);
|
||||
const webcamTargetTime = getWebcamMediaTargetTimeSeconds({
|
||||
currentTime: targetTime,
|
||||
webcamDuration: Number.isFinite(this.webcamVideoElement?.duration)
|
||||
? this.webcamVideoElement?.duration
|
||||
: null,
|
||||
timeOffsetMs: this.config.webcam?.timeOffsetMs,
|
||||
});
|
||||
|
||||
if (this.webcamForwardFrameSource) {
|
||||
const clampedTime = clampMediaTimeToDuration(webcamTargetTime, null);
|
||||
|
||||
Reference in New Issue
Block a user