@@ -349,6 +349,37 @@ export function Player(props?: {
349349 mouseOverSettingsIcon ,
350350 ] ) ;
351351
352+ // Compute chapter markers from step boundaries (each img/insight = new chapter)
353+ const chapterMarkers = useMemo ( ( ) => {
354+ if ( ! frameMap ) return [ ] ;
355+ const { scriptFrames, totalDurationInFrames, openingDurationInFrames } =
356+ frameMap ;
357+ if ( totalDurationInFrames === 0 ) return [ ] ;
358+
359+ const markers : { percent : number ; title : string ; frame : number } [ ] = [ ] ;
360+ for ( const sf of scriptFrames ) {
361+ if (
362+ ( sf . type !== 'img' && sf . type !== 'insight' ) ||
363+ sf . durationInFrames === 0
364+ )
365+ continue ;
366+ const globalFrame = openingDurationInFrames + sf . startFrame ;
367+ const percent = ( globalFrame / totalDurationInFrames ) * 100 ;
368+ if ( percent > 1 && percent < 99 ) {
369+ const parts = [ sf . title , sf . subTitle ] . filter ( Boolean ) ;
370+ markers . push ( {
371+ percent,
372+ title :
373+ parts . length > 0
374+ ? parts . join ( ': ' )
375+ : `Chapter ${ markers . length + 1 } ` ,
376+ frame : globalFrame ,
377+ } ) ;
378+ }
379+ }
380+ return markers ;
381+ } , [ frameMap ] ) ;
382+
352383 // If no scripts, show empty
353384 if ( ! scripts || scripts . length === 0 || ! frameMap ) {
354385 return < div className = "player-container" /> ;
@@ -360,31 +391,51 @@ export function Player(props?: {
360391 return (
361392 < div className = "player-container" data-fit-mode = { props ?. fitMode } >
362393 < div className = "canvas-container" >
363- < RemotionPlayer
364- ref = { playerRef }
365- component = { Composition }
366- inputProps = { {
367- frameMap,
368- effects : effectsEnabled ,
369- autoZoom,
370- } }
371- durationInFrames = { Math . max ( frameMap . totalDurationInFrames , 1 ) }
372- compositionWidth = { compositionWidth }
373- compositionHeight = { compositionHeight }
374- fps = { frameMap . fps }
375- playbackRate = { playbackSpeed }
376- controls
377- showVolumeControls = { false }
378- renderPlayPauseButton = { renderPlayPauseButton }
379- renderFullscreenButton = { renderFullscreenButton }
380- renderCustomControls = { renderCustomControls }
381- autoPlay
382- loop = { false }
383- style = { {
384- width : '100%' ,
385- aspectRatio : `${ compositionWidth } /${ compositionHeight } ` ,
386- } }
387- />
394+ < div className = "player-wrapper" >
395+ < RemotionPlayer
396+ ref = { playerRef }
397+ component = { Composition }
398+ inputProps = { {
399+ frameMap,
400+ effects : effectsEnabled ,
401+ autoZoom,
402+ } }
403+ durationInFrames = { Math . max ( frameMap . totalDurationInFrames , 1 ) }
404+ compositionWidth = { compositionWidth }
405+ compositionHeight = { compositionHeight }
406+ fps = { frameMap . fps }
407+ playbackRate = { playbackSpeed }
408+ controls
409+ showVolumeControls = { false }
410+ renderPlayPauseButton = { renderPlayPauseButton }
411+ renderFullscreenButton = { renderFullscreenButton }
412+ renderCustomControls = { renderCustomControls }
413+ autoPlay
414+ loop = { false }
415+ style = { {
416+ width : '100%' ,
417+ aspectRatio : `${ compositionWidth } /${ compositionHeight } ` ,
418+ zIndex : 0 ,
419+ } }
420+ />
421+
422+ { /* Chapter markers overlay on Remotion's seek bar */ }
423+ { chapterMarkers . length > 0 && (
424+ < div className = "chapter-markers" >
425+ { chapterMarkers . map ( ( marker ) => (
426+ < Tooltip key = { marker . percent } title = { marker . title } >
427+ < div
428+ className = "chapter-marker"
429+ style = { { left : `${ marker . percent } %` } }
430+ onClick = { ( ) => {
431+ playerRef . current ?. seekTo ( marker . frame ) ;
432+ } }
433+ />
434+ </ Tooltip >
435+ ) ) }
436+ </ div >
437+ ) }
438+ </ div >
388439 </ div >
389440 </ div >
390441 ) ;
0 commit comments