11import { useSimContext } from "@/contexts/SimContext/context" ;
2- import { FC , useCallback } from "react" ;
2+ import { FC , useCallback , useEffect , useRef } from "react" ;
33
44export const Playback : FC = ( ) => {
55 const {
66 state : { events, isPlaying, speedMultiplier, currentTime, maxTime } ,
77 dispatch,
88 } = useSimContext ( ) ;
99
10+ // Timeline playback refs
11+ const intervalRef = useRef < number | null > ( null ) ;
12+ const lastUpdateRef = useRef < number > ( 0 ) ;
13+ const currentTimeRef = useRef < number > ( currentTime ) ;
14+
1015 const handleSpeedChange = useCallback (
1116 ( event : React . ChangeEvent < HTMLSelectElement > ) => {
1217 const newSpeed = parseFloat ( event . target . value ) ;
@@ -32,6 +37,60 @@ export const Playback: FC = () => {
3237 [ dispatch , currentTime , events , maxTime ] ,
3338 ) ;
3439
40+ // Timeline playback effect - handles automatic advancement when playing
41+ useEffect ( ( ) => {
42+ if ( isPlaying && events . length > 0 ) {
43+ const maxEventTime = events [ events . length - 1 ] . time_s ;
44+
45+ // Clear any existing interval
46+ if ( intervalRef . current ) {
47+ clearInterval ( intervalRef . current ) ;
48+ }
49+
50+ // Start playback interval
51+ intervalRef . current = window . setInterval ( ( ) => {
52+ const now = performance . now ( ) ;
53+ // Convert to seconds and apply speed
54+ const deltaTime =
55+ ( ( now - lastUpdateRef . current ) / 1000 ) * speedMultiplier ;
56+ lastUpdateRef . current = now ;
57+
58+ const newTime = Math . min ( currentTimeRef . current + deltaTime , maxEventTime ) ;
59+ currentTimeRef . current = newTime ;
60+
61+ dispatch ( {
62+ type : "SET_TIMELINE_TIME" ,
63+ payload : newTime ,
64+ } ) ;
65+
66+ // Auto-pause at the end
67+ if ( newTime >= maxEventTime ) {
68+ dispatch ( { type : "SET_TIMELINE_PLAYING" , payload : false } ) ;
69+ }
70+ } , 16 ) ; // ~60 FPS
71+
72+ lastUpdateRef . current = performance . now ( ) ;
73+ } else {
74+ // Clear interval when paused or no events
75+ if ( intervalRef . current ) {
76+ clearInterval ( intervalRef . current ) ;
77+ intervalRef . current = null ;
78+ }
79+ }
80+
81+ // Cleanup on unmount
82+ return ( ) => {
83+ if ( intervalRef . current ) {
84+ clearInterval ( intervalRef . current ) ;
85+ }
86+ } ;
87+ } , [ isPlaying , events . length , currentTime , speedMultiplier , dispatch ] ) ;
88+
89+ // Keep currentTimeRef in sync when currentTime changes externally (e.g., slider)
90+ useEffect ( ( ) => {
91+ currentTimeRef . current = currentTime ;
92+ lastUpdateRef . current = performance . now ( ) ;
93+ } , [ currentTime ] ) ;
3594
3695 const disabled = events . length === 0 ;
3796
@@ -95,6 +154,8 @@ export const Playback: FC = () => {
95154 onChange = { handleSpeedChange }
96155 disabled = { disabled }
97156 >
157+ < option value = { 0.01 } > 0.01x</ option >
158+ < option value = { 0.1 } > 0.1x</ option >
98159 < option value = { 0.25 } > 0.25x</ option >
99160 < option value = { 0.5 } > 0.5x</ option >
100161 < option value = { 1 } > 1x</ option >
0 commit comments