//! Run through scripts/validate/playback-fixture.sh for real FFmpeg seek coverage. use clipbin_core::store::Store; use clipbin_media::{ decode::{Decoder, scale}, player::{Picture, Player}, }; use std::{ path::PathBuf, thread, time::{Duration, Instant}, }; fn fixture() -> Option { std::env::var_os("CLIPBIN_TEST_MEDIA_DIR").map(PathBuf::from) } #[test] fn seeking_matches_sequential_frames_in_each_independent_segment() { let Some(directory) = fixture() else { return; }; for segment in 0..3 { let path = directory.join(format!("1/segment-{segment:03}.mkv")); let mut sequential = Decoder::open(&path, 60).unwrap(); let mut expected = Vec::new(); while let Some((index, frame)) = sequential.next_frame().unwrap() { expected.push((index, scale(&frame, (160, 90)).unwrap().bgra)); } assert_eq!(expected.len(), 120); for target in [0, 1, 59, 60, 61, 119] { let mut decoder = Decoder::open(&path, 60).unwrap(); decoder .seek(Duration::from_secs_f64(target as f64 / 60.)) .unwrap(); loop { let (index, frame) = decoder.next_frame().unwrap().unwrap(); if index >= target { assert_eq!(index, target); assert_eq!( scale(&frame, (160, 90)).unwrap().bgra, expected[target as usize].1 ); break; } } } } } fn wait(player: &Player, at: Duration) -> clipbin_media::player::Snapshot { let until = Instant::now() + Duration::from_secs(5); loop { let snapshot = player.snapshot(); if snapshot.time == at && !matches!(snapshot.picture, Picture::Loading) { return snapshot; } assert!(Instant::now() < until, "seek timed out: {snapshot:?}"); thread::sleep(Duration::from_millis(10)); } } #[test] fn player_crosses_boundaries_and_gaps_and_discards_superseded_seeks() { let Some(directory) = fixture() else { return; }; let player = Player::new(); player.audio(true, 1.); player.open(Store { root: directory }, 1, Duration::ZERO); assert!(matches!( wait(&player, Duration::ZERO).picture, Picture::Frame(_) )); for millis in [1999, 2000, 2500, 4000, 5999, 6000, 7000] { let at = Duration::from_millis(millis); player.seek(at); let snapshot = wait(&player, at); assert_eq!( matches!(snapshot.picture, Picture::Gap), (4000..6000).contains(&millis), "at {millis}" ); } for i in 0..50 { player.seek(Duration::from_millis(i * 40)); } player.seek(Duration::from_millis(6500)); assert!(matches!( wait(&player, Duration::from_millis(6500)).picture, Picture::Frame(_) )); player.seek(Duration::from_millis(1900)); wait(&player, Duration::from_millis(1900)); player.play(true); thread::sleep(Duration::from_millis(400)); player.play(false); let snapshot = player.snapshot(); assert!((Duration::from_millis(2250)..Duration::from_millis(2500)).contains(&snapshot.time)); assert!(matches!(snapshot.picture, Picture::Frame(_))); assert!(snapshot.video_time.unwrap().abs_diff(snapshot.time) < Duration::from_millis(20)); player.seek(Duration::from_millis(3900)); wait(&player, Duration::from_millis(3900)); player.play(true); thread::sleep(Duration::from_millis(350)); let snapshot = player.snapshot(); assert!(snapshot.time >= Duration::from_millis(4200)); assert!(matches!(snapshot.picture, Picture::Gap)); player.close(); }