@@ -55,7 +55,11 @@ import {
5555 * calques interactifs (zoom gimbal, annotations, drag webcam) qui restent des
5656 * éléments DOM cliquables au-dessus.
5757 */
58- export function NativeCompositorOverlay ( ) {
58+ export function NativeCompositorOverlay ( {
59+ playUneditedTimeline = false ,
60+ } : {
61+ playUneditedTimeline ?: boolean ;
62+ } ) {
5963 const canvasRef = useRef < HTMLCanvasElement > ( null ) ;
6064 const previousActiveClipIdRef = useRef < string | null > ( null ) ;
6165 const document = useProjectStore ( ( s ) => s . document ) ;
@@ -70,22 +74,25 @@ export function NativeCompositorOverlay() {
7074 ( ) => 0 ,
7175 ) ;
7276
73- // `currentTimeSec` est en temps RAW/document (la règle où les trims occupent encore leur
74- // place — même référentiel que le playhead V4 et l'overlay webcam web). Le natif, lui, joue
75- // les segments COMPACTÉS (`resolveVisibleClips`, trims retirés) — le même flux que la SCÈNE
76- // envoyée via `setNativeScene`. `resolveNativePosition` fait le pont : il mappe le playhead
77- // RAW → temps source via le layout RAW (`document.timeline.clips`) PUIS localise le segment
78- // compacté correspondant, d'où sortent le `clipIndex` natif et la bonne paire écran/webcam.
79- // Sans ça (ancien `resolveNativePlaybackPosition(nativeClips, currentTimeSec)`), un playhead
80- // RAW lu contre des clips compactés désignait le mauvais clip après un trim → mauvaise caméra
81- // + décalage écran/cam.
82- const nativeClips = useMemo ( ( ) => {
83- if ( ! document ) return [ ] ;
84- return resolveVisibleClips ( document ) ;
85- } , [ document ] ) ;
77+ // `currentTimeSec` stays on the raw document ruler. During normal playback the native scene
78+ // uses trim-compressed segments; while recording, `playbackDocument` removes trims and speed
79+ // regions so the compositor shows the same unedited timeline as the video clock.
80+ const playbackDocument = useMemo ( ( ) => {
81+ if ( ! document || ! playUneditedTimeline ) return document ;
82+ return {
83+ ...document ,
84+ timeline : { ...document . timeline , trimRanges : [ ] } ,
85+ legacyEditor : document . legacyEditor ? { ...document . legacyEditor , speedRegions : [ ] } : null ,
86+ } ;
87+ } , [ document , playUneditedTimeline ] ) ;
88+ const nativeClips = useMemo (
89+ ( ) => ( playbackDocument ? resolveVisibleClips ( playbackDocument ) : [ ] ) ,
90+ [ playbackDocument ] ,
91+ ) ;
8692 const activePosition = useMemo (
87- ( ) => resolveNativePosition ( currentTimeSec , nativeClips , document ?. timeline . clips ?? [ ] ) ,
88- [ nativeClips , currentTimeSec , document ] ,
93+ ( ) =>
94+ resolveNativePosition ( currentTimeSec , nativeClips , playbackDocument ?. timeline . clips ?? [ ] ) ,
95+ [ nativeClips , currentTimeSec , playbackDocument ] ,
8996 ) ;
9097 const activeClip = activePosition ?. clip ?? null ;
9198
@@ -149,12 +156,12 @@ export function NativeCompositorOverlay() {
149156 // quand on pousse.
150157 // biome-ignore lint/correctness/useExhaustiveDependencies: size revision
151158 useEffect ( ( ) => {
152- if ( viewId === null || ! document ) {
159+ if ( viewId === null || ! playbackDocument ) {
153160 return ;
154161 }
155162 try {
156163 const webcamSourceSize = cameraPath ? getWebcamNativeSize ( cameraPath ) : null ;
157- const scene = buildSceneDescription ( document , webcamSourceSize ) ;
164+ const scene = buildSceneDescription ( playbackDocument , webcamSourceSize ) ;
158165 setNativeScene ( JSON . stringify ( scene ) ) ;
159166 } catch ( error ) {
160167 console . warn ( "[compositor-view] build/push scene failed:" , error ) ;
@@ -164,7 +171,7 @@ export function NativeCompositorOverlay() {
164171 // re-read fresh via getWebcamNativeSize() above on every run (biome flags this as
165172 // an "unnecessary" dependency, but removing it would mean a probed webcam size
166173 // arriving after mount never gets pushed to native).
167- } , [ viewId , document , sources , _webcamSizeRevision ] ) ;
174+ } , [ viewId , playbackDocument , sources , _webcamSizeRevision ] ) ;
168175
169176 // SYNCHRO COMPLETE DES PARAMS, en un seul endroit.
170177 //
@@ -195,14 +202,17 @@ export function NativeCompositorOverlay() {
195202 const activeSourceTimeSec = activePosition ?. sourceTimeSec ?? null ;
196203 // Matched on the raw ruler, like `currentTimeSec` and the regions' pills.
197204 const speedRegions = useMemo (
198- ( ) => ( document ? readSpeedRegions < SpeedRegion > ( document ) : [ ] ) ,
199- [ document ] ,
205+ ( ) => ( playbackDocument ? readSpeedRegions < SpeedRegion > ( playbackDocument ) : [ ] ) ,
206+ [ playbackDocument ] ,
200207 ) ;
201- const playbackSpeed = findActiveSpeedRegion ( speedRegions , currentTimeSec * 1000 ) ?. speed ?? 1 ;
208+ const playbackSpeed = playUneditedTimeline
209+ ? 1
210+ : ( findActiveSpeedRegion ( speedRegions , currentTimeSec * 1000 ) ?. speed ?? 1 ) ;
202211 const pendingTargetClipIdRef = useRef < string | null > ( null ) ;
203212
204213 const playing = useProjectStore ( ( s ) => s . playing ) ;
205214 const wasPlayingRef = useRef ( playing ) ;
215+ const previousUneditedTimelineRef = useRef ( playUneditedTimeline ) ;
206216
207217 // Change les décodeurs screen/webcam quand le playhead entre dans un autre clip, à l'arrêt.
208218 //
@@ -214,6 +224,8 @@ export function NativeCompositorOverlay() {
214224 useEffect ( ( ) => {
215225 const justPaused = wasPlayingRef . current && ! playing ;
216226 wasPlayingRef . current = playing ;
227+ const playbackModeChanged = previousUneditedTimelineRef . current !== playUneditedTimeline ;
228+ previousUneditedTimelineRef . current = playUneditedTimeline ;
217229 if (
218230 viewId === null ||
219231 ! document ||
@@ -230,7 +242,7 @@ export function NativeCompositorOverlay() {
230242 // y retourne, sans quoi le recalage en temps (`setNativeTime`) chercherait dans le
231243 // mauvais fichier.
232244 const nativeElsewhere = justPaused && native !== null && native . clipIndex !== activeClipIndex ;
233- if ( ! clipChanged && ! nativeElsewhere ) {
245+ if ( ! clipChanged && ! playbackModeChanged && ! nativeElsewhere ) {
234246 return ;
235247 }
236248 const asset = document . assets . find ( ( candidate ) => candidate . id === activeClip . assetId ) ;
@@ -305,6 +317,7 @@ export function NativeCompositorOverlay() {
305317 activeClipIndex ,
306318 activeSourceTimeSec ,
307319 playing ,
320+ playUneditedTimeline ,
308321 nativeClips ,
309322 playbackSpeed ,
310323 ] ) ;
0 commit comments