Files
meetingroom-netscreen/push_screen.py

326 lines
13 KiB
Python
Raw Normal View History

import subprocess
import psutil
import os
import sys
import socket
import time
2026-03-19 09:49:31 +08:00
import json
from loguru import logger
from PySide6.QtWidgets import (QApplication, QMainWindow, QPushButton, QMessageBox,
QVBoxLayout, QWidget, QLabel, QGroupBox, QGridLayout,
2026-03-19 09:49:31 +08:00
QStatusBar, QFrame, QDialog, QLineEdit, QFormLayout,
QDialogButtonBox, QFileDialog, QHBoxLayout)
2026-03-19 09:49:31 +08:00
from PySide6.QtCore import Qt, QThread, Signal
from PySide6.QtGui import QFont
logger.add(sys.stderr, format="{time:YYYY-MM-DD HH:mm:ss} | {level} | {message}", level="INFO")
2026-03-19 09:49:31 +08:00
CONFIG_FILE = os.path.join(os.path.dirname(os.path.abspath(__file__)), "config.json")
def load_config():
if os.path.exists(CONFIG_FILE):
with open(CONFIG_FILE, 'r', encoding='utf-8') as f:
return json.load(f)
return {
"server_ip": "192.168.1.100",
"mediamtx_path": r"D:\ScreenCast\mediamtx\mediamtx.exe",
"ffmpeg_path": r"D:\ScreenCast\ffmpeg\bin\ffmpeg.exe",
"stream_path": "screen"
}
def save_config(config):
with open(CONFIG_FILE, 'w', encoding='utf-8') as f:
json.dump(config, f, ensure_ascii=False, indent=4)
logger.info(f"配置已保存到 {CONFIG_FILE}")
class ConfigDialog(QDialog):
def __init__(self, config, parent=None):
super().__init__(parent)
self.config = config
self.setWindowTitle("设置")
self.setFixedSize(450, 220)
layout = QFormLayout(self)
self.server_ip_edit = QLineEdit(config["server_ip"])
self.mediamtx_path_edit = QLineEdit(config["mediamtx_path"])
self.ffmpeg_path_edit = QLineEdit(config["ffmpeg_path"])
self.stream_path_edit = QLineEdit(config["stream_path"])
self.mediamtx_btn = QPushButton("浏览...")
self.mediamtx_btn.clicked.connect(lambda: self.browse("mediamtx"))
self.ffmpeg_btn = QPushButton("浏览...")
self.ffmpeg_btn.clicked.connect(lambda: self.browse("ffmpeg"))
path_layout1 = QGridLayout()
path_layout1.addWidget(self.mediamtx_path_edit, 0, 0)
path_layout1.addWidget(self.mediamtx_btn, 0, 1)
path_layout1.addWidget(self.ffmpeg_path_edit, 1, 0)
path_layout1.addWidget(self.ffmpeg_btn, 1, 1)
layout.addRow("服务器IP:", self.server_ip_edit)
layout.addRow("MediaMTX路径:", self.mediamtx_path_edit)
layout.addRow("FFmpeg路径:", self.ffmpeg_path_edit)
layout.addRow("推流路径:", self.stream_path_edit)
buttons = QDialogButtonBox(QDialogButtonBox.Ok | QDialogButtonBox.Cancel)
buttons.accepted.connect(self.accept)
buttons.rejected.connect(self.reject)
layout.addRow(buttons)
def browse(self, field):
if field == "mediamtx":
path = QFileDialog.getOpenFileName(self, "选择MediaMTX", "", "Executable (*.exe)")[0]
if path:
self.mediamtx_path_edit.setText(path)
elif field == "ffmpeg":
path = QFileDialog.getOpenFileName(self, "选择FFmpeg", "", "Executable (*.exe)")[0]
if path:
self.ffmpeg_path_edit.setText(path)
def get_config(self):
return {
"server_ip": self.server_ip_edit.text().strip(),
"mediamtx_path": self.mediamtx_path_edit.text().strip(),
"ffmpeg_path": self.ffmpeg_path_edit.text().strip(),
"stream_path": self.stream_path_edit.text().strip()
}
class ConnectionChecker(QThread):
status_update = Signal(dict)
def run(self):
while True:
2026-03-19 09:49:31 +08:00
cfg = self.config
status = {
2026-03-19 09:49:31 +08:00
"mediamtx": self.check_mediamtx(cfg["mediamtx_path"]),
"ffmpeg": self.check_ffmpeg(cfg["ffmpeg_path"]),
"server": self.check_server(cfg["server_ip"]),
"server_port": self.check_port(cfg["server_ip"], 8554),
}
self.status_update.emit(status)
time.sleep(3)
2026-03-19 09:49:31 +08:00
def set_config(self, config):
self.config = config
def check_mediamtx(self, path):
for proc in psutil.process_iter(['name']):
if proc.info['name'] == 'mediamtx.exe':
return True
return False
2026-03-19 09:49:31 +08:00
def check_ffmpeg(self, path):
return os.path.exists(path)
2026-03-19 09:49:31 +08:00
def check_server(self, ip):
try:
2026-03-19 09:49:31 +08:00
socket.create_connection((ip, 8554), timeout=2)
return True
except:
return False
def check_port(self, ip, port):
try:
sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
sock.settimeout(2)
result = sock.connect_ex((ip, port))
sock.close()
return result == 0
except:
return False
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
2026-03-19 09:49:31 +08:00
self.config = load_config()
self.setWindowTitle("投屏源控制")
2026-03-19 09:46:44 +08:00
self.setFixedSize(520, 420)
self.status_bar = QStatusBar()
self.setStatusBar(self.status_bar)
central_widget = QWidget()
self.setCentralWidget(central_widget)
main_layout = QVBoxLayout(central_widget)
title_layout = QHBoxLayout()
title_layout.addStretch()
self.btn_settings = QPushButton("⚙ 设置")
self.btn_settings.setFixedSize(70, 30)
self.btn_settings.clicked.connect(self.open_settings)
title_layout.addWidget(self.btn_settings)
title_label = QLabel("会议投屏系统 - 主播端")
title_font = QFont("Microsoft YaHei", 14, QFont.Bold)
title_label.setFont(title_font)
title_label.setAlignment(Qt.AlignCenter)
title_main_layout = QVBoxLayout()
title_main_layout.addLayout(title_layout)
title_main_layout.addWidget(title_label)
main_layout.addLayout(title_main_layout)
config_group = QGroupBox("本地配置")
config_layout = QGridLayout()
self.mediamtx_label = QLabel("MediaMTX: 未检测")
self.ffmpeg_label = QLabel("FFmpeg: 未检测")
self.server_label = QLabel("服务器连接: 未检测")
self.port_label = QLabel("RTSP端口: 未检测")
self.mediamtx_indicator = QLabel("")
self.mediamtx_indicator.setFixedWidth(20)
self.ffmpeg_indicator = QLabel("")
self.ffmpeg_indicator.setFixedWidth(20)
self.server_indicator = QLabel("")
self.server_indicator.setFixedWidth(20)
self.port_indicator = QLabel("")
self.port_indicator.setFixedWidth(20)
config_layout.addWidget(self.mediamtx_indicator, 0, 0)
config_layout.addWidget(self.mediamtx_label, 0, 1)
config_layout.addWidget(self.ffmpeg_indicator, 1, 0)
config_layout.addWidget(self.ffmpeg_label, 1, 1)
config_layout.addWidget(self.server_indicator, 2, 0)
config_layout.addWidget(self.server_label, 2, 1)
config_layout.addWidget(self.port_indicator, 3, 0)
config_layout.addWidget(self.port_label, 3, 1)
config_group.setLayout(config_layout)
main_layout.addWidget(config_group)
2026-03-19 09:49:31 +08:00
self.info_group = QGroupBox("流信息")
self.update_info_group()
main_layout.addWidget(self.info_group)
self.btn_push = QPushButton("🎬 开始全屏投屏")
self.btn_push.setFixedHeight(50)
self.btn_push.setFont(QFont("Microsoft YaHei", 11))
self.btn_push.setStyleSheet("""
QPushButton {
background-color: #2E7D32;
color: white;
border: none;
border-radius: 8px;
font-weight: bold;
}
QPushButton:hover {
background-color: #388E3C;
}
QPushButton:pressed {
background-color: #1B5E20;
}
""")
self.btn_push.clicked.connect(self.push_full_screen)
main_layout.addWidget(self.btn_push)
self.log_label = QLabel("日志: 等待启动...")
self.log_label.setWordWrap(True)
self.log_label.setFrameShape(QFrame.Shape.StyledPanel)
self.log_label.setFixedHeight(40)
main_layout.addWidget(self.log_label)
self.checker = ConnectionChecker()
2026-03-19 09:49:31 +08:00
self.checker.set_config(self.config)
self.checker.status_update.connect(self.update_status)
self.checker.start()
logger.info("投屏源控制界面已初始化")
2026-03-19 09:49:31 +08:00
def update_info_group(self):
cfg = self.config
if self.info_group.layout():
QWidget().setLayout(self.info_group.layout())
info_layout = QGridLayout()
info_layout.addWidget(QLabel("服务器IP:"), 0, 0)
info_layout.addWidget(QLabel(cfg["server_ip"]), 0, 1)
info_layout.addWidget(QLabel("推流地址:"), 1, 0)
info_layout.addWidget(QLabel(f"rtsp://{cfg['server_ip']}:8554/{cfg['stream_path']}"), 1, 1)
info_layout.addWidget(QLabel("访问地址:"), 2, 0)
info_layout.addWidget(QLabel(f"http://{cfg['server_ip']}:8889/webrtc.html?src={cfg['stream_path']}"), 2, 1)
self.info_group.setLayout(info_layout)
def open_settings(self):
dialog = ConfigDialog(self.config, self)
if dialog.exec():
self.config = dialog.get_config()
save_config(self.config)
self.checker.set_config(self.config)
self.update_info_group()
self.log_label.setText("日志: 配置已更新")
def update_status(self, status):
if status["mediamtx"]:
self.mediamtx_indicator.setStyleSheet("color: green; font-size: 16px;")
2026-03-19 09:49:31 +08:00
self.mediamtx_label.setText("MediaMTX: ✓ 运行中")
else:
self.mediamtx_indicator.setStyleSheet("color: red; font-size: 16px;")
2026-03-19 09:49:31 +08:00
self.mediamtx_label.setText("MediaMTX: ✗ 未运行")
if status["ffmpeg"]:
self.ffmpeg_indicator.setStyleSheet("color: green; font-size: 16px;")
2026-03-19 09:49:31 +08:00
self.ffmpeg_label.setText("FFmpeg: ✓ 已安装")
else:
self.ffmpeg_indicator.setStyleSheet("color: red; font-size: 16px;")
2026-03-19 09:49:31 +08:00
self.ffmpeg_label.setText("FFmpeg: ✗ 未找到")
if status["server"]:
self.server_indicator.setStyleSheet("color: green; font-size: 16px;")
2026-03-19 09:49:31 +08:00
self.server_label.setText("服务器连接: ✓ 可连接")
else:
self.server_indicator.setStyleSheet("color: orange; font-size: 16px;")
2026-03-19 09:49:31 +08:00
self.server_label.setText("服务器连接: ✗ 连接失败")
if status["server_port"]:
self.port_indicator.setStyleSheet("color: green; font-size: 16px;")
2026-03-19 09:49:31 +08:00
self.port_label.setText("RTSP端口: ✓ 端口开放")
else:
self.port_indicator.setStyleSheet("color: orange; font-size: 16px;")
2026-03-19 09:49:31 +08:00
self.port_label.setText("RTSP端口: ✗ 端口关闭")
def start_mediamtx(self):
2026-03-19 09:49:31 +08:00
cfg = self.config
if not self.checker.check_mediamtx(cfg["mediamtx_path"]):
logger.info("启动MediaMTX服务")
2026-03-19 09:49:31 +08:00
subprocess.Popen([cfg["mediamtx_path"]], cwd=os.path.dirname(cfg["mediamtx_path"]), creationflags=subprocess.CREATE_NEW_CONSOLE)
time.sleep(3)
logger.info("MediaMTX服务已启动")
self.log_label.setText("日志: MediaMTX服务已启动")
else:
logger.info("MediaMTX已在运行")
self.log_label.setText("日志: MediaMTX已在运行")
def push_full_screen(self):
2026-03-19 09:49:31 +08:00
cfg = self.config
logger.info("开始全屏投屏")
self.start_mediamtx()
2026-03-19 09:49:31 +08:00
if not os.path.exists(cfg["ffmpeg_path"]):
logger.error("FFmpeg未安装")
QMessageBox.warning(self, "错误", "FFmpeg未安装请检查配置")
return
cmd = [
2026-03-19 09:49:31 +08:00
cfg["ffmpeg_path"],
"-f", "gdigrab", "-framerate", "30", "-i", "desktop",
"-c:v", "libx264", "-preset", "ultrafast", "-tune", "zerolatency",
2026-03-19 09:49:31 +08:00
"-f", "rtsp", f"rtsp://{cfg['server_ip']}:8554/{cfg['stream_path']}"
]
logger.info(f"执行FFmpeg命令: {' '.join(cmd)}")
subprocess.Popen(cmd, creationflags=subprocess.CREATE_NEW_CONSOLE)
logger.info("FFmpeg推流已启动")
self.log_label.setText("日志: FFmpeg推流已启动正在向服务器推送...")
2026-03-19 09:49:31 +08:00
QMessageBox.information(self, "提示", f"全屏投屏已启动!\n\n接收端可打开浏览器访问:\nhttp://{cfg['server_ip']}:8889/webrtc.html?src={cfg['stream_path']}")
def closeEvent(self, event):
self.checker.terminate()
event.accept()
if __name__ == "__main__":
logger.info("应用程序启动")
app = QApplication(sys.argv)
app.setStyle("Fusion")
window = MainWindow()
window.show()
sys.exit(app.exec())