use std::path::PathBuf;
use crate::{
AudioPlayer, Engine, Error, Result, VideoFrame, VideoPlayer, audio::AudioPlayerConfig,
video::VideoPlayerConfig,
};
use super::MediaTimestamp;
#[derive(Clone, Debug)]
pub struct MediaPlayerConfig {
pub video_path: PathBuf,
pub audio_path: PathBuf,
pub audio_ring_milliseconds: u32,
}
impl MediaPlayerConfig {
pub fn new(video_path: impl Into<PathBuf>, audio_path: impl Into<PathBuf>) -> Self {
Self {
video_path: video_path.into(),
audio_path: audio_path.into(),
audio_ring_milliseconds: 500,
}
}
}
pub struct MediaPlayer {
audio: AudioPlayer,
video: VideoPlayer,
playing: bool,
}
impl MediaPlayer {
pub fn open(engine: &Engine, config: MediaPlayerConfig) -> Result<Self> {
if config.video_path.as_os_str().is_empty() || config.audio_path.as_os_str().is_empty() {
return Err(Error::invalid_argument(
"MediaPlayer requires both track paths",
));
}
let video = VideoPlayer::open_native(
engine,
&config.video_path,
VideoPlayerConfig {
loop_playback: false,
start_playing: false,
..VideoPlayerConfig::default()
},
)?;
let audio = AudioPlayer::open(
engine,
AudioPlayerConfig {
uri: config.audio_path,
loop_playback: false,
ring_milliseconds: config.audio_ring_milliseconds,
},
)?;
Ok(Self {
audio,
video,
playing: false,
})
}
pub fn play(&mut self) -> Result<()> {
self.require_open()?;
if self.audio.is_eos() {
self.seek_us(0)?;
}
self.audio.play()?;
self.playing = true;
Ok(())
}
pub fn pause(&mut self) -> Result<()> {
self.require_open()?;
self.audio.pause();
self.playing = false;
Ok(())
}
pub fn tick(&mut self, max_frames: u32) -> Result<u32> {
self.require_open()?;
if max_frames == 0 {
return Err(Error::invalid_argument(
"MediaPlayer tick budget must be positive",
));
}
if !self.playing {
return Ok(0);
}
let audio_eos = self.audio.is_eos();
let result = (|| {
let clock = MediaTimestamp::from_microseconds(self.audio.position_us());
let mut advanced = 0;
while advanced < max_frames && !self.video.is_done() {
let Some(boundary) = self.video.next_presentation_boundary()? else {
break;
};
if !clock.has_reached(boundary) || !self.video.advance()? {
break;
}
advanced += 1;
}
Ok(advanced)
})();
match result {
Ok(advanced) => {
if audio_eos && (advanced < max_frames || self.video.is_done()) {
self.playing = false;
}
Ok(advanced)
}
Err(error) => {
self.audio.pause();
self.playing = false;
Err(error)
}
}
}
pub fn seek_us(&mut self, timestamp_us: u64) -> Result<()> {
self.require_open()?;
let was_playing = self.playing;
self.audio.pause();
self.playing = false;
let result = (|| {
let info = self
.video
.info()
.ok_or_else(|| Error::failed_precondition("video closed"))?;
let ticks =
MediaTimestamp::from_microseconds(timestamp_us).to_video_ticks(info.time_base())?;
self.video.seek_at_or_before(ticks)?;
self.audio.seek(timestamp_us)
})();
if let Err(error) = result {
let _ = self.close();
return Err(error);
}
if was_playing {
self.play()?;
}
Ok(())
}
pub fn current_frame(&self) -> Result<&VideoFrame> {
self.require_open()?;
self.video.current_frame()
}
pub fn position_us(&self) -> Result<u64> {
self.require_open()?;
Ok(self.audio.position_us())
}
pub fn is_playing(&self) -> bool {
self.playing
}
pub fn is_done(&self) -> bool {
self.audio.is_eos() && !self.playing
}
pub fn close(&mut self) -> Result<()> {
self.playing = false;
self.video.close();
self.audio.close()
}
fn require_open(&self) -> Result<()> {
if self.video.is_open() && self.audio.is_open() {
Ok(())
} else {
Err(Error::failed_precondition("MediaPlayer is closed"))
}
}
}