Skip to content

Commit 85bf99b

Browse files
authored
fix(visualizer): reset player to first frame after autoplay completes (#2181)
* fix(visualizer): reset player to first frame after autoplay completes - Reset currentFrame to 0 instead of staying at the last frame when playback finishes, so the first frame subtitle and full-screen view are restored. - Clear playingTaskId when player is not playing, removing the sidebar shimmer animation highlight when playback is stopped. * fix(visualizer): stay at last task frame with full view after autoplay - Stay at the last frame (duration-1) when playback finishes, then seek back to the last frame with a taskId to skip the End card. - Disable autoZoom when player is stopped so the view shows full screen. - Clear playingTaskId on stop to remove sidebar shimmer animation. - Extract resetIfAtEnd() to deduplicate play/toggle logic.
1 parent 7483f5f commit 85bf99b

File tree

2 files changed

+30
-8
lines changed

2 files changed

+30
-8
lines changed

packages/visualizer/src/component/player/index.tsx

Lines changed: 24 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -124,15 +124,36 @@ export function Player(props?: {
124124
playbackRate: playbackSpeed,
125125
});
126126

127-
// Track frame for taskId callback
127+
// When playback stops, seek to the last frame with a taskId (skip the End card)
128+
useEffect(() => {
129+
if (!frameMap || player.playing) return;
130+
const { scriptFrames, totalDurationInFrames } = frameMap;
131+
if (player.currentFrame < totalDurationInFrames - 1) return;
132+
for (let i = scriptFrames.length - 1; i >= 0; i--) {
133+
const sf = scriptFrames[i];
134+
if (sf.taskId) {
135+
player.seekTo(sf.startFrame + sf.durationInFrames - 1);
136+
break;
137+
}
138+
}
139+
}, [frameMap, player.playing]);
140+
141+
// Sync taskId to parent: report current task while playing, clear on stop
128142
useEffect(() => {
129143
if (!frameMap || !props?.onTaskChange) return;
144+
if (!player.playing) {
145+
if (lastTaskIdRef.current !== null) {
146+
lastTaskIdRef.current = null;
147+
props.onTaskChange(null);
148+
}
149+
return;
150+
}
130151
const taskId = deriveTaskId(frameMap.scriptFrames, player.currentFrame);
131152
if (taskId !== lastTaskIdRef.current) {
132153
lastTaskIdRef.current = taskId;
133154
props.onTaskChange(taskId);
134155
}
135-
}, [frameMap, props?.onTaskChange, player.currentFrame]);
156+
}, [frameMap, props?.onTaskChange, player.currentFrame, player.playing]);
136157

137158
const currentFrameState = useMemo(() => {
138159
if (!frameMap) return null;
@@ -350,7 +371,7 @@ export function Player(props?: {
350371
>
351372
<StepsTimeline
352373
frameMap={frameMap}
353-
autoZoom={autoZoom}
374+
autoZoom={autoZoom && player.playing}
354375
frame={player.currentFrame}
355376
width={compositionWidth}
356377
height={compositionHeight}

packages/visualizer/src/component/player/use-frame-player.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,11 +76,15 @@ export function useFramePlayer(options: UseFramePlayerOptions): FramePlayer {
7676
return () => cancelAnimationFrame(rafId);
7777
}, [playing]);
7878

79-
const play = useCallback(() => {
79+
const resetIfAtEnd = () => {
8080
if (frameRef.current >= durationRef.current - 1) {
8181
frameRef.current = 0;
8282
setCurrentFrame(0);
8383
}
84+
};
85+
86+
const play = useCallback(() => {
87+
resetIfAtEnd();
8488
setPlaying(true);
8589
}, []);
8690

@@ -90,10 +94,7 @@ export function useFramePlayer(options: UseFramePlayerOptions): FramePlayer {
9094
if (playingRef.current) {
9195
setPlaying(false);
9296
} else {
93-
if (frameRef.current >= durationRef.current - 1) {
94-
frameRef.current = 0;
95-
setCurrentFrame(0);
96-
}
97+
resetIfAtEnd();
9798
setPlaying(true);
9899
}
99100
}, []);

0 commit comments

Comments
 (0)