mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-08 02:36:10 +00:00
fix(engine): hold the last video frame at the inclusive clip end (#1564)
* fix(engine): hold the last video frame at the inclusive clip end The frame-lookup active set deactivated a video on an exclusive end-bound (globalTime < end), while the runtime keeps an element visible through currentTime <= end (core/runtime init.ts). The rendered frame landing exactly on a clip's end went blank even though the runtime still showed the element on its final frame: one blank frame at the end of every clip whose end lands on a frame boundary. Make the active window inclusive of the end to match the runtime, and at t === end serve the last extracted frame (the runtime holds the element's final frame there too). Mid-clip source exhaustion (t < end) stays blank, unchanged. * fix(engine): align getFrame boundary to match refreshActiveSet Make getFrame's end-bound inclusive (> instead of >=) for consistency with the refreshActiveSet changes. getFrame is currently unused externally but should match the same contract. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Carlos Alcaraz <193642530+calcarazgre646@users.noreply.github.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.6
Carlos Alcaraz
parent
1bab79ef4c
commit
fdb8f33fc0
@@ -1003,7 +1003,7 @@ export class FrameLookupTable {
|
||||
getFrame(videoId: string, globalTime: number): string | null {
|
||||
const video = this.videos.get(videoId);
|
||||
if (!video) return null;
|
||||
if (globalTime < video.start || globalTime >= video.end) return null;
|
||||
if (globalTime < video.start || globalTime > video.end) return null;
|
||||
return getFrameAtTime(video.extracted, globalTime, video.start, video.loop, video.mediaStart);
|
||||
}
|
||||
|
||||
@@ -1014,11 +1014,16 @@ export class FrameLookupTable {
|
||||
}
|
||||
|
||||
private refreshActiveSet(globalTime: number): void {
|
||||
// The active window is [start, end] INCLUSIVE of the end, mirroring the
|
||||
// runtime's element-visibility contract (core/runtime init.ts keeps an
|
||||
// element visible through `currentTime <= end`). An exclusive end-bound
|
||||
// here deactivated the video one frame early, so the frame landing exactly
|
||||
// on a clip's end rendered blank while the runtime still showed it.
|
||||
if (this.lastTime == null || globalTime < this.lastTime) {
|
||||
this.activeVideoIds.clear();
|
||||
this.startCursor = 0;
|
||||
for (const entry of this.orderedVideos) {
|
||||
if (entry.start <= globalTime && globalTime < entry.end) {
|
||||
if (entry.start <= globalTime && globalTime <= entry.end) {
|
||||
this.activeVideoIds.add(entry.videoId);
|
||||
}
|
||||
if (entry.start <= globalTime) {
|
||||
@@ -1037,7 +1042,7 @@ export class FrameLookupTable {
|
||||
if (candidate.start > globalTime) {
|
||||
break;
|
||||
}
|
||||
if (globalTime < candidate.end) {
|
||||
if (globalTime <= candidate.end) {
|
||||
this.activeVideoIds.add(candidate.videoId);
|
||||
}
|
||||
this.startCursor += 1;
|
||||
@@ -1045,7 +1050,7 @@ export class FrameLookupTable {
|
||||
|
||||
for (const videoId of Array.from(this.activeVideoIds)) {
|
||||
const video = this.videos.get(videoId);
|
||||
if (!video || globalTime < video.start || globalTime >= video.end) {
|
||||
if (!video || globalTime < video.start || globalTime > video.end) {
|
||||
this.activeVideoIds.delete(videoId);
|
||||
}
|
||||
}
|
||||
@@ -1073,7 +1078,18 @@ export class FrameLookupTable {
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if (frameIndex < 0 || frameIndex >= video.extracted.totalFrames) continue;
|
||||
if (frameIndex < 0 || frameIndex >= video.extracted.totalFrames) {
|
||||
// At the inclusive clip end (globalTime === end), hold the last
|
||||
// extracted frame so the render matches the runtime, which keeps the
|
||||
// element visible on its final frame at `t === end`. Mid-clip source
|
||||
// exhaustion (globalTime < end) stays blank — unchanged.
|
||||
if (globalTime >= video.end && video.extracted.totalFrames > 0) {
|
||||
const lastIndex = video.extracted.totalFrames - 1;
|
||||
const lastPath = video.extracted.framePaths.get(lastIndex);
|
||||
if (lastPath) frames.set(videoId, { framePath: lastPath, frameIndex: lastIndex });
|
||||
}
|
||||
continue;
|
||||
}
|
||||
const framePath = video.extracted.framePaths.get(frameIndex);
|
||||
if (!framePath) continue;
|
||||
frames.set(videoId, { framePath, frameIndex });
|
||||
|
||||
Reference in New Issue
Block a user