2026-08-30 22:22:11 +08:00
|
|
|
"""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():
|
2026-09-04 18:16:14 +08:00
|
|
|
if self.managed_pid():
|
|
|
|
|
return True, "流媒体服务已在运行"
|
|
|
|
|
return False, "流媒体端口被非本实例服务占用;不会接管或结束该进程"
|
2026-08-30 22:22:11 +08:00
|
|
|
|
2026-09-04 18:16:14 +08:00
|
|
|
from monitor_runtime.paths import DESKTOP, data
|
|
|
|
|
work_dir = str(data(".runtime/zlm")) if DESKTOP else os.path.dirname(exe) or str(os.getcwd())
|
|
|
|
|
os.makedirs(work_dir, exist_ok=True)
|
2026-08-30 22:22:11 +08:00
|
|
|
cmd = [exe, "-c", cfg]
|
2026-09-04 18:16:14 +08:00
|
|
|
if DESKTOP:
|
|
|
|
|
log_dir = data("log/zlm")
|
|
|
|
|
log_dir.mkdir(parents=True, exist_ok=True)
|
|
|
|
|
cmd += ["--log-dir", str(log_dir)]
|
2026-08-30 22:22:11 +08:00
|
|
|
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 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():
|
|
|
|
|
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
|