Browse Source

完成了网页端视频显示的优化,使用了websocket

master
张龙 10 months ago
parent
commit
575cc3c8ca
  1. 2
      .idea/FristProject.iml
  2. 2
      .idea/misc.xml
  3. 6
      config.yaml
  4. 62
      core/ModelManager.py
  5. 3
      model/plugins/ModelBase.py
  6. 2
      run.py
  7. 295
      web/API/viedo.py
  8. 13
      web/__init__.py
  9. 107
      web/main/static/resources/scripts/aiortc-client-new.js
  10. 56
      web/main/templates/实时预览.html
  11. BIN
      zfbox.db

2
.idea/FristProject.iml

@ -2,7 +2,7 @@
<module type="PYTHON_MODULE" version="4"> <module type="PYTHON_MODULE" version="4">
<component name="NewModuleRootManager"> <component name="NewModuleRootManager">
<content url="file://$MODULE_DIR$" /> <content url="file://$MODULE_DIR$" />
<orderEntry type="jdk" jdkName="Remote Python 3.9.2 (sftp://root@192.168.3.48:22/usr/local/miniconda3/bin/python)" jdkType="Python SDK" /> <orderEntry type="jdk" jdkName="PyTorch" jdkType="Python SDK" />
<orderEntry type="sourceFolder" forTests="false" /> <orderEntry type="sourceFolder" forTests="false" />
</component> </component>
</module> </module>

2
.idea/misc.xml

@ -3,7 +3,7 @@
<component name="Black"> <component name="Black">
<option name="sdkName" value="PyTorch" /> <option name="sdkName" value="PyTorch" />
</component> </component>
<component name="ProjectRootManager" version="2" project-jdk-name="Remote Python 3.9.2 (sftp://root@192.168.3.48:22/usr/local/miniconda3/bin/python)" project-jdk-type="Python SDK" /> <component name="ProjectRootManager" version="2" project-jdk-name="PyTorch" project-jdk-type="Python SDK" />
<component name="PyCharmProfessionalAdvertiser"> <component name="PyCharmProfessionalAdvertiser">
<option name="shown" value="true" /> <option name="shown" value="true" />
</component> </component>

6
config.yaml

@ -28,11 +28,11 @@ ALLOWED_EXTENSIONS : {'zip'}
RTSP_Check_Time : 600 #10分钟 RTSP_Check_Time : 600 #10分钟
#model #model
model_platform : acl #acl gpu cpu model_platform : cpu #acl gpu cpu
weight_path: /model/weights weight_path: /model/weights
yolov5_path: D:/Project/FristProject/model/base_model/yolov5 #使用绝对路径,不同的部署环境需要修改! yolov5_path: D:/Project/FristProject/model/base_model/yolov5 #使用绝对路径,不同的部署环境需要修改!
cap_sleep_time: 300 #5分钟 cap_sleep_time: 300 #5分钟
buffer_len: 300 #分析后画面缓冲区帧数 -- 可以与验证帧率结合确定缓冲区大小 buffer_len: 100 #分析后画面缓冲区帧数 -- 可以与验证帧率结合确定缓冲区大小
RESET_INTERVAL : 100000 #帧数重置上限 RESET_INTERVAL : 100000 #帧数重置上限
frame_rate : 20 #帧率参考值 -- 后续作用主要基于verify_rate进行帧率控制 frame_rate : 20 #帧率参考值 -- 后续作用主要基于verify_rate进行帧率控制
verify_rate : 5 #验证帧率--- 也就是视频输出的帧率 verify_rate : 10 #验证帧率--- 也就是视频输出的帧率

62
core/ModelManager.py

