use super::PlayaApp;
use crate::core::player_events::*;
use crate::entities::frame::{PixelBuffer, TonemapMode};
use crate::entities::node::Node;
use crate::server::ApiCommand;
use eframe::egui;
use image::{ImageBuffer, Rgba};
impl PlayaApp {
pub fn start_api_server(&mut self, ctx: &egui::Context) {
if self.api_command_rx.is_some() {
return; }
*self.api_state.egui_ctx.write().unwrap() = Some(ctx.clone());
let port = self.settings.api_server_port.unwrap_or(9876);
if self.settings.api_server_enabled {
log::info!("Starting REST API server on port {}", port);
let rx = crate::server::ApiServer::start(port, self.api_state.clone());
self.api_command_rx = Some(rx);
}
}
pub fn update_api_state(&mut self) {
{
let mut player = self.api_state.player.write().unwrap();
player.frame = self.player.current_frame(&self.project);
player.fps = self.player.fps_play();
player.playing = self.player.is_playing();
player.loop_enabled = self.player.loop_enabled();
player.active_comp = self.player.active_comp();
}
{
let mut comp = self.api_state.comp.write().unwrap();
*comp = self.player.active_comp().and_then(|uuid| {
self.project.with_comp(uuid, |c| crate::server::CompSnapshot {
uuid,
name: c.name().to_string(),
width: c.dim().0 as u32,
height: c.dim().1 as u32,
duration: c.frame_count(),
in_frame: c._in(),
out_frame: c._out(),
})
});
}
{
let mut cache = self.api_state.cache.write().unwrap();
let (used, limit) = self.cache_manager.mem();
cache.memory_used_mb = used as f32 / (1024.0 * 1024.0);
cache.memory_limit_mb = limit as f32 / (1024.0 * 1024.0);
}
}
pub fn handle_api_commands(&mut self) {
let commands: Vec<ApiCommand> = if let Some(ref rx) = self.api_command_rx {
let mut cmds = Vec::new();
while let Ok(cmd) = rx.try_recv() {
cmds.push(cmd);
}
cmds
} else {
return;
};
for cmd in commands {
log::trace!("API command: {:?}", cmd);
match cmd {
ApiCommand::Play => {
self.event_bus.emit(TogglePlayPauseEvent);
if !self.player.is_playing() {
self.event_bus.emit(TogglePlayPauseEvent);
}
}
ApiCommand::Pause => {
if self.player.is_playing() {
self.event_bus.emit(TogglePlayPauseEvent);
}
}
ApiCommand::Stop => {
self.event_bus.emit(StopEvent);
}
ApiCommand::SetFrame(frame) => {
self.event_bus.emit(SetFrameEvent(frame));
}
ApiCommand::SetFps(fps) => {
self.player.set_fps_base(fps);
}
ApiCommand::ToggleLoop => {
self.event_bus.emit(ToggleLoopEvent);
}
ApiCommand::LoadSequence(path) => {
let _ = self.load_sequences(vec![std::path::PathBuf::from(path)]);
}
ApiCommand::EmitEvent { event_type, payload } => {
match event_type.as_str() {
"TogglePlayPause" => self.event_bus.emit(TogglePlayPauseEvent),
"Stop" => self.event_bus.emit(StopEvent),
"JumpToStart" => self.event_bus.emit(JumpToStartEvent),
"JumpToEnd" => self.event_bus.emit(JumpToEndEvent),
"StepForward" => self.event_bus.emit(StepForwardEvent),
"StepBackward" => self.event_bus.emit(StepBackwardEvent),
"ToggleLoop" => self.event_bus.emit(ToggleLoopEvent),
_ => {
log::warn!("Unknown event type: {} (payload: {})", event_type, payload);
}
}
}
ApiCommand::Screenshot { viewport_only, response } => {
self.take_screenshot(viewport_only, response);
}
ApiCommand::Exit => {
log::info!("Exit command received via REST API");
self.exit_requested = true;
}
ApiCommand::NextFrame => {
self.event_bus.emit(StepForwardEvent);
}
ApiCommand::PrevFrame => {
self.event_bus.emit(StepBackwardEvent);
}
}
}
}
pub fn take_screenshot(&mut self, viewport_only: bool, response: crossbeam_channel::Sender<Result<Vec<u8>, String>>) {
self.pending_screenshots.push((viewport_only, response));
log::trace!("Screenshot request queued ({} waiting), viewport_only={}", self.pending_screenshots.len(), viewport_only);
}
pub fn capture_raw_frame(&self) -> Result<Vec<u8>, String> {
let frame = match &self.frame {
Some(f) => f,
None => return Err("No frame loaded".to_string()),
};
let (width, height) = frame.resolution();
let buffer = frame.buffer();
let rgba_data: Vec<u8> = match buffer.as_ref() {
PixelBuffer::U8(data) => data.clone(),
PixelBuffer::F16(_) | PixelBuffer::F32(_) => {
let tonemapped = frame.tonemap(TonemapMode::ACES)
.map_err(|e| format!("Tonemap failed: {}", e))?;
match tonemapped.buffer().as_ref() {
PixelBuffer::U8(data) => data.clone(),
_ => return Err("Tonemap did not produce U8 buffer".to_string()),
}
}
};
let img: ImageBuffer<Rgba<u8>, Vec<u8>> =
ImageBuffer::from_raw(width as u32, height as u32, rgba_data)
.ok_or_else(|| "Failed to create image buffer".to_string())?;
let mut jpeg_bytes: Vec<u8> = Vec::new();
let rgb_img = image::DynamicImage::ImageRgba8(img).to_rgb8();
let mut cursor = std::io::Cursor::new(&mut jpeg_bytes);
let mut encoder = image::codecs::jpeg::JpegEncoder::new_with_quality(&mut cursor, 90);
encoder.encode(
rgb_img.as_raw(),
rgb_img.width(),
rgb_img.height(),
image::ExtendedColorType::Rgb8
).map_err(|e| format!("JPEG encoding failed: {}", e))?;
log::info!("Raw frame screenshot: {}x{}, {} bytes", width, height, jpeg_bytes.len());
Ok(jpeg_bytes)
}
}