diff --git a/templates/clips/app/components/player/video-player.test.tsx b/templates/clips/app/components/player/video-player.test.tsx index ed8b69ed27..fdd569eec4 100644 --- a/templates/clips/app/components/player/video-player.test.tsx +++ b/templates/clips/app/components/player/video-player.test.tsx @@ -172,6 +172,32 @@ describe("VideoPlayer playback", () => { expect(video.paused).toBe(false); }); + it("replays from the start when the surface is clicked after the clip ended", () => { + const surface = getPlayerSurface(); + const video = getVideo(); + + act(() => { + surface.click(); + }); + expect(video.paused).toBe(false); + + // Reaching end of stream can fire "ended" while the browser leaves paused + // false (MSE end-of-stream / DB-duration mismatch). The play button must + // still restart from the beginning rather than pausing a finished clip. + video.currentTime = 10; + Object.defineProperty(video, "ended", { configurable: true, value: true }); + act(() => { + video.dispatchEvent(new Event("ended")); + }); + + act(() => { + surface.click(); + }); + + expect(video.currentTime).toBe(0); + expect(video.paused).toBe(false); + }); + it("suppresses the synthetic click that follows a touch tap instead of double-toggling playback", () => { const surface = getPlayerSurface(); const video = getVideo(); diff --git a/templates/clips/app/components/player/video-player.tsx b/templates/clips/app/components/player/video-player.tsx index 74c3767640..368873ba45 100644 --- a/templates/clips/app/components/player/video-player.tsx +++ b/templates/clips/app/components/player/video-player.tsx @@ -598,10 +598,10 @@ export const VideoPlayer = forwardRef( const togglePlayback = useCallback(() => { const v = videoRef.current; if (!v) return; - if (!v.paused || isPlaying) { - pauseVideo(); - return; - } + // A finished clip must always replay from the start, even when the browser + // left `paused` false at end of stream (MSE end-of-stream / DB-duration + // mismatch) or `isPlaying` is stale — otherwise the toggle below would + // pause an already-ended element and the play button appears to do nothing. if (v.ended) { try { v.currentTime = 0; @@ -609,6 +609,12 @@ export const VideoPlayer = forwardRef( } catch { // Let the normal play attempt report a media error if the seek fails. } + requestPlay(); + return; + } + if (!v.paused || isPlaying) { + pauseVideo(); + return; } requestPlay(); }, [isPlaying, pauseVideo, requestPlay]);