@ -175,32 +175,32 @@ class ModelManager:
# 使用 模型 进行目标检测 # 使用 模型 进行目标检测
i_warn_count = 0 #报警标签 i_warn_count = 0 #报警标签
isverify = False isverify = False
# for i in range(len(myModle_list)): # 遍历通道关联的算法进行检测,若不控制模型数量,有可能需要考虑多线程执行。 for i in range(len(myModle_list)): # 遍历通道关联的算法进行检测,若不控制模型数量,有可能需要考虑多线程执行。
# model = myModle_list[i] model = myModle_list[i]
# data = myModle_data[i] data = myModle_data[i]
# schedule = schedule_list[i] schedule = schedule_list[i]
# result = result_list[i] result = result_list[i]
# #验证检测计划,是否在布防时间内 #验证检测计划,是否在布防时间内
# now = datetime.datetime.now() # 获取当前日期和时间 now = datetime.datetime.now() # 获取当前日期和时间
# weekday = now.weekday() # 获取星期几,星期一是0,星期天是6 weekday = now.weekday() # 获取星期几,星期一是0,星期天是6
# hour = now.hour hour = now.hour
# result.pop(0) # 保障结果数组定长 --先把最早的结果推出数组 result.pop(0) # 保障结果数组定长 --先把最早的结果推出数组
# if schedule[weekday][hour] == 1: #不在计划则不进行验证,直接返回图片 if schedule[weekday][hour] == 1: #不在计划则不进行验证,直接返回图片
# # 调用模型,进行检测,model是动态加载的,具体的判断标准由模型内执行 ---- ********* # 调用模型,进行检测,model是动态加载的,具体的判断标准由模型内执行 ---- *********
# isverify = True isverify = True
# detections, bwarn, warntext = model.verify(img, data,isdraw) #****************重要 detections, bwarn, warntext = model.verify(img, data,isdraw) #****************重要
# # 对识别结果要部要进行处理 # 对识别结果要部要进行处理
# if bwarn: # 整个识别有产生报警 if bwarn: # 整个识别有产生报警
# #根据模型设定的时间和占比判断是否 #根据模型设定的时间和占比判断是否
# # 绘制报警文本 # 绘制报警文本
# cv2.putText(img, 'Intruder detected!', (50, (i_warn_count + 1) * 50), cv2.putText(img, 'Intruder detected!', (50, (i_warn_count + 1) * 50),
# cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2) cv2.FONT_HERSHEY_SIMPLEX, 1, (0, 0, 255), 2)
# i_warn_count += 1 i_warn_count += 1
# result.append(1) #要验证数组修改,是地址修改吗? result.append(1) #要验证数组修改,是地址修改吗?
# else: #没有产生报警也需要记录,统一计算占比 else: #没有产生报警也需要记录,统一计算占比
# result.append(0) result.append(0)
# else: else:
# result.append(0) result.append(0)
if not isverify: #没做处理,直接返回的,需要控制下帧率,太快读取没有意义。 if not isverify: #没做处理,直接返回的,需要控制下帧率,太快读取没有意义。
time.sleep(1.0/self.frame_rate) #给个默认帧率,不超过30帧,---若经过模型计算,CPU下单模型也就12帧这样 time.sleep(1.0/self.frame_rate) #给个默认帧率,不超过30帧,---若经过模型计算,CPU下单模型也就12帧这样
@ -247,7 +247,8 @@ class ModelManager:
#获取视频通道的模型相关数据-list #获取视频通道的模型相关数据-list
for model in myModels: for model in myModels:
#基于基类实例化模块类 #基于基类实例化模块类
m = self._import_model("",model[5],model[8]) #动态加载模型处理文件py --需要验证模型文件是否能加载 #m = self._import_model("",model[5],model[8]) #动态加载模型处理文件py --需要验证模型文件是否能加载
m = None
if m: if m:
myModle_list.append(m) #没有成功加载的模型原画输出 myModle_list.append(m) #没有成功加载的模型原画输出
myModle_data.append(model) myModle_data.append(model)
@ -326,9 +327,10 @@ class ModelManager:
# end_time = time.time() # 结束时间 # end_time = time.time() # 结束时间
# print(f"Processing time: {end_time - start_time} seconds") # print(f"Processing time: {end_time - start_time} seconds")
# 本地显示---测试使用 # 本地显示---测试使用
# cv2.imshow('Frame', img) if channel_id == 2:
# if cv2.waitKey(1) & 0xFF == ord('q'): cv2.imshow(str(channel_id), img)
# break if cv2.waitKey(1) & 0xFF == ord('q'):
break
#结束线程 #结束线程
cap.release() cap.release()
#cv2.destroyAllWindows() #cv2.destroyAllWindows()

