| 210 | |
| 211 | const startRAFLoop = useCallback(() => { |
| 212 | const tick = () => { |
| 213 | const adapter = getAdapter(); |
| 214 | if (adapter) { |
| 215 | const rawTime = adapter.getTime(); |
| 216 | const dur = adapter.getDuration(); |
| 217 | const time = dur > 0 ? Math.min(rawTime, dur) : rawTime; |
| 218 | liveTime.notify(time); // direct DOM updates, no React re-render |
| 219 | const { inPoint, outPoint } = usePlayerStore.getState(); |
| 220 | const rawLoopEnd = outPoint !== null ? Math.min(outPoint, dur) : dur; |
| 221 | const rawLoopStart = inPoint !== null ? inPoint : 0; |
| 222 | const loopEnd = rawLoopStart < rawLoopEnd ? rawLoopEnd : dur; |
| 223 | const loopStart = rawLoopStart < rawLoopEnd ? rawLoopStart : 0; |
| 224 | if (time >= loopEnd) { |
| 225 | if (usePlayerStore.getState().loopEnabled && dur > 0) { |
| 226 | // keepPlaying skips the adapter's implicit pause; play() below is then a no-op. |
| 227 | adapter.seek(loopStart, { keepPlaying: true }); |
| 228 | liveTime.notify(loopStart); |
| 229 | adapter.play(); |
| 230 | setIsPlaying(true); |
| 231 | rafRef.current = requestAnimationFrame(tick); |
| 232 | return; |
| 233 | } |
| 234 | if (adapter.isPlaying()) adapter.pause(); |
| 235 | setCurrentTime(time); // sync Zustand once at end |
| 236 | setIsPlaying(false); |
| 237 | cancelAnimationFrame(rafRef.current); |
| 238 | return; |
| 239 | } |
| 240 | } |
| 241 | rafRef.current = requestAnimationFrame(tick); |
| 242 | }; |
| 243 | rafRef.current = requestAnimationFrame(tick); |
| 244 | }, [getAdapter, setCurrentTime, setIsPlaying]); |
| 245 | |