Files
meetingroom-netscreen/push_screen_rust/src/main.rs

304 lines
8.5 KiB
Rust

#![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<StatusMessage>,
}
#[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);
}
}