3
model/plugins/ModelBase.py

@ -5,7 +5,8 @@ import numpy as np
import cv2 import cv2
import ast import ast
import platform import platform
import acl if myCongif.get_data("model_platform") == "acl":
import acl
#-----acl相关------ #-----acl相关------
SUCCESS = 0 # 成功状态值 SUCCESS = 0 # 成功状态值

2
run.py

@ -25,6 +25,6 @@ if __name__ == '__main__':
raise NotImplementedError(f"Unsupported operating system: {system}") raise NotImplementedError(f"Unsupported operating system: {system}")
print(free/(1024*1024)) print(free/(1024*1024))
mMM.start_work() # 启动所有通道的处理 mMM.start_work() # 启动所有通道的处理
mVManager.start_check_rtsp() #线程更新视频在线情况 #mVManager.start_check_rtsp() #线程更新视频在线情况
web.run(debug=True,port=5001,host="0.0.0.0") web.run(debug=True,port=5001,host="0.0.0.0")

295
web/API/viedo.py

@ -2,7 +2,7 @@ import cv2
import asyncio import asyncio
import time import time
from . import api from . import api
from quart import jsonify, request from quart import jsonify, request,websocket
from aiortc import RTCPeerConnection, RTCSessionDescription, VideoStreamTrack,RTCConfiguration from aiortc import RTCPeerConnection, RTCSessionDescription, VideoStreamTrack,RTCConfiguration
from core.ModelManager import mMM from core.ModelManager import mMM
from core.DBManager import mDBM from core.DBManager import mDBM
@ -10,6 +10,7 @@ from myutils.ConfigManager import myCongif
from fractions import Fraction from fractions import Fraction
import threading import threading
import logging import logging
from quart.helpers import Response
# 配置日志 # 配置日志
logging.basicConfig(level=logging.INFO) logging.basicConfig(level=logging.INFO)
@ -53,48 +54,48 @@ pcs = {}
''' '''
---------------------传输-------------------------- ---------------------传输--------------------------
''' '''
class VideoTransformTrack(VideoStreamTrack): # class VideoTransformTrack(VideoStreamTrack):
kind = "video" # kind = "video"
def __init__(self,cid,itype=1): #0-usb,1-RTSP,2-海康SDK --itype已没有作用 # def __init__(self,cid,itype=1): #0-usb,1-RTSP,2-海康SDK --itype已没有作用
super().__init__() # super().__init__()
self.channel_id = cid # self.channel_id = cid
#
self.frame_rate = myCongif.get_data("frame_rate") # self.frame_rate = myCongif.get_data("frame_rate")
self.time_base = Fraction(1, self.frame_rate) # self.time_base = Fraction(1, self.frame_rate)
self.frame_count = 0 # self.frame_count = 0
self.start_time = time.time() # self.start_time = time.time()
#
async def recv(self): # async def recv(self):
new_frame = None # new_frame = None
time.sleep(1.0 / self.frame_rate) # time.sleep(1.0 / self.frame_rate)
new_frame = mMM.verify_list[self.channel_id][4] # new_frame = mMM.verify_list[self.channel_id][4]
# while True: # # while True:
# new_frame = mMM.verify_list[self.channel_id][4] # # new_frame = mMM.verify_list[self.channel_id][4]
# #new_frame = av.VideoFrame.from_ndarray(img, format="rgb24") # # #new_frame = av.VideoFrame.from_ndarray(img, format="rgb24")
# if new_frame is not None: # # if new_frame is not None:
# break # # break
# else: # # else:
# time.sleep(1.0 / self.frame_rate) # # time.sleep(1.0 / self.frame_rate)
# 设置时间戳和时间基数 -- 根据耗时实时计算帧率 # # 设置时间戳和时间基数 -- 根据耗时实时计算帧率
# elapsed_time = time.time() - self.start_time # # elapsed_time = time.time() - self.start_time
# self.frame_count += 1 # # self.frame_count += 1
# actual_frame_rate = self.frame_count / elapsed_time # # actual_frame_rate = self.frame_count / elapsed_time
# self.time_base = Fraction(1, int(actual_frame_rate)) # # self.time_base = Fraction(1, int(actual_frame_rate))
#设置时间戳和帧率 # #设置时间戳和帧率
self.frame_count += 1 # self.frame_count += 1
if self.frame_count > myCongif.get_data("RESET_INTERVAL"): # if self.frame_count > myCongif.get_data("RESET_INTERVAL"):
self.frame_count = 0 # self.frame_count = 0
new_frame.pts = self.frame_count # new_frame.pts = self.frame_count
new_frame.time_base = self.time_base # new_frame.time_base = self.time_base
print(f"{self.channel_id} -- Frame pts: {new_frame.pts}, time_base: {new_frame.time_base}") # #print(f"{self.channel_id} -- Frame pts: {new_frame.pts}, time_base: {new_frame.time_base}")
#
# 将检测结果图像转换为帧 # # 将检测结果图像转换为帧
# new_frame.pts = int(self.cap.get(cv2.CAP_PROP_POS_FRAMES)) # # new_frame.pts = int(self.cap.get(cv2.CAP_PROP_POS_FRAMES))
# new_frame.time_base = self.time_base # # new_frame.time_base = self.time_base
# end_time = time.time() # 结束时间 # # end_time = time.time() # 结束时间
# print(f"Processing time: {end_time - start_time} seconds") # # print(f"Processing time: {end_time - start_time} seconds")
#print("webRTC recv at:",time.strftime("%Y-%m-%d %H:%M:%S", time.localtime())) # #print("webRTC recv at:",time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()))
return new_frame # return new_frame
async def get_stats(peer_connection): async def get_stats(peer_connection):
stats = await peer_connection.getStats() stats = await peer_connection.getStats()
@ -104,89 +105,137 @@ async def get_stats(peer_connection):
print(f"Packets Sent: {report.packetsSent}") print(f"Packets Sent: {report.packetsSent}")
print(f"Bytes Sent: {report.bytesSent}") print(f"Bytes Sent: {report.bytesSent}")
@api.route('/offer', methods=['POST']) # @api.route('/offer', methods=['POST'])
async def offer(): # async def offer():
#接收客户端的连接请求 # #接收客户端的连接请求
params = await request.json # params = await request.json
channel_id = params["cid"] #需要传参过来 # channel_id = params["cid"] #需要传参过来
itype = params["mytype"] # itype = params["mytype"]
element_id = params["element_id"] # element_id = params["element_id"]
reStatus = 0 # reStatus = 0
reMsg = "" # reMsg = ""
ret = False # ret = False
if itype == 1: # if itype == 1:
#添加通道和页面控件ID的关系 # #添加通道和页面控件ID的关系
strsql = f"select * from channel where element_id = {element_id}" # strsql = f"select * from channel where element_id = {element_id}"
data = mDBM.do_select(strsql,1) # data = mDBM.do_select(strsql,1)
if data: # if data:
reStatus = 0 # reStatus = 0
reMsg = '该控件已经关联某一视频通道,请确认数据是否正确!' # reMsg = '该控件已经关联某一视频通道,请确认数据是否正确!'
return jsonify({'status': reStatus, 'msg': reMsg}) # return jsonify({'status': reStatus, 'msg': reMsg})
strsql = f"update channel set element_id = {element_id} where ID = {channel_id};" # strsql = f"update channel set element_id = {element_id} where ID = {channel_id};"
ret = mDBM.do_sql(strsql) # ret = mDBM.do_sql(strsql)
if ret == True: # if ret == True:
reStatus = 1 # reStatus = 1
reMsg = '播放画面成功,请稍等!' # reMsg = '播放画面成功,请稍等!'
else: # else:
reStatus = 0 # reStatus = 0
reMsg = '播放画面失败,请联系技术支持!' # reMsg = '播放画面失败,请联系技术支持!'
#提取客户端发来的SDP,生成服务器端的SDP # #提取客户端发来的SDP,生成服务器端的SDP
offer = RTCSessionDescription(sdp=params["sdp"], type=params["type"]) # offer = RTCSessionDescription(sdp=params["sdp"], type=params["type"])
# # 配置 STUN 服务器 # # # 配置 STUN 服务器
#ice_servers = [{"urls": []}]# 禁用 STUN 服务器 # #ice_servers = [{"urls": []}]# 禁用 STUN 服务器
# pc = RTCPeerConnection(configuration={"iceServers": ice_servers}) # # pc = RTCPeerConnection(configuration={"iceServers": ice_servers})
config = RTCConfiguration(iceServers=[]) # config = RTCConfiguration(iceServers=[])
pc = RTCPeerConnection(configuration=config) # pc = RTCPeerConnection(configuration=config)
# t = threading.Thread(target=get_stats,args=(pc,)) # # t = threading.Thread(target=get_stats,args=(pc,))
# t.start() # # t.start()
#
#pc = RTCPeerConnection() # 实例化一个rtcp对象 # #pc = RTCPeerConnection() # 实例化一个rtcp对象
pcs[channel_id] = pc # 集合中添加一个对象,若已存在则不添 # pcs[channel_id] = pc # 集合中添加一个对象,若已存在则不添
#
@pc.on("datachannel") # @pc.on("datachannel")
def on_datachannel(channel): # def on_datachannel(channel):
@channel.on("message") # @channel.on("message")
def on_message(message): # def on_message(message):
print(f'Message received: {message}') # print(f'Message received: {message}')
# 处理接收到的数据 # # 处理接收到的数据
if message == 'pause': # if message == 'pause':
# 执行暂停操作 # # 执行暂停操作
pass # pass
elif message == 'resume': # elif message == 'resume':
# 执行继续操作 # # 执行继续操作
pass # pass
#
#监听RTC连接状态 # #监听RTC连接状态
@pc.on("iconnectionstatechange") #当ice连接状态发生变化时 # @pc.on("iconnectionstatechange") #当ice连接状态发生变化时
async def iconnectionstatechange(): # async def iconnectionstatechange():
if pc.iceConnectionState == "failed": # if pc.iceConnectionState == "failed":
await pc.close() # await pc.close()
pcs.pop(channel_id, None) #移除对象 # pcs.pop(channel_id, None) #移除对象
#
# 添加视频轨道 # # 添加视频轨道
video_track = VideoTransformTrack(channel_id) # video_track = VideoTransformTrack(channel_id)
pc.addTrack(video_track) # pc.addTrack(video_track)
#
# # @pc.on('track') --- stream.getTracks().forEach(track => pc.addTrack(track, stream)); 猜测是这里触发的添加了摄像头通道
# # def on_track(track):
# # if track.kind == 'video':
# # local_video = VideoTransformTrack(track)
# # pc.addTrack(local_video)
#
# # 记录客户端 SDP
# await pc.setRemoteDescription(offer)
# # 生成本地 SDP
# answer = await pc.createAnswer()
# # 记录本地 SDP
# await pc.setLocalDescription(answer)
#
# print("返回sdp")
# return jsonify({
# "sdp": pc.localDescription.sdp,
# "type": pc.localDescription.type,
# "status":reStatus, #itype =1 的时候才有意义
# "msg":reMsg
# })
# @pc.on('track') --- stream.getTracks().forEach(track => pc.addTrack(track, stream)); 猜测是这里触发的添加了摄像头通道 async def generate_frames(stream_id):
# def on_track(track): #video_capture = streams[stream_id]
# if track.kind == 'video': start_time = time.time()
# local_video = VideoTransformTrack(track) icount = 0
# pc.addTrack(local_video) frame_rate_time = 1.0 / myCongif.get_data("frame_rate")
while True:
try:
await asyncio.sleep(frame_rate_time)
frame = mMM.verify_list[int(stream_id)][4]
if frame is not None:
img = frame.to_ndarray(format="bgr24")
ret, buffer = cv2.imencode('.jpg', img)
if not ret:
continue
frame = buffer.tobytes()
yield (b'--frame\r\n'
b'Content-Type: image/jpeg\r\n\r\n' + frame + b'\r\n')
end_time = time.time() # 结束时间
icount += 1
print(f"icount:{icount},Processing time: {end_time - start_time} seconds")
except asyncio.CancelledError:
print("Task was cancelled")
break # 处理任务取消
except Exception as e:
print(f"Error: {e}")
break # 在出现异常时退出循环
print("退出循环")
# 记录客户端 SDP @api.route('/video_feed/<stream_id>')
await pc.setRemoteDescription(offer) async def video_feed(stream_id):
# 生成本地 SDP if int(stream_id) != 2:
answer = await pc.createAnswer() return "None"
# 记录本地 SDP print("执行一次:video_feed")
await pc.setLocalDescription(answer) return Response(generate_frames(stream_id),mimetype='multipart/x-mixed-replace; boundary=frame')
print("返回sdp") @api.websocket('/ws/video_feed/<int:channel_id>')
return jsonify({ async def ws_video_feed(channel_id):
"sdp": pc.localDescription.sdp, while True:
"type": pc.localDescription.type, frame = mMM.verify_list[int(channel_id)][4]
"status":reStatus, #itype =1 的时候才有意义 if frame is not None:
"msg":reMsg img = frame.to_ndarray(format="bgr24")
}) ret, buffer = cv2.imencode('.jpg', img)
if not ret:
continue
frame = buffer.tobytes()
await websocket.send(frame)
await asyncio.sleep(1.0 / myCongif.get_data("frame_rate")) # Adjust based on frame rate
@api.route('/shutdown', methods=['POST']) @api.route('/shutdown', methods=['POST'])
async def shutdown():#这是全关 async def shutdown():#这是全关

