mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-10 22:20:14 +00:00
feat: make creator media edits render-safe (#3322)
* feat: make creator media edits render-safe * fix: align media playback timing * docs: add creator editing recipes * docs: expand creator editing guidance * fix: unify media source offsets * fix: scale natural media duration * fix: preserve natural media zero spans * fix: align compiled natural media timing * test: classify compiler media test as integration * fix: drop inactive media windows * fix: unify literal timing parsing * fix: keep browser media parsing serializable * fix: keep page timing readers strict * fix: close remaining preview timing gaps * fix(core): preserve Studio voice pitch at playback speed * chore: keep creator contract source-neutral
This commit is contained in:
@@ -7,6 +7,7 @@ import type {
|
||||
import { stableClipId } from "./clipTree";
|
||||
import { swallow } from "./diagnostics";
|
||||
import { readElementPlaybackRate, readElementPlaybackStart } from "./media";
|
||||
import { parseStrictFiniteTimingNumber, resolveNaturalMediaTimelineDuration } from "./playbackRate";
|
||||
import { resolveCssStackingContextId } from "./stackingContext";
|
||||
import { createRuntimeStartTimeResolver } from "./startResolver";
|
||||
import { isSceneLikeCompositionId } from "../slideshow/index.js";
|
||||
@@ -17,9 +18,7 @@ const AUTHORED_DURATION_ATTR = "data-hf-authored-duration";
|
||||
const AUTHORED_END_ATTR = "data-hf-authored-end";
|
||||
|
||||
function parseNum(value: string | null | undefined): number | null {
|
||||
if (value == null || value === "") return null;
|
||||
const parsed = Number(value);
|
||||
return Number.isFinite(parsed) ? parsed : null;
|
||||
return parseStrictFiniteTimingNumber(value);
|
||||
}
|
||||
|
||||
function parseElementDurationAttr(element: Element): number | null {
|
||||
@@ -180,12 +179,8 @@ export function collectRuntimeTimelinePayload(params: {
|
||||
if (declaredDuration != null && declaredDuration > 0) {
|
||||
return declaredDuration;
|
||||
}
|
||||
const playbackStart =
|
||||
parseNum(mediaEl.getAttribute("data-playback-start")) ??
|
||||
parseNum(mediaEl.getAttribute("data-media-start")) ??
|
||||
0;
|
||||
if (Number.isFinite(mediaEl.duration) && mediaEl.duration > playbackStart) {
|
||||
return Math.max(0, (mediaEl.duration - playbackStart) / readElementPlaybackRate(mediaEl));
|
||||
if (Number.isFinite(mediaEl.duration)) {
|
||||
return resolveNaturalMediaTimelineDuration(mediaEl, mediaEl.duration);
|
||||
}
|
||||
return null;
|
||||
};
|
||||
@@ -357,23 +352,15 @@ export function collectRuntimeTimelinePayload(params: {
|
||||
);
|
||||
const nodeCompositionId = node.getAttribute("data-composition-id");
|
||||
let duration = parseElementDurationAttr(node);
|
||||
if (
|
||||
(duration == null || duration <= 0) &&
|
||||
nodeCompositionId &&
|
||||
nodeCompositionId !== rootCompositionId
|
||||
) {
|
||||
if (duration == null && nodeCompositionId && nodeCompositionId !== rootCompositionId) {
|
||||
duration = resolveTimelineDurationSeconds(nodeCompositionId);
|
||||
}
|
||||
if ((duration == null || duration <= 0) && node instanceof HTMLMediaElement) {
|
||||
const mediaStart =
|
||||
parseNum(node.getAttribute("data-playback-start")) ??
|
||||
parseNum(node.getAttribute("data-media-start")) ??
|
||||
0;
|
||||
if (Number.isFinite(node.duration) && node.duration > 0) {
|
||||
duration = Math.max(0, node.duration - mediaStart);
|
||||
if (duration == null && node instanceof HTMLMediaElement) {
|
||||
if (Number.isFinite(node.duration)) {
|
||||
duration = resolveNaturalMediaTimelineDuration(node, node.duration);
|
||||
}
|
||||
}
|
||||
if (duration == null || duration <= 0) {
|
||||
if (duration == null) {
|
||||
const inheritedDuration = compositionContext.inheritedDuration;
|
||||
if (inheritedDuration != null && inheritedDuration > 0) {
|
||||
const inheritedStart = compositionContext.inheritedStart ?? 0;
|
||||
|
||||
Reference in New Issue
Block a user