<video> clip silently ignores GSAP tweens once its source media ends (data-duration > media duration)
Los mantenedores suelen responder en 1 día
Evaluación
- Dificultad
- 4/5
- Tiempo estimado
- 3-5 días
- Aptitud para principiantes
- 52/100
- Tipo de issue
- Error
- Claridad
- Bastante claro
- Estado de actividad
- Activo
- Stack tecnológico
- html, typescript
Línea de trabajo
Start with the self-contained index.html reproduction, generate the two assets with the provided ffmpeg commands, and run npx hyperframes render --format png-sequence. Compare the video and div halves around frames 61–77, then trace the render path responsible for video behavior after media end. Done means the video continues honoring its tween through the declared clip window, or check reports the unsupported duration mismatch.
Escrito por el modelo de indexación a partir del texto del issue.
Descripción
Describe the bug
A <video class="clip"> silently stops honoring GSAP tweens (opacity, filter, transform) once its source media ends, while its data-start/data-duration window is still open. An identical tween on a sibling <div class="clip"> applies normally at the same instant.
The trigger is data-duration > (source media duration). This is easy to hit unintentionally: place a 5.2s screen capture into a 5.85s clip slot and every authored crossfade on it silently becomes a hard cut. There is no error, no warning, and lint/check report 0 findings — the composition is fully valid.
What made this expensive to diagnose is that the sibling <div> in the same clip keeps fading. The two layers desynchronize, so the artifact reads as "the previous scene is showing through the transition" rather than as a video bug.
Link to reproduction
Not provided as a repo — the repro below is fully self-contained and synthetic (~30 lines + two ffmpeg lines that generate the only assets; no external or proprietary media). Happy to push it to a public repo if that's needed for triage.
Steps to reproduce
npx hyperframes init hfrepro --non-interactive
cd hfrepro
mkdir -p assets
# the only variable in the whole repro: source length
ffmpeg -f lavfi -i color=c=white:s=640x360:r=30:d=2 -c:v libx264 -pix_fmt yuv420p assets/clip2s.mp4
ffmpeg -f lavfi -i color=c=white:s=640x360:r=30:d=4 -c:v libx264 -pix_fmt yuv420p assets/clip4s.mp4
Replace index.html with:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=1920, height=1080" />
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/gsap.min.js"></script>
<style>
* { margin: 0; padding: 0; box-sizing: border-box; }
html, body { margin: 0; width: 1920px; height: 1080px; overflow: hidden; background: #000; }
#root { position: relative; width: 1920px; height: 1080px; overflow: hidden; background: #000; }
.clip { position: absolute; }
#vid { left: 0; top: 0; width: 960px; height: 1080px; object-fit: cover; }
#ctl { left: 960px; top: 0; width: 960px; height: 1080px; background: #fff; }
</style>
</head>
<body>
<div id="root" data-composition-id="main" data-start="0" data-duration="4"
data-width="1920" data-height="1080">
<!-- Both clips occupy the same 0..4s window. Swap the src to flip the result. -->
<video id="vid" class="clip" src="assets/clip2s.mp4" muted
data-start="0" data-duration="4" data-track-index="0"></video>
<div id="ctl" class="clip" data-start="0" data-duration="4" data-track-index="1"></div>
</div>
<script>
gsap.config({ lazy: false });
window.__timelines = window.__timelines || {};
const tl = gsap.timeline({ paused: true });
// Identical linear fade on both, straddling the 2.0s media end of clip2s.mp4.
tl.to("#vid", { opacity: 0, duration: 0.8, ease: "none" }, 1.6);
tl.to("#ctl", { opacity: 0, duration: 0.8, ease: "none" }, 1.6);
window.__timelines["main"] = tl;
tl.seek(0);
</script>
</body>
</html>
Then render each variant and sample the two halves:
npx hyperframes render --format png-sequence --output .frames
# left half = the <video>, right half = the control <div>
ffmpeg -i .frames/frame_000073.png -vf "crop=800:800:80:140,scale=1:1,format=rgb24" -f rawvideo - | od -An -tu1 | head -1
ffmpeg -i .frames/frame_000073.png -vf "crop=800:800:1040:140,scale=1:1,format=rgb24" -f rawvideo - | od -An -tu1 | head -1
npx hyperframes check passes clean on this composition: Lint 0 errors, 0 warnings / Runtime 0 errors, 0 warnings.
Expected behavior
A clip whose data-duration exceeds its source duration should keep compositing (its last frame is fine) and keep honoring tweens on the element for the rest of its declared window — the same way the sibling <div> does. If instead this configuration is unsupported by design, lint should flag data-duration > media duration as an error, because today it is silent and the symptom appears somewhere else entirely.
Actual behavior
Past its media end the <video> ignores the tween completely — it never even reaches the tween's end state.
Single variable changed (src), composition HTML otherwise byte-identical. R channel of a flat white patch, 30fps, fade authored 1.6s -> 2.4s, media end at 2.0s:
| frame | t | src = clip2s.mp4 (2.0s, duration > media) | src = clip4s.mp4 (4.0s, duration == media) |
|---|---|---|---|
<video> / <div> |
<video> / <div> |
||
| 49 | 1.600 | 255 / 255 | 255 / 255 |
| 61 | 2.000 | 255 / 255 | 255 / 255 |
| 69 | 2.266 | 255 / 255 | 255 / 255 |
| 73 | 2.400 | 255 / 0 | 0 / 0 |
| 77 | 2.533 | 255 / 0 | 0 / 0 |
With a source at least as long as the clip, the video tracks the control exactly. With a shorter source, it is pinned opaque forever.
Confirmed on a real 6-scene project as well: moving an identical tween wholly before the media end made it fade correctly, so the boundary is the trigger, not the tween.
Environment
✓ Version 0.8.46 (latest)
✓ Node.js v26.3.1 (darwin arm64)
✓ CPU 10 cores · Apple M5 @ 2400MHz
✓ Memory 16.0 GB total
✓ FFmpeg ffmpeg 8.1.2
✓ Chrome bundled chrome-headless-shell mac_arm-152.0.7977.30
✓ Capture beginframe
Additional context
Workaround: pad the asset so its duration is at least the clip duration, e.g. ffmpeg -i in.mp4 -vf tpad=stop=-1:stop_mode=clone -t <clip-duration> out.mp4.
Two unrelated defects found on the same 0.8.46 run, filed here only so they are not lost — happy to split into separate issues:
-
MP4 encoder tints the frame, including pure CSS/SVG content. A wordmark authored
#f0f0f0over#000leaves Chrome at (235.96, 235.96, 235.96) and decodes out of the rendered MP4 at (233.68, 235.86, 235.67) — G and B intact, R low by 2.3. No footage involved, so this is not source tagging.--sdrdoes not change it. SSIM against the PNG-sequence source: CLI MP4 0.999968 overall / 0.999894 on V; a direct x264 encode pinningscale=in_range=full:out_range=limited:out_color_matrix=bt709gives 0.999990 / 1.000000 on V. V (the R-Y axis) being the only errored plane points at an unpinned RGB->YUV conversion rather than at rate control. Workaround: render--format png-sequenceand encode separately. -
--video-bitrateis a no-op.--quality delivery --video-bitrate 20Mand--video-bitrate 28Mboth produce files thatffprobereports at exactly 10.0 Mbps, identical to omitting the flag.--crfis honored, so it is specific to the bitrate path. The flag is accepted silently.
- Lenguaje dominante
- TypeScript
- Estrellas
- 54.1k
- Forks
- 4.9k
- Merge medio
- 8 h 34 min
- PR fusionados (30 d)
- 720
Preparar el entorno
Este proyecto no incluye contenedor de desarrollo, Dockerfile ni guía de contribución, así que la configuración corre por tu cuenta: empieza por su README y consulta nuestra guía para la primera contribución para los pasos generales.
Primeros pasos
- Lee el issue completo y luego la guía de contribución del proyecto.
- Comenta en el issue que vas a ocuparte — evita que dos personas hagan lo mismo.
- Haz un fork del repositorio y trabaja en una rama.
- Abre un pull request que haga referencia al número del issue.
Más de heygen-com/hyperframes
-
desktop-candidate difficulty/easy enhancement good first issue help wanted triage/ready
Dificultad 2/5 1-3 horas Aptitud para principiantes 85/100
heygen-com/hyperframes#5582 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Studio: press ? to open the keyboard shortcuts panelPosiblemente ocupada @B-Payal la tomó hace 2 días. Abiertodesktop-candidate difficulty/easy enhancement good first issue help wanted triage/ready
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
heygen-com/hyperframes#5580 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
remotion-to-hyperframes: Easing.cubic is mapped to power3 (quartic); cubic is power2Posiblemente ocupada @user-github-me la tomó hace 2 días. Abiertodifficulty/easy documentation help wanted triage/ready
Dificultad 1/5 Menos de una hora Aptitud para principiantes 88/100
heygen-com/hyperframes#5538 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
`hyperframes docs examples` lists templates that don't existPosiblemente ocupada @Muhtasim-Munif-Fahim la tomó hace 5 días. Abiertobug difficulty/easy help wanted triage/ready
Dificultad 2/5 1-3 horas Aptitud para principiantes 82/100
heygen-com/hyperframes#5393 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
lint: add tests for three caption rules that have nonePosiblemente ocupada Un pull request vinculado a esta issue está abierto o ya se fusionó. Abiertodifficulty/easy enhancement help wanted triage/ready
Dificultad 2/5 1-3 horas Aptitud para principiantes 85/100
heygen-com/hyperframes#5388 · 1 comentario ·
Los mantenedores suelen responder en 1 día
Todos los issues de heygen-com/hyperframes
Issues similares
-
documentation good first issue
Dificultad 2/5 Medio día Aptitud para principiantes 72/100
sujithputta02/Esapay#13 ·
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 65/100
ehmpathy/rhachet-roles-bhuild#468 ·
Los mantenedores suelen responder en 1 día
-
Share dialog: the address book's contact count reads "1 contacts" (no singular form in any language)Posiblemente ocupada @chibenwa la tomó hoy. Abiertoai-driven-qa bug claude Cosmetic
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
linagora/twake-contacts-frontend#271 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
factory-active task-identify-harness-labels-done task-identify-issue-type-done
Dificultad 1/5 Menos de una hora Aptitud para principiantes 70/100
vercel/ai#23435 · 1 comentario ·
Los mantenedores suelen responder en 1 día
-
Dificultad 2/5 1-3 horas Aptitud para principiantes 72/100
Koenkk/zigbee2mqtt#33402 ·
Los mantenedores suelen responder en 1 día