13
web/__init__.py

@ -4,6 +4,7 @@ from pymemcache.client import base
from .main import main from .main import main
from .API import api from .API import api
from functools import wraps from functools import wraps
from myutils.ConfigManager import myCongif
from quart_sqlalchemy import SQLAlchemy from quart_sqlalchemy import SQLAlchemy
from flask_migrate import Migrate from flask_migrate import Migrate
@ -34,8 +35,10 @@ def create_app():
# app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db' # app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///app.db'
# app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False # app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False
app.config['SECRET_KEY'] = 'zfxxkj_2024_!@#' app.config['SECRET_KEY'] = 'zfxxkj_2024_!@#'
if myCongif.get_data("model_platform") == "acl":
app.config['SESSION_TYPE'] = 'memcached' # session类型 app.config['SESSION_TYPE'] = 'memcached' # session类型
elif myCongif.get_data("model_platform") =="cpu":
app.config['SESSION_TYPE'] = 'redis' # session类型
#app.config['SESSION_FILE_DIR'] = './sessions' # session保存路径 #app.config['SESSION_FILE_DIR'] = './sessions' # session保存路径
#app.config['SESSION_MEMCACHED'] = base.Client(('localhost', 11211)) #app.config['SESSION_MEMCACHED'] = base.Client(('localhost', 11211))
app.config['SESSION_PERMANENT'] = True # 如果设置为True,则关闭浏览器session就失效。 app.config['SESSION_PERMANENT'] = True # 如果设置为True,则关闭浏览器session就失效。
@ -45,12 +48,6 @@ def create_app():
app.session_interface = MemcachedSessionInterface(memcached_client) app.session_interface = MemcachedSessionInterface(memcached_client)
Session(app) Session(app)
#ORM数据库管理
# db = SQLAlchemy()
# migrate = Migrate()
# db.init_app(app)
# migrate.init_app(app, db) # 创建对象,使用它来将ORM模型映射到数据库 -- 效果是及时更新数据的修改到文档?
# 注册main # 注册main
app.register_blueprint(main) app.register_blueprint(main)
#注册API模块 #注册API模块

