299 lines
12 KiB
Python
299 lines
12 KiB
Python
|
|
import os
|
|||
|
|
import time
|
|||
|
|
import json
|
|||
|
|
from datetime import datetime
|
|||
|
|
import sys
|
|||
|
|
from framework.settings import BASE_DIR, PROJECT_UA, PROJECT_BUILT, PROJECT_VERSION, PROJECT_FLAG, PROJECT_ADMIN_START_TIMESTAMP
|
|||
|
|
from app.utils.ZLMediaKitApi import ZLMediaKitApi
|
|||
|
|
from app.utils.Config import Config
|
|||
|
|
from app.utils.Logger import CreateLogger
|
|||
|
|
from app.utils.Database import Database
|
|||
|
|
from app.utils.GB28181SipServer import GB28181SipServer
|
|||
|
|
|
|||
|
|
# ========== 应用名常量 ==========
|
|||
|
|
APP_NAME_LIVE = "live"
|
|||
|
|
APP_NAME_RTP = "rtp"
|
|||
|
|
from app.utils.LanguageUtils import LANG_UI_DICT, LANG_VIEWS_USE_LANG_T
|
|||
|
|
from app.models import *
|
|||
|
|
|
|||
|
|
# BASE_DIR 是项目根目录(扁平化后 settings.py 的父父目录即根目录)
|
|||
|
|
# BASE_PARENT_DIR = str(BASE_DIR) # 扁平化后 BASE_PARENT_DIR 与 BASE_DIR 相同,均在根目录
|
|||
|
|
g_filepath_settings_json = os.path.join(BASE_DIR, "settings.json")
|
|||
|
|
g_filepath_config_json = os.path.join(BASE_DIR, "config.json")
|
|||
|
|
|
|||
|
|
g_config = Config(filepath=g_filepath_config_json)
|
|||
|
|
|
|||
|
|
__log_dir = os.path.join(BASE_DIR, "log")
|
|||
|
|
if not os.path.exists(__log_dir):
|
|||
|
|
os.makedirs(__log_dir)
|
|||
|
|
|
|||
|
|
__log_name = "%s%s.log" % ("monitor", datetime.now().strftime("%Y%m%d-%H%M%S"))
|
|||
|
|
g_logger = CreateLogger(filepath=os.path.join(__log_dir, __log_name),
|
|||
|
|
is_show_console=False,
|
|||
|
|
log_debug=g_config.logDebug)
|
|||
|
|
|
|||
|
|
g_logger.info("%s v%s,%s" % (PROJECT_UA, PROJECT_VERSION, PROJECT_FLAG))
|
|||
|
|
g_logger.info(PROJECT_BUILT)
|
|||
|
|
g_logger.info("g_filepath_config_json=%s" % g_filepath_config_json)
|
|||
|
|
g_logger.info("config.json:%s" % g_config.getStr())
|
|||
|
|
g_logger.info("logDebug=%d" % g_config.logDebug)
|
|||
|
|
|
|||
|
|
__argv_extend = sys.argv[1] if len(sys.argv) >= 2 else None
|
|||
|
|
g_logger.info("argv_extend=%s" % str(__argv_extend))
|
|||
|
|
|
|||
|
|
g_zlm = ZLMediaKitApi(logger=g_logger, config=g_config)
|
|||
|
|
g_database = Database(logger=g_logger)
|
|||
|
|
|
|||
|
|
__config_sip_server = g_config.sipServer
|
|||
|
|
# SIP 对象仅在此构造;绑定端口由显式 ServiceManager 负责。
|
|||
|
|
g_gb28181SipServer = GB28181SipServer(
|
|||
|
|
server_ip=__config_sip_server.get("sipServerIp"),
|
|||
|
|
server_port=__config_sip_server.get("sipServerPort"),
|
|||
|
|
server_id=__config_sip_server.get("sipServerId"),
|
|||
|
|
realm=__config_sip_server.get("sipServerRealm"),
|
|||
|
|
password=__config_sip_server.get("sipServerPass"),
|
|||
|
|
sip_server_timeout=__config_sip_server.get("sipServerTimeout"),
|
|||
|
|
sip_server_expiry=__config_sip_server.get("sipServerExpiry"),
|
|||
|
|
sip_transfer_mode=__config_sip_server.get("sipTransferMode"),
|
|||
|
|
rtp_transfer_mode=__config_sip_server.get("rtpTransferMode"),
|
|||
|
|
rtp_transfer_audio_type=__config_sip_server.get("rtpTransferAudioType"),
|
|||
|
|
auto_invite_after_rec_cate_log=__config_sip_server.get("autoInviteAfterRecCateLog"),
|
|||
|
|
admin_host=g_config.adminHost,
|
|||
|
|
zlm=g_zlm,
|
|||
|
|
logger=g_logger
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
g_pull_stream_types = [
|
|||
|
|
{"id": 1, "name": "RTSP"},
|
|||
|
|
{"id": 2, "name": "RTMP"},
|
|||
|
|
{"id": 3, "name": "FLV"},
|
|||
|
|
{"id": 4, "name": "HLS"},
|
|||
|
|
{"id": 21, "name": "GB28181"},
|
|||
|
|
{"id": 31, "name": "cRTSP"},
|
|||
|
|
{"id": 32, "name": "cRTMP"}
|
|||
|
|
]
|
|||
|
|
|
|||
|
|
def get_audio_types(lang='zh'):
|
|||
|
|
"""返回音频类型"""
|
|||
|
|
T = LANG_UI_DICT.get(lang, {})
|
|||
|
|
result = []
|
|||
|
|
__audio_types = [
|
|||
|
|
{"type": 0, "name": "静音", "name_key": "audio_pull_mute"},
|
|||
|
|
{"type": 1, "name": "原始音频", "name_key": "audio_pull_original"}
|
|||
|
|
]
|
|||
|
|
for audio_type in __audio_types:
|
|||
|
|
result.append({
|
|||
|
|
"type": audio_type["type"],
|
|||
|
|
"name": T.get(audio_type["name_key"], audio_type["name"])
|
|||
|
|
})
|
|||
|
|
return result
|
|||
|
|
|
|||
|
|
g_session_key_user = "user"
|
|||
|
|
g_session_key_captcha = "captcha"
|
|||
|
|
|
|||
|
|
|
|||
|
|
def _bool_cfg(v):
|
|||
|
|
if v is None:
|
|||
|
|
return False
|
|||
|
|
if isinstance(v, bool):
|
|||
|
|
return v
|
|||
|
|
if isinstance(v, (int, float)):
|
|||
|
|
return v != 0
|
|||
|
|
return str(v).strip().lower() in ("1", "true", "yes", "on")
|
|||
|
|
|
|||
|
|
|
|||
|
|
class GlobalUtils(object):
|
|||
|
|
|
|||
|
|
@staticmethod
|
|||
|
|
def addStreamProxy(stream, lang=None):
|
|||
|
|
"""开启流代理(拉流到ZLM)"""
|
|||
|
|
__ret = False
|
|||
|
|
__msg = LANG_VIEWS_USE_LANG_T(lang, "msg_unknown_error")
|
|||
|
|
|
|||
|
|
if stream.pull_stream_type in [1, 2, 3, 4]:
|
|||
|
|
enable_rtmp = 1 if g_config.isEnableMediaProxyRtmp else 0
|
|||
|
|
add_key, add_msg = g_zlm.addStreamProxy(app=stream.app,
|
|||
|
|
name=stream.name,
|
|||
|
|
origin_url=stream.pull_stream_url,
|
|||
|
|
is_audio=stream.is_audio,
|
|||
|
|
enable_rtmp=enable_rtmp)
|
|||
|
|
if add_key:
|
|||
|
|
__ret = True
|
|||
|
|
__msg = LANG_VIEWS_USE_LANG_T(lang, "stream_forward_enabled_success")
|
|||
|
|
else:
|
|||
|
|
__msg = add_msg
|
|||
|
|
elif stream.pull_stream_type == 21:
|
|||
|
|
__ret, __msg = g_gb28181SipServer.request_invite(client_id=stream.camera_device_id, channel_id=stream.name)
|
|||
|
|
if __ret:
|
|||
|
|
__msg = LANG_VIEWS_USE_LANG_T(lang, "stream_forward_enabled_success")
|
|||
|
|
elif stream.pull_stream_type in [31, 32]:
|
|||
|
|
__msg = LANG_VIEWS_USE_LANG_T(lang, "media_push_stream_hint")
|
|||
|
|
else:
|
|||
|
|
__msg = LANG_VIEWS_USE_LANG_T(lang, "media_protocol_not_supported")
|
|||
|
|
return __ret, __msg
|
|||
|
|
|
|||
|
|
@staticmethod
|
|||
|
|
def addAllStreamProxy(lang=None):
|
|||
|
|
"""开启所有流代理"""
|
|||
|
|
ret = False
|
|||
|
|
msg = LANG_VIEWS_USE_LANG_T(lang, "msg_unknown_error")
|
|||
|
|
|
|||
|
|
online_streams = g_zlm.getMediaList()
|
|||
|
|
online_stream_dict = {}
|
|||
|
|
|
|||
|
|
if len(online_streams) == 0:
|
|||
|
|
g_database.execute("update av_stream set forward_state=0")
|
|||
|
|
else:
|
|||
|
|
for d in online_streams:
|
|||
|
|
an = "{app}_{name}".format(app=d["app"], name=d["name"])
|
|||
|
|
online_stream_dict[an] = d
|
|||
|
|
|
|||
|
|
success_count = 0
|
|||
|
|
error_count = 0
|
|||
|
|
streams = StreamModel.objects.all()
|
|||
|
|
for stream in streams:
|
|||
|
|
stream_an = "{app}_{name}".format(app=stream.app, name=stream.name)
|
|||
|
|
if online_stream_dict.get(stream_an):
|
|||
|
|
success_count += 1
|
|||
|
|
else:
|
|||
|
|
__add_ret, __add_msg = GlobalUtils.addStreamProxy(stream, lang=lang)
|
|||
|
|
if __add_ret:
|
|||
|
|
stream.forward_state = 1
|
|||
|
|
stream.save()
|
|||
|
|
success_count += 1
|
|||
|
|
else:
|
|||
|
|
error_count += 1
|
|||
|
|
ret = True
|
|||
|
|
msg = LANG_VIEWS_USE_LANG_T(lang, "msg_batch_result") % (success_count, error_count)
|
|||
|
|
return ret, msg
|
|||
|
|
|
|||
|
|
@staticmethod
|
|||
|
|
def delStreamProxy(stream, lang=None):
|
|||
|
|
"""关闭流代理"""
|
|||
|
|
__ret = False
|
|||
|
|
__msg = LANG_VIEWS_USE_LANG_T(lang, "msg_unknown_error")
|
|||
|
|
|
|||
|
|
if stream.pull_stream_type in [1, 2, 3, 4]:
|
|||
|
|
del_flag, del_msg = g_zlm.delStreamProxy(app=stream.app, name=stream.name)
|
|||
|
|
if del_flag:
|
|||
|
|
__ret = True
|
|||
|
|
__msg = LANG_VIEWS_USE_LANG_T(lang, "media_forward_stop_success")
|
|||
|
|
else:
|
|||
|
|
__msg = del_msg
|
|||
|
|
elif stream.pull_stream_type == 21:
|
|||
|
|
__ret, __msg = g_gb28181SipServer.request_bye(client_id=stream.camera_device_id, channel_id=stream.name)
|
|||
|
|
if __ret:
|
|||
|
|
__msg = LANG_VIEWS_USE_LANG_T(lang, "media_forward_stop_success")
|
|||
|
|
elif stream.pull_stream_type == 31:
|
|||
|
|
__ret, __msg = g_zlm.close_streams(schema="rtsp", app=stream.app, name=stream.name)
|
|||
|
|
if __ret:
|
|||
|
|
__msg = LANG_VIEWS_USE_LANG_T(lang, "media_forward_stop_success")
|
|||
|
|
elif stream.pull_stream_type == 32:
|
|||
|
|
__ret, __msg = g_zlm.close_streams(schema="rtmp", app=stream.app, name=stream.name)
|
|||
|
|
if __ret:
|
|||
|
|
__msg = LANG_VIEWS_USE_LANG_T(lang, "media_forward_stop_success")
|
|||
|
|
else:
|
|||
|
|
__msg = LANG_VIEWS_USE_LANG_T(lang, "media_protocol_not_supported")
|
|||
|
|
return __ret, __msg
|
|||
|
|
|
|||
|
|
@staticmethod
|
|||
|
|
def delAllStreamProxy(lang=None):
|
|||
|
|
"""关闭所有流代理"""
|
|||
|
|
online_streams = g_zlm.getMediaList()
|
|||
|
|
for d in online_streams:
|
|||
|
|
stream = StreamModel.objects.filter(app=d["app"], name=d["name"]).first()
|
|||
|
|
if stream:
|
|||
|
|
__ret, __msg = GlobalUtils.delStreamProxy(stream, lang=lang)
|
|||
|
|
g_database.execute("update av_stream set forward_state=0")
|
|||
|
|
|
|||
|
|
|
|||
|
|
@staticmethod
|
|||
|
|
def apply_runtime_config(before_cfg):
|
|||
|
|
"""保存 config.json 后热更新运行时组件(不重启 Django 进程)。"""
|
|||
|
|
before_cfg = before_cfg or {}
|
|||
|
|
after_cfg = g_config.to_dict(include_secrets=True)
|
|||
|
|
notes = []
|
|||
|
|
|
|||
|
|
def _sip_snapshot(cfg):
|
|||
|
|
sip = dict((cfg or {}).get("sipServer") or {})
|
|||
|
|
return (
|
|||
|
|
sip.get("sipServerIp"), sip.get("sipServerPort"), sip.get("sipServerId"),
|
|||
|
|
sip.get("sipServerRealm"), sip.get("sipServerPass"), sip.get("sipServerTimeout"),
|
|||
|
|
sip.get("sipServerExpiry"), sip.get("sipTransferMode"), sip.get("rtpTransferMode"),
|
|||
|
|
sip.get("rtpTransferAudioType"), sip.get("autoInviteAfterRecCateLog"),
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
if _sip_snapshot(before_cfg) != _sip_snapshot(after_cfg):
|
|||
|
|
try:
|
|||
|
|
from app.services.lifecycle import is_service_leader
|
|||
|
|
if is_service_leader():
|
|||
|
|
GlobalUtils._reload_gb28181_sip()
|
|||
|
|
g_logger.info("apply_runtime_config: GB28181 SIP reloaded")
|
|||
|
|
else:
|
|||
|
|
notes.append("gb28181")
|
|||
|
|
except Exception as e:
|
|||
|
|
g_logger.warning("apply_runtime_config: GB28181 reload failed: %s", e)
|
|||
|
|
notes.append("gb28181")
|
|||
|
|
|
|||
|
|
before_rec = _bool_cfg(before_cfg.get("recordingEnabled"))
|
|||
|
|
after_rec = bool(g_config.recordingEnabled)
|
|||
|
|
if after_rec != before_rec:
|
|||
|
|
try:
|
|||
|
|
from app.services.lifecycle import get_service_manager
|
|||
|
|
get_service_manager().reconcile()
|
|||
|
|
except Exception as e:
|
|||
|
|
g_logger.warning("apply_runtime_config: recording manager: %s", e)
|
|||
|
|
|
|||
|
|
if before_cfg.get("adminPort") != after_cfg.get("adminPort"):
|
|||
|
|
notes.append("adminPort")
|
|||
|
|
if _bool_cfg(before_cfg.get("logDebug")) != bool(g_config.logDebug):
|
|||
|
|
notes.append("logDebug")
|
|||
|
|
|
|||
|
|
media_keys = (
|
|||
|
|
"mediaHttpPort", "mediaRtspPort", "mediaRtmpPort",
|
|||
|
|
"mediaStartPath", "mediaStartConfigPath", "mediaSecret",
|
|||
|
|
)
|
|||
|
|
if any(before_cfg.get(k) != after_cfg.get(k) for k in media_keys):
|
|||
|
|
notes.append("zlm")
|
|||
|
|
|
|||
|
|
return notes
|
|||
|
|
|
|||
|
|
@staticmethod
|
|||
|
|
def _reload_gb28181_sip():
|
|||
|
|
global g_gb28181SipServer
|
|||
|
|
try:
|
|||
|
|
g_gb28181SipServer.stop()
|
|||
|
|
except Exception:
|
|||
|
|
pass
|
|||
|
|
sip = dict(g_config.sipServer or {})
|
|||
|
|
g_gb28181SipServer = GB28181SipServer(
|
|||
|
|
server_ip=sip.get("sipServerIp"),
|
|||
|
|
server_port=sip.get("sipServerPort"),
|
|||
|
|
server_id=sip.get("sipServerId"),
|
|||
|
|
realm=sip.get("sipServerRealm"),
|
|||
|
|
password=sip.get("sipServerPass"),
|
|||
|
|
sip_server_timeout=sip.get("sipServerTimeout"),
|
|||
|
|
sip_server_expiry=sip.get("sipServerExpiry"),
|
|||
|
|
sip_transfer_mode=sip.get("sipTransferMode"),
|
|||
|
|
rtp_transfer_mode=sip.get("rtpTransferMode"),
|
|||
|
|
rtp_transfer_audio_type=sip.get("rtpTransferAudioType"),
|
|||
|
|
auto_invite_after_rec_cate_log=sip.get("autoInviteAfterRecCateLog"),
|
|||
|
|
admin_host=g_config.adminHost,
|
|||
|
|
zlm=g_zlm,
|
|||
|
|
logger=g_logger,
|
|||
|
|
)
|
|||
|
|
g_gb28181SipServer.start()
|
|||
|
|
|
|||
|
|
|
|||
|
|
class CheckServerUtils():
|
|||
|
|
@staticmethod
|
|||
|
|
def checkVersion(request_ip, peer_ip, peer_port, lang=None):
|
|||
|
|
# Network and host information parameters are intentionally ignored.
|
|||
|
|
from app.services.telemetry import check_update
|
|||
|
|
return check_update(lang=lang)
|
|||
|
|
|
|||
|
|
@staticmethod
|
|||
|
|
def reportHeart(report_count, lang=None):
|
|||
|
|
from app.services.telemetry import send_heartbeat
|
|||
|
|
return send_heartbeat(report_count)
|