use std::{
sync::{
Arc,
atomic::{AtomicBool, Ordering},
},
time::Duration,
};
use bytemuck::NoUninit;
use ringbuf::traits::{Observer, Producer};
use crate::{
kits::{Shared, TextureHandleNoMut},
player::{
AV_TIME_BASE_RATIONAL, Clock, CommandGo, VIDEO_SYNC_THRESHOLD_MIN,
audio::{AudioDevice, AudioPlayFrame},
consts::VIDEO_SYNC_THRESHOLD_MAX,
kits::{RingBufferProducer, timestamp_to_millisecond},
video::VideoPlayFrame,
},
};
#[derive(PartialEq, Clone, Copy, Debug)]
pub enum PlayerState {
Stopped,
EndOfFile,
Seeking(bool),
Paused,
Playing,
Restarting,
}
unsafe impl NoUninit for PlayerState {}
#[derive(Clone)]
pub struct PlayCtrl {
pub player_state: Shared<PlayerState>,
packet_finished: Arc<AtomicBool>,
video_finished: Arc<AtomicBool>,
video_clock: Arc<Clock>,
pub audio_dev: Arc<AudioDevice>,
audio_finished: Arc<AtomicBool>,
pub audio_volume: Shared<f64>,
audio_clock: Arc<Clock>,
pub texture_handle: TextureHandleNoMut,
pub duration: i64,
pub duration_ms: i64,
pub video_elapsed_ms: Shared<i64>,
pub audio_elapsed_ms: Shared<i64>,
pub video_elapsed_ms_override: Shared<i64>,
pub command_go: Shared<CommandGo>,
pub video_stream_time_base: Option<ffmpeg::Rational>,
pub audio_stream_time_base: Option<ffmpeg::Rational>,
}
impl PlayCtrl {
pub fn new(
duration: i64,
audio_dev: Arc<AudioDevice>,
texture_handle: TextureHandleNoMut,
video_stream_time_base: Option<ffmpeg::Rational>,
audio_stream_time_base: Option<ffmpeg::Rational>,
) -> Self {
let demux_finished = Arc::new(AtomicBool::new(false));
let audio_finished = Arc::new(AtomicBool::new(false));
let video_finished = Arc::new(AtomicBool::new(false));
let video_clock = {
let q2d = match video_stream_time_base {
None => 0.0,
Some(t) => f64::from(t),
};
Arc::new(Clock::new(q2d))
};
let audio_clock = {
let q2d = match audio_stream_time_base {
None => 0.0,
Some(t) => f64::from(t),
};
Arc::new(Clock::new(q2d))
};
Self {
player_state: Shared::new(PlayerState::Paused),
packet_finished: demux_finished,
video_finished,
video_clock,
audio_dev,
audio_finished,
audio_volume: Shared::new(0.5),
audio_clock,
texture_handle,
duration,
duration_ms: timestamp_to_millisecond(duration, AV_TIME_BASE_RATIONAL),
video_elapsed_ms: Shared::new(0),
audio_elapsed_ms: Shared::new(0),
video_elapsed_ms_override: Shared::new(-1),
command_go: Shared::new(CommandGo::None),
video_stream_time_base,
audio_stream_time_base,
}
}
pub fn set_mute(&self, mute: bool) {
self.audio_dev.set_mute(mute);
}
pub fn elapsed_ms(&self) -> i64 {
match self.video_elapsed_ms_override.get() {
-1 => self.video_elapsed_ms.get(),
t => t,
}
}
pub fn set_audio_finished(&self, finished: bool) {
self.audio_finished.store(finished, Ordering::Relaxed);
}
pub fn audio_finished(&self) -> bool {
self.audio_finished.load(Ordering::Relaxed)
}
pub fn set_video_finished(&self, finished: bool) {
self.video_finished.store(finished, Ordering::Relaxed);
}
pub fn video_finished(&self) -> bool {
self.video_finished.load(Ordering::Relaxed)
}
pub fn set_packet_finished(&self, demux_finished: bool) {
self.packet_finished.store(demux_finished, Ordering::Relaxed);
}
pub fn packet_finished(&self) -> bool {
self.packet_finished.load(Ordering::Relaxed)
}
pub fn audio_config(&self) -> cpal::SupportedStreamConfig {
self.audio_dev.output_config()
}
pub fn play_audio(&self, mut frame: AudioPlayFrame, producer: &mut RingBufferProducer<f32>) -> Result<(), anyhow::Error> {
if producer.vacant_len() < frame.samples.len() {
while producer.vacant_len() < frame.samples.len() {
std::thread::sleep(Duration::from_micros(1));
}
}
self.update_audio_clock(frame.pts, frame.duration, frame.timestamp);
if self.audio_dev.get_mute() {
frame.samples.as_mut_slice().fill(0.0);
}
let mut s = frame.samples.as_slice();
let done = producer.push_slice(s);
if done == s.len() {
} else {
s = &s[done..];
std::thread::sleep(Duration::from_micros(1));
loop {
let done = producer.push_slice(s);
if done == s.len() {
log::info!("play audio done");
break;
} else {
log::info!("play audio not one ");
s = &s[done..];
std::thread::sleep(Duration::from_micros(1));
}
}
}
Ok(())
}
pub fn play_video(&self, frame: VideoPlayFrame, ctx: &egui::Context) -> Result<(), anyhow::Error> {
let delay = self.update_video_clock(frame.pts, frame.duration, frame.timestamp);
self.texture_handle.set(frame.color_image, egui::TextureOptions::LINEAR);
ctx.request_repaint();
if delay > 0.0 {
log::debug!("video delay: {delay}");
spin_sleep::sleep(Duration::from_secs_f64(delay));
}
Ok(())
}
#[inline]
fn update_audio_clock(&self, pts: i64, duration: i64, timestamp: i64) {
self.audio_clock.update(pts, duration, timestamp);
if let Some(time_base) = &self.audio_stream_time_base {
let t = timestamp_to_millisecond(pts, *time_base);
self.audio_elapsed_ms.set(t);
if self.video_stream_time_base.is_none() {
self.video_elapsed_ms.set(t);
}
}
}
#[inline]
fn update_video_clock(&self, pts: i64, duration: i64, timestamp: i64) -> f64 {
self.video_clock.update(pts, duration, timestamp);
if let Some(time_base) = &self.video_stream_time_base {
let t = timestamp_to_millisecond(pts, *time_base);
self.video_elapsed_ms.set(t);
}
self.compute_video_delay()
}
fn compute_video_delay(&self) -> f64 {
let cache_frame = 4;
let audio_clock = self.audio_clock.play_ts(cache_frame);
let (video_clock, duration) = self.video_clock.play_ts_duration();
let diff = video_clock - audio_clock;
if audio_clock == 0.0 || video_clock == 0.0 {
duration
} else if diff <= VIDEO_SYNC_THRESHOLD_MIN {
0.0f64.max(duration + diff)
}
else if diff >= VIDEO_SYNC_THRESHOLD_MAX {
duration + VIDEO_SYNC_THRESHOLD_MAX
}
else {
duration
}
}
}