107
web/main/static/resources/scripts/aiortc-client-new.js

@ -20,7 +20,7 @@ document.addEventListener('DOMContentLoaded', async function() {
// console.log(`Type: ${channel.type}`); // console.log(`Type: ${channel.type}`);
// console.log(`Status: ${channel.status}`); // console.log(`Status: ${channel.status}`);
// console.log(`Element ID: ${channel.element_id}`); // console.log(`Element ID: ${channel.element_id}`);
start(channel.element_id,channel.ID,0) connectToStream(channel.element_id,channel.ID,channel.area_name,channel.channel_name)
} }
}); });
} catch (error) { } catch (error) {
@ -28,86 +28,37 @@ document.addEventListener('DOMContentLoaded', async function() {
} }
}); });
function negotiate(pc,channel_id,itype,element_id) { function connectToStream(element_id,channel_id,area_name,channel_name) {
pc.addTransceiver('video', { direction: 'recvonly' }); // const videoContainer = document.getElementById('video-container');
return pc.createOffer().then((offer) => { // const imgElement = document.createElement('img');
return pc.setLocalDescription(offer); // imgElement.id = `${element_id}`;
}).then(() => { // videoContainer.appendChild(imgElement);
// wait for ICE gathering to complete const imgElement = document.getElementById(element_id);
return new Promise((resolve) => { imgElement.alt = `Stream ${area_name}--${channel_name}`;
if (pc.iceGatheringState === 'complete') { const streamUrl = `ws://${window.location.host}/api/ws/video_feed/${channel_id}`;
resolve(); console.log(streamUrl);
} else { function connect() {
const checkState = () => { const socket = new WebSocket(streamUrl);
if (pc.iceGatheringState === 'complete') {
pc.removeEventListener('icegatheringstatechange', checkState); socket.onmessage = function(event) {
resolve(); //const blob = new Blob([event.data], { type: 'image/jpeg' });
//imgElement.src = URL.createObjectURL(blob);
// 释放旧的对象URL
if (imgElement.src) {
URL.revokeObjectURL(imgElement.src);
} }
imgElement.src = URL.createObjectURL(event.data);
}; };
pc.addEventListener('icegatheringstatechange', checkState);
}
});
}).then(() => {
var offer = pc.localDescription;
return fetch('/api/offer', {
body: JSON.stringify({
sdp: offer.sdp,
type: offer.type,
cid: channel_id,
mytype: itype,
element_id:element_id
}),
headers: {
'Content-Type': 'application/json'
},
method: 'POST'
});
}).then((response) => {
return response.json();
}).then((answer) => {
return pc.setRemoteDescription(answer);
}).catch((e) => {
alert(e);
});
}
function start(element_id,channel_id,itype) {// iytpe =0,不用修改数据库,1,需要添加数据库记录 socket.onclose = function() {
console.log(`Element ID: ${element_id}`); setTimeout(connect, 1000); // 尝试在1秒后重新连接
console.log(`Channel ID: ${channel_id}`); };
pc = new RTCPeerConnection();
pc_list[channel_id] = pc; //保留pc
pc.addEventListener('track', (evt) => {
if (evt.track.kind == 'video') {
//document.getElementById('video').srcObject = evt.streams[0];
document.getElementById(element_id).srcObject = evt.streams[0];
}
});
negotiate(pc,channel_id,itype,element_id);
}
function showView(){ socket.onerror = function() {
channel_list.forEach(channel => { console.log(`WebSocket错误,尝试重新连接... Channel ID: ${channel_id}`);
if(channel.element_id){ //""空为false,非空为true socket.close();
console.log('status:',document.getElementById(channel.element_id).paused) };
if (document.getElementById(channel.element_id).paused) {
document.getElementById(channel.element_id).play().catch(error => {
console.error('Error playing video:', error);
});
} }
}
});
}
async function closestream(channel_id){ connect();
let response = await fetch('/close_stream', { }
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ channel_id: channel_id })
});
let data = await response.json();
console.log(data);
if(pc_list[channel_id]){
pc_list[channel_id].close();
delete pc_list[channel_id];
}
}

