video_monitor/app/utils/MediaServerManager.py
2026-08-30 22:23:12 +08:00

149 lines
4.9 KiB
Python

"""ZLMediaKit (monitor_zlm) 进程管理 — 启动/停止/重启/状态查询。"""
import logging
import os
import subprocess
import sys
import threading
import time
logger = logging.getLogger("utils.media_server")
_MANAGER = None
_LOCK = threading.Lock()
class MediaServerManager(object):
def __init__(self):
self._proc = None
self._lock = threading.RLock()
def _paths(self):
from app.utils.GlobalUtils import g_config
exe = getattr(g_config, "mediaStartPath", "") or ""
cfg = getattr(g_config, "mediaStartConfigPath", "") or ""
return exe, cfg
def api_alive(self):
try:
from app.utils.GlobalUtils import g_zlm
ok, _msg, _data = g_zlm.getThreadsLoad()
return bool(ok)
except Exception as e:
logger.debug("api_alive: %s", e)
return False
def managed_pid(self):
with self._lock:
if self._proc is None:
return None
if self._proc.poll() is None:
return self._proc.pid
self._proc = None
return None
def status(self):
exe, cfg = self._paths()
pid = self.managed_pid()
api_ok = self.api_alive()
return {
"running": api_ok,
"api_ok": api_ok,
"managed": pid is not None,
"pid": pid or 0,
"exe": exe,
"config": cfg,
"exe_exists": bool(exe and os.path.isfile(exe)),
"config_exists": bool(cfg and os.path.isfile(cfg)),
}
def start(self):
from app.services.lifecycle import is_service_leader
if not is_service_leader():
return False, "background services are owned by another process or disabled"
exe, cfg = self._paths()
if not exe or not os.path.isfile(exe):
return False, "mediaStartPath 无效或文件不存在: %s" % (exe or "(空)")
if not cfg or not os.path.isfile(cfg):
return False, "mediaStartConfigPath 无效或文件不存在: %s" % (cfg or "(空)")
if self.api_alive():
return True, "流媒体服务已在运行"
work_dir = os.path.dirname(exe) or str(os.getcwd())
cmd = [exe, "-c", cfg]
logger.info("启动 ZLM: %s", " ".join(cmd))
try:
kwargs = {"cwd": work_dir, "stdout": subprocess.DEVNULL, "stderr": subprocess.DEVNULL}
if sys.platform == "win32":
kwargs["creationflags"] = getattr(subprocess, "CREATE_NO_WINDOW", 0)
else:
kwargs["start_new_session"] = True
with self._lock:
self._proc = subprocess.Popen(cmd, **kwargs)
for _ in range(30):
time.sleep(0.2)
if self.api_alive():
return True, "流媒体服务已启动 (pid=%s)" % self._proc.pid
return False, "进程已拉起但 API 未响应,请检查端口与 config.ini"
except Exception as e:
logger.exception("启动 ZLM 失败")
return False, str(e)
def _kill_by_image(self, exe):
if not exe:
return
name = os.path.basename(exe)
try:
if sys.platform == "win32":
subprocess.run(
["taskkill", "/F", "/IM", name],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=10,
)
else:
subprocess.run(
["pkill", "-f", exe],
stdout=subprocess.DEVNULL, stderr=subprocess.DEVNULL, timeout=10,
)
except Exception as e:
logger.warning("kill_by_image %s: %s", name, e)
def stop(self):
from app.services.lifecycle import is_service_leader
if not is_service_leader():
return False, "background services are owned by another process or disabled"
exe, _cfg = self._paths()
with self._lock:
proc = self._proc
if proc is not None and proc.poll() is None:
try:
proc.terminate()
proc.wait(timeout=5)
except Exception:
try:
proc.kill()
except Exception:
pass
self._proc = None
if self.api_alive():
self._kill_by_image(exe)
time.sleep(0.5)
if self.api_alive():
return False, "流媒体服务仍在运行,请检查是否有其他进程占用"
return True, "流媒体服务已停止"
def restart(self):
ok, msg = self.stop()
if not ok and self.api_alive():
return False, msg
time.sleep(0.8)
return self.start()
def get_media_server_manager():
global _MANAGER
with _LOCK:
if _MANAGER is None:
_MANAGER = MediaServerManager()
return _MANAGER