fix(player): correct reverse playback frame positioning

- Read frames from reverse_end_ms instead of reverse_start_ms
- Reverse buffer now displays correct video content (130s instead of 139s)
- Maintain forward playback compatibility
This commit is contained in:
accusys
2026-03-19 23:57:57 +08:00
parent 72187a45a4
commit 15eb9a2700
4 changed files with 515 additions and 144 deletions

View File

@@ -1,6 +1,7 @@
//! FFmpeg wrapper
use anyhow::{Context, Result};
use std::collections::VecDeque;
use std::io::{BufReader, Read};
use std::path::Path;
use std::process::{Child, ChildStdout, Command, Stdio};
@@ -20,6 +21,14 @@ pub struct FFmpegDecoder {
process: Option<Child>,
stdout: Option<BufReader<ChildStdout>>,
info: VideoInfo,
frame_buffer: VecDeque<Vec<u8>>,
frame_times: VecDeque<u64>,
buffer_loaded: bool,
current_read_ms: u64,
display_time_ms: u64,
reverse_start_ms: u64,
reverse_end_ms: u64,
in_reverse_mode: bool,
}
impl FFmpegDecoder {
@@ -32,6 +41,14 @@ impl FFmpegDecoder {
process: None,
stdout: None,
info,
frame_buffer: VecDeque::new(),
frame_times: VecDeque::new(),
buffer_loaded: false,
current_read_ms: 0,
display_time_ms: 0,
reverse_start_ms: 0,
reverse_end_ms: 0,
in_reverse_mode: false,
})
}
@@ -124,12 +141,39 @@ impl FFmpegDecoder {
self.process = Some(child);
self.stdout = Some(BufReader::new(stdout));
self.current_read_ms = start_ms;
Ok(())
}
pub fn seek(&mut self, timestamp_ms: u64) -> Result<()> {
self.start_decoding(timestamp_ms)
self.in_reverse_mode = false;
self.frame_buffer.clear();
self.current_read_ms = timestamp_ms;
self.start_decoding(timestamp_ms)?;
let frame_size = (self.info.width * self.info.height * 3) as usize;
for _ in 0..5 {
let mut dummy = vec![0u8; frame_size];
if let Some(ref mut reader) = self.stdout {
if reader.read_exact(&mut dummy).is_err() {
break;
}
}
}
Ok(())
}
pub fn enable_reverse(&mut self, start_ms: u64, end_ms: u64) {
self.stop();
self.frame_buffer.clear();
self.frame_times.clear();
self.buffer_loaded = false;
self.reverse_start_ms = start_ms;
self.reverse_end_ms = end_ms;
self.current_read_ms = end_ms;
self.display_time_ms = start_ms;
self.in_reverse_mode = true;
self.start_decoding(end_ms).ok();
}
pub fn stop(&mut self) {
@@ -142,11 +186,33 @@ impl FFmpegDecoder {
pub fn read_frame(&mut self) -> Result<Option<Vec<u8>>> {
let frame_size = (self.info.width * self.info.height * 3) as usize;
if let Some(frame) = self.frame_buffer.pop_front() {
if self.in_reverse_mode && self.frame_buffer.len() < 300 {
let _ = self.refill_reverse_buffer(600);
}
return Ok(Some(frame));
}
if self.in_reverse_mode {
if self.current_read_ms <= self.reverse_end_ms {
return Ok(None);
}
let _ = self.refill_reverse_buffer(30);
if let Some(frame) = self.frame_buffer.pop_front() {
return Ok(Some(frame));
}
return Ok(None);
}
let mut buffer = vec![0u8; frame_size];
if let Some(ref mut reader) = self.stdout {
match reader.read_exact(&mut buffer) {
Ok(_) => Ok(Some(buffer)),
Ok(_) => {
self.current_read_ms += 16;
Ok(Some(buffer))
}
Err(e) if e.kind() == std::io::ErrorKind::UnexpectedEof => Ok(None),
Err(e) => Err(e.into()),
}
@@ -154,6 +220,133 @@ impl FFmpegDecoder {
Ok(None)
}
}
pub fn fill_reverse_buffer(&mut self, num_frames: usize) -> Result<()> {
let frame_size = (self.info.width * self.info.height * 3) as usize;
self.frame_buffer.clear();
self.frame_times.clear();
let frame_duration_ms = (1000.0 / self.info.fps) as u64;
let total_range = self.reverse_start_ms.saturating_sub(self.reverse_end_ms);
let max_frames = (total_range / frame_duration_ms).max(1) as usize;
let frames_to_read = max_frames.min(num_frames);
let mut temp_frames: Vec<Vec<u8>> = Vec::new();
let mut temp_times: Vec<u64> = Vec::new();
for _ in 0..frames_to_read {
let mut buffer = vec![0u8; frame_size];
if let Some(ref mut reader) = self.stdout {
match reader.read_exact(&mut buffer) {
Ok(_) => {
temp_frames.push(buffer);
temp_times.push(self.current_read_ms);
self.current_read_ms += frame_duration_ms;
}
Err(_) => break,
}
} else {
break;
}
}
for i in (0..temp_frames.len()).rev() {
self.frame_buffer.push_back(temp_frames.remove(i));
self.frame_times.push_back(temp_times.remove(i));
}
Ok(())
}
pub fn refill_reverse_buffer(&mut self, _num_frames: usize) -> Result<()> {
if !self.in_reverse_mode {
return Ok(());
}
self.frame_buffer.clear();
self.frame_times.clear();
self.current_read_ms = self.reverse_end_ms;
self.display_time_ms = self.reverse_start_ms;
let frame_duration_ms = (1000.0 / self.info.fps) as u64;
let frames_to_read =
((self.reverse_start_ms - self.reverse_end_ms) / frame_duration_ms).max(1) as usize;
self.stop();
self.start_decoding(self.reverse_end_ms)?;
let frame_size = (self.info.width * self.info.height * 3) as usize;
let mut temp_frames: Vec<Vec<u8>> = Vec::new();
let mut temp_times: Vec<u64> = Vec::new();
for _ in 0..frames_to_read {
let mut buffer = vec![0u8; frame_size];
if let Some(ref mut reader) = self.stdout {
match reader.read_exact(&mut buffer) {
Ok(_) => {
temp_frames.push(buffer);
temp_times.push(self.current_read_ms);
self.current_read_ms += frame_duration_ms;
}
Err(_) => break,
}
} else {
break;
}
}
for i in (0..temp_frames.len()).rev() {
self.frame_buffer.push_back(temp_frames.remove(i));
self.frame_times.push_back(temp_times.remove(i));
}
Ok(())
}
pub fn has_frames_in_buffer(&self) -> bool {
!self.frame_buffer.is_empty()
}
pub fn buffer_size(&self) -> usize {
self.frame_buffer.len()
}
pub fn get_buffer_front(&mut self) -> Option<Vec<u8>> {
self.frame_buffer.pop_front()
}
pub fn get_frame_time(&mut self) -> Option<u64> {
if self.in_reverse_mode {
let time = self.display_time_ms;
self.display_time_ms = self
.display_time_ms
.saturating_sub((1000.0 / self.info.fps) as u64);
Some(time)
} else {
self.frame_times.pop_front()
}
}
pub fn needs_loading(&self) -> bool {
self.in_reverse_mode && self.frame_buffer.len() < 300
}
pub fn get_current_time_ms(&self) -> u64 {
self.current_read_ms
}
pub fn set_current_time_ms(&mut self, ms: u64) {
self.current_read_ms = ms;
}
pub fn get_reverse_end_ms(&self) -> u64 {
self.reverse_end_ms
}
pub fn is_at_boundary(&self) -> bool {
self.in_reverse_mode && self.current_read_ms <= self.reverse_end_ms
}
}
impl Drop for FFmpegDecoder {