diff --git a/src/player/video.rs b/src/player/video.rs new file mode 100644 index 0000000..0e111d0 --- /dev/null +++ b/src/player/video.rs @@ -0,0 +1,107 @@ +//! 視頻播放核心實現 + +use anyhow::{Context, Result}; +use std::path::Path; +use std::sync::{Arc, Mutex}; +use std::collections::VecDeque; + +use crate::player::ffmpeg::{FFmpegDecoder, VideoInfo}; + +#[derive(Debug, Clone)] +pub struct Frame { + pub data: Vec, + pub width: u32, + pub height: u32, + pub timestamp_ms: u64, +} + +#[derive(Debug, Clone, PartialEq)] +pub enum PlayState { + Stopped, + Playing, + Paused, +} + +pub struct VideoPlayer { + decoder: Option, + info: Option, + state: PlayState, + current_frame: u64, + frame_buffer: Arc>>, +} + +impl VideoPlayer { + pub fn new() -> Result { + Ok(Self { + decoder: None, + info: None, + state: PlayState::Stopped, + current_frame: 0, + frame_buffer: Arc::new(Mutex::new(VecDeque::new())), + }) + } + + pub fn open(&mut self, path: &str) -> Result<()> { + let decoder = FFmpegDecoder::new(Path::new(path)) + .with_context(|| format!("Failed to open video: {}", path))?; + + self.info = Some(decoder.get_info()); + self.decoder = Some(decoder); + self.state = PlayState::Stopped; + self.current_frame = 0; + + Ok(()) + } + + pub fn play(&mut self) -> Result<()> { + if self.decoder.is_none() { + anyhow::bail!("No video loaded"); + } + self.state = PlayState::Playing; + Ok(()) + } + + pub fn pause(&mut self) -> Result<()> { + self.state = PlayState::Paused; + Ok(()) + } + + pub fn stop(&mut self) -> Result<()> { + self.state = PlayState::Stopped; + self.current_frame = 0; + Ok(()) + } + + pub fn seek_frame(&mut self, frame: u64) -> Result<()> { + if let Some(ref decoder) = self.decoder { + if let Some(info) = &self.info { + let timestamp_ms = (frame * 1000) / info.fps as u64; + decoder.seek(timestamp_ms)?; + self.current_frame = frame; + } + } + Ok(()) + } + + pub fn seek_time(&mut self, ms: u64) -> Result<()> { + if let Some(ref decoder) = self.decoder { + decoder.seek(ms)?; + if let Some(info) = &self.info { + self.current_frame = (ms * info.fps as u64) / 1000; + } + } + Ok(()) + } + + pub fn get_state(&self) -> PlayState { + self.state.clone() + } + + pub fn get_current_frame(&self) -> u64 { + self.current_frame + } + + pub fn get_info(&self) -> Option<&VideoInfo> { + self.info.as_ref() + } +}