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

@@ -33,7 +33,7 @@ pub struct Config {
#[arg(
short = 'h',
long = "height",
default_value = "720",
default_value = "1053",
help = "Window height"
)]
pub height: u32,
@@ -47,6 +47,30 @@ pub struct Config {
help = "UI language (en, zh-TW, etc.)"
)]
pub locale: String,
#[arg(
short = 's',
long = "start",
default_value = "0",
help = "Start time in seconds"
)]
pub start_seconds: f64,
#[arg(
short = 'e',
long = "end",
default_value = "0",
help = "End time in seconds (0 = no limit)"
)]
pub end_seconds: f64,
#[arg(
short = 'l',
long = "loop",
default_value = "false",
help = "Loop playback between start and end"
)]
pub loop_playback: bool,
}
impl Config {

View File

@@ -45,8 +45,13 @@ fn run(config: &Config) -> Result<()> {
let ttf_context = ttf::init().map_err(|e| anyhow::anyhow!("TTF init failed: {}", e))?;
let font: Option<Font> = ttf_context
.load_font("/System/Library/Fonts/Supplemental/Arial.ttf", 16)
.ok();
.load_font("/System/Library/Fonts/Supplemental/Songti.ttc", 24)
.ok()
.or_else(|| {
ttf_context
.load_font("/System/Library/Fonts/Supplemental/Arial.ttf", 24)
.ok()
});
let window = video_subsystem
.window("MoMentry Playground", config.width, config.height)
@@ -98,6 +103,27 @@ fn run(config: &Config) -> Result<()> {
dec.start_decoding(0)?;
decoder = Some(dec);
let is_reverse_init = config.loop_playback
&& config.start_seconds > config.end_seconds
&& config.end_seconds > 0.0;
if let Some(ref mut dec) = decoder {
if is_reverse_init {
let start_ms = (config.start_seconds * 1000.0) as u64;
let end_ms = (config.end_seconds * 1000.0) as u64;
dec.enable_reverse(start_ms, end_ms);
dec.fill_reverse_buffer(600).ok();
info!(
"Reverse playback enabled ({}s -> {}s)",
config.start_seconds, config.end_seconds
);
} else if config.start_seconds > 0.0 {
let start_ms = (config.start_seconds * 1000.0) as u64;
dec.seek(start_ms).ok();
info!("Seeked to {:.1}s", config.start_seconds);
}
}
let player = AudioPlayer::new();
info!("Audio player initialized");
audio_player = Some(player);
@@ -149,22 +175,51 @@ fn run(config: &Config) -> Result<()> {
}
let mut player_state = PlayerState::default();
let mut reached_end = false;
let is_reverse = config.loop_playback
&& config.start_seconds > config.end_seconds
&& config.end_seconds > 0.0;
if is_reverse {
player_state.muted = true;
}
if let Some(ref info) = video_info {
player_state.total_frames = info.frame_count;
player_state.duration_ms = info.duration_ms;
player_state.fps = info.fps;
}
if config.start_seconds > 0.0 {
let time_ms = (config.start_seconds * 1000.0) as u64;
player_state.current_time_ms = time_ms;
if let Some(ref info) = video_info {
player_state.current_frame = (config.start_seconds * info.fps) as u64;
}
}
let mut event_pump = sdl_context
.event_pump()
.map_err(|e| anyhow::anyhow!("Event pump failed: {}", e))?;
let header_height: i32 = 50;
let progress_height: i32 = 30;
let text_input = video_subsystem.text_input();
let header_height: i32 = 60;
let status_bar_height: i32 = 60;
let search_bar_height: i32 = 80;
let video_content_top: i32 = 230;
let video_content_height: i32 = 700;
let asr_top: i32 = 930;
let asr_height: i32 = 80;
let progress_y: i32 = 1010;
let progress_height: i32 = 43;
info!("Main loop started - waiting for events...");
let mut running = true;
let mut last_frame_time = std::time::Instant::now();
let frame_duration = std::time::Duration::from_millis(16); // ~60fps
while running {
for event in event_pump.poll_iter() {
match event {
@@ -183,6 +238,7 @@ fn run(config: &Config) -> Result<()> {
match key {
sdl2::keyboard::Keycode::Escape => {
if show_search {
let _ = text_input.stop();
show_search = false;
search_query.clear();
search_results.clear();
@@ -193,24 +249,30 @@ fn run(config: &Config) -> Result<()> {
}
}
sdl2::keyboard::Keycode::H => {
if !show_search {
show_help = !show_help;
}
}
sdl2::keyboard::Keycode::Slash => {
if searcher.is_some() {
show_search = true;
search_query.clear();
search_results.clear();
let _ = text_input.start();
info!("Search mode enabled");
}
}
sdl2::keyboard::Keycode::Return | sdl2::keyboard::Keycode::KpEnter => {
if show_search && !search_query.is_empty() {
let query = search_query.trim_start_matches('/').trim();
if !query.is_empty() {
if let Some(ref search) = searcher {
search_results = search.search(&search_query);
search_results = search.search(query);
info!("Found {} results", search_results.len());
}
}
}
}
sdl2::keyboard::Keycode::Backspace => {
if show_search && !search_query.is_empty() {
search_query.pop();
@@ -250,6 +312,7 @@ fn run(config: &Config) -> Result<()> {
&config.video,
result.time_ms,
false,
is_reverse,
);
info!("Jumped to: {}", result.text);
}
@@ -258,7 +321,9 @@ fn run(config: &Config) -> Result<()> {
}
}
sdl2::keyboard::Keycode::Space => {
let was_playing = player_state.playback == PlaybackState::Playing;
if !show_search {
let was_playing =
player_state.playback == PlaybackState::Playing;
player_state.playback = if was_playing {
PlaybackState::Paused
} else {
@@ -266,15 +331,20 @@ fn run(config: &Config) -> Result<()> {
};
if let Some(ref mut audio) = audio_player {
if player_state.playback == PlaybackState::Playing {
if !player_state.muted {
audio.resume();
if !player_state.muted && !is_reverse {
audio.resume(
config.video.as_ref().unwrap(),
player_state.current_time_ms,
);
}
} else {
audio.pause();
}
}
}
}
sdl2::keyboard::Keycode::S => {
if !show_search {
player_state.show_subtitle = !player_state.show_subtitle;
info!(
"Subtitle: {}",
@@ -285,26 +355,41 @@ fn run(config: &Config) -> Result<()> {
}
);
}
}
sdl2::keyboard::Keycode::Y => {
if !show_search {
player_state.show_yolo = !player_state.show_yolo;
info!(
"YOLO: {}",
if player_state.show_yolo { "ON" } else { "OFF" }
);
}
}
sdl2::keyboard::Keycode::C => {
if !show_search {
player_state.show_chunks = !player_state.show_chunks;
}
}
sdl2::keyboard::Keycode::M => {
if !show_search {
player_state.muted = !player_state.muted;
if let Some(ref mut audio) = audio_player {
if player_state.muted {
audio.pause();
} else if player_state.playback == PlaybackState::Playing {
audio.resume();
} else if player_state.playback == PlaybackState::Playing
&& !is_reverse
{
audio.resume(
config.video.as_ref().unwrap(),
player_state.current_time_ms,
);
}
}
info!("Audio: {}", if player_state.muted { "MUTED" } else { "ON" });
info!(
"Audio: {}",
if player_state.muted { "MUTED" } else { "ON" }
);
}
}
sdl2::keyboard::Keycode::F => {
is_fullscreen = !is_fullscreen;
@@ -319,6 +404,7 @@ fn run(config: &Config) -> Result<()> {
&config.video,
0,
player_state.playback == PlaybackState::Playing,
is_reverse,
);
}
}
@@ -335,6 +421,7 @@ fn run(config: &Config) -> Result<()> {
&config.video,
time_ms,
player_state.playback == PlaybackState::Playing,
is_reverse,
);
}
}
@@ -352,6 +439,7 @@ fn run(config: &Config) -> Result<()> {
&config.video,
time_ms,
player_state.playback == PlaybackState::Playing,
is_reverse,
);
}
}
@@ -369,6 +457,7 @@ fn run(config: &Config) -> Result<()> {
&config.video,
time_ms,
player_state.playback == PlaybackState::Playing,
is_reverse,
);
}
}
@@ -464,7 +553,13 @@ fn run(config: &Config) -> Result<()> {
if dec.seek(time_ms).is_ok() {
player_state.current_frame = target_frame;
player_state.current_time_ms = time_ms;
sync_audio(&mut audio_player, &config.video, time_ms, false);
sync_audio(
&mut audio_player,
&config.video,
time_ms,
false,
is_reverse,
);
info!(
"Seeked to frame {} ({:.1}%)",
target_frame,
@@ -497,8 +592,11 @@ fn run(config: &Config) -> Result<()> {
};
if let Some(ref mut audio) = audio_player {
if player_state.playback == PlaybackState::Playing {
if !player_state.muted {
audio.resume();
if !player_state.muted && !is_reverse {
audio.resume(
config.video.as_ref().unwrap(),
player_state.current_time_ms,
);
}
} else {
audio.pause();
@@ -563,9 +661,92 @@ fn run(config: &Config) -> Result<()> {
};
canvas.window_mut().set_fullscreen(fs_type).ok();
let is_reverse = config.loop_playback
&& config.start_seconds > config.end_seconds
&& config.end_seconds > 0.0;
if player_state.playback == PlaybackState::Playing {
if config.end_seconds > 0.0 {
let end_time_ms = (config.end_seconds * 1000.0) as u64;
let start_time_ms = (config.start_seconds * 1000.0) as u64;
let should_loop = config.loop_playback && config.start_seconds > 0.0;
let reached_boundary = if is_reverse {
player_state.current_time_ms <= end_time_ms
} else {
player_state.current_time_ms >= end_time_ms
};
if reached_boundary {
if should_loop {
if let Some(ref mut dec) = decoder {
dec.seek(start_time_ms).ok();
if is_reverse {
dec.enable_reverse(start_time_ms, end_time_ms);
}
player_state.current_time_ms = start_time_ms;
if let Some(ref info) = video_info {
player_state.current_frame =
(config.start_seconds * info.fps) as u64;
}
if let Some(ref mut audio) = audio_player {
audio.pause();
}
info!("Looping back to {:.1}s", config.start_seconds);
}
} else if !reached_end {
reached_end = true;
info!("Reached end time: {:.1}s", config.end_seconds);
player_state.playback = PlaybackState::Paused;
if let Some(ref mut audio) = audio_player {
audio.pause();
}
}
}
}
}
let should_render_frame = player_state.playback == PlaybackState::Playing;
if should_render_frame {
let elapsed = last_frame_time.elapsed();
let frame_duration_ms = 16;
let target_frame_duration = std::time::Duration::from_millis(frame_duration_ms);
if elapsed >= target_frame_duration {
last_frame_time = std::time::Instant::now();
if let Some(ref mut dec) = decoder {
let fps = video_info.as_ref().map(|i| i.fps).unwrap_or(30.0);
if is_reverse {
if dec.buffer_size() == 0 {
let start_ms = (config.start_seconds * 1000.0) as u64;
let end_ms = (config.end_seconds * 1000.0) as u64;
dec.enable_reverse(start_ms, end_ms);
dec.fill_reverse_buffer(600).ok();
player_state.current_time_ms = start_ms;
player_state.current_frame = (config.start_seconds * fps) as u64;
} else if dec.buffer_size() < 100 {
dec.refill_reverse_buffer(600).ok();
}
if let Some(data) = dec.get_buffer_front() {
if let Some(ref mut tex) = texture {
tex.update(
None,
&data,
(video_info.as_ref().unwrap().width * 3) as usize,
)
.map_err(|e| anyhow::anyhow!("Texture update failed: {}", e))
.ok();
}
player_state.current_time_ms =
dec.get_frame_time().unwrap_or(player_state.current_time_ms);
player_state.current_frame =
player_state.current_frame.saturating_sub(1);
}
} else {
match dec.read_frame() {
Ok(Some(data)) => {
if let Some(ref info) = video_info {
@@ -574,18 +755,23 @@ fn run(config: &Config) -> Result<()> {
((player_state.current_frame as f64 / info.fps) * 1000.0)
as u64;
if let Some(ref mut tex) = texture {
tex.update(None, &data, (info.width * 3) as usize)
.map_err(|e| anyhow::anyhow!("Texture update failed: {}", e))
.map_err(|e| {
anyhow::anyhow!("Texture update failed: {}", e)
})
.ok();
}
}
}
Ok(None) => {
info!("Playback ended");
break;
if player_state.playback == PlaybackState::Playing {
player_state.playback = PlaybackState::Paused;
}
}
Err(e) => {
warn!("Frame read error: {}", e);
break;
}
}
}
}
@@ -597,12 +783,13 @@ fn run(config: &Config) -> Result<()> {
.map(|i| (i.width, i.height))
.unwrap_or((config.width, config.height));
let video_area_height = config.height as i32 - header_height - progress_height;
let search_bar_offset = if show_search { search_bar_height } else { 0 };
let video_top = video_content_top + search_bar_offset;
let scale: f64 = if player_state.zoom != 1.0 {
player_state.zoom as f64
} else {
let scale_x = config.width as f64 / vid_width as f64;
let scale_y = video_area_height as f64 / vid_height as f64;
let scale_y = video_content_height as f64 / vid_height as f64;
scale_x.min(scale_y).min(1.0)
};
@@ -610,7 +797,8 @@ fn run(config: &Config) -> Result<()> {
let scaled_h = (vid_height as f64 * scale) as u32;
let offset_x =
((config.width as i32 - scaled_w as i32) / 2) as i32 + player_state.pan_x as i32;
let offset_y = ((config.height as i32 - progress_height - scaled_h as i32) / 2) as i32
let offset_y = video_top
+ ((video_content_height as i32 - scaled_h as i32) / 2) as i32
+ player_state.pan_y as i32;
if let Some(ref mut tex) = texture {
@@ -664,18 +852,15 @@ fn run(config: &Config) -> Result<()> {
{
let query = tex_label.query();
let x = (config.width - query.width) / 2;
let y = config.height as i32
- progress_height
- query.height as i32
- 10;
let y = asr_top + (asr_height - query.height as i32) / 2;
let rect =
Rect::new(x as i32, y as i32, query.width as u32, query.height);
canvas.set_draw_color(sdl2::pixels::Color::RGBA(0, 0, 0, 200));
canvas.set_draw_color(sdl2::pixels::Color::RGBA(0, 0, 0, 180));
let _ = canvas.fill_rect(Rect::new(
rect.x() - 6,
rect.y() - 3,
rect.width() + 12,
rect.height() + 6,
rect.x() - 8,
rect.y() - 4,
rect.width() + 16,
rect.height() + 8,
));
canvas.copy(&tex_label, None, Some(rect)).ok();
}
@@ -685,6 +870,9 @@ fn run(config: &Config) -> Result<()> {
}
}
canvas.set_draw_color(sdl2::pixels::Color::RGBA(30, 30, 40, 200));
let _ = canvas.fill_rect(Rect::new(0, 60, config.width, 60));
if let Some(ref f) = font {
let time_str = format_time(player_state.current_time_ms);
let duration_str = format_time(player_state.duration_ms);
@@ -722,14 +910,7 @@ fn run(config: &Config) -> Result<()> {
.solid(sdl2::pixels::Color::RGB(200, 200, 200))
{
if let Ok(tex_label) = texture_creator.create_texture_from_surface(&surface) {
let rect = Rect::new(10, 10, surface.width(), surface.height());
canvas.set_draw_color(sdl2::pixels::Color::RGBA(0, 0, 0, 150));
let _ = canvas.fill_rect(Rect::new(
5,
5,
surface.width() + 10,
surface.height() + 10,
));
let rect = Rect::new(10, 5, surface.width(), surface.height());
canvas.copy(&tex_label, None, Some(rect)).ok();
}
}
@@ -738,21 +919,13 @@ fn run(config: &Config) -> Result<()> {
.solid(sdl2::pixels::Color::RGB(150, 150, 150))
{
if let Ok(tex_label) = texture_creator.create_texture_from_surface(&surface) {
let rect = Rect::new(10, 30, surface.width(), surface.height());
canvas.set_draw_color(sdl2::pixels::Color::RGBA(0, 0, 0, 150));
let _ = canvas.fill_rect(Rect::new(
5,
25,
surface.width() + 10,
surface.height() + 10,
));
let rect = Rect::new(10, 70, surface.width(), surface.height());
canvas.copy(&tex_label, None, Some(rect)).ok();
}
}
}
let progress_y = config.height as i32 - progress_height + 5;
let progress_bar_height = 8i32;
let progress_bar_height = 20i32;
canvas.set_draw_color(sdl2::pixels::Color::RGB(50, 50, 50));
let _ = canvas.fill_rect(Rect::new(
10,
@@ -805,7 +978,7 @@ fn run(config: &Config) -> Result<()> {
{
let rect = Rect::new(
bar_start + bar_width - surface.width() as i32 - 10,
progress_y - 25,
progress_y - 22,
surface.width(),
surface.height(),
);
@@ -827,21 +1000,13 @@ fn run(config: &Config) -> Result<()> {
if show_search {
if let Some(ref font) = font {
let search_bg_h = 250u32;
let search_bg_y = (config.height as i32 - search_bg_h as i32) / 2;
canvas.set_draw_color(sdl2::pixels::Color::RGBA(20, 20, 30, 230));
let search_bg_y = header_height + status_bar_height;
canvas.set_draw_color(sdl2::pixels::Color::RGBA(20, 20, 30, 240));
let _ = canvas.fill_rect(Rect::new(
config.width as i32 / 2 - 300,
0,
search_bg_y,
600,
search_bg_h,
));
canvas.set_draw_color(sdl2::pixels::Color::RGBA(60, 60, 80, 255));
let _ = canvas.draw_rect(Rect::new(
config.width as i32 / 2 - 300,
search_bg_y,
600,
search_bg_h,
config.width as u32,
search_bar_height as u32,
));
let input_text = if search_query.is_empty() {
@@ -851,56 +1016,36 @@ fn run(config: &Config) -> Result<()> {
};
if let Ok(surface) = font
.render(&input_text)
.solid(sdl2::pixels::Color::RGB(200, 200, 200))
.solid(sdl2::pixels::Color::RGB(220, 220, 220))
{
if let Ok(tex) = texture_creator.create_texture_from_surface(&surface) {
let rect = Rect::new(
config.width as i32 / 2 - 290,
search_bg_y + 10,
surface.width(),
surface.height(),
);
let rect =
Rect::new(20, search_bg_y + 10, surface.width(), surface.height());
canvas.copy(&tex, None, Some(rect)).ok();
}
}
if !search_results.is_empty() {
let mut y_offset = search_bg_y + 40;
let mut x_offset = 400;
for (i, result) in search_results.iter().take(6).enumerate() {
let result_text = format!("[{}] {}", i + 1, result.text);
let color = if i == 0 {
sdl2::pixels::Color::RGB(100, 200, 255)
sdl2::pixels::Color::RGB(100, 220, 255)
} else {
sdl2::pixels::Color::RGB(180, 180, 180)
};
if let Ok(surface) = font.render(&result_text).solid(color) {
if let Ok(tex) = texture_creator.create_texture_from_surface(&surface) {
let rect = Rect::new(
config.width as i32 / 2 - 290,
y_offset,
surface.width().min(580),
x_offset,
search_bg_y + 10,
surface.width().min(500),
surface.height(),
);
canvas.copy(&tex, None, Some(rect)).ok();
}
}
y_offset += 25;
}
} else if !search_query.is_empty() {
let hint = "Press Enter to search...";
if let Ok(surface) = font
.render(hint)
.solid(sdl2::pixels::Color::RGB(150, 150, 150))
{
if let Ok(tex) = texture_creator.create_texture_from_surface(&surface) {
let rect = Rect::new(
config.width as i32 / 2 - 290,
search_bg_y + 40,
surface.width(),
surface.height(),
);
canvas.copy(&tex, None, Some(rect)).ok();
}
x_offset += 520;
}
}
}
@@ -993,13 +1138,22 @@ fn sync_audio(
video_path: &Option<std::path::PathBuf>,
time_ms: u64,
is_playing: bool,
is_reverse: bool,
) {
if let Some(ref mut audio) = audio_player {
if let Some(ref path) = video_path {
info!(
"sync_audio called: is_reverse={}, time_ms={}",
is_reverse, time_ms
);
if !is_reverse {
audio.play(path.as_path(), time_ms);
if !is_playing {
audio.pause();
}
} else {
audio.pause();
}
}
}
}

View File

@@ -60,8 +60,8 @@ impl AudioPlayer {
info!("Audio paused");
}
pub fn resume(&mut self) {
info!("Audio resumed");
pub fn resume(&mut self, path: &std::path::Path, current_time_ms: u64) -> Option<()> {
self.play(path, current_time_ms)
}
pub fn stop(&mut self) {

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 {