56
web/main/templates/实时预览.html

@ -171,58 +171,86 @@
<p></p> <p></p>
</div> </div>
</div> </div>
<script src="/resources/scripts/aiortc-client-new.js"> <script src="/resources/scripts/aiortc-client-new.js">
console.log('当指定了src属性时,浏览器会加载并执行该文件中的脚本,而忽略标签内部的任何内容,所以不会执行'); console.log('当指定了src属性时,浏览器会加载并执行该文件中的脚本,而忽略标签内部的任何内容,所以不会执行');
</script> </script>
<script> <script>
window.onload = function() { window.onload = function() {
// 页面加载完成后执行的代码 // 页面加载完成后执行的代码
var intervalId = setInterval(showView, 1000); //var intervalId = setInterval(showView, 1000);
setTimeout(function() { //setTimeout(function() {
clearInterval(intervalId); // 停止定时循环 // clearInterval(intervalId); // 停止定时循环
console.log("Interval stopped after 10 seconds"); // console.log("Interval stopped after 10 seconds");
}, 10000); // }, 10000);
} }
</script> </script>
<!-- Unnamed (Image) --> <!-- Unnamed (Image) -->
<div id="u32" class="ax_default image"> <div id="u32" class="ax_default image">
<video id="u32_video" autoplay="true" playsinline="true"></video> <img id="u32_video" src="" alt="Video Stream">
</div> </div>
<script>
const videoStream = document.getElementById('u32_video');
const streamUrl = 'ws://localhost:5001/api/ws/video_feed/2';
function connect() {
const socket = new WebSocket(streamUrl);
socket.onmessage = function(event) {
// 释放旧的对象URL
if (videoStream.src) {
URL.revokeObjectURL(videoStream.src);
}
videoStream.src = URL.createObjectURL(event.data);
};
socket.onclose = function() {
setTimeout(connect, 1000); // Attempt to reconnect after 1 second
};
}
window.onload = function() {
//connect();
}
</script>
<!-- Unnamed (Image) --> <!-- Unnamed (Image) -->
<div id="u33" class="ax_default image"> <div id="u33" class="ax_default image">
<video id="u33_video" autoplay="true" playsinline="true"></video> <img id="u33_video" src="" alt="Video Stream">
</div> </div>
<!-- Unnamed (Image) --> <!-- Unnamed (Image) -->
<div id="u34" class="ax_default image"> <div id="u34" class="ax_default image">
<video id="u34_video" autoplay="true" playsinline="true"></video> <!-- <video id="u34_video" autoplay="true" playsinline="true"></video>-->
<img id="u34_video" src="" alt="Video Stream">
</div> </div>
<!-- Unnamed (Image) --> <!-- Unnamed (Image) -->
<div id="u35" class="ax_default image"> <div id="u35" class="ax_default image">
<video id="u35_video" autoplay="true" playsinline="true"></video> <!-- <video id="u35_video" autoplay="true" playsinline="true"></video>-->
<img id="u35_video" src="" alt="Video Stream">
</div> </div>
<!-- Unnamed (Image) --> <!-- Unnamed (Image) -->
<div id="u36" class="ax_default image"> <div id="u36" class="ax_default image">
<video id="u36_video" autoplay="true" playsinline="true"></video> <!-- <video id="u36_video" autoplay="true" playsinline="true"></video>-->
<img id="u36_video" src="" alt="Video Stream">
</div> </div>
<!-- Unnamed (Image) --> <!-- Unnamed (Image) -->
<div id="u37" class="ax_default image"> <div id="u37" class="ax_default image">
<video id="u37_video" autoplay="true" playsinline="true"></video> <!-- <video id="u37_video" autoplay="true" playsinline="true"></video>-->
<img id="u37_video" src="" alt="Video Stream">
</div> </div>
<!-- Unnamed (Image) --> <!-- Unnamed (Image) -->
<div id="u38" class="ax_default image"> <div id="u38" class="ax_default image">
<video id="u38_video" autoplay="true" playsinline="true"></video> <!-- <video id="u38_video" autoplay="true" playsinline="true"></video>-->
<img id="u38_video" src="" alt="Video Stream">
</div> </div>
<!-- Unnamed (Image) --> <!-- Unnamed (Image) -->
<div id="u39" class="ax_default image"> <div id="u39" class="ax_default image">
<video id="u39_video" autoplay="true" playsinline="true"></video> <!-- <video id="u39_video" autoplay="true" playsinline="true"></video>-->
<img id="u39_video" src="" alt="Video Stream">
</div> </div>
<!-- Unnamed (Image) --> <!-- Unnamed (Image) -->

BIN
zfbox.db

Binary file not shown.
Loading…
Cancel
Save