extern crate ffmpeg_next as ffmpeg; use cosmic::widget; use cpal::{ traits::{DeviceTrait, HostTrait, StreamTrait}, FromSample, SizedSample, }; use ffmpeg::{ codec::discard, format::{input, Pixel}, media::Type, software::{resampling, scaling}, util::{ channel_layout, error, format::sample, frame::{audio::Audio, video::Video}, }, Packet, }; use std::{ collections::VecDeque, error::Error, path::{Path, PathBuf}, slice, sync::{mpsc, Arc, Mutex}, thread, time::{Duration, Instant}, }; #[derive(Clone, Debug)] pub enum PlayerMessage { SeekRelative(f64), } pub struct VideoFrame(pub Video); impl VideoFrame { pub fn into_handle(self) -> widget::image::Handle { let width = self.0.width(); let height = self.0.height(); widget::image::Handle::from_pixels(width, height, self) } } impl AsRef<[u8]> for VideoFrame { fn as_ref(&self) -> &[u8] { self.0.data(0) } } fn cpal( audio_queue_lock: Arc>>, ) -> (cpal::SupportedStreamConfig, Box) { let host = cpal::default_host(); let device = host .default_output_device() .expect("failed to get default audio output device"); let config = device .default_output_config() .expect("failed to get default audio output config"); println!("{:?}: {:?}", device.name(), config); let stream = { let config = config.clone(); match config.sample_format() { cpal::SampleFormat::I8 => cpal_stream::(device, config.into(), audio_queue_lock), cpal::SampleFormat::I16 => cpal_stream::(device, config.into(), audio_queue_lock), // cpal::SampleFormat::I24 => cpal_stream::(device, config.into(), audio_queue_lock), cpal::SampleFormat::I32 => cpal_stream::(device, config.into(), audio_queue_lock), // cpal::SampleFormat::I48 => cpal_stream::(device, config.into(), audio_queue_lock), cpal::SampleFormat::I64 => cpal_stream::(device, config.into(), audio_queue_lock), cpal::SampleFormat::U8 => cpal_stream::(device, config.into(), audio_queue_lock), cpal::SampleFormat::U16 => cpal_stream::(device, config.into(), audio_queue_lock), // cpal::SampleFormat::U24 => cpal_stream::(device, config.into(), audio_queue_lock), cpal::SampleFormat::U32 => cpal_stream::(device, config.into(), audio_queue_lock), // cpal::SampleFormat::U48 => cpal_stream::(device, config.into(), audio_queue_lock), cpal::SampleFormat::U64 => cpal_stream::(device, config.into(), audio_queue_lock), cpal::SampleFormat::F32 => cpal_stream::(device, config.into(), audio_queue_lock), cpal::SampleFormat::F64 => cpal_stream::(device, config.into(), audio_queue_lock), sample_format => panic!("unsupported sample format '{sample_format}'"), } .unwrap() }; (config, stream) } fn cpal_stream( device: cpal::Device, config: cpal::StreamConfig, audio_queue_lock: Arc>>, ) -> Result, Box> where T: SizedSample + FromSample, { let data_fn = { let audio_queue_lock = audio_queue_lock.clone(); move |data: &mut [T], _: &cpal::OutputCallbackInfo| { let mut underrun = 0; { //TODO: buffer audio let mut audio_queue = audio_queue_lock.lock().unwrap(); for sample in data { let float = match audio_queue.pop_front() { Some(some) => some, None => { underrun += 1; 0.0 } }; *sample = T::from_sample(float); } } if underrun > 0 { log::error!("audio underrun {}", underrun); } } }; let err_fn = |err| eprintln!("an error occurred on stream: {}", err); let stream = device.build_output_stream(&config, data_fn, err_fn, None)?; Ok(Box::new(stream)) } fn ffmpeg_thread>( path: P, player_rx: mpsc::Receiver, video_frame_lock: Arc>>, ) -> Result<(), Box> { let audio_queue_lock = Arc::new(Mutex::new(VecDeque::new())); let (audio_config, cpal_stream) = cpal(audio_queue_lock.clone()); let mut ictx = input(&path)?; let video_stream = ictx .streams() .best(Type::Video) .ok_or(ffmpeg::Error::StreamNotFound)?; let video_stream_index = video_stream.index(); let video_context_decoder = ffmpeg::codec::context::Context::from_parameters(video_stream.parameters())?; let mut video_decoder = video_context_decoder.decoder().video()?; let video_format = video_decoder.format(); let video_width = video_decoder.width(); let video_height = video_decoder.height(); let (raw_frame_tx, raw_frame_rx) = mpsc::channel::