Add auto archived overlay recordings for live analysis
这个提交包含在:
@@ -8,6 +8,26 @@ export type ChangeLogEntry = {
|
||||
};
|
||||
|
||||
export const CHANGE_LOG_ENTRIES: ChangeLogEntry[] = [
|
||||
{
|
||||
version: "2026.03.16-live-analysis-overlay-archive",
|
||||
releaseDate: "2026-03-16",
|
||||
repoVersion: "e3fe9a8 + local changes",
|
||||
summary: "实时分析新增 60 秒自动归档录像,录制内容会保留骨架、关键点和虚拟形象叠层,并同步进入视频库。",
|
||||
features: [
|
||||
"实时分析开始后会自动录制合成画布,每 60 秒自动切段归档",
|
||||
"归档录像会保留原视频、骨架线、关键点和当前虚拟形象覆盖效果",
|
||||
"归档片段会自动写入视频库,标签显示为“实时分析”",
|
||||
"删除视频库中的实时分析录像时,不会删除已写入的实时分析数据和训练记录",
|
||||
"线上 smoke 已确认 `https://te.hao.work/` 已切换到本次新构建,`/live-camera`、`/videos`、`/changelog` 页面均可正常访问",
|
||||
],
|
||||
tests: [
|
||||
"pnpm check",
|
||||
"pnpm test",
|
||||
"pnpm build",
|
||||
"pnpm test:e2e",
|
||||
"Playwright smoke: 真实站点登录 H1,完成 /live-camera 引导、开始/结束分析,并确认 /videos 可见实时分析条目",
|
||||
],
|
||||
},
|
||||
{
|
||||
version: "2026.03.15-live-analysis-leave-hint",
|
||||
releaseDate: "2026-03-15",
|
||||
|
||||
@@ -662,18 +662,22 @@ function drawFullFigureAvatar(
|
||||
drawLimbs(ctx, anchors, visual.limbStroke);
|
||||
}
|
||||
|
||||
export function drawLiveCameraOverlay(
|
||||
canvas: HTMLCanvasElement | null,
|
||||
export function renderLiveCameraOverlayToContext(
|
||||
ctx: CanvasRenderingContext2D | null,
|
||||
width: number,
|
||||
height: number,
|
||||
landmarks: PosePoint[] | undefined,
|
||||
avatarState?: AvatarRenderState,
|
||||
options?: { clear?: boolean },
|
||||
) {
|
||||
const ctx = canvas?.getContext("2d");
|
||||
if (!canvas || !ctx) return;
|
||||
ctx.clearRect(0, 0, canvas.width, canvas.height);
|
||||
if (!ctx) return;
|
||||
if (options?.clear !== false) {
|
||||
ctx.clearRect(0, 0, width, height);
|
||||
}
|
||||
if (!landmarks) return;
|
||||
|
||||
if (avatarState?.enabled) {
|
||||
const anchors = getAvatarAnchors(landmarks, canvas.width, canvas.height);
|
||||
const anchors = getAvatarAnchors(landmarks, width, height);
|
||||
if (anchors) {
|
||||
const sprite = getAvatarImage(avatarState.avatarKey);
|
||||
const visual = AVATAR_VISUALS[avatarState.avatarKey];
|
||||
@@ -715,8 +719,8 @@ export function drawLiveCameraOverlay(
|
||||
const end = landmarks[to];
|
||||
if (!start || !end || (start.visibility ?? 1) < 0.25 || (end.visibility ?? 1) < 0.25) return;
|
||||
ctx.beginPath();
|
||||
ctx.moveTo(start.x * canvas.width, start.y * canvas.height);
|
||||
ctx.lineTo(end.x * canvas.width, end.y * canvas.height);
|
||||
ctx.moveTo(start.x * width, start.y * height);
|
||||
ctx.lineTo(end.x * width, end.y * height);
|
||||
ctx.stroke();
|
||||
});
|
||||
|
||||
@@ -724,7 +728,17 @@ export function drawLiveCameraOverlay(
|
||||
if ((point.visibility ?? 1) < 0.25) return;
|
||||
ctx.fillStyle = index >= 11 && index <= 16 ? "rgba(253, 224, 71, 0.95)" : "rgba(255,255,255,0.88)";
|
||||
ctx.beginPath();
|
||||
ctx.arc(point.x * canvas.width, point.y * canvas.height, index >= 11 && index <= 16 ? 5 : 4, 0, Math.PI * 2);
|
||||
ctx.arc(point.x * width, point.y * height, index >= 11 && index <= 16 ? 5 : 4, 0, Math.PI * 2);
|
||||
ctx.fill();
|
||||
});
|
||||
}
|
||||
|
||||
export function drawLiveCameraOverlay(
|
||||
canvas: HTMLCanvasElement | null,
|
||||
landmarks: PosePoint[] | undefined,
|
||||
avatarState?: AvatarRenderState,
|
||||
) {
|
||||
const ctx = canvas?.getContext("2d");
|
||||
if (!canvas || !ctx) return;
|
||||
renderLiveCameraOverlayToContext(ctx, canvas.width, canvas.height, landmarks, avatarState, { clear: true });
|
||||
}
|
||||
|
||||
在新工单中引用
屏蔽一个用户