video_monitor/app/views/InnerlView.py

323 lines
12 KiB
Python
Raw Permalink Normal View History

2026-08-30 22:22:11 +08:00
import os
import time
import threading
import platform
from django.db import transaction
from app.views.ViewsBase import *
from app.utils.LanguageUtils import GSettingsLangDefault
from app.utils.Utils import GB28181CodeUtils
"""
内部服务调用的接口
"""
# 按code的细粒度锁不同code完全并发同一code串行化防止并发插入重复编号
_code_locks = {}
_code_locks_guard = threading.Lock()
def _get_code_lock(code):
"""获取指定code的锁对象"""
with _code_locks_guard:
if code not in _code_locks:
_code_locks[code] = threading.Lock()
return _code_locks[code]
# 内部调用无需国际化被monitor调用用于修改流主要是国标等协议
def api_on_media_update_stream(request):
ret = False
msg = "unknown error"
if request.method == 'POST':
params = f_parsePostParams(request)
lang = f_parseRequestLang(request)
try:
forward_state = int(params.get("forwardState", 0))
app = params.get("app") # 流app
name = params.get("name") # 流name视频通道channelId
ip = params.get("ip") # 设备IP
port = int(params.get("port", 0)) # 设备SIP通信端口
clientId = params.get("clientId") # gb28181注册的client_id
parentID = params.get("parentID", "") # 设备连接的sipServer-sipId
rtpServerPort = int(params.get("rtpServerPort", 0))
rtpPort = int(params.get("rtpPort", 0))
pullStreamType = int(params.get("pullStreamType", 0))
pullStreamUrl = params.get("pullStreamUrl")
cameraSumNum = int(params.get("cameraSumNum", 1))
cameraName = params.get("cameraName", "")
cameraManufacturer = params.get("cameraManufacturer", "")
cameraModel = params.get("cameraModel", "")
cameraOwner = params.get("cameraOwner", "")
cameraCivilCode = params.get("cameraCivilCode", "")
lastKeepaliveTime = int(params.get("lastKeepaliveTime", 0))
lastRegisterTime = int(params.get("lastRegisterTime", 0))
rtpTransferMode = int(params.get("rtpTransferMode", 0))
rtpTransferAudioType = int(params.get("rtpTransferAudioType", 0))
if app is None:
raise Exception("The parameter app is invalid")
else:
app = str(app)
if name is None:
raise Exception("The parameter name is invalid")
else:
name = str(name)
if ip is None:
raise Exception("The parameter ip is invalid")
else:
ip = str(ip)
if clientId is None:
raise Exception("The parameter clientId is invalid")
else:
clientId = str(clientId)
if pullStreamUrl is None:
raise Exception("The parameter pullStreamUrl is invalid")
if cameraName is None:
raise Exception("The parameter cameraName is invalid")
now_date = datetime.now()
2026-09-04 18:16:14 +08:00
# 【关键修复 v2】GB28181 多设备共享同一 channel code 时,
# 以 (code + camera_device_id) 复合键查找/锁:
# - 不同 device_id 的同名通道各自独立创建/更新记录
# - 非 GB28181 场景 camera_device_id 为空字符串,退化为纯 code 匹配
# 注意SQLite写锁是数据库级的不能用transaction.atomic(),否则不同复合键会互相阻塞
compound_key = f"{name}::{clientId}"
code_lock = _get_code_lock(compound_key)
2026-08-30 22:22:11 +08:00
with code_lock:
2026-09-04 18:16:14 +08:00
stream = StreamModel.objects.filter(code=name, camera_device_id=clientId).first()
2026-08-30 22:22:11 +08:00
if stream:
2026-09-04 18:16:14 +08:00
# 编辑:不修改 nickname用户自定义
2026-08-30 22:22:11 +08:00
pass
else:
# 新增
stream = StreamModel()
stream.user_id = 0
stream.sort = 0
stream.code = name
stream.app = app
stream.name = name
stream.create_time = now_date
2026-09-04 18:16:14 +08:00
stream.add_type = 21 if pullStreamType == 21 else 0 # 21=GB28181 自动添加标记
2026-08-30 22:22:11 +08:00
stream.state = 0
stream.nickname = cameraName
stream.remark = ""
stream.forward_state = forward_state
stream.last_update_time = now_date
stream.pull_stream_type = pullStreamType
stream.pull_stream_url = pullStreamUrl
stream.pull_stream_ip = ip
stream.pull_stream_port = port
stream.camera_sum_num = cameraSumNum
stream.camera_name = cameraName
stream.camera_manufacturer = cameraManufacturer
stream.camera_owner = cameraOwner
stream.camera_model = cameraModel
stream.camera_civilcode = cameraCivilCode
stream.camera_device_id = clientId
stream.camera_parent_id = parentID
if lastKeepaliveTime > 0:
lastKeepaliveTime_date = datetime.fromtimestamp(int(lastKeepaliveTime / 1000))
stream.camera_last_keepalive_time = lastKeepaliveTime_date
if lastRegisterTime > 0:
lastRegisterTime_date = datetime.fromtimestamp(int(lastRegisterTime / 1000))
stream.camera_last_register_time = lastRegisterTime_date
stream.pull_stream_transfer_mode = rtpTransferMode
stream.is_audio = rtpTransferAudioType
stream.save()
ret = True
msg = LANG_VIEWS_T(request, "msg_success")
except Exception as e:
msg = str(e)
else:
msg = LANG_VIEWS_T(request, "msg_method_not_supported")
res = {
"code": 1000 if ret else 0,
"msg": msg
}
if not ret:
g_logger.warning("InnerView.api_on_media_update_stream() res=%s" % str(res))
return f_responseJson(res)
# 内部调用无需国际化被GB28181SipServer调用用于删除回退通道的数据库记录
def api_on_media_delete_stream(request):
ret = False
msg = "unknown error"
if request.method == 'POST':
try:
params = f_parsePostParams(request)
code = params.get("code")
if not code:
raise Exception("The parameter code is invalid")
2026-09-04 18:16:14 +08:00
# 支持可选 cameraDeviceIdGB28181 多设备共享 channel code 时
# 按 (code, camera_device_id) 复合键精准删除,避免误删同名不同设备的记录
camera_device_id = params.get("cameraDeviceId") or ""
if camera_device_id:
stream = StreamModel.objects.filter(code=code, camera_device_id=camera_device_id).first()
else:
stream = StreamModel.objects.filter(code=code).first()
2026-08-30 22:22:11 +08:00
if stream:
stream.delete()
2026-09-04 18:16:14 +08:00
g_gb28181SipServer.remove_channel(code, device_id=camera_device_id or None)
2026-08-30 22:22:11 +08:00
ret = True
msg = "success"
else:
msg = "stream not found"
except Exception as e:
msg = str(e)
else:
msg = "method not supported"
res = {
"code": 1000 if ret else 0,
"msg": msg
}
if not ret:
g_logger.warning("InnerView.api_on_media_delete_stream() res=%s" % str(res))
return f_responseJson(res)
# 内部调用无需国际化被monitor_zlm调用用于实时获得推流信息
def api_on_publish(request):
# ZLMediaKit Hook https://github.com/ZLMediaKit/ZLMediaKit/wiki/MediaServer%E6%94%AF%E6%8C%81%E7%9A%84HTTP-HOOK-API
ret = False
msg = "unknown error"
if request.method == 'POST':
try:
params = f_parsePostParams(request)
lang = f_parseRequestLang(request)
_app = params.get("app", "").strip()
_stream = params.get("stream", "").strip()
_tcp_id = params.get("id", "").strip()
_schema = params.get("schema", "").strip()
_ip = params.get("ip", "").strip()
_port = int(params.get("port", 0))
if _app == APP_NAME_LIVE:
# 被动推流RTSP/RTMP推流到ZLM的live应用
stream_code = _stream
stream = StreamModel.objects.filter(code=stream_code).first()
now_date = datetime.now()
if stream:
if stream.pull_stream_type == 31 or stream.pull_stream_type == 32:
pass
else:
raise Exception("This stream does not come from passive streaming")
else:
stream = StreamModel()
stream.user_id = 0
stream.sort = 0
stream.code = stream_code
stream.app = _app
stream.name = _stream
stream.create_time = now_date
stream.add_type = 0
stream.state = 0
stream.is_audio = 0 # 默认静音(与手动添加一致)
stream.pull_stream_transfer_mode = 0
stream.nickname = _stream
stream.remark = ""
stream.camera_name = _stream
stream.camera_manufacturer = _stream
stream.camera_device_id = "default" # 默认分组编号
if _schema == "rtsp":
pullStreamType = 31
elif _schema == "rtmp":
pullStreamType = 32
else:
raise Exception("unsupported schema type, schema=%s" % _schema)
pullStreamUrl = "%s://%s:%d/%s" % (_schema, _ip, _port, _tcp_id)
stream.forward_state = 1
stream.last_update_time = now_date
stream.pull_stream_type = pullStreamType
stream.pull_stream_url = pullStreamUrl
stream.pull_stream_ip = _ip
stream.pull_stream_port = _port
stream.save()
ret = True
msg = LANG_VIEWS_T(request, "msg_success")
elif _app == APP_NAME_RTP:
# gb28181接入被动接收的推流默认通过
ret = True
msg = LANG_VIEWS_T(request, "msg_success")
else:
raise Exception("unsupported app, app=%s" % _app)
except Exception as e:
msg = "error %s" % str(e)
else:
msg = LANG_VIEWS_T(request, "msg_method_not_supported")
res = {
"code": 0 if ret else -1, # 返回-1的请求推流将会被取消
"msg": msg
}
if not ret:
g_logger.warning("InnerView.api_on_publish() res=%s" % str(res))
return f_responseJson(res)
# 内部调用无需国际化被monitor_zlm调用用于实时获得not found的流信息
def api_on_stream_not_found(request):
# ZLMediaKit Hook https://github.com/ZLMediaKit/ZLMediaKit/wiki/MediaServer%E6%94%AF%E6%8C%81%E7%9A%84HTTP-HOOK-API
ret = False
msg = "unknown error"
params = None
if request.method == 'POST':
params = f_parsePostParams(request)
_app = params.get("app", "").strip()
_stream = params.get("stream", "").strip()
if _app == APP_NAME_RTP:
# gb28181 流不存在,尝试重新 invite
stream = StreamModel.objects.filter(code=_stream).first()
if stream and stream.pull_stream_type == 21:
__ret, __msg = g_gb28181SipServer.request_invite(
client_id=stream.camera_device_id, channel_id=stream.name)
if __ret:
ret = True
msg = "success"
else:
msg = __msg
else:
msg = "the stream does not exist"
else:
msg = "unsupported app"
else:
msg = LANG_VIEWS_T(request, "msg_method_not_supported")
if not ret:
g_logger.warning("InnerView.api_on_stream_not_found() params=%s,msg=%s" % (str(params), msg))
res = {
"code": 0,
"msg": "success"
}
return f_responseJson(res)