Skip to content

Commit a4fe184

Browse files
committed
Merge branch 'main' of https://github.com/CapSoftware/Cap
2 parents cf0ad7f + 50898da commit a4fe184

File tree

2 files changed

+89
-43
lines changed

2 files changed

+89
-43
lines changed

crates/editor/src/editor_instance.rs

Lines changed: 12 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::editor;
2-
use crate::playback::{self, PlaybackHandle};
2+
use crate::playback::{self, PlaybackHandle, PlaybackStartError};
33
use cap_audio::AudioData;
44
use cap_project::StudioRecordingMeta;
55
use cap_project::{CursorEvents, ProjectConfiguration, RecordingMeta, RecordingMetaInner, XY};
@@ -10,7 +10,7 @@ use cap_rendering::{
1010
use std::ops::Deref;
1111
use std::{path::PathBuf, sync::Arc};
1212
use tokio::sync::{Mutex, watch};
13-
use tracing::trace;
13+
use tracing::{trace, warn};
1414

1515
pub struct EditorInstance {
1616
pub project_path: PathBuf,
@@ -145,15 +145,22 @@ impl EditorInstance {
145145

146146
let start_frame_number = state.playhead_position;
147147

148-
let playback_handle = playback::Playback {
148+
let playback_handle = match (playback::Playback {
149149
segments: self.segments.clone(),
150150
renderer: self.renderer.clone(),
151151
render_constants: self.render_constants.clone(),
152152
start_frame_number,
153153
project: self.project_config.0.subscribe(),
154-
}
154+
})
155155
.start(fps, resolution_base)
156-
.await;
156+
.await
157+
{
158+
Ok(handle) => handle,
159+
Err(PlaybackStartError::InvalidFps) => {
160+
warn!(fps, "Skipping playback start due to invalid FPS");
161+
return;
162+
}
163+
};
157164

158165
let prev = state.playback_task.replace(playback_handle.clone());
159166

crates/editor/src/playback.rs

Lines changed: 77 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,11 @@ use crate::{
2020
segments::get_audio_segments,
2121
};
2222

23+
#[derive(Debug)]
24+
pub enum PlaybackStartError {
25+
InvalidFps,
26+
}
27+
2328
pub struct Playback {
2429
pub renderer: Arc<editor::RendererHandle>,
2530
pub render_constants: Arc<RenderVideoConstants>,
@@ -42,7 +47,18 @@ pub struct PlaybackHandle {
4247
}
4348

4449
impl Playback {
45-
pub async fn start(self, fps: u32, resolution_base: XY<u32>) -> PlaybackHandle {
50+
pub async fn start(
51+
self,
52+
fps: u32,
53+
resolution_base: XY<u32>,
54+
) -> Result<PlaybackHandle, PlaybackStartError> {
55+
let fps_f64 = fps as f64;
56+
57+
if !(fps_f64.is_finite() && fps_f64 > 0.0) {
58+
warn!(fps, "Invalid FPS provided for playback start");
59+
return Err(PlaybackStartError::InvalidFps);
60+
}
61+
4662
let (stop_tx, mut stop_rx) = watch::channel(false);
4763
stop_rx.borrow_and_update();
4864

@@ -72,60 +88,83 @@ impl Playback {
7288
}
7389
.spawn();
7490

75-
loop {
76-
let time =
77-
(self.start_frame_number as f64 / fps as f64) + start.elapsed().as_secs_f64();
78-
let frame_number = (time * fps as f64).floor() as u32;
91+
let frame_duration = Duration::from_secs_f64(1.0 / fps_f64);
92+
let mut frame_number = self.start_frame_number;
93+
94+
'playback: loop {
95+
let frame_offset = frame_number.saturating_sub(self.start_frame_number) as f64;
96+
let next_deadline = start + frame_duration.mul_f64(frame_offset);
7997

80-
if frame_number as f64 >= fps as f64 * duration {
98+
tokio::select! {
99+
_ = stop_rx.changed() => break 'playback,
100+
_ = tokio::time::sleep_until(next_deadline) => {}
101+
}
102+
103+
if *stop_rx.borrow() {
81104
break;
82-
};
105+
}
106+
107+
let playback_time = frame_number as f64 / fps_f64;
108+
if playback_time >= duration {
109+
break;
110+
}
83111

84112
let project = self.project.borrow().clone();
85113

86-
if let Some((segment_time, segment_i)) = project.get_segment_time(time) {
87-
let segment = &self.segments[segment_i as usize];
88-
let clip_config = project.clips.iter().find(|v| v.index == segment_i);
89-
let clip_offsets = clip_config.map(|v| v.offsets).unwrap_or_default();
114+
let Some((segment_time, segment_i)) = project.get_segment_time(playback_time)
115+
else {
116+
break;
117+
};
90118

91-
let data = tokio::select! {
92-
_ = stop_rx.changed() => { break; },
93-
data = segment.decoders.get_frames(segment_time as f32, !project.camera.hide, clip_offsets) => { data }
94-
};
119+
let segment = &self.segments[segment_i as usize];
120+
let clip_offsets = project
121+
.clips
122+
.iter()
123+
.find(|v| v.index == segment_i)
124+
.map(|v| v.offsets)
125+
.unwrap_or_default();
126+
127+
let data = tokio::select! {
128+
_ = stop_rx.changed() => break 'playback,
129+
data = segment
130+
.decoders
131+
.get_frames(segment_time as f32, !project.camera.hide, clip_offsets) => data,
132+
};
95133

96-
if let Some(segment_frames) = data {
97-
let uniforms = ProjectUniforms::new(
98-
&self.render_constants,
99-
&project,
100-
frame_number,
101-
fps,
102-
resolution_base,
103-
&segment.cursor,
104-
&segment_frames,
105-
);
106-
107-
self.renderer
108-
.render_frame(segment_frames, uniforms, segment.cursor.clone())
109-
.await;
110-
}
111-
}
134+
if let Some(segment_frames) = data {
135+
let uniforms = ProjectUniforms::new(
136+
&self.render_constants,
137+
&project,
138+
frame_number,
139+
fps,
140+
resolution_base,
141+
&segment.cursor,
142+
&segment_frames,
143+
);
112144

113-
tokio::time::sleep_until(
114-
start
115-
+ (frame_number - self.start_frame_number)
116-
* Duration::from_secs_f32(1.0 / fps as f32),
117-
)
118-
.await;
145+
self.renderer
146+
.render_frame(segment_frames, uniforms, segment.cursor.clone())
147+
.await;
148+
}
119149

120150
event_tx.send(PlaybackEvent::Frame(frame_number)).ok();
151+
152+
frame_number = frame_number.saturating_add(1);
153+
154+
let expected_frame = self.start_frame_number
155+
+ (start.elapsed().as_secs_f64() * fps_f64).floor() as u32;
156+
157+
if frame_number < expected_frame {
158+
frame_number = expected_frame;
159+
}
121160
}
122161

123162
stop_tx.send(true).ok();
124163

125164
event_tx.send(PlaybackEvent::Stop).ok();
126165
});
127166

128-
handle
167+
Ok(handle)
129168
}
130169
}
131170

0 commit comments

Comments
 (0)