1use std::collections::VecDeque;
19use std::path::{Path, PathBuf};
20use std::sync::atomic::{AtomicU32, Ordering};
21use std::sync::{Arc, Condvar, Mutex, MutexGuard};
22use std::thread::JoinHandle;
23use std::time::Instant;
24
25use crate::decode::{Decoder, Input, OUT_CHANNELS, OUT_RATE, Packet, Resampler, Scaler};
26use crate::ffi;
27use crate::media;
28use crate::sync::{self, AudioClock, FrameQueue, WallClock};
29use crate::tempo::Stretch;
30use crate::{Error, Frame, Info, Result};
31
32const AUDIO_AHEAD: f64 = 1.0;
34const VIDEO_PACKETS: usize = 48;
36const MAX_PACKET_BYTES: usize = 64 << 20;
38const CHUNK: usize = 1024;
40
41enum VideoMsg {
42 Packet(Packet),
43 Flush { generation: u64, target: f64 },
45 End,
46}
47
48struct State {
49 stop: bool,
50 generation: u64,
51 seek: Option<f64>,
53 rate: f64,
54 audio_on: bool,
56 paused: bool,
57 packets: VecDeque<VideoMsg>,
58 packet_bytes: usize,
59 frames: FrameQueue<Frame>,
60 frame_cap: usize,
61 video_done: bool,
63 demux_done: bool,
65 samples: VecDeque<f32>,
68 samples_pts: f64,
69 samples_rate: f64,
72 clock: Option<AudioClock>,
73}
74
75impl State {
76 fn audio_ahead(&self) -> f64 {
77 (self.samples.len() / OUT_CHANNELS) as f64 * self.samples_rate / f64::from(OUT_RATE)
78 }
79}
80
81struct Shared {
82 state: Mutex<State>,
83 cv: Condvar,
84 volume: AtomicU32,
86 epoch: Instant,
87}
88
89impl Shared {
90 fn lock(&self) -> MutexGuard<'_, State> {
91 self.state.lock().unwrap_or_else(|p| p.into_inner())
92 }
93
94 fn wait<'a>(&self, g: MutexGuard<'a, State>) -> MutexGuard<'a, State> {
95 self.cv.wait(g).unwrap_or_else(|p| p.into_inner())
96 }
97
98 fn wall(&self) -> f64 {
99 self.epoch.elapsed().as_secs_f64()
100 }
101}
102
103pub struct AudioOut {
107 shared: Arc<Shared>,
108 buf: Vec<f32>,
109 pos: usize,
110}
111
112impl AudioOut {
113 pub const CHANNELS: u16 = OUT_CHANNELS as u16;
114 pub const RATE: u32 = OUT_RATE;
115
116 pub fn next_sample(&mut self) -> Option<f32> {
118 if self.pos >= self.buf.len() {
119 self.refill()?;
120 }
121 let s = self.buf[self.pos];
122 self.pos += 1;
123 Some(s)
124 }
125
126 fn refill(&mut self) -> Option<()> {
127 let mut st = self.shared.lock();
128 if st.stop {
129 return None;
130 }
131 self.buf.clear();
132 self.pos = 0;
133 if st.paused || !st.audio_on || st.samples.is_empty() {
134 self.buf.resize(CHUNK, 0.0);
135 return Some(());
136 }
137 let n = st.samples.len().min(CHUNK) / OUT_CHANNELS * OUT_CHANNELS;
138 let volume = f32::from_bits(self.shared.volume.load(Ordering::Relaxed));
139 self.buf.extend(st.samples.drain(..n).map(|s| s * volume));
140 if self.buf.is_empty() {
141 self.buf.resize(CHUNK, 0.0);
142 return Some(());
143 }
144 let span = (n / OUT_CHANNELS) as f64 * st.samples_rate / f64::from(OUT_RATE);
145 st.clock = Some(AudioClock { pts: st.samples_pts, taken_at: self.shared.wall(), span, rate: st.samples_rate });
146 st.samples_pts += span;
147 drop(st);
148 self.shared.cv.notify_all();
149 Some(())
150 }
151}
152
153#[derive(Clone, Copy)]
155struct Snapshot {
156 audio: bool,
158 clock: Option<AudioClock>,
159}
160
161pub struct Player {
164 shared: Arc<Shared>,
165 threads: Vec<JoinHandle<()>>,
166 path: PathBuf,
167 info: Info,
168 audio_out: bool,
170 detach: bool,
173 wall: WallClock,
174 on_audio: bool,
176 generation: u64,
177 playing: bool,
178 looping: bool,
179 rate: f32,
180 muted: bool,
181 pos: f64,
183 show_next: u32,
186 seeking: Option<f64>,
188 ready: bool,
189}
190
191impl Player {
192 pub fn open(path: &Path, audio: bool) -> Result<(Player, Option<AudioOut>)> {
196 Player::open_sized(path, audio, u32::MAX, false)
197 }
198
199 pub fn open_preview(path: &Path, audio: bool, max_side: u32) -> Result<(Player, Option<AudioOut>)> {
205 Player::open_sized(path, audio, max_side, true)
206 }
207
208 fn open_sized(path: &Path, audio: bool, max_side: u32, detach: bool) -> Result<(Player, Option<AudioOut>)> {
209 let input = Input::open(path)?;
210 let api = input.api();
211 let (vi, vcodec) = input.best(ffi::AVMEDIA_TYPE_VIDEO).ok_or_else(|| Error("no video stream FFmpeg can decode".into()))?;
212 let video = Decoder::open(&input, vi, vcodec)?;
213 let info = media::info(&input, &video);
214 let sound = if audio { input.best(ffi::AVMEDIA_TYPE_AUDIO).and_then(|(ai, ac)| Decoder::open(&input, ai, ac).ok()) } else { None };
215 let audio_out = sound.is_some();
216 let shared = Arc::new(Shared {
217 state: Mutex::new(State {
218 stop: false,
219 generation: 0,
220 seek: None,
221 rate: 1.0,
222 audio_on: audio_out,
223 paused: true,
224 packets: VecDeque::new(),
225 packet_bytes: 0,
226 frames: FrameQueue::default(),
227 frame_cap: 4,
228 video_done: false,
229 demux_done: false,
230 samples: VecDeque::new(),
231 samples_pts: 0.0,
232 samples_rate: 1.0,
233 clock: None,
234 }),
235 cv: Condvar::new(),
236 volume: AtomicU32::new(1.0f32.to_bits()),
237 epoch: Instant::now(),
238 });
239 let fps = if info.fps > 0.0 { f64::from(info.fps) } else { 30.0 };
240 let packet = Packet::new(api)?;
241 let demux = {
242 let shared = shared.clone();
243 let resampler = Resampler::new(api);
244 std::thread::Builder::new()
245 .name("falcon-ffmpeg-demux".into())
246 .spawn(move || demux_loop(&shared, input, vi, sound, resampler, packet))
247 .map_err(|e| Error(e.to_string()))?
248 };
249 let decode = {
250 let shared = shared.clone();
251 let scaler = Scaler::new(api);
252 std::thread::Builder::new()
253 .name("falcon-ffmpeg-video".into())
254 .spawn(move || video_loop(&shared, video, scaler, fps, max_side))
255 .map_err(|e| Error(e.to_string()))?
256 };
257 let out = audio_out.then(|| AudioOut { shared: shared.clone(), buf: Vec::new(), pos: 0 });
258 let player = Player {
259 shared,
260 threads: vec![demux, decode],
261 path: path.to_path_buf(),
262 info,
263 audio_out,
264 detach,
265 wall: WallClock::new(0.0),
266 on_audio: false,
267 generation: 0,
268 playing: false,
269 looping: false,
270 rate: 1.0,
271 muted: false,
272 pos: 0.0,
273 show_next: 1,
274 seeking: None,
275 ready: false,
276 };
277 Ok((player, out))
278 }
279
280 pub fn info(&self) -> &Info {
281 &self.info
282 }
283
284 pub fn is_ready(&self) -> bool {
286 self.ready
287 }
288
289 fn fps(&self) -> f64 {
290 if self.info.fps > 0.0 { f64::from(self.info.fps) } else { 30.0 }
291 }
292
293 pub fn play(&mut self) {
295 if self.at_end() {
296 self.seek(0.0);
297 }
298 self.playing = true;
299 let wall = self.shared.wall();
300 self.wall.set(self.pos, wall);
301 self.wall.start(wall);
302 self.shared.lock().paused = false;
303 self.shared.cv.notify_all();
304 }
305
306 pub fn pause(&mut self) {
307 if self.playing {
308 self.pos = self.current_time();
309 }
310 self.playing = false;
311 self.wall.pause(self.shared.wall());
312 self.shared.lock().paused = true;
313 }
314
315 pub fn toggle(&mut self) {
316 if self.playing { self.pause() } else { self.play() }
317 }
318
319 pub fn is_playing(&self) -> bool {
320 self.playing
321 }
322
323 pub fn seek(&mut self, secs: f64) {
326 let t = secs.clamp(0.0, self.info.duration.max(0.0));
327 self.generation += 1;
328 {
329 let mut st = self.shared.lock();
330 st.generation = self.generation;
331 st.seek = Some(t);
332 st.frames.clear();
333 st.samples.clear();
334 st.clock = None;
335 st.video_done = false;
336 st.demux_done = false;
337 }
338 self.shared.cv.notify_all();
339 self.wall.set(t, self.shared.wall());
340 self.on_audio = false;
341 self.pos = t;
342 self.seeking = Some(t);
343 self.show_next = 1;
344 }
345
346 pub fn step(&mut self, frames: i32) {
350 self.pause();
351 if frames > 0 {
352 self.show_next = self.show_next.max(frames.unsigned_abs());
353 } else if frames < 0 {
354 let t = sync::step_back(self.pos, frames, self.fps());
355 self.seek(t);
356 }
357 }
358
359 pub fn rate(&self) -> f32 {
360 self.rate
361 }
362
363 pub fn set_rate(&mut self, rate: f32) {
367 let rate = rate.clamp(0.1, 16.0);
368 if (rate - self.rate).abs() < 1e-4 {
369 return;
370 }
371 let now = self.current_time();
372 self.rate = rate;
373 self.wall.set_rate(f64::from(rate), self.shared.wall());
374 let audio = {
375 let mut st = self.shared.lock();
376 st.rate = f64::from(rate);
377 st.audio_on
378 };
379 if audio {
380 self.seek(now);
381 }
382 }
383
384 pub fn set_volume(&mut self, volume: f32) {
386 self.shared.volume.store(volume.clamp(0.0, 1.0).to_bits(), Ordering::Relaxed);
387 }
388
389 pub fn set_muted(&mut self, muted: bool) {
392 if muted == self.muted {
393 return;
394 }
395 let now = self.current_time();
396 self.muted = muted;
397 if !self.audio_out {
398 return;
399 }
400 {
401 let mut st = self.shared.lock();
402 st.audio_on = !muted;
403 st.samples.clear();
404 st.clock = None;
405 }
406 let wall = self.shared.wall();
407 self.wall.set(now, wall);
408 self.on_audio = false;
409 self.pos = now;
410 if !muted {
411 self.seek(now);
412 }
413 }
414
415 pub fn set_looping(&mut self, looping: bool) {
416 self.looping = looping;
417 }
418
419 pub fn looping(&self) -> bool {
420 self.looping
421 }
422
423 pub fn current_time(&self) -> f64 {
425 if let Some(t) = self.seeking {
426 return t;
427 }
428 if !self.playing {
429 return self.pos;
430 }
431 self.read_clock(self.snapshot()).clamp(0.0, self.info.duration.max(0.0))
432 }
433
434 fn snapshot(&self) -> Snapshot {
435 let st = self.shared.lock();
436 Snapshot { audio: st.audio_on && !(st.demux_done && st.samples.is_empty()), clock: st.clock }
437 }
438
439 fn read_clock(&self, s: Snapshot) -> f64 {
443 let wall = self.shared.wall();
444 if s.audio || self.on_audio {
445 return s.clock.map_or(self.pos, |c| c.now(wall));
446 }
447 self.wall.now(wall)
448 }
449
450 fn follow_clock(&mut self, s: Snapshot) {
453 if self.on_audio && !s.audio {
454 let wall = self.shared.wall();
455 self.wall.set(s.clock.map_or(self.pos, |c| c.now(wall)), wall);
456 }
457 self.on_audio = s.audio;
458 }
459
460 fn at_end(&self) -> bool {
461 self.info.duration > 0.0 && self.pos >= self.info.duration - 1.0 / self.fps()
462 }
463
464 pub fn tick(&mut self) {
467 if !self.playing || self.seeking.is_some() || self.show_next > 0 {
468 return;
469 }
470 let ended = {
471 let st = self.shared.lock();
472 st.video_done && st.frames.is_empty() && !(st.audio_on && !st.samples.is_empty())
473 };
474 if !ended {
475 return;
476 }
477 if self.looping {
478 self.seek(0.0);
479 } else {
480 self.pause();
481 self.pos = self.info.duration;
482 }
483 }
484
485 pub fn frame(&mut self) -> Option<Frame> {
487 let shared = self.shared.clone();
488 let mut st = shared.lock();
489 if self.show_next > 0 {
490 let mut shown = None;
491 while self.show_next > 0 {
492 let Some(f) = st.frames.pop() else { break };
493 self.show_next -= 1;
494 shown = Some(f);
495 }
496 let done = st.video_done && st.frames.is_empty();
497 drop(st);
498 self.shared.cv.notify_all();
499 if let Some((pts, f)) = shown {
500 self.ready = true;
501 if self.seeking.take().is_some() || !self.playing {
502 self.pos = pts.max(0.0);
503 }
504 let wall = self.shared.wall();
505 self.wall.set(self.pos, wall);
506 return Some(f);
507 }
508 if done {
509 self.ready = true;
511 self.show_next = 0;
512 self.seeking = None;
513 }
514 return None;
515 }
516 if !self.playing {
517 return None;
518 }
519 let snapshot = Snapshot { audio: st.audio_on && !(st.demux_done && st.samples.is_empty()), clock: st.clock };
520 self.follow_clock(snapshot);
521 let now = self.read_clock(snapshot);
522 let due = st.frames.pop_due(now);
523 drop(st);
524 let (_, f) = due?;
525 self.shared.cv.notify_all();
526 Some(f)
527 }
528
529 pub fn current_frame(&self) -> Result<Frame> {
532 let t = self.current_time();
533 media::frame_at(&self.path, t)
534 }
535}
536
537impl Drop for Player {
538 fn drop(&mut self) {
539 self.shared.lock().stop = true;
540 self.shared.cv.notify_all();
541 if self.detach {
542 return;
543 }
544 for t in self.threads.drain(..) {
545 let _ = t.join();
546 }
547 }
548}
549
550fn demux_loop(shared: &Shared, mut input: Input, vi: i32, mut sound: Option<Decoder>, mut resampler: Resampler, mut packet: Packet) {
552 let api = input.api();
553 let mut stretch = Stretch::new(api);
554 let mut target = f64::NEG_INFINITY;
555 loop {
556 let mut st = shared.lock();
557 let (generation, rate, audio_on) = loop {
558 if st.stop {
559 return;
560 }
561 if let Some(t) = st.seek.take() {
562 let generation = st.generation;
563 drop(st);
564 let _ = input.seek(t);
565 if let Some(s) = sound.as_mut() {
566 s.flush();
567 }
568 resampler.reset();
569 stretch.reset();
570 target = t;
571 st = shared.lock();
572 if st.generation == generation {
573 st.packets.clear();
574 st.packet_bytes = 0;
575 st.packets.push_back(VideoMsg::Flush { generation, target: t });
576 st.samples.clear();
577 st.demux_done = false;
578 shared.cv.notify_all();
579 }
580 continue;
581 }
582 let need_audio = st.audio_on && sound.is_some() && st.audio_ahead() < AUDIO_AHEAD;
583 let need_video = st.packets.len() < VIDEO_PACKETS;
584 if !st.demux_done && (need_audio || need_video) && st.packet_bytes < MAX_PACKET_BYTES {
585 break (st.generation, st.rate, st.audio_on);
586 }
587 st = shared.wait(st);
588 };
589 drop(st);
590 packet.unref();
591 let more = input.read(&mut packet).unwrap_or(false);
592 if more && packet.stream_index() == vi {
593 let Ok(next) = Packet::new(api) else { return };
594 let p = std::mem::replace(&mut packet, next);
595 let mut st = shared.lock();
596 if st.generation == generation {
597 st.packet_bytes += p.size();
598 st.packets.push_back(VideoMsg::Packet(p));
599 drop(st);
600 shared.cv.notify_all();
601 }
602 continue;
603 }
604 let Some(s) = sound.as_mut() else {
605 if !more {
606 end(shared, generation);
607 }
608 continue;
609 };
610 if more && (packet.stream_index() != s.info.index || !audio_on) {
611 continue;
612 }
613 let _ = s.send(more.then_some(&packet));
614 let mut out = Vec::new();
615 let mut first_pts = None;
616 let mut tempo = stretch.at(rate);
617 let resampled_for = if tempo.is_some() { 1.0 } else { rate };
619 while s.receive() {
620 let f = s.frame();
621 let pts = s.pts(f);
622 let Ok(mut samples) = resampler.convert(f, resampled_for) else { continue };
623 let t = pts.unwrap_or(f64::NAN);
624 let mut start = t.is_finite().then_some(t);
625 if t.is_finite() && t < target {
626 let skip = (((target - t) * f64::from(OUT_RATE) / resampled_for) as usize * OUT_CHANNELS).min(samples.len());
628 samples.drain(..skip);
629 if samples.is_empty() {
630 continue;
631 }
632 start = Some(target);
633 }
634 if let Some(tempo) = tempo.as_deref_mut() {
635 let _ = tempo.push(&samples, start);
636 start = tempo.pull(&mut out);
637 } else {
638 out.extend_from_slice(&samples);
639 }
640 if let Some(start) = start {
641 first_pts.get_or_insert(start);
642 }
643 }
644 if !more && let Some(tempo) = tempo {
645 let _ = tempo.finish();
646 if let Some(start) = tempo.pull(&mut out) {
647 first_pts.get_or_insert(start);
648 }
649 }
650 let mut st = shared.lock();
651 if st.generation == generation && st.audio_on && !out.is_empty() {
652 if st.samples.is_empty() {
653 st.samples_pts = first_pts.unwrap_or(st.samples_pts).max(0.0);
654 st.samples_rate = rate;
655 }
656 st.samples.extend(out);
657 }
658 drop(st);
659 if !more {
660 end(shared, generation);
661 }
662 }
663}
664
665fn end(shared: &Shared, generation: u64) {
667 let mut st = shared.lock();
668 if st.generation == generation {
669 st.packets.push_back(VideoMsg::End);
670 st.demux_done = true;
671 }
672 drop(st);
673 shared.cv.notify_all();
674}
675
676fn video_loop(shared: &Shared, mut video: Decoder, mut scaler: Scaler, fps: f64, max_side: u32) {
678 let mut generation = 0;
679 let mut target = f64::NEG_INFINITY;
680 let mut delivered = false;
681 let mut last_pts = 0.0;
682 loop {
683 let msg = {
684 let mut st = shared.lock();
685 loop {
686 if st.stop {
687 return;
688 }
689 let control = matches!(st.packets.front(), Some(VideoMsg::Flush { .. } | VideoMsg::End));
690 if st.packets.front().is_some() && (control || st.frames.len() < st.frame_cap) {
691 break;
692 }
693 st = shared.wait(st);
694 }
695 let msg = st.packets.pop_front();
696 if let Some(VideoMsg::Packet(p)) = &msg {
697 st.packet_bytes = st.packet_bytes.saturating_sub(p.size());
698 }
699 drop(st);
700 shared.cv.notify_all();
701 msg
702 };
703 let Some(msg) = msg else { continue };
704 let ended = match msg {
705 VideoMsg::Flush { generation: g, target: t } => {
706 video.flush();
707 generation = g;
708 target = t;
709 delivered = false;
710 continue;
711 }
712 VideoMsg::Packet(p) => {
713 let _ = video.send(Some(&p));
714 false
715 }
716 VideoMsg::End => {
717 let _ = video.send(None);
718 true
719 }
720 };
721 while video.receive() {
722 let f = video.frame();
723 let pts = video.pts(f).unwrap_or(last_pts + 1.0 / fps);
724 last_pts = pts;
725 if sync::before_target(pts, target, fps) {
726 video.hold();
727 continue;
728 }
729 let Some(frame) = scaler.convert(f, &video.info, max_side) else { continue };
730 delivered = true;
731 push(shared, generation, pts, frame);
732 }
733 if ended {
734 if !delivered
736 && let Some(f) = video.held()
737 && let Some(frame) = scaler.convert(f, &video.info, max_side)
738 {
739 let pts = video.pts(f).unwrap_or(target);
740 push(shared, generation, pts, frame);
741 }
742 let mut st = shared.lock();
743 if st.generation == generation {
744 st.video_done = true;
745 }
746 drop(st);
747 shared.cv.notify_all();
748 }
749 }
750}
751
752fn push(shared: &Shared, generation: u64, pts: f64, frame: Frame) {
753 let mut st = shared.lock();
754 if st.generation == generation {
755 st.frame_cap = sync::queue_cap(frame.rgba.len());
756 st.frames.push(pts, frame);
757 }
758 drop(st);
759 shared.cv.notify_all();
760}