use std::path::PathBuf;
use std::sync::Arc;
use tokio::sync::{Mutex, mpsc};
use crate::Page;
const FPS: u32 = 25;
pub use crate::ffmpeg::{video_content_type, video_extension};
pub struct VideoRecordingHandle {
pump_task: tokio::task::JoinHandle<()>,
encoder_task: tokio::task::JoinHandle<Result<(), String>>,
output_path: PathBuf,
}
pub async fn start_recording(
page: &Page,
output_path: PathBuf,
width: u32,
height: u32,
quality: u8,
) -> Result<VideoRecordingHandle, String> {
let w = width & !1;
let h = height & !1;
let (frame_tx, frame_rx) = mpsc::channel::<(Vec<u8>, f64)>(64);
let cdp_rx = page.start_screencast(quality, w, h).await?;
let pump_task = tokio::spawn(async move {
let mut rx = cdp_rx;
while let Some((jpeg, ts)) = rx.recv().await {
if frame_tx.send((jpeg, ts)).await.is_err() {
break; }
}
});
let path = output_path.clone();
let encoder_task = tokio::task::spawn_blocking(move || crate::ffmpeg::encode_stream(frame_rx, &path, w, h, FPS));
Ok(VideoRecordingHandle {
pump_task,
encoder_task,
output_path,
})
}
impl VideoRecordingHandle {
pub async fn stop(self, page: &Page) -> Result<PathBuf, String> {
let _ = page.stop_screencast().await;
self.pump_task.abort();
self.encoder_task.await.map_err(|e| format!("encoder join: {e}"))??;
Ok(self.output_path)
}
}
type FrameBuffer = Arc<Mutex<Vec<(Vec<u8>, f64)>>>;
pub struct BufferedRecordingHandle {
frames: FrameBuffer,
pump_task: tokio::task::JoinHandle<()>,
width: u32,
height: u32,
}
pub async fn start_buffered_recording(
page: &Page,
width: u32,
height: u32,
quality: u8,
) -> Result<BufferedRecordingHandle, String> {
let w = width & !1;
let h = height & !1;
let cdp_rx = page.start_screencast(quality, w, h).await?;
let frames: FrameBuffer = Arc::new(Mutex::new(Vec::with_capacity(64)));
let frames_clone = Arc::clone(&frames);
let pump_task = tokio::spawn(async move {
let mut rx = cdp_rx;
while let Some((jpeg, ts)) = rx.recv().await {
frames_clone.lock().await.push((jpeg, ts));
}
});
Ok(BufferedRecordingHandle {
frames,
pump_task,
width: w,
height: h,
})
}
impl BufferedRecordingHandle {
pub async fn encode(self, page: &Page, output_path: PathBuf) -> Result<PathBuf, String> {
let _ = page.stop_screencast().await;
self.pump_task.abort();
let _ = self.pump_task.await;
let frames = self.frames.lock().await;
if frames.is_empty() {
return Err("no frames captured".into());
}
let w = self.width;
let h = self.height;
let frames_owned: Vec<(Vec<u8>, f64)> = frames.clone();
drop(frames);
let path = output_path.clone();
tokio::task::spawn_blocking(move || crate::ffmpeg::encode_frames(&frames_owned, &path, w, h, FPS))
.await
.map_err(|e| format!("encode join: {e}"))??;
Ok(output_path)
}
pub async fn discard(self, page: &Page) {
let _ = page.stop_screencast().await;
self.pump_task.abort();
let _ = self.pump_task.await;
}
}