use eframe::egui; use std::process::{Command, Stdio}; use std::sync::mpsc::{channel, Receiver}; use std::thread; use std::time::Duration; use tracing::{info, error}; // Windows API 用于设置控制台编码 #[cfg(windows)] extern "system" { fn SetConsoleOutputCP(wCodePageID: u32) -> i32; fn SetConsoleCP(wCodePageID: u32) -> i32; } #[cfg(windows)] const CP_UTF8: u32 = 65001; fn set_utf8_encoding() { #[cfg(windows)] unsafe { SetConsoleOutputCP(CP_UTF8); SetConsoleCP(CP_UTF8); } } struct PushScreenApp { server_ip: String, server_port: String, ffmpeg_path: String, device_name: String, status: String, is_streaming: bool, rx: Receiver, } #[derive(Debug, Clone)] enum StatusMessage { Status(String), Error(String), } impl PushScreenApp { fn new(_cc: &eframe::CreationContext<'_>) -> Self { let (tx, rx) = channel(); // 启动状态监控线程 let tx_clone = tx.clone(); thread::spawn(move || { loop { if let Err(e) = tx_clone.send(StatusMessage::Status("运行中".to_string())) { error!("状态发送失败: {}", e); break; } thread::sleep(Duration::from_secs(5)); } }); Self { server_ip: "192.168.1.100".to_string(), server_port: "8080".to_string(), ffmpeg_path: r"C:\ffmpeg\bin\ffmpeg.exe".to_string(), device_name: "会议室主屏".to_string(), status: "就绪".to_string(), is_streaming: false, rx, } } fn start_stream(&mut self) { if self.is_streaming { return; } info!("开始推流: {}:{}", self.server_ip, self.server_port); // 构建 FFmpeg 命令 let output_url = format!( "rtmp://{}:{}/live/{}", self.server_ip, self.server_port, self.device_name ); let args = vec![ "-f", "gdigrab", "-framerate", "30", "-i", "desktop", "-c:v", "libx264", "-preset", "ultrafast", "-tune", "zerolatency", "-f", "flv", &output_url, ]; match Command::new(&self.ffmpeg_path) .args(&args) .stdout(Stdio::null()) .stderr(Stdio::null()) .spawn() { Ok(mut child) => { info!("FFmpeg 进程启动成功, PID: {:?}", child.id()); self.is_streaming = true; self.status = format!("推流中 - {}", self.device_name); // 监控进程 thread::spawn(move || { match child.wait() { Ok(status) => { info!("FFmpeg 进程结束: {:?}", status); } Err(e) => { error!("等待 FFmpeg 进程失败: {}", e); } } }); } Err(e) => { error!("启动 FFmpeg 失败: {}", e); self.status = format!("错误: {}", e); } } } fn stop_stream(&mut self) { if !self.is_streaming { return; } info!("停止推流"); // 查找并终止 FFmpeg 进程 #[cfg(windows)] { let _ = Command::new("taskkill") .args(&["/F", "/IM", "ffmpeg.exe"]) .output(); } self.is_streaming = false; self.status = "已停止".to_string(); } } impl eframe::App for PushScreenApp { fn update(&mut self, ctx: &egui::Context, _frame: &mut eframe::Frame) { // 接收状态更新 while let Ok(msg) = self.rx.try_recv() { match msg { StatusMessage::Status(s) => { if self.is_streaming { self.status = format!("推流中 - {}", s); } } StatusMessage::Error(e) => { self.status = format!("错误: {}", e); self.is_streaming = false; } } } egui::CentralPanel::default().show(ctx, |ui| { ui.heading("投屏源控制系统"); ui.add_space(20.0); // 服务器设置 ui.group(|ui| { ui.label("服务器设置"); ui.horizontal(|ui| { ui.label("服务器 IP:"); ui.text_edit_singleline(&mut self.server_ip); }); ui.horizontal(|ui| { ui.label("端口:"); ui.text_edit_singleline(&mut self.server_port); }); }); ui.add_space(10.0); // FFmpeg 设置 ui.group(|ui| { ui.label("FFmpeg 设置"); ui.horizontal(|ui| { ui.label("FFmpeg 路径:"); ui.text_edit_singleline(&mut self.ffmpeg_path); }); }); ui.add_space(10.0); // 设备设置 ui.group(|ui| { ui.label("设备设置"); ui.horizontal(|ui| { ui.label("设备名称:"); ui.text_edit_singleline(&mut self.device_name); }); }); ui.add_space(20.0); // 状态显示 ui.group(|ui| { ui.label("状态:"); ui.label(&self.status); }); ui.add_space(20.0); // 控制按钮 ui.horizontal(|ui| { if ui.button("开始推流").clicked() { self.start_stream(); } if ui.button("停止推流").clicked() { self.stop_stream(); } if ui.button("退出").clicked() { self.stop_stream(); std::process::exit(0); } }); }); } } fn main() { // 设置 UTF-8 编码,解决中文乱码问题 set_utf8_encoding(); // 初始化日志 tracing_subscriber::fmt::init(); info!("应用程序启动"); let options = eframe::NativeOptions { viewport: egui::ViewportBuilder::default() .with_inner_size([520.0, 420.0]) .with_resizable(false), ..Default::default() }; if let Err(e) = eframe::run_native( "投屏源控制", options, Box::new(|cc| Box::new(PushScreenApp::new(cc))), ) { eprintln!("应用程序错误: {}", e); std::process::exit(1); } }