1use std::collections::VecDeque;
9use std::path::{Path, PathBuf};
10use std::sync::atomic::{AtomicBool, AtomicU64, Ordering};
11use std::sync::{Arc, Mutex, mpsc};
12use std::thread::JoinHandle;
13use std::time::{Duration, Instant};
14
15use ff_decode::HardwareAccel;
16#[cfg(feature = "timeline")]
17use ff_pipeline::timeline::Timeline;
18
19use super::decode_buffer::DecodeBuffer;
20use super::master_clock::MasterClock;
21use super::player_handle::PlayerHandle;
22use super::player_runner::{PlayerRunner, spawn_audio_thread};
23
24use crate::error::PreviewError;
25
26const CHANNEL_CAP: usize = 64;
29pub(crate) const DECODED_SAMPLE_RATE: u32 = 48_000;
33
34pub enum PlayerCommand {
39 Play,
41 Pause,
43 Stop,
46 Seek(Duration),
49 SetRate(f64),
51 SetAvOffset(i64),
53 #[cfg(feature = "timeline")]
60 UpdateLayout(Box<Timeline>),
61}
62
63pub struct PreviewPlayer {
88 path: PathBuf,
89 decode_buf: Option<DecodeBuffer>,
91 fps: f64,
92 clock: Option<MasterClock>,
94 audio_buf: Option<Arc<Mutex<VecDeque<f32>>>>,
95 audio_cancel: Option<Arc<AtomicBool>>,
96 audio_handle: Option<JoinHandle<()>>,
97 duration_millis: u64,
98 active_path: PathBuf,
99}
100
101impl PreviewPlayer {
102 pub fn open(path: impl AsRef<Path>) -> Result<Self, PreviewError> {
113 let path = path.as_ref();
114 let info = ff_probe::open(path)?;
115
116 if !info.has_video() && !info.has_audio() {
117 return Err(PreviewError::Ffmpeg {
118 code: -1,
119 message: "file has neither a video nor an audio stream".into(),
120 });
121 }
122
123 let fps = info.frame_rate().unwrap_or(30.0).max(1.0);
124
125 let d = info.duration();
126 let duration_millis = if d.is_zero() {
127 u64::MAX
128 } else {
129 u64::try_from(d.as_millis()).unwrap_or(u64::MAX)
130 };
131
132 let clock = if info.has_audio() {
133 MasterClock::Audio {
134 samples_consumed: Arc::new(AtomicU64::new(0)),
135 sample_rate: DECODED_SAMPLE_RATE,
136 rate: 1.0,
137 samples_base: 0,
138 pts_base: Duration::ZERO,
139 fallback: None,
140 }
141 } else {
142 log::debug!(
143 "using system clock fallback path={} no_audio=true",
144 path.display()
145 );
146 MasterClock::System {
147 started_at: Instant::now(),
148 base_pts: Duration::ZERO,
149 rate: 1.0,
150 }
151 };
152
153 let decode_buf = if info.has_video() {
154 Some(DecodeBuffer::open(path).build()?)
155 } else {
156 log::debug!(
157 "audio-only file; skipping video decode buffer path={}",
158 path.display()
159 );
160 None
161 };
162
163 let (audio_buf, audio_cancel, audio_handle) = if let MasterClock::Audio { .. } = &clock {
164 let buf = Arc::new(Mutex::new(VecDeque::<f32>::new()));
165 let cancel = Arc::new(AtomicBool::new(false));
166 let handle = spawn_audio_thread(
167 path.to_path_buf(),
168 Duration::ZERO,
169 Arc::clone(&buf),
170 Arc::clone(&cancel),
171 );
172 (Some(buf), Some(cancel), Some(handle))
173 } else {
174 (None, None, None)
175 };
176
177 Ok(PreviewPlayer {
178 path: path.to_path_buf(),
179 decode_buf,
180 fps,
181 clock: Some(clock),
182 audio_buf,
183 audio_cancel,
184 audio_handle,
185 duration_millis,
186 active_path: path.to_path_buf(),
187 })
188 }
189
190 #[must_use]
202 #[allow(clippy::expect_used)]
203 pub fn split(mut self) -> (PlayerRunner, PlayerHandle) {
204 let current_pts = Arc::new(AtomicU64::new(0));
205 let paused = Arc::new(AtomicBool::new(false));
206 let stopped = Arc::new(AtomicBool::new(false));
207 let (cmd_tx, cmd_rx) = mpsc::sync_channel(CHANNEL_CAP);
208 let (event_tx, event_rx) = mpsc::sync_channel(CHANNEL_CAP);
209
210 let clock = self.clock.take().expect("clock consumed before split");
211 let samples_consumed = match &clock {
212 MasterClock::Audio {
213 samples_consumed, ..
214 } => Some(Arc::clone(samples_consumed)),
215 MasterClock::System { .. } => None,
216 };
217
218 let audio_buf_for_handle = self.audio_buf.clone();
219 let duration_millis = self.duration_millis;
220
221 let runner = PlayerRunner {
222 path: self.path.clone(),
223 cmd_rx,
224 event_tx,
225 decode_buf: self.decode_buf.take(),
226 fps: self.fps,
227 sink: None,
228 clock,
229 audio_buf: self.audio_buf.take(),
230 audio_cancel: self.audio_cancel.take(),
231 audio_handle: self.audio_handle.take(),
232 sws: super::playback_inner::SwsRgbaConverter::new(),
233 rgba_buf: Vec::new(),
234 active_path: self.active_path.clone(),
235 current_pts: Arc::clone(¤t_pts),
236 paused: Arc::clone(&paused),
237 stopped: Arc::clone(&stopped),
238 av_offset_ms: 0,
239 rate: 1.0,
240 duration_millis,
241 frame_cache: None,
242 hw_accel: HardwareAccel::Auto,
243 };
244
245 let handle = PlayerHandle {
246 cmd_tx,
247 event_rx: Arc::new(Mutex::new(event_rx)),
248 current_pts,
249 audio_buf: audio_buf_for_handle,
250 samples_consumed,
251 audio_mixer: None,
252 paused,
253 stopped,
254 duration_millis,
255 };
256
257 (runner, handle)
258 }
259}
260
261impl Drop for PreviewPlayer {
262 fn drop(&mut self) {
263 if let Some(cancel) = &self.audio_cancel {
264 cancel.store(true, Ordering::Release);
265 }
266 if let Some(h) = self.audio_handle.take() {
267 let _ = h.join();
268 }
269 }
270}