#![windows_subsystem = "windows"] 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 { // 配置中文字体 configure_fonts(&cc.egui_ctx); 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(); } } /// 配置中文字体支持 fn configure_fonts(ctx: &egui::Context) { let mut fonts = egui::FontDefinitions::default(); // 尝试从系统加载中文字体 let font_paths = [ r"C:\Windows\Fonts\msyh.ttc", // 微软雅黑 r"C:\Windows\Fonts\msyhbd.ttc", // 微软雅黑粗体 r"C:\Windows\Fonts\simsun.ttc", // 宋体 r"C:\Windows\Fonts\simhei.ttf", // 黑体 r"C:\Windows\Fonts\arial.ttf", // Arial (备用) ]; let mut font_loaded = false; for font_path in &font_paths { if let Ok(font_data) = std::fs::read(font_path) { let font_name = std::path::Path::new(font_path) .file_stem() .and_then(|s| s.to_str()) .unwrap_or("custom_font"); fonts.font_data.insert( font_name.to_owned(), egui::FontData::from_owned(font_data), ); // 将字体添加到所有字体族 fonts.families.get_mut(&egui::FontFamily::Proportional).unwrap() .insert(0, font_name.to_owned()); fonts.families.get_mut(&egui::FontFamily::Monospace).unwrap() .push(font_name.to_owned()); font_loaded = true; info!("成功加载字体: {}", font_path); break; } } if !font_loaded { // 如果系统字体加载失败,使用 egui 默认字体并尝试嵌入基本中文字体数据 error!("无法加载系统中文字体,使用默认字体"); } ctx.set_fonts(fonts); } 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); } }