-
Notifications
You must be signed in to change notification settings - Fork 1.4k
Playback performance and sync #1595
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
e1fb5e4
3eacadb
9980169
bde144b
f2efab8
576f1fd
634e18f
ab756a9
a0e2a17
0a19cdb
16f7170
53f21e1
7b8a79d
0202484
9f94a26
b7652b7
b1b534e
af068d9
a7ad109
d6e0f6d
e0dc3ef
5fd803b
91055a8
ba37061
c1e1f85
1434554
c332ada
19b0083
efc29ab
cc1cd3f
7f15843
703e397
bc1d20f
766dabf
625efa6
87fa391
b911d5d
ee2c5a9
1b105c7
2fb1b74
ffc84f2
d770bc4
51f9832
35d5a5e
3f99687
33f15fe
59ef247
9fadd0f
c87ca9e
05bbd0a
8048eb1
6bc816e
46413ab
1b7a2a5
7567a1f
baace4f
e98a64e
fa6d23a
b2c8a7d
297b9b4
ff55bcf
68a0128
f19a985
2b2ea79
ed3efe5
0965dec
ba334da
c801652
e4a5319
3fcb14e
796e617
b2b4804
eea5e69
97bd11c
127f727
a848479
232608d
0f6795e
42017c5
b459535
fd2b6c7
bf7aa98
5995626
1370bd6
59d1e6f
e30027f
bcbb5f4
bed0925
3ca8913
43db499
225de24
b7d0737
8fe2d5a
055cf42
ed40518
afa9ea5
d83dc86
79dd6d6
a5ef5aa
34f45b5
f9e371c
56235f5
3c4e004
78b1266
2b7ae23
df142fc
2afdbd6
5b1807c
baba6e6
43f298d
4becc57
bee50f5
61d10d7
d140e69
9b4e9b9
7b26c21
65148e5
e6a5548
6924c38
5498a51
75add0b
b13aa6c
56557c2
1cd997c
ae13562
dc516ba
47d7947
25435cf
afbd5d1
3effc17
9c465a6
4455c5f
4896872
4860d74
f4a3cb8
4bbfa59
0d6f995
913e6fd
2219362
b05da47
63ba3f6
7f0778b
f12e5c5
5d56b00
3d20258
cd2f465
fbc5fe4
16550d2
eae4930
7e368c6
58d2fb5
2215938
7ca0f42
8ebe171
e9f609f
39f9465
51df1ca
2b600a3
246fd2e
20bf8ce
179984a
caa8771
9f110e4
d03cf49
af1de59
684b0ed
78eb47d
0ab9cda
3149a66
9f4d607
18a0d7d
cc9c79f
9b9335c
c62eec6
fea600f
67f58a2
59892cf
ba37eb3
8da4cbf
f0e7760
35c5a41
6461262
2fd740e
83da539
cfa718f
99a1063
45059ca
81ad88a
baacc39
45265df
a8abcf5
4a94a8d
3664636
12a4a2f
a846a89
e8a0910
8f2d0e5
55df9a5
f87f62e
0ddd232
4766b55
cc18f5a
6cd720a
a8d52a5
004e281
5ddf604
1a12a55
c8cccf0
09f2b57
6ce10a3
21c57c9
796e839
9673152
74fe6c8
358812a
9331188
34b063d
e52e799
b6d0506
f9a2ed9
472e79c
ade9d8b
d797e3f
dc7d73e
e808b16
e34446b
27dd799
d53082b
ee3ee2d
5e8ffd6
51044cf
7f796ff
3c81ece
75892b7
09e8307
dad0f79
94fdebe
09bb2f0
6d0b272
6b4f0c9
4beb9af
c6e1116
d632a0f
83cfbad
2788856
fbcd20e
6e05540
977414e
1fea346
0cbac24
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -1915,11 +1915,29 @@ async fn set_playhead_position( | |
| editor_instance: WindowEditorInstance, | ||
| frame_number: u32, | ||
| ) -> Result<(), String> { | ||
| editor_instance | ||
| .modify_and_emit_state(|state| { | ||
| state.playhead_position = frame_number; | ||
| }) | ||
| .await; | ||
| let state_changed = { | ||
| let state = editor_instance.state.lock().await; | ||
| state.playhead_position != frame_number | ||
| }; | ||
|
|
||
| if state_changed { | ||
| editor_instance | ||
| .modify_and_emit_state(|state| { | ||
| state.playhead_position = frame_number; | ||
| }) | ||
| .await; | ||
| } | ||
|
|
||
| let playback_handle = if state_changed { | ||
| let state = editor_instance.state.lock().await; | ||
| state.playback_task.clone() | ||
| } else { | ||
| None | ||
| }; | ||
|
|
||
| if let Some(handle) = playback_handle { | ||
| handle.seek(frame_number); | ||
| } | ||
|
|
||
| Ok(()) | ||
| } | ||
|
|
@@ -2539,11 +2557,29 @@ async fn is_camera_window_open(app: AppHandle) -> bool { | |
| #[specta::specta] | ||
| #[instrument(skip(editor_instance))] | ||
| async fn seek_to(editor_instance: WindowEditorInstance, frame_number: u32) -> Result<(), String> { | ||
| editor_instance | ||
| .modify_and_emit_state(|state| { | ||
| state.playhead_position = frame_number; | ||
| }) | ||
| .await; | ||
| let state_changed = { | ||
| let state = editor_instance.state.lock().await; | ||
| state.playhead_position != frame_number | ||
| }; | ||
|
|
||
| if state_changed { | ||
| editor_instance | ||
| .modify_and_emit_state(|state| { | ||
| state.playhead_position = frame_number; | ||
| }) | ||
| .await; | ||
| } | ||
|
|
||
| let playback_handle = if state_changed { | ||
| let state = editor_instance.state.lock().await; | ||
| state.playback_task.clone() | ||
| } else { | ||
| None | ||
| }; | ||
|
|
||
| if let Some(handle) = playback_handle { | ||
| handle.seek(frame_number); | ||
| } | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seek dedupe uses stale stateMedium Severity
Additional Locations (1) |
||
|
|
||
| Ok(()) | ||
| } | ||
|
|
||
| Original file line number | Diff line number | Diff line change | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
|
|
@@ -7,6 +7,7 @@ import { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| createSignal, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Index, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| type JSX, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onCleanup, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onMount, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| Show, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } from "solid-js"; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -89,7 +90,6 @@ export function Timeline() { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| editorState, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| projectActions, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| meta, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| previewResolutionBase, | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } = useEditorContext(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const duration = () => editorInstance.recordingDuration; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -221,6 +221,17 @@ export function Timeline() { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let pendingScrollDelta = 0; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let scrollRafId: number | null = null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let pendingSeekFrame: number | null = null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let seekRafId: number | null = null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let seekInFlight = false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let inFlightSeekFrame: number | null = null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| let lastCompletedSeekFrame: number | null = null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| onCleanup(() => { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (zoomRafId !== null) cancelAnimationFrame(zoomRafId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (scrollRafId !== null) cancelAnimationFrame(scrollRafId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (seekRafId !== null) cancelAnimationFrame(seekRafId); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| }); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unmount doesn’t cancel pending seek pipelineLow Severity
Additional Locations (1) |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function flushPendingZoom() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (pendingZoomDelta === 0 || pendingZoomOrigin === null) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
@@ -266,41 +277,65 @@ export function Timeline() { | |||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async function handleUpdatePlayhead(e: MouseEvent) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function scheduleSeek(frameNumber: number) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| frameNumber === pendingSeekFrame || | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| frameNumber === inFlightSeekFrame || | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| frameNumber === lastCompletedSeekFrame | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Seek dedupe blocks valid repeat seeksMedium Severity
Additional Locations (1) |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pendingSeekFrame = frameNumber; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (seekRafId === null) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| seekRafId = requestAnimationFrame(flushPendingSeek); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
+280
to
+292
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. deduplicates seeks against Prompt To Fix With AIThis is a comment left during a code review.
Path: apps/desktop/src/routes/editor/Timeline/index.tsx
Line: 280:292
Comment:
deduplicates seeks against `lastCompletedSeekFrame` which is only set on success (line 311) - if `seekTo` throws (line 313), `lastCompletedSeekFrame` stays stale, and retrying the same frame later gets incorrectly rejected as duplicate on line 284
How can I resolve this? If you propose a fix, please make it concise. |
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| async function flushPendingSeek() { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| seekRafId = null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (seekInFlight || pendingSeekFrame === null) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (pendingSeekFrame !== null && seekRafId === null) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| seekRafId = requestAnimationFrame(flushPendingSeek); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const frameNumber = pendingSeekFrame; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| pendingSeekFrame = null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| seekInFlight = true; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| inFlightSeekFrame = frameNumber; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await commands.seekTo(frameNumber); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| lastCompletedSeekFrame = frameNumber; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (err) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.error("Failed to seek timeline playhead:", err); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } finally { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| seekInFlight = false; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| inFlightSeekFrame = null; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (pendingSeekFrame !== null && seekRafId === null) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| seekRafId = requestAnimationFrame(flushPendingSeek); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| function handleUpdatePlayhead(e: MouseEvent) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const { left } = timelineBounds; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if ( | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| zoomSegmentDragState.type !== "moving" && | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| sceneSegmentDragState.type !== "moving" && | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| maskSegmentDragState.type !== "moving" && | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| textSegmentDragState.type !== "moving" | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| ) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Guard against missing bounds and clamp computed time to [0, totalDuration()] | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (left == null) return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const rawTime = | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| secsPerPixel() * (e.clientX - left) + transform().position; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const newTime = Math.min(Math.max(0, rawTime), totalDuration()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // If playing, some backends require restart to seek reliably | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (editorState.playing) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| try { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await commands.stopPlayback(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // Round to nearest frame to prevent off-by-one drift | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const targetFrame = Math.round(newTime * FPS); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await commands.seekTo(targetFrame); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| // If the user paused during these async ops, bail out without restarting | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| if (!editorState.playing) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setEditorState("playbackTime", newTime); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| return; | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| await commands.startPlayback(FPS, previewResolutionBase()); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setEditorState("playing", true); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } catch (err) { | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| console.error("Failed to seek during playback:", err); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const total = totalDuration(); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const maxFrame = Math.max(0, Math.ceil(total * FPS) - 1); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
Comment on lines
334
to
+336
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Minor:
Suggested change
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| const targetFrame = Math.min(Math.round(newTime * FPS), maxFrame); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| scheduleSeek(targetFrame); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| setEditorState("playbackTime", newTime); | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
| } | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|
|
||||||||||||||||||||||||||||||||||||||||||||||||||||||||||


There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
locks
statetwice (lines 1919, 1932) - second lock held duringseek()call which may briefly block - consider reading bothplayhead_positionandplayback_taskin single lock scope to reduce hold timePrompt To Fix With AI