1use bitflags::bitflags;
25use std::cmp::Ordering;
26use std::collections::HashMap;
27
28bitflags! {
29 #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, Default)]
34 pub struct SeekFlags: u32 {
35 const BACKWARD = 0x0001;
40
41 const ANY = 0x0002;
47
48 const KEYFRAME = 0x0004;
53
54 const BYTE = 0x0008;
59
60 const FRAME_ACCURATE = 0x0010;
65 }
66}
67
68#[derive(Clone, Copy, Debug, PartialEq)]
72pub struct SeekTarget {
73 pub position: f64,
75
76 pub stream_index: Option<usize>,
81
82 pub flags: SeekFlags,
84}
85
86impl SeekTarget {
87 #[must_use]
93 pub const fn time(position: f64) -> Self {
94 Self {
95 position,
96 stream_index: None,
97 flags: SeekFlags::KEYFRAME,
98 }
99 }
100
101 #[must_use]
107 #[allow(clippy::cast_precision_loss)]
108 pub fn byte(offset: u64) -> Self {
109 Self {
110 position: offset as f64,
111 stream_index: None,
112 flags: SeekFlags::BYTE,
113 }
114 }
115
116 #[must_use]
121 pub const fn sample_accurate(position: f64) -> Self {
122 Self {
123 position,
124 stream_index: None,
125 flags: SeekFlags::from_bits_truncate(
126 SeekFlags::FRAME_ACCURATE.bits() | SeekFlags::BACKWARD.bits(),
127 ),
128 }
129 }
130
131 #[must_use]
133 pub const fn with_stream(mut self, stream_index: usize) -> Self {
134 self.stream_index = Some(stream_index);
135 self
136 }
137
138 #[must_use]
140 pub const fn with_flags(mut self, flags: SeekFlags) -> Self {
141 self.flags = flags;
142 self
143 }
144
145 #[must_use]
147 pub const fn add_flags(mut self, flags: SeekFlags) -> Self {
148 self.flags = SeekFlags::from_bits_truncate(self.flags.bits() | flags.bits());
149 self
150 }
151
152 #[must_use]
154 pub const fn is_backward(&self) -> bool {
155 self.flags.contains(SeekFlags::BACKWARD)
156 }
157
158 #[must_use]
160 pub const fn is_any(&self) -> bool {
161 self.flags.contains(SeekFlags::ANY)
162 }
163
164 #[must_use]
166 pub const fn is_keyframe(&self) -> bool {
167 self.flags.contains(SeekFlags::KEYFRAME)
168 }
169
170 #[must_use]
172 pub const fn is_byte(&self) -> bool {
173 self.flags.contains(SeekFlags::BYTE)
174 }
175
176 #[must_use]
178 pub const fn is_frame_accurate(&self) -> bool {
179 self.flags.contains(SeekFlags::FRAME_ACCURATE)
180 }
181}
182
183#[derive(Debug, Clone, Copy, PartialEq, Eq)]
187pub enum SeekAccuracy {
188 Keyframe,
190 SampleAccurate,
192 WithinTolerance(u64),
194}
195
196#[derive(Debug, Clone, Copy, PartialEq, Eq)]
200pub struct SeekIndexEntry {
201 pub pts: i64,
203 pub dts: i64,
205 pub file_offset: u64,
207 pub size: u32,
209 pub duration: u32,
211 pub is_keyframe: bool,
213 pub sample_number: u32,
215}
216
217impl SeekIndexEntry {
218 #[must_use]
220 pub const fn keyframe(
221 pts: i64,
222 dts: i64,
223 file_offset: u64,
224 size: u32,
225 duration: u32,
226 sample_number: u32,
227 ) -> Self {
228 Self {
229 pts,
230 dts,
231 file_offset,
232 size,
233 duration,
234 is_keyframe: true,
235 sample_number,
236 }
237 }
238
239 #[must_use]
241 pub const fn non_keyframe(
242 pts: i64,
243 dts: i64,
244 file_offset: u64,
245 size: u32,
246 duration: u32,
247 sample_number: u32,
248 ) -> Self {
249 Self {
250 pts,
251 dts,
252 file_offset,
253 size,
254 duration,
255 is_keyframe: false,
256 sample_number,
257 }
258 }
259
260 #[must_use]
262 pub const fn end_pts(&self) -> i64 {
263 self.pts + self.duration as i64
264 }
265}
266
267#[derive(Debug, Clone, PartialEq, Eq)]
274pub struct SeekPlan {
275 pub keyframe_entry: SeekIndexEntry,
277 pub discard_count: u32,
280 pub target_entry: SeekIndexEntry,
282 pub file_offset: u64,
284 pub requested_pts: i64,
286 pub is_exact: bool,
288}
289
290#[derive(Debug, Clone)]
298pub struct SeekIndex {
299 timescale: u32,
301 entries: Vec<SeekIndexEntry>,
303 keyframe_indices: Vec<usize>,
305}
306
307impl SeekIndex {
308 #[must_use]
310 pub fn new(timescale: u32) -> Self {
311 Self {
312 timescale,
313 entries: Vec::new(),
314 keyframe_indices: Vec::new(),
315 }
316 }
317
318 #[must_use]
320 pub fn with_capacity(timescale: u32, capacity: usize) -> Self {
321 Self {
322 timescale,
323 entries: Vec::with_capacity(capacity),
324 keyframe_indices: Vec::new(),
325 }
326 }
327
328 #[must_use]
330 pub const fn timescale(&self) -> u32 {
331 self.timescale
332 }
333
334 #[must_use]
336 pub fn len(&self) -> usize {
337 self.entries.len()
338 }
339
340 #[must_use]
342 pub fn is_empty(&self) -> bool {
343 self.entries.is_empty()
344 }
345
346 #[must_use]
348 pub fn keyframe_count(&self) -> usize {
349 self.keyframe_indices.len()
350 }
351
352 #[must_use]
354 pub fn entries(&self) -> &[SeekIndexEntry] {
355 &self.entries
356 }
357
358 pub fn add_entry(&mut self, entry: SeekIndexEntry) {
364 let idx = self.entries.len();
365 if entry.is_keyframe {
366 self.keyframe_indices.push(idx);
367 }
368 self.entries.push(entry);
369 }
370
371 pub fn sort(&mut self) {
373 self.entries.sort_by_key(|e| e.dts);
374 self.keyframe_indices.clear();
375 for (i, entry) in self.entries.iter().enumerate() {
376 if entry.is_keyframe {
377 self.keyframe_indices.push(i);
378 }
379 }
380 }
381
382 pub fn finalize(&mut self) {
388 self.sort();
389 }
390
391 #[must_use]
393 #[allow(clippy::cast_possible_truncation, clippy::cast_precision_loss)]
394 pub fn seconds_to_ticks(&self, seconds: f64) -> i64 {
395 (seconds * f64::from(self.timescale)) as i64
396 }
397
398 #[must_use]
400 #[allow(clippy::cast_precision_loss)]
401 pub fn ticks_to_seconds(&self, ticks: i64) -> f64 {
402 if self.timescale == 0 {
403 return 0.0;
404 }
405 ticks as f64 / f64::from(self.timescale)
406 }
407
408 #[must_use]
412 pub fn find_keyframe_before(&self, target_pts: i64) -> Option<&SeekIndexEntry> {
413 if self.keyframe_indices.is_empty() {
414 return None;
415 }
416
417 let mut best: Option<usize> = None;
419 let mut lo = 0usize;
420 let mut hi = self.keyframe_indices.len();
421
422 while lo < hi {
423 let mid = lo + (hi - lo) / 2;
424 let kf_idx = self.keyframe_indices[mid];
425 let kf = &self.entries[kf_idx];
426
427 match kf.pts.cmp(&target_pts) {
428 Ordering::Less | Ordering::Equal => {
429 best = Some(kf_idx);
430 lo = mid + 1;
431 }
432 Ordering::Greater => {
433 hi = mid;
434 }
435 }
436 }
437
438 best.map(|idx| &self.entries[idx])
439 }
440
441 #[must_use]
445 pub fn find_keyframe_after(&self, target_pts: i64) -> Option<&SeekIndexEntry> {
446 if self.keyframe_indices.is_empty() {
447 return None;
448 }
449
450 let mut lo = 0usize;
451 let mut hi = self.keyframe_indices.len();
452
453 while lo < hi {
454 let mid = lo + (hi - lo) / 2;
455 let kf_idx = self.keyframe_indices[mid];
456 let kf = &self.entries[kf_idx];
457
458 if kf.pts < target_pts {
459 lo = mid + 1;
460 } else {
461 hi = mid;
462 }
463 }
464
465 if lo < self.keyframe_indices.len() {
466 Some(&self.entries[self.keyframe_indices[lo]])
467 } else {
468 None
469 }
470 }
471
472 #[must_use]
476 pub fn find_nearest_keyframe(&self, target_pts: i64) -> Option<&SeekIndexEntry> {
477 let before = self.find_keyframe_before(target_pts);
478 let after = self.find_keyframe_after(target_pts);
479
480 match (before, after) {
481 (None, None) => None,
482 (Some(b), None) => Some(b),
483 (None, Some(a)) => Some(a),
484 (Some(b), Some(a)) => {
485 let dist_before = (target_pts - b.pts).unsigned_abs();
486 let dist_after = (a.pts - target_pts).unsigned_abs();
487 if dist_before <= dist_after {
488 Some(b)
489 } else {
490 Some(a)
491 }
492 }
493 }
494 }
495
496 #[must_use]
500 pub fn find_sample_at(&self, target_pts: i64) -> Option<&SeekIndexEntry> {
501 let result = self.entries.binary_search_by(|entry| {
503 if entry.pts > target_pts {
504 Ordering::Greater
505 } else if entry.end_pts() <= target_pts {
506 Ordering::Less
507 } else {
508 Ordering::Equal
509 }
510 });
511
512 match result {
513 Ok(idx) => Some(&self.entries[idx]),
514 Err(_) => {
515 let mut best = None;
517 for entry in &self.entries {
518 if entry.pts <= target_pts {
519 best = Some(entry);
520 } else {
521 break;
522 }
523 }
524 best
525 }
526 }
527 }
528
529 #[must_use]
540 pub fn plan_seek(&self, target_pts: i64, accuracy: SeekAccuracy) -> Option<SeekPlan> {
541 if self.entries.is_empty() || self.keyframe_indices.is_empty() {
542 return None;
543 }
544
545 match accuracy {
546 SeekAccuracy::Keyframe => {
547 let kf = self.find_keyframe_before(target_pts)?;
548 Some(SeekPlan {
549 keyframe_entry: *kf,
550 discard_count: 0,
551 target_entry: *kf,
552 file_offset: kf.file_offset,
553 requested_pts: target_pts,
554 is_exact: kf.pts == target_pts,
555 })
556 }
557 SeekAccuracy::SampleAccurate => self.plan_sample_accurate_seek(target_pts),
558 SeekAccuracy::WithinTolerance(tolerance) => {
559 if let Some(plan) = self.plan_sample_accurate_seek(target_pts) {
561 let distance = (plan.target_entry.pts - target_pts).unsigned_abs();
562 if distance <= tolerance {
563 return Some(plan);
564 }
565 }
566 let kf = self.find_nearest_keyframe(target_pts)?;
568 let distance = (kf.pts - target_pts).unsigned_abs();
569 if distance <= tolerance {
570 Some(SeekPlan {
571 keyframe_entry: *kf,
572 discard_count: 0,
573 target_entry: *kf,
574 file_offset: kf.file_offset,
575 requested_pts: target_pts,
576 is_exact: kf.pts == target_pts,
577 })
578 } else {
579 None
580 }
581 }
582 }
583 }
584
585 fn plan_sample_accurate_seek(&self, target_pts: i64) -> Option<SeekPlan> {
586 let kf = self.find_keyframe_before(target_pts)?;
588 let kf_copy = *kf;
589
590 let target_sample = self.find_sample_at(target_pts);
592 let target = match target_sample {
593 Some(s) => *s,
594 None => {
595 *self.entries.last()?
597 }
598 };
599
600 let mut discard_count: u32 = 0;
602 for entry in &self.entries {
603 if entry.dts > kf_copy.dts && entry.dts < target.dts {
604 discard_count += 1;
605 }
606 }
607
608 Some(SeekPlan {
609 keyframe_entry: kf_copy,
610 discard_count,
611 target_entry: target,
612 file_offset: kf_copy.file_offset,
613 requested_pts: target_pts,
614 is_exact: target.pts <= target_pts && target_pts < target.end_pts(),
615 })
616 }
617
618 #[must_use]
620 pub fn duration_ticks(&self) -> i64 {
621 self.entries.last().map_or(0, |e| e.pts + e.duration as i64)
622 }
623
624 #[must_use]
626 pub fn duration_seconds(&self) -> f64 {
627 self.ticks_to_seconds(self.duration_ticks())
628 }
629
630 #[must_use]
632 pub fn average_keyframe_interval(&self) -> Option<f64> {
633 if self.keyframe_indices.len() < 2 {
634 return None;
635 }
636
637 let mut total_interval: i64 = 0;
638 for i in 1..self.keyframe_indices.len() {
639 let prev = &self.entries[self.keyframe_indices[i - 1]];
640 let curr = &self.entries[self.keyframe_indices[i]];
641 total_interval += curr.pts - prev.pts;
642 }
643
644 #[allow(clippy::cast_precision_loss)]
645 let avg = total_interval as f64 / (self.keyframe_indices.len() - 1) as f64;
646 Some(avg)
647 }
648}
649
650pub type SampleIndex = SeekIndex;
656
657#[derive(Debug, Clone)]
665pub struct TrackIndex {
666 pub seek_index: SeekIndex,
668 pub codec_delay_samples: u32,
671}
672
673impl TrackIndex {
674 #[must_use]
676 pub fn new(seek_index: SeekIndex) -> Self {
677 Self {
678 seek_index,
679 codec_delay_samples: 0,
680 }
681 }
682
683 #[must_use]
685 pub fn with_codec_delay(seek_index: SeekIndex, codec_delay_samples: u32) -> Self {
686 Self {
687 seek_index,
688 codec_delay_samples,
689 }
690 }
691}
692
693#[derive(Debug, Clone, PartialEq, Eq)]
700pub struct SeekResult {
701 pub keyframe_pts: u64,
706 pub sample_offset: u64,
708 pub preroll_samples: u32,
713}
714
715#[derive(Debug, Clone, Copy, PartialEq, Eq)]
721pub struct DecodeSkipCursor {
722 pub byte_offset: u64,
724 pub sample_index: usize,
726 pub skip_samples: u32,
728 pub target_pts: i64,
730}
731
732pub struct SampleAccurateSeeker {
756 pub track: TrackIndex,
762 streams: HashMap<u32, SampleIndex>,
766}
767
768impl SampleAccurateSeeker {
769 #[must_use]
775 pub fn new() -> Self {
776 let empty_index = SeekIndex::new(90_000);
777 let empty_track = TrackIndex::new(empty_index);
778 Self {
779 track: empty_track,
780 streams: HashMap::new(),
781 }
782 }
783
784 #[must_use]
789 pub fn with_track(track: TrackIndex) -> Self {
790 Self {
791 track,
792 streams: HashMap::new(),
793 }
794 }
795
796 pub fn add_stream(&mut self, stream_id: u32, index: SampleIndex) {
801 self.streams.insert(stream_id, index);
802 }
803
804 #[must_use]
817 pub fn plan_preroll_seek(
818 &self,
819 stream_id: u32,
820 target_pts: i64,
821 max_preroll: Option<u32>,
822 ) -> Option<crate::preroll::PreRollSeekPlan> {
823 use crate::preroll::{PreRollAction, PreRollSample, PreRollSeekPlan};
824
825 let index = self.streams.get(&stream_id)?;
826 let keyframe = index.find_keyframe_before(target_pts)?;
827
828 let all_from_kf: Vec<&SeekIndexEntry> = index
834 .entries()
835 .iter()
836 .filter(|e| e.pts >= keyframe.pts)
837 .collect();
838
839 let discard_candidates: Vec<&&SeekIndexEntry> =
841 all_from_kf.iter().filter(|e| e.pts < target_pts).collect();
842 let present_candidate: Option<&SeekIndexEntry> =
843 all_from_kf.iter().find(|e| e.pts >= target_pts).copied();
844
845 let capped_discards: Vec<&SeekIndexEntry> = if let Some(max) = max_preroll {
848 let max = max as usize;
849 if discard_candidates.len() > max {
850 discard_candidates[discard_candidates.len() - max..]
851 .iter()
852 .copied()
853 .copied()
854 .collect()
855 } else {
856 discard_candidates.iter().copied().copied().collect()
857 }
858 } else {
859 discard_candidates.iter().copied().copied().collect()
860 };
861
862 let mut samples: Vec<PreRollSample> = capped_discards
863 .iter()
864 .map(|e| PreRollSample {
865 entry: **e,
866 action: PreRollAction::Decode,
867 })
868 .collect();
869
870 let discard_count = samples.len() as u32;
871 let mut present_count: u32 = 0;
872
873 if let Some(entry) = present_candidate {
874 samples.push(PreRollSample {
875 entry: *entry,
876 action: PreRollAction::Present,
877 });
878 present_count = 1;
879 } else if discard_count == 0 {
880 return None;
882 } else {
883 if let Some(last) = samples.last_mut() {
886 last.action = PreRollAction::Present;
887 present_count = 1;
888 }
889 }
890
891 let final_discard_count = samples
892 .iter()
893 .filter(|s| matches!(s.action, PreRollAction::Decode))
894 .count() as u32;
895
896 Some(PreRollSeekPlan {
897 keyframe: *keyframe,
898 target_pts,
899 samples,
900 discard_count: final_discard_count,
901 present_count,
902 file_offset: keyframe.file_offset,
903 })
904 }
905
906 #[must_use]
913 pub fn preroll_count(&self, stream_id: u32, target_pts: i64) -> Option<u32> {
914 let plan = self.plan_preroll_seek(stream_id, target_pts, None)?;
915 Some(plan.discard_count)
916 }
917
918 #[must_use]
937 pub fn seek_to_sample(&self, target_pts: u64, track: &TrackIndex) -> Option<SeekResult> {
938 let target_i64 = i64::try_from(target_pts).unwrap_or(i64::MAX);
939
940 let plan = track
941 .seek_index
942 .plan_seek(target_i64, SeekAccuracy::SampleAccurate)?;
943
944 let keyframe_pts = u64::try_from(plan.keyframe_entry.pts.max(0)).unwrap_or(0);
945 let sample_offset = plan.keyframe_entry.file_offset;
946 let preroll_samples = plan.discard_count.saturating_add(track.codec_delay_samples);
947
948 Some(SeekResult {
949 keyframe_pts,
950 sample_offset,
951 preroll_samples,
952 })
953 }
954}
955
956impl Default for SampleAccurateSeeker {
957 fn default() -> Self {
958 Self::new()
959 }
960}
961
962#[derive(Debug, Clone, Copy, PartialEq, Eq)]
968pub struct SampleIndexEntry {
969 pub pts: i64,
971 pub byte_offset: u64,
973 pub is_sync: bool,
975}
976
977impl SampleIndexEntry {
978 #[must_use]
980 pub const fn keyframe(pts: i64, byte_offset: u64) -> Self {
981 Self {
982 pts,
983 byte_offset,
984 is_sync: true,
985 }
986 }
987
988 #[must_use]
990 pub const fn delta(pts: i64, byte_offset: u64) -> Self {
991 Self {
992 pts,
993 byte_offset,
994 is_sync: false,
995 }
996 }
997}
998
999#[derive(Debug, Clone, Copy, PartialEq, Eq)]
1006pub struct PtsSeekResult {
1007 pub found_pts: i64,
1009 pub byte_offset: u64,
1011 pub sample_idx: usize,
1013}
1014
1015#[derive(Debug, thiserror::Error)]
1017pub enum MultiTrackSeekerError {
1018 #[error("no index for track {0}")]
1020 NoIndex(u32),
1021 #[error("empty index for track {0}")]
1023 EmptyIndex(u32),
1024 #[error("pts {0} is before the first sample in track {1}")]
1026 BeforeFirstSample(i64, u32),
1027}
1028
1029pub struct MultiTrackSeeker {
1053 indices: HashMap<u32, Vec<SampleIndexEntry>>,
1055}
1056
1057impl MultiTrackSeeker {
1058 #[must_use]
1060 pub fn new() -> Self {
1061 Self {
1062 indices: HashMap::new(),
1063 }
1064 }
1065
1066 pub fn build_index(
1076 &mut self,
1077 track_id: u32,
1078 samples: &[SampleIndexEntry],
1079 ) -> Result<(), MultiTrackSeekerError> {
1080 let mut sorted = samples.to_vec();
1081 sorted.sort_unstable_by_key(|e| e.pts);
1082 self.indices.insert(track_id, sorted);
1083 Ok(())
1084 }
1085
1086 pub fn seek_to_pts(
1100 &self,
1101 track_id: u32,
1102 target_pts: i64,
1103 ) -> Result<PtsSeekResult, MultiTrackSeekerError> {
1104 let entries = self
1105 .indices
1106 .get(&track_id)
1107 .ok_or(MultiTrackSeekerError::NoIndex(track_id))?;
1108
1109 if entries.is_empty() {
1110 return Err(MultiTrackSeekerError::EmptyIndex(track_id));
1111 }
1112
1113 let insertion = entries.partition_point(|e| e.pts <= target_pts);
1117
1118 if insertion == 0 {
1119 return Err(MultiTrackSeekerError::BeforeFirstSample(
1121 target_pts, track_id,
1122 ));
1123 }
1124
1125 let sample_idx = insertion - 1;
1126 let entry = &entries[sample_idx];
1127
1128 Ok(PtsSeekResult {
1129 found_pts: entry.pts,
1130 byte_offset: entry.byte_offset,
1131 sample_idx,
1132 })
1133 }
1134
1135 #[must_use]
1137 pub fn indexed_track_count(&self) -> usize {
1138 self.indices.len()
1139 }
1140
1141 #[must_use]
1143 pub fn sample_count(&self, track_id: u32) -> Option<usize> {
1144 self.indices.get(&track_id).map(Vec::len)
1145 }
1146
1147 pub fn clear_index(&mut self, track_id: u32) {
1149 self.indices.remove(&track_id);
1150 }
1151
1152 #[must_use]
1154 pub fn entries(&self, track_id: u32) -> Option<&[SampleIndexEntry]> {
1155 self.indices.get(&track_id).map(Vec::as_slice)
1156 }
1157}
1158
1159impl Default for MultiTrackSeeker {
1160 fn default() -> Self {
1161 Self::new()
1162 }
1163}
1164
1165#[cfg(test)]
1166mod tests {
1167 use super::*;
1168
1169 #[test]
1172 fn test_seek_flags() {
1173 let flags = SeekFlags::BACKWARD | SeekFlags::KEYFRAME;
1174 assert!(flags.contains(SeekFlags::BACKWARD));
1175 assert!(flags.contains(SeekFlags::KEYFRAME));
1176 assert!(!flags.contains(SeekFlags::ANY));
1177 }
1178
1179 #[test]
1182 fn test_seek_target_time() {
1183 let target = SeekTarget::time(10.5);
1184 assert_eq!(target.position, 10.5);
1185 assert!(target.is_keyframe());
1186 assert!(!target.is_byte());
1187 assert_eq!(target.stream_index, None);
1188 }
1189
1190 #[test]
1191 fn test_seek_target_byte() {
1192 let target = SeekTarget::byte(1024);
1193 assert_eq!(target.position, 1024.0);
1194 assert!(target.is_byte());
1195 assert_eq!(target.stream_index, None);
1196 }
1197
1198 #[test]
1199 fn test_seek_target_sample_accurate() {
1200 let target = SeekTarget::sample_accurate(5.0);
1201 assert!(target.is_frame_accurate());
1202 assert!(target.is_backward());
1203 assert!(!target.is_keyframe());
1204 }
1205
1206 #[test]
1207 fn test_seek_target_with_stream() {
1208 let target = SeekTarget::time(5.0).with_stream(1);
1209 assert_eq!(target.stream_index, Some(1));
1210 assert_eq!(target.position, 5.0);
1211 }
1212
1213 #[test]
1214 fn test_seek_target_with_flags() {
1215 let target = SeekTarget::time(3.0)
1216 .with_flags(SeekFlags::BACKWARD)
1217 .add_flags(SeekFlags::ANY);
1218
1219 assert!(target.is_backward());
1220 assert!(target.is_any());
1221 }
1222
1223 #[test]
1224 fn test_seek_target_predicates() {
1225 let target =
1226 SeekTarget::time(1.0).add_flags(SeekFlags::BACKWARD | SeekFlags::FRAME_ACCURATE);
1227
1228 assert!(target.is_backward());
1229 assert!(!target.is_any());
1230 assert!(target.is_keyframe());
1231 assert!(!target.is_byte());
1232 assert!(target.is_frame_accurate());
1233 }
1234
1235 #[test]
1238 fn test_entry_keyframe() {
1239 let e = SeekIndexEntry::keyframe(0, 0, 100, 500, 3000, 0);
1240 assert!(e.is_keyframe);
1241 assert_eq!(e.pts, 0);
1242 assert_eq!(e.file_offset, 100);
1243 assert_eq!(e.end_pts(), 3000);
1244 }
1245
1246 #[test]
1247 fn test_entry_non_keyframe() {
1248 let e = SeekIndexEntry::non_keyframe(3000, 3000, 600, 200, 3000, 1);
1249 assert!(!e.is_keyframe);
1250 assert_eq!(e.sample_number, 1);
1251 assert_eq!(e.end_pts(), 6000);
1252 }
1253
1254 fn build_test_index() -> SeekIndex {
1257 let mut index = SeekIndex::new(90000);
1260 for i in 0u32..20 {
1261 let pts = i64::from(i) * 3000;
1262 let is_kf = i % 5 == 0;
1263 let offset = u64::from(i) * 500 + 1000; if is_kf {
1265 index.add_entry(SeekIndexEntry::keyframe(pts, pts, offset, 500, 3000, i));
1266 } else {
1267 index.add_entry(SeekIndexEntry::non_keyframe(pts, pts, offset, 200, 3000, i));
1268 }
1269 }
1270 index
1271 }
1272
1273 #[test]
1274 fn test_index_new() {
1275 let index = SeekIndex::new(48000);
1276 assert_eq!(index.timescale(), 48000);
1277 assert!(index.is_empty());
1278 assert_eq!(index.len(), 0);
1279 assert_eq!(index.keyframe_count(), 0);
1280 }
1281
1282 #[test]
1283 fn test_index_add_entries() {
1284 let index = build_test_index();
1285 assert_eq!(index.len(), 20);
1286 assert_eq!(index.keyframe_count(), 4); }
1288
1289 #[test]
1290 fn test_seconds_to_ticks() {
1291 let index = SeekIndex::new(90000);
1292 assert_eq!(index.seconds_to_ticks(1.0), 90000);
1293 assert_eq!(index.seconds_to_ticks(0.5), 45000);
1294 }
1295
1296 #[test]
1297 fn test_ticks_to_seconds() {
1298 let index = SeekIndex::new(90000);
1299 let s = index.ticks_to_seconds(90000);
1300 assert!((s - 1.0).abs() < 1e-10);
1301 }
1302
1303 #[test]
1304 fn test_ticks_to_seconds_zero_timescale() {
1305 let index = SeekIndex::new(0);
1306 assert_eq!(index.ticks_to_seconds(12345), 0.0);
1307 }
1308
1309 #[test]
1312 fn test_find_keyframe_before_exact() {
1313 let index = build_test_index();
1314 let kf = index.find_keyframe_before(15000).expect("should find");
1316 assert_eq!(kf.pts, 15000);
1317 assert!(kf.is_keyframe);
1318 }
1319
1320 #[test]
1321 fn test_find_keyframe_before_between() {
1322 let index = build_test_index();
1323 let kf = index.find_keyframe_before(20000).expect("should find");
1325 assert_eq!(kf.pts, 15000);
1326 }
1327
1328 #[test]
1329 fn test_find_keyframe_before_start() {
1330 let index = build_test_index();
1331 let kf = index.find_keyframe_before(0).expect("should find");
1332 assert_eq!(kf.pts, 0);
1333 }
1334
1335 #[test]
1336 fn test_find_keyframe_before_none() {
1337 let index = build_test_index();
1338 let kf = index.find_keyframe_before(-1);
1340 assert!(kf.is_none());
1341 }
1342
1343 #[test]
1344 fn test_find_keyframe_after() {
1345 let index = build_test_index();
1346 let kf = index.find_keyframe_after(20000).expect("should find");
1347 assert_eq!(kf.pts, 30000);
1348 }
1349
1350 #[test]
1351 fn test_find_keyframe_after_exact() {
1352 let index = build_test_index();
1353 let kf = index.find_keyframe_after(15000).expect("should find");
1354 assert_eq!(kf.pts, 15000);
1355 }
1356
1357 #[test]
1358 fn test_find_keyframe_after_past_end() {
1359 let index = build_test_index();
1360 let kf = index.find_keyframe_after(99999);
1361 assert!(kf.is_none());
1362 }
1363
1364 #[test]
1365 fn test_find_nearest_keyframe_closer_before() {
1366 let index = build_test_index();
1367 let kf = index.find_nearest_keyframe(16000).expect("should find");
1369 assert_eq!(kf.pts, 15000);
1370 }
1371
1372 #[test]
1373 fn test_find_nearest_keyframe_closer_after() {
1374 let index = build_test_index();
1375 let kf = index.find_nearest_keyframe(28000).expect("should find");
1377 assert_eq!(kf.pts, 30000);
1378 }
1379
1380 #[test]
1381 fn test_find_nearest_keyframe_equidistant() {
1382 let index = build_test_index();
1383 let kf = index.find_nearest_keyframe(22500).expect("should find");
1386 assert_eq!(kf.pts, 15000);
1387 }
1388
1389 #[test]
1392 fn test_find_sample_at_exact_start() {
1393 let index = build_test_index();
1394 let sample = index.find_sample_at(3000).expect("should find");
1395 assert_eq!(sample.pts, 3000);
1396 }
1397
1398 #[test]
1399 fn test_find_sample_at_mid_frame() {
1400 let index = build_test_index();
1401 let sample = index.find_sample_at(4000).expect("should find");
1403 assert_eq!(sample.pts, 3000);
1404 }
1405
1406 #[test]
1407 fn test_find_sample_at_last() {
1408 let index = build_test_index();
1409 let sample = index.find_sample_at(58000).expect("should find");
1411 assert_eq!(sample.pts, 57000);
1412 }
1413
1414 #[test]
1417 fn test_plan_seek_keyframe() {
1418 let index = build_test_index();
1419 let plan = index
1420 .plan_seek(20000, SeekAccuracy::Keyframe)
1421 .expect("should plan");
1422 assert_eq!(plan.keyframe_entry.pts, 15000);
1424 assert_eq!(plan.discard_count, 0);
1425 assert_eq!(plan.target_entry.pts, 15000);
1426 }
1427
1428 #[test]
1429 fn test_plan_seek_sample_accurate() {
1430 let index = build_test_index();
1431 let plan = index
1433 .plan_seek(21000, SeekAccuracy::SampleAccurate)
1434 .expect("should plan");
1435
1436 assert_eq!(plan.keyframe_entry.pts, 15000);
1438 assert_eq!(plan.target_entry.pts, 21000);
1440 assert_eq!(plan.discard_count, 1); assert!(plan.is_exact);
1443 }
1444
1445 #[test]
1446 fn test_plan_seek_sample_accurate_on_keyframe() {
1447 let index = build_test_index();
1448 let plan = index
1449 .plan_seek(15000, SeekAccuracy::SampleAccurate)
1450 .expect("should plan");
1451 assert_eq!(plan.keyframe_entry.pts, 15000);
1452 assert_eq!(plan.discard_count, 0);
1453 assert!(plan.is_exact);
1454 }
1455
1456 #[test]
1457 fn test_plan_seek_sample_accurate_first_frame() {
1458 let index = build_test_index();
1459 let plan = index
1460 .plan_seek(0, SeekAccuracy::SampleAccurate)
1461 .expect("should plan");
1462 assert_eq!(plan.keyframe_entry.pts, 0);
1463 assert_eq!(plan.target_entry.pts, 0);
1464 assert_eq!(plan.discard_count, 0);
1465 }
1466
1467 #[test]
1468 fn test_plan_seek_within_tolerance_exact() {
1469 let index = build_test_index();
1470 let plan = index
1472 .plan_seek(16000, SeekAccuracy::WithinTolerance(5000))
1473 .expect("should plan");
1474 assert_eq!(plan.target_entry.pts, 15000);
1475 }
1476
1477 #[test]
1478 fn test_plan_seek_within_tolerance_out_of_range() {
1479 let index = build_test_index();
1480 let plan = index.plan_seek(20000, SeekAccuracy::WithinTolerance(1));
1482 assert!(plan.is_none());
1483 }
1484
1485 #[test]
1486 fn test_plan_seek_empty_index() {
1487 let index = SeekIndex::new(90000);
1488 let plan = index.plan_seek(0, SeekAccuracy::Keyframe);
1489 assert!(plan.is_none());
1490 }
1491
1492 #[test]
1495 fn test_duration_ticks() {
1496 let index = build_test_index();
1497 assert_eq!(index.duration_ticks(), 60000);
1499 }
1500
1501 #[test]
1502 fn test_duration_seconds() {
1503 let index = build_test_index();
1504 let dur = index.duration_seconds();
1505 assert!((dur - 60000.0 / 90000.0).abs() < 1e-6);
1507 }
1508
1509 #[test]
1510 fn test_average_keyframe_interval() {
1511 let index = build_test_index();
1512 let avg = index.average_keyframe_interval().expect("should calculate");
1513 assert!((avg - 15000.0).abs() < 1e-6);
1515 }
1516
1517 #[test]
1518 fn test_average_keyframe_interval_single() {
1519 let mut index = SeekIndex::new(90000);
1520 index.add_entry(SeekIndexEntry::keyframe(0, 0, 0, 100, 3000, 0));
1521 assert!(index.average_keyframe_interval().is_none());
1522 }
1523
1524 #[test]
1527 fn test_sort_reorders_entries() {
1528 let mut index = SeekIndex::new(90000);
1529 index.add_entry(SeekIndexEntry::non_keyframe(6000, 6000, 300, 100, 3000, 2));
1531 index.add_entry(SeekIndexEntry::keyframe(0, 0, 100, 100, 3000, 0));
1532 index.add_entry(SeekIndexEntry::non_keyframe(3000, 3000, 200, 100, 3000, 1));
1533
1534 index.sort();
1535
1536 assert_eq!(index.entries()[0].pts, 0);
1537 assert_eq!(index.entries()[1].pts, 3000);
1538 assert_eq!(index.entries()[2].pts, 6000);
1539 assert_eq!(index.keyframe_count(), 1);
1540 }
1541
1542 #[test]
1545 fn test_find_keyframe_empty() {
1546 let index = SeekIndex::new(90000);
1547 assert!(index.find_keyframe_before(0).is_none());
1548 assert!(index.find_keyframe_after(0).is_none());
1549 assert!(index.find_nearest_keyframe(0).is_none());
1550 }
1551
1552 #[test]
1553 fn test_single_keyframe_index() {
1554 let mut index = SeekIndex::new(90000);
1555 index.add_entry(SeekIndexEntry::keyframe(0, 0, 0, 100, 90000, 0));
1556
1557 let kf = index.find_keyframe_before(45000).expect("should find");
1558 assert_eq!(kf.pts, 0);
1559
1560 let plan = index
1561 .plan_seek(45000, SeekAccuracy::SampleAccurate)
1562 .expect("should plan");
1563 assert_eq!(plan.keyframe_entry.pts, 0);
1564 }
1565
1566 #[test]
1567 fn test_all_keyframes_index() {
1568 let mut index = SeekIndex::new(48000);
1569 for i in 0u32..100 {
1571 let pts = i64::from(i) * 960;
1572 index.add_entry(SeekIndexEntry::keyframe(
1573 pts,
1574 pts,
1575 u64::from(i) * 100,
1576 100,
1577 960,
1578 i,
1579 ));
1580 }
1581
1582 assert_eq!(index.keyframe_count(), 100);
1583
1584 let plan = index
1585 .plan_seek(48000, SeekAccuracy::SampleAccurate)
1586 .expect("should plan");
1587 assert_eq!(plan.keyframe_entry.pts, 48000);
1589 assert_eq!(plan.discard_count, 0);
1590 }
1591
1592 #[test]
1593 fn test_with_capacity() {
1594 let index = SeekIndex::with_capacity(90000, 1000);
1595 assert_eq!(index.timescale(), 90000);
1596 assert!(index.is_empty());
1597 }
1598
1599 fn build_seeker_index() -> SeekIndex {
1602 let mut index = SeekIndex::new(90000);
1604 for i in 0u32..20 {
1605 let pts = i64::from(i) * 3000;
1606 let is_kf = i % 5 == 0;
1607 let offset = u64::from(i) * 500 + 1000;
1608 if is_kf {
1609 index.add_entry(SeekIndexEntry::keyframe(pts, pts, offset, 500, 3000, i));
1610 } else {
1611 index.add_entry(SeekIndexEntry::non_keyframe(pts, pts, offset, 200, 3000, i));
1612 }
1613 }
1614 index
1615 }
1616
1617 #[test]
1618 fn test_sample_accurate_seeker_on_keyframe() {
1619 let track = TrackIndex::new(build_seeker_index());
1620 let seeker = SampleAccurateSeeker::with_track(TrackIndex::new(build_seeker_index()));
1621 let result = seeker.seek_to_sample(15000, &track).expect("should find");
1622 assert_eq!(result.keyframe_pts, 15000);
1623 assert_eq!(result.preroll_samples, 0);
1624 assert_eq!(result.sample_offset, 1000 + 5 * 500); }
1626
1627 #[test]
1628 fn test_sample_accurate_seeker_between_keyframes() {
1629 let track = TrackIndex::new(build_seeker_index());
1630 let seeker = SampleAccurateSeeker::with_track(TrackIndex::new(build_seeker_index()));
1631 let result = seeker.seek_to_sample(21000, &track).expect("should find");
1634 assert_eq!(result.keyframe_pts, 15000);
1635 assert_eq!(result.preroll_samples, 1);
1636 }
1637
1638 #[test]
1639 fn test_sample_accurate_seeker_codec_delay_added() {
1640 let track = TrackIndex::with_codec_delay(build_seeker_index(), 512);
1641 let seeker = SampleAccurateSeeker::with_track(TrackIndex::new(build_seeker_index()));
1642 let result = seeker.seek_to_sample(0, &track).expect("should find");
1644 assert_eq!(result.keyframe_pts, 0);
1645 assert_eq!(result.preroll_samples, 512);
1646 }
1647
1648 #[test]
1649 fn test_sample_accurate_seeker_empty_index() {
1650 let track = TrackIndex::new(SeekIndex::new(90000));
1651 let seeker = SampleAccurateSeeker::with_track(TrackIndex::new(SeekIndex::new(90000)));
1652 let result = seeker.seek_to_sample(0, &track);
1653 assert!(result.is_none());
1654 }
1655
1656 #[test]
1657 fn test_track_index_default_codec_delay() {
1658 let idx = SeekIndex::new(90000);
1659 let track = TrackIndex::new(idx);
1660 assert_eq!(track.codec_delay_samples, 0);
1661 }
1662
1663 #[test]
1664 fn test_seek_result_fields() {
1665 let track = TrackIndex::new(build_seeker_index());
1666 let seeker = SampleAccurateSeeker::with_track(TrackIndex::new(build_seeker_index()));
1667 let result = seeker.seek_to_sample(0, &track).expect("should find");
1668 assert_eq!(result.keyframe_pts, 0);
1670 assert_eq!(result.sample_offset, 1000);
1671 assert_eq!(result.preroll_samples, 0);
1672 }
1673
1674 fn build_multi_track_samples() -> Vec<SampleIndexEntry> {
1677 (0u32..20)
1679 .map(|i| {
1680 let pts = i64::from(i) * 3000;
1681 let offset = 1000 + u64::from(i) * 500;
1682 if i % 5 == 0 {
1683 SampleIndexEntry::keyframe(pts, offset)
1684 } else {
1685 SampleIndexEntry::delta(pts, offset)
1686 }
1687 })
1688 .collect()
1689 }
1690
1691 #[test]
1692 fn test_multi_track_seeker_new() {
1693 let seeker = MultiTrackSeeker::new();
1694 assert_eq!(seeker.indexed_track_count(), 0);
1695 }
1696
1697 #[test]
1698 fn test_multi_track_build_index() {
1699 let mut seeker = MultiTrackSeeker::new();
1700 let samples = build_multi_track_samples();
1701 seeker.build_index(1, &samples).expect("build ok");
1702 assert_eq!(seeker.indexed_track_count(), 1);
1703 assert_eq!(seeker.sample_count(1), Some(20));
1704 }
1705
1706 #[test]
1707 fn test_multi_track_entries_sorted() {
1708 let mut seeker = MultiTrackSeeker::new();
1709 let samples = vec![
1711 SampleIndexEntry::delta(9000, 3000),
1712 SampleIndexEntry::keyframe(0, 1000),
1713 SampleIndexEntry::delta(3000, 1500),
1714 SampleIndexEntry::delta(6000, 2000),
1715 ];
1716 seeker.build_index(1, &samples).expect("ok");
1717 let entries = seeker.entries(1).expect("entries exist");
1718 assert_eq!(entries[0].pts, 0);
1719 assert_eq!(entries[1].pts, 3000);
1720 assert_eq!(entries[2].pts, 6000);
1721 assert_eq!(entries[3].pts, 9000);
1722 }
1723
1724 #[test]
1725 fn test_seek_to_pts_exact() {
1726 let mut seeker = MultiTrackSeeker::new();
1727 seeker
1728 .build_index(1, &build_multi_track_samples())
1729 .expect("ok");
1730 let result = seeker.seek_to_pts(1, 15000).expect("seek ok");
1732 assert_eq!(result.found_pts, 15000);
1733 assert_eq!(result.byte_offset, 1000 + 5 * 500);
1734 assert_eq!(result.sample_idx, 5);
1735 }
1736
1737 #[test]
1738 fn test_seek_to_pts_between_samples() {
1739 let mut seeker = MultiTrackSeeker::new();
1740 seeker
1741 .build_index(1, &build_multi_track_samples())
1742 .expect("ok");
1743 let result = seeker.seek_to_pts(1, 16000).expect("seek ok");
1745 assert_eq!(
1746 result.found_pts, 15000,
1747 "should return the preceding sample"
1748 );
1749 assert_eq!(result.sample_idx, 5);
1750 }
1751
1752 #[test]
1753 fn test_seek_to_pts_first_sample() {
1754 let mut seeker = MultiTrackSeeker::new();
1755 seeker
1756 .build_index(1, &build_multi_track_samples())
1757 .expect("ok");
1758 let result = seeker.seek_to_pts(1, 0).expect("seek ok");
1759 assert_eq!(result.found_pts, 0);
1760 assert_eq!(result.sample_idx, 0);
1761 }
1762
1763 #[test]
1764 fn test_seek_to_pts_last_sample() {
1765 let mut seeker = MultiTrackSeeker::new();
1766 seeker
1767 .build_index(1, &build_multi_track_samples())
1768 .expect("ok");
1769 let result = seeker.seek_to_pts(1, 99999).expect("seek ok");
1771 assert_eq!(result.found_pts, 19 * 3000); assert_eq!(result.sample_idx, 19);
1773 }
1774
1775 #[test]
1776 fn test_seek_to_pts_before_first_sample() {
1777 let mut seeker = MultiTrackSeeker::new();
1778 seeker
1779 .build_index(1, &build_multi_track_samples())
1780 .expect("ok");
1781 let err = seeker.seek_to_pts(1, -1);
1782 assert!(matches!(
1783 err,
1784 Err(MultiTrackSeekerError::BeforeFirstSample(-1, 1))
1785 ));
1786 }
1787
1788 #[test]
1789 fn test_seek_to_pts_no_index() {
1790 let seeker = MultiTrackSeeker::new();
1791 let err = seeker.seek_to_pts(42, 0);
1792 assert!(matches!(err, Err(MultiTrackSeekerError::NoIndex(42))));
1793 }
1794
1795 #[test]
1796 fn test_seek_to_pts_empty_index() {
1797 let mut seeker = MultiTrackSeeker::new();
1798 seeker.build_index(1, &[]).expect("ok");
1799 let err = seeker.seek_to_pts(1, 0);
1800 assert!(matches!(err, Err(MultiTrackSeekerError::EmptyIndex(1))));
1801 }
1802
1803 #[test]
1804 fn test_multi_track_multiple_tracks() {
1805 let mut seeker = MultiTrackSeeker::new();
1806 let video = build_multi_track_samples();
1807 let audio: Vec<SampleIndexEntry> = (0u32..=50)
1809 .map(|i| SampleIndexEntry::keyframe(i64::from(i) * 960, u64::from(i) * 100 + 500))
1810 .collect();
1811
1812 seeker.build_index(1, &video).expect("video ok");
1813 seeker.build_index(2, &audio).expect("audio ok");
1814
1815 assert_eq!(seeker.indexed_track_count(), 2);
1816
1817 let v_result = seeker.seek_to_pts(1, 15000).expect("video seek ok");
1818 let a_result = seeker.seek_to_pts(2, 48000).expect("audio seek ok");
1819
1820 assert_eq!(v_result.found_pts, 15000);
1821 assert_eq!(a_result.found_pts, 48000);
1822 }
1823
1824 #[test]
1825 fn test_clear_index() {
1826 let mut seeker = MultiTrackSeeker::new();
1827 seeker
1828 .build_index(1, &build_multi_track_samples())
1829 .expect("ok");
1830 assert_eq!(seeker.indexed_track_count(), 1);
1831 seeker.clear_index(1);
1832 assert_eq!(seeker.indexed_track_count(), 0);
1833 let err = seeker.seek_to_pts(1, 0);
1834 assert!(matches!(err, Err(MultiTrackSeekerError::NoIndex(1))));
1835 }
1836
1837 #[test]
1838 fn test_build_index_replaces_existing() {
1839 let mut seeker = MultiTrackSeeker::new();
1840 let old: Vec<SampleIndexEntry> = vec![SampleIndexEntry::keyframe(0, 100)];
1841 let new: Vec<SampleIndexEntry> = vec![
1842 SampleIndexEntry::keyframe(0, 200),
1843 SampleIndexEntry::keyframe(3000, 300),
1844 ];
1845 seeker.build_index(1, &old).expect("ok");
1846 seeker.build_index(1, &new).expect("replace ok");
1847 assert_eq!(seeker.sample_count(1), Some(2));
1848 let result = seeker.seek_to_pts(1, 0).expect("ok");
1849 assert_eq!(result.byte_offset, 200);
1850 }
1851
1852 #[test]
1853 fn test_sample_index_entry_constructors() {
1854 let kf = SampleIndexEntry::keyframe(1000, 9999);
1855 assert!(kf.is_sync);
1856 assert_eq!(kf.pts, 1000);
1857 assert_eq!(kf.byte_offset, 9999);
1858
1859 let df = SampleIndexEntry::delta(2000, 8888);
1860 assert!(!df.is_sync);
1861 assert_eq!(df.pts, 2000);
1862 }
1863
1864 #[test]
1865 fn test_pts_seek_result_fields() {
1866 let mut seeker = MultiTrackSeeker::new();
1867 seeker
1868 .build_index(1, &[SampleIndexEntry::keyframe(5000, 12345)])
1869 .expect("ok");
1870 let r = seeker.seek_to_pts(1, 5000).expect("ok");
1871 assert_eq!(r.found_pts, 5000);
1872 assert_eq!(r.byte_offset, 12345);
1873 assert_eq!(r.sample_idx, 0);
1874 }
1875}