mirror of
https://github.com/heygen-com/hyperframes.git
synced 2026-09-01 19:42:03 +00:00
fix(player): replay from start when play is pressed after video ends (#649)
* fix(player): replay from start when play is pressed after video ends When a non-looping composition reaches its end, pressing play again had no effect because the playhead stayed at the final frame. Now play() detects the ended state and seeks to 0 before resuming. * chore: fix pre-existing format issues in registry files
This commit is contained in:
@@ -1010,6 +1010,56 @@ describe("HyperframesPlayer loop end-state handling", () => {
|
||||
expect(ended).toHaveBeenCalledTimes(1);
|
||||
expect(player._paused).toBe(true);
|
||||
});
|
||||
|
||||
it("play() seeks to 0 and replays when called after the video has ended", () => {
|
||||
const seek = vi.spyOn(player, "seek");
|
||||
player.loop = false;
|
||||
player._duration = 4;
|
||||
player._paused = false;
|
||||
|
||||
player._onMessage(
|
||||
new MessageEvent("message", {
|
||||
source: frameWindow,
|
||||
data: {
|
||||
source: "hf-preview",
|
||||
type: "state",
|
||||
frame: 120,
|
||||
isPlaying: false,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
expect(player._paused).toBe(true);
|
||||
seek.mockClear();
|
||||
|
||||
player.play();
|
||||
|
||||
expect(seek).toHaveBeenCalledWith(0);
|
||||
expect(player._paused).toBe(false);
|
||||
});
|
||||
|
||||
it("play() does not seek to 0 when called mid-playback", () => {
|
||||
const seek = vi.spyOn(player, "seek");
|
||||
player._duration = 4;
|
||||
player._paused = true;
|
||||
// Simulate mid-video position (frame 60 = 2s into a 4s video)
|
||||
player._onMessage(
|
||||
new MessageEvent("message", {
|
||||
source: frameWindow,
|
||||
data: {
|
||||
source: "hf-preview",
|
||||
type: "state",
|
||||
frame: 60,
|
||||
isPlaying: false,
|
||||
},
|
||||
}),
|
||||
);
|
||||
|
||||
player.play();
|
||||
|
||||
expect(seek).not.toHaveBeenCalled();
|
||||
expect(player._paused).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
describe("HyperframesPlayer srcdoc attribute", () => {
|
||||
|
||||
@@ -402,6 +402,9 @@ class HyperframesPlayer extends HTMLElement {
|
||||
|
||||
play() {
|
||||
this._hidePoster();
|
||||
if (this._duration > 0 && this._currentTime >= this._duration) {
|
||||
this.seek(0);
|
||||
}
|
||||
// Always drive the iframe runtime — it's the single source of timeline
|
||||
// truth regardless of who owns audible output. When we own audio, the
|
||||
// proxies join; when the runtime owns, they stay silent.
|
||||
|
||||
+57
-17
@@ -52,20 +52,34 @@
|
||||
overflow: hidden;
|
||||
}
|
||||
.text-source::before {
|
||||
content: '';
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 700px; height: 700px; border-radius: 50%;
|
||||
background: radial-gradient(circle, rgba(0,212,255,0.06) 0%, transparent 70%);
|
||||
top: -150px; right: -100px; pointer-events: none;
|
||||
width: 700px;
|
||||
height: 700px;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(circle, rgba(0, 212, 255, 0.06) 0%, transparent 70%);
|
||||
top: -150px;
|
||||
right: -100px;
|
||||
pointer-events: none;
|
||||
}
|
||||
.text-source::after {
|
||||
content: '';
|
||||
content: "";
|
||||
position: absolute;
|
||||
width: 500px; height: 500px; border-radius: 50%;
|
||||
background: radial-gradient(circle, rgba(124,58,237,0.05) 0%, transparent 70%);
|
||||
bottom: -100px; left: -50px; pointer-events: none;
|
||||
width: 500px;
|
||||
height: 500px;
|
||||
border-radius: 50%;
|
||||
background: radial-gradient(circle, rgba(124, 58, 237, 0.05) 0%, transparent 70%);
|
||||
bottom: -100px;
|
||||
left: -50px;
|
||||
pointer-events: none;
|
||||
}
|
||||
.badge {
|
||||
font-size: 14px;
|
||||
font-weight: 600;
|
||||
letter-spacing: 3px;
|
||||
text-transform: uppercase;
|
||||
color: rgba(0, 212, 255, 0.8);
|
||||
}
|
||||
.badge { font-size: 14px; font-weight: 600; letter-spacing: 3px; text-transform: uppercase; color: rgba(0,212,255,0.8); }
|
||||
.text-source h1 {
|
||||
font-size: 148px;
|
||||
font-weight: 900;
|
||||
@@ -83,7 +97,7 @@
|
||||
.subtitle {
|
||||
font-size: 28px;
|
||||
font-weight: 400;
|
||||
color: rgba(255,255,255,0.35);
|
||||
color: rgba(255, 255, 255, 0.35);
|
||||
max-width: 800px;
|
||||
line-height: 1.5;
|
||||
}
|
||||
@@ -92,9 +106,23 @@
|
||||
gap: 48px;
|
||||
margin-top: 16px;
|
||||
}
|
||||
.stat { text-align: center; }
|
||||
.stat-val { font-size: 42px; font-weight: 800; color: #00d4ff; letter-spacing: -1px; }
|
||||
.stat-label { font-size: 12px; font-weight: 600; color: rgba(255,255,255,0.3); text-transform: uppercase; letter-spacing: 2px; margin-top: 4px; }
|
||||
.stat {
|
||||
text-align: center;
|
||||
}
|
||||
.stat-val {
|
||||
font-size: 42px;
|
||||
font-weight: 800;
|
||||
color: #00d4ff;
|
||||
letter-spacing: -1px;
|
||||
}
|
||||
.stat-label {
|
||||
font-size: 12px;
|
||||
font-weight: 600;
|
||||
color: rgba(255, 255, 255, 0.3);
|
||||
text-transform: uppercase;
|
||||
letter-spacing: 2px;
|
||||
margin-top: 4px;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -125,11 +153,23 @@
|
||||
<div class="text-source">
|
||||
<div class="badge">Write HTML → Render Video</div>
|
||||
<h1>Ship videos <span>10x faster</span></h1>
|
||||
<div class="subtitle">HTML is the source of truth for video. No timeline editors, no After Effects — just code.</div>
|
||||
<div class="subtitle">
|
||||
HTML is the source of truth for video. No timeline editors, no After Effects — just
|
||||
code.
|
||||
</div>
|
||||
<div class="stats">
|
||||
<div class="stat"><div class="stat-val">47x</div><div class="stat-label">Faster than AE</div></div>
|
||||
<div class="stat"><div class="stat-val">12.4K</div><div class="stat-label">Creators</div></div>
|
||||
<div class="stat"><div class="stat-val">2.4M</div><div class="stat-label">Videos Rendered</div></div>
|
||||
<div class="stat">
|
||||
<div class="stat-val">47x</div>
|
||||
<div class="stat-label">Faster than AE</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-val">12.4K</div>
|
||||
<div class="stat-label">Creators</div>
|
||||
</div>
|
||||
<div class="stat">
|
||||
<div class="stat-val">2.4M</div>
|
||||
<div class="stat-label">Videos Rendered</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</canvas>
|
||||
|
||||
@@ -252,4 +252,4 @@
|
||||
"type": "hyperframes:block"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user